FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Chiselで設計したHWのコンパイルオプションで生成されるモジュールを切り替える方法

Chiselという言語はVerilogとは似て非なる。 ハードウェアを記述する言語ではあるが、Verilogと同じように考えているとどのようにハードウェアを作ればよいのかわからなくなったり、Chiselの本来のうま味を引き出せなくなる。

前回は、Chiselの記述中にVerilogモデルを埋め込んだり、Verilogファイルを参照してモジュールとして埋め込む方法について調査した。

この方法を調査した目的は、「シミュレーションは全てChiselを使う(Verilogを使わない分変換コストが削減される)」と「合成用のデザインにはVerilogを埋め込む」という、外部のスイッチにより、生成されるVerilogを切り替えたいためだ。 これを達成するためには、

を切り替える方法について考える。

まず問題となるのが、ブラックボックス側は基底クラスがBlackBox()で、Chisel側はModule()クラスが基底クラスになっていることだ。 BlackBox()クラスはベースクラスがBaseModuleであり、一方でModuleクラスもBaseModuleであるため、どうにかして接続できるはずだ。

https://chisel.eecs.berkeley.edu/api/latest/chisel3/core/BaseBlackBox.html

まずは、モジュールの切り替え方だが、パラメータconf.debugにより接続するモジュールを切り替える。

class CpuMemory [Conf <: RVConfig](conf: Conf) extends Module {
  val io = IO(new Bundle {
    val mem = new MemoryIo(conf)
  })

  val memory = conf.debug match {
    case true => {
      val mem_model = Module(new MemoryModel(conf))
      mem_model
    }
    case _ => {
      val mem_model = Module(new MemoryInlineBoxCore(conf))
      mem_model.io.clock <> clock
      mem_model
    }
  }

  memory.io.mem <> io.mem
}

conf.debugがTrueならば、Chiselで記述されたシミュレーション用のMemoryModelクラスが使用され、そうでなければMemoryInlineBox()が使用される。

しかし、MemoryInlineBox()クラスがBlackBoxを基底クラスとしており、どうも接続時に怒られる。

[error] /home/msyksphinz/work/riscv/chisel/minicpu/src/main/scala/cpu/memory.scala:34:10: value io is not a member of chisel3.core.BaseModule
[error]   memory.io.mem <> io.mem
[error]          ^

そこで、ブラックボックスをラップするモジュールをさらに一つ作成し、切り替えの境界はModuleクラスで統一する。

  val memory = conf.debug match {
    case true => {
      val mem_model = Module(new MemoryModel(conf))
      mem_model
    }
    case _ => {
      val mem_model = Module(new MemoryInlineBox(conf))
      mem_model
    }
  }
  memory.io.mem <> io.mem

ここで注意しなければならないのが、Chiselのモジュールクラスは明示的なクロックとリセットが存在しない。一方でBlackBoxモジュールはクロックとリセットがそもそも存在しないため、明示的に接続する必要がある。

調べてみると、Moduleクラスはclockという変数が暗黙的にクロックとして使用されているらしい。そこで、BlackBoxクラスのIOとしてclockポートを定義する。

class MemoryInlineBox [Conf <: RVConfig](conf: Conf) extends Module {
  val io = IO(new Bundle {
    val mem = new MemoryIo(conf)
  })
  val mem_black_box_core = Module(new MemoryInlineBoxCore(conf))

  mem_black_box_core.io.clock <> clock  // 明示的にクロックを接続する。
  mem_black_box_core.io.mem <> io.mem
}
class MemoryInlineBoxCore [Conf <: RVConfig](conf: Conf) extends BlackBox with HasBlackBoxInline {
  val io = IO(new Bundle {
    val clock = Input(Clock())
    val mem = new MemoryIo(conf)
  })

  setInline("MemoryBlackBox.v",
    s"""...

これでVerilogを生成すると、生成されたVerilogで接続されていることが分かる。

module CpuMemory( // @[:@35.2]
  input         clock, // @[:@36.4]
  input         io_mem_inst_bus_req, // @[:@38.4]
  input  [15:0] io_mem_inst_bus_addr, // @[:@38.4]
...
  MemoryInlineBox memory ( // @[memory.scala 28:29:@40.4]
    .clock(memory_clock),
    .io_mem_inst_bus_req(memory_io_mem_inst_bus_req),
    .io_mem_inst_bus_addr(memory_io_mem_inst_bus_addr),
...
  assign memory_clock = clock; // @[:@41.4]

これで、Chisel内で、スイッチを使用したモジュールの切り替えと、Verilogブラックボックスとの切り替えが出来るようになった。

f:id:msyksphinz:20190108001048p:plain
CpuCoreと3種類のChiselモジュールの切り替え

2019/01/31追記。結局、BlackBox入りのモジュールを切り替えるために何個もクラスを経由させなければならないのをどうにかできないかと思ったが、よく考えたらBlackBoxを継承したクラスはModuleクラスの中に作ってしまえば良いんだ。これなら楽。

class ModuleWrapper extends Module {
  private clas ModuleBlkBoxWrapper extends Module {
    val u_mem = Module(new ModuleBlackBox())
    u_mem.io <> io
  }
  val u_mem = Conf.Debug match {
    true => {
      val u_mem = Module(new ModuleReal ())
      u_mem
    }
    _ => {
      val u_mem = Module(new ModuleBlkBoxWrapper())
      u_mem
    }
  }
}

class ModuleBlackBox extends BlackBox with HasBlackBoxInline {
  setInline("MemoryBlackBox.v",
    s"""...
}