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

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

Chisel Bootcamp - Module2.3 (1) - フロー制御(when/elsewhen/otherwise)

スポンサーリンク

前回の記事でChisel BootcampのModule2.2の組み合わせ回路の勉強が終わった。

www.tech-diningyo.info

今日からはModule2.3に入っていく。

タイトルは「フロー制御」だ。

Module 2.3: フロー制御

モチベーション

これまでと同様にモチベーションから見ていく。前回同様に引用させてもらって訳したもの見ていく。

ここからはChiselにおいてソフトとハードが強く対応を持っていく。フローを制御していくと、考えられる2つの方法の間で大きく発散することがあるだろう。このモジュールではジェネレータ・ソフトウェアとハードウェアにおけるフロー制御について紹介する。Chiselの信号を再接続した場合、何が起きるだろう?どのようにすれば2つ以上の入力をMUX出来るのだろう?これらの問への答えはこのモジュールを終えた時に、あなたの中にあるだろう。

最後に接続することが意味すること

例題:再アサイ

前回にも紹介されていたがChiselにおいては:=を使用するとコンポーネントを接続できる。いくつかの理由から同一のコンポーネントの中で複数の文で接続を行うことが出来る。

もしこれが起きた際には最後の文が採用される

要は同一の信号に複数の信号を:=で接続するとどうなるか、、ということだ。例で実際に見てみよう。

class LastConnect extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(4.W))
    val out = Output(UInt(4.W))
  })
  io.out := 1.U
  io.out := 2.U
  io.out := 3.U
  io.out := 4.U
}

// Chisel Code: Declare a new tester for modules
class LastConnectTester(c: LastConnect) extends PeekPokeTester(c) {
  expect(c.io.out, 4)  // Assert that the output correctly has 4
}

//  Test LastConnect
val works = Driver(() => new LastConnect) {
  c => new LastConnectTester(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 LastConnect))

上記の例にあるとおりモジュールLastConnectにおいて出力であるio.outに1〜4が接続されている。 これを実行すると以下の結果が得られる。

  • 実行結果
[info] [0.000] Elaborating design...
[info] [0.009] Done elaborating.
Total FIRRTL Compile Time: 6.3 ms
Total FIRRTL Compile Time: 7.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539960387411
test cmd4HelperLastConnect Success: 1 tests passed in 5 cycles taking 0.003091 seconds
[info] [0.003] RAN 0 CYCLES PASSED
SUCCESS!!
[info] [0.000] Elaborating design...
[info] [0.006] Done elaborating.
Total FIRRTL Compile Time: 15.0 ms
module cmd4HelperLastConnect( // @[:@3.2]
  input        clock, // @[:@4.4]
  input        reset, // @[:@5.4]
  input  [3:0] io_in, // @[:@6.4]
  output [3:0] io_out // @[:@6.4]
);
  assign io_out = 4'h4;
endmodule

When, elsewhen, otherwise

この章のタイトルがChiselの主要な条件分岐論理を生成する文法になる。すなわち以下の3つだ

  • when
  • elsewhen
  • otherwise

これはVerilogでいうif ~ else if ~ elseに相当する概念だ。

これを使ってChiselにおける条件分岐の処理を書くと、以下のような形が一般的な形となる。

when(someBooleanCondition) {
  // someBooleanCondition == trueの場合の処理
}.elsewhen(someOtherBooleanCondition) {
  // someBooleanOtherCondition == trueの場合の処理
}.otherwise {
  // それ以外
}

書いたとおりまんま、if文。

大事な点が一つあって、when/elsewhen/otherwiseはChiselの文法になるため、Scalaの文法にある”式"が持つ”式の終わりに何か値を返す"という処理が行われない。

なので、以下のような3項演算子的な書き方は出来そうな気がするが、実際には動作しない。

val result = when(squareIt) { x * x }.otherwise { x }

例題:Chiselの条件分岐

早速、上記で見たwhen/elsewhen/otherwiseを使ってみよ

例題は3つの入力から最大値を返却するモジュールだ。

// Max3 returns the max of its 3 arguments
class Max3 extends Module {
  val io = IO(new Bundle {
    val in1 = Input(UInt(16.W))
    val in2 = Input(UInt(16.W))
    val in3 = Input(UInt(16.W))
    val out = Output(UInt(16.W))
  })
    
  when(io.in1 > io.in2 && io.in1 > io.in3) {
    io.out := io.in1  
  }.elsewhen(io.in2 > io.in1 && io.in2 > io.in3) {
    io.out := io.in2 
  }.otherwise {
    io.out := io.in3
  }
}

// verify that the max of the three inputs is correct
class Max3Tester(c: Max3) extends PeekPokeTester(c) {
  poke(c.io.in1, 6)
  poke(c.io.in2, 4)  
  poke(c.io.in3, 2)  
  expect(c.io.out, 6)  // input 1 should be biggest
  poke(c.io.in2, 7)  
  expect(c.io.out, 7)  // now input 2 is
  poke(c.io.in3, 11)  
  expect(c.io.out, 11) // and now input 3
  poke(c.io.in3, 3)  
  expect(c.io.out, 7)  // show that decreasing an input works as well
}

// Test Max3
val works = Driver(() => new Max3) {
  c => new Max3Tester(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 Max3))

特に難しいことはなく、when/elsewhen/otherwiseを使って最大値を探索しているだけである。

  • 実行結果
[info] [0.000] Elaborating design...
[info] [0.090] Done elaborating.
Total FIRRTL Compile Time: 31.4 ms
Total FIRRTL Compile Time: 19.1 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1539959385770
test cmd3HelperMax3 Success: 4 tests passed in 5 cycles taking 0.014607 seconds
[info] [0.009] RAN 0 CYCLES PASSED
SUCCESS!!
[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 37.2 ms
module cmd3HelperMax3( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.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_out // @[:@6.4]
);
  wire  _T_13; // @[cmd3.sc 9:15:@8.4]
  wire  _T_14; // @[cmd3.sc 9:34:@9.4]
  wire  _T_15; // @[cmd3.sc 9:24:@10.4]
  wire  _T_16; // @[cmd3.sc 11:21:@15.6]
  wire  _T_17; // @[cmd3.sc 11:40:@16.6]
  wire  _T_18; // @[cmd3.sc 11:30:@17.6]
  wire [15:0] _GEN_0; // @[cmd3.sc 11:50:@18.6]
  wire [15:0] _GEN_1; // @[cmd3.sc 9:44:@11.4]
  assign _T_13 = io_in1 > io_in2; // @[cmd3.sc 9:15:@8.4]
  assign _T_14 = io_in1 > io_in3; // @[cmd3.sc 9:34:@9.4]
  assign _T_15 = _T_13 & _T_14; // @[cmd3.sc 9:24:@10.4]
  assign _T_16 = io_in2 > io_in1; // @[cmd3.sc 11:21:@15.6]
  assign _T_17 = io_in2 > io_in3; // @[cmd3.sc 11:40:@16.6]
  assign _T_18 = _T_16 & _T_17; // @[cmd3.sc 11:30:@17.6]
  assign _GEN_0 = _T_18 ? io_in2 : io_in3; // @[cmd3.sc 11:50:@18.6]
  assign _GEN_1 = _T_15 ? io_in1 : _GEN_0; // @[cmd3.sc 9:44:@11.4]
  assign io_out = _GEN_1;
endmodule

生成されたVerilogはこれまでよりは信号が増えているが、よく見るとwhen/elsewhenの各条件式がそれぞれ_T_13~_T_18に割り当てられており、それを使って出力を選択しているのがわかると思う。

Module2.3はまだ1/3位なのだが、今日はここまで。