ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chisel Bootcamp - Module2.1 (2) - 最初のジェネレータ

スポンサーリンク

前回の記事でChisel BootcampのModule2.1の最初のモジュールの作成が終わった。

www.tech-diningyo.info

今回はModule2.1の最初のモジュールの2つ目の例からスタートしていく。いよいよChiselで作るジェネレータに関しての話題だ。

Module 2.1: 最初のChiselモジュール

例題:モジュール・ジェネレータ

”ジェネレータ”となんだか大層な名前も付いているが、やるのは至ってシンプルなので、早速例を見てみよう。

// Chisel Code, but pass in a parameter to set widths of ports
class PassthroughGenerator(width: Int) extends Module { 
  val io = IO(new Bundle {
    val in = Input(UInt(width.W))
    val out = Output(UInt(width.W))
  })
  io.out := io.in
}

// Let's now generate modules with different widths
println(getVerilog(new PassthroughGenerator(10)))
println(getVerilog(new PassthroughGenerator(20)))

先ほどの”最初のモジュール”との違いは以下になる。

  • モジュール名:PassthroughPassthroughGenerator
  • クラスのパラメータを追加:(width: Int)
  • 追加したパラメータをInput/Outputに指定

これだけの修正だがパラメータを指定できるようになったことによって、このモジュールの入力/出力のビット幅をインスタンス時に可変に出来るようになった。

実際にgetVerilogでエラボレーション後に出力されたものを見てみると以下のようになる。

[info] [0.001] Elaborating design...
[info] [0.709] Done elaborating.
Total FIRRTL Compile Time: 242.0 ms
module cmd3HelperPassthroughGenerator( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [9:0] io_in, // @[:@6.4]
  output [9:0] io_out // @[:@6.4]
);
  assign io_out = io_in;
endmodule

[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 14.8 ms
module cmd3HelperPassthroughGenerator( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [19:0] io_in, // @[:@6.4]
  output [19:0] io_out // @[:@6.4]
);
  assign io_out = io_in;
endmodule

見ての通り、ひとつ目のgetVerilogではビット幅を”10”に指定したため、Verilogのコード上のio_in/io_outのビット幅が10-bitになっている。同様に2つ目は20-bitになっていることがわかる。

この例だけだとVerilog-2001のgenerate構文でも同じようなことは出来るのだが、Chiselの場合はScalaの構文をフルに使えるのでこちらのほうが全然自由度は高そう。

Bootcampの説明から大事そうな部分をピックアップしておく。一部、上記の記述と被るが以下のようになる。

  • 生成されたVerilogコードの入力/出力がwidthに与えた値になる
  • このパラメタライズの処理はChiselの機能ではなく、ChiselのモジュールがScalaのクラスで実装されていることよるものである
  • Chiselとしてはパラメタライズに関する特別なAPIを持っているわけではないが、設計者はScalaのパラメタライズ機能を簡単に利用できる
  • PassthroughGeneratorはもはや単一のモジュールではなく、widthによってパラメタライズされたモジュールであり、このようなモジュールをジェネレータと呼ぶ。

ハードウェアのテスト

まずはBootcampの本文を引用させていただく。

ハードウェアのモジュールやジェネレータはテスター無しでは完成すべきでない。Chiselはビルトインのテスト機能を持っており、このブートキャンプを通してそれらについては学んでいくことになる。次のコードはChiselのテストハーネスを使ったテストの例で、Passthroughインスタンスの入力ポートinに入れたものと同じ値が、出力ポートoutに出てくるかをチェックしている。

例題:テスター

// Scala Code: Calling Driver to instantiate Passthrough + PeekPokeTester and execute the test.
// Don't worry about understanding this code; it is very complicated Scala.
// Think of it more as boilerplate to run a Chisel test.
val testResult = Driver(() => new Passthrough()) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0)     // Set our input to value 0
    expect(c.io.out, 0)  // Assert that the output correctly has 0
    poke(c.io.in, 1)     // Set our input to value 1
    expect(c.io.out, 1)  // Assert that the output correctly has 1
    poke(c.io.in, 2)     // Set our input to value 2
    expect(c.io.out, 2)  // Assert that the output correctly has 2
  }
}
assert(testResult)   // Scala Code: if testResult == false, will throw an error
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!

上記の例には色々な”見知らぬ”Scalaのコードが出てきているが、"poke/expect以外はテンプレートみたいなものと思って置いといて"ってことなので、こちらでも特に触れないでおく。

上記のコードを実行すると以下のような出力を得られる。

[info] [0.000] Elaborating design...
[info] [0.100] Done elaborating.
Total FIRRTL Compile Time: 10.1 ms
Total FIRRTL Compile Time: 5.3 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1539499360981
test cmd2HelperPassthrough Success: 3 tests passed in 5 cycles taking 0.012104 seconds
[info] [0.004] RAN 0 CYCLES PASSED
SUCCESS!!

上記コードでテストに使われているものをまとめると以下のようになる。

  • poke : 入力に値をセットする
  • expect : 出力の値をチェックする
  • peek : 出力の値の比較がいらない場合にexpectの代わりに使用する

PeekPokeTesterのブロック式内のコードのexpectが全てtrueだった場合に、testResultにtrueが設定され、その結果assert(testResult)が発火せずに最後の"SUCESS!!"の表示に辿り着く。

エクササイズ

本モジュールには練習問題が付いていてそれは以下のようなものだ。

PassthroughGeneratorに対して以下の2つのテストを実装せよ。

  1. bit幅が10-bitのケース
  2. bit幅が20-bitのケース

テストの中身としては最低限以下の2つの値に対してのチェックを実行すること

  1. 0の場合
  2. 特定のbit幅における最大値

なお、下記コード中の???Scalaでは特別な意味を持っていて、この後のBootcampにおいても度々見かけることになる。この???を評価するとScalaNotImplementErrorを返すので、この部分を自分で実装したテストに置き換えて実行せよ。

val test10result = ???

val test20result = ???

assert((test10result == true) && (test20result == true))
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!

なるほど、???にはそんな意味が。。。 ということでとりあえずそのまま実行してみる。

scala.NotImplementedError: an implementation is missing
  scala.Predef$.$qmark$qmark$qmark(Predef.scala:230)
  ammonite.$sess.cmd12$Helper.<init>(cmd12.sc:1)
  ammonite.$sess.cmd12$.<init>(cmd12.sc:7)
  ammonite.$sess.cmd12$.<clinit>(cmd12.sc:-1)

ほんとだ、エラー出た。

以下は自分が試した解答。見たくない場合は展開しないようにご注意を。

解答

val test10result = Driver(() => new PassthroughGenerator(10)) {
    c => new PeekPokeTester(c) {
        poke(c.io.in, 0)
        expect(c.io.out, 0)
        poke(c.io.in, (1 << 10) - 1)
        expect(c.io.out, (1 << 10) - 1)
    }
}

val test20result = Driver(() => new PassthroughGenerator(20)) {
    c => new PeekPokeTester(c) {
        poke(c.io.in, 0)
        expect(c.io.out, 0)
        poke(c.io.in, (1 << 20) - 1)
        expect(c.io.out, (1 << 20) -1)
    }
}

assert((test10result == true) && (test20result == true))
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
[info] [0.000] Elaborating design...
[info] [0.003] Done elaborating.
Total FIRRTL Compile Time: 7.4 ms
Total FIRRTL Compile Time: 7.3 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539501141285
test cmd3HelperPassthroughGenerator Success: 2 tests passed in 5 cycles taking 0.002805 seconds
[info] [0.002] RAN 0 CYCLES PASSED
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 11.0 ms
Total FIRRTL Compile Time: 10.8 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539501141319
test cmd3HelperPassthroughGenerator Success: 2 tests passed in 5 cycles taking 0.002964 seconds
[info] [0.002] RAN 0 CYCLES PASSED
SUCCESS!!

これでModule2.1の中身はだいたい終わりなのだが、残りの内容としてデバッグの際に”何をどのように確認するか”という話が残っている。

これについては次の記事で見ることにして、今日はここまで。