From e06d0ec0f33ebcf9108926f452f27ac70ef208d4 Mon Sep 17 00:00:00 2001 From: galtendorfer Date: Thu, 23 Jul 2026 01:13:35 +0200 Subject: [PATCH 1/6] [hw] add MemPool Back2Local routing --- CHANGELOG.md | 1 + config/README.md | 14 +++ config/config.mk | 5 + hardware/Makefile | 7 +- hardware/src/mempool_group.sv | 169 +++++++++++++++++++++++++++++----- hardware/src/mempool_pkg.sv | 3 + hardware/src/mempool_tile.sv | 25 ++++- 7 files changed, 198 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff2e1dd0..50e1b5cf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add support for DRAMsys5.0 co-simulation - Add support for atomics in L2 - Add Dynamic Address Scrambling (DAS) support with configurable partitioning, hardware address scrambler, and software runtime for dynamic heap allocation +- Add Back2Local rerouting mechanism and interconnect extensions to use underutilized tile ports and increase TCDM bandwidth for local accesses ### Changes - Add physical feasible TeraPool configuration with SubGroup hierarchy. diff --git a/config/README.md b/config/README.md index 1e87bb4de..1c9cd7971 100644 --- a/config/README.md +++ b/config/README.md @@ -35,6 +35,20 @@ you can use the following command to make git pick up tracking the file again: git update-index --no-assume-unchanged config/config.mk ``` +## Back2Local + +Back2Local reuses group-level tile ports for TCDM requests that remain in the +source group. It changes the route to memory, not the address mapping, and can +be used with or without DAS. + +| Variable | Default | Description | +|--------------|---------|------------------------------------------| +| `back2local` | `0` | Enable Back2Local for the selected setup | + +The current port selection uses the core index modulo the available ports. It +is a simple baseline policy that can be adapted in +`hardware/src/mempool_tile.sv`. + ## Dynamic Address Scrambling (DAS) Dynamic Address Scrambling (DAS) is a runtime-configurable address mapping diff --git a/config/config.mk b/config/config.mk index 3a4fd33f8..f356a4550 100644 --- a/config/config.mk +++ b/config/config.mk @@ -79,6 +79,11 @@ num_das_partitions ?= 4 # Size of DAS-heap per core das_mem_size ?= 2048 +# Enable Back2Local rerouting for the selected topology. +# The core-index modulo port selection in mempool_tile.sv is a placeholder, +# not an optimized routing policy. Adapt it to the target traffic pattern. +back2local ?= 0 + # This parameter is only used for TeraPool configurations num_sub_groups_per_group ?= 1 remote_group_latency_cycles ?= 7 diff --git a/hardware/Makefile b/hardware/Makefile index 9f1448a24..9303820ea 100644 --- a/hardware/Makefile +++ b/hardware/Makefile @@ -120,6 +120,11 @@ vlog_defs += -DL1_BANK_SIZE=$(l1_bank_size) vlog_defs += -DBOOT_ADDR=32\'d$(boot_addr) vlog_defs += -DDAS=$(das) vlog_defs += -DNUM_DAS_PARTITIONS=$(num_das_partitions) +ifeq ($(config),mempool) +ifeq ($(back2local),1) +vlog_defs += -DBACK2LOCAL +endif +endif # Snitch ISA vlog_defs += -DXPULPIMG=$(xpulpimg) vlog_defs += -DZFINX=$(zfinx) @@ -438,4 +443,4 @@ clean-dasm: rm -rf $(buildpath)/*.dasm clean-trace: - rm -rf $(buildpath)/*.trace \ No newline at end of file + rm -rf $(buildpath)/*.trace diff --git a/hardware/src/mempool_group.sv b/hardware/src/mempool_group.sv index 3b18fa32e..a2548db47 100644 --- a/hardware/src/mempool_group.sv +++ b/hardware/src/mempool_group.sv @@ -642,9 +642,6 @@ module mempool_group logic [NumTilesPerGroup-1:0] tcdm_dma_resp_ready; // Connect the IOs to the tiles' signals - assign tcdm_master_resp[NumGroups-1:1] = tcdm_master_resp_i[NumGroups-1:1]; - assign tcdm_master_resp_valid[NumGroups-1:1] = tcdm_master_resp_valid_i[NumGroups-1:1]; - assign tcdm_master_resp_ready_o[NumGroups-1:1] = tcdm_master_resp_ready[NumGroups-1:1]; assign tcdm_slave_req[NumGroups-1:1] = tcdm_slave_req_i[NumGroups-1:1]; assign tcdm_slave_req_valid[NumGroups-1:1] = tcdm_slave_req_valid_i[NumGroups-1:1]; assign tcdm_slave_req_ready_o[NumGroups-1:1] = tcdm_slave_req_ready[NumGroups-1:1]; @@ -655,6 +652,14 @@ module mempool_group axi_tile_req_t [NumDmasPerGroup-1:0] axi_dma_req; axi_tile_resp_t [NumDmasPerGroup-1:0] axi_dma_resp; + // Tile-facing TCDM input channels. + tcdm_master_resp_t [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_master_resp; + logic [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_master_resp_valid; + logic [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_master_resp_ready; + tcdm_slave_req_t [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_slave_req; + logic [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_slave_req_valid; + logic [NumTilesPerGroup-1:0][NumGroups-1:0] tile_tcdm_slave_req_ready; + for (genvar t = 0; unsigned'(t) < NumTilesPerGroup; t++) begin: gen_tiles tile_id_t id; assign id = (group_id_i << $clog2(NumTilesPerGroup)) | t[idx_width(NumTilesPerGroup)-1:0]; @@ -662,12 +667,6 @@ module mempool_group tcdm_master_req_t [NumGroups-1:0] tran_tcdm_master_req; logic [NumGroups-1:0] tran_tcdm_master_req_valid; logic [NumGroups-1:0] tran_tcdm_master_req_ready; - tcdm_slave_req_t [NumGroups-1:0] tran_tcdm_slave_req; - logic [NumGroups-1:0] tran_tcdm_slave_req_valid; - logic [NumGroups-1:0] tran_tcdm_slave_req_ready; - tcdm_master_resp_t [NumGroups-1:0] tran_tcdm_master_resp; - logic [NumGroups-1:0] tran_tcdm_master_resp_valid; - logic [NumGroups-1:0] tran_tcdm_master_resp_ready; tcdm_slave_resp_t [NumGroups-1:0] tran_tcdm_slave_resp; logic [NumGroups-1:0] tran_tcdm_slave_resp_valid; logic [NumGroups-1:0] tran_tcdm_slave_resp_ready; @@ -686,13 +685,13 @@ module mempool_group .tcdm_master_req_o (tran_tcdm_master_req ), .tcdm_master_req_valid_o (tran_tcdm_master_req_valid ), .tcdm_master_req_ready_i (tran_tcdm_master_req_ready ), - .tcdm_master_resp_i (tran_tcdm_master_resp ), - .tcdm_master_resp_valid_i(tran_tcdm_master_resp_valid ), - .tcdm_master_resp_ready_o(tran_tcdm_master_resp_ready ), + .tcdm_master_resp_i (tile_tcdm_master_resp[t] ), + .tcdm_master_resp_valid_i(tile_tcdm_master_resp_valid[t] ), + .tcdm_master_resp_ready_o(tile_tcdm_master_resp_ready[t] ), // TCDM banks interface - .tcdm_slave_req_i (tran_tcdm_slave_req ), - .tcdm_slave_req_valid_i (tran_tcdm_slave_req_valid ), - .tcdm_slave_req_ready_o (tran_tcdm_slave_req_ready ), + .tcdm_slave_req_i (tile_tcdm_slave_req[t] ), + .tcdm_slave_req_valid_i (tile_tcdm_slave_req_valid[t] ), + .tcdm_slave_req_ready_o (tile_tcdm_slave_req_ready[t] ), .tcdm_slave_resp_o (tran_tcdm_slave_resp ), .tcdm_slave_resp_valid_o (tran_tcdm_slave_resp_valid ), .tcdm_slave_resp_ready_i (tran_tcdm_slave_resp_ready ), @@ -720,16 +719,17 @@ module mempool_group assign tcdm_master_req[g][t] = tran_tcdm_master_req[g]; assign tcdm_master_req_valid[g][t] = tran_tcdm_master_req_valid[g]; assign tran_tcdm_master_req_ready[g] = tcdm_master_req_ready[g][t]; - assign tran_tcdm_master_resp[g] = tcdm_master_resp[g][t]; - assign tran_tcdm_master_resp_valid[g] = tcdm_master_resp_valid[g][t]; - assign tcdm_master_resp_ready[g][t] = tran_tcdm_master_resp_ready[g]; - assign tran_tcdm_slave_req[g] = tcdm_slave_req[g][t]; - assign tran_tcdm_slave_req_valid[g] = tcdm_slave_req_valid[g][t]; - assign tcdm_slave_req_ready[g][t] = tran_tcdm_slave_req_ready[g]; assign tcdm_slave_resp[g][t] = tran_tcdm_slave_resp[g]; assign tcdm_slave_resp_valid[g][t] = tran_tcdm_slave_resp_valid[g]; assign tran_tcdm_slave_resp_ready[g] = tcdm_slave_resp_ready[g][t]; end: gen_tran_group_req + + assign tile_tcdm_master_resp[t][0] = tcdm_master_resp[0][t]; + assign tile_tcdm_master_resp_valid[t][0] = tcdm_master_resp_valid[0][t]; + assign tcdm_master_resp_ready[0][t] = tile_tcdm_master_resp_ready[t][0]; + assign tile_tcdm_slave_req[t][0] = tcdm_slave_req[0][t]; + assign tile_tcdm_slave_req_valid[t][0] = tcdm_slave_req_valid[0][t]; + assign tcdm_slave_req_ready[0][t] = tile_tcdm_slave_req_ready[t][0]; end : gen_tiles /************************* @@ -846,6 +846,119 @@ module mempool_group tile_group_id_t [NumTilesPerGroup-1:0] slave_remote_resp_ini_addr; tcdm_payload_t [NumTilesPerGroup-1:0] slave_remote_resp_rdata; + `ifdef BACK2LOCAL + // Back2Local paths from the demuxes to the arbiters before the tile ports. + logic [NumTilesPerGroup-1:0] slave_back2local_req_valid; + logic [NumTilesPerGroup-1:0] slave_back2local_req_ready; + logic [NumTilesPerGroup-1:0] master_back2local_resp_valid; + logic [NumTilesPerGroup-1:0] master_back2local_resp_ready; + + // Remote interconnect outputs to the demuxes. + logic [NumTilesPerGroup-1:0] slave_demux_req_valid; + logic [NumTilesPerGroup-1:0] slave_demux_req_ready; + logic [NumTilesPerGroup-1:0] master_demux_resp_valid; + logic [NumTilesPerGroup-1:0] master_demux_resp_ready; + + for (genvar t = 0; t < NumTilesPerGroup; t++) begin: gen_tile + logic [1:0] slave_split_req_valid; + logic [1:0] slave_split_req_ready; + logic [1:0] master_split_resp_valid; + logic [1:0] master_split_resp_ready; + + // Arbiter input 0 is incoming remote; input 1 is Back2Local. + logic [1:0] slave_arb_req_valid; + logic [1:0] slave_arb_req_ready; + tcdm_slave_req_t [1:0] slave_arb_req; + logic [1:0] master_arb_resp_valid; + logic [1:0] master_arb_resp_ready; + tcdm_master_resp_t [1:0] master_arb_resp; + + stream_demux #( + .N_OUP(2) + ) i_req_demux ( + .inp_valid_i(slave_demux_req_valid[t] ), + .inp_ready_o(slave_demux_req_ready[t] ), + .oup_sel_i (slave_remote_req_wdata[t].back2local ), + .oup_valid_o(slave_split_req_valid ), + .oup_ready_i(slave_split_req_ready ) + ); + + assign slave_remote_req_valid[t] = slave_split_req_valid[0]; + assign slave_split_req_ready[0] = slave_remote_req_ready[t]; + assign slave_back2local_req_valid[t] = slave_split_req_valid[1]; + assign slave_split_req_ready[1] = slave_back2local_req_ready[t]; + + stream_demux #( + .N_OUP(2) + ) i_resp_demux ( + .inp_valid_i(master_demux_resp_valid[t] ), + .inp_ready_o(master_demux_resp_ready[t] ), + .oup_sel_i (master_remote_resp_rdata[t].back2local ), + .oup_valid_o(master_split_resp_valid ), + .oup_ready_i(master_split_resp_ready ) + ); + + assign master_remote_resp_valid[t] = master_split_resp_valid[0]; + assign master_split_resp_ready[0] = master_remote_resp_ready[t]; + assign master_back2local_resp_valid[t] = master_split_resp_valid[1]; + assign master_split_resp_ready[1] = master_back2local_resp_ready[t]; + + assign slave_arb_req[0] = tcdm_slave_req[r][t]; + assign slave_arb_req_valid[0] = tcdm_slave_req_valid[r][t]; + assign tcdm_slave_req_ready[r][t] = slave_arb_req_ready[0]; + assign slave_arb_req[1] = tcdm_master_req_s[r][t]; + assign slave_arb_req_valid[1] = slave_back2local_req_valid[t]; + assign slave_back2local_req_ready[t] = slave_arb_req_ready[1]; + + stream_arbiter #( + .DATA_T (tcdm_slave_req_t), + .N_INP (2 ), + .ARBITER("rr" ) + ) i_req_arbiter ( + .clk_i (clk_i ), + .rst_ni (rst_ni ), + .inp_data_i (slave_arb_req ), + .inp_valid_i(slave_arb_req_valid ), + .inp_ready_o(slave_arb_req_ready ), + .oup_data_o (tile_tcdm_slave_req[t][r] ), + .oup_valid_o(tile_tcdm_slave_req_valid[t][r] ), + .oup_ready_i(tile_tcdm_slave_req_ready[t][r] ) + ); + + assign master_arb_resp[0] = tcdm_master_resp_i[r][t]; + assign master_arb_resp_valid[0] = tcdm_master_resp_valid_i[r][t]; + assign tcdm_master_resp_ready_o[r][t] = master_arb_resp_ready[0]; + assign master_arb_resp[1] = tcdm_slave_resp_s[r][t]; + assign master_arb_resp_valid[1] = master_back2local_resp_valid[t]; + assign master_back2local_resp_ready[t] = master_arb_resp_ready[1]; + + stream_arbiter #( + .DATA_T (tcdm_master_resp_t), + .N_INP (2 ), + .ARBITER("rr" ) + ) i_resp_arbiter ( + .clk_i (clk_i ), + .rst_ni (rst_ni ), + .inp_data_i (master_arb_resp ), + .inp_valid_i(master_arb_resp_valid ), + .inp_ready_o(master_arb_resp_ready ), + .oup_data_o (tile_tcdm_master_resp[t][r] ), + .oup_valid_o(tile_tcdm_master_resp_valid[t][r] ), + .oup_ready_i(tile_tcdm_master_resp_ready[t][r] ) + ); + end: gen_tile + + `else + for (genvar t = 0; t < NumTilesPerGroup; t++) begin: gen_remote_tile_passthrough + assign tile_tcdm_master_resp[t][r] = tcdm_master_resp_i[r][t]; + assign tile_tcdm_master_resp_valid[t][r] = tcdm_master_resp_valid_i[r][t]; + assign tcdm_master_resp_ready_o[r][t] = tile_tcdm_master_resp_ready[t][r]; + assign tile_tcdm_slave_req[t][r] = tcdm_slave_req[r][t]; + assign tile_tcdm_slave_req_valid[t][r] = tcdm_slave_req_valid[r][t]; + assign tcdm_slave_req_ready[r][t] = tile_tcdm_slave_req_ready[t][r]; + end: gen_remote_tile_passthrough + `endif + for (genvar t = 0; t < NumTilesPerGroup; t++) begin: gen_remote_connections assign master_remote_req_valid[t] = tcdm_master_req_valid[r][t]; assign master_remote_req_tgt_addr[t] = tcdm_master_req[r][t].tgt_addr; @@ -891,15 +1004,25 @@ module mempool_group .req_wen_i (master_remote_req_wen ), .req_wdata_i (master_remote_req_wdata ), .req_be_i (master_remote_req_be ), - .resp_valid_o (master_remote_resp_valid ), - .resp_ready_i (master_remote_resp_ready ), + `ifdef BACK2LOCAL + .resp_valid_o (master_demux_resp_valid ), + .resp_ready_i (master_demux_resp_ready ), + `else + .resp_valid_o (master_remote_resp_valid ), + .resp_ready_i (master_remote_resp_ready ), + `endif .resp_rdata_o (master_remote_resp_rdata ), .resp_ini_addr_i(slave_remote_resp_ini_addr), .resp_rdata_i (slave_remote_resp_rdata ), .resp_valid_i (slave_remote_resp_valid ), .resp_ready_o (slave_remote_resp_ready ), + `ifdef BACK2LOCAL + .req_valid_o (slave_demux_req_valid ), + .req_ready_i (slave_demux_req_ready ), + `else .req_valid_o (slave_remote_req_valid ), .req_ready_i (slave_remote_req_ready ), + `endif .req_be_o (slave_remote_req_be ), .req_wdata_o (slave_remote_req_wdata ), .req_wen_o (slave_remote_req_wen ), diff --git a/hardware/src/mempool_pkg.sv b/hardware/src/mempool_pkg.sv index cf6877959..ea419da50 100644 --- a/hardware/src/mempool_pkg.sv +++ b/hardware/src/mempool_pkg.sv @@ -251,6 +251,9 @@ package mempool_pkg; meta_id_t meta_id; tile_core_id_t core_id; amo_t amo; + `ifdef BACK2LOCAL + logic back2local; + `endif data_t data; } tcdm_payload_t; diff --git a/hardware/src/mempool_tile.sv b/hardware/src/mempool_tile.sv index ee87dc32d..161fec2a0 100644 --- a/hardware/src/mempool_tile.sv +++ b/hardware/src/mempool_tile.sv @@ -366,6 +366,9 @@ module mempool_tile meta_id_t meta_id; tile_group_id_t tile_id; tile_core_id_t core_id; + `ifdef BACK2LOCAL + logic back2local; + `endif logic wide; } bank_metadata_t; @@ -507,6 +510,9 @@ module mempool_tile meta_id : bank_req_payload[b].wdata.meta_id, core_id : bank_req_payload[b].wdata.core_id, tile_id : bank_req_payload[b].ini_addr, + `ifdef BACK2LOCAL + back2local: bank_req_payload[b].wdata.back2local, + `endif wide : bank_req_wide[b] }; assign bank_resp_ini_addr[b] = meta_out.ini_addr; @@ -514,6 +520,9 @@ module mempool_tile assign bank_resp_payload[b].ini_addr = meta_out.tile_id; assign bank_resp_payload[b].rdata.core_id = meta_out.core_id; assign bank_resp_payload[b].rdata.amo = '0; // Don't care + `ifdef BACK2LOCAL + assign bank_resp_payload[b].rdata.back2local = meta_out.back2local; + `endif assign bank_resp_wide[b] = meta_out.wide; tcdm_adapter #( @@ -662,6 +671,7 @@ module mempool_tile sgroup_group_id_t [NumCoresPerTile-1:0] remote_req_interco_tgt_sg_sel_tmp; `else group_id_t [NumCoresPerTile-1:0] remote_req_interco_tgt_sel; + group_id_t [NumCoresPerTile-1:0] remote_req_interco_tgt_g_sel; `endif stream_xbar #( @@ -880,10 +890,16 @@ module mempool_tile prescramble_tcdm_req_tgt_addr[ByteOffset + idx_width(NumBanksPerTile) +: $clog2(NumTilesPerGroup)]}); // Tile end if (NumGroups == 1) begin : gen_remote_req_interco_tgt_sel + assign remote_req_interco_tgt_g_sel[c] = '0; assign remote_req_interco_tgt_sel[c] = 1'b0; end else begin : gen_remote_req_interco_tgt_sel - // Output port depends on both the target and initiator group - assign remote_req_interco_tgt_sel[c] = (prescramble_tcdm_req_tgt_addr[ByteOffset + $clog2(NumBanksPerTile) + $clog2(NumTilesPerGroup) +: $clog2(NumGroups)]) ^ group_id; + // Compute the normal output port before Back2Local rerouting. + assign remote_req_interco_tgt_g_sel[c] = (prescramble_tcdm_req_tgt_addr[ByteOffset + $clog2(NumBanksPerTile) + $clog2(NumTilesPerGroup) +: $clog2(NumGroups)]) ^ group_id; + `ifdef BACK2LOCAL + assign remote_req_interco_tgt_sel[c] = (remote_req_interco_tgt_g_sel[c] == '0) ? group_id_t'(c % NumGroups) : remote_req_interco_tgt_g_sel[c]; + `else + assign remote_req_interco_tgt_sel[c] = remote_req_interco_tgt_g_sel[c]; + `endif end `endif @@ -895,6 +911,11 @@ module mempool_tile // Constant value assign remote_req_interco[c].wdata.core_id = c[idx_width(NumCoresPerTile)-1:0]; + `ifdef BACK2LOCAL + assign remote_req_interco[c].wdata.back2local = (remote_req_interco_tgt_g_sel[c] == '0); + assign local_req_interco_payload[c].wdata.back2local = 1'b0; + `endif + // Scramble address before entering TCDM shim for sequential+interleaved memory map addr_t snitch_data_qaddr_scrambled; address_scrambler #( From 731cb4b385d36674c36fa5e4df6389b38584e37f Mon Sep 17 00:00:00 2001 From: galtendorfer Date: Fri, 24 Jul 2026 20:52:32 +0200 Subject: [PATCH 2/6] [hw] fix DAS DMA address handling --- hardware/Makefile | 3 +++ .../idma/src/midends/idma_address_scrambler.sv | 15 ++++++++------- .../idma/src/midends/idma_distributed_midend.sv | 4 ++-- .../deps/idma/src/midends/idma_split_midend.sv | 15 ++++++++++----- hardware/src/mempool_cluster.sv | 5 +++-- hardware/src/mempool_group.sv | 6 +++--- software/runtime/runtime.mk | 2 +- 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/hardware/Makefile b/hardware/Makefile index 9303820ea..46588bf7f 100644 --- a/hardware/Makefile +++ b/hardware/Makefile @@ -120,6 +120,9 @@ vlog_defs += -DL1_BANK_SIZE=$(l1_bank_size) vlog_defs += -DBOOT_ADDR=32\'d$(boot_addr) vlog_defs += -DDAS=$(das) vlog_defs += -DNUM_DAS_PARTITIONS=$(num_das_partitions) +ifeq ($(das),1) +vlog_defs += -DDAS_MEM_SIZE=$(das_mem_size) +endif ifeq ($(config),mempool) ifeq ($(back2local),1) vlog_defs += -DBACK2LOCAL diff --git a/hardware/deps/idma/src/midends/idma_address_scrambler.sv b/hardware/deps/idma/src/midends/idma_address_scrambler.sv index b637a903f..b0a2331e1 100644 --- a/hardware/deps/idma/src/midends/idma_address_scrambler.sv +++ b/hardware/deps/idma/src/midends/idma_address_scrambler.sv @@ -17,6 +17,7 @@ module idma_address_scrambler #( parameter int unsigned NumTiles = 128, parameter int unsigned NumBanksPerTile = 32, parameter int unsigned TCDMSizePerBank = 1024, + parameter int unsigned RowsInterleavingWidth = $clog2(TCDMSizePerBank) - ByteOffset, parameter int unsigned NumDASPartitions = 4, parameter int unsigned DASStartAddr = 1024, parameter int unsigned MemSizePerTile = NumBanksPerTile*TCDMSizePerBank, @@ -25,10 +26,10 @@ module idma_address_scrambler #( input logic [AddrWidth-1:0] address_i, input logic [31:0] num_bytes_i, input logic [NumDASPartitions-1:0][$clog2(NumTiles):0] tiles_das_i, - input logic [NumDASPartitions-1:0][$clog2(NumTiles):0] rows_das_i, + input logic [NumDASPartitions-1:0][RowsInterleavingWidth-1:0] rows_das_i, input logic [NumDASPartitions-1:0][DataWidth-1:0] start_das_i, output logic [$clog2(NumTiles):0] tiles_das_o, - output logic [$clog2(NumTiles):0] rows_das_o, + output logic [RowsInterleavingWidth:0] rows_das_o, output logic [AddrWidth-1:0] address_o ); // Basic Settings @@ -45,7 +46,7 @@ module idma_address_scrambler #( // `tile_index` : how many bits to shift for TileID bits in each partition // `row_index`: how many bits need to swap within Row Index logic [NumDASPartitions-1:0][$clog2($clog2(NumTiles)+1)-1:0] tile_index; - logic [NumDASPartitions-1:0][$clog2($clog2(NumTiles)+1)-1:0] row_index; + logic [NumDASPartitions-1:0][$clog2(RowsInterleavingWidth)-1:0] row_index; for (genvar i = 0; i < NumDASPartitions; i++) begin : gen_shift_index lzc #( @@ -57,12 +58,12 @@ module idma_address_scrambler #( .empty_o (/* Unused */ ) ); lzc #( - .WIDTH ($clog2(NumTiles)+1), + .WIDTH (RowsInterleavingWidth), .MODE (1'b0 ) ) i_log_row_index ( - .in_i (rows_das_i[i][$clog2(NumTiles):0]), - .cnt_o (row_index[i] ), - .empty_o (/* Unused */ ) + .in_i (rows_das_i[i]), + .cnt_o (row_index[i] ), + .empty_o (/* Unused */ ) ); end diff --git a/hardware/deps/idma/src/midends/idma_distributed_midend.sv b/hardware/deps/idma/src/midends/idma_distributed_midend.sv index adff5b758..8ec433753 100644 --- a/hardware/deps/idma/src/midends/idma_distributed_midend.sv +++ b/hardware/deps/idma/src/midends/idma_distributed_midend.sv @@ -20,8 +20,8 @@ module idma_distributed_midend #( /// Number of generic 1D requests that can be buffered parameter int unsigned TransFifoDepth = 1, `ifdef DAS - parameter int unsigned NumTiles = 64, parameter int unsigned NumDASPartitions = 4, + parameter int unsigned RowsWidth, `endif /// Arbitrary 1D burst request definition parameter type burst_req_t = logic, @@ -32,7 +32,7 @@ module idma_distributed_midend #( input logic rst_ni, `ifdef DAS // DAS signals - input logic [$clog2(NumTiles):0] rows_das_i, + input logic [RowsWidth-1:0] rows_das_i, `endif // Slave input burst_req_t burst_req_i, diff --git a/hardware/deps/idma/src/midends/idma_split_midend.sv b/hardware/deps/idma/src/midends/idma_split_midend.sv index 73d27b62a..9ea7b908b 100644 --- a/hardware/deps/idma/src/midends/idma_split_midend.sv +++ b/hardware/deps/idma/src/midends/idma_split_midend.sv @@ -17,6 +17,7 @@ module idma_split_midend #( parameter int unsigned NumTiles = 64, parameter int unsigned NumBanksPerTile = 32, parameter int unsigned TCDMSizePerBank = 1024, + parameter int unsigned RowsInterleavingWidth = $clog2(TCDMSizePerBank) - 2, parameter int unsigned NumDASPartitions = 4, parameter int unsigned DASStartAddr = 1024, parameter int unsigned NumTilesPerDma = 16, @@ -30,8 +31,8 @@ module idma_split_midend #( // DAS signals input logic [NumDASPartitions-1:0][$clog2(NumTiles):0] tiles_das_i, input logic [NumDASPartitions-1:0][AddrWidth-1:0] start_das_i, - input logic [NumDASPartitions-1:0][$clog2(NumTiles):0] rows_das_i, - output logic [$clog2(NumTiles):0] rows_das_o, + input logic [NumDASPartitions-1:0][RowsInterleavingWidth-1:0] rows_das_i, + output logic [RowsInterleavingWidth:0] rows_das_o, `endif // Slave input burst_req_t burst_req_i, @@ -80,7 +81,7 @@ module idma_split_midend #( logic [AddrWidth-1:0] post_scramble_src; logic [AddrWidth-1:0] post_scramble_dst; logic [$clog2(NumTiles):0] tiles_das_src, tiles_das_dst, tiles_das_sel; - logic [$clog2(NumTiles):0] rows_das_src, rows_das_dst, rows_das_sel; + logic [RowsInterleavingWidth:0] rows_das_src, rows_das_dst, rows_das_sel; assign tiles_das_sel = tiles_das_src | tiles_das_dst; assign rows_das_sel = rows_das_src | rows_das_dst; @@ -90,8 +91,10 @@ module idma_split_midend #( .AddrWidth (AddrWidth ), .NumTiles (NumTiles ), .NumBanksPerTile (NumBanksPerTile ), + .RowsInterleavingWidth (RowsInterleavingWidth), .Bypass (0 ), .NumDASPartitions (NumDASPartitions), + .DASStartAddr (DASStartAddr ), .TCDMSizePerBank (TCDMSizePerBank ) ) i_idma_address_scrambler_src ( .address_i (burst_req_i.src), @@ -108,8 +111,10 @@ module idma_split_midend #( .AddrWidth (AddrWidth ), .NumTiles (NumTiles ), .NumBanksPerTile (NumBanksPerTile ), + .RowsInterleavingWidth (RowsInterleavingWidth), .Bypass (0 ), .NumDASPartitions (NumDASPartitions), + .DASStartAddr (DASStartAddr ), .TCDMSizePerBank (TCDMSizePerBank ) ) i_idma_address_scrambler_dst ( .address_i (burst_req_i.dst), @@ -173,7 +178,7 @@ module idma_split_midend #( `FFARN(beat_cnt_q, beat_cnt_d, '0, clk_i, rst_ni) // log2(rows_das_sel): number of row-index bits in the active partition - logic [$clog2(NumTiles):0] log2_rows; + logic [$clog2(RowsInterleavingWidth+1)-1:0] log2_rows; // Bitmask to extract row index from the beat counter (lower log2_rows bits) logic [$clog2(NumTiles):0] row_idx_mask; // Current row index within the partition (0 .. rows_das-1) @@ -182,7 +187,7 @@ module idma_split_midend #( logic [$clog2(NumTiles):0] col_idx; lzc #( - .WIDTH ($clog2(NumTiles)+1), + .WIDTH (RowsInterleavingWidth+1), .MODE (1'b0 ) ) i_log2_rows ( .in_i (rows_das_sel), diff --git a/hardware/src/mempool_cluster.sv b/hardware/src/mempool_cluster.sv index 007320ac7..3011b6165 100644 --- a/hardware/src/mempool_cluster.sv +++ b/hardware/src/mempool_cluster.sv @@ -89,7 +89,7 @@ module mempool_cluster logic [NumGroups-1:0] dma_req_group_valid, dma_req_group_q_valid; logic [NumGroups-1:0] dma_req_group_ready, dma_req_group_q_ready; dma_meta_t [NumGroups-1:0] dma_meta, dma_meta_q; - logic [RowsInterleavingWidth-1:0] dma_rows_das; + logic [RowsInterleavingWidth:0] dma_rows_das; `FF(dma_meta_q, dma_meta, '0, clk_i, rst_ni); @@ -103,6 +103,7 @@ module mempool_cluster .NumTiles (NumTiles ), .NumBanksPerTile (NumBanksPerTile ), .TCDMSizePerBank (TCDMSizePerBank ), + .RowsInterleavingWidth (RowsInterleavingWidth ), .NumDASPartitions (NumDASPartitions ), .NumTilesPerDma (NumTilesPerDma ), .DASStartAddr (DASStartAddr ) @@ -131,8 +132,8 @@ module mempool_cluster .DmaRegionStart (TCDMBaseAddr ), .DmaRegionEnd (TCDMBaseAddr+TCDMSize), .TransFifoDepth (16 ), - .NumTiles (NumTiles ), .NumDASPartitions (NumDASPartitions ), + .RowsWidth (RowsInterleavingWidth + 1), .burst_req_t (dma_req_t ), .meta_t (dma_meta_t ) ) i_idma_distributed_midend ( diff --git a/hardware/src/mempool_group.sv b/hardware/src/mempool_group.sv index a2548db47..fdc9e4d3e 100644 --- a/hardware/src/mempool_group.sv +++ b/hardware/src/mempool_group.sv @@ -77,7 +77,7 @@ module mempool_group input logic [NumDASPartitions-1:0][TileInterleavingWidth-1:0] tiles_das_i, input logic [NumDASPartitions-1:0][AddrWidth-1:0] start_das_i, input logic [NumDASPartitions-1:0][RowsInterleavingWidth-1:0] rows_das_i, - input logic [RowsInterleavingWidth-1:0] dma_rows_das_i + input logic [RowsInterleavingWidth:0] dma_rows_das_i `endif ); @@ -584,8 +584,8 @@ module mempool_group .DmaRegionStart (TCDMBaseAddr ), .DmaRegionEnd (TCDMBaseAddr+TCDMSize ), .TransFifoDepth (8 ), - .NumTiles (NumTiles ), .NumDASPartitions (NumDASPartitions ), + .RowsWidth (RowsInterleavingWidth + 1 ), .burst_req_t (dma_req_t ), .meta_t (dma_meta_t ) ) i_idma_distributed_midend ( @@ -1127,8 +1127,8 @@ module mempool_group .DmaRegionStart (TCDMBaseAddr ), .DmaRegionEnd (TCDMBaseAddr+TCDMSize ), .TransFifoDepth (8 ), - .NumTiles (NumTiles ), .NumDASPartitions (NumDASPartitions ), + .RowsWidth (RowsInterleavingWidth + 1 ), .burst_req_t (dma_req_t ), .meta_t (dma_meta_t ) ) i_idma_distributed_midend ( diff --git a/software/runtime/runtime.mk b/software/runtime/runtime.mk index 039473a6e..a39499a84 100644 --- a/software/runtime/runtime.mk +++ b/software/runtime/runtime.mk @@ -111,7 +111,7 @@ ifdef terapool DEFINES += -DNUM_CORES_PER_SUB_GROUP=$(shell awk 'BEGIN{print ($(num_cores)/$(num_groups))/$(num_sub_groups_per_group)}') DEFINES += -DNUM_TILES_PER_SUB_GROUP=$(shell awk 'BEGIN{print ($(num_cores)/$(num_groups))/$(num_cores_per_tile)/$(num_sub_groups_per_group)}') endif -ifdef das +ifeq ($(das),1) DEFINES += -DNUM_DAS_PARTITIONS=$(num_das_partitions) DEFINES += -DDAS_MEM_SIZE=$(das_mem_size) DEFINES += -DLOG2_DAS_MEM_SIZE=$(shell awk 'BEGIN{print log($(das_mem_size))/log(2)}') From d03f8f1b92c781a9d7737b677150da58e81a07a0 Mon Sep 17 00:00:00 2001 From: galtendorfer Date: Sat, 25 Jul 2026 01:34:33 +0200 Subject: [PATCH 3/6] [sw] fix B matrix shape in generate_imatmul --- software/data/gendatalib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/data/gendatalib.py b/software/data/gendatalib.py index 8422bb085..d060a1438 100644 --- a/software/data/gendatalib.py +++ b/software/data/gendatalib.py @@ -427,7 +427,7 @@ def generate_imatmul(my_type=np.int32, defines={}): matrix_P = defines['matrix_P'] MAX = select_maxval(my_type) A = irandom(MAX=MAX, size=(matrix_M, matrix_N), my_type=my_type) - B = irandom(MAX=MAX, size=(matrix_M, matrix_N), my_type=my_type) + B = irandom(MAX=MAX, size=(matrix_N, matrix_P), my_type=my_type) C = np.matmul(A, B) A = np.reshape(A, (matrix_M * matrix_N), order='C').astype(my_type) From 92a9abcf873e551a2d5e34ca1b1147275f061792 Mon Sep 17 00:00:00 2001 From: galtendorfer Date: Mon, 3 Aug 2026 14:45:30 +0200 Subject: [PATCH 4/6] [hw/sw] add trace analysis and plotting workflow --- .gitignore | 1 + README.md | 6 +- hardware/Makefile | 42 +- hardware/scripts/gen_trace.py | 45 +- hardware/scripts/merge_tcdm_trace.py | 97 ++ hardware/scripts/merge_trace_csvs.py | 119 ++ hardware/scripts/trace_analysis/README.md | 190 +++ .../trace_analysis/_workflow_metadata.py | 119 ++ hardware/scripts/trace_analysis/benchmark.mk | 93 ++ .../extract/extract_benchmark_csvs.py | 811 ++++++++++ .../trace_analysis/plot/_stall_plots.py | 712 +++++++++ .../trace_analysis/plot/plot_all_tiles.py | 278 ++++ .../scripts/trace_analysis/plot/plot_comm.py | 1399 +++++++++++++++++ hardware/tb/mempool_tb.sv | 68 +- python-requirements.txt | 1 + 15 files changed, 3960 insertions(+), 21 deletions(-) create mode 100755 hardware/scripts/merge_tcdm_trace.py create mode 100644 hardware/scripts/merge_trace_csvs.py create mode 100644 hardware/scripts/trace_analysis/README.md create mode 100644 hardware/scripts/trace_analysis/_workflow_metadata.py create mode 100644 hardware/scripts/trace_analysis/benchmark.mk create mode 100644 hardware/scripts/trace_analysis/extract/extract_benchmark_csvs.py create mode 100644 hardware/scripts/trace_analysis/plot/_stall_plots.py create mode 100644 hardware/scripts/trace_analysis/plot/plot_all_tiles.py create mode 100644 hardware/scripts/trace_analysis/plot/plot_comm.py diff --git a/.gitignore b/.gitignore index aa589cf28..f9ff5ab89 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ tags # Python **.idea venv +__pycache__/ # Visual Studio Code .vscode *.code-workspace diff --git a/README.md b/README.md index 70f0d8493..33522eb21 100644 --- a/README.md +++ b/README.md @@ -163,8 +163,10 @@ app=hello_world make simc make trace # Generate a visualization of the traces app=hello_world make tracevis -# Automatically run the benchmark (headless), extract the traces, and log the results -app=hello_world make benchmark +# Benchmark and plot an app bracketed by mempool_start_benchmark() and +# mempool_stop_benchmark(); see hardware/scripts/trace_analysis/README.md. +app=matmul_i32 make benchmark +app=matmul_i32 make plots ``` You can set up the configuration of the system in the file `config/config.mk`, controlling the total number of cores, the number of cores per tile and whether the Xpulpimg extension is enabled or not in the Snitch core; the `xpulpimg` parameter also control the default core architecture considered when compiling applications for MemPool. diff --git a/hardware/Makefile b/hardware/Makefile index 46588bf7f..1c6d68f4a 100644 --- a/hardware/Makefile +++ b/hardware/Makefile @@ -48,6 +48,8 @@ python ?= python3 buildpath ?= build # Result path resultpath ?= results +# Two-stage benchmark/plotting workflow; see scripts/trace_analysis/README.md. +include $(ROOT_DIR)/scripts/trace_analysis/benchmark.mk result_dir := $(if $(strip $(result_dir)),$(result_dir),$(resultpath)/$(shell date +"%Y%m%d_%H%M%S_$(app)_$$(git rev-parse --short HEAD)")) # Traces snitch_trace ?= 1 @@ -67,6 +69,7 @@ questa_args += -sv_lib $(dramsys_lib_path)/libsystemc -sv_lib $(dramsys_lib_path app_path ?= $(abspath $(ROOT_DIR)/../software/bin) ifdef app preload := "$(app_path)/$(app)" + ifeq ($(skip_app_validation),) ifeq ("$(shell test -f $(preload))","") # The file doesn't exist, let's search for the app in the bin folder # since we might just be missing the hierarchy @@ -81,6 +84,7 @@ ifdef app $(error "Multiple apps ($(app)) found in bin dir $(app_path): $(search_app)") endif endif + endif endif # QuestaSim args @@ -134,6 +138,9 @@ vlog_defs += -DZFINX=$(zfinx) vlog_defs += -DZQUARTERINX=$(zquarterinx) vlog_defs += -DXDIVSQRT=$(xDivSqrt) vlog_defs += -DSNITCH_TRACE=$(snitch_trace) +ifeq ($(tcdm_addr_trace),1) +vlog_defs += -DTCDM_ADDR_TRACE +endif # AXI & DMA vlog_defs += -DAXI_DATA_WIDTH=$(axi_data_width) vlog_defs += -DRO_LINE_WIDTH=$(ro_line_width) @@ -263,7 +270,7 @@ simvcs: compile_vcs_simv cd $(buildpath) && \ ./mempool_simv $(vcs_args) -ucli -do ../scripts/vcs/run.tcl -gui -simcvcs: compile_vcs_simvopt +simcvcs: clean-dasm compile_vcs_simvopt cd $(buildpath) && \ ./mempool_simvopt $(vcs_args) @@ -367,24 +374,51 @@ spyglass/tmp/files: $(bender) # Give configuration in env trace_env += NUM_CORES=$(num_cores) +trace_env += NUM_CORES_PER_TILE=$(num_cores_per_tile) trace_env += SEQ_MEM_SIZE=$(seq_mem_size) +trace_env += BANKING_FACTOR=$(banking_factor) +trace_env += L1_BANK_SIZE=$(l1_bank_size) + +# Per-hart CSV parts, merged by post_trace; `make benchmark` skips the +# .trace copies into result_dir (copy_traces=0). +tracepartspath ?= $(tracepath)/results_parts +copy_traces ?= 1 trace: pre_trace $(trace) post_trace +# Order pre_trace -> per-hart traces -> post_trace under `make -j`. +$(trace): | pre_trace +post_trace: $(trace) + pre_trace: rm -rf $(tracepath) + rm -f $(buildpath)/trace_hart_*.trace post_trace: mkdir -p "$(result_dir)" cp $(buildpath)/transcript "$(result_dir)/" | true + $(python) $(ROOT_DIR)/scripts/merge_trace_csvs.py \ + --folder "$(tracepartspath)" \ + --expected-count "$(words $(trace))" \ + --csv "$(traceresult)" cp $(traceresult) "$(result_dir)" +ifeq ($(copy_traces),1) cp $(trace) "$(result_dir)" +endif $(python) $(ROOT_DIR)/scripts/gen_avg.py --folder "$(result_dir)" | tee $(result_dir)/avg.txt $(buildpath)/%.trace: $(buildpath)/%.dasm - mkdir -p $(tracepath) + mkdir -p $(tracepath) $(tracepartspath) +ifeq ($(tcdm_addr_trace),1) + $(INSTALL_DIR)/riscv-isa-sim/bin/spike-dasm < $< > $(tracepath)/$*.raw + $(python) $(ROOT_DIR)/scripts/merge_tcdm_trace.py \ + $(tracepath)/$*.raw $(buildpath)/tcdm_addr_hart_$(patsubst trace_hart_%,%,$*).csv \ + > $(tracepath)/$* + rm -f $(tracepath)/$*.raw +else $(INSTALL_DIR)/riscv-isa-sim/bin/spike-dasm < $< > $(tracepath)/$* - $(trace_env) $(python) $(ROOT_DIR)/scripts/gen_trace.py -p --csv $(traceresult) $(tracepath)/$* > $@ +endif + $(trace_env) $(python) $(ROOT_DIR)/scripts/gen_trace.py -p --csv $(tracepartspath)/$*.csv $(tracepath)/$* > $@ tracevis: $(MEMPOOL_DIR)/scripts/tracevis.py $(preload) $(buildpath)/*.trace -o $(buildpath)/tracevis.json @@ -443,7 +477,7 @@ clean: @rm -rf $(verilator_build) clean-dasm: - rm -rf $(buildpath)/*.dasm + rm -rf $(buildpath)/*.dasm $(buildpath)/tcdm_addr_hart_*.csv clean-trace: rm -rf $(buildpath)/*.trace diff --git a/hardware/scripts/gen_trace.py b/hardware/scripts/gen_trace.py index 1763f9151..e121a6610 100755 --- a/hardware/scripts/gen_trace.py +++ b/hardware/scripts/gen_trace.py @@ -74,23 +74,39 @@ RAW_TYPES = ['lsu', 'acc'] # ----------------- Architecture Info ----------------- -NUM_CORES = int(os.environ.get('num_cores', 256)) -NUM_TILES = NUM_CORES / 4 -SEQ_MEM_SIZE = 4 * int(os.environ.get('seq_mem_size', 1024)) -TCDM_SIZE = 16 * 1024 * NUM_TILES -def addr_to_meta(address): +def env_int(name, default): + # Keep direct legacy invocations using lowercase variable names working. + return int(os.environ.get(name, os.environ.get(name.lower(), default))) + + +NUM_CORES = env_int('NUM_CORES', 256) +NUM_CORES_PER_TILE = env_int('NUM_CORES_PER_TILE', 4) +BANKING_FACTOR = env_int('BANKING_FACTOR', 4) +L1_BANK_SIZE = env_int('L1_BANK_SIZE', 1024) +NUM_TILES = NUM_CORES // NUM_CORES_PER_TILE +NUM_BANKS_PER_TILE = NUM_CORES_PER_TILE * BANKING_FACTOR +INTERLEAVE_STRIDE = 4 * NUM_BANKS_PER_TILE +SEQ_MEM_SIZE = NUM_CORES_PER_TILE * env_int('SEQ_MEM_SIZE', 1024) +TCDM_SIZE = NUM_BANKS_PER_TILE * L1_BANK_SIZE * NUM_TILES + + +def addr_to_meta(address, tcdm_address=None): + # The logical address selects the region; the observed TCDM address + # selects the actual destination tile when DAS remaps the request. region = MEM_REGIONS['Other'] tile = -1 if (address < SEQ_MEM_SIZE * NUM_TILES): # Local memory region = MEM_REGIONS['Sequential'] - tile = address // SEQ_MEM_SIZE + tile = (address // SEQ_MEM_SIZE if tcdm_address is None else + (tcdm_address // INTERLEAVE_STRIDE) % NUM_TILES) elif (address < TCDM_SIZE): # Interleaved memory region = MEM_REGIONS['Interleaved'] - tile = address // 64 + tile = ((address if tcdm_address is None else tcdm_address) // + INTERLEAVE_STRIDE) tile = tile % NUM_TILES return region, tile @@ -188,7 +204,8 @@ def annotate_snitch( # Load / Store if extras['is_load']: perf_metrics[-1]['snitch_loads'] += 1 - gpr_wb_info[extras['rd']].appendleft((cycle, extras['alu_result'])) + gpr_wb_info[extras['rd']].appendleft(( + cycle, extras['alu_result'], extras.get('tcdm_addr'))) ret.append('{:<3} <~~ {}[{}]'.format( REG_ABI_NAMES_I[extras['rd']], LS_SIZES[extras['ls_size']], int_lit(extras['alu_result'], force_hex=force_hex_addr))) @@ -197,7 +214,8 @@ def annotate_snitch( ret.append('{} ~~> {}[{}]'.format( int_lit(extras['gpr_rdata_1']), LS_SIZES[extras['ls_size']], int_lit(extras['alu_result'], force_hex=force_hex_addr))) - region, tile = addr_to_meta(extras['alu_result']) + region, tile = addr_to_meta( + extras['alu_result'], extras.get('tcdm_addr')) perf_metrics[-1].setdefault('snitch_store_region', []).append(region) perf_metrics[-1].setdefault('snitch_store_tile', @@ -215,8 +233,9 @@ def annotate_snitch( # stall and during other ops if extras['retire_load']: try: - start_time, address = gpr_wb_info[extras['lsu_rd']].pop() - region, tile = addr_to_meta(address) + start_time, address, tcdm_address = \ + gpr_wb_info[extras['lsu_rd']].pop() + region, tile = addr_to_meta(address, tcdm_address) perf_metrics[-1].setdefault('snitch_load_latency', []).append(cycle - start_time) perf_metrics[-1].setdefault('snitch_load_region', @@ -284,7 +303,7 @@ def annotate_snitch( def annotate_insn( line: str, - # One deque (FIFO) per GPR storing start cycles for each GPR WB + # One deque (FIFO) per GPR storing metadata for each pending load gpr_wb_info: dict, # A list performance metric dicts perf_metrics: list, @@ -348,7 +367,7 @@ def safe_div(dividend, divisor): def eval_perf_metrics(perf_metrics: list, id: int): - tile_id = int(id // 4) + tile_id = int(id // NUM_CORES_PER_TILE) for seg in perf_metrics: end = seg['end'] cycles = end - seg['start'] + 1 diff --git a/hardware/scripts/merge_tcdm_trace.py b/hardware/scripts/merge_tcdm_trace.py new file mode 100755 index 000000000..b94d990bc --- /dev/null +++ b/hardware/scripts/merge_tcdm_trace.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Copyright 2026 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +"""Merge a testbench TCDM address trace into a raw core trace.""" + +import argparse +import csv +import re +import sys +from pathlib import Path + + +# Raw trace: +#