前回の記事ではChisel BootcampのModule3.1に入り、パラメタライズとエラボレーション時のアサーションであるrequire
について勉強した。
今日も引き続きModule3.1を見ていく。今日はModule2.3で扱ったsortモジュール、再びだ!!
Module 3.1: ジェネレータ:パラメータ
パラメタライズされたモジュールによるソート
ここでは、Module2.3で扱ったソートモジュールについてを再度取り上げる。パラメタライズについての話題なのでジェネレータを扱うことになる。
前回の記事の加算器との違いは、パラメタライズされる対象がI/Oではなく、内部の実装をパラメータによって変更することにある。
パラメタライズされた4入力のソートモジュール
Module2.3で以下の画像のソートを行うモジュールを作成した。
その時のモジュールのコードは以下のようなものだった。(長いので一部のみ掲載)
/** 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(row22 < row23) { io.out2 := row22 // preserve first two elements io.out3 := row23 }.otherwise { io.out2 := row23 // swap first two elements io.out3 := row22 } }
今回の例として紹介されているのは以下のChiselコードだ。
class Sort4(ascending: Boolean) 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)) }) // 以下の比較関数ではモジュールのパラメータによって"<"と">"を使用するかを決定する def comp(l: UInt, r: UInt): Bool = { if (ascending) { l < r } else { l > r } } 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(comp(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(comp(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(comp(row11, row12)) { row21 := row11 // preserve middle 2 elements row22 := row12 }.otherwise { row21 := row12 // swap middle two elements row22 := row11 } val row31 = Wire(UInt(16.W)) val row32 = Wire(UInt(16.W)) when(comp(row10, row13)) { row31 := row10 // preserve middle 2 elements row32 := row13 }.otherwise { row31 := row13 // swap middle two elements row32 := row10 } when(comp(row10, row21)) { io.out0 := row31 // preserve first two elements io.out1 := row21 }.otherwise { io.out0 := row21 // swap first two elements io.out1 := row31 } when(comp(row22, row13)) { io.out2 := row22 // preserve first two elements io.out3 := row32 }.otherwise { io.out2 := row32 // swap first two elements io.out3 := row22 } }
変更点としては以下の2つ
- Scalaの関数
comp
が実装された when
での条件分岐にcomp
を使った比較が行われている
このケースのように、when
の部分にScalaの関数を渡せるので、それを使うことで実際に生成されるハードウェアをパラメータによって切り替えることが出来るということになる。
テスト
このSort4
にはパラメータascending
に応じたテストが準備されている。
// verify the inputs are sorted class Sort4AscendingTester(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) // ~あと2つのテストが存在しているが省略~ } class Sort4DescendingTester(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, 12) expect(c.io.out1, 9) expect(c.io.out2, 6) expect(c.io.out3, 3) // ~あと2つのテストが存在しているが省略~ } // Here are the testers val worksAscending = iotesters.Driver(() => new Sort4(true)) { c => new Sort4AscendingTester(c) } val worksDescending = iotesters.Driver(() => new Sort4(false)) { c => new Sort4DescendingTester(c) } assert(worksAscending && worksDescending) // Scala Code: if works == false, will throw an error println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
ポイントはSort4AscendingTester
/Sort4DescendingTester
でテスト入力としてpoke
で与える入力値は一緒だが、expect
の期待値が昇順/降順で異なっている。
生成されるRTL
一応、生成されたRTLを確認してみよう。
今回注目すべきポイント以外の要素、例えばwire
宣言などについては記載を省略している。実際には文法的にも正しいRTLが生成されている。
ascending == true
の場合
module cmd4HelperSort4( // @[:@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_xx で使用されている比較演算子が"<"になっている assign _T_25 = io_in0 < io_in1; // @[cmd4.sc 16:11:@12.4] assign row10 = _T_25 ? io_in0 : io_in1; // @[cmd4.sc 27:30:@13.4] assign row11 = _T_25 ? io_in1 : io_in0; // @[cmd4.sc 27:30:@13.4] assign _T_26 = io_in2 < io_in3; // @[cmd4.sc 16:11:@21.4] assign row12 = _T_26 ? io_in2 : io_in3; // @[cmd4.sc 35:30:@22.4] assign row13 = _T_26 ? io_in3 : io_in2; // @[cmd4.sc 35:30:@22.4] assign _T_29 = row11 < row12; // @[cmd4.sc 16:11:@32.4] assign row21 = _T_29 ? row11 : row12; // @[cmd4.sc 46:28:@33.4] assign row22 = _T_29 ? row12 : row11; // @[cmd4.sc 46:28:@33.4] assign _T_32 = row10 < row13; // @[cmd4.sc 16:11:@43.4] assign row31 = _T_32 ? row10 : row13; // @[cmd4.sc 56:28:@44.4] assign row32 = _T_32 ? row13 : row10; // @[cmd4.sc 56:28:@44.4] assign _T_33 = row10 < row21; // @[cmd4.sc 16:11:@52.4] assign _GEN_8 = _T_33 ? row31 : row21; // @[cmd4.sc 64:28:@53.4] assign _GEN_9 = _T_33 ? row21 : row31; // @[cmd4.sc 64:28:@53.4] assign _T_34 = row22 < row13; // @[cmd4.sc 16:11:@61.4] assign _GEN_10 = _T_34 ? row22 : row32; // @[cmd4.sc 72:28:@62.4] assign _GEN_11 = _T_34 ? row32 : row22; // @[cmd4.sc 72:28:@62.4] assign io_out0 = _GEN_8; assign io_out1 = _GEN_9; assign io_out2 = _GEN_10; assign io_out3 = _GEN_11; endmodule
ascending == false
の場合
module cmd4HelperSort4( // @[:@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_xx で使用されている比較演算子が">"になっている assign _T_25 = io_in0 > io_in1; // @[cmd4.sc 18:11:@12.4] assign row10 = _T_25 ? io_in0 : io_in1; // @[cmd4.sc 27:30:@13.4] assign row11 = _T_25 ? io_in1 : io_in0; // @[cmd4.sc 27:30:@13.4] assign _T_26 = io_in2 > io_in3; // @[cmd4.sc 18:11:@21.4] assign row12 = _T_26 ? io_in2 : io_in3; // @[cmd4.sc 35:30:@22.4] assign row13 = _T_26 ? io_in3 : io_in2; // @[cmd4.sc 35:30:@22.4] assign _T_29 = row11 > row12; // @[cmd4.sc 18:11:@32.4] assign row21 = _T_29 ? row11 : row12; // @[cmd4.sc 46:28:@33.4] assign row22 = _T_29 ? row12 : row11; // @[cmd4.sc 46:28:@33.4] assign _T_32 = row10 > row13; // @[cmd4.sc 18:11:@43.4] assign row31 = _T_32 ? row10 : row13; // @[cmd4.sc 56:28:@44.4] assign row32 = _T_32 ? row13 : row10; // @[cmd4.sc 56:28:@44.4] assign _T_33 = row10 > row21; // @[cmd4.sc 18:11:@52.4] assign _GEN_8 = _T_33 ? row31 : row21; // @[cmd4.sc 64:28:@53.4] assign _GEN_9 = _T_33 ? row21 : row31; // @[cmd4.sc 64:28:@53.4] assign _T_34 = row22 > row13; // @[cmd4.sc 18:11:@61.4] assign _GEN_10 = _T_34 ? row22 : row32; // @[cmd4.sc 72:28:@62.4] assign _GEN_11 = _T_34 ? row32 : row22; // @[cmd4.sc 72:28:@62.4] assign io_out0 = _GEN_8; assign io_out1 = _GEN_9; assign io_out2 = _GEN_10; assign io_out3 = _GEN_11; endmodule
テストも動作しているので、問題ないのはわかってはいたが、上記の通り生成されたRTLについてもcomp
関数を使ってパラメタライズした部分については、パラメータascending
の値に応じて">"と"<"が異なる記述になっていることが確認できた。
今回はここまでとして次回も引き続きModule3.1を見ていくが、パラメタライズのバリエーションとしてデフォルト値やオプションについてを扱うことになるようだ。