前回までで多次元構造体配列について、とりあえずエラーが出ずにVerilogが生成されるようになったが、生成されたSystemVerilogのコードは明らかにおかしい。
circuit VecBundle1 : module VecBundle9 : input in: { a : { b : { c : UInt<8>[4] } [8] } [16] }[32] input sel1: UInt<5> input sel2: UInt<4> input sel3: UInt<3> output out : { b : UInt<8>[4] } out.b <= in[sel1].a[sel2].b[sel3].c
module VecBundle8( input logic [7:0][3][7][15][31] in_a_b_c, output logic [7:0][3] out_b ); assign out_b = in_a_b_c[0][1][2]; endmodule
ビット幅の指定がおかしくなっているのと、ベクトル配列の指定がおかしい。理想としては以下だ。
module VecBundle8( input logic [7:0] in_a_b_c[4][8][16][32], output logic [7:0] out_b[4] ); assign out_b = in_a_b_c[0][1][2]; endmodule
SystemVerilogの生成については、SystemVerilogEmitter
が担当している。そして、SystemVerilogEmitter
内のstringify
が型の出力を担当している。
// Turn ports into Seq[String] and add to portdefs def build_ports(): Unit = { ... } def emit_systemverilog(): DefModule = { build_netlist(m.body) build_ports() ...
build_ports()
でポートタイプを作り上げているのだが、それを2つに分割する。つまり、以下の2つの分割するわけだ。
input logic [7:0] in_a_b_c[4][8][16][32], // ~~~~~ ~~~~~~~~~~~~~~ // | `----------- stringify_vec() // `------------------------------ stringify()
// Turn types into strings, all ports must be GroundTypes val tpes = m.ports map { case Port(_, _, _, tpe: GroundType) => stringify(tpe) case Port(_, _, _, tpe: VectorType) => stringify(tpe) case port: Port => error(s"Trying to emit non-GroundType Port $port") } // Turn types into strings, all ports must be GroundTypes val tpes_vec = m.ports map { // case Port(_, _, _, tpe: GroundType) => stringify_vec(tpe) case Port(_, _, _, tpe: GroundType) => "" case Port(_, _, _, tpe: VectorType) => stringify_vec(tpe) case port: Port => error(s"Trying to emit non-GroundType Port $port") }
stringify_vec()
はVectorType
の型を再帰的に探していき、VectorType
のたびに"[$wx]"
を出力するという寸法である。
def stringify_vec(tpe: VectorType): String = { val elem_type = tpe.tpe match { case (t: VectorType) => { val wx = sv_bitWidth(tpe) val field_str = if (wx > 0) s"[$wx]" else "" val str_element = tpe.tpe match { case tpe_elem: VectorType => stringify_vec(tpe_elem) } str_element + field_str } case (_: UIntType | _: SIntType | _: AnalogType) => { val wx = sv_bitWidth(tpe) val vec_str = if (wx > 0) s"[$wx]" else "" vec_str } case ClockType | AsyncResetType | AsyncResetNType => "" case _ => throwInternalError(s"trying to write unsupported type in the Verilog Emitter: $tpe") } elem_type }
最後に、stringify_vec()
の結果をポート宣言に追加する。
// dirs are already padded val pad_to_port = padToMax(tpes) val pad_to_portvec = padToMax(tpes_vec) val pad_vec = pad_to_port.zip(pad_to_portvec) (dirs, pad_vec, m.ports).zipped.toSeq.zipWithIndex.foreach { case ((dir, (tpe, tpe_vec), Port(info, name, _, _)), i) => ... if (i != m.ports.size - 1) { portdefs += Seq(dir, " ", tpe, " ", name, " ", tpe_vec, ",", info) } else { portdefs += Seq(dir, " ", tpe, " ", name, " ", tpe_vec, info) } ...
これにより、上記のFIRRTLは以下のようになる。
./utils/bin/firrtl -td regress -i ./regress/VecBundle.fir -X sverilog -ll trace 2>&1 | tee VecBundle.log
module VecBundle8( input logic [7:0] in_a_b_c [4][8][16][32], output logic [7:0] out_b [4] ); assign out_b = in_a_b_c[0][1][2]; endmodule
ある程度形になってきた。まだ配列を正常に処理できていないと思うけど、とりあえず形になってきた。
ついでに、以下のようなFIRのテストを動かしてみた。
module VecBundle9 : input in: { a : { b : { c : UInt<8>[4] } [8] } [16] }[32] input sel1: UInt<5> input sel2: UInt<4> input sel3: UInt<3> output out : { b : UInt<8>[4] } out.b <= in[sel1].a[sel2].b[sel3].c
module VecBundle9( input logic [7:0] in_a_b_c [4][8][16][32], input logic [4:0] sel1 , input logic [3:0] sel2 , input logic [2:0] sel3 , output logic [7:0] out_b [4] ); assign out_b = in_a_b_c[sel1][sel2][sel3]; endmodule
良さそうだ。