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/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/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/config/README.md b/config/README.md index 1e87bb4de..b75566097 100644 --- a/config/README.md +++ b/config/README.md @@ -35,6 +35,25 @@ 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 upper-level tile ports for TCDM requests that remain in the +source group or subgroup. 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 | +| `back2local_tera_group` | `0` | Also use TeraPool's group-level tile ports | + +For TeraPool, `back2local=1` uses the subgroup-level ports by default. Setting +`back2local_tera_group=1` also makes the group-level ports available and may +contend with inter-group traffic. + +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/config/terapool.mk b/config/terapool.mk index 6bdd329e9..12d49fa97 100644 --- a/config/terapool.mk +++ b/config/terapool.mk @@ -30,6 +30,11 @@ num_divsqrt_per_tile ?= 2 # L1 scratchpad banking factor banking_factor ?= 4 +# Also use group-level ports for same-subgroup Back2Local requests. +# Requires back2local=1 and more than one subgroup per group. This may +# contend with inter-group traffic. +back2local_tera_group ?= 0 + # Access latency between remote groups # Options: "7", "9" or "11": remote_group_latency_cycles ?= 7 diff --git a/hardware/Makefile b/hardware/Makefile index 9f1448a24..3f06848de 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 @@ -120,12 +124,26 @@ 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 ($(back2local),1) +vlog_defs += -DBACK2LOCAL +ifeq ($(config),terapool) +ifeq ($(back2local_tera_group),1) +vlog_defs += -DBACK2LOCAL_TERA_GROUP +endif +endif +endif # Snitch ISA vlog_defs += -DXPULPIMG=$(xpulpimg) 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) @@ -255,7 +273,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) @@ -359,24 +377,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 @@ -435,7 +480,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 \ No newline at end of file + rm -rf $(buildpath)/*.trace 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/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: +#