From 70122a50b88707c207d9420c939f2450c8f35896 Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Fri, 7 Aug 2026 23:55:44 +0200 Subject: [PATCH 1/6] [verilator] Add a Verilator simulation flow for the mesh Simulates the 4x4 mesh with Verilator alongside the existing QuestaSim flow. Independent of it: own targets, own build directory, no vsim-scripts/build-hw step. make verilate build the model make verilate-run test= build + run make clean-verilate remove verilator/build Hierarchical Verilation ----------------------- Flattening the tile 16 times does not build in reasonable time or memory, so the tile is Verilated once into a library and instantiated 16 times, via a hier_block metacomment on a magia_tile_hier wrapper. Consequences: - magia_hier_params.v is empty but required. Verilator only substitutes a prebuilt library when --hierarchical-params-file is given; without it the tile is inlined and the result is a flat model that still builds, links the unused library, and looks fine. verilate-check-hierarchy inspects the built parent for exactly that. - A hierarchy block is opaque to dotted paths from the parent, so the testbench can no longer reach into a tile. magia_tile drives a packed observe_o struct (AXI write channel for prints, ID/EX/WB instructions) that the VIP reads instead. Verilator-only, behind `ifdef VERILATOR. - Parent and child must agree on where simulated time lives. Verilator defines VL_TIME_CONTEXT only as a side effect of --main and only for the parent, so this flow forces it on both; the mismatch segfaults in VlDelayScheduler at time 0. Waveforms --------- The model is always built with --trace-fst; dumping is opt-in per run with VERILATOR_FST=, so a run that asks for nothing pays only a larger binary. There is no way to build without tracing: the non-tracing model diverges from the tracing one on tests that do real memory traffic, for reasons not yet understood, and nothing about that failure announces itself. target/verilator/src/magia_main.cpp owns dumping, replacing the main Verilator generates. A $dumpvars in the testbench runs at time zero, before the first eval() has constructed the hier_block children, so their trace callbacks are never registered and the dump silently contains nothing below i_magia_tile. The main evaluates once, then connects and opens the trace. It also constructs the model as "TOP": the parent looks its children up by scope name, and the empty name Verilator's own main passes matches nothing. Fixes found by this flow ------------------------ - The tile was never reset. Its flops run on a gated sys_clk whose enable is itself reset to 0, so no clock edge arrives while reset is asserted; and in a 2-state simulator rst_n powers up at 0, so clk_rst_gen driving it to 0 at time 0 is not a falling edge either. Neither event `always_ff @(posedge clk_i or negedge rst_ni)` needs ever occurred. Questa is unaffected because rst_n is X until time 0 and X -> 0 is an edge. Only flops with a non-zero reset value showed a symptom: obi_atop_resolver's credit counter came up at 0, held amo_available low, and deadlocked the first L1 access of any program. The Verilator VIP now holds test_mode during reset so the gate passes the clock. - NoC address maps ended at 32'h100000000, which does not fit 32 bits. - local_interconnect sized FILTER_WRITE_R_VALID from N_HWPE alone rather than the widest of N_HWPE/N_DMA/N_CORE. Layout ------ target/verilator/src/ model sources: main, hier_block control, wrapper, VIP verilator/scripts/ file-list filter and the two checkers verilator/verilator.mk the flow; verilator/build/ its output Limits ------ mesh_dv=1 and CV32E40P only. VERILATOR_THREADS>1 is experimental: 8 halved a test, 4 segfaults at time zero. Nothing bounds a hung run. Roughly 1 us of simulated time per wall-clock second. --- .gitignore | 3 +- Bender.yml | 16 + Makefile | 1 + README.md | 88 ++++ hw/mesh/magia.sv | 34 +- hw/mesh/noc/floo_axi_nw_mesh_16x16_noc.sv | 2 +- hw/mesh/noc/floo_axi_nw_mesh_2x2_noc.sv | 2 +- hw/mesh/noc/floo_axi_nw_mesh_32x32_noc.sv | 2 +- hw/mesh/noc/floo_axi_nw_mesh_4x4_noc.sv | 2 +- hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv | 2 +- hw/tile/local_interconnect.sv | 4 +- hw/tile/magia_tile.sv | 44 +- hw/tile/magia_tile_pkg.sv | 21 +- target/sim/src/mesh/magia_tb.sv | 14 +- target/sim/src/mesh/magia_vip.sv | 9 +- target/sim/src/tile/magia_tile_vip.sv | 2 +- target/verilator/src/magia_fixture.sv | 140 ++++++ target/verilator/src/magia_hier.vlt | 3 + target/verilator/src/magia_hier_params.v | 17 + target/verilator/src/magia_main.cpp | 99 ++++ target/verilator/src/magia_tile_hier.sv | 160 +++++++ target/verilator/src/magia_vip.sv | 528 ++++++++++++++++++++++ verilator/scripts/check_filelist.py | 53 +++ verilator/scripts/check_hierarchy.py | 140 ++++++ verilator/scripts/filter_filelist.py | 51 +++ verilator/verilator.mk | 314 +++++++++++++ 26 files changed, 1732 insertions(+), 19 deletions(-) create mode 100644 target/verilator/src/magia_fixture.sv create mode 100644 target/verilator/src/magia_hier.vlt create mode 100644 target/verilator/src/magia_hier_params.v create mode 100644 target/verilator/src/magia_main.cpp create mode 100644 target/verilator/src/magia_tile_hier.sv create mode 100644 target/verilator/src/magia_vip.sv create mode 100644 verilator/scripts/check_filelist.py create mode 100644 verilator/scripts/check_hierarchy.py create mode 100644 verilator/scripts/filter_filelist.py create mode 100644 verilator/verilator.mk 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..e673fb55 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,94 @@ 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 +``` +`verilate-run` compiles the test itself, so step **1** is optional — it is +listed separately only because the model build takes a couple of minutes and +you usually want to do it once. + +**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=8 +``` + +`VERILATOR_THREADS` must be identical on the build and the run: it is compiled into the +model, and the two commands must agree or the model is rebuilt. + +**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..3aac9b86 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'h80000000},// 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..30251539 100644 --- a/target/sim/src/mesh/magia_vip.sv +++ b/target/sim/src/mesh/magia_vip.sv @@ -223,10 +223,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/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..cf2b9f7a --- /dev/null +++ b/target/verilator/src/magia_vip.sv @@ -0,0 +1,528 @@ +/* + * 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 **/ +/*******************************************************/ + + // Held during reset so the tile's gated sys_clk keeps running while rst_n is + // low. Without it the tile is never reset at all: its clock gate is enabled + // by a flop that itself resets to 0, so sys_clk produces its first edge only + // after reset is released, and in a 2-state simulator rst_n powers up at 0 so + // clk_rst_gen's drive to 1'b0 at time 0 is not a falling edge either. The + // tile therefore sees neither of the two events `always_ff @(posedge clk_i or + // negedge rst_ni)` needs. Questa is unaffected because rst_n is X until time + // 0 and X -> 0 is a falling edge. + assign test_mode = ~rst_n; + 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 **/ +/*******************************************************/ + + clk_rst_gen #( + .ClkPeriod ( CLK_PERIOD ), + .RstClkCycles ( RST_CYCLES ) + ) i_clk_rst_sys ( + .clk_o ( clk ), + .rst_no ( rst_n ) + ); + +/*******************************************************/ +/** 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 + + task wait_for_reset; + @(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..c83caabe --- /dev/null +++ b/verilator/verilator.mk @@ -0,0 +1,314 @@ +# 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 build + run test= +# 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. +VERILATOR_SRC := $(MAGIA_ROOT)/target/verilator/src +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 ?= 4 +# Simulation threads. Experimental: 8 halved a test, 4 segfaults at time zero. +# Leave at 1. +VERILATOR_THREADS ?= 1 +# 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 ?= -O2 +# 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-unoptflat -Wno-blkandnblk -Wno-ENUMVALUE \ + --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_exclude_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. +$(VERILATOR_RAW_FLIST): Bender.yml Bender.lock Makefile bender_common.mk \ + bender_sim.mk bender_synth.mk bender_profile.mk $(VERILATOR_BENDER_STAMP) | $(VERILATOR_BUILD_DIR) + $(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) + +# 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: verilate all + @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) From 38f74a0c3810cbdef2874f7ed599f660fff83963 Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Sat, 8 Aug 2026 13:53:23 +0200 Subject: [PATCH 2/6] [tb] Give rst_n a real falling edge in the mesh testbenches clk_rst_gen drives rst_no to 1'b0 from an initial block. In a simulator whose signals power up at 0 (e.g. Verilator) 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 works thanks to its X -> 0 transition at time 0. The Verilator VIP worked around the symptom by holding test_mode high while rst_n was low, which forces test_en_i on every tc_clk_gating in the tile and un-gates its clocks during reset. Because the gate enable is a latch that can only re-evaluate on a low clock phase, one posedge still escaped after reset had already been released. This caused problems with the Spatz i-cache, and likely other subsystems that did not work correctly in Verilator simulation (e.g., the long-running mesh_mm_test is currently looking fixed by this commit) This commit generates clock and reset in the VIP instead, with the same waveform clk_rst_gen produced: - rst_n starts high, falls, is held for RST_CYCLES, then goes high for good, so the asynchronous reset edge exists in any simulator; - the clock is held low across the initial high phase and starts only once reset is already asserted, so no flop is clocked while its state is unknown (clocking X's there trips assertions in the FlooNoC chimneys at time 0); - the two transitions never land in the same time step, so reset and clock cannot race. wait_for_reset() now waits for the falling edge first; rst_n is high at power-up and a bare @(posedge rst_n) would return at time 0, before reset had happened. Applied to both mesh testbenches, since this is a portability defect rather than a Verilator one, and the test_mode workaround is dropped. hello_mesh_spatz_test now passes on both: 16/16 tiles start and complete the Spatz task, exit code 0, and the two simulators' output matches message for message. hello_mesh and inter_l1_test also pass under Verilator. --- target/sim/src/mesh/magia_vip.sv | 43 +++++++++++++++++++++---- target/verilator/src/magia_vip.sv | 53 +++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/target/sim/src/mesh/magia_vip.sv b/target/sim/src/mesh/magia_vip.sv index 30251539..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 diff --git a/target/verilator/src/magia_vip.sv b/target/verilator/src/magia_vip.sv index cf2b9f7a..a4f8287d 100644 --- a/target/verilator/src/magia_vip.sv +++ b/target/verilator/src/magia_vip.sv @@ -79,15 +79,7 @@ module magia_vip /** Hardwired Signals Beginning **/ /*******************************************************/ - // Held during reset so the tile's gated sys_clk keeps running while rst_n is - // low. Without it the tile is never reset at all: its clock gate is enabled - // by a flop that itself resets to 0, so sys_clk produces its first edge only - // after reset is released, and in a 2-state simulator rst_n powers up at 0 so - // clk_rst_gen's drive to 1'b0 at time 0 is not a falling edge either. The - // tile therefore sees neither of the two events `always_ff @(posedge clk_i or - // negedge rst_ni)` needs. Questa is unaffected because rst_n is X until time - // 0 and X -> 0 is a falling edge. - assign test_mode = ~rst_n; + assign test_mode = 1'b0; assign tile_enable = 1'b1; assign scan_cg_en = 1'b0; assign mtvec_addr = '0; @@ -103,13 +95,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 **/ @@ -127,7 +145,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 From 5c241440733b9205bd4fc242c919fc832e595d0c Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Sat, 8 Aug 2026 15:18:10 +0200 Subject: [PATCH 3/6] Fix end address clearly broken in Verilator commit. --- hw/mesh/noc/floo_axi_nw_mesh_8x8_noc.sv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3aac9b86..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'h80000000},// 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 From ed8ae079bf4965225eadde8b665e563f2a3098d6 Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Sat, 8 Aug 2026 15:57:52 +0200 Subject: [PATCH 4/6] Disable auto-rebuilding of verilator model with make verilate-run --- README.md | 14 ++++++++------ verilator/verilator.mk | 11 +++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e673fb55..645071ba 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,10 @@ make verilate core=CV32E40P mesh_dv=1 VERILATOR_JOBS=16 ```bash make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test ``` -`verilate-run` compiles the test itself, so step **1** is optional — it is -listed separately only because the model build takes a couple of minutes and -you usually want to do it once. +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 @@ -157,11 +158,12 @@ moment it is moved. ```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=8 +make verilate-run core=CV32E40P mesh_dv=1 test=inter_l1_test ``` -`VERILATOR_THREADS` must be identical on the build and the run: it is compiled into the -model, and the two commands must agree or the model is rebuilt. +`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: diff --git a/verilator/verilator.mk b/verilator/verilator.mk index c83caabe..cd2234e3 100644 --- a/verilator/verilator.mk +++ b/verilator/verilator.mk @@ -5,7 +5,7 @@ # once into a library and instantiated 16 times, so mesh_dv=1 is required. # # make verilate build the model -# make verilate-run build + run test= +# make verilate-run compile + run test= against the built model # make clean-verilate remove verilator/build MAGIA_ROOT ?= $(shell git rev-parse --show-toplevel) @@ -294,10 +294,17 @@ verilate-check-hierarchy: $(VERILATOR_BIN) --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: verilate all +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) \ From c0a5a437f692f267d7e8e718e16815122d15f8d5 Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Sat, 8 Aug 2026 18:42:51 +0200 Subject: [PATCH 5/6] [verilator] Enable the CV32E40P core tracer The tracer could not be compiled by Verilator for two independent reasons, both addressed here. UVM: cv32e40p_tracer.sv includes uvm_macros.svh, imports uvm_pkg and calls `uvm_info/`uvm_fatal purely to report messages -- the trace output itself goes through $fopen/$fwrite. Verilator has no UVM, so the include alone failed the build and the unexpanded macros cascaded into ~20 secondary syntax errors. Add a stand-in header supplying a uvm_pkg with the verbosity enum and report functions wrapping $info/$warning/$error/$fatal, plus the four macros. `UVM_VERBOSITY defaults to UVM_MEDIUM as uvm_pkg itself does, so the tracer's UVM_DEBUG messages fold away at elaboration and cost nothing; +define+UVM_VERBOSITY=500 brings them back. Reachable only through the Verilator include path, so QuestaSim keeps using the real UVM. Delays: the tile is built as a hierarchical block, and Verilator 5.046 rejects --lib-create combined with --timing and delays. Vendorize the tracer, identical to cv32e40p rev a8206ab except for its two `#` delays. The 0.01 clock skew existed to order the negedge clk_i_d pipeline blocks after the negedge clk_i monitor, which a plain continuous assign preserves by delta; the 0.1ns paced the handoff to the ISS, which is commented out, and nothing waits on the retire event here. Both include dirs are written ahead of Bender's output in the raw file list: `include resolution follows +incdir+ order and gives the including file's own directory no priority, which is what lets the vendorized tracer win over the one in the cv32e40p checkout. Also make the raw file list depend on verilator.mk, which it did not, so recipe edits invalidate it. Verified: make verilate is clean (0 errors, hierarchy check passes) and verilate-run test=hello_mesh finishes with exit code 0, writing correct per-hart trace_core_*.log files. --- target/verilator/include/uvm_macros.svh | 88 ++++ target/verilator/src/cv32e40p_tracer.sv | 588 ++++++++++++++++++++++++ verilator/verilator.mk | 28 +- 3 files changed, 696 insertions(+), 8 deletions(-) create mode 100644 target/verilator/include/uvm_macros.svh create mode 100644 target/verilator/src/cv32e40p_tracer.sv 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/verilator/verilator.mk b/verilator/verilator.mk index cd2234e3..fdd7a53e 100644 --- a/verilator/verilator.mk +++ b/verilator/verilator.mk @@ -13,8 +13,13 @@ 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. +# 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 @@ -30,10 +35,10 @@ VERILATOR_HIER_PARAMS := $(VERILATOR_SRC)/magia_hier_params.v VERILATOR_MAIN := $(VERILATOR_SRC)/magia_main.cpp # Build parallelism (not simulation speed). -VERILATOR_JOBS ?= 4 -# Simulation threads. Experimental: 8 halved a test, 4 segfaults at time zero. +VERILATOR_JOBS ?= 16 +# Simulation threads (4 looks like the best trade-off). # Leave at 1. -VERILATOR_THREADS ?= 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 @@ -99,7 +104,7 @@ VERILATOR_DPI := \ 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_exclude_tracer + -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 @@ -137,10 +142,17 @@ $(VERILATOR_BUILD_DIR)/.bender-check: | $(VERILATOR_BUILD_DIR) $(VERILATOR_BENDER_STAMP): $(VERILATOR_BUILD_DIR)/.bender-check @test -f $@ -# Bender's raw file list, plus the JTAG DPI sources. +# 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 $(VERILATOR_BENDER_STAMP) | $(VERILATOR_BUILD_DIR) - $(BENDER) script verilator $(VERILATOR_BENDER_TARGS) $(bender_defs) -DSYNTHESIS -DVERILATOR > $@.tmp + 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 From b41d07b9c12ab38ee0e840a50019cb5c5424a36c Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Mon, 10 Aug 2026 15:53:13 +0200 Subject: [PATCH 6/6] Verilator performance optimizations. This commit includes - defaulting to 4 threads (which yields ~2x performance improvement compared to 1 thread) - use -O3 --x-assign fast --x-initial fast --no-assert options in verilation - compile with -march=native --- verilator/verilator.mk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/verilator/verilator.mk b/verilator/verilator.mk index fdd7a53e..3d51eef3 100644 --- a/verilator/verilator.mk +++ b/verilator/verilator.mk @@ -43,7 +43,7 @@ VERILATOR_THREADS ?= 4 # on tests that do real memory traffic, for reasons not yet understood. VERILATOR_TRACE_STRUCTS ?= 0 VERILATOR_TRACE_PARAMS ?= 0 -VERILATOR_CFLAGS ?= -O2 +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. @@ -84,7 +84,8 @@ VERILATOR_ARGS = -j $(VERILATOR_JOBS) -Wno-fatal \ -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-unoptflat -Wno-blkandnblk -Wno-ENUMVALUE \ + -Wno-blkandnblk -Wno-ENUMVALUE \ + -O3 --x-assign fast --x-initial fast --no-assert \ --timing --autoflush --threads $(VERILATOR_THREADS) VERILATOR_ARGS += --trace-fst