Diplomacyを使ってみるために、最小構成を考えてみよう。上記の2つのPusher(リクエスト挿入するためのモジュール)を介してMemoryに接続する。
下記のソースコードはいくつか説明しておいた方が良い。
まず、pusher1
, ifu
, xbar
, memory
はLazyModule
として宣言されている。pusher1
と, xbar
, memory
はちょっと中身が複雑なので説明ができないが、ifu
は自作なので中身を見てみよう。
src/main/scala/core_complex/core_complex.scala
package core_complex import chisel3._ import freechips.rocketchip.config._ import freechips.rocketchip.amba.ahb._ import freechips.rocketchip.amba.apb._ import freechips.rocketchip.amba.axi4._ import freechips.rocketchip.subsystem.{BaseSubsystemConfig} import freechips.rocketchip.devices.tilelink._ import freechips.rocketchip.tilelink._ import freechips.rocketchip.util._ import freechips.rocketchip.diplomacy._ import freechips.rocketchip.diplomaticobjectmodel.logicaltree.{BusMemoryLogicalTreeNode, LogicalModuleTree, LogicalTreeNode} import freechips.rocketchip.diplomaticobjectmodel.model.{OMECC, TL_UL} class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule { val pusher1 = LazyModule(new TLPatternPusher("pat1", Seq( new WritePattern(0x100, 0x2, 0x012345678L), new WritePattern(0x500, 0x2, 0x0abcdef01L), new ReadExpectPattern(0x100, 0x2, 0x012345678L), new ReadExpectPattern(0x500, 0x2, 0x0abcdef01L) ))) val ifu = LazyModule(new ifu("ifu")) val xbar = LazyModule(new TLXbar) val memory = LazyModule(new TLRAM(AddressSet(0x02000, 0x0fff), beatBytes = ramBeatBytes)) xbar.node := pusher1.node xbar.node := ifu.node memory.node := xbar.node lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle { val finished = Output(Bool()) }) pusher1.module.io.run := true.B io.finished := pusher1.module.io.done } }
src/main/scala/ifu/ifu.scala
package freechips.rocketchip.tilelink import chisel3._ import freechips.rocketchip.config._ import freechips.rocketchip.amba.ahb._ import freechips.rocketchip.amba.apb._ import freechips.rocketchip.amba.axi4._ import freechips.rocketchip.subsystem.{BaseSubsystemConfig} import freechips.rocketchip.devices.tilelink._ import freechips.rocketchip.tilelink._ import freechips.rocketchip.util._ import freechips.rocketchip.diplomacy._ import freechips.rocketchip.diplomaticobjectmodel.logicaltree.{BusMemoryLogicalTreeNode, LogicalModuleTree, LogicalTreeNode} import freechips.rocketchip.diplomaticobjectmodel.model.{OMECC, TL_UL} class ifu(name: String)(implicit p: Parameters) extends LazyModule { val node = TLClientNode(Seq(TLClientPortParameters(Seq(TLClientParameters(name = name))))) lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle { val run = Input(Bool()) val done = Output(Bool()) val error = Output(Bool()) }) val (out, edge) = node.out(0) ... } object ifu { def apply(name: String)(implicit p: Parameters): TLInwardNode = { val pusher = LazyModule(new ifu(name)) pusher.node } }
ifu
は2つのセクションに分かれる。ifu
クラスの先頭にnode
を宣言し、これはTLClientNode
(つまりTileLinkのマスターである)である。そして次にLazyModuleImpl
を宣言してノードに接続するデジタル回路部分を宣言する。
module
にの内部にノードの情報を引き込むために、val (out, edge) = node.out(0)
としている。out
はTileLinkの接続信号、Edgeはパラメータ情報として考えて良い。つまり、以下のようにout.a
やout.b
のようにしてTileLinkのチャネルにアクセスが可能となる。
val (out, edge) = node.out(0) val baseEnd = 0 val (sizeEnd, sizeOff) = (edge.bundle.sizeBits + baseEnd, baseEnd) val (sourceEnd, sourceOff) = (edge.bundle.sourceBits + sizeEnd, sizeEnd) val beatBytes = edge.bundle.dataBits val counter = RegInit(0.U((beatBytes * 8).W)) val a = out.a val d = out.d ... a.valid := io.run && !finish a.bits := Mux(req_counter(4), get_pbits, put_pbits) d.ready := true.B io.done := finish // Tie off unused channels out.b.valid := false.B out.c.ready := true.B out.e.ready := true.B
core_complex
に立ち戻ると、2つのマスターifu
とpusher
はxbar
を介して接続される。xbar
はアービトレーション機能を持っており、2つのマスターからの信号を制御してメモリに伝え、メモリからのレスポンスを正確に2つのマスターに返す。
xbar.node := pusher1.node xbar.node := ifu.node memory.node := xbar.node
もうひとつ、core_complex
にも内部にModuleImpl
が設けられており、ここに各LazyModuleの制御が書かれている。
class core_complex(ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule { ... lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle { val finished = Output(Bool()) }) pusher1.module.io.run := true.B io.finished := pusher1.module.io.done }
pusher1
のrun
信号に対して1をアサートし、core_complex
モジュールに対してio.finished
出力信号を定義したうえでpusher1
の制御がすべて完了するとそれをアサートするようにしている。
このcore_complex
をインスタンス化するためのモジュールがTestHarness
で、これはModuleつまり普通のChiselのモジュールとして宣言する。
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) io.success := dut.io.finished }
dut.io
つまりcore_complex.module
の信号io.finished
を外部の信号io.success
に接続している。これが無いとLazyModuleはTestHarness
の中でインスタンス化されることなく、なにもVerilogが生成されない。
そして、このTestHarnessからVerilogを生成するのがGenerator
オブジェクトだ。Generator
オブジェクトがemitVerilog
を読みだしてVerilogファイルを生成する。これにはChiselのエンジンおよびFIRRTLのエンジンが呼び出される(Chisel 3.3.0からはDriver
ではなくChiselStage
の使用が推奨されているが、まだ3.2.2を使っているのでDriver
のまま)。
// See LICENSE.SiFive for license details. package core_complex import chisel3._ import freechips.rocketchip.config.Config import freechips.rocketchip.config.Parameters import freechips.rocketchip.devices.debug.Debug import freechips.rocketchip.diplomacy.LazyModule import freechips.rocketchip.util.AsyncResetReg import chisel3.Driver import freechips.rocketchip.subsystem.BaseSubsystemConfig object Generator { final def main(args: Array[String]) { val verilog = Driver.emitVerilog( new TestHarness()(Parameters.empty) ) } }
このGenerator
を呼び出すために、以下のようにコマンドライン上からタイプする。
$ TERM=xterm-color java -Xmx2G -Xss8M -XX:MaxPermSize=256M -jar ./sbt-launch.jar 'runMain core_complex.Generator'
TestHarness.v
が生成されることが確認できた。
module TestHarness( input clock, input reset, output io_success ); wire ldut_clock; // @[test_harness.scala 19:19] wire ldut_reset; // @[test_harness.scala 19:19] wire ldut_io_finished; // @[test_harness.scala 19:19] core_complex ldut ( // @[test_harness.scala 19:19] .clock(ldut_clock), .reset(ldut_reset), .io_finished(ldut_io_finished) ); assign io_success = ldut_io_finished; // @[test_harness.scala 21:14] assign ldut_clock = clock; assign ldut_reset = reset; endmodule
実際には大量のモジュールが配置されて生成されているのだが、Chiselによりcore_complex
と外部モジュールが正しく接続されていることが分かると思う。
module core_complex( input clock, input reset, output io_finished ); .... TLPatternPusher pusher1 ( // @[core_complex.scala 18:27] .clock(pusher1_clock), .reset(pusher1_reset), .auto_out_a_ready(pusher1_auto_out_a_ready), .auto_out_a_valid(pusher1_auto_out_a_valid), ... ifu ifu ( // @[core_complex.scala 25:26] .clock(ifu_clock), .reset(ifu_reset), .auto_out_d_valid(ifu_auto_out_d_valid) ); TLXbar xbar ( // @[core_complex.scala 27:26] .clock(xbar_clock), TLRAM memory ( // @[core_complex.scala 28:26] .clock(memory_clock), .reset(memory_reset), .auto_in_a_ready(memory_auto_in_a_ready), .auto_in_a_valid(memory_auto_in_a_valid), .auto_in_a_bits_opcode(memory_auto_in_a_bits_opcode), ...