ハードウェア記述言語の一種であるChiselは、Scalaをベースにした言語であり、Scalaの機能を使って様々な便利な記法が実現可能だ。
その一つの便利な手法として、Bundleを使った演算子のオーバロードがある。SystemVerilogからの移行や、Chiselでの便利な書き方の一つとしてメモしておきたい。
Bundleとは、SystemVerilogのstructのようなもの。複数の信号をまとめて、一つの信号タイプとして定義する。
以下の例は、Bundle ExampleBundle
を定義する。ExampleBundle
には、upper
とlower
のそれぞれ32ビットの信号が含まれている。
class ExampleBundle extends Bundle { val upper = UInt(32.W) val lower = UInt(32.W) }
このBundleは、Chiselのモジュール内で様々な形で使用することができる。SystemVerilogと同様、信号の入出力と肩に使用したり、内部ステートを記憶するためのレジスタや配線として定義することもできる。
以下は単純な例だが、ExampleBundle型
の信号を一度レジスタで叩き、その結果を出力する(まあスライサみたいなものだ)。
- モジュール :
ExampleModule
- 入力ポート :
in_a
(ExampleBundle
型) - 入力ポート :
out_a
(ExampleBundle
型)
class ExampleBundle extends Bundle { val upper = UInt(32.W) val lower = UInt(32.W) } class ExampleModule extends Module { val io = IO(new Bundle { val in_a = Input(new BundleExample) val out_a = Output(new BundleExample) }) val reg_a = Reg(new BundleExample) reg_a := io.in_a io.out_a := reg_a }
Verilogを生成すると、以下のようになる。
module ExampleModule( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [31:0] io_in_a_upper, // @[:@6.4] input [31:0] io_in_a_lower, // @[:@6.4] output [31:0] io_out_a_upper, // @[:@6.4] output [31:0] io_out_a_lower // @[:@6.4] ); reg [31:0] reg_a_upper; // @[bundle_example.scala 18:18:@8.4] reg [31:0] reg_a_lower; // @[bundle_example.scala 18:18:@8.4] assign io_out_a_upper = reg_a_upper; // @[bundle_example.scala 20:12:@12.4] assign io_out_a_lower = reg_a_lower; // @[bundle_example.scala 20:12:@11.4] ... always @(posedge clock) begin reg_a_upper <= io_in_a_upper; reg_a_lower <= io_in_a_lower; end
上記は単純な例だが、BundleExample
型で定義されていない、BundleExample
型同士の演算や、UInt型からのBundleExample
への代入を行ってみると、当然エラーとなる。
class ExampleModule extends Module { val io = IO(new Bundle { val in_a = Input(new ExampleBundle) val out_a = Output(new ExampleBundle) }) val reg_a = Reg(new ExampleBundle) val const_1 = Wire(new ExampleBundle) const_1 := 1.U reg_a := io.in_a + const_1 io.out_a := reg_a }
[error] (run-main-0) chisel3.internal.ChiselException: Connection between sink (bundle_example.ExampleBundle@2a) and source (chisel3.core.UInt@2d) failed @: Sink (bundle_example.ExampleBundle@2a) and Source (chisel3.core.UInt@2d) have different types. [error] chisel3.internal.ChiselException: Connection between sink (bundle_example.ExampleBundle@2a) and source (chisel3.core.UInt@2d) failed @: Sink (bundle_example.ExampleBundle@2a) and Source (chisel3.core.UInt@2d) have different types.
これは、ExampleBundle
型で、
が定義されていないことに依存する。C++経験者なら容易に想像できるだろうが、これは演算子のオーバロードで解決することができる。
つまり、ExamlpeBundle
に対して、以下を定義する。
class ExampleBundle extends Bundle { val upper = UInt(32.W) val lower = UInt(32.W) def := (that: UInt) : ExampleBundle = { this.upper := that(63,32) this.lower := that(31, 0) this } def + (that: ExampleBundle) : ExampleBundle = { val res = Wire(new ExampleBundle) res.upper := this.upper + that.upper res.lower := this.lower + that.lower res } }
C++と同様に見れば良く分かる。def :=
の方は、現在のExampleBundle
の実体this
に対してUInt
の値that
を分割して代入する。その結果はthis
として返される。
また、def +
の場合は新たな実体ExampleBundle
を定義して、そこに対して実体this
ともう一つのオペランドthat
を代入し、その結果をres
として返す。
これでVerilogを生成すると、正しくVerilogが生成されていることが分かる。このように、新たなBundleを定義した場合、同様に多用する演算子も定義しておくと、うまくBundleを活用することができる。
module ExampleModule( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input [31:0] io_in_a_upper, // @[:@6.4] input [31:0] io_in_a_lower, // @[:@6.4] output [31:0] io_out_a_upper, // @[:@6.4] output [31:0] io_out_a_lower // @[:@6.4] ); reg [31:0] reg_a_upper; // @[bundle_example.scala 34:18:@8.4] reg [31:0] reg_a_lower; // @[bundle_example.scala 34:18:@8.4] wire [32:0] _T_16; // @[bundle_example.scala 20:29:@13.4] wire [32:0] _T_18; // @[bundle_example.scala 21:29:@16.4] assign _T_16 = {{1'd0}, io_in_a_upper}; // @[bundle_example.scala 20:29:@13.4] assign _T_18 = io_in_a_lower + 32'h1; // @[bundle_example.scala 21:29:@16.4] assign io_out_a_upper = reg_a_upper; // @[bundle_example.scala 39:12:@22.4] assign io_out_a_lower = reg_a_lower; // @[bundle_example.scala 39:12:@21.4] ... always @(posedge clock) begin reg_a_upper <= _T_16[31:0]; reg_a_lower <= io_in_a_lower + 32'h1; end