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

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

Chisel Bootcamp - Module3.1(2) - パラメータを使った論理の変更

スポンサーリンク

前回の記事ではChisel BootcampのModule3.1に入り、パラメタライズとエラボレーション時のアサーションであるrequireについて勉強した。

www.tech-diningyo.info

今日も引き続きModule3.1を見ていく。今日はModule2.3で扱ったsortモジュール、再びだ!!

Module 3.1: ジェネレータ:パラメータ

パラメタライズされたモジュールによるソート

ここでは、Module2.3で扱ったソートモジュールについてを再度取り上げる。パラメタライズについての話題なのでジェネレータを扱うことになる。

前回の記事の加算器との違いは、パラメタライズされる対象がI/Oではなく、内部の実装をパラメータによって変更することにある。

パラメタライズされた4入力のソートモジュール

Module2.3で以下の画像のソートを行うモジュールを作成した。

f:id:diningyo-kpuku-jougeki:20181020233516p:plain

その時のモジュールのコードは以下のようなものだった。(長いので一部のみ掲載)

/** 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を見ていくが、パラメタライズのバリエーションとしてデフォルト値やオプションについてを扱うことになるようだ。