前回の記事でChisel BootcampのModule2.2の組み合わせ回路の勉強が終わった。
今日からは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位なのだが、今日はここまで。