Chiselを使ったDiplomacyのデザイン、久しぶりに再開した。Chisel + Diplomacyを使って簡単なSoCを作ってみているが、まずは外部からメモリにデータを書き込むパスが必要だろう。これについて検討する。
Rocket-ChipなどのSoC構成では、DTMというモジュールを使ってこれらの制御を行っている。DTMはJTAGとも接続されており外部から接続することができる。今回はそこまでする気が無いので、外部からELFファイルを受け取ってロードするくらいの機能で良い。今回はDTMは2つのユニットで構成することにする。以下の基本的な構成はSiFiveの構成から引っ張ってきた。
まずC++側は簡単に、リクエストを発行して書き込みアドレスとデータを送信するための簡単なコードとする。
extern "C" int debug_tick( unsigned char *debug_req_valid, unsigned char debug_req_ready, int *debug_req_bits_addr, int *debug_req_bits_data) { static int count = 0; static int addr = 0; static int data = 0xdead000; if (debug_req_ready && count < 100) { *debug_req_valid = 1; *debug_req_bits_addr = addr; *debug_req_bits_data = data; addr += 4; data += 4; count++; } else { *debug_req_valid = 0; } return 0; }
そしてこれを受け取るのがVerilog側で、Verilog側のDTMは非常に簡易なプロトコルを通じてTileLinkを読み書きできるユニットLoader
にデータを転送する。
module sim_dtm( input clock, input reset, output logic req_valid, output logic [31:0] req_bits_addr, output logic [31:0] req_bits_data, input logic req_ready ); .... always_ff @(posedge clock) begin req_valid_reg <= __debug_req_valid; req_bits_addr_reg <= __debug_req_bits_addr; req_bits_data_reg <= __debug_req_bits_data; end assign req_valid = req_valid_reg; assign req_bits_addr = req_bits_addr_reg; assign req_bits_data = req_bits_data_reg; int debug_tick_val; always_ff @(negedge clock) begin if (reset) begin end else begin debug_tick_val = debug_tick( __debug_req_valid, req_ready, __debug_req_bits_addr, __debug_req_bits_data ); end end endmodule // sim_dtm
Loader
とDTMはChiselを通じて接続されており、LoaderはLazyModuleを通じてインスタンスされている。このDTMとのインタフェースはTileLinkではないので、LazyModule内のLazyModuleImpに作成されたポートを使用して接続している。
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); dtm.io.clock := clock dtm.io.reset := reset dut.io.req := dtm.io.req.valid dut.io.addr := dtm.io.req.bits.addr dut.io.data := dtm.io.req.bits.data dtm.io.req.ready := dut.io.ready ...
core_complex
内に直接接続用のmodule
を用意しており、これを通じてloader
のI/Oポートと接続している。
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
Loader
の実装は以下のようになっており、ステートマシンを使ってTileLinkにリクエストを送信している。
class loader(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 req = Input(Bool()) val addr = Input(UInt(32.W)) ... switch (trans_state) { is (s_init) { io.ready := true.B when(io.req) { reg_a_valid := true.B reg_a_address := io.addr reg_a_data := io.data io.ready := false.B trans_state := s_trans } } is (s_trans) { when(out.a.fire) { reg_a_valid := false.B io.ready := true.B trans_state := s_init } } } out.a.valid := reg_a_valid out.a.bits.address := reg_a_address out.a.bits.data := reg_a_data out.a.bits.opcode := TLMessages.PutFullData out.a.bits.mask := 0xf.U out.a.bits.size := 0.U out.a.bits.param := 0.U }
シミュレーションしてみると、一応シーケンシャルにメモリリクエストを出力できているようだ。