FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

Chisel3で生成されたVerilogのデバッグを捗らせるためのいくつかの技法

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181105/20181105012126.png

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はいったいどこに行ったのだ、となる。

  • 上記のChiselコードをコンパイルした場合の生成されたVerilog。変数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には最適化を抑制したり、デバッグのために信号名をキープするためのマクロがある。 これらについて調査した。調査にあたり以下の資料をしっかりと読んだ。

www.youtube.com

あとは以下の記事。非常に分かりやすくて感心した。

jijing.site

  • 方法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の生成に失敗している。