自作CPUにおいてデバッグモニタというのは常に重要な機能で、私は特に波形を見るのがとても嫌いなのでデバッグ情報はなるべくテキスト形式で出力している。ちなみ今回のデバッグ機能というのはいわゆるJTAGのようなデバッグ機能ではなく、CPUをデバッグするための実装者向けデバッグ機能だ。
昔私の作った自作CPUでは、コアのパイプラインの流れが監視できるようにデバッグモニタを用意しており、デバッグビルド時にこのポートを生やして情報を出力していた。
/* Debug-Port */
io.dbg_monitor.hart_id := hart_id.U
io.dbg_monitor.inst_fetch_req := io.inst_bus.req
io.dbg_monitor.inst_fetch_addr := io.inst_bus.addr
io.dbg_monitor.inst_fetch_ack := io.inst_bus.ack
io.dbg_monitor.inst_fetch_rddata := io.inst_bus.rddata
...
このデバッグモニタポートは、Chiselのコンフィグレーションクラスを通じて有効無効を決定する。もし無効ならばこのモニターはインスタンス化されない。
class CpuDebugMonitor [Conf <: RVConfig](conf: Conf) extends Bundle { override def cloneType: this.type = new CpuDebugMonitor(conf).asInstanceOf[this.type] val hart_id = Output(UInt()) val inst_fetch_req = if (conf.debug == true) Output(Bool()) else Output(UInt(0.W)) val inst_fetch_addr = if (conf.debug == true) Output(UInt(conf.bus_width.W)) else Output(UInt(0.W)) val inst_fetch_ack = if (conf.debug == true) Output(Bool()) else Output(UInt(0.W)) val inst_fetch_rddata = if (conf.debug == true) Output(SInt(32.W)) else Output(SInt(0.W)) val pc_update_cause = if(conf.debug == true) Output(UInt(3.W)) else Output(UInt(0.W)) ...
このポートをどのようにSoC側から監視しようかと思ったのだが、結局はこのポートをVerilogのモジュールで引き取って良い具合なフォーマットに置き換えるのが良いのではないかと思った。
module sim_monitor ( input logic clock, input logic reset, input logic dbg_hart_id, input logic dbg_inst_fetch_req, input logic [31:0] dbg_inst_fetch_addr, ... ); always_ff @ (posedge clock, posedge reset) begin if (reset) begin end else begin $write("HART%01d : ", dbg_hart_id); if (dbg_inst_fetch_req & dbg_inst_fetch_ack) begin
またしてもVerilogのモジュールとChiselのモジュールをどのようにつなげるのかという問題に立ち戻ってくるが、これについてはBlackBoxの機能を用いている。
class CoreTop [Conf <: RVConfig] (conf: Conf, hartid: Int, name: String)(implicit p: Parameters) extends LazyModule { val inst_node = TLClientNode(Seq(TLClientPortParameters(Seq(TLClientParameters(name = name + "_inst"))))) val data_node = TLClientNode(Seq(TLClientPortParameters(Seq(TLClientParameters(name = 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()) // dbg_monitorポートを用意しておく val dbg_monitor = Output(new CpuDebugMonitor(conf)) }) ... // dbg_monitorポートを接続する io.dbg_monitor := cpu.io.dbg_monitor } }
このポートはTestHarness上で接続してしまおう。
val sim_mon0 = Module(new sim_monitor(rv_conf)) sim_mon0.io.clock := clock sim_mon0.io.reset := reset sim_mon0.io.dbg := dut.io.cpu0_dbg val sim_mon1 = Module(new sim_monitor(rv_conf)) sim_mon1.io.clock := clock sim_mon1.io.reset := reset sim_mon1.io.dbg := dut.io.cpu1_dbg
そしてsim_monitor
モジュールをVerilog側で宣言しておけば無事に接続できる、という訳だ。シミュレーションを走らせてみると、一応上手く監視できている様子。ただしRTLが間違っているので出力された情報は全く意味がないが...
99781 HART1 : 99781 HART0 : 11800044 99791 HART1 : 11800044 99791 HART0 : 99801 HART1 : 99801 HART0 : 1180004c 99811 HART1 : 1180004c 99811 HART0 : R30<=0000000011801040 99821 HART1 : R30<=0000000011801040 99821 HART0 : 99831 HART1 :