FPGA開発日記

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

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モジュールの切り替え