diff --git a/.gitignore b/.gitignore index 6696595f..d03f86be 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ sw/kernel_pulp/bin/ sw/kernel_pulp/headers_bin/ *.wlf -*.dbg \ No newline at end of file +*.dbg +verilator/build diff --git a/Bender.yml b/Bender.yml index ebb50a06..44eca32c 100644 --- a/Bender.yml +++ b/Bender.yml @@ -170,6 +170,22 @@ sources: - hw/tile/magia_tile_icache_wrap.sv - hw/tile/magia_redmule_wrap.sv - hw/tile/magia_tile.sv + + - target: all(magia_dv, not(standalone_tile), verilator) + files: + - target/verilator/src/magia_tile_hier.sv + # MAGIA + - hw/mesh/magia.sv + # MAGIA DV + - target/sim/src/tile/magia_tile_tb_pkg.sv + - target/sim/src/mesh/magia_tb_pkg.sv + - target/sim/src/mesh/magia_l2_mem_wrapper.sv + - target/verilator/src/magia_vip.sv + - target/verilator/src/magia_fixture.sv + - target/sim/src/mesh/magia_tb.sv + + - target: all(magia_dv, not(standalone_tile), not(verilator)) + files: # MAGIA - hw/mesh/magia.sv # MAGIA DV diff --git a/Makefile b/Makefile index ff1c29bb..dce40399 100644 --- a/Makefile +++ b/Makefile @@ -583,4 +583,5 @@ magia-nonfree-init: cd $(MAGIA_NONFREE_DIR) && git checkout $(MAGIA_NONFREE_COMMIT) if [ "$(MAGIA_NONFREE_DEPS)" -eq "1" ]; then $(MAKE) nonfree-init-dep ; fi +include verilator/verilator.mk -include $(MAGIA_NONFREE_DIR)/nonfree.mk diff --git a/README.md b/README.md index e1f9c47f..645071ba 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,96 @@ make all test=fsync_test make run test=fsync_test ``` +### Simulation with Verilator + +MAGIA can also be simulated with [Verilator](https://verilator.org) instead of +QuestaSim. The flow is independent of the `vsim` one above: it has its own +targets, its own build directory (`verilator/build`), and needs no +`build-hw`/`vsim-scripts` step. + +Verilator builds the mesh **hierarchically**: the tile is compiled once into a +separate library (`magia_tile_hier`) and instantiated 16 times, instead of +being flattened 16 times over. This is what keeps the build tractable, and it +is why the flow requires `mesh_dv=1` — there is no single-tile Verilator +target at this time. + +The following *optional* parameters can be specified: + +`core`: **CV32E40P** (**Default**: CV32E40P). CV32E40X is not supported by this +flow. + +`VERILATOR_JOBS`: **N** (**Default**: 4). Parallelism used to *build* the model. +Unrelated to simulation speed. + +`VERILATOR_THREADS`: **N** (**Default**: 1). Threads the *simulation itself* runs on. See +the note below before changing it. + +`VERILATOR_FST`: **<file>** (**Default**: empty). Dump a waveform to this file. + +**Instructions to build and run a Verilator simulation**: + +**1)** *Build* the model (`MAGIA` folder): +```bash +make verilate core=CV32E40P mesh_dv=1 VERILATOR_JOBS=16 +``` +**2)** *Compile and run* a test (`MAGIA` folder): +```bash +make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test +``` +Step **1** is *required at least once*: `verilate-run` compiles the test but +never the model — it runs the `Vmagia_tb` that is already in +`verilator/build/obj_dir`, and errors out if there is none. +**After changing the RTL, re-run `make verilate` yourself**, or you will keep simulating the old model. + +**Full example**: +```bash +source setup_env.sh +make verilate core=CV32E40P mesh_dv=1 VERILATOR_JOBS=16 +make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test +``` + +Other targets: `verilate-gen` (code generation only), `verilate-build` (native +compile only), `verilate-check-hierarchy` (asserts the model really links the +tile library instead of inlining it), and `clean-verilate`. + +#### Waveforms (FST) + +The model is **always** built with tracing compiled in, so no rebuild is needed +to capture a waveform. Dumping is off until a run asks for it, and a run that +does not ask pays nothing beyond a larger binary: + +```bash +make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test VERILATOR_FST=dump.fst +``` + +The file is written in the test's build directory. Waveforms include the +internals of every tile. + +**Let the simulation reach `$finish`.** A run killed before it gets there +leaves the FST unclosed, and an unclosed FST is not a file you can keep: the hierarchy is still sitting in a `.fst.hier` +companion, so the dump reads only in place and loses every signal name the +moment it is moved. + +#### Multithreaded simulation (experimental) + +`VERILATOR_THREADS=8` runs the model on 8 threads: + +```bash +make verilate core=CV32E40P mesh_dv=1 VERILATOR_JOBS=16 VERILATOR_THREADS=8 +make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test +``` + +`VERILATOR_THREADS` is compiled *into* the model, so it belongs on `verilate` +only — passing it to `verilate-run` does nothing, and does not rebuild anything. +To change the thread count, re-run `make verilate` with the new value. + +**This is experimental and only partially tested.** What is actually known: + +- `VERILATOR_THREADS=8` halved `inter_l1_test` (132 s → 66 s, same result, same + `$finish` time). Only that one test was checked, once. +- `VERILATOR_THREADS=4` **segfaults at time zero**, deterministically, in an + `eval_initial` coroutine — from a clean build, same flags but the count. + ## ⚙️ Architecture ![](doc/MAGIA.png) diff --git a/hw/mesh/magia.sv b/hw/mesh/magia.sv index 2d6d94f9..7731af21 100644 --- a/hw/mesh/magia.sv +++ b/hw/mesh/magia.sv @@ -19,7 +19,9 @@ * MAGIA */ +`ifndef VERILATOR `include "fractal_sync/assign.svh" +`endif module magia import magia_pkg::*; @@ -74,6 +76,9 @@ module magia input floo_wide_t [N_TILES_Y-1:0] l2_noc_wide_i, output floo_wide_t [N_TILES_Y-1:0] l2_noc_wide_o +`ifdef VERILATOR + , output magia_tile_observe_t tile_observe_o[N_TILES] +`endif ); /*******************************************************/ @@ -112,6 +117,7 @@ module magia /*******************************************************/ /** Internal Signal Definitions End **/ +`ifndef VERILATOR /*******************************************************/ /** Interface Definitions Beginning **/ /*******************************************************/ @@ -140,6 +146,7 @@ module magia /*******************************************************/ /** Interface Assignments End **/ +`endif /*******************************************************/ /** Hardwired Signals Beginning **/ /*******************************************************/ @@ -165,16 +172,23 @@ module magia for (genvar i = 0; i < N_TILES_Y; i++) begin: gen_y_tile for (genvar j = 0; j < N_TILES_X; j++) begin: gen_x_tile +`ifdef VERILATOR + magia_tile_hier #( + .N_MEM_BANKS ( N_MEM_BANKS ), + .N_WORDS_BANK ( N_WORDS_BANK ) + ) i_magia_tile ( +`else magia_tile #( .N_MEM_BANKS ( N_MEM_BANKS ), .N_WORDS_BANK ( N_WORDS_BANK ), - + .CORE_ISA ( ), .CORE_A ( ), .CORE_B ( ), .CORE_M ( ), .ERROR_CAP ( ) ) i_magia_tile ( +`endif .clk_i , .rst_ni , .test_mode_i , @@ -209,10 +223,21 @@ module magia .x_id_i ( j ), .y_id_i ( i ), +`ifdef VERILATOR + .ht_fsync_req_o ( ht_tile_fsync_req[i*N_TILES_X+j][0] ), + .ht_fsync_rsp_i ( ht_tile_fsync_rsp[i*N_TILES_X+j][0] ), + .hn_fsync_req_o ( hn_tile_fsync_req[i*N_TILES_X+j] ), + .hn_fsync_rsp_i ( hn_tile_fsync_rsp[i*N_TILES_X+j] ), + .vt_fsync_req_o ( vt_tile_fsync_req[i*N_TILES_X+j][0] ), + .vt_fsync_rsp_i ( vt_tile_fsync_rsp[i*N_TILES_X+j][0] ), + .vn_fsync_req_o ( vn_tile_fsync_req[i*N_TILES_X+j] ), + .vn_fsync_rsp_i ( vn_tile_fsync_rsp[i*N_TILES_X+j] ), +`else .ht_fsync_if_o ( ht_fsync_if[i*N_TILES_X+j] ), .hn_fsync_if_o ( hn_fsync_if[i*N_TILES_X+j] ), .vt_fsync_if_o ( vt_fsync_if[i*N_TILES_X+j] ), .vn_fsync_if_o ( vn_fsync_if[i*N_TILES_X+j] ), +`endif .scan_cg_en_i , @@ -243,11 +268,16 @@ module magia .fetch_enable_i , .core_sleep_o ( core_sleep_o[i*N_TILES_X+j] ), .wu_wfe_i +`ifdef VERILATOR + , .observe_o ( tile_observe_o[i*N_TILES_X+j] ) +`endif ); `ifdef CORE_TRACES `ifdef CV32E40X +`ifndef VERILATOR localparam string core_trace_file_name = $sformatf("%s%0d", "log_file_", i*N_TILES_X+j); defparam i_magia_tile.i_cv32e40x_ctrl_core.rvfi_i.tracer_i.LOGFILE_PATH_PLUSARG = core_trace_file_name; +`endif `endif // Note: cv32e40p tracer generates its own filename: trace_core_{cluster_id}_{core_id}.log `endif @@ -483,4 +513,4 @@ module magia /** FractalSync Network End **/ /*******************************************************/ -endmodule: magia \ No newline at end of file +endmodule: magia diff --git a/hw/mesh/noc/floo_axi_nw_mesh_16x16_noc.sv b/hw/mesh/noc/floo_axi_nw_mesh_16x16_noc.sv index c8eab555..b7254bbb 100644 --- a/hw/mesh/noc/floo_axi_nw_mesh_16x16_noc.sv +++ b/hw/mesh/noc/floo_axi_nw_mesh_16x16_noc.sv @@ -314,7 +314,7 @@ typedef struct packed { } sam_rule_t; localparam sam_rule_t[SamNumRules-1:0] Sam = '{ -'{idx: '{x: 0, y: 15, port_id: 0}, start_addr: 32'hfc000000, end_addr: 32'h100000000},// L2_15_sam_idx +'{idx: '{x: 0, y: 15, port_id: 0}, start_addr: 32'hfc000000, end_addr: 32'hffffffff},// L2_15_sam_idx '{idx: '{x: 0, y: 14, port_id: 0}, start_addr: 32'hf8000000, end_addr: 32'hfc000000},// L2_14_sam_idx '{idx: '{x: 0, y: 13, port_id: 0}, start_addr: 32'hf4000000, end_addr: 32'hf8000000},// L2_13_sam_idx '{idx: '{x: 0, y: 12, port_id: 0}, start_addr: 32'hf0000000, end_addr: 32'hf4000000},// L2_12_sam_idx diff --git a/hw/mesh/noc/floo_axi_nw_mesh_2x2_noc.sv b/hw/mesh/noc/floo_axi_nw_mesh_2x2_noc.sv index eee5c695..fb4995bc 100644 --- a/hw/mesh/noc/floo_axi_nw_mesh_2x2_noc.sv +++ b/hw/mesh/noc/floo_axi_nw_mesh_2x2_noc.sv @@ -48,7 +48,7 @@ typedef struct packed { } sam_rule_t; localparam sam_rule_t[SamNumRules-1:0] Sam = '{ -'{idx: '{x: 0, y: 1, port_id: 0}, start_addr: 32'he0000000, end_addr: 32'h100000000},// L2_1_sam_idx +'{idx: '{x: 0, y: 1, port_id: 0}, start_addr: 32'he0000000, end_addr: 32'hffffffff},// L2_1_sam_idx '{idx: '{x: 0, y: 0, port_id: 0}, start_addr: 32'hc0000000, end_addr: 32'he0000000},// L2_0_sam_idx '{idx: '{x: 2, y: 1, port_id: 0}, start_addr: 32'h00300000, end_addr: 32'h00400000},// magia_tile_x1_y1_sam_idx '{idx: '{x: 1, y: 1, port_id: 0}, start_addr: 32'h00200000, end_addr: 32'h00300000},// magia_tile_x0_y1_sam_idx diff --git a/hw/mesh/noc/floo_axi_nw_mesh_32x32_noc.sv b/hw/mesh/noc/floo_axi_nw_mesh_32x32_noc.sv index 985b5dea..13bf1817 100644 --- a/hw/mesh/noc/floo_axi_nw_mesh_32x32_noc.sv +++ b/hw/mesh/noc/floo_axi_nw_mesh_32x32_noc.sv @@ -1098,7 +1098,7 @@ typedef struct packed { } sam_rule_t; localparam sam_rule_t[SamNumRules-1:0] Sam = '{ -'{idx: '{x: 0, y: 31, port_id: 0}, start_addr: 32'hfe000000, end_addr: 32'h100000000},// L2_31_sam_idx +'{idx: '{x: 0, y: 31, port_id: 0}, start_addr: 32'hfe000000, end_addr: 32'hffffffff},// L2_31_sam_idx '{idx: '{x: 0, y: 30, port_id: 0}, start_addr: 32'hfc000000, end_addr: 32'hfe000000},// L2_30_sam_idx '{idx: '{x: 0, y: 29, port_id: 0}, start_addr: 32'hfa000000, end_addr: 32'hfc000000},// L2_29_sam_idx '{idx: '{x: 0, y: 28, port_id: 0}, start_addr: 32'hf8000000, end_addr: 32'hfa000000},// L2_28_sam_idx diff --git a/hw/mesh/noc/floo_axi_nw_mesh_4x4_noc.sv b/hw/mesh/noc/floo_axi_nw_mesh_4x4_noc.sv index e17a6cf7..56952493 100644 --- a/hw/mesh/noc/floo_axi_nw_mesh_4x4_noc.sv +++ b/hw/mesh/noc/floo_axi_nw_mesh_4x4_noc.sv @@ -62,7 +62,7 @@ typedef struct packed { } sam_rule_t; localparam sam_rule_t[SamNumRules-1:0] Sam = '{ -'{idx: '{x: 0, y: 3, port_id: 0}, start_addr: 32'hf0000000, end_addr: 32'h100000000},// L2_3_sam_idx +'{idx: '{x: 0, y: 3, port_id: 0}, start_addr: 32'hf0000000, end_addr: 32'hffffffff},// L2_3_sam_idx '{idx: '{x: 0, y: 2, port_id: 0}, start_addr: 32'he0000000, end_addr: 32'hf0000000},// L2_2_sam_idx '{idx: '{x: 0, y: 1, port_id: 0}, start_addr: 32'hd0000000, end_addr: 32'he0000000},// L2_1_sam_idx '{idx: '{x: 0, y: 0, port_id: 0}, start_addr: 32'hc0000000, end_addr: 32'hd0000000},// L2_0_sam_idx diff --git a/hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv b/hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv index 71d28794..15060161 100644 --- a/hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv +++ b/hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv @@ -114,7 +114,7 @@ typedef struct packed { } sam_rule_t; localparam sam_rule_t[SamNumRules-1:0] Sam = '{ -'{idx: '{x: 0, y: 7, port_id: 0}, start_addr: 32'hf8000000, end_addr: 32'h100000000},// L2_7_sam_idx +'{idx: '{x: 0, y: 7, port_id: 0}, start_addr: 32'hf8000000, end_addr: 32'hffffffff},// L2_7_sam_idx '{idx: '{x: 0, y: 6, port_id: 0}, start_addr: 32'hf0000000, end_addr: 32'hf8000000},// L2_6_sam_idx '{idx: '{x: 0, y: 5, port_id: 0}, start_addr: 32'he8000000, end_addr: 32'hf0000000},// L2_5_sam_idx '{idx: '{x: 0, y: 4, port_id: 0}, start_addr: 32'he0000000, end_addr: 32'he8000000},// L2_4_sam_idx diff --git a/hw/tile/local_interconnect.sv b/hw/tile/local_interconnect.sv index e033396a..4f7019d2 100644 --- a/hw/tile/local_interconnect.sv +++ b/hw/tile/local_interconnect.sv @@ -31,7 +31,9 @@ module local_interconnect parameter int unsigned N_CORE = magia_tile_pkg::N_CORE, parameter int unsigned N_MEM = magia_pkg::N_MEM_BANKS, parameter int unsigned EXPFIFO = magia_tile_pkg::EXPFIFO, - parameter int unsigned FILTER_WRITE_R_VALID[0:N_HWPE-1] = '{default: 0}, + parameter int unsigned FILTER_WRITE_R_VALID[0:((N_HWPE > N_DMA ? N_HWPE : N_DMA) > N_CORE ? + (N_HWPE > N_DMA ? N_HWPE : N_DMA) : N_CORE)-1] = + '{default: 0}, parameter int unsigned MEM_DATA_W = 0, parameter int unsigned MEM_ADDR_W = 0, parameter int unsigned MEM_BYTE_W = 0, diff --git a/hw/tile/magia_tile.sv b/hw/tile/magia_tile.sv index c3397536..161ff914 100644 --- a/hw/tile/magia_tile.sv +++ b/hw/tile/magia_tile.sv @@ -126,12 +126,50 @@ module magia_tile input logic fetch_enable_i, output logic core_sleep_o, input logic wu_wfe_i +`ifdef VERILATOR + , output magia_tile_observe_t observe_o +`endif ); /*******************************************************/ /** Internal Signal Definitions Beginning **/ /*******************************************************/ +`ifdef VERILATOR + // A hierarchical block cannot be observed through parent dotted paths. + always_comb begin + observe_o = '0; + // The print peripheral (0xFFFF_0000/0xFFFF_0004) matches no address rule + // and therefore leaves through the default master port ('0), i.e. the ext + // port. This is the port the pre-hierarchy VIP snooped as + // i_axi_xbar.mst_ports_req_o[0]; observing the OBI port sees no prints. + observe_o.axi_aw_addr = axi_xbar_mst_req[magia_tile_pkg::AXI_MST_EXT_IDX].aw.addr; + observe_o.axi_aw_id = axi_xbar_mst_req[magia_tile_pkg::AXI_MST_EXT_IDX].aw.id; + observe_o.axi_aw_valid = axi_xbar_mst_req[magia_tile_pkg::AXI_MST_EXT_IDX].aw_valid; + observe_o.axi_w_data = axi_xbar_mst_req[magia_tile_pkg::AXI_MST_EXT_IDX].w.data; + observe_o.axi_w_valid = axi_xbar_mst_req[magia_tile_pkg::AXI_MST_EXT_IDX].w_valid; +`ifdef CV32E40X + observe_o.instr_ex = i_cv32e40x_ctrl_core.core_i.id_stage_i.id_ex_pipe_o.instr.bus_resp.rdata; + observe_o.instr_id = i_cv32e40x_ctrl_core.core_i.id_stage_i.if_id_pipe_i.instr.bus_resp.rdata; + observe_o.instr_wb = i_cv32e40x_ctrl_core.core_i.wb_stage_i.ex_wb_pipe_i.instr_valid ? + i_cv32e40x_ctrl_core.core_i.wb_stage_i.ex_wb_pipe_i.instr.bus_resp.rdata : '0; + observe_o.wb_data = observe_o.instr_wb; +`else + // CORE_TRACES is unconditionally defined for the magia_dv target (see + // Bender.yml), so i_cv32e40p_ctrl_core is always a cv32e40p_wrapper + // instance, which instantiates cv32e40p_top as cv32e40p_top_i, which in + // turn instantiates cv32e40p_core as core_i. + observe_o.instr_ex = i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.ex_valid ? + i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.id_stage_i.instr_rdata_i : '0; + observe_o.instr_id = i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.id_stage_i.instr_rdata_i; + observe_o.instr_wb = i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.wb_valid ? + i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.instr_rdata_id : '0; + observe_o.wb_data = i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.wb_valid ? + i_cv32e40p_ctrl_core.cv32e40p_top_i.core_i.regfile_wdata : '0; +`endif + end +`endif + logic[magia_pkg::ADDR_W-1:0] tile_l1_start_addr; logic[magia_pkg::ADDR_W-1:0] tile_l1_end_addr; logic[magia_pkg::ADDR_W-1:0] tile_reserved_start_addr; @@ -237,8 +275,8 @@ module magia_tile magia_tile_pkg::axi_xbar_slv_req_t[magia_tile_pkg::AxiXbarNoSlvPorts-1:0] axi_xbar_slv_req; // Index 2 -> ext, Index 1 -> Core Data, Index 0 -> Core Instruction magia_tile_pkg::axi_xbar_slv_rsp_t[magia_tile_pkg::AxiXbarNoSlvPorts-1:0] axi_xbar_slv_rsp; // Index 2 -> ext, Index 1 -> Core Data, Index 0 -> Core Instruction - magia_pkg::axi_xbar_mst_req_t[magia_tile_pkg::AxiXbarNoMstPorts-1:0] axi_xbar_mst_req; // Index 1 -> ext, Index 0 -> OBI XBAR - magia_pkg::axi_xbar_mst_rsp_t[magia_tile_pkg::AxiXbarNoMstPorts-1:0] axi_xbar_mst_rsp; // Index 1 -> ext, Index 0 -> OBI XBAR + magia_pkg::axi_xbar_mst_req_t[magia_tile_pkg::AxiXbarNoMstPorts-1:0] axi_xbar_mst_req; // Index 0 -> ext, Index 1 -> OBI XBAR, Index 2 -> Spatz bootrom + magia_pkg::axi_xbar_mst_rsp_t[magia_tile_pkg::AxiXbarNoMstPorts-1:0] axi_xbar_mst_rsp; // Index 0 -> ext, Index 1 -> OBI XBAR, Index 2 -> Spatz bootrom logic[magia_tile_pkg::axi_xbar_cfg.NoSlvPorts-1:0] en_default_mst_port; @@ -2671,4 +2709,4 @@ endgenerate -endmodule: magia_tile \ No newline at end of file +endmodule: magia_tile diff --git a/hw/tile/magia_tile_pkg.sv b/hw/tile/magia_tile_pkg.sv index 836db59b..65d911dc 100644 --- a/hw/tile/magia_tile_pkg.sv +++ b/hw/tile/magia_tile_pkg.sv @@ -772,6 +772,25 @@ package magia_tile_pkg; `FSYNC_TYPEDEF_ALL(hn_tile_fsync, logic[FSYNC_NBR_AGGR_W-1:0], logic[FSYNC_NBR_LVL_W-1:0], logic[FSYNC_NBR_ID_W-1:0]) `FSYNC_TYPEDEF_ALL(vn_tile_fsync, logic[FSYNC_NBR_AGGR_W-1:0], logic[FSYNC_NBR_LVL_W-1:0], logic[FSYNC_NBR_ID_W-1:0]) +`ifdef VERILATOR + // Packed observation boundary required by hierarchical Verilation. + typedef struct packed { + logic [magia_pkg::ADDR_W-1:0] axi_aw_addr; + // axi_aw_id is driven from axi_xbar_mst_req_t.aw.id (magia_pkg's NoC AXI + // alias), whose ID width is magia_pkg::AXI_NOC_ID_W. This tile package's + // own AXI_ID_W (3 bits) sizes a *different* xbar (the tile-internal one + // with 5 slave ports) and must not be reused here. + logic [magia_pkg::AXI_NOC_ID_W-1:0] axi_aw_id; + logic axi_aw_valid; + logic [magia_pkg::DATA_W-1:0] axi_w_data; + logic axi_w_valid; + logic [31:0] instr_ex; + logic [31:0] instr_id; + logic [31:0] instr_wb; + logic [31:0] wb_data; + } magia_tile_observe_t; +`endif + /*******************************************************************/ /* Spatz Core Complex Wrapper Types */ /*******************************************************************/ @@ -821,4 +840,4 @@ package magia_tile_pkg; -endpackage: magia_tile_pkg \ No newline at end of file +endpackage: magia_tile_pkg diff --git a/target/sim/src/mesh/magia_tb.sv b/target/sim/src/mesh/magia_tb.sv index 25d82ca5..b2afc64e 100644 --- a/target/sim/src/mesh/magia_tb.sv +++ b/target/sim/src/mesh/magia_tb.sv @@ -28,6 +28,18 @@ module magia_tb; magia_fixture fixture(); +`ifdef VERILATOR + // When running under Verilator this testbench prints nothing until the first + // tile output, so emit a heartbeat to tell a slow run from a stalled one. + // $time is scaled to the module timeunit (1ns), hence the /1000 to get us. + initial begin + forever begin + $display("[INFO] %0d us elapsed in simulation.", $time/1000); + #10000; + end + end +`endif + initial begin // Fetch plusargs or use safe (fail-fast) defaults if (!$value$plusargs("INST_HEX=%s" , inst_hex)) inst_hex = ""; @@ -57,4 +69,4 @@ module magia_tb; end -endmodule: magia_tb \ No newline at end of file +endmodule: magia_tb diff --git a/target/sim/src/mesh/magia_vip.sv b/target/sim/src/mesh/magia_vip.sv index 940ef3dc..d67dd631 100644 --- a/target/sim/src/mesh/magia_vip.sv +++ b/target/sim/src/mesh/magia_vip.sv @@ -93,13 +93,39 @@ module magia_vip /** Clock and Reset Beginning **/ /*******************************************************/ - clk_rst_gen #( - .ClkPeriod ( CLK_PERIOD ), - .RstClkCycles ( RST_CYCLES ) - ) i_clk_rst_sys ( - .clk_o ( clk ), - .rst_no ( rst_n ) - ); + // Clock and reset are generated here rather than by clk_rst_gen, which drives + // rst_no to 1'b0 from an initial block: in a simulator whose signals power up + // at 0 that is not a falling edge, so no `always_ff @(posedge clk_i or negedge + // rst_ni)` in the design is ever reset. A four-state simulator only papers + // over it with an X -> 0 transition, which is luck, not portability. + // + // The sequence below is therefore: rst_n high at power-up, a real 1 -> 0 edge, + // RST_CYCLES of reset, then high for the rest of the simulation. The clock is + // held low across the high phase and only starts once reset is already + // asserted, so no flop is ever clocked while its state is still unknown, and + // the two transitions never land in the same time step. + localparam time RST_ASSERT_TIME = CLK_PERIOD; // rst_n 1 -> 0 here + localparam time CLK_START_TIME = 2 * CLK_PERIOD; // first clock edge here + + // Waveform identical to clk_rst_gen's, integer division included. + initial begin: p_clk_gen + clk = 1'b0; + #(CLK_START_TIME); + forever begin + clk = 1'b1; + #(CLK_PERIOD / 2); + clk = 1'b0; + #((CLK_PERIOD + 1) / 2); + end + end + + initial begin: p_rst_gen + rst_n = 1'b1; + #(RST_ASSERT_TIME); + rst_n = 1'b0; + repeat (RST_CYCLES) @(negedge clk); + rst_n = 1'b1; + end /*******************************************************/ /** Clock and Reset End **/ @@ -117,7 +143,10 @@ module magia_vip $readmemh(image, i_l2_mem.i_l2_mem.mem); endtask: data_preload + // The falling edge first: rst_n is high at power-up (see p_rst_gen), so + // waiting only for its rising edge would return immediately at time 0. task wait_for_reset; + @(negedge rst_n); @(posedge rst_n); @(posedge clk); endtask: wait_for_reset @@ -223,10 +252,11 @@ module magia_vip for (int k = 0; k < 2**magia_tb_pkg::L2_ID_W; k++) begin if (print_line[k] == 1'b1) begin $write("[mhartid %0d] ", i*magia_tb_pkg::N_TILES_X+j); - for (int j = 0; j < chars.size(); j++) begin - if (chars[j].id == k) begin - $write("%c", chars[j].data); - chars.delete(j--); + for (int ch_idx = 0; ch_idx < chars.size(); ch_idx++) begin + if (chars[ch_idx].id == k) begin + $write("%c", chars[ch_idx].data); + chars.delete(ch_idx); + ch_idx--; end end print_line[k] = 1'b0; diff --git a/target/sim/src/tile/magia_tile_vip.sv b/target/sim/src/tile/magia_tile_vip.sv index 42d21b97..64944510 100644 --- a/target/sim/src/tile/magia_tile_vip.sv +++ b/target/sim/src/tile/magia_tile_vip.sv @@ -305,4 +305,4 @@ end /** Instruction Monitor End **/ /*******************************************************/ -endmodule: magia_tile_vip \ No newline at end of file +endmodule: magia_tile_vip diff --git a/target/verilator/include/uvm_macros.svh b/target/verilator/include/uvm_macros.svh new file mode 100644 index 00000000..9342ad66 --- /dev/null +++ b/target/verilator/include/uvm_macros.svh @@ -0,0 +1,88 @@ +// Copyright 2026 ETH Zurich and University of Bologna +// SPDX-License-Identifier: Apache-2.0 +// +// Minimal stand-in for UVM's uvm_macros.svh, for the Verilator flow only. +// +// cv32e40p's tracer (bhv/cv32e40p_tracer.sv) pulls in UVM purely to report +// messages: it includes "uvm_macros.svh", imports uvm_pkg, and calls `uvm_info +// and `uvm_fatal. Nothing else in it touches UVM -- the actual trace output +// goes through $fopen/$fwrite. Verilator has no UVM, so that include alone +// breaks the build whenever CV32E40P_TRACE_EXECUTION is on. +// +// This header supplies just enough to compile: the verbosity enum the tracer +// names (UVM_DEBUG), report functions wrapping $info/$warning/$error/$fatal, +// and macros with UVM's signatures. It is reachable only via the +incdir+ that +// the flow's makefile appends to the Verilator file list, so the QuestaSim flow +// keeps using the real UVM from $UVM_HOME. + +`ifndef MAGIA_VERILATOR_UVM_MACROS_SVH +`define MAGIA_VERILATOR_UVM_MACROS_SVH + +// Message verbosity cutoff, compared against the level passed to `uvm_info. +// UVM_MEDIUM is what uvm_pkg itself defaults to, so out of the box we print +// exactly what a default UVM run prints -- in particular the tracer's UVM_DEBUG +// messages stay off. Both sides of the comparison are elaboration-time +// constants, so the suppressed call sites fold away and cost nothing at run +// time. Override from the file list with +define+UVM_VERBOSITY=500 to get them. +`ifndef UVM_VERBOSITY +`define UVM_VERBOSITY 200 +`endif + +package uvm_pkg; + + typedef enum int { + UVM_NONE = 0, + UVM_LOW = 100, + UVM_MEDIUM = 200, + UVM_HIGH = 300, + UVM_FULL = 400, + UVM_DEBUG = 500 + } uvm_verbosity; + + // Format matches UVM's report server closely enough that existing log-scraping + // keeps working. The file/line come from the call site, passed in by the + // macros; $info's own location would point back into this header. + function automatic void uvm_report_info(string id, string message, string fname, int lineno); + $info("UVM_INFO %s(%0d) @ %0t: [%s] %s", fname, lineno, $time, id, message); + endfunction + + function automatic void uvm_report_warning(string id, string message, string fname, int lineno); + $warning("UVM_WARNING %s(%0d) @ %0t: [%s] %s", fname, lineno, $time, id, message); + endfunction + + function automatic void uvm_report_error(string id, string message, string fname, int lineno); + $error("UVM_ERROR %s(%0d) @ %0t: [%s] %s", fname, lineno, $time, id, message); + endfunction + + function automatic void uvm_report_fatal(string id, string message, string fname, int lineno); + $fatal(1, "UVM_FATAL %s(%0d) @ %0t: [%s] %s", fname, lineno, $time, id, message); + endfunction + +endpackage : uvm_pkg + +// begin/end (rather than a bare call) mirrors the real macros: call sites write +// them both with and without a trailing semicolon, and both must parse. +// uvm_pkg:: is spelled out so the macros also work where it was not imported. + +`define uvm_info(ID, MSG, VERBOSITY) \ + begin \ + if ((VERBOSITY) <= `UVM_VERBOSITY) \ + uvm_pkg::uvm_report_info(ID, MSG, `__FILE__, `__LINE__); \ + end + +`define uvm_warning(ID, MSG) \ + begin \ + uvm_pkg::uvm_report_warning(ID, MSG, `__FILE__, `__LINE__); \ + end + +`define uvm_error(ID, MSG) \ + begin \ + uvm_pkg::uvm_report_error(ID, MSG, `__FILE__, `__LINE__); \ + end + +`define uvm_fatal(ID, MSG) \ + begin \ + uvm_pkg::uvm_report_fatal(ID, MSG, `__FILE__, `__LINE__); \ + end + +`endif // MAGIA_VERILATOR_UVM_MACROS_SVH diff --git a/target/verilator/src/cv32e40p_tracer.sv b/target/verilator/src/cv32e40p_tracer.sv new file mode 100644 index 00000000..0abd2b96 --- /dev/null +++ b/target/verilator/src/cv32e40p_tracer.sv @@ -0,0 +1,588 @@ +// --------------------------------------------------------------------------- +// VENDORIZED COPY -- Verilator flow only. +// +// Byte-identical to cv32e40p's bhv/cv32e40p_tracer.sv at rev a8206ab except for +// the removal of its two `#` delays. The tile is built as a hierarchical +// block, and Verilator 5.046 rejects `--lib-create` combined with `--timing` +// and delays ("Unsupported: --lib-create with --timing and delays"), so the +// upstream file cannot be compiled here at all. +// +// It is picked up because the flow's makefile puts target/verilator/src on the +// include path ahead of the cv32e40p checkout's bhv/, and cv32e40p_wrapper.sv +// pulls the tracer in as `include "cv32e40p_tracer.sv"`. Both changes are +// marked "MAGIA verilator:" below. Re-vendorize on any cv32e40p bump. +// --------------------------------------------------------------------------- + +// Copyright 2018 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +//////////////////////////////////////////////////////////////////////////////// +// Engineer: Andreas Traber - atraber@iis.ee.ethz.ch // +// // +// Additional contributions by: // +// Davide Schiavone - pschiavo@iis.ee.ethz.ch // +// // +// Design Name: RISC-V Tracer // +// Project Name: RI5CY // +// Language: SystemVerilog // +// // +// Description: Traces the executed instructions // +// // +//////////////////////////////////////////////////////////////////////////////// + +`ifdef CV32E40P_TRACE_EXECUTION + +`include "uvm_macros.svh" + +module cv32e40p_tracer + import cv32e40p_pkg::*; + import uvm_pkg::*; +#( + parameter FPU = 0, + parameter ZFINX = 0 +) ( + // Clock and Reset + input logic clk_i, + input logic rst_n, + + input logic [31:0] hart_id_i, + + + input logic [31:0] pc, + input logic [31:0] instr, + input ctrl_state_e controller_state_i, + + input logic compressed, + input logic id_valid, + input logic is_decoding, + input logic is_illegal, + input logic trigger_match, + + input logic [31:0] rs1_value, + input logic [31:0] rs2_value, + input logic [31:0] rs3_value, + + input logic [31:0] rs2_value_vec, + + input logic rd_is_fp, + input logic rs1_is_fp, + input logic rs2_is_fp, + input logic rs3_is_fp, + + input logic ex_valid, + input logic [ 5:0] ex_reg_addr, + input logic ex_reg_we, + input logic [31:0] ex_reg_wdata, + + input logic ex_data_req, + input logic ex_data_gnt, + input logic ex_data_we, + input logic [31:0] ex_data_addr, + input logic [31:0] ex_data_wdata, + input logic data_misaligned, + + input logic ebrk_insn, + input logic debug_mode, + input logic ebrk_force_debug_mode, + + input logic wb_bypass, + + input logic wb_valid, + input logic [ 5:0] wb_reg_addr, + input logic wb_reg_we, + input logic [31:0] wb_reg_wdata, + + input logic [31:0] imm_u_type, + input logic [31:0] imm_uj_type, + input logic [31:0] imm_i_type, + input logic [11:0] imm_iz_type, + input logic [31:0] imm_z_type, + input logic [31:0] imm_s_type, + input logic [31:0] imm_sb_type, + input logic [31:0] imm_s2_type, + input logic [31:0] imm_s3_type, + input logic [31:0] imm_vs_type, + input logic [31:0] imm_vu_type, + input logic [31:0] imm_shuffle_type, + input logic [ 4:0] imm_clip_type, + + input logic apu_en_i, + input logic apu_singlecycle_i, + input logic apu_multicycle_i, + input logic apu_rvalid_i + +); + + import cv32e40p_tracer_pkg::*; + + // Make clock a bit to avoid x->0 transitions in tracer logic + bit clk_i_d, eval_comb; + // MAGIA verilator: was `assign #0.01 clk_i_d = clk_i;`. The delay exists to + // order the `negedge clk_i_d` pipeline blocks after the `negedge clk_i` + // register/memory monitor below; a plain continuous assign keeps that + // ordering by delta. eval_comb's 0.01-wide low pulse after each posedge + // collapses to a delta, which changes nothing: it gates an `always @*` whose + // outputs are only ever sampled on the falling edge, where it reads 1 either + // way. + assign clk_i_d = clk_i; + assign eval_comb = ~(clk_i & ~clk_i_d); + + //event ovp_retire; + //bit use_iss; + + integer f; + string fn; + integer cycles; + logic [5:0] rd, rs1, rs2, rs3, rs4; + + logic [31:0] pc_ex_stage; + logic [31:0] pc_ex_delay_stage; + logic [31:0] pc_wb_stage; + logic [31:0] pc_wb_delay_stage; + logic [31:0] pc_retire_head_q; + + `include "cv32e40p_instr_trace.svh" + + string info_tag; + + event retire; + + instr_trace_t trace_ex; + instr_trace_t trace_wb; + instr_trace_t trace_wb_delay; + instr_trace_t trace_ex_delay; + + bit clear_trace_ex = 0; + bit move_trace_ex_to_trace_wb = 0; + bit clear_trace_wb = 0; + bit move_trace_wb_to_trace_wb_delay = 0; + bit clear_trace_wb_delay = 0; + bit move_trace_ex_to_trace_ex_delay = 0; + bit clear_trace_ex_delay = 0; + bit move_trace_ex_delay_to_trace_wb = 0; + + bit trace_new = 0; + bit trace_new_ebreak = 0; + bit trace_ex_misaligned = 0; + bit trace_ex_retire = 0; + bit trace_ex_wb_bypass = 0; + bit trace_wb_retire = 0; + bit trace_wb_delay_retire = 0; + + bit trace_ex_is_null = 1; + bit trace_ex_delay_is_null = 1; + bit trace_wb_is_null = 1; + bit trace_wb_delay_is_null = 1; + + bit trace_ex_is_delay_instr = 0; + bit trace_wb_is_delay_instr = 0; + + string insn_disas; + logic insn_compressed; + logic insn_wb_bypass; + logic insn_ebreak; + logic [31:0] insn_pc; + logic [31:0] insn_val; + reg_t insn_regs_write [$]; + + instr_trace_t trace_q [$]; + instr_trace_t trace_retire; + + // cycle counter + always_ff @(posedge clk_i, negedge rst_n) begin + if (rst_n == 1'b0) cycles <= 0; + else cycles <= cycles + 1; + end + + initial begin + wait(rst_n == 1'b1); + $sformat(fn, "trace_core_%h.log", hart_id_i); + $sformat(info_tag, "CORE_TRACER %2d", hart_id_i); + $display("[%s] Output filename is: %s", info_tag, fn); + f = $fopen(fn, "w"); + $fwrite(f, + " Time Cycle PC Instr Ctx Decoded instruction Register and memory contents\n"); + end + + //initial begin + // use_iss = 0; + // if ($test$plusargs("USE_ISS")) use_iss = 1; + //end + + always @(trace_ex or trace_ex_delay or trace_wb or trace_wb_delay or trace_retire) begin + pc_ex_stage = (trace_ex != null) ? trace_ex.pc : 'x; + pc_ex_delay_stage = (trace_ex_delay != null) ? trace_ex_delay.pc : 'x; + pc_wb_stage = (trace_wb != null) ? trace_wb.pc : 'x; + pc_wb_delay_stage = (trace_wb_delay != null) ? trace_wb_delay.pc : 'x; + pc_retire_head_q = (trace_retire != null) ? trace_retire.pc : 'x; + end + + always @(trace_wb) + trace_wb_is_delay_instr = (trace_wb != null && is_wb_delay_instr( + trace_wb + )) ? 1 : 0; + + assign rd = {rd_is_fp, instr[11:07]}; + assign rs1 = {rs1_is_fp, instr[19:15]}; + assign rs2 = {rs2_is_fp, instr[24:20]}; + assign rs3 = {rs3_is_fp, instr[29:25]}; + assign rs4 = {rs3_is_fp, instr[31:27]}; + + function void apply_reg_write(instr_trace_t trace, int unsigned reg_addr, int unsigned wdata); + foreach (trace.regs_write[i]) + if (trace.regs_write[i].addr == reg_addr) begin + trace.regs_write[i].value = wdata; + `uvm_info(info_tag, $sformatf( + "Write mapped %0d, %0d:0x%08x pc:0x%08x", i, reg_addr, wdata, trace.pc), + UVM_DEBUG) + end else begin + `uvm_info(info_tag, $sformatf( + "Unmapped write to %0d:0x%08x, expected write to %0d", + reg_addr, + wdata, + trace.regs_write[i].addr + ), UVM_DEBUG) + end + endfunction : apply_reg_write + + function void apply_mem_access(instr_trace_t trace, bit we, int unsigned addr, + int unsigned wdata); + mem_acc_t mem_acc; + + mem_acc.addr = addr; + mem_acc.we = we; + + if (we) mem_acc.wdata = wdata; + else mem_acc.wdata = 'x; + + trace.mem_access.push_back(mem_acc); + endfunction : apply_mem_access + + function instr_trace_t trace_new_instr(); + instr_trace_t trace; + + trace = new(); + trace.init(.cycles(cycles), .pc(pc), .compressed(compressed), .instr(instr)); + + return trace; + endfunction : trace_new_instr + + // Funnel all handoffs to the ISS here, note that this must be automatic + // as multiple retire events may occur at a time (wb_bypass) + always begin + wait(trace_q.size() != 0); + trace_retire = trace_q.pop_front(); + wait(trace_retire.retire != 0); + + if (trace_retire.ebreak) wait(debug_mode == 1); + + // Write signals and data structures used by step-and-compare + insn_regs_write = trace_retire.regs_write; + insn_disas = trace_retire.str; + insn_compressed = trace_retire.compressed; + insn_pc = trace_retire.pc; + insn_val = trace_retire.instr; + insn_wb_bypass = trace_retire.wb_bypass; + + trace_retire.printInstrTrace(); + + ->retire; + // if (use_iss) @(ovp_retire); + // MAGIA verilator: was `#0.1ns;`. It paced the handoff to the ISS, which is + // the commented-out `@(ovp_retire)` above; nothing waits on `retire` here. + // The loop still blocks on `wait(trace_q.size() != 0)` once drained, so + // dropping the delay cannot spin. + end + + // EX stage + always @(negedge clk_i_d or negedge rst_n) begin + if (!rst_n) begin + trace_ex <= null; + trace_ex_is_null <= 1; + end else begin + if (trace_ex_retire) trace_ex.retire = 1; + if (trace_ex_wb_bypass) trace_ex.wb_bypass = 1; + if (trace_ex_misaligned) trace_ex.misaligned = 1; + + if (trace_new_ebreak) begin + instr_trace_t new_instr; + new_instr = trace_new_instr(); + + // The EBREAK bypasses the pipeline so it retirable immediately upon debug_mode entry + new_instr.ebreak = 1; + new_instr.retire = 1; + trace_q.push_back(new_instr); + end + + if (trace_new) begin + instr_trace_t new_instr; + new_instr = trace_new_instr(); + trace_q.push_back(new_instr); + trace_ex <= new_instr; + trace_ex_is_null <= 0; + end else if (clear_trace_ex) begin + trace_ex <= null; + trace_ex_is_null <= 1; + end + end + end + + // EX delay stage + always @(negedge clk_i_d or negedge rst_n) begin + if (!rst_n) begin + trace_ex_delay <= null; + trace_ex_delay_is_null <= 1; + end else begin + if (move_trace_ex_to_trace_ex_delay) begin + trace_ex_delay <= trace_ex; + trace_ex_delay_is_null <= (trace_ex == null) ? 1 : 0; + end else if (clear_trace_ex_delay) begin + trace_ex_delay <= null; + trace_ex_delay_is_null <= 1; + end + end + end + + // WB stage + always @(negedge clk_i_d or negedge rst_n) begin + if (rst_n) begin + if (trace_wb_retire) begin + if (trace_wb != null) begin + trace_wb.retire = 1; + end else begin + `uvm_fatal(info_tag, "Received retire signal with no valid trace_wb instruction"); + end + end + end + end + + always @(negedge clk_i_d or negedge rst_n) begin + if (!rst_n) begin + trace_wb <= null; + trace_wb_is_null <= 1; + end else begin + if (move_trace_ex_to_trace_wb) begin + trace_wb <= trace_ex; + trace_wb_is_null <= (trace_ex == null) ? 1 : 0; + end else if (move_trace_ex_delay_to_trace_wb) begin + trace_wb <= trace_ex_delay; + trace_wb_is_null <= (trace_ex_delay == null) ? 1 : 0; + end else if (clear_trace_wb) begin + trace_wb <= null; + trace_wb_is_null <= 1; + end + end + end + + // WB delay stage + always @(negedge clk_i_d or negedge rst_n) begin + if (!rst_n) begin + trace_wb_delay <= null; + trace_wb_delay_is_null <= 1; + end else begin + if (trace_wb_delay_retire) trace_wb_delay.retire = 1; + + if (move_trace_wb_to_trace_wb_delay) begin + trace_wb_delay <= trace_wb; + trace_wb_delay_is_null <= (trace_wb == null) ? 1 : 0; + end else if (clear_trace_wb_delay) begin + trace_wb_delay <= null; + trace_wb_delay_is_null <= 1; + end + end + end + + function bit is_wb_delay_instr(instr_trace_t trace_wb); + if (trace_wb.str == "mret" || trace_wb.str == "uret" || trace_wb.str == "ebreak" || trace_wb.str == "c.ebreak") + return 1; + + return 0; + endfunction : is_wb_delay_instr + + always @* begin + trace_new = 0; + trace_new_ebreak = 0; + trace_ex_misaligned = 0; + trace_ex_retire = 0; + trace_ex_wb_bypass = 0; + trace_wb_retire = 0; + trace_wb_delay_retire = 0; + + clear_trace_ex = 0; + + move_trace_ex_to_trace_wb = 0; + clear_trace_wb = 0; + + move_trace_wb_to_trace_wb_delay = 0; + clear_trace_wb_delay = 0; + + move_trace_ex_to_trace_ex_delay = 0; + move_trace_ex_delay_to_trace_wb = 0; + clear_trace_ex_delay = 0; + + // ---------------------------------------------- + // WB Delay logic + // ---------------------------------------------- + if (!trace_wb_delay_is_null && eval_comb) begin + // Always retire + trace_wb_delay_retire = 1; + clear_trace_wb_delay = 1; + end + + // ---------------------------------------------- + // WB logic + // ---------------------------------------------- + if (!trace_wb_is_null && eval_comb) begin + if (wb_valid) begin + // Some instructons get an extra cycle to retire + if (trace_wb_is_delay_instr) begin + move_trace_wb_to_trace_wb_delay = 1; + end else begin + trace_wb_retire = 1; + clear_trace_wb = 1; + end + end + end + + // ---------------------------------------------- + // EX Delay logic + // ---------------------------------------------- + // apu_rvalid_i for variable latency apu offloading + // non apu instructions will always go to wb as fast as possible (i.e. next cycle) + if (!trace_ex_delay_is_null && eval_comb) begin + if (apu_rvalid_i || !trace_ex_delay.is_apu) begin + move_trace_ex_delay_to_trace_wb = 1; + clear_trace_ex_delay = 1; + end + end + + // ---------------------------------------------- + // EX logic + // ---------------------------------------------- + + // New instruction created if is a legal decoded instruction + if (id_valid && is_decoding && !is_illegal) begin + trace_new = 1; + // Create a new EBREAK instruuction (will bypass pipeline execution) + end else if (is_decoding && !trigger_match && ebrk_insn && (ebrk_force_debug_mode || debug_mode)) begin + trace_new_ebreak = 1; + end + + // Instruction remains in the pipeline for one more cycle if misaligned + if (!trace_ex_is_null && eval_comb && ex_valid && data_misaligned) begin + trace_ex_misaligned = 1; + // Instruction bypasses WB - mark as retirable and do not advance + end else if (wb_bypass) begin + trace_ex_retire = 1; + trace_ex_wb_bypass = 1; + clear_trace_ex = 1; + + // Some instructons get an extra cycle to retire + // offload to (potentially) multicycle APU/FPU + end else if (!trace_ex_is_null && eval_comb && apu_en_i && !apu_rvalid_i) begin + move_trace_ex_to_trace_ex_delay = 1; + clear_trace_ex = 1; + + // Instruction leaves EX + end else if (!trace_ex_is_null && eval_comb && ex_valid && !data_misaligned) begin + // if ex delay is already writing to wb then we also delay this one + if (move_trace_ex_delay_to_trace_wb) begin + move_trace_ex_to_trace_ex_delay = 1; + clear_trace_ex = 1; + end else begin + move_trace_ex_to_trace_wb = 1; + clear_trace_ex = 1; + end + end + + if (move_trace_ex_to_trace_wb && move_trace_ex_delay_to_trace_wb) begin + `uvm_info(info_tag, "ex delay stage and ex stage collide", UVM_DEBUG); + end + end + + // Monitors for memory access and register writeback + always @(negedge clk_i or negedge rst_n) begin + if (!rst_n) begin + end else begin + // Register updates in EX + // !wb_valid necessary for CSR to GPR write when OBI Data stalled + if (ex_reg_we && (ex_valid || !wb_valid || apu_rvalid_i)) begin + `uvm_info(info_tag, $sformatf("EX: Reg WR %02d = 0x%08x", ex_reg_addr, ex_reg_wdata), + UVM_DEBUG); + if (!trace_ex_delay_is_null && !trace_ex_delay.got_regs_write && !trace_ex_delay.is_load && + ((!trace_ex_delay.is_apu && (ex_valid || !wb_valid)) || + (trace_ex_delay.is_apu && apu_rvalid_i && (apu_singlecycle_i || apu_multicycle_i)) + ) + ) begin + apply_reg_write(trace_ex_delay, ex_reg_addr, ex_reg_wdata); + trace_ex_delay.got_regs_write = 1; + end else if (!trace_ex_is_null) begin + apply_reg_write(trace_ex, ex_reg_addr, ex_reg_wdata); + if (trace_ex.got_regs_write) begin + `uvm_info(info_tag, $sformatf( + "EX: Multiple Reg WR %02d = 0x%08x", ex_reg_addr, ex_reg_wdata), UVM_DEBUG); + end + trace_ex.got_regs_write = 1; + end else begin + `uvm_info(info_tag, $sformatf( + "EX: Reg WR %02d:0x%08x but no active EX instruction", ex_reg_addr, ex_reg_wdata + ), UVM_DEBUG); + end + end + + // Register updates in WB + if (wb_reg_we) begin + `uvm_info(info_tag, $sformatf("WB: Reg WR %02d = 0x%08x", wb_reg_addr, wb_reg_wdata), + UVM_DEBUG); + if (!trace_ex_delay_is_null && + ((trace_ex_delay.is_load) || + (trace_ex_delay.is_apu && apu_rvalid_i && !apu_singlecycle_i && !apu_multicycle_i) + ) + ) begin + apply_reg_write(trace_ex_delay, wb_reg_addr, wb_reg_wdata); + trace_ex_delay.got_regs_write = 1; + end else if (!trace_wb_is_null && !trace_wb.got_regs_write) begin + if (!trace_wb.is_load || (trace_wb.is_load && wb_valid)) begin + apply_reg_write(trace_wb, wb_reg_addr, wb_reg_wdata); + trace_wb.got_regs_write = 1; + end + end else if (!trace_ex_is_null && !trace_ex.got_regs_write && trace_ex.misaligned) begin + // Do nothing as double load concatenation will be managed by trace_wb + end else begin + `uvm_info(info_tag, $sformatf( + "WB: Reg WR %02d:0x%08x but no active WB instruction", wb_reg_addr, wb_reg_wdata + ), UVM_DEBUG); + end + end + + // Memory access in EX + if (ex_data_req && ex_data_gnt) begin + if (ex_data_we) begin + `uvm_info(info_tag, $sformatf("EX: Mem WR 0x%08x = 0x%08x", ex_data_addr, ex_data_wdata), + UVM_DEBUG); + end else begin + `uvm_info(info_tag, $sformatf("EX: Mem RD 0x%08x", ex_data_addr), UVM_DEBUG); + end + if (trace_ex_is_null) begin + `uvm_info(info_tag, $sformatf( + "EX: Mem %s 0x%08x:0x%08x but no active EX instruction", + ex_data_we ? "WR" : "RD", + ex_data_addr, + ex_reg_wdata + ), UVM_DEBUG); + end else apply_mem_access(trace_ex, ex_data_we, ex_data_addr, ex_data_wdata); + end + end + end + +endmodule : cv32e40p_tracer + + +`endif // CV32E40P_TRACE_EXECUTION diff --git a/target/verilator/src/magia_fixture.sv b/target/verilator/src/magia_fixture.sv new file mode 100644 index 00000000..cdaf6cd3 --- /dev/null +++ b/target/verilator/src/magia_fixture.sv @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023-2024 ETH Zurich and University of Bologna + * + * Licensed under the Solderpad Hardware License, Version 0.51 + * (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: SHL-0.51 + * + * Authors: Victor Isachi + * + * MAGIA Fixture + */ + +module magia_fixture; + + import magia_tile_pkg::*; + import magia_pkg::*; + import magia_tile_tb_pkg::*; + import magia_tb_pkg::*; + import magia_noc_pkg::*; + +/*******************************************************/ +/** Internal Signal Definitions Beginning **/ +/*******************************************************/ + + logic clk; + logic rst_n; + logic test_mode; + logic tile_enable; + + logic scan_cg_en; + + logic[31:0] boot_addr; + logic[31:0] mtvec_addr; + logic[31:0] dm_halt_addr; + logic[31:0] dm_exception_addr; + logic[ 3:0] mimpid_patch; + + logic[63:0] mcycle[magia_tb_pkg::N_TILES]; + logic[63:0] time_var; + + logic[magia_pkg::N_IRQ-1:0] irq[magia_tb_pkg::N_TILES]; + + logic debug_req; + logic debug_havereset[magia_tb_pkg::N_TILES]; + logic debug_running[magia_tb_pkg::N_TILES]; + logic debug_halted[magia_tb_pkg::N_TILES]; + logic debug_pc_valid[magia_tb_pkg::N_TILES]; + logic[31:0] debug_pc[magia_tb_pkg::N_TILES]; + + logic fetch_enable; + logic core_sleep[magia_tb_pkg::N_TILES]; + + logic wu_wfe; + + floo_req_t [magia_pkg::N_TILES_Y-1:0] l2_noc_req_o; + floo_rsp_t [magia_pkg::N_TILES_Y-1:0] l2_noc_rsp_i; + floo_req_t [magia_pkg::N_TILES_Y-1:0] l2_noc_req_i; + floo_rsp_t [magia_pkg::N_TILES_Y-1:0] l2_noc_rsp_o; + + floo_wide_t [magia_pkg::N_TILES_Y-1:0] l2_noc_wide_o; + floo_wide_t [magia_pkg::N_TILES_Y-1:0] l2_noc_wide_i; + magia_tile_observe_t tile_observe[magia_tb_pkg::N_TILES]; + +/*******************************************************/ +/** Internal Signal Definitions End **/ +/*******************************************************/ +/** DUT (MAGIA) Beginning **/ +/*******************************************************/ + + magia #( + .N_TILES_Y ( magia_tb_pkg::N_TILES_Y ), + .N_TILES_X ( magia_tb_pkg::N_TILES_X ), + .N_TILES ( magia_tb_pkg::N_TILES ), + .N_MEM_BANKS ( magia_tb_pkg::N_MEM_BANKS ), + .N_WORDS_BANK ( magia_tb_pkg::N_WORDS_BANK ), + .TILE_FSYNC_AGGR_W ( magia_tb_pkg::TILE_FSYNC_AGGR_W ), + .TILE_FSYNC_LVL_W ( magia_tb_pkg::TILE_FSYNC_LVL_W ), + .TILE_FSYNC_ID_W ( magia_tb_pkg::TILE_FSYNC_ID_W ) + ) i_magia ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .test_mode_i ( test_mode ), + .tile_enable_i ( tile_enable ), + + .scan_cg_en_i ( scan_cg_en ), + + .boot_addr_i ( boot_addr ), + .mtvec_addr_i ( mtvec_addr ), + .dm_halt_addr_i ( dm_halt_addr ), + .dm_exception_addr_i ( dm_exception_addr ), + .mimpid_patch_i ( mimpid_patch ), + + .mcycle_o ( mcycle ), + .time_i ( time_var ), + + .irq_i ( irq ), + + .debug_req_i ( debug_req ), + .debug_havereset_o ( debug_havereset ), + .debug_running_o ( debug_running ), + .debug_halted_o ( debug_halted ), + .debug_pc_valid_o ( debug_pc_valid ), + .debug_pc_o ( debug_pc ), + + .fetch_enable_i ( fetch_enable ), + .core_sleep_o ( core_sleep ), + + .wu_wfe_i ( wu_wfe ), + + .l2_noc_req_i ( l2_noc_req_o ), + .l2_noc_rsp_o ( l2_noc_rsp_i ), + .l2_noc_req_o ( l2_noc_req_i ), + .l2_noc_rsp_i ( l2_noc_rsp_o ), + .l2_noc_wide_i ( l2_noc_wide_o ), + .l2_noc_wide_o ( l2_noc_wide_i ), + .tile_observe_o ( tile_observe ) + ); + +/*******************************************************/ +/** DUT (MAGIA) End **/ +/*******************************************************/ +/** VIP Beginning **/ +/*******************************************************/ + + magia_vip vip (.*); + +/*******************************************************/ +/** VIP End **/ +/*******************************************************/ + +endmodule: magia_fixture diff --git a/target/verilator/src/magia_hier.vlt b/target/verilator/src/magia_hier.vlt new file mode 100644 index 00000000..28d27d82 --- /dev/null +++ b/target/verilator/src/magia_hier.vlt @@ -0,0 +1,3 @@ +`verilator_config + +hier_block -module "magia_tile_hier" diff --git a/target/verilator/src/magia_hier_params.v b/target/verilator/src/magia_hier_params.v new file mode 100644 index 00000000..af8a5434 --- /dev/null +++ b/target/verilator/src/magia_hier_params.v @@ -0,0 +1,17 @@ +// Copyright 2026 ETH Zurich and University of Bologna +// SPDX-License-Identifier: Apache-2.0 +// +// Deliberately empty because: Verilator 5.046 only substitutes a prebuilt hier_block +// library for its instances when ParameterizedHierBlocks::m_hierSubRun is set +// (V3Param.cpp:101-103), and that requires --hierarchical-params-file to be +// present in addition to --hierarchical-block. Verilator emits a parameter +// file of its own only for blocks that have `parameter type` parameters +// (V3HierBlock.cpp:282); magia_tile_hier has none, so without this file the +// top-level Verilation silently de-parameterizes magia_tile_hier into an +// inlined magia_tile_hier__ clone per tile and produces a flat model. +// The Verilator engine writes its own parameter files as __hierParameters.v +// (V3HierBlock.cpp:110-112), so this one matches what it would have generated. +// +// The option only needs to name a parseable file; no type parameters have to +// be declared here. Add declarations only if magia_tile_hier ever gains type +// parameters, and keep them in sync with what Verilator would generate. diff --git a/target/verilator/src/magia_main.cpp b/target/verilator/src/magia_main.cpp new file mode 100644 index 00000000..3e3d4e1b --- /dev/null +++ b/target/verilator/src/magia_main.cpp @@ -0,0 +1,99 @@ +// Copyright 2026 ETH Zurich and University of Bologna +// SPDX-License-Identifier: Apache-2.0 + +// Simulation entry point for the hierarchical Verilator build, replacing the +// one Verilator generates with --main. It exists to own waveform dumping, +// which the testbench cannot do correctly for a --hierarchical model. +// +// A hier_block instance is a protect-library child model: it is constructed by +// the DPI call in the generated wrapper during the *first* evaluation of the +// parent, and only then does it register its trace callbacks in the shared +// VerilatedContext. The parent's own trace registration emits an initLib() +// lookup per instance, matched by scope name against those child callbacks, so +// a trace opened from the testbench's time-zero $dumpvars runs that lookup +// before any child exists and silently dumps the parent only. Hence: evaluate +// once, then connect and open. + +#include "verilated.h" + +#include "Vmagia_tb.h" + +#if VM_TRACE +#include "verilated_fst_c.h" +#endif + +#include +#include + +// Mirrors --threads from verilator.mk. It has to match: VerilatedContext sizes +// its pool to every core on the machine when nobody sets it. +#ifndef MAGIA_THREADS +#define MAGIA_THREADS 1 +#endif + +#if VM_TRACE +// Path from +FST=, or nullptr when no waveform was requested. +static const char* fstPath(VerilatedContext* contextp) { + const char* const arg = contextp->commandArgsPlusMatch("FST="); + if (!arg || std::strncmp(arg, "+FST=", 5) != 0 || arg[5] == '\0') return nullptr; + return arg + 5; +} +#endif + +int main(int argc, char** argv, char**) { + Verilated::debug(0); + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(MAGIA_THREADS); + contextp->commandArgs(argc, argv); + +#if VM_TRACE + const char* const fst_path = fstPath(contextp.get()); + // Has to be decided before the model is constructed. + if (fst_path) contextp->traceEverOn(true); +#endif + + // "TOP" is not cosmetic. The parent looks its hier_block children up by + // scope name, building the key as model-name + "." + instance path, while + // each child registers under the $sformatf("%m") of its wrapper. The empty + // name Verilator's generated --main passes yields a leading-dot key that + // matches nothing, the children's trace codes are never allocated, and the + // first dump segfaults inside fstWriterEmitValueChange. Any non-empty name + // works; "TOP" is Verilator's own convention and prefixes every dumped + // signal path with a "TOP" scope. + const std::unique_ptr topp{new Vmagia_tb{contextp.get(), "TOP"}}; + + // Constructs every hier_block child and registers its trace callbacks. + topp->eval(); + +#if VM_TRACE + std::unique_ptr tracep; + if (fst_path) { + VL_PRINTF("[INFO] dumping waveform to %s\n", fst_path); + tracep.reset(new VerilatedFstC); + topp->trace(tracep.get(), 0); + tracep->open(fst_path); + tracep->dump(contextp->time()); + } +#endif + + while (VL_LIKELY(!contextp->gotFinish())) { + if (!topp->eventsPending()) break; + contextp->time(topp->nextTimeSlot()); + topp->eval(); +#if VM_TRACE + if (tracep) tracep->dump(contextp->time()); +#endif + } + + if (VL_LIKELY(!contextp->gotFinish())) { + VL_DEBUG_IF(VL_PRINTF("+ Exiting without $finish; no events left\n");); + } + + topp->final(); +#if VM_TRACE + // Flushes the FST hierarchy and value blocks; skipping it truncates the dump. + if (tracep) tracep->close(); +#endif + contextp->statsPrintSummary(); + return 0; +} diff --git a/target/verilator/src/magia_tile_hier.sv b/target/verilator/src/magia_tile_hier.sv new file mode 100644 index 00000000..bcc24846 --- /dev/null +++ b/target/verilator/src/magia_tile_hier.sv @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2026 ETH Zurich and University of Bologna + * SPDX-License-Identifier: SHL-0.51 + * + * Simulator-neutral packed-port wrapper used as the Verilator hierarchy + * boundary. Interfaces are instantiated entirely inside this module. + */ + +`include "fractal_sync/assign.svh" + +module magia_tile_hier + import magia_tile_pkg::*; + import magia_pkg::*; + import magia_noc_pkg::*; +#( + parameter int unsigned N_MEM_BANKS = magia_pkg::N_MEM_BANKS, + parameter int unsigned N_WORDS_BANK = magia_pkg::N_WORDS_BANK, + // Hierarchical Verilation (see verilator/magia_hier.vlt) only accepts + // integer/floating point/string/type parameters on a hier_block; + // package-scoped enum types (cv32e40x_pkg::rv32_e etc.) are rejected + // even when never overridden. Declare these as plain logic vectors + // sized to match each enum's own base type, keep the same default + // values, and cast back to the real enum type only at the magia_tile + // instantiation below. + parameter logic CORE_ISA = cv32e40x_pkg::RV32I, + parameter logic [1:0] CORE_A = cv32e40x_pkg::A, + parameter logic [1:0] CORE_B = cv32e40x_pkg::ZBA_ZBB_ZBC_ZBS, + parameter logic [1:0] CORE_M = cv32e40x_pkg::M, + parameter logic ERROR_CAP = idma_pkg::NO_ERROR_HANDLING +)( + input logic clk_i, rst_ni, test_mode_i, tile_enable_i, + + input floo_req_t noc_south_req_i, output floo_rsp_t noc_south_rsp_o, + input floo_wide_t noc_south_wide_i, output floo_req_t noc_south_req_o, + input floo_rsp_t noc_south_rsp_i, output floo_wide_t noc_south_wide_o, + input floo_req_t noc_east_req_i, output floo_rsp_t noc_east_rsp_o, + input floo_wide_t noc_east_wide_i, output floo_req_t noc_east_req_o, + input floo_rsp_t noc_east_rsp_i, output floo_wide_t noc_east_wide_o, + input floo_req_t noc_north_req_i, output floo_rsp_t noc_north_rsp_o, + input floo_wide_t noc_north_wide_i, output floo_req_t noc_north_req_o, + input floo_rsp_t noc_north_rsp_i, output floo_wide_t noc_north_wide_o, + input floo_req_t noc_west_req_i, output floo_rsp_t noc_west_rsp_o, + input floo_wide_t noc_west_wide_i, output floo_req_t noc_west_req_o, + input floo_rsp_t noc_west_rsp_i, output floo_wide_t noc_west_wide_o, + + input logic [31:0] x_id_i, y_id_i, + output ht_tile_fsync_req_t ht_fsync_req_o, input ht_tile_fsync_rsp_t ht_fsync_rsp_i, + output hn_tile_fsync_req_t hn_fsync_req_o, input hn_tile_fsync_rsp_t hn_fsync_rsp_i, + output vt_tile_fsync_req_t vt_fsync_req_o, input vt_tile_fsync_rsp_t vt_fsync_rsp_i, + output vn_tile_fsync_req_t vn_fsync_req_o, input vn_tile_fsync_rsp_t vn_fsync_rsp_i, + + input logic scan_cg_en_i, + input logic [31:0] boot_addr_i, mtvec_addr_i, dm_halt_addr_i, dm_exception_addr_i, + input logic [31:0] mhartid_i, + input logic [3:0] mimpid_patch_i, + output logic [63:0] mcycle_o, + input logic [63:0] time_i, + input logic [magia_pkg::N_IRQ-1:0] irq_i, + input logic [magia_tile_pkg::N_CLUSTER_CORES:0] debug_req_i, + output logic debug_havereset_o, debug_running_o, debug_halted_o, + output logic debug_pc_valid_o, + output logic [31:0] debug_pc_o, + input logic fetch_enable_i, + output logic core_sleep_o, + input logic wu_wfe_i, + output magia_tile_observe_t observe_o +); + + fractal_sync_if #(.AGGR_WIDTH(FSYNC_AGGR_W), .LVL_WIDTH(FSYNC_LVL_W), .ID_WIDTH(FSYNC_ID_W)) ht_fsync_if(); + fractal_sync_if #(.AGGR_WIDTH(FSYNC_NBR_AGGR_W), .LVL_WIDTH(FSYNC_NBR_LVL_W), .ID_WIDTH(FSYNC_NBR_ID_W)) hn_fsync_if(); + fractal_sync_if #(.AGGR_WIDTH(FSYNC_AGGR_W), .LVL_WIDTH(FSYNC_LVL_W), .ID_WIDTH(FSYNC_ID_W)) vt_fsync_if(); + fractal_sync_if #(.AGGR_WIDTH(FSYNC_NBR_AGGR_W), .LVL_WIDTH(FSYNC_NBR_LVL_W), .ID_WIDTH(FSYNC_NBR_ID_W)) vn_fsync_if(); + + `FSYNC_ASSIGN_I2S_REQ(ht_fsync_if, ht_fsync_req_o) + `FSYNC_ASSIGN_S2I_RSP(ht_fsync_rsp_i, ht_fsync_if) + `FSYNC_ASSIGN_I2S_REQ(hn_fsync_if, hn_fsync_req_o) + `FSYNC_ASSIGN_S2I_RSP(hn_fsync_rsp_i, hn_fsync_if) + `FSYNC_ASSIGN_I2S_REQ(vt_fsync_if, vt_fsync_req_o) + `FSYNC_ASSIGN_S2I_RSP(vt_fsync_rsp_i, vt_fsync_if) + `FSYNC_ASSIGN_I2S_REQ(vn_fsync_if, vn_fsync_req_o) + `FSYNC_ASSIGN_S2I_RSP(vn_fsync_rsp_i, vn_fsync_if) + + magia_tile #( + .N_MEM_BANKS(N_MEM_BANKS), .N_WORDS_BANK(N_WORDS_BANK), + .CORE_ISA(cv32e40x_pkg::rv32_e'(CORE_ISA)), + .CORE_A(cv32e40x_pkg::a_ext_e'(CORE_A)), + .CORE_B(cv32e40x_pkg::b_ext_e'(CORE_B)), + .CORE_M(cv32e40x_pkg::m_ext_e'(CORE_M)), + .ERROR_CAP(idma_pkg::error_cap_e'(ERROR_CAP)) + ) i_magia_tile ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_mode_i ( test_mode_i ), + .tile_enable_i ( tile_enable_i ), + + .noc_south_req_i ( noc_south_req_i ), + .noc_south_rsp_o ( noc_south_rsp_o ), + .noc_south_wide_i ( noc_south_wide_i ), + .noc_south_req_o ( noc_south_req_o ), + .noc_south_rsp_i ( noc_south_rsp_i ), + .noc_south_wide_o ( noc_south_wide_o ), + + .noc_east_req_i ( noc_east_req_i ), + .noc_east_rsp_o ( noc_east_rsp_o ), + .noc_east_wide_i ( noc_east_wide_i ), + .noc_east_req_o ( noc_east_req_o ), + .noc_east_rsp_i ( noc_east_rsp_i ), + .noc_east_wide_o ( noc_east_wide_o ), + + .noc_north_req_i ( noc_north_req_i ), + .noc_north_rsp_o ( noc_north_rsp_o ), + .noc_north_wide_i ( noc_north_wide_i ), + .noc_north_req_o ( noc_north_req_o ), + .noc_north_rsp_i ( noc_north_rsp_i ), + .noc_north_wide_o ( noc_north_wide_o ), + + .noc_west_req_i ( noc_west_req_i ), + .noc_west_rsp_o ( noc_west_rsp_o ), + .noc_west_wide_i ( noc_west_wide_i ), + .noc_west_req_o ( noc_west_req_o ), + .noc_west_rsp_i ( noc_west_rsp_i ), + .noc_west_wide_o ( noc_west_wide_o ), + + .x_id_i ( x_id_i ), + .y_id_i ( y_id_i ), + + .ht_fsync_if_o ( ht_fsync_if ), + .hn_fsync_if_o ( hn_fsync_if ), + .vt_fsync_if_o ( vt_fsync_if ), + .vn_fsync_if_o ( vn_fsync_if ), + + .scan_cg_en_i ( scan_cg_en_i ), + + .boot_addr_i ( boot_addr_i ), + .mtvec_addr_i ( mtvec_addr_i ), + .dm_halt_addr_i ( dm_halt_addr_i ), + .dm_exception_addr_i ( dm_exception_addr_i ), + .mhartid_i ( mhartid_i ), + .mimpid_patch_i ( mimpid_patch_i ), + + .mcycle_o ( mcycle_o ), + .time_i ( time_i ), + + .irq_i ( irq_i ), + + .debug_req_i ( debug_req_i ), + .debug_havereset_o ( debug_havereset_o ), + .debug_running_o ( debug_running_o ), + .debug_halted_o ( debug_halted_o ), + .debug_pc_valid_o ( debug_pc_valid_o ), + .debug_pc_o ( debug_pc_o ), + + .fetch_enable_i ( fetch_enable_i ), + .core_sleep_o ( core_sleep_o ), + .wu_wfe_i ( wu_wfe_i ), + + .observe_o ( observe_o ) + ); + +endmodule diff --git a/target/verilator/src/magia_vip.sv b/target/verilator/src/magia_vip.sv new file mode 100644 index 00000000..a4f8287d --- /dev/null +++ b/target/verilator/src/magia_vip.sv @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2023-2024 ETH Zurich and University of Bologna + * + * Licensed under the Solderpad Hardware License, Version 0.51 + * (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: SHL-0.51 + * + * Authors: Victor Isachi + * + * MAGIA Verification IP + */ + + `include "axi/assign.svh" + +module magia_vip + import magia_tile_pkg::*; + import magia_pkg::*; + import magia_tile_tb_pkg::*; + import magia_tb_pkg::*; + import magia_noc_pkg::*; +#( + // Timing + parameter time CLK_PERIOD = 5ns, + parameter int unsigned RST_CYCLES = 5, + parameter real T_APPL = 0.1, + parameter real T_TEST = 0.9 +)( + output logic clk, + output logic rst_n, + output logic test_mode, + output logic tile_enable, + + output logic scan_cg_en, + + output logic[31:0] boot_addr, + output logic[31:0] mtvec_addr, + output logic[31:0] dm_halt_addr, + output logic[31:0] dm_exception_addr, + output logic[ 3:0] mimpid_patch, + + input logic[63:0] mcycle[magia_tb_pkg::N_TILES], + output logic[63:0] time_var, + + output logic[magia_pkg::N_IRQ-1:0] irq[magia_tb_pkg::N_TILES], + + output logic debug_req, + input logic debug_havereset[magia_tb_pkg::N_TILES], + input logic debug_running[magia_tb_pkg::N_TILES], + input logic debug_halted[magia_tb_pkg::N_TILES], + input logic debug_pc_valid[magia_tb_pkg::N_TILES], + input logic[31:0] debug_pc[magia_tb_pkg::N_TILES], + + output logic fetch_enable, + input logic core_sleep[magia_tb_pkg::N_TILES], + + output logic wu_wfe, + + input floo_req_t [magia_pkg::N_TILES_Y-1:0] l2_noc_req_i, + output floo_rsp_t [magia_pkg::N_TILES_Y-1:0] l2_noc_rsp_o, + output floo_req_t [magia_pkg::N_TILES_Y-1:0] l2_noc_req_o, + input floo_rsp_t [magia_pkg::N_TILES_Y-1:0] l2_noc_rsp_i, + + input floo_wide_t [magia_pkg::N_TILES_Y-1:0] l2_noc_wide_i, + output floo_wide_t [magia_pkg::N_TILES_Y-1:0] l2_noc_wide_o, + + input magia_tile_observe_t tile_observe[magia_tb_pkg::N_TILES] +); + +/*******************************************************/ +/** Hardwired Signals Beginning **/ +/*******************************************************/ + + assign test_mode = 1'b0; + assign tile_enable = 1'b1; + assign scan_cg_en = 1'b0; + assign mtvec_addr = '0; + assign dm_halt_addr = '0; + assign dm_exception_addr = '0; + assign mimpid_patch = '0; + assign debug_req = 1'b0; + assign wu_wfe = 1'b0; + +/*******************************************************/ +/** Hardwired Signals End **/ +/*******************************************************/ +/** Clock and Reset Beginning **/ +/*******************************************************/ + + // Clock and reset are generated here rather than by clk_rst_gen, which drives + // rst_no to 1'b0 from an initial block: in a simulator whose signals power up + // at 0 that is not a falling edge, so no `always_ff @(posedge clk_i or negedge + // rst_ni)` in the design is ever reset. A four-state simulator only papers + // over it with an X -> 0 transition, which is luck, not portability. + // + // The sequence below is therefore: rst_n high at power-up, a real 1 -> 0 edge, + // RST_CYCLES of reset, then high for the rest of the simulation. The clock is + // held low across the high phase and only starts once reset is already + // asserted, so no flop is ever clocked while its state is still unknown, and + // the two transitions never land in the same time step. + localparam time RST_ASSERT_TIME = CLK_PERIOD; // rst_n 1 -> 0 here + localparam time CLK_START_TIME = 2 * CLK_PERIOD; // first clock edge here + + // Waveform identical to clk_rst_gen's, integer division included. + initial begin: p_clk_gen + clk = 1'b0; + #(CLK_START_TIME); + forever begin + clk = 1'b1; + #(CLK_PERIOD / 2); + clk = 1'b0; + #((CLK_PERIOD + 1) / 2); + end + end + + initial begin: p_rst_gen + rst_n = 1'b1; + #(RST_ASSERT_TIME); + rst_n = 1'b0; + repeat (RST_CYCLES) @(negedge clk); + rst_n = 1'b1; + end + +/*******************************************************/ +/** Clock and Reset End **/ +/*******************************************************/ +/** TB Subroutines Beginning **/ +/*******************************************************/ + + // Preload instruction cache subroutine + task automatic inst_preload(input string image); + $readmemh(image, i_l2_mem.i_l2_mem.mem); + endtask: inst_preload + + // Preload data subroutine + task automatic data_preload(input string image); + $readmemh(image, i_l2_mem.i_l2_mem.mem); + endtask: data_preload + + // The falling edge first: rst_n is high at power-up (see p_rst_gen), so + // waiting only for its rising edge would return immediately at time 0. + task wait_for_reset; + @(negedge rst_n); + @(posedge rst_n); + @(posedge clk); + endtask: wait_for_reset + + task automatic init(input bit[31:0] entry_addr); + for (int unsigned i = 0; i < magia_tb_pkg::N_TILES; i++) + irq[i] = '0; + fetch_enable = 1'b0; + boot_addr = entry_addr; + for (int i = 0; i < magia_tb_pkg::N_TILES; i++) begin + i_l2_mem.i_l2_mem.mem[32'hCCFF_0000 + 2*i] = 8'h00; + i_l2_mem.i_l2_mem.mem[32'hCCFF_0000 + 2*i+1] = 8'h00; + end + #1000; + endtask: init + + task automatic elf_run; + fetch_enable = 1'b1; + #1000; + endtask: elf_run + + task automatic wait_for_eoc(output bit[magia_tb_pkg::N_TILES*16-1:0] exit_code); + bit eoc = 1'b0; + int tile_cnt; + int error = 0; + + do begin + tile_cnt = 0; + //eoc = 1'b1; + for (int i = 0; i < magia_tb_pkg::N_TILES; i++) + if (i_l2_mem.i_l2_mem.mem[32'hCCFF_0000 + 2*i+1][3] == 1'b1) + tile_cnt++; + #10000; + end while(tile_cnt sync_iteration) + completed_sentinels++; + end + always @(completed_sentinels) begin: ex_sync_time_reporter + if (completed_sentinels == magia_tb_pkg::N_TILES) begin + localparam time sentinel_overhead = CLK_PERIOD; // Overhead of the sentinel start/end pair + localparam int unsigned n_pairs = magia_tb_pkg::N_TILES/2; + + automatic time global_last_start = 0; + automatic time global_last_end = 0; + automatic time global_sync_time; + + automatic time row_last_start[magia_tb_pkg::N_TILES_Y] = '{default: 0}; + automatic time row_last_end[magia_tb_pkg::N_TILES_Y] = '{default: 0}; + automatic time row_sync_times[magia_tb_pkg::N_TILES_Y] = '{default: 0}; + automatic time row_sync_time = 0; + + automatic time col_last_start[magia_tb_pkg::N_TILES_X] = '{default: 0}; + automatic time col_last_end[magia_tb_pkg::N_TILES_X] = '{default: 0}; + automatic time col_sync_times[magia_tb_pkg::N_TILES_X] = '{default: 0}; + automatic time col_sync_time = 0; + + automatic time hnbr_last_start[n_pairs] = '{default: 0}; + automatic time hnbr_last_end[n_pairs] = '{default: 0}; + automatic time hnbr_sync_times[n_pairs] = '{default: 0}; + automatic time hnbr_sync_time = 0; + + automatic time vnbr_last_start[n_pairs] = '{default: 0}; + automatic time vnbr_last_end[n_pairs] = '{default: 0}; + automatic time vnbr_sync_times[n_pairs] = '{default: 0}; + automatic time vnbr_sync_time = 0; + + automatic time hring_last_start[n_pairs] = '{default: 0}; + automatic time hring_last_end[n_pairs] = '{default: 0}; + automatic time hring_sync_times[n_pairs] = '{default: 0}; + automatic time hring_sync_time = 0; + + automatic time vring_last_start[n_pairs] = '{default: 0}; + automatic time vring_last_end[n_pairs] = '{default: 0}; + automatic time vring_sync_times[n_pairs] = '{default: 0}; + automatic time vring_sync_time = 0; + + for (int unsigned i = 0; i < magia_tb_pkg::N_TILES; i++) begin + if (global_last_start < start_sentinel[i][sync_iteration]) global_last_start = start_sentinel[i][sync_iteration]; + if (global_last_end < end_sentinel[i][sync_iteration]) global_last_end = end_sentinel[i][sync_iteration]; + end + global_sync_time = global_last_end - global_last_start - sentinel_overhead; + $display("[TB][SYNC PERF][GLOBAL] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", global_sync_time, global_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < magia_tb_pkg::N_TILES_Y; i++) begin + for (int unsigned j = 0; j < magia_tb_pkg::N_TILES_X; j++) begin + if (row_last_start[i] < start_sentinel[i*magia_tb_pkg::N_TILES_X+j][sync_iteration]) row_last_start[i] = start_sentinel[i*magia_tb_pkg::N_TILES_X+j][sync_iteration]; + if (row_last_end[i] < end_sentinel[i*magia_tb_pkg::N_TILES_X+j][sync_iteration]) row_last_end[i] = end_sentinel[i*magia_tb_pkg::N_TILES_X+j][sync_iteration]; + end + row_sync_times[i] = row_last_end[i] - row_last_start[i] - sentinel_overhead; + if (row_sync_time < row_sync_times[i]) row_sync_time = row_sync_times[i]; + end + $display("[TB][SYNC PERF][ROW] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", row_sync_time, row_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < magia_tb_pkg::N_TILES_X; i++) begin + for (int unsigned j = 0; j < magia_tb_pkg::N_TILES_Y; j++) begin + if (col_last_start[i] < start_sentinel[i+j*magia_tb_pkg::N_TILES_X][sync_iteration]) col_last_start[i] = start_sentinel[i+j*magia_tb_pkg::N_TILES_X][sync_iteration]; + if (col_last_end[i] < end_sentinel[i+j*magia_tb_pkg::N_TILES_X][sync_iteration]) col_last_end[i] = end_sentinel[i+j*magia_tb_pkg::N_TILES_X][sync_iteration]; + end + col_sync_times[i] = col_last_end[i] - col_last_start[i] - sentinel_overhead; + if (col_sync_time < col_sync_times[i]) col_sync_time = col_sync_times[i]; + end + $display("[TB][SYNC PERF][COLUMN] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", col_sync_time, col_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < n_pairs; i++) begin + automatic int unsigned src_idx = 2*i+1; + automatic int unsigned dst_idx = 2*i; + hnbr_last_start[i] = start_sentinel[dst_idx][sync_iteration] > start_sentinel[src_idx][sync_iteration] ? start_sentinel[dst_idx][sync_iteration] : start_sentinel[src_idx][sync_iteration]; + hnbr_last_end[i] = end_sentinel[dst_idx][sync_iteration] > end_sentinel[src_idx][sync_iteration] ? end_sentinel[dst_idx][sync_iteration] : end_sentinel[src_idx][sync_iteration]; + hnbr_sync_times[i] = hnbr_last_end[i] - hnbr_last_start[i] - sentinel_overhead; + if (hnbr_sync_time < hnbr_sync_times[i]) hnbr_sync_time = hnbr_sync_times[i]; + end + $display("[TB][SYNC PERF][HNBR] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", hnbr_sync_time, hnbr_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < n_pairs; i++) begin + automatic int unsigned src_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X + magia_tb_pkg::N_TILES_X; + automatic int unsigned dst_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X; + vnbr_last_start[i] = start_sentinel[dst_idx][sync_iteration] > start_sentinel[src_idx][sync_iteration] ? start_sentinel[dst_idx][sync_iteration] : start_sentinel[src_idx][sync_iteration]; + vnbr_last_end[i] = end_sentinel[dst_idx][sync_iteration] > end_sentinel[src_idx][sync_iteration] ? end_sentinel[dst_idx][sync_iteration] : end_sentinel[src_idx][sync_iteration]; + vnbr_sync_times[i] = vnbr_last_end[i] - vnbr_last_start[i] - sentinel_overhead; + if (vnbr_sync_time < vnbr_sync_times[i]) vnbr_sync_time = vnbr_sync_times[i]; + end + $display("[TB][SYNC PERF][VNBR] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", vnbr_sync_time, vnbr_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < n_pairs; i++) begin + automatic int unsigned src_idx; + automatic int unsigned dst_idx; + if (i%(magia_tb_pkg::N_TILES_X/2)) begin + src_idx = 2*i; + dst_idx = 2*i-1; + end else begin + src_idx = 2*i; + dst_idx = 2*i-1 + magia_tb_pkg::N_TILES_X; + end + hring_last_start[i] = start_sentinel[dst_idx][sync_iteration] > start_sentinel[src_idx][sync_iteration] ? start_sentinel[dst_idx][sync_iteration] : start_sentinel[src_idx][sync_iteration]; + hring_last_end[i] = end_sentinel[dst_idx][sync_iteration] > end_sentinel[src_idx][sync_iteration] ? end_sentinel[dst_idx][sync_iteration] : end_sentinel[src_idx][sync_iteration]; + hring_sync_times[i] = hring_last_end[i] - hring_last_start[i] - sentinel_overhead; + if (hring_sync_time < hring_sync_times[i]) hring_sync_time = hring_sync_times[i]; + end + $display("[TB][SYNC PERF][HRING] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", hring_sync_time, hring_sync_time/CLK_PERIOD); + + for (int unsigned i = 0; i < n_pairs; i++) begin + automatic int unsigned src_idx; + automatic int unsigned dst_idx; + if (i%(magia_tb_pkg::N_TILES_Y/2)) begin + src_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X; + dst_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X - magia_tb_pkg::N_TILES_X; + end else begin + src_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X; + dst_idx = i%magia_tb_pkg::N_TILES_X + 2*(i/magia_tb_pkg::N_TILES_X)*magia_tb_pkg::N_TILES_X - magia_tb_pkg::N_TILES_X + magia_tb_pkg::N_TILES; + end + vring_last_start[i] = start_sentinel[dst_idx][sync_iteration] > start_sentinel[src_idx][sync_iteration] ? start_sentinel[dst_idx][sync_iteration] : start_sentinel[src_idx][sync_iteration]; + vring_last_end[i] = end_sentinel[dst_idx][sync_iteration] > end_sentinel[src_idx][sync_iteration] ? end_sentinel[dst_idx][sync_iteration] : end_sentinel[src_idx][sync_iteration]; + vring_sync_times[i] = vring_last_end[i] - vring_last_start[i] - sentinel_overhead; + if (vring_sync_time < vring_sync_times[i]) vring_sync_time = vring_sync_times[i]; + end + $display("[TB][SYNC PERF][VRING] detected completed synchronization in EX stage. Synchronization time %0tns (%0d clock cycles)", vring_sync_time, vring_sync_time/CLK_PERIOD); + + sync_iteration++; + end + end +`endif + +`ifdef PROFILE_SENTINEL + localparam time sentinel_overhead = CLK_PERIOD; // Overhead of the sentinel start/end pair + bit[31:0] curr_instr_wb[magia_tb_pkg::N_TILES]; + time start_sentinel[magia_tb_pkg::N_TILES][$]; + time end_sentinel[magia_tb_pkg::N_TILES][$]; + time sentinel_latency[magia_tb_pkg::N_TILES]; + for (genvar i = 0; i < magia_tb_pkg::N_TILES_Y; i++) begin: gen_tile_instr_monitor_y + for (genvar j = 0; j < magia_tb_pkg::N_TILES_X; j++) begin: gen_tile_instr_monitor_x + assign curr_instr_wb[i*magia_tb_pkg::N_TILES_X+j] = tile_observe[i*magia_tb_pkg::N_TILES_X+j].instr_wb; + always @(curr_instr_wb[i*magia_tb_pkg::N_TILES_X+j]) begin: instr_wb_reporter + if (curr_instr_wb[i*magia_tb_pkg::N_TILES_X+j] == 32'h5AA00013) begin + start_sentinel[i*magia_tb_pkg::N_TILES_X+j].push_back($time); + $display("[TB][mhartid %0d - Tile (%0d, %0d)] Detected sentinel start instruction in WB stage at time %0dns", + i*magia_tb_pkg::N_TILES_X+j, i, j, $time); + end + if (curr_instr_wb[i*magia_tb_pkg::N_TILES_X+j] == 32'h5FF00013) begin + end_sentinel[i*magia_tb_pkg::N_TILES_X+j].push_back($time); + $display("[TB][mhartid %0d - Tile (%0d, %0d)] Detected sentinel end instruction in WB stage at time %0dns", + i*magia_tb_pkg::N_TILES_X+j, i, j, $time); + + if (start_sentinel[i*magia_tb_pkg::N_TILES_X+j].size() < 1) begin + $error("[TB][mhartid %0d - Tile (%0d, %0d)] Detected sentinel end instruction without corresponding sentinel start instruction", + i*magia_tb_pkg::N_TILES_X+j, i, j); + end_sentinel[i*magia_tb_pkg::N_TILES_X+j].pop_back(); + end else begin + sentinel_latency[i*magia_tb_pkg::N_TILES_X+j] = end_sentinel[i*magia_tb_pkg::N_TILES_X+j].pop_back() - start_sentinel[i*magia_tb_pkg::N_TILES_X+j].pop_back() - sentinel_overhead; + $display("[TB][mhartid %0d - Tile (%0d, %0d)] Detected sentinel start-end pair with latency %0tns (%0d clock cycles)", + i*magia_tb_pkg::N_TILES_X+j, i, j, sentinel_latency[i*magia_tb_pkg::N_TILES_X+j], sentinel_latency[i*magia_tb_pkg::N_TILES_X+j]/CLK_PERIOD); + end + end + end + end + end +`endif + +/*******************************************************/ +/** Instruction Monitor End **/ +/*******************************************************/ + +endmodule: magia_vip diff --git a/verilator/scripts/check_filelist.py b/verilator/scripts/check_filelist.py new file mode 100644 index 00000000..df45af19 --- /dev/null +++ b/verilator/scripts/check_filelist.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Fail early when the normalized Verilator input list is inconsistent.""" + +import argparse +from collections import Counter +from pathlib import Path + + +UNSUPPORTED = { + "pad_functional.sv", + "apb_test.sv", + "dmi_test.sv", + "reqrsp_test.sv", + "tcdm_test.sv", + "snitch_icache_l0_tb.sv", +} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("filelist", type=Path) + parser.add_argument("--top", required=True) + parser.add_argument("--dpi", action="append", default=[]) + args = parser.parse_args() + + lines = [line.strip() for line in args.filelist.read_text().splitlines()] + sources = [line for line in lines if line and not line.startswith(("+", "-"))] + errors = [] + + duplicates = [path for path, count in Counter(sources).items() if count > 1] + if duplicates: + errors.append("duplicate source paths: " + ", ".join(duplicates)) + bad = [path for path in sources if Path(path).name in UNSUPPORTED] + if bad: + errors.append("unsupported verification sources: " + ", ".join(bad)) + if not any(Path(path).name == f"{args.top}.sv" for path in sources): + errors.append(f"top source {args.top}.sv is missing") + missing_files = [path for path in sources if not Path(path).is_file()] + if missing_files: + errors.append("missing source files: " + ", ".join(missing_files)) + for dpi in args.dpi: + resolved = str(Path(dpi).resolve()) + if resolved not in sources: + errors.append(f"DPI source is missing from file list: {resolved}") + + if errors: + raise SystemExit("file-list check failed:\n " + "\n ".join(errors)) + print(f"file-list check passed: {len(sources)} unique sources, top={args.top}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verilator/scripts/check_hierarchy.py b/verilator/scripts/check_hierarchy.py new file mode 100644 index 00000000..f78f72cc --- /dev/null +++ b/verilator/scripts/check_hierarchy.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Verify that the hierarchical Verilator build is actually hierarchical. + +Two independent things are checked, because they can and did disagree: + +1. The plan. Parses the authoritative `VM_HIER_LIBS` manifest in the generated + `V_hier.mk` (one `/lib.a` entry per specialization + Verilator actually built), not raw text occurrences of the module name, + which can appear many times in unrelated prerequisites/commands. +2. The built parent, via `--classes-mk`. A child library can be built, + archived and linked while the parent still Verilates its own inlined + `__` specialization of every instance, which is a flat + build wearing a hierarchical build's artifacts. The plan check alone + cannot see that. +""" + +import argparse +from pathlib import Path +import re + + +def parse_hier_libs(text): + match = re.search(r"^VM_HIER_LIBS\s*:=\s*\\\n((?:.*\\\n)*)", text, re.MULTILINE) + if not match: + return [] + names = [] + for line in match.group(1).splitlines(): + m = re.search(r"lib([A-Za-z0-9_]+)\.a", line) + if m: + names.append(m.group(1)) + return names + + +def check_parent_is_hierarchical(classes_mk, obj_dir, module, lib_names): + """Fail if the parent inlined the hier_block instead of using the child.""" + if not classes_mk.is_file(): + raise SystemExit( + f"parent class list not found: {classes_mk}; the stage-2 top " + "Verilation (V.mk, regenerated by V_hier.mk with " + "--hierarchical-block) did not run" + ) + + text = classes_mk.read_text() + + # A parameterized specialization of the hier_block module in the parent's + # own class list means the parent Verilated the tile itself. + inlined = sorted(set(re.findall(r"\b\w*" + re.escape(module) + r"__\w+", text))) + if inlined: + raise SystemExit( + f"parent Verilated its own specialization(s) of '{module}' instead " + f"of linking the prebuilt child librar(y/ies) {', '.join(lib_names)}: " + + ", ".join(inlined[:5]) + + (" ..." if len(inlined) > 5 else "") + + f"\nthis is a flat build; check {classes_mk.parent}/../codegen.log " + "for a HIERBLOCK diagnostic and confirm V.mk was regenerated " + "by V_hier.mk" + ) + + # And the parent must actually name a built child somewhere. + haystacks = [text] + root_header = obj_dir / (classes_mk.name.split("_classes.mk")[0] + "___024root.h") + if root_header.is_file(): + haystacks.append(root_header.read_text()) + if not any(name in h for name in lib_names for h in haystacks): + raise SystemExit( + "parent build never references a built hier_block library (" + + ", ".join(lib_names) + + f"); inspected {classes_mk}" + + (f" and {root_header}" if root_header.is_file() else "") + ) + + print(f"parent check passed: no inlined '{module}' specialization in {classes_mk.name}") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("hier_makefile", type=Path) + parser.add_argument("--module", default="magia_tile_hier") + parser.add_argument("--expected-count", type=int, default=1) + parser.add_argument( + "--classes-mk", + type=Path, + help="generated V_classes.mk; enables the built-parent checks", + ) + parser.add_argument("--obj-dir", type=Path, help="generated output directory") + args = parser.parse_args() + + if args.expected_count < 1: + raise SystemExit("--expected-count must be a positive integer") + + if not args.hier_makefile.is_file(): + raise SystemExit(f"hierarchy makefile not found: {args.hier_makefile}") + + text = args.hier_makefile.read_text() + lib_names = parse_hier_libs(text) + if not lib_names: + raise SystemExit( + f"no VM_HIER_LIBS entries found in {args.hier_makefile}; " + "hierarchical Verilation did not build any hier_block library " + "(the control file's hier_block directive may not have taken effect)" + ) + + module_names = sorted({n for n in lib_names if n.startswith(args.module)}) + if not module_names: + raise SystemExit( + f"no specialization of '{args.module}' found; built libraries: " + + ", ".join(sorted(set(lib_names))) + ) + if len(module_names) != args.expected_count: + raise SystemExit( + f"expected {args.expected_count} specialization(s) of " + f"'{args.module}', found {len(module_names)}: " + + ", ".join(module_names) + ) + + other_names = sorted({n for n in lib_names if not n.startswith(args.module)}) + if other_names: + raise SystemExit( + "unexpected additional hier_block librar" + + ("y" if len(other_names) == 1 else "ies") + + " besides '" + + args.module + + "': " + + ", ".join(other_names) + ) + + if not re.search(r"^hier_build:\s*\$\(VM_HIER_LIBS\)", text, re.MULTILINE): + raise SystemExit("hier_build does not depend on VM_HIER_LIBS") + + print("hierarchy plan check passed: " + ", ".join(module_names)) + + if args.classes_mk is not None: + obj_dir = args.obj_dir if args.obj_dir is not None else args.classes_mk.parent + check_parent_is_hierarchical(args.classes_mk, obj_dir, args.module, module_names) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verilator/scripts/filter_filelist.py b/verilator/scripts/filter_filelist.py new file mode 100644 index 00000000..cc794aa4 --- /dev/null +++ b/verilator/scripts/filter_filelist.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Normalize Bender's Verilator list and remove known-incompatible DV files.""" + +import argparse +from pathlib import Path +import sys + + +EXCLUDED = { + "pad_functional.sv", + "apb_test.sv", + "dmi_test.sv", + "reqrsp_test.sv", + "tcdm_test.sv", + # Not reachable from magia_tb/magia_tile_tb (standalone snitch_icache + # verification), but unlike the flat build, Verilator's hierarchical + # child-block parsing does not lazily skip it and hits an unsupported + # `##[min:max]` cycle-delay-range assertion (Plan 3, Step 6). + "snitch_icache_l0_tb.sv", +} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + seen_sources = set() + result = [] + for raw in sys.stdin: + line = raw.rstrip() + stripped = line.strip() + if not stripped: + continue + if Path(stripped).name in EXCLUDED: + continue + if not stripped.startswith(("+", "-")): + normalized = str(Path(stripped).resolve()) + if normalized in seen_sources: + continue + seen_sources.add(normalized) + line = normalized + result.append(line) + + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text("\n".join(result) + "\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/verilator/verilator.mk b/verilator/verilator.mk new file mode 100644 index 00000000..3d51eef3 --- /dev/null +++ b/verilator/verilator.mk @@ -0,0 +1,334 @@ +# Copyright 2026 ETH Zurich and University of Bologna +# SPDX-License-Identifier: Apache-2.0 +# +# Verilator flow for the MAGIA mesh. Hierarchical only: the tile is Verilated +# once into a library and instantiated 16 times, so mesh_dv=1 is required. +# +# make verilate build the model +# make verilate-run compile + run test= against the built model +# make clean-verilate remove verilator/build + +MAGIA_ROOT ?= $(shell git rev-parse --show-toplevel) +VERILATOR ?= verilator +BASE_PYTHON ?= python3 + +# Model sources live with the Verilator-only RTL; verilator/ holds the build +# system and its output, verilator/scripts/ the host-side checkers. Both dirs +# below also go on the include path, ahead of Bender's -- see the raw flist rule. +VERILATOR_SRC := $(MAGIA_ROOT)/target/verilator/src +# Headers that only exist to make third-party RTL compile here; currently the +# uvm_macros.svh stand-in the cv32e40p tracer needs. Verilator-only on purpose: +# putting it on any other flow's include path would shadow the real UVM. +VERILATOR_INC := $(MAGIA_ROOT)/target/verilator/include +VERILATOR_SCRIPTS := $(MAGIA_ROOT)/verilator/scripts +VERILATOR_BUILD_DIR ?= $(MAGIA_ROOT)/verilator/build +VERILATOR_OBJ_DIR := $(VERILATOR_BUILD_DIR)/obj_dir +VERILATOR_TOP := magia_tb + +# hier_block metacomment marking the tile as a separately Verilated block. +VERILATOR_CONTROL := $(VERILATOR_SRC)/magia_hier.vlt +# Empty on purpose, but required: without it Verilator inlines the tile and +# silently produces a flat model. See the file. +VERILATOR_HIER_PARAMS := $(VERILATOR_SRC)/magia_hier_params.v +# Owns +FST dumping. A testbench $dumpvars cannot see into hier_blocks; see +# the file. +VERILATOR_MAIN := $(VERILATOR_SRC)/magia_main.cpp + +# Build parallelism (not simulation speed). +VERILATOR_JOBS ?= 16 +# Simulation threads (4 looks like the best trade-off). +# Leave at 1. +VERILATOR_THREADS ?= 4 +# Extra trace detail. Tracing itself is always on: a non-tracing model diverges +# on tests that do real memory traffic, for reasons not yet understood. +VERILATOR_TRACE_STRUCTS ?= 0 +VERILATOR_TRACE_PARAMS ?= 0 +VERILATOR_CFLAGS ?= -march=native +# Parent and child must agree on where simulated time lives, or they segfault +# at time 0. Nothing sets VL_TIME_CONTEXT implicitly here, so force it on both. +# MAGIA_THREADS must match --threads: magia_main.cpp sizes the pool with it. +VERILATOR_ABI_CFLAGS := -DVL_TIME_CONTEXT -DMAGIA_THREADS=$(VERILATOR_THREADS) +# Waveform for verilate-run. Empty means no dump and no cost. +VERILATOR_FST ?= +# One tile library is expected; more means a parameter leaked into the boundary. +VERILATOR_EXPECTED_TILE_SPECIALIZATIONS ?= 1 + +# Reject junk early: these size a thread pool and a rebuild. +_VERILATOR_JOBS_NUM := $(strip $(shell expr $(VERILATOR_JOBS) + 0 2>/dev/null)) +ifeq ($(_VERILATOR_JOBS_NUM),) +$(error VERILATOR_JOBS must be a positive integer, got '$(VERILATOR_JOBS)') +endif +ifeq ($(_VERILATOR_JOBS_NUM),0) +$(error VERILATOR_JOBS must be a positive integer, got '$(VERILATOR_JOBS)') +endif + +_VERILATOR_THREADS_NUM := $(strip $(shell expr $(VERILATOR_THREADS) + 0 2>/dev/null)) +ifeq ($(_VERILATOR_THREADS_NUM),) +$(error VERILATOR_THREADS must be a positive integer, got '$(VERILATOR_THREADS)') +endif +ifeq ($(_VERILATOR_THREADS_NUM),0) +$(error VERILATOR_THREADS must be a positive integer, got '$(VERILATOR_THREADS)') +endif + +_VERILATOR_EXPECTED_NUM := $(strip $(shell expr $(VERILATOR_EXPECTED_TILE_SPECIALIZATIONS) + 0 2>/dev/null)) +ifeq ($(_VERILATOR_EXPECTED_NUM),) +$(error VERILATOR_EXPECTED_TILE_SPECIALIZATIONS must be a positive integer) +endif +ifeq ($(_VERILATOR_EXPECTED_NUM),0) +$(error VERILATOR_EXPECTED_TILE_SPECIALIZATIONS must be a positive integer) +endif + +# Warnings waived to get this RTL through; --timing is needed by the testbench. +VERILATOR_ARGS = -j $(VERILATOR_JOBS) -Wno-fatal \ + -Wno-style -Wno-timescalemod -Wno-redefmacro -Wno-implicit \ + -Wno-ascrange -Wno-widthexpand -Wno-widthconcat -Wno-misindent \ + -Wno-pinmissing -Wno-widthtrunc -Wno-unsigned -Wno-cmpconst \ + -Wno-userfatal -Wno-caseincomplete -Wno-combdly -Wno-latch \ + -Wno-blkandnblk -Wno-ENUMVALUE \ + -O3 --x-assign fast --x-initial fast --no-assert \ + --timing --autoflush --threads $(VERILATOR_THREADS) + +VERILATOR_ARGS += --trace-fst +ifeq ($(VERILATOR_TRACE_STRUCTS),1) +VERILATOR_ARGS += --trace-structs +endif +ifeq ($(VERILATOR_TRACE_PARAMS),1) +VERILATOR_ARGS += --trace-params +endif + +# JTAG DPI: plain C, appended to the file list rather than compiled here. +RISCV_DBG_ROOT ?= $(shell $(BENDER) path riscv-dbg) +FRACTAL_SYNC_ROOT ?= $(shell $(BENDER) path fractal_sync) +VERILATOR_DPI := \ + $(RISCV_DBG_ROOT)/tb/remote_bitbang/sim_jtag.c \ + $(RISCV_DBG_ROOT)/tb/remote_bitbang/remote_bitbang.c + +VERILATOR_BENDER_TARGS := $(bender_targs) \ + -t tech_cells_generic_include_deprecated -t verilator -t rtl_sim \ + -t verilator_dpi -t magia_dv -t simulation -t cv32e40p_include_tracer + +VERILATOR_RAW_FLIST := $(VERILATOR_BUILD_DIR)/magia.raw.f +VERILATOR_FLIST := $(VERILATOR_BUILD_DIR)/magia.f +VERILATOR_BENDER_STAMP := $(VERILATOR_BUILD_DIR)/bender.stamp +VERILATOR_CONFIG_STAMP := $(VERILATOR_BUILD_DIR)/config.stamp +VERILATOR_VERSION_LOG := $(VERILATOR_BUILD_DIR)/verilator-version.log +VERILATOR_PARENT_MK := $(VERILATOR_OBJ_DIR)/V$(VERILATOR_TOP).mk +VERILATOR_HIER_MK := $(VERILATOR_OBJ_DIR)/V$(VERILATOR_TOP)_hier.mk +VERILATOR_CLASSES_MK := $(VERILATOR_OBJ_DIR)/V$(VERILATOR_TOP)_classes.mk +VERILATOR_CODEGEN_STAMP := $(VERILATOR_BUILD_DIR)/codegen.stamp +VERILATOR_TRACE_MODE := $(VERILATOR_BUILD_DIR)/trace.mode +VERILATOR_BIN := $(VERILATOR_OBJ_DIR)/V$(VERILATOR_TOP) +VERILATOR_CODEGEN_LOG := $(VERILATOR_BUILD_DIR)/codegen.log +VERILATOR_BUILD_LOG := $(VERILATOR_BUILD_DIR)/build.log +VERILATOR_TIME := $(shell command -v /usr/bin/time 2>/dev/null) + +$(VERILATOR_BUILD_DIR): + mkdir -p $@ + +# Stamps below record configuration identity: rewritten only when the content +# changes, so an unchanged rerun does not invalidate anything downstream. +.PHONY: $(VERILATOR_BUILD_DIR)/.bender-check +$(VERILATOR_BUILD_DIR)/.bender-check: | $(VERILATOR_BUILD_DIR) + @command -v $(BENDER) >/dev/null 2>&1 || { echo "error: bender not found: $(BENDER)" >&2; exit 1; } + @{ \ + echo "path=$$(command -v $(BENDER))"; \ + echo "version=$$($(BENDER) --version)"; \ + echo "targets=$(VERILATOR_BENDER_TARGS)"; \ + echo "defines=$(bender_defs)"; \ + } > $(VERILATOR_BENDER_STAMP).tmp + @if ! cmp -s $(VERILATOR_BENDER_STAMP).tmp $(VERILATOR_BENDER_STAMP) 2>/dev/null; then \ + mv $(VERILATOR_BENDER_STAMP).tmp $(VERILATOR_BENDER_STAMP); \ + else rm -f $(VERILATOR_BENDER_STAMP).tmp; fi + +$(VERILATOR_BENDER_STAMP): $(VERILATOR_BUILD_DIR)/.bender-check + @test -f $@ + +# Bender's raw file list, plus the JTAG DPI sources. Our two include dirs are +# written before Bender's output on purpose: `include resolution follows +# +incdir+ order and gives the including file's own directory no priority, so +# this is what makes the vendorized cv32e40p_tracer.sv in target/verilator/src +# win over the one in the cv32e40p checkout's bhv/. +$(VERILATOR_RAW_FLIST): Bender.yml Bender.lock Makefile bender_common.mk \ + bender_sim.mk bender_synth.mk bender_profile.mk $(MAGIA_ROOT)/verilator/verilator.mk \ + $(VERILATOR_BENDER_STAMP) | $(VERILATOR_BUILD_DIR) + echo +incdir+$(VERILATOR_SRC) > $@.tmp + echo +incdir+$(VERILATOR_INC) >> $@.tmp + $(BENDER) script verilator $(VERILATOR_BENDER_TARGS) $(bender_defs) -DSYNTHESIS -DVERILATOR >> $@.tmp + echo +incdir+$(FRACTAL_SYNC_ROOT)/hw >> $@.tmp + for f in $(VERILATOR_DPI); do echo $$f >> $@.tmp; done + @if ! cmp -s $@.tmp $@ 2>/dev/null; then mv $@.tmp $@; else rm -f $@.tmp; fi + +# filter: drop files Verilator cannot take. check: fail if the filter dropped +# something we need (the top, the DPI) rather than let codegen fail obscurely. +$(VERILATOR_FLIST): $(VERILATOR_RAW_FLIST) \ + $(VERILATOR_SCRIPTS)/filter_filelist.py \ + $(VERILATOR_SCRIPTS)/check_filelist.py | $(VERILATOR_BUILD_DIR) + $(BASE_PYTHON) $(VERILATOR_SCRIPTS)/filter_filelist.py --output $@.tmp < $(VERILATOR_RAW_FLIST) + $(BASE_PYTHON) $(VERILATOR_SCRIPTS)/check_filelist.py $@.tmp \ + --top $(VERILATOR_TOP) \ + --dpi $(RISCV_DBG_ROOT)/tb/remote_bitbang/sim_jtag.c \ + --dpi $(RISCV_DBG_ROOT)/tb/remote_bitbang/remote_bitbang.c + @if ! cmp -s $@.tmp $@ 2>/dev/null; then mv $@.tmp $@; else rm -f $@.tmp; fi + +.PHONY: verilator-bender +verilator-bender: $(VERILATOR_FLIST) + +# Everything that changes the generated model: tools, targets, flags, sources. +# Changing any of it re-runs codegen. 5.046 is the first version with the +# hier_block fixes this flow needs. +.PHONY: $(VERILATOR_BUILD_DIR)/.config-check +$(VERILATOR_BUILD_DIR)/.config-check: | $(VERILATOR_BUILD_DIR) + @command -v $(VERILATOR) >/dev/null 2>&1 || { echo "error: verilator not found: $(VERILATOR)" >&2; exit 1; } + @test -f $(VERILATOR_CONTROL) || { echo "error: missing $(VERILATOR_CONTROL)" >&2; exit 1; } + @version="$$($(VERILATOR) --version)"; \ + numeric="$$(printf '%s\n' "$$version" | grep -oE '[0-9]+\.[0-9]+' | head -1)"; \ + awk -v got="$$numeric" 'BEGIN { split(got,a,"."); exit !((a[1]*1000+a[2]) >= 5046) }' || \ + { echo "error: hierarchical Verilation requires Verilator >= 5.046; got $$version" >&2; exit 1; }; \ + printf '%s\n' "$$version" > $(VERILATOR_VERSION_LOG).tmp + @if ! cmp -s $(VERILATOR_VERSION_LOG).tmp $(VERILATOR_VERSION_LOG) 2>/dev/null; then \ + mv $(VERILATOR_VERSION_LOG).tmp $(VERILATOR_VERSION_LOG); \ + else rm -f $(VERILATOR_VERSION_LOG).tmp; fi + @{ \ + echo "verilator_path=$$(command -v $(VERILATOR))"; \ + echo "verilator_version=$$($(VERILATOR) --version)"; \ + echo "bender_path=$$(command -v $(BENDER))"; \ + echo "bender_version=$$($(BENDER) --version 2>/dev/null)"; \ + echo "top=$(VERILATOR_TOP)"; \ + echo "mesh_dv=$(mesh_dv)"; \ + echo "core=$(core)"; \ + echo "bender_targets=$(VERILATOR_BENDER_TARGS)"; \ + echo "bender_defines=$(bender_defs)"; \ + echo "trace_structs=$(VERILATOR_TRACE_STRUCTS)"; \ + echo "trace_params=$(VERILATOR_TRACE_PARAMS)"; \ + echo "cflags=$(VERILATOR_CFLAGS) $(VERILATOR_ABI_CFLAGS)"; \ + echo "cppflags=$(CPPFLAGS)"; \ + echo "cxxflags=$(CXXFLAGS)"; \ + echo "ldflags=$(LDFLAGS)"; \ + echo "cxx_path=$$(command -v $(CXX) 2>/dev/null || echo $(CXX))"; \ + echo "cxx_version=$$($(CXX) --version 2>/dev/null | head -1)"; \ + echo "objcache=$$(if [ -n "$$OBJCACHE" ]; then printf '%s' "$$OBJCACHE"; else command -v ccache 2>/dev/null || echo none; fi)"; \ + echo "dpi=$(VERILATOR_DPI)"; \ + echo "verilator_args=$(VERILATOR_ARGS) --hierarchical"; \ + echo "control_sha256=$$(sha256sum $(VERILATOR_CONTROL) | cut -d' ' -f1)"; \ + echo "hier_params_sha256=$$(sha256sum $(VERILATOR_HIER_PARAMS) | cut -d' ' -f1)"; \ + echo "main=$(VERILATOR_MAIN)"; \ + } > $(VERILATOR_CONFIG_STAMP).tmp + @if ! cmp -s $(VERILATOR_CONFIG_STAMP).tmp $(VERILATOR_CONFIG_STAMP) 2>/dev/null; then \ + mv $(VERILATOR_CONFIG_STAMP).tmp $(VERILATOR_CONFIG_STAMP); \ + else rm -f $(VERILATOR_CONFIG_STAMP).tmp; fi + +$(VERILATOR_CONFIG_STAMP): $(VERILATOR_BUILD_DIR)/.config-check + @test -f $@ + +# 5.046 does not forward +define/+incdir from -f to the child run; mirror them. +VERILATOR_HIER_FLIST_FLAGS = $(shell grep -E '^\+(define|incdir)' $(VERILATOR_FLIST)) + +# Verilator's own dependency file, rewritten as a rule for the target we own. +VERILATOR_HIER_DEP := $(VERILATOR_OBJ_DIR)/V$(VERILATOR_TOP)__hierVer.d +VERILATOR_HIER_DEP_MK := $(VERILATOR_BUILD_DIR)/hierVer.mk + +-include $(VERILATOR_HIER_DEP_MK) + +# Keep source prerequisites only; entries under obj_dir are products of the very +# rule they would trigger. mv unconditionally: make restarts after remaking an +# included makefile, so this must end up newer or the restart loops. +$(VERILATOR_HIER_DEP_MK): $(VERILATOR_HIER_DEP) | $(VERILATOR_BUILD_DIR) + @{ printf '%s:' '$(VERILATOR_HIER_MK)'; \ + sed -e 's|^[^:]*:||' -e 's|[^ ]*$(VERILATOR_OBJ_DIR)[^ ]*||g' $<; } > $@.tmp \ + && mv -f $@.tmp $@ + +# Stage 1: plan the hierarchy. Writes V_hier.mk and the per-block argument +# files. V.mk is a stage-2 product -- never create, touch or depend on it +# here, or stage 2 is skipped and a flat parent is built instead. +$(VERILATOR_HIER_MK): $(VERILATOR_FLIST) $(VERILATOR_CONFIG_STAMP) \ + $(VERILATOR_CONTROL) $(VERILATOR_HIER_PARAMS) $(VERILATOR_VERSION_LOG) \ + | $(VERILATOR_BUILD_DIR) + cd $(VERILATOR_BUILD_DIR) && \ + { $(if $(VERILATOR_TIME),$(VERILATOR_TIME) -v -o $(VERILATOR_BUILD_DIR)/codegen.time.log,) \ + $(VERILATOR) $(VERILATOR_CONTROL) --hierarchical \ + --hierarchical-params-file $(VERILATOR_HIER_PARAMS) \ + $(VERILATOR_ARGS) $(VERILATOR_HIER_FLIST_FLAGS) \ + --cc --exe $(VERILATOR_MAIN) -Mdir $(VERILATOR_OBJ_DIR) \ + -CFLAGS "$(VERILATOR_CFLAGS) $(VERILATOR_ABI_CFLAGS)" \ + --top-module $(VERILATOR_TOP) \ + -f $(VERILATOR_FLIST); \ + echo $$? > $(VERILATOR_BUILD_DIR)/.codegen.status; } 2>&1 \ + | tee $(VERILATOR_CODEGEN_LOG); \ + exit $$(cat $(VERILATOR_BUILD_DIR)/.codegen.status) + @test -f $(VERILATOR_HIER_MK) + # The planner leaves this file alone when the plan is unchanged, so a merely + # newer prerequisite would re-run Verilator on every make. Touch to converge. + @touch $(VERILATOR_HIER_MK) + +$(VERILATOR_CODEGEN_STAMP): $(VERILATOR_HIER_MK) + @touch $@ + +# Stage 2: native build, driven by Verilator's generated makefile. +# VM_TRACE* are compiler flags the generated makefiles do not track, and tracing +# changes the model's class layout, so objects left over from another trace +# setting would silently mix ABIs. Drop them when it changes; unchanged leaves +# the tree alone, keeping a no-change rebuild a no-op. +$(VERILATOR_BIN): $(VERILATOR_CODEGEN_STAMP) $(VERILATOR_MAIN) + @mode="structs=$(VERILATOR_TRACE_STRUCTS) params=$(VERILATOR_TRACE_PARAMS)"; \ + if [ -d $(VERILATOR_OBJ_DIR) ] && [ "$$mode" != "$$(cat $(VERILATOR_TRACE_MODE) 2>/dev/null)" ]; then \ + echo "trace configuration changed to [$$mode]; dropping stale objects"; \ + find $(VERILATOR_OBJ_DIR) \( -name '*.o' -o -name '*.a' -o -name '*.d' \) -delete; \ + fi; \ + printf '%s\n' "$$mode" > $(VERILATOR_TRACE_MODE) + { $(if $(VERILATOR_TIME),$(VERILATOR_TIME) -v -o $(VERILATOR_BUILD_DIR)/build.time.log,) \ + $(MAKE) -C $(VERILATOR_OBJ_DIR) -f V$(VERILATOR_TOP)_hier.mk -j $(VERILATOR_JOBS); \ + echo $$? > $(VERILATOR_BUILD_DIR)/.build.status; } 2>&1 \ + | tee $(VERILATOR_BUILD_LOG); \ + exit $$(cat $(VERILATOR_BUILD_DIR)/.build.status) + @test -x $@ + +# The flow only exists for the mesh, and CV32E40X would need per-tile trace +# filenames resolved at run time. +ifeq ($(mesh_dv),0) +.PHONY: verilate-gen verilate-build verilate verilate-check-hierarchy verilate-run +verilate-gen verilate-build verilate verilate-check-hierarchy verilate-run: + $(error $@ requires mesh_dv=1) +else ifeq ($(core),CV32E40X) +.PHONY: verilate-gen verilate-build verilate verilate-check-hierarchy verilate-run +verilate-gen verilate-build verilate verilate-check-hierarchy verilate-run: + $(error CV32E40X hierarchical CORE_TRACES needs runtime per-tile filenames) +else +.PHONY: verilate-gen verilate-build verilate +verilate-gen: $(VERILATOR_CODEGEN_STAMP) +verilate-build: $(VERILATOR_BIN) +verilate: $(VERILATOR_BIN) verilate-check-hierarchy + +# Inspects the built parent, not just the plan: inlining the tile instead of +# linking the library still builds, and is a failed hierarchical build. +.PHONY: verilate-check-hierarchy +verilate-check-hierarchy: $(VERILATOR_BIN) + $(BASE_PYTHON) $(VERILATOR_SCRIPTS)/check_hierarchy.py \ + $(VERILATOR_HIER_MK) --module magia_tile_hier \ + --expected-count $(VERILATOR_EXPECTED_TILE_SPECIALIZATIONS) \ + --classes-mk $(VERILATOR_CLASSES_MK) --obj-dir $(VERILATOR_OBJ_DIR) + +# Uses whatever model is already in obj_dir: it deliberately does not depend on +# $(VERILATOR_BIN), so an edit anywhere in the RTL cannot turn a run into a +# multi-minute rebuild. Build the model yourself with `make verilate`. +# Runs in the test's build dir, so a relative VERILATOR_FST lands there. +# Output goes straight to the terminal; nothing bounds a hung run. +.PHONY: verilate-run +verilate-run: all + @test -x $(VERILATOR_BIN) || { \ + echo "error: no Verilator model at $(VERILATOR_BIN)" >&2; \ + echo " build it first: make verilate core=$(core) mesh_dv=$(mesh_dv)" >&2; \ + exit 1; } + @cd $(TEST_BUILD_DIR) && \ + $(VERILATOR_BIN) \ + +INST_HEX=$(inst_hex_name) +DATA_HEX=$(data_hex_name) \ + +INST_ENTRY=$(inst_entry) +DATA_ENTRY=$(data_entry) \ + +BOOT_ADDR=$(boot_addr) +itb_file=$(itb_file) \ + $(if $(VERILATOR_FST),+FST=$(VERILATOR_FST),) +endif + +# Guarded: this is an rm -rf of a variable path. +.PHONY: clean-verilate +clean-verilate: + @test -n "$(VERILATOR_BUILD_DIR)" + @test "$(VERILATOR_BUILD_DIR)" = "$(MAGIA_ROOT)/verilator/build" + rm -rf $(VERILATOR_BUILD_DIR)