前回の記事でChisel BootcampのModule2.3に入りChiselのwhen
/elsewhen
/otherwise
について勉強した。
今日からは引き続きModule2.3を進めていくが、今日扱うのChiselのWire
についてだ。
Module 2.3: フロー制御
Wire
宣言
前回の記事で扱ったwhen
は”評価時に値を返さない”と書いた。今回出てくるChiselのWire
はこの問題に対しての方法の一つとして存在している。Wire
は:=
を使った接続の際の左辺/右辺のどちらに使ってもよいものとして定義されている。
という風に書いてあるのだが、いまいちピンとこないのでとりあえず早速例を見ていこう
Wire
を使った4つの入力のソート
作るものは簡単で4つの入力をソートするモジュールになる。とても丁寧に図が付いているのでそれも使わせていただいた。以下のように4つの入力が出力される際にソートされるように並べ替える必要がある。なお図中の線の色は以下の意味を持っている。
- 赤の線:左の値が小さい
- 黒の線:左の値が大きいので、値がスワップされる
これをChiselで実装すると以下のようになる。
/** Sort4 sorts its 4 inputs to its 4 outputs */ class Sort4 extends Module { val io = IO(new Bundle { val in0 = Input(UInt(16.W)) val in1 = Input(UInt(16.W)) val in2 = Input(UInt(16.W)) val in3 = Input(UInt(16.W)) val out0 = Output(UInt(16.W)) val out1 = Output(UInt(16.W)) val out2 = Output(UInt(16.W)) val out3 = Output(UInt(16.W)) }) val row10 = Wire(UInt(16.W)) val row11 = Wire(UInt(16.W)) val row12 = Wire(UInt(16.W)) val row13 = Wire(UInt(16.W)) when(io.in0 < io.in1) { row10 := io.in0 // preserve first two elements row11 := io.in1 }.otherwise { row10 := io.in1 // swap first two elements row11 := io.in0 } when(io.in2 < io.in3) { row12 := io.in2 // preserve last two elements row13 := io.in3 }.otherwise { row12 := io.in3 // swap last two elements row13 := io.in2 } val row21 = Wire(UInt(16.W)) val row22 = Wire(UInt(16.W)) when(row11 < row12) { row21 := row11 // preserve middle 2 elements row22 := row12 }.otherwise { row21 := row12 // swap middle two elements row22 := row11 } val row20 = Wire(UInt(16.W)) val row23 = Wire(UInt(16.W)) when(row10 < row13) { row20 := row10 // preserve middle 2 elements row23 := row13 }.otherwise { row20 := row13 // swap middle two elements row23 := row10 } when(row20 < row21) { io.out0 := row20 // preserve first two elements io.out1 := row21 }.otherwise { io.out0 := row21 // swap first two elements io.out1 := row20 } when(row22 < row23) { io.out2 := row22 // preserve first two elements io.out3 := row23 }.otherwise { io.out2 := row23 // swap first two elements io.out3 := row22 } } // verify the inputs are sorted class Sort4Tester(c: Sort4) extends PeekPokeTester(c) { poke(c.io.in0, 3) poke(c.io.in1, 6) poke(c.io.in2, 9) poke(c.io.in3, 12) expect(c.io.out0, 3) expect(c.io.out1, 6) expect(c.io.out2, 9) expect(c.io.out3, 12) poke(c.io.in0, 13) poke(c.io.in1, 4) poke(c.io.in2, 6) poke(c.io.in3, 1) expect(c.io.out0, 1) expect(c.io.out1, 4) expect(c.io.out2, 6) expect(c.io.out3, 13) poke(c.io.in0, 13) poke(c.io.in1, 6) poke(c.io.in2, 4) poke(c.io.in3, 1) expect(c.io.out0, 1) expect(c.io.out1, 4) expect(c.io.out2, 6) expect(c.io.out3, 13) } // Here's the tester val works = iotesters.Driver(() => new Sort4) { c => new Sort4Tester(c) } assert(works) // Scala Code: if works == false, will throw an error println("SUCCESS!!") // Scala Code: if we get here, our tests passed! println(getVerilog(new Sort4))
そうか、これまで使ってたのはScalaのval
だった。。
だから、初期化は出来ても代入が出来ないので今回みたいな処理が出来ないんだ。ピンとこないと書いたけどやっと理解できた。
上記のコードを実行すると以下のような結果が得られる。
[info] [0.000] Elaborating design... [info] [0.018] Done elaborating. Total FIRRTL Compile Time: 39.5 ms Total FIRRTL Compile Time: 24.8 ms End of dependency graph Circuit state created [info] [0.000] SEED 1539525355736 test cmd6HelperSort4 Success: 12 tests passed in 5 cycles taking 0.012863 seconds [info] [0.007] RAN 0 CYCLES PASSED SUCCESS!! [info] [0.000] Elaborating design... [info] [0.009] Done elaborating. Total FIRRTL Compile Time: 44.9 ms module cmd6HelperSort4( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [15:0] io_in0, // @[:@6.4] input [15:0] io_in1, // @[:@6.4] input [15:0] io_in2, // @[:@6.4] input [15:0] io_in3, // @[:@6.4] output [15:0] io_out0, // @[:@6.4] output [15:0] io_out1, // @[:@6.4] output [15:0] io_out2, // @[:@6.4] output [15:0] io_out3 // @[:@6.4] ); wire _T_25; // @[cmd6.sc 18:15:@12.4] wire [15:0] row10; // @[cmd6.sc 18:25:@13.4] wire [15:0] row11; // @[cmd6.sc 18:25:@13.4] wire _T_26; // @[cmd6.sc 26:15:@21.4] wire [15:0] row12; // @[cmd6.sc 26:25:@22.4] wire [15:0] row13; // @[cmd6.sc 26:25:@22.4] wire _T_29; // @[cmd6.sc 37:14:@32.4] wire [15:0] row21; // @[cmd6.sc 37:23:@33.4] wire [15:0] row22; // @[cmd6.sc 37:23:@33.4] wire _T_32; // @[cmd6.sc 47:14:@43.4] wire [15:0] row20; // @[cmd6.sc 47:23:@44.4] wire [15:0] row23; // @[cmd6.sc 47:23:@44.4] wire _T_33; // @[cmd6.sc 55:14:@52.4] wire [15:0] _GEN_8; // @[cmd6.sc 55:23:@53.4] wire [15:0] _GEN_9; // @[cmd6.sc 55:23:@53.4] wire _T_34; // @[cmd6.sc 63:14:@61.4] wire [15:0] _GEN_10; // @[cmd6.sc 63:23:@62.4] wire [15:0] _GEN_11; // @[cmd6.sc 63:23:@62.4] assign _T_25 = io_in0 < io_in1; // @[cmd6.sc 18:15:@12.4] assign row10 = _T_25 ? io_in0 : io_in1; // @[cmd6.sc 18:25:@13.4] assign row11 = _T_25 ? io_in1 : io_in0; // @[cmd6.sc 18:25:@13.4] assign _T_26 = io_in2 < io_in3; // @[cmd6.sc 26:15:@21.4] assign row12 = _T_26 ? io_in2 : io_in3; // @[cmd6.sc 26:25:@22.4] assign row13 = _T_26 ? io_in3 : io_in2; // @[cmd6.sc 26:25:@22.4] assign _T_29 = row11 < row12; // @[cmd6.sc 37:14:@32.4] assign row21 = _T_29 ? row11 : row12; // @[cmd6.sc 37:23:@33.4] assign row22 = _T_29 ? row12 : row11; // @[cmd6.sc 37:23:@33.4] assign _T_32 = row10 < row13; // @[cmd6.sc 47:14:@43.4] assign row20 = _T_32 ? row10 : row13; // @[cmd6.sc 47:23:@44.4] assign row23 = _T_32 ? row13 : row10; // @[cmd6.sc 47:23:@44.4] assign _T_33 = row20 < row21; // @[cmd6.sc 55:14:@52.4] assign _GEN_8 = _T_33 ? row20 : row21; // @[cmd6.sc 55:23:@53.4] assign _GEN_9 = _T_33 ? row21 : row20; // @[cmd6.sc 55:23:@53.4] assign _T_34 = row22 < row23; // @[cmd6.sc 63:14:@61.4] assign _GEN_10 = _T_34 ? row22 : row23; // @[cmd6.sc 63:23:@62.4] assign _GEN_11 = _T_34 ? row23 : row22; // @[cmd6.sc 63:23:@62.4] assign io_out0 = _GEN_8; assign io_out1 = _GEN_9; assign io_out2 = _GEN_10; assign io_out3 = _GEN_11; endmodule
Verilogで考えると
ってくらいの認識でとりあえず大丈夫そう。
この辺はどっかのタイミングでVerilog→Chiselの比較表みたいのを作ってみて把握するのが良いかな。
テストモジュールの書き換え
このWire
のトピックの最後は”この4入力のソートモジュールのテストに使ったテスターはもっといい感じに書き換えれるよ”という話題で締めくくられている。
その”いい感じ”のテスターを作るにはScalaのList
を使って以下のように書くそうだ。
// verify the all possible ordering of 4 numbers are sorted class BetterSort4Tester(c: Sort4) extends PeekPokeTester(c) { List(1, 2, 3, 4).permutations.foreach { case i0 :: i1 :: i2 :: i3 :: Nil => println(s"Sorting $i0 $i1 $i2 $i3") poke(c.io.in0, i0) poke(c.io.in1, i1) poke(c.io.in2, i2) poke(c.io.in3, i3) expect(c.io.out0, 1) expect(c.io.out1, 2) expect(c.io.out2, 3) expect(c.io.out3, 4) } } // Here's the tester val works = iotesters.Driver(() => new Sort4) { c => new BetterSort4Tester(c) } assert(works) // Scala Code: if works == false, will throw an error println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
確かに、、、だいぶシンプルになっている。流れとしては、以下の感じ。
- 1 - 4までを要素に持つ
List
を作成 List
のpermutations
メソッド使ってList
の要素を元に順列作ってIterator
を返却permutations
メソッドの詳細については以下のリンクを参照- https://www.scala-lang.org/api/2.12.3/scala/collection/immutable/List.html#permutations:Iterator[Repr]
- 2.の
Iterator
がforeach
にわたって、List
のデータがi0
-i3
にマッピングされる i0
-i3
をpoke
に渡してテスト実行- モジュールの機能がソートなので、テストは固定値でOK
こういうテストの作り方が出来ると、総当りのテストとかも簡単に作れるのでいいですな。総当りじゃなくても、テストの入力データ&期待値を作っといてそれをループで処理することも出来るし。
Module2.3の学習自体はこれで終わりで、残りは練習問題となる。それは次の記事で行うことにして、今日はここまで。