FPGA開発日記

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

ChiselとDiplomacyを使ってオリジナルデザインを作成してみる (3. 外部モジュールとの接続方法検討)

前回の続き。Diplomacyを使って実際にいくつかデザインを作ってみようと思った。Diplomacyを使えばデバッグユニットをその場で生成して、さらに外部と接続して制御したい。そんなデザインを作ってみよう。

CoreComplexモジュールの作成

まずはコアの部分を作る、CoreComplexというモジュールを作り、そこにIFUとメモリをLazyModuleとして配置した。

class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
  val ifu    = LazyModule(new ifu("ifu"))
  val xbar   = LazyModule(new TLXbar)
  val memory = LazyModule(new TLRAM(AddressSet(0x020000000, 0x0ffff), beatBytes = ramBeatBytes))

  xbar.node := TLDelayer(0.0001) := ifu.node
  memory.node := xbar.node

上記の記述では、ifuxbarmemoryの3つをLazyModuleとして宣言し、これを接続する。見てわかる通りcore_complexモジュールはLazyModuleとして宣言されており、これはその名の通りDiplomacyによるパス探索で見つからなければ自動的に削除される。またDiplomacyによる「外交」実行中にパラメータの調整が行われる。

さらにこれに対して外部からメモリアクセスするための専用モジュールとしてtest_loaderを宣言し接続した。

class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
  val loader = LazyModule(new loader("loader"))

  val ifu    = LazyModule(new ifu("ifu"))
  val xbar   = LazyModule(new TLXbar)
  val memory = LazyModule(new TLRAM(AddressSet(0x020000000, 0x0ffff), beatBytes = ramBeatBytes))

  xbar.node := loader.node
  xbar.node := TLDelayer(0.0001) := ifu.node
  memory.node := xbar.node

CoreComplexをインスタンス化するTestHarnessモジュールを作成

次にこのCoreComplexをインスタンス化するTestHarnessモジュールを作成する。このTestHarnessモジュールはシミュレーション用で、実際にチップを起こす場合には使用されない。従ってここに外部プログラムローダなどを配置することになる。

class TestHarness()(implicit p: Parameters) extends Module {
  val io = IO(new Bundle {
    val success = Output(Bool())
  })

  val ldut = LazyModule(new core_complex(4, 5000))
  val dut = Module(ldut.module)

  val dtm = Module(new sim_dtm);
  ldut.loader.module.io.req  := dtm.io.req
  ldut.loader.module.io.addr := dtm.io.addr
  ldut.loader.module.io.data := dtm.io.data
  dtm.io.ready := ldut.loader.module.io.ready
}

class DefaultConfig extends Config(new BaseSubsystemConfig)

この時にLazyModuleとしてcore_complexインスタンス化しており、さらにcore_complexの内部インスタンスmoduledutとして引っ張り出している。core_complexにはLazyではないモジュールとしてmoduleを宣言していた。

class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
  val loader = LazyModule(new loader("loader"))
...
  lazy val module = new LazyModuleImp(this) with UnitTestModule {

    // TLPatternPusher
    ifu.module.io.run := true.B

    io.finished := ifu.module.io.done
    when (ifu.module.io.error) {
      printf("Error is detected")
    }
  }

さらに、dtmとして外部との接続用にデバッグユニットをインスタンス化している。このsim_dtmは実体はBlackBoxモジュールでVerilogとして実装されている。DPIを経由してVerilogモジュールと通信をする仕組みになっている。

  • sim_dtm.v
module sim_dtm(
  input clk,
  input reset,

  output        debug_req_valid,
  input         debug_req_ready,
  output [ 6:0] debug_req_bits_addr,
  output [ 1:0] debug_req_bits_op,
  output [31:0] debug_req_bits_data,

  input         debug_resp_valid,
  output        debug_resp_ready,
  input  [ 1:0] debug_resp_bits_resp,
  input  [31:0] debug_resp_bits_data,

  output [31:0] exit
);

  bit r_reset;

  wire __debug_req_ready = debug_req_ready;
  wire __debug_resp_valid = debug_resp_valid;
  wire [31:0] __debug_resp_bits_resp = {30'b0, debug_resp_bits_resp};
...

この状態でVerilogを生成してみたのだが、FIRRTLの段階で失敗してしまった。ldut.loaderが存在しないらしい。確かにLazyModuleなのでタッチしないとインスタンス化されないと思うのだが、そうはいってもどうやってインスタンス化すればいいんだ?

firrtl.passes.CheckHighFormLike$UndeclaredReferenceException:  @[test_harness.scala 25:30]: [module TestHarness] Reference loader is not declared.
firrtl.passes.CheckHighFormLike$UndeclaredReferenceException:  @[test_harness.scala 26:30]: [module TestHarness] Reference loader is not declared.
firrtl.passes.CheckHighFormLike$UndeclaredReferenceException:  @[test_harness.scala 27:30]: [module TestHarness] Reference loader is not declared.
firrtl.passes.CheckHighFormLike$UndeclaredReferenceException:  @[test_harness.scala 28:16]: [module TestHarness] Reference loader is not declared.

これは、一度接続先をLazyModuleの内部にインスタンスされているModuleに接続することで解決できる。以下のように、LazyModule内部のmoduleに対して接続することでモジュール間を接続できる。

f:id:msyksphinz:20201004233440p:plain
class TestHarness()(implicit p: Parameters) extends Module {
  val io = IO(new Bundle {
    val success = Output(Bool())
  })

  val ldut = LazyModule(new core_complex(4, 5000))
  val dut = Module(ldut.module)

  val dtm = Module(new sim_dtm);
  dut.io.req  := dtm.io.req
  dut.io.addr := dtm.io.addr
  dut.io.data := dtm.io.data
  dtm.io.ready := dut.io.ready
}
  module TestHarness :
    input clock : Clock
    input reset : UInt<1>
    output io : {success : UInt<1>}

    clock is invalid
    reset is invalid
    io is invalid
    inst ldut of core_complex @[test_harness.scala 22:19]
    ldut.clock is invalid
    ldut.reset is invalid
    ldut.auto is invalid
    ldut.io is invalid
    ldut.clock <= clock
    ldut.reset <= reset
    inst dtm of sim_dtm @[test_harness.scala 24:19]
    dtm.ready is invalid
    dtm.data is invalid
    dtm.addr is invalid
    dtm.req is invalid
    ldut.io.req <= dtm.req @[test_harness.scala 25:15]
    ldut.io.addr <= dtm.addr @[test_harness.scala 26:15]
    ldut.io.data <= dtm.data @[test_harness.scala 27:15]
    dtm.ready <= ldut.io.ready @[test_harness.scala 28:16]
class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule {
  val loader = LazyModule(new loader("loader"))

  lazy val module = new LazyModuleImp(this) {
    val io = IO(new Bundle {
      val req   = Input(Bool())
      val addr  = Input(UInt(32.W))
      val data  = Input(UInt(32.W))
      val ready = Output(Bool())
    })

    loader.module.io.req  := io.req
    loader.module.io.addr := io.addr
    loader.module.io.data := io.data
    io.ready := loader.module.io.ready