ChiselはScalaをベースとしたDSLからVerilog-HDLを生成することのできるハードウェア記述言語であるが、ちょっと複雑な記法をすると生成されたVerilogが全く読めなくなる。
例えば、ChiselはModuleの中に関数を定義することができる。Scalaなので以下のような記法は当然可能である。
class pmp_simple extends Module { val io = IO(new Bundle { val vld = Input(Bool()) val addr = Input(UInt(32.W)) val en = Output(Bool()) }) def check_region(addr: UInt, base: Int, length: Int) : Bool = { val cond = addr >= base.U(64.W) && addr < (base + length).U(64.W) return Mux(cond, true.B, false.B) } io.en := Mux(io.vld, check_region(io.addr, 0x3000, 0x1000), false.B) }
しかし生成されるVerilogは非常に煩雑で分かりにくい。特にcheck_region()
内で宣言された変数cond
はいったいどこに行ったのだ、となる。
assign _GEN_0 = {{32'd0}, io_addr}; assign _T_12 = _GEN_0 >= 64'h3000; assign _T_14 = _GEN_0 < 64'h4000; assign _T_15 = _T_12 & _T_14; assign io_en = io_vld & _T_15;
そこで、Chiselには最適化を抑制したり、デバッグのために信号名をキープするためのマクロがある。 これらについて調査した。調査にあたり以下の資料をしっかりと読んだ。
あとは以下の記事。非常に分かりやすくて感心した。
- 方法1.
@chiselName
を使う
@chiselName
マクロを使うと、上記の関数内に配置された変数の名前を生成されたVerilogの中でキープする。例えば、Chiselのコードを以下のように変更する。
import chisel3.experimental.chiselName @chiselName class pmp_simple(cfg: AddressCfgBase) extends Module { val io = IO(new Bundle { val vld = Input(Bool()) val addr = Input(UInt(32.W)) val en = Output(Bool()) }) def check_region(addr: UInt, base: Int, length: Int) : Bool = { val cond = addr >= base.U(64.W) && addr < (base + length).U(64.W) return Mux(cond, true.B, false.B) } io.en := Mux(io.vld, check_region(io.addr, 0x3000, 0x1000), false.B)
こうすると、cond
という変数名を残してくれる。これはデバッグが行いやすい。
wire cond; // 変数condを残す。 assign _GEN_0 = {{32'd0}, io_addr}; assign _T_12 = _GEN_0 >= 64'h3000; assign _T_14 = _GEN_0 < 64'h4000; assign cond = _T_12 & _T_14; // condを使用する。 assign io_en = io_vld & cond;
- 方法2.
suggestName
を使う
suggestName
は@chiselName
よりもさらに柔軟性がある。特定の信号に対して、変数名を消さずに残しておくように依頼する。例えば、Chiselのコードを以下のように変更する。
class pmp_simple(cfg: AddressCfgBase) extends Module { val io = IO(new Bundle { val vld = Input(Bool()) val addr = Input(UInt(32.W)) val en = Output(Bool()) }) def check_region(addr: UInt, base: Int, length: Int) : Bool = { val cond = addr >= base.U(64.W) && addr < (base + length).U(64.W) cond.suggestName("special_cond") // suggestNameを追加する。 return Mux(cond, true.B, false.B) } io.en := Mux(io.vld, check_region(io.addr, 0x3000, 0x1000), false.B)
suggestName
によって指定された変数名に書き換えられる。
wire special_cond; assign _GEN_0 = {{32'd0}, io_addr}; assign _T_12 = _GEN_0 >= 64'h3000; assign _T_14 = _GEN_0 < 64'h4000; assign special_cond = _T_12 & _T_14; assign io_en = io_vld & special_cond;
- 方法3. 変数のdontTouchを使う
変数に対してdontTouch
を付加すると、その変数の最適化による除去を抑制する。
これはマニュアルにも出ているので使えるはずなのだが、FIRまで生成された時点で落ちてしまったのでまだ検証が十分ではない?
(.firにはdontTouchが効いていたのでChiselでは有効なのかも)
例えば、以下のようなChiselコードでは変数in_data
が除去されるが、
class pmp_simple extends Module { val io = IO(new Bundle { val vld = Input(Bool()) val addr = Input(UInt(32.W)) val en = Output(Bool()) }) def check_region(addr: UInt, base: Int, length: Int) : Bool = { val cond = addr >= base.U(64.W) && addr < (base + length).U(64.W) return Mux(cond, true.B, false.B) } val in_data = Wire(Bool()) in_data := Mux(io.vld, check_region(io.addr, 0x3000, 0x1000), false.B)))) io.en := in_data
これにdontTouch
を付加する。
import chisel3.experimental.dontTouch class pmp_simple extends Module { val io = IO(new Bundle { val vld = Input(Bool()) val addr = Input(UInt(32.W)) val en = Output(Bool()) }) def check_region(addr: UInt, base: Int, length: Int) : Bool = { val cond = addr >= base.U(64.W) && addr < (base + length).U(64.W) return Mux(cond, true.B, false.B) } val in_data = dontTouch(Wire(Bool()) in_data := Mux(io.vld, check_region(io.addr, 0x3000, 0x1000), false.B)))) io.en := in_data
が、FIRまでは出てきたがVerilogの生成に失敗している。