前回の記事ではChisel BootcampのModule2.1の学習を終えた。
今回はModule2.2に入って学習を続けていく。内容は『組み合わせ回路』だ。
Module 2.2: 組み合わせ回路
モチベーション
Module2.1の時と同じく、まずはモチベーションから見ていく。前回同様、これについては丸々引用させてもらうことにして訳したものを掲載する。
このセクションではChiselの要素を使ってどのように組み合わせ回路を実装するかについて見ていく。以下に示すChiselの基本的な3つの型がどのように接続され、動作するのかについて詳しく説明していく。
UInt
: unsigned integer (符号なし整数型)SInt
: signed integer(符号付き整数型)Bool
: true or false (ブーリアン型)Chiselの全ての変数はScalaの
val
として宣言されることを知っておいてほしい。ハードウェア自体は一度定義されて以降決して変化しないので、構築時に決してScalaのvar
を使わないこと。それらの値が変化するのはハードウェアが動作している時だけである。Wireはパラメタライズされた型としての使用は許可される。
次はModule2.1と同じくChiselのセットアップになるのだが、同様のコードを実行するだけなので割愛。
共通の演算子
まずは空のModule
を作ってどのようにそれが構築されるかを見ていく。
class MyModule extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) }
上記とまとめると以下のようになる。
- Chiselの
Module
を継承したクラスを作ると、それはVerilogのModule
に対応したものにマッピングされる MyModule
は一つの入力/出力を持っていて、bit幅は4-bitのUInt
になる
例題:ScalaとChiselの演算子で一緒に見えるもの
上記の空モジュールに処理を追加していく。
class MyModule extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) val two = 1 + 1 println(two) val utwo = 1.U + 1.U println(utwo) io.out := io.in } println(getVerilog(new MyModule))
上記のコード実行すると以下の出力が得られる。
[info] [0.001] Elaborating design... 2 chisel3.core.UInt@14 [info] [0.756] Done elaborating. Total FIRRTL Compile Time: 334.7 ms module cmd3HelperMyModule( // @[:@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 = io_in; endmodule
追加したことについてまとめると、2つのval
変数を作ったが、それらは以下のようなことを示している。
- 1つ目はScalaの
Int
型を2つ足しあわせ、それをprintln
で出力したので整数"2"が表示される - 2つ目の
val
はChiselのUInt
型を足しあわせているので、println
で出力をするとハードウェアのノードとして見えるので、その型の名前とポインタ(chisel3.core.UInt@d
)が出力される
なお、上記のChiselのUInt
として扱われたコード1.U
は型のキャスト扱いとなりこの場合においてはInt
(1)がChiselのUInt
リテラルに変換されている。
モジュールの出力は何かによってドライブされる必要があるので、ここでは入力信号をそのまま出力に接続している。その結果このモジュールはModule2.1で作ったpassthroughモジュールになる。
非対応の動作
ここでは先程出てきた1.U
と1
を出すと何が起こるかについて見ていく。
class MyModuleTwo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(UInt(4.W)) }) val twotwo = 1.U + 1 println(twotwo) io.out := io.in } println(getVerilog(new MyModule))
見ての通りval twotwo
において1.U
と1
が加算されているが、このコードを実行すると以下のようにコンパイルに失敗する。
cmd4.sc:7: type mismatch; found : Int(1) required: chisel3.core.UInt val twotwo = 1.U + 1 ^Compilation Failed
セクション名の通り、この足し算はInt
とUInt
の足し算であり、このような足し算は非対応となるのでコンパイラエラーが発生した。Scalaは強力に型付けされた言語なのでどのような方であっても明示的なキャストが必須となる。演算子を用いて演算を行う場合には常に型に気をつけたほうが良さそう。
他のChiselの演算子
他の演算子として
- 減算
- 乗算
がある。
これらはいずれもunsinged integerにおいては期待したとおりに動く。さて例を見てみよう
class MyOperators extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_add = Output(UInt(4.W)) val out_sub = Output(UInt(4.W)) val out_mul = Output(UInt(4.W)) }) io.out_add := 1.U + 4.U io.out_sub := 2.U - 1.U io.out_mul := 4.U * 2.U } println(getVerilog(new MyOperators))
出力は以下のようになる。
[info] [0.000] Elaborating design... [info] [0.012] Done elaborating. Total FIRRTL Compile Time: 41.0 ms module cmd4HelperMyOperators( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [3:0] io_in, // @[:@6.4] output [3:0] io_out_add, // @[:@6.4] output [3:0] io_out_sub, // @[:@6.4] output [3:0] io_out_mul // @[:@6.4] ); wire [3:0] _T_15; // @[cmd4.sc 9:21:@8.4] wire [2:0] _T_16; // @[cmd4.sc 9:21:@9.4] wire [2:0] _T_19; // @[cmd4.sc 10:21:@11.4] wire [2:0] _T_20; // @[cmd4.sc 10:21:@12.4] wire [1:0] _T_21; // @[cmd4.sc 10:21:@13.4] wire [4:0] _T_24; // @[cmd4.sc 11:21:@15.4] assign _T_15 = 3'h1 + 3'h4; // @[cmd4.sc 9:21:@8.4] assign _T_16 = _T_15[2:0]; // @[cmd4.sc 9:21:@9.4] assign _T_19 = 2'h2 - 2'h1; // @[cmd4.sc 10:21:@11.4] assign _T_20 = $unsigned(_T_19); // @[cmd4.sc 10:21:@12.4] assign _T_21 = _T_20[1:0]; // @[cmd4.sc 10:21:@13.4] assign _T_24 = 3'h4 * 3'h2; // @[cmd4.sc 11:21:@15.4] assign io_out_add = {{1'd0}, _T_16}; assign io_out_sub = {{2'd0}, _T_21}; assign io_out_mul = _T_24[3:0]; endmodule
中間変数_T_N
が定義され、その中で書く計算が行われているのがわかると思う。
上記のChiselコードをテストするためのコードは例えば以下のような物になる。
class MyOperatorsTester(c: MyOperators) extends PeekPokeTester(c) { expect(c.io.out_add, 5) expect(c.io.out_sub, 1) expect(c.io.out_mul, 8) } assert(Driver(() => new MyOperators) {c => new MyOperatorsTester(c)}) println("SUCCESS!!")
Module2.1の時とは異なり、PeekPokeTester
を継承して使っているが、基本的な動きは一緒なので割愛。
因みにまだ他にも演算子はあるんだが、それはいずれ出てくるはずなのでまたの機会に一緒にまとめようと思う。
マルチプレクサと連結
Chiselではこれまでに出てきた3つの演算(加算、減算、乗算)の他に以下の演算子が用意されている
- mux
- concatenation
まずは早速例から。
class MyOperatorsTwo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out_mux = Output(UInt(4.W)) val out_cat = Output(UInt(4.W)) }) val s = true.B io.out_mux := Mux(s, 3.U, 0.U) // should return 3.U, since s is true io.out_cat := Cat(2.U, 1.U) // concatenates 2 (b10) with 1 (b1) to give 5 (101) } println(getVerilog(new MyOperatorsTwo)) class MyOperatorsTwoTester(c: MyOperatorsTwo) extends PeekPokeTester(c) { expect(c.io.out_mux, 3) expect(c.io.out_cat, 5) } assert(Driver(() => new MyOperatorsTwo) {c => new MyOperatorsTwoTester(c)}) println("SUCCESS!!")
- 実行結果
[info] [0.000] Elaborating design... [info] [0.022] Done elaborating. Total FIRRTL Compile Time: 25.9 ms module cmd6HelperMyOperatorsTwo( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [3:0] io_in, // @[:@6.4] output [3:0] io_out_mux, // @[:@6.4] output [3:0] io_out_cat // @[:@6.4] ); assign io_out_mux = 4'h3; assign io_out_cat = 4'h5; endmodule [info] [0.000] Elaborating design... [info] [0.007] Done elaborating. Total FIRRTL Compile Time: 12.1 ms Total FIRRTL Compile Time: 14.3 ms End of dependency graph Circuit state created [info] [0.000] SEED 1539612763352 test cmd6HelperMyOperatorsTwo Success: 2 tests passed in 5 cycles taking 0.006764 seconds [info] [0.005] RAN 0 CYCLES PASSED SUCCESS!!
上記コードとテストを見れば動きは一目瞭然だが、一応ざっとまとめてみると以下のようになる。
因みに上記のMyOperatorsTwo
をVerilogに変換して見ると以下のようになる。
[info] [0.000] Elaborating design... [info] [0.004] Done elaborating. Total FIRRTL Compile Time: 12.8 ms module cmd6HelperMyOperatorsTwo( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [3:0] io_in, // @[:@6.4] output [3:0] io_out_mux, // @[:@6.4] output [3:0] io_out_cat // @[:@6.4] ); assign io_out_mux = 4'h3; assign io_out_cat = 4'h5; endmodule
Mux
についてはtrue.B
で選択しているので"3"が、Cat
はverilog的に書くと{2'b10, 1'b1}
となり"5"がそれぞれの出力端子に出力される。これはFIRRTLがより簡潔な回路を生成するために、明らかに不要な回路を削除したためだ。
これでModule2.2のレクチャーは終了で、残りは練習問題となるのだがそれはまた後日。