From 29210366d3db4020465d977e9633c67fd1741da6 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Thu, 10 Jul 2025 16:15:12 +0200 Subject: [PATCH 01/35] sw: Add GEMM tests and start AXI dump logic in memory tile --- hw/mem_tile.sv | 20 ++++- hw/picobello_top.sv | 4 +- sw/sw.mk | 10 ++- util/axi_analyzer.py | 188 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 util/axi_analyzer.py diff --git a/hw/mem_tile.sv b/hw/mem_tile.sv index 1458fda0..371224f6 100644 --- a/hw/mem_tile.sv +++ b/hw/mem_tile.sv @@ -16,7 +16,8 @@ module mem_tile #( parameter bit AxiUserAtop = 1'b1, parameter int unsigned AxiUserAtopMsb = 3, - parameter int unsigned AxiUserAtopLsb = 0 + parameter int unsigned AxiUserAtopLsb = 0, + parameter int unsigned MemTileId = 0 ) ( input logic clk_i, input logic rst_ni, @@ -275,6 +276,23 @@ module mem_tile end end + // AXI Monitor dumper to improvce debiugging + axi_dumper #( + .BusName ($sformatf("mem_tile_%d", MemTileId)), + .LogAW (1'b1), + .LogAR (1'b1), + .LogW (1'b1), + .LogB (1'b1), + .LogR (1'b1), + .axi_req_t (axi_nw_join_req_t), + .axi_resp_t(axi_nw_join_rsp_t) + ) i_axi_monitor ( + .clk_i, + .rst_ni, + .axi_req_i (axi_req), + .axi_resp_i(axi_rsp) + ); + axi_to_obi #( .ObiCfg (MgrObiCfg), .obi_req_t (mgr_obi_req_t), diff --git a/hw/picobello_top.sv b/hw/picobello_top.sv index 7a150820..e8d737fe 100644 --- a/hw/picobello_top.sv +++ b/hw/picobello_top.sv @@ -273,7 +273,9 @@ module picobello_top localparam int MemTileX = int'(MemTilePhysicalId.x); localparam int MemTileY = int'(MemTilePhysicalId.y); - mem_tile i_mem_tile ( + mem_tile #( + .MemTileId(int'(m)) + ) i_mem_tile ( .clk_i, .rst_ni, .test_enable_i (test_mode_i), diff --git a/sw/sw.mk b/sw/sw.mk index c4008512..2c209e4b 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -34,6 +34,11 @@ ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk endif +# SNITCH APPLICATIONS +gemm_BUILD_DIR = $(PB_SNITCH_SW_DIR)/apps/build +$(eval include $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm/app.mk) + + # Collect Snitch tests which should be built PB_SNRT_TESTS_DIR = $(PB_SNITCH_SW_DIR)/tests PB_SNRT_TESTS_BUILDDIR = $(PB_SNITCH_SW_DIR)/tests/build @@ -95,8 +100,9 @@ chs-sw-tests-clean: # Alias targets to align them with Picobello naming convention sn-tests-clean: sn-clean-tests sn-runtime-clean: sn-clean-runtime +sn-apps-clean: sn-clean-apps .PHONY: sw sw-tests sw-clean sw-tests-clean -sw sw-tests: chs-sw-tests sn-tests pb-sn-tests +sw sw-tests: chs-sw-tests sn-tests pb-sn-tests sn-apps -sw-clean sw-tests-clean: chs-sw-tests-clean sn-tests-clean sn-runtime-clean clean-pb-sn-tests +sw-clean sw-tests-clean: chs-sw-tests-clean sn-tests-clean sn-runtime-clean clean-pb-sn-tests sn-apps-clean diff --git a/util/axi_analyzer.py b/util/axi_analyzer.py new file mode 100644 index 00000000..7d6754b6 --- /dev/null +++ b/util/axi_analyzer.py @@ -0,0 +1,188 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Lorenzo Leone + +import pandas as pd +import ast + +pd.options.display.float_format = '{:.1f}'.format + +INT_FIELDS = {'atop', 'burst', 'cache', 'id', 'len', 'size', 'user'} + +###################### +# PARSING LOG # +###################### + +# Function to parse the log file from AXI DUMPER +def parse_axi_dump(file_path): + """ + Parses the AXI transaction log file into a pandas DataFrame. + All values (even hex) are converted and stored as integers to simplify processing. + + Parameters: + file_path (str): Path to the AXI dump text file. + + Returns: + pd.DataFrame: A DataFrame where each row is a transaction dictionary entry. + """ + parsed_entries = [] + + with open(file_path, 'r') as file: + for line_num, line in enumerate(file, start=1): + line = line.strip().rstrip(',') + if not line: + continue + + try: + # Safely parse the line as a Python dictionary + entry = ast.literal_eval(line) + + # Convert all values to integers if possible + intified_entry = {} + for k, v in entry.items(): + if isinstance(v, int): + intified_entry[k] = v + elif isinstance(v, str): + intified_entry[k] = v # keep strings like 'type' + else: + intified_entry[k] = int(v) # cast floats or hex-compatible + + parsed_entries.append(intified_entry) + + except Exception as e: + print(f"[Line {line_num}] Failed to parse line: {line}") + print(f" Error: {e}") + + df = pd.DataFrame(parsed_entries) + # Set the time as first column in the dataframe + if 'time' in df.columns: + cols = ['time'] + [col for col in df.columns if col != 'time'] + df = df[cols] + + return df + + +# Function to annotate write addresses in the DataFrame +def resolve_write_addresses(df): + """ + Resolves and fills in the missing 'addr' field in each W (write data) entry + based on the most recent AW (write address) entry before it. + + Modifies the DataFrame in place and returns it. + + Parameters: + df (pd.DataFrame): Parsed AXI transactions + + Returns: + pd.DataFrame: Modified DataFrame with filled 'addr' fields for W entries + """ + + current_aw = None + w_counter = 0 # tracks how many W entries have occurred since last AW + + df = df.copy() # Optional: avoid mutating original DF + + for idx, row in df.iterrows(): + if row.get('type') == 'AW': + current_aw = { + 'addr': row.get('addr'), + 'size': row.get('size'), + } + w_counter = 0 # reset on new AW + print(current_aw) + + elif row.get('type') == 'W': + if current_aw is not None: + bytes_per_transfer = 1 << int(current_aw['size']) + resolved_addr = current_aw['addr'] + w_counter * bytes_per_transfer + df.at[idx, 'addr'] = resolved_addr + w_counter += 1 + + if row.get('last') == 1: + current_aw = None + w_counter = 0 + else: + raise RuntimeError(f"[Line {idx}] Found W entry without preceding AW.") + + return df + + +def format_axi_df_for_display(df): + """ + Returns a new DataFrame with selected fields formatted as strings. + This allows clean display, printing, or export to CSV. + + Only modifies formatting of specific fields; all others are untouched. + """ + df_formatted = df.copy() + + def format_hex(x): + return f"0x{int(x):x}" if pd.notnull(x) else "None" + + def format_int(x): + return str(int(x)) if pd.notnull(x) else "None" + + def format_bool(x): + return str(bool(x)) if pd.notnull(x) else "None" + + # Define format rules to apply to each df column + format_rules = { + 'addr': format_hex, + 'atop': format_hex, + 'burst': format_hex, + 'id': format_int, + 'len': format_hex, + 'size': format_hex, + 'data': format_hex, + 'last': format_bool, + 'strb': format_hex, + } + + for col, formatter in format_rules.items(): + # Apply format rules for the specified columns + if col in df_formatted.columns: + df_formatted[col] = df_formatted[col].apply(formatter) + + return df_formatted + +# Function to select transactions matching a specific criteria +def filter_transactions(df, tx_type, **kwargs): + """ + Filters the DataFrame for entries of a given transaction type (AW, W, etc.) + and optional field-based filters like addr, id, burst, etc. + + Parameters: + df (pd.DataFrame): The AXI transaction log DataFrame. + tx_type (str): Required. Transaction type to filter on (e.g., 'AW', 'W'). + kwargs (dict): Additional field filters, e.g. addr='0x70000000', id=7. + + Returns: + pd.DataFrame: Filtered DataFrame matching the criteria. + """ + df_filtered = df[df['type'] == tx_type] + + for key, val in kwargs.items(): + if key not in df.columns: + raise ValueError(f"Column '{key}' not found in DataFrame.") + + # Convert hex strings to int + if isinstance(val, str) and val.startswith('0x'): + try: + val = int(val, 16) + except ValueError: + raise ValueError(f"Invalid hex value for {key}: '{val}'") + + df_filtered = df_filtered[df_filtered[key] == val] + + return df_filtered.reset_index(drop=True) + + +if __name__ == "__main__": + file_path = "axi_trace_mem_tile_0.log" + df = parse_axi_dump(file_path) + df = resolve_write_addresses(df) + df_formatted = filter_transactions(df, "W", addr="0x700044e8") + df_formatted = format_axi_df_for_display(df_formatted) + print(df_formatted) From c4c88a861e6b4c066355abeb811f540e3fd07159 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Fri, 11 Jul 2025 16:48:13 +0200 Subject: [PATCH 02/35] sw: Add flow for applications and adapt GEMM with mcast support --- Makefile | 2 +- sw/snitch/apps/blas/gemm/app.mk | 17 + sw/snitch/apps/blas/gemm/data/params.json | 25 ++ sw/snitch/apps/blas/gemm/roi.json | 28 ++ sw/snitch/apps/blas/gemm/scripts/datagen.py | 185 ++++++++++ sw/snitch/apps/blas/gemm/scripts/verify.py | 58 +++ sw/snitch/apps/blas/gemm/src/gemm_picobello.c | 342 ++++++++++++++++++ sw/sw.mk | 3 +- 8 files changed, 657 insertions(+), 3 deletions(-) create mode 100644 sw/snitch/apps/blas/gemm/app.mk create mode 100644 sw/snitch/apps/blas/gemm/data/params.json create mode 100644 sw/snitch/apps/blas/gemm/roi.json create mode 100755 sw/snitch/apps/blas/gemm/scripts/datagen.py create mode 100755 sw/snitch/apps/blas/gemm/scripts/verify.py create mode 100644 sw/snitch/apps/blas/gemm/src/gemm_picobello.c diff --git a/Makefile b/Makefile index 29d13c89..0833fff7 100644 --- a/Makefile +++ b/Makefile @@ -218,7 +218,7 @@ python-venv: .venv python -m pip install --upgrade pip setuptools && \ python -m pip install --cache-dir $(PIP_CACHE_DIR) -r requirements.txt && \ python -m pip install --cache-dir $(PIP_CACHE_DIR) $(shell $(BENDER) path floo_noc) --no-deps && \ - python -m pip install --cache-dir $(PIP_CACHE_DIR) $(shell $(BENDER) path snitch_cluster) + python -m pip install --cache-dir $(PIP_CACHE_DIR) "$(shell $(BENDER) path snitch_cluster)[kernels]" python-venv-clean: rm -rf .venv diff --git a/sw/snitch/apps/blas/gemm/app.mk b/sw/snitch/apps/blas/gemm/app.mk new file mode 100644 index 00000000..293a374c --- /dev/null +++ b/sw/snitch/apps/blas/gemm/app.mk @@ -0,0 +1,17 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Lorenzo leone + +APP := gemm +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/blas/$(APP)/build +SRC_DIR := $(PB_SNITCH_SW_DIR)/apps/blas/$(APP)/src +SRCS := $(SRC_DIR)/gemm_picobello.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/blas $(SN_ROOT)/sw/blas/$(APP)/src + +# Refer to snitch utiulities +$(APP)_SCRIPT_DIR := $(SN_ROOT)/sw/blas/$(APP)/scripts + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/blas/gemm/data/params.json b/sw/snitch/apps/blas/gemm/data/params.json new file mode 100644 index 00000000..3aa05e24 --- /dev/null +++ b/sw/snitch/apps/blas/gemm/data/params.json @@ -0,0 +1,25 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +{ + setup_ssr: 1, + parallelize_m: 16, + parallelize_k: 0, + m_tiles: 16, // number of tiles in M dimension + n_tiles: 1, // number of tiles in N dimension + k_tiles: 1, // number of tiles in K dimension + load_a: 1, + load_b: 1, + load_c: 1, + double_buffer: 1, + partition_banks: 0, + transa: false, + transb: false, // must be true for SIMD + m: 2048, + n: 16, + k: 16, + alpha: 1, + beta: 0, + gemm_fp: "gemm_fp64_opt" +} diff --git a/sw/snitch/apps/blas/gemm/roi.json b/sw/snitch/apps/blas/gemm/roi.json new file mode 100644 index 00000000..8a501328 --- /dev/null +++ b/sw/snitch/apps/blas/gemm/roi.json @@ -0,0 +1,28 @@ +[ + <% N_TILES = 4 %> + + % for cluster in range(0,15): + // Compute cores + % for j in range(0, 8): + { + "thread": "${f'hart_{cluster * 9 + j + 1}'}", + "roi": [ + % for i in range(0, N_TILES): + {"idx": ${2 * i + 1}, "label": "${f'tile_{i}'}"}, + % endfor + ] + }, + % endfor + + // DMA core + { + + "thread": "${f'hart_{cluster * 9 + 8 + 1}'}", + "roi": [ + % for i in range(0, N_TILES): + {"idx": ${2 * i + 1}, "label": "${f'tile_{i}'}"}, + % endfor + ] + }, + % endfor +] diff --git a/sw/snitch/apps/blas/gemm/scripts/datagen.py b/sw/snitch/apps/blas/gemm/scripts/datagen.py new file mode 100755 index 00000000..02abcb97 --- /dev/null +++ b/sw/snitch/apps/blas/gemm/scripts/datagen.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# Copyright 2022 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Authors: Tim Fischer +# Luca Bertaccini +# Viviane Potocnik +# Luca Colagrande + +import numpy as np +import re +import sys + +import snitch.util.sim.data_utils as du + + +np.random.seed(42) + + +class GemmDataGen(du.DataGen): + + # AXI splits bursts crossing 4KB address boundaries. To minimize + # the occurrence of these splits the data should be aligned to 4KB + BURST_ALIGNMENT = 4096 + NUM_CORES = 8 + + def golden_model(self, alpha, a, b, beta, c): + return alpha * np.matmul(a, b) + beta * c + + def exact_golden_model(self, alpha, a, b, beta, c): + m, n, k = a.shape[0], b.shape[1], b.shape[0] + + result = beta * c + for i in range(m): + for j in range(n): + for h in range(k): + result[i][j] += a[i][h] * b[h][j] + return result + + def infer_implementation(self, gemm_fp): + # gemm_fp: "gemm_fp64_opt" + # create a regex with fp__ + prec, impl = re.search(r'gemm_fp(\d+)_(\w+)', gemm_fp).group(1, 2) + return int(prec) // 8, impl + + def validate(self, gemm_fp, parallelize_m, + parallelize_k, m_tiles, n_tiles, k_tiles, transa, + transb, m, n, k, beta, **kwargs): + double_buffer = kwargs.get('double_buffer', False) + partition_banks = kwargs.get('partition_banks', False) + tile_m = m / m_tiles + tile_n = n / n_tiles + tile_k = k / k_tiles + + dtype, impl = self.infer_implementation(gemm_fp) + + # Calculate total TCDM occupation + # Note: doesn't account for double buffering + prec = du.size_from_precision_t(dtype) + a_size = tile_m * tile_k * prec + b_size = tile_k * tile_n * prec + c_size = tile_m * tile_n * prec + total_size = a_size + total_size += b_size + total_size += c_size + du.validate_tcdm_footprint(total_size) + + assert (m % m_tiles) == 0, 'm is not an integer multiple of tile size' + assert (n % n_tiles) == 0, 'n is not an integer multiple of tile size' + assert (k % k_tiles) == 0, 'k is not an integer multiple of tile size' + assert kwargs['load_a'] or (m_tiles == 1 and k_tiles == 1), 'A matrix can\'t be tiled if' \ + ' local tile buffer is externally managed (load_a == 0)' + assert kwargs['load_b'] or (k_tiles == 1 and n_tiles == 1), 'B matrix can\'t be tiled if' \ + ' local tile buffer is externally managed (load_b == 0)' + assert kwargs['load_c'] or (m_tiles == 1 and n_tiles == 1), 'C matrix can\'t be tiled if' \ + ' local tile buffer is externally managed (load_c == 0)' + assert not (parallelize_m and parallelize_k), 'Cannot parallelize k and m simultaneously' + assert not (double_buffer and parallelize_k), 'Cannot parallelize k when double buffering' + assert not transa, 'SIMD kernels don\'t support transposed A matrix' + assert (dtype == 8) or (impl == 'baseline') or (impl == 'naive') \ + or transb, 'Optimized SIMD kernels only support transposed B matrix' + assert (impl == 'baseline') or (impl == 'naive') or tile_n >= 8, \ + 'n dimension of tile size must be greater or equal to the unrolling factor (8) ' \ + 'when using optimized kernels' + assert beta == 0 or beta == 1, 'Only values of 0 or 1 supported for beta' + assert not (dtype == 8 and impl == "baseline"), 'No baseline implemented' \ + ' for FP64 (switch to NAIVE)' + assert not (((dtype == 8) or (dtype == 4)) and impl == "opt_ex"), \ + 'Expanding GEMM kernels not supported for FP64 and FP32' + assert not (dtype == 1 and impl == "opt"), 'FP8 not supported in' \ + ' optimized implementation (switch to opt_ex)' + assert not (partition_banks and (transb or transa or k_tiles > 1 or n_tiles > 1)), \ + 'Cannot allocate buffer in a subset of banks if the A, B and C matrix tiles are not ' \ + 'a contiguous 1D block of data. This is guaranteed if A and B are not transposed and' \ + ' only M is tiled.' + elements_per_tcdm_line = (8 * self.NUM_CORES) / dtype + k_invalid = (k % elements_per_tcdm_line) != 0 + n_invalid = (n % elements_per_tcdm_line) != 0 + assert not (partition_banks and (k_invalid or n_invalid)), 'Cannot allocate buffer' \ + ' in a subset of banks if the number of elements in K and N is not an exact ' \ + 'multiple of the number of elements that can be stored in a line of the TCDM. This ' \ + 'constraint could potentially be loosened.' + assert not (partition_banks and (dtype != 8)), 'Lower than double precision kernels do' \ + 'not support partitioned banks, yet.' + + def emit_header(self, **kwargs): + header = [super().emit_header()] + + # Validate parameters + self.validate(**kwargs) + + m, n, k = kwargs['m'], kwargs['n'], kwargs['k'] + + prec, _ = self.infer_implementation(kwargs['gemm_fp']) + + ctype = du.ctype_from_precision_t(prec) + + a = du.generate_random_array((m, k), prec, seed=42) + b = du.generate_random_array((k, n), prec, seed=42) + c = du.generate_random_array((m, n), prec, seed=42) + result = self.exact_golden_model(1, a, b, kwargs['beta'], c) + + # Store matrices in transposed form if requested + a = a.T if kwargs['transa'] else a + b = b.T if kwargs['transb'] else b + + a_uid = 'a' + b_uid = 'b' + c_uid = 'c' + m_uid = 'm' + n_uid = 'n' + k_uid = 'k' + prec_uid = 'prec' + beta_uid = 'beta' + transb_uid = 'transb' + + cfg = { + **kwargs, + 'a': a_uid, + 'b': b_uid, + 'c': c_uid, + 'prec': prec_uid, + 'lda': m if kwargs['transa'] else k, + 'ldb': k if kwargs['transb'] else n, + 'ldc': n, + } + cfg['m'] = m_uid + cfg['n'] = n_uid + cfg['k'] = k_uid + cfg['beta'] = beta_uid + cfg['transb'] = transb_uid + + a = a.flatten() + b = b.flatten() + c = c.flatten() + + # "extern" specifier is required on declarations preceding a definition + header += [du.format_array_declaration(f'extern {ctype}', a_uid, a.shape)] + header += [du.format_array_declaration(f'extern {ctype}', b_uid, b.shape)] + header += [du.format_array_declaration(f'extern {ctype}', c_uid, c.shape)] + # "extern" specifier ensures that the variable is emitted and not mangled + header += [du.format_scalar_definition('extern const uint32_t', prec_uid, prec)] + header += [du.format_scalar_definition('extern const uint32_t', m_uid, m)] + header += [du.format_scalar_definition('extern const uint32_t', n_uid, n)] + header += [du.format_scalar_definition('extern const uint32_t', k_uid, k)] + header += [du.format_scalar_definition('extern const uint32_t', beta_uid, kwargs['beta'])] + header += [du.format_scalar_definition('extern const uint32_t', transb_uid, + kwargs['transb'])] + header += [du.format_struct_definition('extern const gemm_args_t', 'args', cfg)] + header += [du.format_array_definition(ctype, a_uid, a, + section=kwargs['section'])] + header += [du.format_array_definition(ctype, b_uid, b, + section=kwargs['section'])] + header += [du.format_array_definition(ctype, c_uid, c, + section=kwargs['section'])] + result_def = du.format_array_definition(ctype, 'result', result.flatten()) + header += [du.format_ifdef_wrapper('BIST', result_def)] + header = '\n\n'.join(header) + + return header + + +if __name__ == "__main__": + sys.exit(GemmDataGen().main()) diff --git a/sw/snitch/apps/blas/gemm/scripts/verify.py b/sw/snitch/apps/blas/gemm/scripts/verify.py new file mode 100755 index 00000000..25df58c8 --- /dev/null +++ b/sw/snitch/apps/blas/gemm/scripts/verify.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright 2023 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Luca Colagrande + +import numpy as np +import sys +from datagen import GemmDataGen + +from snitch.util.sim.verif_utils import Verifier +from snitch.util.sim.data_utils import ctype_from_precision_t + + +class GemmVerifier(Verifier): + + OUTPUT_UIDS = ['c'] + ERR_THRESHOLD = { + 1: 1e-4, + 2: 5e-1, + 4: 1e-3, + 8: 1e-3 + } + + def __init__(self): + super().__init__() + self.prec = self.get_input_from_symbol('prec', 'uint32_t')[0] + + def get_actual_results(self): + return self.get_output_from_symbol(self.OUTPUT_UIDS[0], ctype_from_precision_t(self.prec)) + + def get_expected_results(self): + a = self.get_input_from_symbol('a', ctype_from_precision_t(self.prec)) + b = self.get_input_from_symbol('b', ctype_from_precision_t(self.prec)) + c = self.get_input_from_symbol('c', ctype_from_precision_t(self.prec)) + m = self.get_input_from_symbol('m', 'uint32_t')[0] + n = self.get_input_from_symbol('n', 'uint32_t')[0] + k = self.get_input_from_symbol('k', 'uint32_t')[0] + beta = self.get_input_from_symbol('beta', 'uint32_t')[0] + transb = self.get_input_from_symbol('transb', 'uint32_t')[0] + + a = np.reshape(a, (m, k)) + if transb: + b = np.reshape(b, (n, k)) + b = b.transpose() + else: + b = np.reshape(b, (k, n)) + c = np.reshape(c, (m, n)) + + return GemmDataGen().exact_golden_model(1, a, b, beta, c).flatten() + + def check_results(self, *args): + return super().check_results(*args, rtol=self.ERR_THRESHOLD[self.prec]) + + +if __name__ == "__main__": + sys.exit(GemmVerifier().main()) diff --git a/sw/snitch/apps/blas/gemm/src/gemm_picobello.c b/sw/snitch/apps/blas/gemm/src/gemm_picobello.c new file mode 100644 index 00000000..8d985d53 --- /dev/null +++ b/sw/snitch/apps/blas/gemm/src/gemm_picobello.c @@ -0,0 +1,342 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Author: Tim Fischer +// Luca Bertaccini +// Luca Colagrande +// Viviane Potocnik + +#include "snrt.h" +#include +#include + +#include +#include "blas.h" + +// #define JOB_ARGS_PRELOADED + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreorder-init-list" +#include "data.h" +#pragma clang diagnostic pop + + +/** + * @brief Performs a General Matrix Multiplication (GEMM) operation on a + * Snitch-based multiple-cluster architecture with support for + * parallelization, tiling, and data movement optimizations. + * + * @param args Pointer to a `gemm_args_t` structure containing arguments + * for the GEMM operation. + * + * @details + * The function performs the following steps: + * 1. Copies the input arguments to local memory for faster access. + * 2. Calculates tile sizes based on the input dimensions and number of tiles. + * 3. Allocates space in TCDM for local copies of matrix tiles, unless + * matrix tiles are already stored in TCDM (see `load_* arguments`). + * 4. Distributes tiles to clusters for parallel processing. + * 5. Iterates over the tiles, performing the following: + * - Copies data for the current tile into local memory. + * - Performs the tile computation using the `sc_st_gemm` function. + * - Performs a logarithmic reduction to combine partial results across + * clusters, if `parallelize_k` is enabled. + * - Writes the result back to global memory. + * + * @note Current implementation assumes that `parallelize_m` and + * `parallelize_k` options are mutually exclusive. + */ +static inline int gemm_picobello(const gemm_args_t *args) { +#ifndef JOB_ARGS_PRELOADED + // Copy the arguments to local memory + gemm_args_t *largs = (gemm_args_t *)snrt_l1_alloc_cluster_local( + sizeof(gemm_args_t), alignof(gemm_args_t)); + if (snrt_is_dm_core()) { + snrt_dma_start_1d((void *)largs, (void *)args, sizeof(gemm_args_t)); + snrt_dma_wait_all(); + } + snrt_cluster_hw_barrier(); +#else + const gemm_args_t *largs = args; +#endif + + // Calculate tile sizes + uint32_t tile_m = largs->m / largs->m_tiles; + uint32_t tile_n = largs->n / largs->n_tiles; + uint32_t tile_k = largs->k / largs->k_tiles; + uint32_t tile_a_size = tile_m * tile_k * largs->prec; + uint32_t tile_b_size = tile_k * tile_n * largs->prec; + uint32_t tile_c_size = tile_m * tile_n * largs->prec; + + // Allocate space for local tile buffers in TCDM, unless preloaded + void *a0, *a1, *b0, *b1, *c0, *c1; + void *la[2], *lb[2], *lc[2], *lcr; + int banks_per_buffer = snrt_cluster_compute_core_num(); + allocate_buffers(tile_a_size, tile_b_size, tile_c_size, largs, + banks_per_buffer, la, lb, lc, &lcr); + if (snrt_cluster_core_idx() == 0) { + DUMP(la[0]); + DUMP(la[1]); + DUMP(lb[0]); + DUMP(lb[1]); + DUMP(lc[0]); + DUMP(lc[1]); + } + snrt_cluster_hw_barrier(); + + // Distribute m and k tiles to clusters + uint32_t cluster_m_tiles = largs->m_tiles; + uint32_t cluster_k_tiles = largs->k_tiles; + if (largs->parallelize_m) cluster_m_tiles /= snrt_cluster_num(); + if (largs->parallelize_k) cluster_k_tiles /= snrt_cluster_num(); + + // Calculate number of iterations + uint32_t num_tiles = cluster_m_tiles * largs->n_tiles * cluster_k_tiles; + uint32_t num_iters = num_tiles; + if (largs->double_buffer) + num_iters += 2; + else + num_iters += 1; + + // Iterate over all tiles + for (uint32_t i = 0; i < num_iters; i++) { + // Calculate tile indices (we iterate in k->n->m order) + int dma_in_i = i; + int comp_i = largs->double_buffer ? i - 1 : i; + int dma_out_i = largs->double_buffer ? i - 2 : i - 1; + int dma_in_k = dma_in_i % cluster_k_tiles; + int dma_in_mn = dma_in_i / cluster_k_tiles; + int dma_in_n = dma_in_mn % largs->n_tiles; + int dma_in_m = dma_in_mn / largs->n_tiles; + int comp_k = comp_i % cluster_k_tiles; + int comp_mn = comp_i / cluster_k_tiles; + int comp_n = comp_mn % largs->n_tiles; + int comp_m = comp_mn / largs->n_tiles; + int dma_out_k = dma_out_i % cluster_k_tiles; + int dma_out_mn = dma_out_i / cluster_k_tiles; + int dma_out_n = dma_out_mn % largs->n_tiles; + int dma_out_m = dma_out_mn / largs->n_tiles; + + // If m and k tiles are parallelized across clusters, + // calculate the absolute m and k indices for each cluster + int dma_in_m_abs = dma_in_m; + int comp_m_abs = comp_m; + int dma_out_m_abs = dma_out_m; + int dma_in_k_abs = dma_in_k; + int comp_k_abs = comp_k; + int dma_out_k_abs = dma_out_k; + if (largs->parallelize_m) { + dma_in_m_abs += snrt_cluster_idx() * cluster_m_tiles; + comp_m_abs += snrt_cluster_idx() * cluster_m_tiles; + dma_out_m_abs += snrt_cluster_idx() * cluster_m_tiles; + } + if (largs->parallelize_k) { + dma_in_k_abs += snrt_cluster_idx() * cluster_k_tiles; + comp_k_abs += snrt_cluster_idx() * cluster_k_tiles; + dma_out_k_abs += snrt_cluster_idx() * cluster_k_tiles; + } + + // DMA out phase + if (snrt_is_dm_core()) { + if (dma_out_i >= 0) { + // Switch buffers + int buff_idx = largs->double_buffer ? dma_out_mn % 2 : 0; + + // Store C + // If parallelize_k, then only cluster 0 must writeback + if ((snrt_cluster_idx() == 0) || !(largs->parallelize_k)) { + if (largs->partition_banks) { + snrt_dma_2d_to_1d( + (void *)((uintptr_t)largs->c + + dma_out_m_abs * tile_c_size), + lc[buff_idx], tile_c_size, + banks_per_buffer * SNRT_TCDM_BANK_WIDTH, + SNRT_TCDM_HYPERBANK_WIDTH); + } else { + snrt_dma_store_2d_tile(largs->c, lc[buff_idx], + dma_out_m_abs, dma_out_n, tile_m, + tile_n, largs->ldc, largs->prec); + } + snrt_dma_wait_all(); + } + } + } + + // DMA in phase + if (snrt_is_dm_core()) { + if (dma_in_i < num_tiles) { + snrt_mcycle(); + // Switch buffers + // A and B buffers are switched every iteration, while the C + // buffer only needs to be switched after fully accumulating + // the result, i.e. after finishing the K loop. + int buff_idx = largs->double_buffer ? dma_in_i % 2 : 0; + int c_buff_idx = largs->double_buffer ? dma_in_mn % 2 : 0; + + // Load A + if (largs->load_a) { + if (largs->partition_banks) { + snrt_dma_1d_to_2d( + la[buff_idx], + (void *)((uintptr_t)largs->a + + dma_in_m_abs * tile_a_size), + tile_a_size, + banks_per_buffer * SNRT_TCDM_BANK_WIDTH, + SNRT_TCDM_HYPERBANK_WIDTH); + } else { + snrt_dma_load_2d_tile( + la[buff_idx], largs->a, dma_in_m_abs, dma_in_k_abs, + tile_m, tile_k, largs->lda, largs->prec); + } + } + + // Load B + if (largs->load_b) { + if (largs->transb) { + snrt_dma_load_2d_tile(lb[buff_idx], largs->b, dma_in_n, + dma_in_k_abs, tile_n, tile_k, + largs->ldb, largs->prec); + } else { + if (largs->partition_banks) { + snrt_dma_1d_to_2d( + lb[buff_idx], + (void *)((uintptr_t)largs->b + + dma_in_k_abs * tile_b_size), + tile_b_size, + banks_per_buffer * SNRT_TCDM_BANK_WIDTH, + SNRT_TCDM_HYPERBANK_WIDTH); + } else { + // Multicast B to all clusters + if (snrt_cluster_idx() == 0) { + // Load B from L2 + snrt_dma_load_2d_tile_mcast( + lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, + tile_k, tile_n, largs->ldb, largs->prec, 0x003C0000); + } + } + } + } + + // Load C + // C tile is loaded only upon the first k iteration, then + // the C array will contain the partial results from the + // previous iteration + if (largs->load_c) { + if (dma_in_k_abs == 0) { + if (largs->partition_banks) { + snrt_dma_1d_to_2d( + lc[c_buff_idx], + (void *)((uintptr_t)largs->c + + dma_in_m_abs * tile_c_size), + tile_c_size, + banks_per_buffer * SNRT_TCDM_BANK_WIDTH, + SNRT_TCDM_HYPERBANK_WIDTH); + } else { + snrt_dma_load_2d_tile(lc[c_buff_idx], largs->c, + dma_in_m_abs, dma_in_n, + tile_m, tile_n, largs->ldc, + largs->prec); + } + } else if (dma_in_k == 0) { + // Clusters other than the first need to initialize + // the C array to zero in their first iteration + if (largs->partition_banks) { + snrt_dma_1d_to_2d( + lc[c_buff_idx], snrt_cluster()->zeromem.mem, + tile_c_size, + banks_per_buffer * SNRT_TCDM_BANK_WIDTH, + SNRT_TCDM_HYPERBANK_WIDTH); + } else { + snrt_dma_start_1d(lc[c_buff_idx], + snrt_cluster()->zeromem.mem, + tile_c_size); + } + } + } + snrt_dma_wait_all(); + snrt_mcycle(); + } + } + + // Additional barrier required when not double buffering + if (!largs->double_buffer) snrt_cluster_hw_barrier(); + + // Compute phase + if (comp_i >= 0 && comp_i < num_tiles) { + // Switch buffers + int buff_idx = largs->double_buffer ? comp_i % 2 : 0; + int c_buff_idx = largs->double_buffer ? comp_mn % 2 : 0; + + // Only compute cores participate in the tile computation + if (!snrt_is_dm_core()) { + uint32_t start_cycle = snrt_mcycle(); + + // In the first k iteration we accumulate with the C matrix + // scaled by beta, in successive iterations we accumulate + // the previous partial result. The tile-level beta is thus + // a function of k: beta(k). + uint32_t beta_k = comp_k_abs == 0 ? largs->beta : 1; + + // Tile computation + sc_st_gemm_args_t sc_st_args; + sc_st_args.prec = largs->prec; + sc_st_args.setup_ssr = largs->setup_ssr; + sc_st_args.partition_banks = largs->partition_banks; + sc_st_args.transa = largs->transa; + sc_st_args.transb = largs->transb; + sc_st_args.a = la[buff_idx]; + if (largs->transa) { + sc_st_args.lda = tile_m; + } else if (largs->partition_banks) { + sc_st_args.lda = calculate_partitioned_banks_stride( + banks_per_buffer, tile_k, largs->prec); + } else { + sc_st_args.lda = tile_k; + } + sc_st_args.b = lb[buff_idx]; + if (largs->transb) { + sc_st_args.ldb = tile_k; + } else if (largs->partition_banks) { + sc_st_args.ldb = calculate_partitioned_banks_stride( + banks_per_buffer, tile_n, largs->prec); + } else { + sc_st_args.ldb = tile_n; + } + sc_st_args.beta = beta_k; + sc_st_args.c = lc[c_buff_idx]; + if (largs->partition_banks) { + sc_st_args.ldc = calculate_partitioned_banks_stride( + banks_per_buffer, tile_n, largs->prec); + } else { + sc_st_args.ldc = tile_n; + } + sc_st_args.m = tile_m; + sc_st_args.n = tile_n; + sc_st_args.k = tile_k; + sc_st_gemm(largs->gemm_fp, &sc_st_args); + + uint32_t end_cycle = snrt_mcycle(); + } + + // Add the partial result tiles from the various clusters together + // in a logarithmic reduction fashion. + // Note: both compute and DMA cores participate in this step. + if (largs->parallelize_k && (comp_k == (cluster_k_tiles - 1))) { + snrt_global_reduction_dma( + (double *)lcr, (double *)lc[c_buff_idx], tile_m * tile_n); + } + } + + // Synchronize cores after every iteration + snrt_global_barrier(); + } + + return 0; +} + + +int main () { + gemm_picobello(&args); + return 0; +} diff --git a/sw/sw.mk b/sw/sw.mk index 2c209e4b..4880f3eb 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -35,8 +35,7 @@ include $(SN_ROOT)/target/snitch_cluster/sw.mk endif # SNITCH APPLICATIONS -gemm_BUILD_DIR = $(PB_SNITCH_SW_DIR)/apps/build -$(eval include $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm/app.mk) +$(eval include $(PB_SNITCH_SW_DIR)/apps/blas/gemm/app.mk) # Collect Snitch tests which should be built From 18f57e06d5c1c2b538992a467e79b628d1ffe51b Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 17 Jul 2025 10:27:40 +0200 Subject: [PATCH 03/35] sw: Remove legacy GEMM scripts --- sw/snitch/apps/blas/gemm/scripts/datagen.py | 185 -------------------- sw/snitch/apps/blas/gemm/scripts/verify.py | 58 ------ 2 files changed, 243 deletions(-) delete mode 100755 sw/snitch/apps/blas/gemm/scripts/datagen.py delete mode 100755 sw/snitch/apps/blas/gemm/scripts/verify.py diff --git a/sw/snitch/apps/blas/gemm/scripts/datagen.py b/sw/snitch/apps/blas/gemm/scripts/datagen.py deleted file mode 100755 index 02abcb97..00000000 --- a/sw/snitch/apps/blas/gemm/scripts/datagen.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2022 ETH Zurich and University of Bologna. -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 -# -# Authors: Tim Fischer -# Luca Bertaccini -# Viviane Potocnik -# Luca Colagrande - -import numpy as np -import re -import sys - -import snitch.util.sim.data_utils as du - - -np.random.seed(42) - - -class GemmDataGen(du.DataGen): - - # AXI splits bursts crossing 4KB address boundaries. To minimize - # the occurrence of these splits the data should be aligned to 4KB - BURST_ALIGNMENT = 4096 - NUM_CORES = 8 - - def golden_model(self, alpha, a, b, beta, c): - return alpha * np.matmul(a, b) + beta * c - - def exact_golden_model(self, alpha, a, b, beta, c): - m, n, k = a.shape[0], b.shape[1], b.shape[0] - - result = beta * c - for i in range(m): - for j in range(n): - for h in range(k): - result[i][j] += a[i][h] * b[h][j] - return result - - def infer_implementation(self, gemm_fp): - # gemm_fp: "gemm_fp64_opt" - # create a regex with fp__ - prec, impl = re.search(r'gemm_fp(\d+)_(\w+)', gemm_fp).group(1, 2) - return int(prec) // 8, impl - - def validate(self, gemm_fp, parallelize_m, - parallelize_k, m_tiles, n_tiles, k_tiles, transa, - transb, m, n, k, beta, **kwargs): - double_buffer = kwargs.get('double_buffer', False) - partition_banks = kwargs.get('partition_banks', False) - tile_m = m / m_tiles - tile_n = n / n_tiles - tile_k = k / k_tiles - - dtype, impl = self.infer_implementation(gemm_fp) - - # Calculate total TCDM occupation - # Note: doesn't account for double buffering - prec = du.size_from_precision_t(dtype) - a_size = tile_m * tile_k * prec - b_size = tile_k * tile_n * prec - c_size = tile_m * tile_n * prec - total_size = a_size - total_size += b_size - total_size += c_size - du.validate_tcdm_footprint(total_size) - - assert (m % m_tiles) == 0, 'm is not an integer multiple of tile size' - assert (n % n_tiles) == 0, 'n is not an integer multiple of tile size' - assert (k % k_tiles) == 0, 'k is not an integer multiple of tile size' - assert kwargs['load_a'] or (m_tiles == 1 and k_tiles == 1), 'A matrix can\'t be tiled if' \ - ' local tile buffer is externally managed (load_a == 0)' - assert kwargs['load_b'] or (k_tiles == 1 and n_tiles == 1), 'B matrix can\'t be tiled if' \ - ' local tile buffer is externally managed (load_b == 0)' - assert kwargs['load_c'] or (m_tiles == 1 and n_tiles == 1), 'C matrix can\'t be tiled if' \ - ' local tile buffer is externally managed (load_c == 0)' - assert not (parallelize_m and parallelize_k), 'Cannot parallelize k and m simultaneously' - assert not (double_buffer and parallelize_k), 'Cannot parallelize k when double buffering' - assert not transa, 'SIMD kernels don\'t support transposed A matrix' - assert (dtype == 8) or (impl == 'baseline') or (impl == 'naive') \ - or transb, 'Optimized SIMD kernels only support transposed B matrix' - assert (impl == 'baseline') or (impl == 'naive') or tile_n >= 8, \ - 'n dimension of tile size must be greater or equal to the unrolling factor (8) ' \ - 'when using optimized kernels' - assert beta == 0 or beta == 1, 'Only values of 0 or 1 supported for beta' - assert not (dtype == 8 and impl == "baseline"), 'No baseline implemented' \ - ' for FP64 (switch to NAIVE)' - assert not (((dtype == 8) or (dtype == 4)) and impl == "opt_ex"), \ - 'Expanding GEMM kernels not supported for FP64 and FP32' - assert not (dtype == 1 and impl == "opt"), 'FP8 not supported in' \ - ' optimized implementation (switch to opt_ex)' - assert not (partition_banks and (transb or transa or k_tiles > 1 or n_tiles > 1)), \ - 'Cannot allocate buffer in a subset of banks if the A, B and C matrix tiles are not ' \ - 'a contiguous 1D block of data. This is guaranteed if A and B are not transposed and' \ - ' only M is tiled.' - elements_per_tcdm_line = (8 * self.NUM_CORES) / dtype - k_invalid = (k % elements_per_tcdm_line) != 0 - n_invalid = (n % elements_per_tcdm_line) != 0 - assert not (partition_banks and (k_invalid or n_invalid)), 'Cannot allocate buffer' \ - ' in a subset of banks if the number of elements in K and N is not an exact ' \ - 'multiple of the number of elements that can be stored in a line of the TCDM. This ' \ - 'constraint could potentially be loosened.' - assert not (partition_banks and (dtype != 8)), 'Lower than double precision kernels do' \ - 'not support partitioned banks, yet.' - - def emit_header(self, **kwargs): - header = [super().emit_header()] - - # Validate parameters - self.validate(**kwargs) - - m, n, k = kwargs['m'], kwargs['n'], kwargs['k'] - - prec, _ = self.infer_implementation(kwargs['gemm_fp']) - - ctype = du.ctype_from_precision_t(prec) - - a = du.generate_random_array((m, k), prec, seed=42) - b = du.generate_random_array((k, n), prec, seed=42) - c = du.generate_random_array((m, n), prec, seed=42) - result = self.exact_golden_model(1, a, b, kwargs['beta'], c) - - # Store matrices in transposed form if requested - a = a.T if kwargs['transa'] else a - b = b.T if kwargs['transb'] else b - - a_uid = 'a' - b_uid = 'b' - c_uid = 'c' - m_uid = 'm' - n_uid = 'n' - k_uid = 'k' - prec_uid = 'prec' - beta_uid = 'beta' - transb_uid = 'transb' - - cfg = { - **kwargs, - 'a': a_uid, - 'b': b_uid, - 'c': c_uid, - 'prec': prec_uid, - 'lda': m if kwargs['transa'] else k, - 'ldb': k if kwargs['transb'] else n, - 'ldc': n, - } - cfg['m'] = m_uid - cfg['n'] = n_uid - cfg['k'] = k_uid - cfg['beta'] = beta_uid - cfg['transb'] = transb_uid - - a = a.flatten() - b = b.flatten() - c = c.flatten() - - # "extern" specifier is required on declarations preceding a definition - header += [du.format_array_declaration(f'extern {ctype}', a_uid, a.shape)] - header += [du.format_array_declaration(f'extern {ctype}', b_uid, b.shape)] - header += [du.format_array_declaration(f'extern {ctype}', c_uid, c.shape)] - # "extern" specifier ensures that the variable is emitted and not mangled - header += [du.format_scalar_definition('extern const uint32_t', prec_uid, prec)] - header += [du.format_scalar_definition('extern const uint32_t', m_uid, m)] - header += [du.format_scalar_definition('extern const uint32_t', n_uid, n)] - header += [du.format_scalar_definition('extern const uint32_t', k_uid, k)] - header += [du.format_scalar_definition('extern const uint32_t', beta_uid, kwargs['beta'])] - header += [du.format_scalar_definition('extern const uint32_t', transb_uid, - kwargs['transb'])] - header += [du.format_struct_definition('extern const gemm_args_t', 'args', cfg)] - header += [du.format_array_definition(ctype, a_uid, a, - section=kwargs['section'])] - header += [du.format_array_definition(ctype, b_uid, b, - section=kwargs['section'])] - header += [du.format_array_definition(ctype, c_uid, c, - section=kwargs['section'])] - result_def = du.format_array_definition(ctype, 'result', result.flatten()) - header += [du.format_ifdef_wrapper('BIST', result_def)] - header = '\n\n'.join(header) - - return header - - -if __name__ == "__main__": - sys.exit(GemmDataGen().main()) diff --git a/sw/snitch/apps/blas/gemm/scripts/verify.py b/sw/snitch/apps/blas/gemm/scripts/verify.py deleted file mode 100755 index 25df58c8..00000000 --- a/sw/snitch/apps/blas/gemm/scripts/verify.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2023 ETH Zurich and University of Bologna. -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 -# -# Luca Colagrande - -import numpy as np -import sys -from datagen import GemmDataGen - -from snitch.util.sim.verif_utils import Verifier -from snitch.util.sim.data_utils import ctype_from_precision_t - - -class GemmVerifier(Verifier): - - OUTPUT_UIDS = ['c'] - ERR_THRESHOLD = { - 1: 1e-4, - 2: 5e-1, - 4: 1e-3, - 8: 1e-3 - } - - def __init__(self): - super().__init__() - self.prec = self.get_input_from_symbol('prec', 'uint32_t')[0] - - def get_actual_results(self): - return self.get_output_from_symbol(self.OUTPUT_UIDS[0], ctype_from_precision_t(self.prec)) - - def get_expected_results(self): - a = self.get_input_from_symbol('a', ctype_from_precision_t(self.prec)) - b = self.get_input_from_symbol('b', ctype_from_precision_t(self.prec)) - c = self.get_input_from_symbol('c', ctype_from_precision_t(self.prec)) - m = self.get_input_from_symbol('m', 'uint32_t')[0] - n = self.get_input_from_symbol('n', 'uint32_t')[0] - k = self.get_input_from_symbol('k', 'uint32_t')[0] - beta = self.get_input_from_symbol('beta', 'uint32_t')[0] - transb = self.get_input_from_symbol('transb', 'uint32_t')[0] - - a = np.reshape(a, (m, k)) - if transb: - b = np.reshape(b, (n, k)) - b = b.transpose() - else: - b = np.reshape(b, (k, n)) - c = np.reshape(c, (m, n)) - - return GemmDataGen().exact_golden_model(1, a, b, beta, c).flatten() - - def check_results(self, *args): - return super().check_results(*args, rtol=self.ERR_THRESHOLD[self.prec]) - - -if __name__ == "__main__": - sys.exit(GemmVerifier().main()) From d0116ee7765259bc929d2039d0d27b74e1f7059a Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 17 Jul 2025 10:35:57 +0200 Subject: [PATCH 04/35] sw: Test GEMM parallelized over K --- sw/snitch/apps/blas/gemm/data/params.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sw/snitch/apps/blas/gemm/data/params.json b/sw/snitch/apps/blas/gemm/data/params.json index 3aa05e24..3771a12a 100644 --- a/sw/snitch/apps/blas/gemm/data/params.json +++ b/sw/snitch/apps/blas/gemm/data/params.json @@ -4,21 +4,21 @@ { setup_ssr: 1, - parallelize_m: 16, - parallelize_k: 0, - m_tiles: 16, // number of tiles in M dimension + parallelize_m: 0, + parallelize_k: 1, + m_tiles: 1, // number of tiles in M dimension n_tiles: 1, // number of tiles in N dimension - k_tiles: 1, // number of tiles in K dimension + k_tiles: 16, // number of tiles in K dimension load_a: 1, load_b: 1, load_c: 1, - double_buffer: 1, + double_buffer: 0, partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 2048, + m: 16, n: 16, - k: 16, + k: 2048, alpha: 1, beta: 0, gemm_fp: "gemm_fp64_opt" From 94d79a14840eb047c07010182e74204f25f9ef61 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 17 Jul 2025 17:39:06 +0200 Subject: [PATCH 05/35] sw: Streamline Snitch app integration --- sw/sw.mk | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sw/sw.mk b/sw/sw.mk index 4880f3eb..1adf44de 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -30,14 +30,13 @@ SNRT_BUILD_APPS = OFF SNRT_MEMORY_LD = $(PB_SNITCH_SW_DIR)/memory.ld SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h +SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/blas/gemm +SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy + ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk endif -# SNITCH APPLICATIONS -$(eval include $(PB_SNITCH_SW_DIR)/apps/blas/gemm/app.mk) - - # Collect Snitch tests which should be built PB_SNRT_TESTS_DIR = $(PB_SNITCH_SW_DIR)/tests PB_SNRT_TESTS_BUILDDIR = $(PB_SNITCH_SW_DIR)/tests/build From 31c66b24a17597ea7efa54d4f44213ee02210bef Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 17 Jul 2025 17:39:41 +0200 Subject: [PATCH 06/35] target: Add Make target for Snitch app verification --- README.md | 8 ++++++++ target/sim/vsim/vsim.mk | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 7c17363b..a91d7e00 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,14 @@ Use the `vsim-run-batch` command to run tests in batch mode with RTL optimizatio Use the `PRELMODE=3` flag to enable fast preload of the Snitch binary, and speed up the simulation. +Some applications produce a lot of output data, which would be time-consuming to check in simulation. +Said applications usually come with a Python verification script that can check the results from a dump of the memory contents at the end of the simulation. +For example, a verification script for the GEMM kernel can be found under `$(bender path snitch_cluster)/sw/blas/gemm/scripts/verify.py` +To run an application on Snitch and verify its results, do: +```bash +make vsim-run-batch-verify VERIFY_PY=$(bender path snitch_cluster)/sw/blas/gemm/scripts/verify.py PRELMODE=3 CHS_BINARY=sw/cheshire/tests/simple_offload.spm.elf SN_BINARY=sw/snitch/apps/blas/gemm/build/gemm.elf +``` + ### Additional help Additionally, you can run the following command to get a list of all available commands: diff --git a/target/sim/vsim/vsim.mk b/target/sim/vsim/vsim.mk index 438489fc..40e0ad30 100644 --- a/target/sim/vsim/vsim.mk +++ b/target/sim/vsim/vsim.mk @@ -53,3 +53,8 @@ vsim-run: vsim-run-batch: $(VSIM) -c $(VSIM_FLAGS) $(TB_DUT) -do "run -all; quit" + +vsim-run-batch-verify: vsim-run-batch +ifdef VERIFY_PY + $(VERIFY_PY) placeholder $(SN_BINARY) --no-ipc --memdump l2mem.bin --memaddr 0x70000000 +endif \ No newline at end of file From a2bd17e03d88b6573cb65c3710efb1e9103b0c3a Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Fri, 18 Jul 2025 13:40:29 +0200 Subject: [PATCH 07/35] gemm_picobello: Fix regression on parallelize_k config --- sw/snitch/apps/blas/gemm/src/gemm_picobello.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sw/snitch/apps/blas/gemm/src/gemm_picobello.c b/sw/snitch/apps/blas/gemm/src/gemm_picobello.c index 8d985d53..2d791cc3 100644 --- a/sw/snitch/apps/blas/gemm/src/gemm_picobello.c +++ b/sw/snitch/apps/blas/gemm/src/gemm_picobello.c @@ -207,12 +207,18 @@ static inline int gemm_picobello(const gemm_args_t *args) { banks_per_buffer * SNRT_TCDM_BANK_WIDTH, SNRT_TCDM_HYPERBANK_WIDTH); } else { - // Multicast B to all clusters - if (snrt_cluster_idx() == 0) { - // Load B from L2 - snrt_dma_load_2d_tile_mcast( - lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, - tile_k, tile_n, largs->ldb, largs->prec, 0x003C0000); + if (largs->parallelize_k) { + snrt_dma_load_2d_tile( + lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, + tile_k, tile_n, largs->ldb, largs->prec); + } else { + // Multicast B to all clusters + if (snrt_cluster_idx() == 0) { + // Load B from L2 + snrt_dma_load_2d_tile_mcast( + lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, + tile_k, tile_n, largs->ldb, largs->prec, 0x003C0000); + } } } } From b048875f2947f8de6827032fc542bce6417c40f5 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Fri, 18 Jul 2025 13:49:00 +0200 Subject: [PATCH 08/35] sw: Rename Picobello GEMM to support both GEMM versions --- sw/snitch/apps/blas/gemm/app.mk | 17 ----------------- sw/snitch/apps/gemm_2d/app.mk | 17 +++++++++++++++++ .../{blas/gemm => gemm_2d}/data/params.json | 8 ++++---- sw/snitch/apps/{blas/gemm => gemm_2d}/roi.json | 0 .../gemm_picobello.c => gemm_2d/src/gemm_2d.c} | 0 sw/sw.mk | 3 ++- 6 files changed, 23 insertions(+), 22 deletions(-) delete mode 100644 sw/snitch/apps/blas/gemm/app.mk create mode 100644 sw/snitch/apps/gemm_2d/app.mk rename sw/snitch/apps/{blas/gemm => gemm_2d}/data/params.json (89%) rename sw/snitch/apps/{blas/gemm => gemm_2d}/roi.json (100%) rename sw/snitch/apps/{blas/gemm/src/gemm_picobello.c => gemm_2d/src/gemm_2d.c} (100%) diff --git a/sw/snitch/apps/blas/gemm/app.mk b/sw/snitch/apps/blas/gemm/app.mk deleted file mode 100644 index 293a374c..00000000 --- a/sw/snitch/apps/blas/gemm/app.mk +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023 ETH Zurich and University of Bologna. -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 -# -# Lorenzo leone - -APP := gemm -$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/blas/$(APP)/build -SRC_DIR := $(PB_SNITCH_SW_DIR)/apps/blas/$(APP)/src -SRCS := $(SRC_DIR)/gemm_picobello.c -$(APP)_INCDIRS := $(SN_ROOT)/sw/blas $(SN_ROOT)/sw/blas/$(APP)/src - -# Refer to snitch utiulities -$(APP)_SCRIPT_DIR := $(SN_ROOT)/sw/blas/$(APP)/scripts - -include $(SN_ROOT)/sw/apps/common.mk -include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/gemm_2d/app.mk b/sw/snitch/apps/gemm_2d/app.mk new file mode 100644 index 00000000..f0155c5b --- /dev/null +++ b/sw/snitch/apps/gemm_2d/app.mk @@ -0,0 +1,17 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Lorenzo Leone + +APP := gemm_2d +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/$(APP)/build +SRC_DIR := $(PB_SNITCH_SW_DIR)/apps/$(APP)/src +SRCS := $(SRC_DIR)/gemm_2d.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/blas $(SN_ROOT)/sw/blas/gemm/src + +# Refer to Snitch scripts +$(APP)_SCRIPT_DIR := $(SN_ROOT)/sw/blas/gemm/scripts + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/blas/gemm/data/params.json b/sw/snitch/apps/gemm_2d/data/params.json similarity index 89% rename from sw/snitch/apps/blas/gemm/data/params.json rename to sw/snitch/apps/gemm_2d/data/params.json index 3771a12a..54ed893d 100644 --- a/sw/snitch/apps/blas/gemm/data/params.json +++ b/sw/snitch/apps/gemm_2d/data/params.json @@ -16,10 +16,10 @@ partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 16, - n: 16, - k: 2048, + m: 8, + n: 1, + k: 16, alpha: 1, beta: 0, - gemm_fp: "gemm_fp64_opt" + gemm_fp: "gemm_fp64_naive" } diff --git a/sw/snitch/apps/blas/gemm/roi.json b/sw/snitch/apps/gemm_2d/roi.json similarity index 100% rename from sw/snitch/apps/blas/gemm/roi.json rename to sw/snitch/apps/gemm_2d/roi.json diff --git a/sw/snitch/apps/blas/gemm/src/gemm_picobello.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c similarity index 100% rename from sw/snitch/apps/blas/gemm/src/gemm_picobello.c rename to sw/snitch/apps/gemm_2d/src/gemm_2d.c diff --git a/sw/sw.mk b/sw/sw.mk index 1adf44de..8c101b8e 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -30,7 +30,8 @@ SNRT_BUILD_APPS = OFF SNRT_MEMORY_LD = $(PB_SNITCH_SW_DIR)/memory.ld SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h -SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/blas/gemm +SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/gemm_2d +SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) From ffa7c3bd9b7a9940ed811084797214b31fa90ad1 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 21 Jul 2025 10:26:58 +0200 Subject: [PATCH 09/35] sw: Add FlashAttention kernel --- sw/sw.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/sw/sw.mk b/sw/sw.mk index 8c101b8e..9c71d38a 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -33,6 +33,7 @@ SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/gemm_2d SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy +SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/dnn/flashattention_2 ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk From e2681494f00f4fbb84488d249fd8b7ad25046183 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 21 Jul 2025 15:45:54 +0200 Subject: [PATCH 10/35] sw: Fix Snitch integration --- sw/snitch/runtime/src/snrt.S | 19 ++++++++++++++++++- sw/snitch/runtime/src/snrt.h | 3 ++- sw/sw.mk | 4 ++++ 3 files changed, 24 insertions(+), 2 deletions(-) mode change 120000 => 100644 sw/snitch/runtime/src/snrt.S diff --git a/sw/snitch/runtime/src/snrt.S b/sw/snitch/runtime/src/snrt.S deleted file mode 120000 index 6fb619df..00000000 --- a/sw/snitch/runtime/src/snrt.S +++ /dev/null @@ -1 +0,0 @@ -../../../../.deps/snitch_cluster/target/snitch_cluster/sw/runtime/rtl/src/snrt.S \ No newline at end of file diff --git a/sw/snitch/runtime/src/snrt.S b/sw/snitch/runtime/src/snrt.S new file mode 100644 index 00000000..89fa8995 --- /dev/null +++ b/sw/snitch/runtime/src/snrt.S @@ -0,0 +1,18 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#define SNRT_INIT_INT_REGS +#define SNRT_INIT_FP_REGS +#define SNRT_INIT_GP +#define SNRT_INIT_CORE_INFO +#define SNRT_INIT_CLS +#define SNRT_INIT_STACK +#define SNRT_INIT_TLS +#define SNRT_CRT0_PARK + +#include "pb_raw_addrmap.h" +#define SNRT_TCDM_START_ADDR PICOBELLO_ADDRMAP_CLUSTER_0_TCDM_BASE_ADDR + +#include "snitch_cluster_cfg.h" +#include "start.S" diff --git a/sw/snitch/runtime/src/snrt.h b/sw/snitch/runtime/src/snrt.h index 137a3737..61602996 100644 --- a/sw/snitch/runtime/src/snrt.h +++ b/sw/snitch/runtime/src/snrt.h @@ -11,7 +11,8 @@ #include "pb_addrmap.h" #include "snitch_cluster_cfg.h" #include "snitch_cluster_peripheral_addrmap.h" -#include "snitch_cluster_raw_addrmap.h" +#include "pb_raw_addrmap.h" +#define SNRT_TCDM_START_ADDR PICOBELLO_ADDRMAP_CLUSTER_0_TCDM_BASE_ADDR // TODO: the 40000 stride is hardcoded here, but it would better be // autogenerated by Floogen. At the same time that would be diff --git a/sw/sw.mk b/sw/sw.mk index 9c71d38a..c2f8da10 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -29,6 +29,7 @@ SNRT_INCDIRS = $(PB_INCDIR) $(PB_GEN_DIR) SNRT_BUILD_APPS = OFF SNRT_MEMORY_LD = $(PB_SNITCH_SW_DIR)/memory.ld SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h +SNRT_HAL_HDRS += $(PB_GEN_DIR)/pb_raw_addrmap.h SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/gemm_2d SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm @@ -39,6 +40,9 @@ ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk endif +$(PB_GEN_DIR)/pb_raw_addrmap.h: $(PB_RDL_ALL) + $(PEAKRDL) raw-header $< -o $@ $(PEAKRDL_INCLUDES) $(PEAKRDL_DEFINES) --base_name $(notdir $(basename $@)) --format c + # Collect Snitch tests which should be built PB_SNRT_TESTS_DIR = $(PB_SNITCH_SW_DIR)/tests PB_SNRT_TESTS_BUILDDIR = $(PB_SNITCH_SW_DIR)/tests/build From de81188d8d05fedd85aa739ac5e43e93bf20236d Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 21 Jul 2025 15:46:11 +0200 Subject: [PATCH 11/35] sw: Add `fused_concat_linear` app --- sw/snitch/apps/fused_concat_linear/app.mk | 15 +++++++++++++++ .../apps/fused_concat_linear/data/params.json | 11 +++++++++++ sw/sw.mk | 1 + 3 files changed, 27 insertions(+) create mode 100644 sw/snitch/apps/fused_concat_linear/app.mk create mode 100644 sw/snitch/apps/fused_concat_linear/data/params.json diff --git a/sw/snitch/apps/fused_concat_linear/app.mk b/sw/snitch/apps/fused_concat_linear/app.mk new file mode 100644 index 00000000..fd0eb531 --- /dev/null +++ b/sw/snitch/apps/fused_concat_linear/app.mk @@ -0,0 +1,15 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Luca Colagrande + +APP := fused_concat_linear +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/$(APP)/build +$(APP)_DATA_CFG := $(PB_SNITCH_SW_DIR)/apps/$(APP)/data/params.json +SRC_DIR := $(SN_ROOT)/sw/dnn/$(APP)/src +SRCS := $(SRC_DIR)/main.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/dnn/src $(SN_ROOT)/sw/blas + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/fused_concat_linear/data/params.json b/sw/snitch/apps/fused_concat_linear/data/params.json new file mode 100644 index 00000000..be80d646 --- /dev/null +++ b/sw/snitch/apps/fused_concat_linear/data/params.json @@ -0,0 +1,11 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +{ + num_inputs: 16, + input_shape: [8, 1], + output_shape: [8, 1], + dtype: "FP64", + gemm_implementation: "gemm_fp64_naive" +} \ No newline at end of file diff --git a/sw/sw.mk b/sw/sw.mk index c2f8da10..0be5660b 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -35,6 +35,7 @@ SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/gemm_2d SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/dnn/flashattention_2 +SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/fused_concat_linear ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk From 5df282c9db2a7de8b7325baa7b46d56416ed80e9 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Tue, 22 Jul 2025 13:19:59 +0200 Subject: [PATCH 12/35] sw: Separate Snitch HAL build and source directories --- sw/snitch/runtime/src/snitch_cluster_memory.c | 1 + sw/sw.mk | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 120000 sw/snitch/runtime/src/snitch_cluster_memory.c diff --git a/sw/snitch/runtime/src/snitch_cluster_memory.c b/sw/snitch/runtime/src/snitch_cluster_memory.c new file mode 120000 index 00000000..adb289b9 --- /dev/null +++ b/sw/snitch/runtime/src/snitch_cluster_memory.c @@ -0,0 +1 @@ +../../../../.deps/snitch_cluster/target/snitch_cluster/sw/runtime/common/snitch_cluster_memory.c \ No newline at end of file diff --git a/sw/sw.mk b/sw/sw.mk index 0be5660b..730c57c1 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -28,6 +28,7 @@ SN_RVTESTS_BUILDDIR = $(PB_SNITCH_SW_DIR)/riscv-tests/build SNRT_INCDIRS = $(PB_INCDIR) $(PB_GEN_DIR) SNRT_BUILD_APPS = OFF SNRT_MEMORY_LD = $(PB_SNITCH_SW_DIR)/memory.ld +SNRT_HAL_BUILD_DIR = $(PB_SNITCH_SW_DIR)/runtime/build SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h SNRT_HAL_HDRS += $(PB_GEN_DIR)/pb_raw_addrmap.h @@ -37,7 +38,7 @@ SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/dnn/flashattention_2 SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/fused_concat_linear -ifneq (,$(filter chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) +ifneq (,$(filter $(PB_SNITCH_SW_DIR)% chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk endif @@ -77,7 +78,7 @@ PB_LINK_MODE ?= spm # We need to include the address map and snitch cluster includes CHS_SW_INCLUDES += -I$(PB_INCDIR) -CHS_SW_INCLUDES += -I$(SNRT_HAL_HDRS_DIR) +CHS_SW_INCLUDES += -I$(SNRT_HAL_BUILD_DIR) CHS_SW_INCLUDES += -I$(PB_GEN_DIR) # Collect tests, which should be build for all modes, and their .dump targets From 277494c75069ebcdace74644e8793d97aa48d862 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Wed, 23 Jul 2025 10:10:38 +0200 Subject: [PATCH 13/35] sw: Add MHA kernel --- sw/snitch/apps/mha/app.mk | 15 +++++++++++++++ sw/snitch/apps/mha/data/params.json | 14 ++++++++++++++ sw/sw.mk | 1 + 3 files changed, 30 insertions(+) create mode 100644 sw/snitch/apps/mha/app.mk create mode 100644 sw/snitch/apps/mha/data/params.json diff --git a/sw/snitch/apps/mha/app.mk b/sw/snitch/apps/mha/app.mk new file mode 100644 index 00000000..4af8cb3b --- /dev/null +++ b/sw/snitch/apps/mha/app.mk @@ -0,0 +1,15 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Luca Colagrande + +APP := mha +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/$(APP)/build +$(APP)_DATA_CFG := $(PB_SNITCH_SW_DIR)/apps/$(APP)/data/params.json +SRC_DIR := $(SN_ROOT)/sw/dnn/$(APP)/src +SRCS := $(SRC_DIR)/main.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/dnn/src $(SN_ROOT)/sw/blas + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/mha/data/params.json b/sw/snitch/apps/mha/data/params.json new file mode 100644 index 00000000..2c63ab3d --- /dev/null +++ b/sw/snitch/apps/mha/data/params.json @@ -0,0 +1,14 @@ +// Copyright 2025 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +{ + num_heads: 2, + L: 16, + S: 16, + d: 16, + B_r: 16, + B_c: 16, + dtype: "FP32", + baseline: true +} \ No newline at end of file diff --git a/sw/sw.mk b/sw/sw.mk index 730c57c1..b0f5c5d9 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -37,6 +37,7 @@ SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/dnn/flashattention_2 SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/fused_concat_linear +SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/mha ifneq (,$(filter $(PB_SNITCH_SW_DIR)% chs-bootrom% chs-sw% sn% pb-sn-tests% sw%,$(MAKECMDGOALS))) include $(SN_ROOT)/target/snitch_cluster/sw.mk From 1eaa72f872c6a6030a8fef8baac96b7f5588fe9a Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Wed, 23 Jul 2025 10:35:33 +0200 Subject: [PATCH 14/35] sw: Shrink FusedConcatLinear to only 2 clusters --- sw/snitch/apps/fused_concat_linear/data/params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sw/snitch/apps/fused_concat_linear/data/params.json b/sw/snitch/apps/fused_concat_linear/data/params.json index be80d646..48260f9a 100644 --- a/sw/snitch/apps/fused_concat_linear/data/params.json +++ b/sw/snitch/apps/fused_concat_linear/data/params.json @@ -3,7 +3,7 @@ // SPDX-License-Identifier: SHL-0.51 { - num_inputs: 16, + num_inputs: 2, input_shape: [8, 1], output_shape: [8, 1], dtype: "FP64", From f17a3ca82c05e5de764daeaa23a7b67bd2546f55 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Thu, 24 Jul 2025 20:16:47 +0200 Subject: [PATCH 15/35] sw: Start performance evaluation GEMM with mcast --- sw/snitch/apps/gemm_2d/data/params.json | 16 ++++++++-------- sw/snitch/apps/gemm_2d/roi.json | 16 ++++++++++++---- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 2 ++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/sw/snitch/apps/gemm_2d/data/params.json b/sw/snitch/apps/gemm_2d/data/params.json index 54ed893d..524ad42b 100644 --- a/sw/snitch/apps/gemm_2d/data/params.json +++ b/sw/snitch/apps/gemm_2d/data/params.json @@ -4,22 +4,22 @@ { setup_ssr: 1, - parallelize_m: 0, - parallelize_k: 1, - m_tiles: 1, // number of tiles in M dimension + parallelize_m: 1, + parallelize_k: 0, + m_tiles: 64, // number of tiles in M dimension n_tiles: 1, // number of tiles in N dimension - k_tiles: 16, // number of tiles in K dimension + k_tiles: 1, // number of tiles in K dimension load_a: 1, load_b: 1, load_c: 1, - double_buffer: 0, + double_buffer: 1, partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 8, - n: 1, + m: 2048, + n: 16, k: 16, alpha: 1, beta: 0, - gemm_fp: "gemm_fp64_naive" + gemm_fp: "gemm_fp64_opt" } diff --git a/sw/snitch/apps/gemm_2d/roi.json b/sw/snitch/apps/gemm_2d/roi.json index 8a501328..f9559587 100644 --- a/sw/snitch/apps/gemm_2d/roi.json +++ b/sw/snitch/apps/gemm_2d/roi.json @@ -16,12 +16,20 @@ // DMA core { - "thread": "${f'hart_{cluster * 9 + 8 + 1}'}", "roi": [ - % for i in range(0, N_TILES): - {"idx": ${2 * i + 1}, "label": "${f'tile_{i}'}"}, - % endfor + {"idx": 1, "label": "${f'tile_in_0'}"}, + {"idx": 3, "label": "${f'tile_in_1'}"}, + {"idx": 7, "label": "${f'tile_in_2'}"}, + {"idx": 11, "label": "${f'tile_in_3'}"}, + // % for i in range(2, N_TILES-1): + // {"idx": ${2*i + 1}, "label": "${f'tile_out_{i}'}"}, + // {"idx": ${4*i + 5}, "label": "${f'tile_in_{i}'}"}, + // % endfor + {"idx": 5, "label": "${f'tile_out_0'}"}, + {"idx": 9, "label": "${f'tile_out_1'}"}, + {"idx": 13, "label": "${f'tile_out_2'}"}, + {"idx": 15, "label": "${f'tile_out_3'}"}, ] }, % endfor diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index 2d791cc3..4d1984ca 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -140,6 +140,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { // DMA out phase if (snrt_is_dm_core()) { if (dma_out_i >= 0) { + snrt_mcycle(); // Switch buffers int buff_idx = largs->double_buffer ? dma_out_mn % 2 : 0; @@ -160,6 +161,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { } snrt_dma_wait_all(); } + snrt_mcycle(); } } From bb2b43a65fccf74e1a4f95ab4e1a8584cf4fe1f4 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 17 Jul 2025 18:06:07 +0200 Subject: [PATCH 16/35] ci: Add Snitch apps --- .gitlab-ci.yml | 7 ++++--- .gitlab/common.yml | 4 +++- .gitlab/sw-tests.yml | 6 ++++++ sw/snitch/apps/axpy/app.mk | 15 +++++++++++++++ sw/snitch/apps/axpy/data/params.json | 9 +++++++++ sw/snitch/apps/gemm/app.mk | 15 +++++++++++++++ sw/snitch/apps/gemm/data/params.json | 25 +++++++++++++++++++++++++ sw/sw.mk | 4 ++-- 8 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 sw/snitch/apps/axpy/app.mk create mode 100644 sw/snitch/apps/axpy/data/params.json create mode 100644 sw/snitch/apps/gemm/app.mk create mode 100644 sw/snitch/apps/gemm/data/params.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 551a1e96..9a98fdb8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,15 +87,16 @@ sim-vsim: - vsim-compile script: # Run the simulation - - make vsim-run-batch + - make vsim-run-batch-verify # Check either success or failure for non-zero exit codes - 'if [ -z "${NZ_EXIT_CODE}" ]; then grep "] SUCCESS" transcript || (exit 1); else grep "] FAILED: return code ${NZ_EXIT_CODE}" transcript || (exit 1); fi' # Check for UART output - 'if [ ! -z "${USTR}" ]; then (grep " \[UART\] ${USTR}" transcript); fi' # Check for any fatal errors - 'if grep "Fatal:" transcript; then exit 1; fi' - # Check for any errors (except one for non-zero exit codes) - - 'if [ ! -z "${NZ_EXIT_CODE}" ]; then count=$(grep -c "Error:" transcript); if [ "$count" -ne 1 ]; then exit 1; fi; else if grep -q "Error:" transcript; then exit 1; fi; fi' + # Check for any non-fatal errors. One and only one error is expected with a non-zero exit code. + # Ignore all errors when using a separate verification script. + - 'if [ -z "${VERIFY_PY}" ]; then if [ ! -z "${NZ_EXIT_CODE}" ]; then count=$(grep -c "Error:" transcript); if [ "$count" -ne 1 ]; then exit 1; fi; else if grep -q "Error:" transcript; then exit 1; fi; fi; fi' artifacts: paths: - transcript diff --git a/.gitlab/common.yml b/.gitlab/common.yml index 3091b58e..4e6dd5ed 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -66,11 +66,13 @@ variables: - .cache-deps - .init-env script: - - make sn-tests + - make sn-tests DEBUG=ON + - make sn-apps DEBUG=ON - make pb-sn-tests artifacts: paths: - sw/snitch/tests/build/*.elf + - sw/snitch/apps/**/build/*.elf expire_in: 1 day # Compile the cheshire software tests diff --git a/.gitlab/sw-tests.yml b/.gitlab/sw-tests.yml index 5a4bbcb6..dd4b62c0 100644 --- a/.gitlab/sw-tests.yml +++ b/.gitlab/sw-tests.yml @@ -7,6 +7,7 @@ variables: CHS_BUILD_DIR: sw/cheshire/tests SN_BUILD_DIR: sw/snitch/tests/build + SN_ROOT: .deps/snitch_cluster parallel: matrix: - { CHS_BINARY: $CHS_BUILD_DIR/sanity.spm.elf, PRELMODE: 0 } @@ -29,3 +30,8 @@ - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: $SN_BUILD_DIR/redmule.elf } - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: $SN_BUILD_DIR/redmule_quant.elf } - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: $SN_BUILD_DIR/datamover.elf } + - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: sw/snitch/apps/gemm_2d/build/gemm_2d.elf, VERIFY_PY: $SN_ROOT/sw/blas/gemm/scripts/verify.py, PRELMODE: 3 } + - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: sw/snitch/apps/fused_concat_linear/build/fused_concat_linear.elf, VERIFY_PY: $SN_ROOT/sw/dnn/fused_concat_linear/scripts/verify.py, PRELMODE: 3 } + - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: sw/snitch/apps/mha/build/mha.elf, VERIFY_PY: $SN_ROOT/sw/dnn/mha/scripts/verify.py, PRELMODE: 3 } + - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: sw/snitch/apps/gemm/build/gemm.elf, VERIFY_PY: $SN_ROOT/sw/blas/gemm/scripts/verify.py, PRELMODE: 3 } + - { CHS_BINARY: $CHS_BUILD_DIR/simple_offload.spm.elf, SN_BINARY: sw/snitch/apps/axpy/build/axpy.elf, VERIFY_PY: $SN_ROOT/sw/blas/axpy/scripts/verify.py, PRELMODE: 3 } diff --git a/sw/snitch/apps/axpy/app.mk b/sw/snitch/apps/axpy/app.mk new file mode 100644 index 00000000..9e032762 --- /dev/null +++ b/sw/snitch/apps/axpy/app.mk @@ -0,0 +1,15 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Luca Colagrande + +APP := axpy +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/$(APP)/build +$(APP)_DATA_CFG := $(PB_SNITCH_SW_DIR)/apps/$(APP)/data/params.json +SRC_DIR := $(SN_ROOT)/sw/blas/$(APP)/src +SRCS := $(SRC_DIR)/main.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/blas + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/axpy/data/params.json b/sw/snitch/apps/axpy/data/params.json new file mode 100644 index 00000000..eafb7128 --- /dev/null +++ b/sw/snitch/apps/axpy/data/params.json @@ -0,0 +1,9 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +{ + "n_tiles": 5, + "n": 2560, + "funcptr": "axpy_opt" +} diff --git a/sw/snitch/apps/gemm/app.mk b/sw/snitch/apps/gemm/app.mk new file mode 100644 index 00000000..1dd86b44 --- /dev/null +++ b/sw/snitch/apps/gemm/app.mk @@ -0,0 +1,15 @@ +# Copyright 2025 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Luca Colagrande + +APP := gemm +$(APP)_BUILD_DIR ?= $(PB_SNITCH_SW_DIR)/apps/$(APP)/build +$(APP)_DATA_CFG := $(PB_SNITCH_SW_DIR)/apps/$(APP)/data/params.json +SRC_DIR := $(SN_ROOT)/sw/blas/$(APP)/src +SRCS := $(SRC_DIR)/main.c +$(APP)_INCDIRS := $(SN_ROOT)/sw/blas + +include $(SN_ROOT)/sw/apps/common.mk +include $(SN_ROOT)/target/snitch_cluster/sw/apps/common.mk diff --git a/sw/snitch/apps/gemm/data/params.json b/sw/snitch/apps/gemm/data/params.json new file mode 100644 index 00000000..c0b2aa52 --- /dev/null +++ b/sw/snitch/apps/gemm/data/params.json @@ -0,0 +1,25 @@ +// Copyright 2024 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +{ + setup_ssr: 1, + parallelize_m: 0, + parallelize_k: 0, + m_tiles: 1, // number of tiles in M dimension + n_tiles: 1, // number of tiles in N dimension + k_tiles: 1, // number of tiles in K dimension + load_a: 1, + load_b: 1, + load_c: 1, + double_buffer: 0, + partition_banks: 0, + transa: false, + transb: false, // must be true for SIMD + m: 32, + n: 16, + k: 16, + alpha: 1, + beta: 0, + gemm_fp: "gemm_fp64_opt" +} diff --git a/sw/sw.mk b/sw/sw.mk index b0f5c5d9..49144ecf 100644 --- a/sw/sw.mk +++ b/sw/sw.mk @@ -33,8 +33,8 @@ SNRT_HAL_HDRS = $(PB_GEN_DIR)/pb_addrmap.h SNRT_HAL_HDRS += $(PB_GEN_DIR)/pb_raw_addrmap.h SNRT_APPS = $(PB_SNITCH_SW_DIR)/apps/gemm_2d -SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/gemm -SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/blas/axpy +SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/gemm +SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/axpy SNRT_APPS += $(SN_ROOT)/target/snitch_cluster/sw/apps/dnn/flashattention_2 SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/fused_concat_linear SNRT_APPS += $(PB_SNITCH_SW_DIR)/apps/mha From 30c117bd8fc36521284507890690534126dcda09 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Wed, 30 Jul 2025 14:14:54 +0200 Subject: [PATCH 17/35] sw: Change GEMM configuration --- sw/snitch/apps/gemm/data/params.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sw/snitch/apps/gemm/data/params.json b/sw/snitch/apps/gemm/data/params.json index c0b2aa52..524ad42b 100644 --- a/sw/snitch/apps/gemm/data/params.json +++ b/sw/snitch/apps/gemm/data/params.json @@ -4,19 +4,19 @@ { setup_ssr: 1, - parallelize_m: 0, + parallelize_m: 1, parallelize_k: 0, - m_tiles: 1, // number of tiles in M dimension + m_tiles: 64, // number of tiles in M dimension n_tiles: 1, // number of tiles in N dimension k_tiles: 1, // number of tiles in K dimension load_a: 1, load_b: 1, load_c: 1, - double_buffer: 0, + double_buffer: 1, partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 32, + m: 2048, n: 16, k: 16, alpha: 1, From f7a16dc96921d27b088d1221a4838e2c82b3f3b8 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 28 Jul 2025 14:57:58 +0200 Subject: [PATCH 18/35] bender: Bump snitch_cluster --- Bender.lock | 4 ++-- Bender.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Bender.lock b/Bender.lock index c8cba67f..b2f05122 100644 --- a/Bender.lock +++ b/Bender.lock @@ -383,10 +383,10 @@ packages: - common_cells - register_interface snitch_cluster: - revision: e4eaa0fb64767bb8f6b7d1f5fa705928171092b2 + revision: 37706b02959c22276bbd16ad8c934d8bdd4d416b version: null source: - Git: https://github.com/pulp-platform/snitch_cluster.git + Git: https://github.com/Lore0599/snitch_cluster.git dependencies: - apb - axi diff --git a/Bender.yml b/Bender.yml index a2172691..b38346cc 100644 --- a/Bender.yml +++ b/Bender.yml @@ -12,7 +12,7 @@ dependencies: axi: { git: "https://github.com/pulp-platform/axi.git", version: "0.39.6" } common_cells: { git: "https://github.com/pulp-platform/common_cells.git", rev: "snitch" } cheshire: { git: "https://github.com/pulp-platform/cheshire.git", rev: "picobello" } - snitch_cluster: { git: "https://github.com/pulp-platform/snitch_cluster.git", rev: "e4eaa0fb64767bb8f6b7d1f5fa705928171092b2" } + snitch_cluster: { git: "https://github.com/pulp-platform/snitch_cluster.git", rev: "37706b02959c22276bbd16ad8c934d8bdd4d416b" } floo_noc: { git: "https://github.com/pulp-platform/FlooNoC.git", rev: "develop" } obi: { git: "https://github.com/pulp-platform/obi.git", rev: "acfcd0f80c7539aa8da7821a66d9acf2074a5b4e" } redmule: { git: "https://github.com/pulp-platform/redmule.git", rev: "picobello" } From 31fb9ec58a103793fbb96bb50e580a0504e87675 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 31 Jul 2025 17:12:44 +0200 Subject: [PATCH 19/35] sw: Use optimized GEMM in FusedConcatLinear --- sw/snitch/apps/fused_concat_linear/data/params.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sw/snitch/apps/fused_concat_linear/data/params.json b/sw/snitch/apps/fused_concat_linear/data/params.json index 48260f9a..935cbd99 100644 --- a/sw/snitch/apps/fused_concat_linear/data/params.json +++ b/sw/snitch/apps/fused_concat_linear/data/params.json @@ -4,8 +4,8 @@ { num_inputs: 2, - input_shape: [8, 1], - output_shape: [8, 1], + input_shape: [16, 16], + output_shape: [16, 16], dtype: "FP64", - gemm_implementation: "gemm_fp64_naive" + gemm_implementation: "gemm_fp64_opt" } \ No newline at end of file From 5281efd368db38b03314830cef95b3ca15d0a214 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Tue, 19 Aug 2025 15:28:40 +0200 Subject: [PATCH 20/35] Start gemm2d optimization for NoC --- sw/snitch/apps/gemm_2d/data/params.json | 6 +- sw/snitch/apps/gemm_2d/roi.json | 22 +-- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 200 +++++++++++++++++++++--- 3 files changed, 194 insertions(+), 34 deletions(-) diff --git a/sw/snitch/apps/gemm_2d/data/params.json b/sw/snitch/apps/gemm_2d/data/params.json index 524ad42b..5095ae3e 100644 --- a/sw/snitch/apps/gemm_2d/data/params.json +++ b/sw/snitch/apps/gemm_2d/data/params.json @@ -6,17 +6,17 @@ setup_ssr: 1, parallelize_m: 1, parallelize_k: 0, - m_tiles: 64, // number of tiles in M dimension + m_tiles: 2, // number of tiles in M dimension n_tiles: 1, // number of tiles in N dimension k_tiles: 1, // number of tiles in K dimension load_a: 1, load_b: 1, load_c: 1, - double_buffer: 1, + double_buffer: 0, partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 2048, + m: 16, n: 16, k: 16, alpha: 1, diff --git a/sw/snitch/apps/gemm_2d/roi.json b/sw/snitch/apps/gemm_2d/roi.json index f9559587..a669996d 100644 --- a/sw/snitch/apps/gemm_2d/roi.json +++ b/sw/snitch/apps/gemm_2d/roi.json @@ -19,17 +19,17 @@ "thread": "${f'hart_{cluster * 9 + 8 + 1}'}", "roi": [ {"idx": 1, "label": "${f'tile_in_0'}"}, - {"idx": 3, "label": "${f'tile_in_1'}"}, - {"idx": 7, "label": "${f'tile_in_2'}"}, - {"idx": 11, "label": "${f'tile_in_3'}"}, - // % for i in range(2, N_TILES-1): - // {"idx": ${2*i + 1}, "label": "${f'tile_out_{i}'}"}, - // {"idx": ${4*i + 5}, "label": "${f'tile_in_{i}'}"}, - // % endfor - {"idx": 5, "label": "${f'tile_out_0'}"}, - {"idx": 9, "label": "${f'tile_out_1'}"}, - {"idx": 13, "label": "${f'tile_out_2'}"}, - {"idx": 15, "label": "${f'tile_out_3'}"}, + // {"idx": 3, "label": "${f'tile_in_1'}"}, + // {"idx": 7, "label": "${f'tile_in_2'}"}, + // {"idx": 11, "label": "${f'tile_in_3'}"}, + % for i in range(0, N_TILES - 1): + {"idx": ${4*i + 3}, "label": "${f'tile_in_{i+1}'}"}, + {"idx": ${4*i + 5}, "label": "${f'tile_out_{i}'}"}, + % endfor + // {"idx": 5, "label": "${f'tile_out_0'}"}, + // {"idx": 9, "label": "${f'tile_out_1'}"}, + // {"idx": 13, "label": "${f'tile_out_2'}"}, + {"idx": 15, "label": "${f'tile_out_{N_TILES-1}'}"}, ] }, % endfor diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index 4d1984ca..f9e7f565 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -6,6 +6,12 @@ // Luca Bertaccini // Luca Colagrande // Viviane Potocnik +// Lorenzo leone + +// TODO (lleone): LIMITATIONS +// +// - Works only when M_tile = snrt_cluster_num() +// - Works only if parallelized on M #include "snrt.h" #include @@ -14,6 +20,12 @@ #include #include "blas.h" +#define HW_MCAST + +#define L3_START_ADDRESS 0x70000000UL // Base address of memory tile 0 +#define L3_SIZE 0x100000UL // Size of memory tile (1MiB) +#define NUM_L3_TILES 8 // Number of memory tiles + // #define JOB_ARGS_PRELOADED #pragma clang diagnostic push @@ -21,6 +33,71 @@ #include "data.h" #pragma clang diagnostic pop +/*------------------------- NoC Helper functions ---------------------------*/ +static inline uintptr_t l3_tile_address(uint32_t tile_idx) { + return (uintptr_t)L3_START_ADDRESS + + (uintptr_t)tile_idx * (uintptr_t)L3_SIZE; +} + +static inline uintptr_t l3_tile_offset(uintptr_t src_addr) { + return (src_addr - (uintptr_t)L3_START_ADDRESS) & (uintptr_t)(L3_SIZE - 1); +} + +static inline uint32_t cluster_row(uint32_t cid) { return cid % 4u; } + +static inline uint32_t dst_tile_for_cluster(uint32_t cid) { + uint32_t row = cluster_row(cid); + return (cid < 8u) ? row // first 8 clusters -> left column tiles 0..3 + : (row + 4u); // clusters >= 8 -> right column tiles 4..7 +} + +// Allocate data in L3 to betetr map kernel in NoC system +static inline void allocate_l3_buffers(gemm_args_t *largs ) { + + uint32_t prec = largs->prec; + uint32_t mem_tile_idx = dst_tile_for_cluster(snrt_cluster_idx()); + + uintptr_t a_off = l3_tile_offset((uintptr_t) largs -> a); + uintptr_t c_off = l3_tile_offset((uintptr_t) largs -> c); + uintptr_t a_dst = l3_tile_address(mem_tile_idx) + a_off; + uintptr_t c_dst = l3_tile_address(mem_tile_idx) + c_off; + + // Move data in the correct memory tile location + uint32_t size_a = (size_t)largs->m * (size_t)largs->k; + uint32_t size_c = (size_t)largs->m * (size_t)largs->n; + snrt_dma_start_1d((void *) a_dst, (void *) largs->a, size_a * prec); + snrt_dma_start_1d((void *) c_dst, (void *) largs->c, size_c * prec); + + // Update A and C local pointer to the relocated memory tile address + largs->a = (void *) a_dst; + largs->c = (void *) c_dst; +} + +// Write back C tiles in original memory tile for verification purposes +static inline void write_back_c_tiles(gemm_args_t* largs, uint32_t m_tile_size, + uint32_t n_tile_size) { + uintptr_t c_src, c_dst; + int c_m_abs, c_n_abs; + + + for (uint32_t i = 0; i < largs->n_tiles; i++) { + // Position of the first element in the Tile to be written back + c_src = (uintptr_t )largs->c + (snrt_cluster_idx() * largs->n * m_tile_size + i * n_tile_size) * largs->prec; + c_dst = l3_tile_address(0) + l3_tile_offset(c_src); + + if (c_src != c_dst) { + c_m_abs = snrt_cluster_idx() * m_tile_size; + c_n_abs = i * n_tile_size; + + // Transfer the C tile into the destination + snrt_dma_store_2d_tile((void *) c_dst, (void *) c_src, + c_m_abs, c_n_abs, m_tile_size, + n_tile_size, largs->ldc, largs->prec); + + snrt_dma_wait_all(); + } + } +} /** * @brief Performs a General Matrix Multiplication (GEMM) operation on a @@ -85,10 +162,72 @@ static inline int gemm_picobello(const gemm_args_t *args) { } snrt_cluster_hw_barrier(); + // NoC layout (6 columns x 4 rows) + /* + // + |------| |------| |------| |------| |------| |------| + | M3 |---| C3 |---| C7 |---| C11 |---| C15 |---| M7 | + |------| |------| |------| |------| |------| |------| + | | | | | | + | | | | | | + |------| |------| |------| |------| |------| |------| + | M2 |---| C2 |---| C6 |---| C10 |---| C14 |---| M6 | + |------| |------| |------| |------| |------| |------| + | | | | | | + | | | | | | + |------| |------| |------| |------| |------| |------| + | M1 |---| C1 |---| C5 |---| C9 |---| C13 |---| M5 | + |------| |------| |------| |------| |------| |------| + | | | | | | + | | | | | | + |------| |------| |------| |------| |------| |------| + | M0 |---| C0 |---| C4 |---| C8 |---| C12 |---| M4 | + |------| |------| |------| |------| |------| |------| + // + */ + + // Use the DMA core of cluster 0 to place all data in the correct positions + // so the problem becomes NoC-optimized. + // + // Case: parallelization over M, with tiling along both M and N. + // - Each cluster processes a set of rows of A: + // [Cluster_idx * Mt : (Cluster_idx + 1) * Mt - 1]. + // - All clusters share the same set of columns of B: + // [num_iter * Nt : (num_iter + 1) * Nt - 1]. + // - Each cluster computes a full tile of C (no partial results - no reduction). + // + // Memory tile mapping: + // - Rows of A for a given cluster are placed in the same row. + // * The first half of the clusters load A from the memory tiles on the left [tile 0 - 3]. + // * The second half of the clusters load A from the memory tiles on the right [tile 4 - 7]. + // - The same scheme is used to store the corresponding tile of C. + // - Matrix B is stored entirely in the first memory tile. + // * Since all clusters need access to B, its exact location does not affect + // performance significantly. + // + // Notes: + // - All data movement to arrange memory tiles is performed before measuring + // kernel execution time. + // - With a proper linker script, data could be placed directly in the correct + // memory tiles without requiring extra DMA work from cluster 0. + + // TODO (lleone): Improve copying only the necessary information and not the full data stack + if (snrt_is_dm_core()) + { + allocate_l3_buffers(largs); + snrt_dma_wait_all(); + } + snrt_global_barrier(); + // Distribute m and k tiles to clusters uint32_t cluster_m_tiles = largs->m_tiles; uint32_t cluster_k_tiles = largs->k_tiles; - if (largs->parallelize_m) cluster_m_tiles /= snrt_cluster_num(); + if (largs->parallelize_m) { + uint32_t m_tiles_quotient = cluster_m_tiles / snrt_cluster_num(); + uint32_t m_tiles_remainder = cluster_m_tiles % snrt_cluster_num(); + cluster_m_tiles = m_tiles_quotient; + if (snrt_cluster_idx() < m_tiles_remainder) cluster_m_tiles++; + } if (largs->parallelize_k) cluster_k_tiles /= snrt_cluster_num(); // Calculate number of iterations @@ -137,6 +276,13 @@ static inline int gemm_picobello(const gemm_args_t *args) { dma_out_k_abs += snrt_cluster_idx() * cluster_k_tiles; } + // In the first k iteration we accumulate with the C matrix + // scaled by beta, in successive iterations we accumulate + // the previous partial result. The tile-level beta is thus + // a function of k: beta(k). + uint32_t comp_k_beta = comp_k_abs == 0 ? largs->beta : 1; + uint32_t dma_in_k_beta = dma_in_k_abs == 0 ? largs->beta : 1; + // DMA out phase if (snrt_is_dm_core()) { if (dma_out_i >= 0) { @@ -175,8 +321,14 @@ static inline int gemm_picobello(const gemm_args_t *args) { // the result, i.e. after finishing the K loop. int buff_idx = largs->double_buffer ? dma_in_i % 2 : 0; int c_buff_idx = largs->double_buffer ? dma_in_mn % 2 : 0; + int load_a = largs->double_buffer ? (dma_in_i < 2) : (dma_in_i < 1); // Load A + // TODO (lleone): When tiling on M and parallelizing on M there is no need + // to load At multiple times. + // If you have DOBU, you load twice and then At is available + // in both buffers. This can be done only when Mt is fully parallelizable + // in you system. if (largs->load_a) { if (largs->partition_banks) { snrt_dma_1d_to_2d( @@ -187,9 +339,11 @@ static inline int gemm_picobello(const gemm_args_t *args) { banks_per_buffer * SNRT_TCDM_BANK_WIDTH, SNRT_TCDM_HYPERBANK_WIDTH); } else { - snrt_dma_load_2d_tile( - la[buff_idx], largs->a, dma_in_m_abs, dma_in_k_abs, - tile_m, tile_k, largs->lda, largs->prec); + if (load_a) { + snrt_dma_load_2d_tile( + la[buff_idx], largs->a, dma_in_m_abs, dma_in_k_abs, + tile_m, tile_k, largs->lda, largs->prec); + } } } @@ -209,18 +363,25 @@ static inline int gemm_picobello(const gemm_args_t *args) { banks_per_buffer * SNRT_TCDM_BANK_WIDTH, SNRT_TCDM_HYPERBANK_WIDTH); } else { + // TODO (lleone): Is it really necessary? if (largs->parallelize_k) { snrt_dma_load_2d_tile( lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, tile_k, tile_n, largs->ldb, largs->prec); } else { // Multicast B to all clusters - if (snrt_cluster_idx() == 0) { - // Load B from L2 - snrt_dma_load_2d_tile_mcast( + #ifdef HW_MCAST + if (snrt_cluster_idx() == 0) { + // Load B from L2 + snrt_dma_load_2d_tile_mcast( + lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, + tile_k, tile_n, largs->ldb, largs->prec, 0x003C0000); + } + #else + snrt_dma_load_2d_tile( lb[buff_idx], largs->b, dma_in_k_abs, dma_in_n, - tile_k, tile_n, largs->ldb, largs->prec, 0x003C0000); - } + tile_k, tile_n, largs->ldb, largs->prec); + #endif } } } @@ -230,7 +391,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { // C tile is loaded only upon the first k iteration, then // the C array will contain the partial results from the // previous iteration - if (largs->load_c) { + if (largs->load_c && dma_in_k_beta != 0) { if (dma_in_k_abs == 0) { if (largs->partition_banks) { snrt_dma_1d_to_2d( @@ -268,7 +429,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { } // Additional barrier required when not double buffering - if (!largs->double_buffer) snrt_cluster_hw_barrier(); + if (!largs->double_buffer) snrt_global_barrier(); // Compute phase if (comp_i >= 0 && comp_i < num_tiles) { @@ -278,13 +439,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { // Only compute cores participate in the tile computation if (!snrt_is_dm_core()) { - uint32_t start_cycle = snrt_mcycle(); - - // In the first k iteration we accumulate with the C matrix - // scaled by beta, in successive iterations we accumulate - // the previous partial result. The tile-level beta is thus - // a function of k: beta(k). - uint32_t beta_k = comp_k_abs == 0 ? largs->beta : 1; + // uint32_t start_cycle = snrt_mcycle(); // Tile computation sc_st_gemm_args_t sc_st_args; @@ -311,7 +466,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { } else { sc_st_args.ldb = tile_n; } - sc_st_args.beta = beta_k; + sc_st_args.beta = comp_k_beta; sc_st_args.c = lc[c_buff_idx]; if (largs->partition_banks) { sc_st_args.ldc = calculate_partitioned_banks_stride( @@ -324,7 +479,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { sc_st_args.k = tile_k; sc_st_gemm(largs->gemm_fp, &sc_st_args); - uint32_t end_cycle = snrt_mcycle(); + // uint32_t end_cycle = snrt_mcycle(); } // Add the partial result tiles from the various clusters together @@ -340,6 +495,11 @@ static inline int gemm_picobello(const gemm_args_t *args) { snrt_global_barrier(); } + // Before completing the kernel, each cluster writes back its C tiles in the + // original memory tile. This is necessary only to run teh verify.py script + // if (snrt_is_dm_core()) { + // write_back_c_tiles(largs, tile_m, tile_n); + // } return 0; } From 2e7c293f262a6da3bd0000e719dd6e8920fe1d61 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Tue, 26 Aug 2025 09:13:54 +0200 Subject: [PATCH 21/35] sw: Optimize gemm2d for NoC architecture --- sw/snitch/apps/gemm_2d/data/params.json | 10 ++-- sw/snitch/apps/gemm_2d/roi.json | 10 +--- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 62 +++++++++++++------------ 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/sw/snitch/apps/gemm_2d/data/params.json b/sw/snitch/apps/gemm_2d/data/params.json index 5095ae3e..afc30631 100644 --- a/sw/snitch/apps/gemm_2d/data/params.json +++ b/sw/snitch/apps/gemm_2d/data/params.json @@ -6,18 +6,18 @@ setup_ssr: 1, parallelize_m: 1, parallelize_k: 0, - m_tiles: 2, // number of tiles in M dimension - n_tiles: 1, // number of tiles in N dimension + m_tiles: 16, // number of tiles in M dimension + n_tiles: 4, // number of tiles in N dimension k_tiles: 1, // number of tiles in K dimension load_a: 1, load_b: 1, load_c: 1, - double_buffer: 0, + double_buffer: 1, partition_banks: 0, transa: false, transb: false, // must be true for SIMD - m: 16, - n: 16, + m: 128, + n: 32, k: 16, alpha: 1, beta: 0, diff --git a/sw/snitch/apps/gemm_2d/roi.json b/sw/snitch/apps/gemm_2d/roi.json index a669996d..4249f886 100644 --- a/sw/snitch/apps/gemm_2d/roi.json +++ b/sw/snitch/apps/gemm_2d/roi.json @@ -1,7 +1,7 @@ [ <% N_TILES = 4 %> - % for cluster in range(0,15): + % for cluster in range(0,16): // Compute cores % for j in range(0, 8): { @@ -19,17 +19,11 @@ "thread": "${f'hart_{cluster * 9 + 8 + 1}'}", "roi": [ {"idx": 1, "label": "${f'tile_in_0'}"}, - // {"idx": 3, "label": "${f'tile_in_1'}"}, - // {"idx": 7, "label": "${f'tile_in_2'}"}, - // {"idx": 11, "label": "${f'tile_in_3'}"}, % for i in range(0, N_TILES - 1): {"idx": ${4*i + 3}, "label": "${f'tile_in_{i+1}'}"}, {"idx": ${4*i + 5}, "label": "${f'tile_out_{i}'}"}, % endfor - // {"idx": 5, "label": "${f'tile_out_0'}"}, - // {"idx": 9, "label": "${f'tile_out_1'}"}, - // {"idx": 13, "label": "${f'tile_out_2'}"}, - {"idx": 15, "label": "${f'tile_out_{N_TILES-1}'}"}, + {"idx": ${N_TILES * 4 - 1}, "label": "${f'tile_out_{N_TILES-1}'}"}, ] }, % endfor diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index f9e7f565..21b69d70 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -20,7 +20,7 @@ #include #include "blas.h" -#define HW_MCAST +// #define HW_MCAST #define L3_START_ADDRESS 0x70000000UL // Base address of memory tile 0 #define L3_SIZE 0x100000UL // Size of memory tile (1MiB) @@ -33,6 +33,7 @@ #include "data.h" #pragma clang diagnostic pop +// TODO (lleone): This functions are Picobello specific and might be moved inside a dedicated header /*------------------------- NoC Helper functions ---------------------------*/ static inline uintptr_t l3_tile_address(uint32_t tile_idx) { return (uintptr_t)L3_START_ADDRESS + @@ -77,26 +78,16 @@ static inline void allocate_l3_buffers(gemm_args_t *largs ) { static inline void write_back_c_tiles(gemm_args_t* largs, uint32_t m_tile_size, uint32_t n_tile_size) { uintptr_t c_src, c_dst; + uint32_t transfer_size; int c_m_abs, c_n_abs; - for (uint32_t i = 0; i < largs->n_tiles; i++) { - // Position of the first element in the Tile to be written back - c_src = (uintptr_t )largs->c + (snrt_cluster_idx() * largs->n * m_tile_size + i * n_tile_size) * largs->prec; - c_dst = l3_tile_address(0) + l3_tile_offset(c_src); + // Position of the first element in the Tile to be written back + c_src = (uintptr_t )largs->c + (snrt_cluster_idx() * largs->n * m_tile_size) * largs->prec; + c_dst = l3_tile_address(0) + l3_tile_offset( (uintptr_t) c_src); + transfer_size = m_tile_size * largs->n * largs->prec; - if (c_src != c_dst) { - c_m_abs = snrt_cluster_idx() * m_tile_size; - c_n_abs = i * n_tile_size; - - // Transfer the C tile into the destination - snrt_dma_store_2d_tile((void *) c_dst, (void *) c_src, - c_m_abs, c_n_abs, m_tile_size, - n_tile_size, largs->ldc, largs->prec); - - snrt_dma_wait_all(); - } - } + if (c_src != c_dst) snrt_dma_start_1d((void *) c_dst, (void *) c_src, transfer_size); } /** @@ -211,25 +202,22 @@ static inline int gemm_picobello(const gemm_args_t *args) { // - With a proper linker script, data could be placed directly in the correct // memory tiles without requiring extra DMA work from cluster 0. - // TODO (lleone): Improve copying only the necessary information and not the full data stack - if (snrt_is_dm_core()) - { - allocate_l3_buffers(largs); - snrt_dma_wait_all(); - } - snrt_global_barrier(); - // Distribute m and k tiles to clusters uint32_t cluster_m_tiles = largs->m_tiles; uint32_t cluster_k_tiles = largs->k_tiles; + uint32_t num_working_clusters = snrt_cluster_num(); if (largs->parallelize_m) { uint32_t m_tiles_quotient = cluster_m_tiles / snrt_cluster_num(); uint32_t m_tiles_remainder = cluster_m_tiles % snrt_cluster_num(); cluster_m_tiles = m_tiles_quotient; if (snrt_cluster_idx() < m_tiles_remainder) cluster_m_tiles++; + if (m_tiles_quotient == 0) num_working_clusters = m_tiles_remainder; } if (largs->parallelize_k) cluster_k_tiles /= snrt_cluster_num(); + snrt_comm_t comm; + snrt_comm_create(num_working_clusters, &comm); + // Calculate number of iterations uint32_t num_tiles = cluster_m_tiles * largs->n_tiles * cluster_k_tiles; uint32_t num_iters = num_tiles; @@ -238,6 +226,18 @@ static inline int gemm_picobello(const gemm_args_t *args) { else num_iters += 1; + // Place data in the correct memory tile pre-kernel. + // TODO (lleone): Improve copying only the necessary information and not the full data stack + + if (snrt_is_dm_core()) + { + allocate_l3_buffers(largs); + snrt_dma_wait_all(); + } + snrt_global_barrier(comm); + + + // Iterate over all tiles for (uint32_t i = 0; i < num_iters; i++) { // Calculate tile indices (we iterate in k->n->m order) @@ -429,7 +429,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { } // Additional barrier required when not double buffering - if (!largs->double_buffer) snrt_global_barrier(); + if (!largs->double_buffer) snrt_global_barrier(comm); // Compute phase if (comp_i >= 0 && comp_i < num_tiles) { @@ -492,14 +492,16 @@ static inline int gemm_picobello(const gemm_args_t *args) { } // Synchronize cores after every iteration - snrt_global_barrier(); + snrt_global_barrier(comm); } // Before completing the kernel, each cluster writes back its C tiles in the // original memory tile. This is necessary only to run teh verify.py script - // if (snrt_is_dm_core()) { - // write_back_c_tiles(largs, tile_m, tile_n); - // } + + if (snrt_is_dm_core() && snrt_cluster_idx() < num_working_clusters) { + write_back_c_tiles(largs, tile_m, tile_n); + } + return 0; } From 12bdd966eba652a217935eb4bc6c1e82ab3adc36 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Tue, 2 Sep 2025 19:13:58 +0200 Subject: [PATCH 22/35] sw: Initial Picobello NoC runtime --- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 26 +---------- sw/snitch/runtime/src/pb_noc_cfg.h | 11 +++++ sw/snitch/runtime/src/pb_team.c | 13 ++++++ sw/snitch/runtime/src/pb_team.h | 64 ++++++++++++++++++++++++++++ sw/snitch/runtime/src/snrt.h | 2 + 5 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 sw/snitch/runtime/src/pb_noc_cfg.h create mode 100644 sw/snitch/runtime/src/pb_team.c create mode 100644 sw/snitch/runtime/src/pb_team.h diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index 21b69d70..23ce4e5f 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -22,10 +22,6 @@ // #define HW_MCAST -#define L3_START_ADDRESS 0x70000000UL // Base address of memory tile 0 -#define L3_SIZE 0x100000UL // Size of memory tile (1MiB) -#define NUM_L3_TILES 8 // Number of memory tiles - // #define JOB_ARGS_PRELOADED #pragma clang diagnostic push @@ -33,26 +29,7 @@ #include "data.h" #pragma clang diagnostic pop -// TODO (lleone): This functions are Picobello specific and might be moved inside a dedicated header -/*------------------------- NoC Helper functions ---------------------------*/ -static inline uintptr_t l3_tile_address(uint32_t tile_idx) { - return (uintptr_t)L3_START_ADDRESS + - (uintptr_t)tile_idx * (uintptr_t)L3_SIZE; -} - -static inline uintptr_t l3_tile_offset(uintptr_t src_addr) { - return (src_addr - (uintptr_t)L3_START_ADDRESS) & (uintptr_t)(L3_SIZE - 1); -} - -static inline uint32_t cluster_row(uint32_t cid) { return cid % 4u; } - -static inline uint32_t dst_tile_for_cluster(uint32_t cid) { - uint32_t row = cluster_row(cid); - return (cid < 8u) ? row // first 8 clusters -> left column tiles 0..3 - : (row + 4u); // clusters >= 8 -> right column tiles 4..7 -} - -// Allocate data in L3 to betetr map kernel in NoC system +// Allocate data in L3 to better map kernel in NoC system static inline void allocate_l3_buffers(gemm_args_t *largs ) { uint32_t prec = largs->prec; @@ -228,7 +205,6 @@ static inline int gemm_picobello(const gemm_args_t *args) { // Place data in the correct memory tile pre-kernel. // TODO (lleone): Improve copying only the necessary information and not the full data stack - if (snrt_is_dm_core()) { allocate_l3_buffers(largs); diff --git a/sw/snitch/runtime/src/pb_noc_cfg.h b/sw/snitch/runtime/src/pb_noc_cfg.h new file mode 100644 index 00000000..e441ee53 --- /dev/null +++ b/sw/snitch/runtime/src/pb_noc_cfg.h @@ -0,0 +1,11 @@ +// Copyright 2025 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Lorenzo Leone + +#define L3_START_ADDRESS 0x70000000UL // Base address of memory tile 0 +#define L3_SIZE 0x100000UL // Size of memory tile (1MiB) + +#define CLUSTER_PER_ROW 4 +#define CLUSTER_PER_COL 4 diff --git a/sw/snitch/runtime/src/pb_team.c b/sw/snitch/runtime/src/pb_team.c new file mode 100644 index 00000000..eee41864 --- /dev/null +++ b/sw/snitch/runtime/src/pb_team.c @@ -0,0 +1,13 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +extern uintptr_t l3_tile_address(uint32_t tile_idx); + +extern inline uintptr_t l3_tile_offset(uintptr_t src_addr); + +extern inline uint32_t cluster_row(uint32_t cidx); + +extern inline uint32_t cluster_col(uint32_t cidx); + +extern inline uint32_t dst_tile_for_cluster(uint32_t cidx); diff --git a/sw/snitch/runtime/src/pb_team.h b/sw/snitch/runtime/src/pb_team.h new file mode 100644 index 00000000..880a7275 --- /dev/null +++ b/sw/snitch/runtime/src/pb_team.h @@ -0,0 +1,64 @@ +// Copyright 2025 ETH Zurich and University of Bologna. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Lorenzo Leone + + +/** + * @file + * @brief This file contains functions and macros related to Picobello team + * management. + * + * The functions in this file provide information about the Snitch hardware + * configuration, such as the number of clusters, cores per cluster, and the + * current core's index within the system. These functions can be used for team + * management and core-specific operations. + */ + +// TODO (lleone): Chekc if these functions can be difend as static + +/** + * @brief Get start address of a memory tile + * @param tile_idx The memory tile idx in the NoC + * @return Start addres of memory tile idx + */ +inline uintptr_t l3_tile_address(uint32_t tile_idx) { + return (uintptr_t)L3_START_ADDRESS + + (uintptr_t)tile_idx * (uintptr_t)L3_SIZE; +} + +/** + * @brief Get the address offset of a data respect to the memory tile start address + * @param src_addr The data absolute address + * @return Address location offset respect to the tile start address + */ +inline uintptr_t l3_tile_offset(uintptr_t src_addr) { + return (src_addr - (uintptr_t)L3_START_ADDRESS) & (uintptr_t)(L3_SIZE - 1); +} + +/** + * @brief Get the NoC row index + * @param cidx The cluster index + * @return The Row index + */ +inline uint32_t cluster_row(uint32_t cidx) +{ + return cidx % CLUSTER_PER_ROW; +} + +/** + * @brief Get the NoC column index + * @param cidx The cluster index + * @return The Column index + */ +inline uint32_t cluster_col(uint32_t cidx) +{ + return cidx % CLUSTER_PER_COL; +} + +inline uint32_t dst_tile_for_cluster(uint32_t cidx) { + uint32_t row = cluster_row(cidx); + return (cidx < 8u) ? row // first 8 clusters -> left column tiles 0..3 + : (row + 4u); // clusters >= 8 -> right column tiles 4..7 +} diff --git a/sw/snitch/runtime/src/snrt.h b/sw/snitch/runtime/src/snrt.h index 61602996..8959c80a 100644 --- a/sw/snitch/runtime/src/snrt.h +++ b/sw/snitch/runtime/src/snrt.h @@ -12,6 +12,7 @@ #include "snitch_cluster_cfg.h" #include "snitch_cluster_peripheral_addrmap.h" #include "pb_raw_addrmap.h" +#include "pb_noc_cfg.h" #define SNRT_TCDM_START_ADDR PICOBELLO_ADDRMAP_CLUSTER_0_TCDM_BASE_ADDR // TODO: the 40000 stride is hardcoded here, but it would better be @@ -51,6 +52,7 @@ typedef snitch_cluster__stride40000_t snitch_cluster_t; #include "sync.h" #include "team.h" #include "types.h" +#include "pb_team.h" // Accelerators #include "datamover/archi_datamover.h" From 39c03d38b8b186507e00cb9451bca912813376f7 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 4 Sep 2025 15:41:52 +0200 Subject: [PATCH 23/35] bender: Bump common_cells to remove false unstable data assertion errors --- Bender.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bender.lock b/Bender.lock index b2f05122..57b3add0 100644 --- a/Bender.lock +++ b/Bender.lock @@ -146,7 +146,7 @@ packages: dependencies: - common_cells common_cells: - revision: bef3d3c5ed0e2cc211e69a6dbd81c4fe3a97025c + revision: e1c09c75775c5f03eb45906d5145dbd2f5bcfb95 version: null source: Git: https://github.com/pulp-platform/common_cells.git From 7cc24e08c402f3ff16d0a71530af4d7d65ae4d04 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Thu, 4 Sep 2025 15:52:14 +0200 Subject: [PATCH 24/35] bender: Bump snitch_cluster to include sequencer bug fix --- Bender.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Bender.lock b/Bender.lock index 57b3add0..4fdb1dbf 100644 --- a/Bender.lock +++ b/Bender.lock @@ -383,10 +383,10 @@ packages: - common_cells - register_interface snitch_cluster: - revision: 37706b02959c22276bbd16ad8c934d8bdd4d416b + revision: fa0aaa974fb37a0da49d6bf6f4be165afccf0007 version: null source: - Git: https://github.com/Lore0599/snitch_cluster.git + Git: https://github.com/pulp-platform/snitch_cluster.git dependencies: - apb - axi From 2aa5ff5dd9272b62aff7da4e652facfff83fd038 Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Fri, 5 Sep 2025 10:29:20 +0200 Subject: [PATCH 25/35] sw: Align NoC API with systemRDL --- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 12 +++---- sw/snitch/runtime/src/pb_noc_cfg.h | 4 +-- sw/snitch/runtime/src/pb_team.h | 53 ++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index 23ce4e5f..c8e534ad 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -33,12 +33,12 @@ static inline void allocate_l3_buffers(gemm_args_t *largs ) { uint32_t prec = largs->prec; - uint32_t mem_tile_idx = dst_tile_for_cluster(snrt_cluster_idx()); + uint32_t mem_tile_idx = pb_closest_mem_tile(snrt_cluster_idx()); - uintptr_t a_off = l3_tile_offset((uintptr_t) largs -> a); - uintptr_t c_off = l3_tile_offset((uintptr_t) largs -> c); - uintptr_t a_dst = l3_tile_address(mem_tile_idx) + a_off; - uintptr_t c_dst = l3_tile_address(mem_tile_idx) + c_off; + uintptr_t a_off = pb_l3_tile_offset((uintptr_t) largs -> a); + uintptr_t c_off = pb_l3_tile_offset((uintptr_t) largs -> c); + uintptr_t a_dst = pb_l3_tile_address(mem_tile_idx) + a_off; + uintptr_t c_dst = pb_l3_tile_address(mem_tile_idx) + c_off; // Move data in the correct memory tile location uint32_t size_a = (size_t)largs->m * (size_t)largs->k; @@ -61,7 +61,7 @@ static inline void write_back_c_tiles(gemm_args_t* largs, uint32_t m_tile_size, // Position of the first element in the Tile to be written back c_src = (uintptr_t )largs->c + (snrt_cluster_idx() * largs->n * m_tile_size) * largs->prec; - c_dst = l3_tile_address(0) + l3_tile_offset( (uintptr_t) c_src); + c_dst = pb_l3_tile_address(0) + pb_l3_tile_offset( (uintptr_t) c_src); transfer_size = m_tile_size * largs->n * largs->prec; if (c_src != c_dst) snrt_dma_start_1d((void *) c_dst, (void *) c_src, transfer_size); diff --git a/sw/snitch/runtime/src/pb_noc_cfg.h b/sw/snitch/runtime/src/pb_noc_cfg.h index e441ee53..239b0747 100644 --- a/sw/snitch/runtime/src/pb_noc_cfg.h +++ b/sw/snitch/runtime/src/pb_noc_cfg.h @@ -4,8 +4,8 @@ // // Lorenzo Leone -#define L3_START_ADDRESS 0x70000000UL // Base address of memory tile 0 -#define L3_SIZE 0x100000UL // Size of memory tile (1MiB) +#define L3_START_ADDRESS PICOBELLO_ADDRMAP_L2_SPM_0_BASE_ADDR +#define L3_SIZE 0x00100000 #define CLUSTER_PER_ROW 4 #define CLUSTER_PER_COL 4 diff --git a/sw/snitch/runtime/src/pb_team.h b/sw/snitch/runtime/src/pb_team.h index 880a7275..591c38bd 100644 --- a/sw/snitch/runtime/src/pb_team.h +++ b/sw/snitch/runtime/src/pb_team.h @@ -16,16 +16,13 @@ * management and core-specific operations. */ -// TODO (lleone): Chekc if these functions can be difend as static - /** * @brief Get start address of a memory tile * @param tile_idx The memory tile idx in the NoC * @return Start addres of memory tile idx */ -inline uintptr_t l3_tile_address(uint32_t tile_idx) { - return (uintptr_t)L3_START_ADDRESS + - (uintptr_t)tile_idx * (uintptr_t)L3_SIZE; +inline uintptr_t pb_l3_tile_address(uint32_t tile_idx) { + return (uintptr_t) (picobello_addrmap.l2_spm[tile_idx].mem); } /** @@ -33,32 +30,68 @@ inline uintptr_t l3_tile_address(uint32_t tile_idx) { * @param src_addr The data absolute address * @return Address location offset respect to the tile start address */ -inline uintptr_t l3_tile_offset(uintptr_t src_addr) { +inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr) { return (src_addr - (uintptr_t)L3_START_ADDRESS) & (uintptr_t)(L3_SIZE - 1); } + /** * @brief Get the NoC row index * @param cidx The cluster index * @return The Row index */ -inline uint32_t cluster_row(uint32_t cidx) +inline uint32_t pb_cluster_row(uint32_t cidx) { return cidx % CLUSTER_PER_ROW; } +/** + * @brief Get the NoC row index + * This is a convenience orload of pb_cluster_row() + * @return The Row index + */ +inline uint32_t pb_cluster_row() +{ + return pb_cluster_row(snrt_cluster_idx()); +} + + /** * @brief Get the NoC column index * @param cidx The cluster index * @return The Column index */ -inline uint32_t cluster_col(uint32_t cidx) +inline uint32_t pb_cluster_col(uint32_t cidx) { return cidx % CLUSTER_PER_COL; } -inline uint32_t dst_tile_for_cluster(uint32_t cidx) { - uint32_t row = cluster_row(cidx); +/** + * @brief Get the NoC column index + * This is a convenience orload of pb_cluster_row() + * @return The Column index + */ +inline uint32_t pb_cluster_col() +{ + return pb_cluster_col(snrt_cluster_idx()); +} + + +/** + * @brief Get the index of the closest memory tile + * @param cidx The cluster index + * @return Index of the closest memory tile to cidx + */ +inline uint32_t pb_closest_mem_tile(uint32_t cidx) { + uint32_t row = pb_cluster_row(cidx); return (cidx < 8u) ? row // first 8 clusters -> left column tiles 0..3 : (row + 4u); // clusters >= 8 -> right column tiles 4..7 } + +/** + * @brief Get the index of the closest memory tile + * This is a convenience orload of pb_closest_mem_tile() + */ +inline uint32_t pb_closest_mem_tile() { + return pb_closest_mem_tile(snrt_cluster_idx()); +} From 62e288affbe81a7dccd33baac3315c54d87f2476 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Fri, 5 Sep 2025 11:41:54 +0200 Subject: [PATCH 26/35] requirements.txt: Bump `peakrdl-rawheader` --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4892f657..78565385 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,5 +25,5 @@ hjson # for reggen # For peakrdl peakrdl -peakrdl-rawheader @ git+https://github.com/micprog/peakrdl-rawheader.git +peakrdl-rawheader @ git+https://github.com/colluca/PeakRDL-rawheader.git@7b8dbc9ad5854dc1cdaf36d4ea024c29ffb00a4c peakrdl-markdown From 5fad5d889a261e5d9f85e55a7db702adc321c274 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Fri, 5 Sep 2025 13:53:23 +0200 Subject: [PATCH 27/35] Final runtime improvements --- sw/snitch/runtime/src/pb_noc_cfg.h | 7 ++----- sw/snitch/runtime/src/pb_team.c | 16 +++++++++++----- sw/snitch/runtime/src/pb_team.h | 19 +++++++++++-------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/sw/snitch/runtime/src/pb_noc_cfg.h b/sw/snitch/runtime/src/pb_noc_cfg.h index 239b0747..bc285ba6 100644 --- a/sw/snitch/runtime/src/pb_noc_cfg.h +++ b/sw/snitch/runtime/src/pb_noc_cfg.h @@ -4,8 +4,5 @@ // // Lorenzo Leone -#define L3_START_ADDRESS PICOBELLO_ADDRMAP_L2_SPM_0_BASE_ADDR -#define L3_SIZE 0x00100000 - -#define CLUSTER_PER_ROW 4 -#define CLUSTER_PER_COL 4 +#define PB_CLUSTER_PER_ROW 4 +#define PB_CLUSTER_PER_COL 4 diff --git a/sw/snitch/runtime/src/pb_team.c b/sw/snitch/runtime/src/pb_team.c index eee41864..be9aac02 100644 --- a/sw/snitch/runtime/src/pb_team.c +++ b/sw/snitch/runtime/src/pb_team.c @@ -2,12 +2,18 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -extern uintptr_t l3_tile_address(uint32_t tile_idx); +extern inline uintptr_t pb_l3_tile_address(uint32_t tile_idx); -extern inline uintptr_t l3_tile_offset(uintptr_t src_addr); +extern inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr); -extern inline uint32_t cluster_row(uint32_t cidx); +extern inline uint32_t pb_cluster_row(uint32_t cidx); -extern inline uint32_t cluster_col(uint32_t cidx); +extern inline uint32_t pb_cluster_row(); -extern inline uint32_t dst_tile_for_cluster(uint32_t cidx); +extern inline uint32_t pb_cluster_col(uint32_t cidx); + +extern inline uint32_t pb_cluster_col(); + +extern inline uint32_t pb_closest_mem_tile(uint32_t cidx); + +extern inline uint32_t pb_closest_mem_tile(); \ No newline at end of file diff --git a/sw/snitch/runtime/src/pb_team.h b/sw/snitch/runtime/src/pb_team.h index 591c38bd..adbcfa0f 100644 --- a/sw/snitch/runtime/src/pb_team.h +++ b/sw/snitch/runtime/src/pb_team.h @@ -31,7 +31,8 @@ inline uintptr_t pb_l3_tile_address(uint32_t tile_idx) { * @return Address location offset respect to the tile start address */ inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr) { - return (src_addr - (uintptr_t)L3_START_ADDRESS) & (uintptr_t)(L3_SIZE - 1); + return (src_addr - PICOBELLO_ADDRMAP_L2_SPM_0_BASE_ADDR) % + PICOBELLO_ADDRMAP_L2_SPM_0_SIZE; } @@ -42,7 +43,7 @@ inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr) { */ inline uint32_t pb_cluster_row(uint32_t cidx) { - return cidx % CLUSTER_PER_ROW; + return cidx % PB_CLUSTER_PER_ROW; } /** @@ -52,7 +53,7 @@ inline uint32_t pb_cluster_row(uint32_t cidx) */ inline uint32_t pb_cluster_row() { - return pb_cluster_row(snrt_cluster_idx()); + return pb_cluster_row(snrt_cluster_idx()); } @@ -63,7 +64,7 @@ inline uint32_t pb_cluster_row() */ inline uint32_t pb_cluster_col(uint32_t cidx) { - return cidx % CLUSTER_PER_COL; + return cidx / PB_CLUSTER_PER_COL; } /** @@ -73,7 +74,7 @@ inline uint32_t pb_cluster_col(uint32_t cidx) */ inline uint32_t pb_cluster_col() { - return pb_cluster_col(snrt_cluster_idx()); + return pb_cluster_col(snrt_cluster_idx()); } @@ -84,13 +85,15 @@ inline uint32_t pb_cluster_col() */ inline uint32_t pb_closest_mem_tile(uint32_t cidx) { uint32_t row = pb_cluster_row(cidx); - return (cidx < 8u) ? row // first 8 clusters -> left column tiles 0..3 - : (row + 4u); // clusters >= 8 -> right column tiles 4..7 + // e.g. with 4x4 matrix + // first 8 clusters -> left column tiles 0..3 + // clusters >= 8 -> right column tiles 4..7 + return (cidx < (snrt_cluster_num() / 2)) ? row : (row + PB_CLUSTER_PER_COL); } /** * @brief Get the index of the closest memory tile - * This is a convenience orload of pb_closest_mem_tile() + * This is a convenience overload of pb_closest_mem_tile() */ inline uint32_t pb_closest_mem_tile() { return pb_closest_mem_tile(snrt_cluster_idx()); From e4abf0a161c8725e41be900f0cc66ead2784877b Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Fri, 5 Sep 2025 15:36:00 +0200 Subject: [PATCH 28/35] Fix `slink_32b_elf_preload` --- target/sim/src/tb_picobello_top.sv | 111 ++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/target/sim/src/tb_picobello_top.sv b/target/sim/src/tb_picobello_top.sv index dcf8920b..fc5832cb 100644 --- a/target/sim/src/tb_picobello_top.sv +++ b/target/sim/src/tb_picobello_top.sv @@ -62,56 +62,97 @@ module tb_picobello_top; $display("[JTAG] Preload complete"); endtask + // Handles misalignments, burst limits and 4KiB crossings + task automatic slink_write_generic(input addr_t addr, input longint size, ref byte bytes[]); + // Using `slink_write_beats`, writes must be beat-aligned and beat-sized (strobing is not + // possible). If we have a misaligned transfer of arbitrary size we may have at most two + // incomplete beats (start and end) and one misaligned beat (start). In case of an incomplete + // beat we read-modify-write the full beat. + + // Burst and beat geometry + const int beat_bytes = fix.vip.AxiStrbWidth; + const int beat_mask = beat_bytes - 1; + const int SlinkBurstBeats = fix.vip.SlinkBurstBytes / beat_bytes; + + // Iterate beat-by-beat over the address range [addr, addr+size) + addr_t first_aligned = addr_t'(addr) & ~addr_t'(beat_mask); + addr_t end_addr = addr_t'(addr + size); + addr_t last_aligned = addr_t'((end_addr - 1) & ~addr_t'(beat_mask)); + + // Running index into bytes[]: "how many bytes have we already consumed?" + longint base_idx = 0; + + // Group beats in a burst + addr_t batch_addr = first_aligned; + axi_data_t burst [$]; + burst = {}; + + for (addr_t beat_addr = first_aligned; beat_addr <= last_aligned; beat_addr += beat_bytes) begin + addr_t next_addr; + bit crosses_4k_next, exceeds_burst_length, last_beat_in_section; + + // Window of the current beat that has to be written + int start_off = (beat_addr == first_aligned) ? int'(addr & beat_mask) : 0; + int end_off_excl = (beat_addr == last_aligned) ? int'(end_addr - last_aligned) : beat_bytes; + int win_len = end_off_excl - start_off; + + // Compose beat + axi_data_t beat = '0; + if (win_len == beat_bytes && start_off == 0) begin + // FULL BEAT: write directly, no RMW + for (int e = 0; e < beat_bytes; e++) begin + beat[8*e+:8] = bytes[base_idx+e]; + end + end else begin + // PARTIAL BEAT: RMW + axi_data_t rd[$]; + fix.vip.slink_read_beats(beat_addr, fix.vip.AxiStrbBits, 0, rd); + beat = rd[0]; + for (int i = 0; i < win_len; i++) begin + beat[8*(start_off+i)+:8] = bytes[base_idx+i]; + end + end + + // Accumulate and advance + burst.push_back(beat); + base_idx += win_len; + + // Decide if the next beat would cross a 4 KiB boundary, exceed the maximum burst length + // or this is the last beat + next_addr = beat_addr + win_len; + crosses_4k_next = ((next_addr & 12'hFFF) == 12'h000); // next beat starts a new page + exceeds_burst_length = (burst.size() == SlinkBurstBeats); + last_beat_in_section = (beat_addr == last_aligned); + + if (crosses_4k_next || exceeds_burst_length || last_beat_in_section) begin + // Flush accumulated beats for this page + fix.vip.slink_write_beats(batch_addr, fix.vip.AxiStrbBits, burst); + burst = {}; + batch_addr = next_addr; + end + end + endtask + task automatic slink_32b_elf_preload(input string binary, output bit [63:0] entry); longint sec_addr, sec_len; + $display("[SLINK] Preloading ELF binary: %s", binary); if (fix.vip.read_elf(binary)) $fatal(1, "[SLINK] Failed to load ELF!"); + while (fix.vip.get_section( sec_addr, sec_len )) begin - byte bf [] = new[sec_len]; - int burst_len; + byte bf[] = new[sec_len]; $display("[SLINK] Preloading section at 0x%h (%0d bytes)", sec_addr, sec_len); if (fix.vip.read_section(sec_addr, bf, sec_len)) $fatal(1, "[SLINK] Failed to read ELF section!"); - // Write section in bursts <= SlinkBurstBytes that never cross a 4 KiB page - for (longint sec_offs = 0; sec_offs < sec_len; sec_offs += burst_len) begin - longint sec_left, page_left; - axi_data_t beats [$]; - int bus_offs; - addr_t addr_cur = sec_addr + sec_offs; - if (sec_offs != 0) begin - $display("[SLINK] - %0d/%0d bytes (%0d%%)", sec_offs, sec_len, - sec_offs * 100 / (sec_len > 1 ? sec_len - 1 : 1)); - end - // By default the burst length is SlinkBurstBytes - burst_len = fix.vip.SlinkBurstBytes; - // Cut the burst length if it exceeds the remaining section length - // or it crosses a 4 KiB page boundary - sec_left = sec_len - sec_offs; - page_left = 4096 - (addr_cur & 12'hFFF); - if (burst_len > sec_left) burst_len = int'(sec_left); - if (burst_len > page_left) burst_len = int'(page_left); - bus_offs = addr_cur[fix.vip.AxiStrbBits-1:0]; - - // If the address is not aligned subtract the offset from the burst length to avoid an additional write - burst_len = burst_len - bus_offs; - // Assemble beats, handling unaligned start in the first beat - for (int b = -bus_offs; b < burst_len; b += fix.vip.AxiStrbWidth) begin - axi_data_t beat = '0; - for (int e = 0; e < fix.vip.AxiStrbWidth; ++e) - if (b + e >= 0 && b + e < burst_len) beat[8*e+:8] = bf[sec_offs+b+e]; - beats.push_back(beat); - end - // Address must be beat‑aligned for slink_write_beats - fix.vip.slink_write_beats(addr_cur - bus_offs, fix.vip.AxiStrbBits, beats); - end + slink_write_generic(sec_addr, sec_len, bf); end + void'(fix.vip.get_entry(entry)); $display("[SLINK] Preload complete"); endtask - initial begin // Fetch plusargs or use safe (fail-fast) defaults if (!$value$plusargs("BOOTMODE=%d", boot_mode)) boot_mode = 0; From 0a87358839b3301ee3f996d69569b11d258f970c Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 8 Sep 2025 09:07:07 +0200 Subject: [PATCH 29/35] Bump pd in CI --- .gitlab/common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/common.yml b/.gitlab/common.yml index 4e6dd5ed..911226a5 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -4,7 +4,7 @@ variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - PD_COMMIT: "c22baf1fcce6b0cf76b85d2009e146ba3bbb6807" + PD_COMMIT: "45d076deae8f0d498e2c4e3a8b9a92f809068a9b" SPU_COMMIT: "c2e8815487bd713624d74ef3e3e0465196b6d67f" # Check the cache for bender and python dependencies From cee0d5c3ce7172807f5ad1612369b9b216d7f15e Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 8 Sep 2025 18:03:13 +0200 Subject: [PATCH 30/35] Exclude non-synthesizable code on synthesis --- hw/mem_tile.sv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/mem_tile.sv b/hw/mem_tile.sv index 371224f6..faf4249a 100644 --- a/hw/mem_tile.sv +++ b/hw/mem_tile.sv @@ -276,6 +276,7 @@ module mem_tile end end +`ifndef SYNTHESIS // AXI Monitor dumper to improvce debiugging axi_dumper #( .BusName ($sformatf("mem_tile_%d", MemTileId)), @@ -292,6 +293,7 @@ module mem_tile .axi_req_i (axi_req), .axi_resp_i(axi_rsp) ); +`endif axi_to_obi #( .ObiCfg (MgrObiCfg), From 5fdea71fcc02d85451a35d44b8bdda9e0a6fd2c8 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Tue, 9 Sep 2025 11:17:27 +0200 Subject: [PATCH 31/35] Move new tb tasks to tb_picobello_tasks --- target/sim/include/tb_picobello_tasks.svh | 128 ++++++++++++++++++++++ target/sim/src/tb_picobello_top.sv | 128 ---------------------- 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/target/sim/include/tb_picobello_tasks.svh b/target/sim/include/tb_picobello_tasks.svh index 40db1f36..9158a327 100644 --- a/target/sim/include/tb_picobello_tasks.svh +++ b/target/sim/include/tb_picobello_tasks.svh @@ -12,6 +12,9 @@ import "DPI-C" context function byte read_section(input longint address, inout b import picobello_pkg::*; `include "pb_addrmap.svh" +`include "cheshire/typedef.svh" + +`CHESHIRE_TYPEDEF_ALL(, fix.vip.DutCfg) task automatic jtag_enable_tiles(); $display("Resetting tiles and enabling clock..."); @@ -151,3 +154,128 @@ task automatic fastmode_elf_preload(input string binary, output cheshire_pkg::do void'(get_entry(entry)); $display("[FAST_PRELOAD] Preload complete"); endtask + +// Suitable for loading ELFs with 32b-aligned sections +task automatic jtag_32b_elf_preload(input string binary, output bit [63:0] entry); + longint sec_addr, sec_len; + dm::sbcs_t sbcs = dm::sbcs_t +'{sbautoincrement: 1'b1, sbreadondata: 1'b1, sbaccess: 2, default: '0}; + $display("[JTAG] Preloading ELF binary: %s", binary); + if (fix.vip.read_elf(binary)) $fatal(1, "[JTAG] Failed to load ELF!"); + while (fix.vip.get_section( + sec_addr, sec_len + )) begin + byte bf[] = new[sec_len]; + $display("[JTAG] Preloading section at 0x%h (%0d bytes)", sec_addr, sec_len); + if (fix.vip.read_section(sec_addr, bf, sec_len)) + $fatal(1, "[JTAG] Failed to read ELF section!"); + fix.vip.jtag_write(dm::SBCS, sbcs, 1, 1); + // Write address as 64-bit double + fix.vip.jtag_write(dm::SBAddress1, sec_addr[63:32]); + fix.vip.jtag_write(dm::SBAddress0, sec_addr[31:0]); + for (longint i = 0; i <= sec_len; i += 4) begin + bit checkpoint = (i != 0 && i % 512 == 0); + if (checkpoint) + $display( + "[JTAG] - %0d/%0d bytes (%0d%%)", + i, + sec_len, + i * 100 / (sec_len > 1 ? sec_len - 1 : 1) + ); + fix.vip.jtag_write(dm::SBData0, {bf[i+3], bf[i+2], bf[i+1], bf[i]}, checkpoint, checkpoint); + end + end + void'(get_entry(entry)); + $display("[JTAG] Preload complete"); +endtask + +// Handles misalignments, burst limits and 4KiB crossings +task automatic slink_write_generic(input addr_t addr, input longint size, ref byte bytes[]); + // Using `slink_write_beats`, writes must be beat-aligned and beat-sized (strobing is not + // possible). If we have a misaligned transfer of arbitrary size we may have at most two + // incomplete beats (start and end) and one misaligned beat (start). In case of an incomplete + // beat we read-modify-write the full beat. + + // Burst and beat geometry + const int beat_bytes = fix.vip.AxiStrbWidth; + const int beat_mask = beat_bytes - 1; + const int SlinkBurstBeats = fix.vip.SlinkBurstBytes / beat_bytes; + + // Iterate beat-by-beat over the address range [addr, addr+size) + addr_t first_aligned = addr_t'(addr) & ~addr_t'(beat_mask); + addr_t end_addr = addr_t'(addr + size); + addr_t last_aligned = addr_t'((end_addr - 1) & ~addr_t'(beat_mask)); + + // Running index into bytes[]: "how many bytes have we already consumed?" + longint base_idx = 0; + + // Group beats in a burst + addr_t batch_addr = first_aligned; + axi_data_t burst [$]; + burst = {}; + + for (addr_t beat_addr = first_aligned; beat_addr <= last_aligned; beat_addr += beat_bytes) begin + addr_t next_addr; + bit crosses_4k_next, exceeds_burst_length, last_beat_in_section; + + // Window of the current beat that has to be written + int start_off = (beat_addr == first_aligned) ? int'(addr & beat_mask) : 0; + int end_off_excl = (beat_addr == last_aligned) ? int'(end_addr - last_aligned) : beat_bytes; + int win_len = end_off_excl - start_off; + + // Compose beat + axi_data_t beat = '0; + if (win_len == beat_bytes && start_off == 0) begin + // FULL BEAT: write directly, no RMW + for (int e = 0; e < beat_bytes; e++) begin + beat[8*e+:8] = bytes[base_idx+e]; + end + end else begin + // PARTIAL BEAT: RMW + axi_data_t rd[$]; + fix.vip.slink_read_beats(beat_addr, fix.vip.AxiStrbBits, 0, rd); + beat = rd[0]; + for (int i = 0; i < win_len; i++) begin + beat[8*(start_off+i)+:8] = bytes[base_idx+i]; + end + end + + // Accumulate and advance + burst.push_back(beat); + base_idx += win_len; + + // Decide if the next beat would cross a 4 KiB boundary, exceed the maximum burst length + // or this is the last beat + next_addr = beat_addr + win_len; + crosses_4k_next = ((next_addr & 12'hFFF) == 12'h000); // next beat starts a new page + exceeds_burst_length = (burst.size() == SlinkBurstBeats); + last_beat_in_section = (beat_addr == last_aligned); + + if (crosses_4k_next || exceeds_burst_length || last_beat_in_section) begin + // Flush accumulated beats for this page + fix.vip.slink_write_beats(batch_addr, fix.vip.AxiStrbBits, burst); + burst = {}; + batch_addr = next_addr; + end + end +endtask + +task automatic slink_32b_elf_preload(input string binary, output bit [63:0] entry); + longint sec_addr, sec_len; + + $display("[SLINK] Preloading ELF binary: %s", binary); + if (fix.vip.read_elf(binary)) $fatal(1, "[SLINK] Failed to load ELF!"); + + while (fix.vip.get_section( + sec_addr, sec_len + )) begin + byte bf[] = new[sec_len]; + $display("[SLINK] Preloading section at 0x%h (%0d bytes)", sec_addr, sec_len); + if (fix.vip.read_section(sec_addr, bf, sec_len)) + $fatal(1, "[SLINK] Failed to read ELF section!"); + slink_write_generic(sec_addr, sec_len, bf); + end + + void'(fix.vip.get_entry(entry)); + $display("[SLINK] Preload complete"); +endtask diff --git a/target/sim/src/tb_picobello_top.sv b/target/sim/src/tb_picobello_top.sv index fc5832cb..673cbe88 100644 --- a/target/sim/src/tb_picobello_top.sv +++ b/target/sim/src/tb_picobello_top.sv @@ -10,9 +10,6 @@ module tb_picobello_top; gen_sram_banks[j].gen_sram_macros[k].i_mem.sram `include "tb_picobello_tasks.svh" - `include "cheshire/typedef.svh" - - `CHESHIRE_TYPEDEF_ALL(, fix.vip.DutCfg) // Instantiate the fixture fixture_picobello_top fix (); @@ -28,131 +25,6 @@ module tb_picobello_top; int snitch_fn; int chs_fn; - // Load Snitch binary - task automatic jtag_32b_elf_preload(input string binary, output bit [63:0] entry); - longint sec_addr, sec_len; - dm::sbcs_t sbcs = dm::sbcs_t -'{sbautoincrement: 1'b1, sbreadondata: 1'b1, sbaccess: 2, default: '0}; - $display("[JTAG] Preloading ELF binary: %s", binary); - if (fix.vip.read_elf(binary)) $fatal(1, "[JTAG] Failed to load ELF!"); - while (fix.vip.get_section( - sec_addr, sec_len - )) begin - byte bf[] = new[sec_len]; - $display("[JTAG] Preloading section at 0x%h (%0d bytes)", sec_addr, sec_len); - if (fix.vip.read_section(sec_addr, bf, sec_len)) - $fatal(1, "[JTAG] Failed to read ELF section!"); - fix.vip.jtag_write(dm::SBCS, sbcs, 1, 1); - // Write address as 64-bit double - fix.vip.jtag_write(dm::SBAddress1, sec_addr[63:32]); - fix.vip.jtag_write(dm::SBAddress0, sec_addr[31:0]); - for (longint i = 0; i <= sec_len; i += 4) begin - bit checkpoint = (i != 0 && i % 512 == 0); - if (checkpoint) - $display( - "[JTAG] - %0d/%0d bytes (%0d%%)", - i, - sec_len, - i * 100 / (sec_len > 1 ? sec_len - 1 : 1) - ); - fix.vip.jtag_write(dm::SBData0, {bf[i+3], bf[i+2], bf[i+1], bf[i]}, checkpoint, checkpoint); - end - end - void'(get_entry(entry)); - $display("[JTAG] Preload complete"); - endtask - - // Handles misalignments, burst limits and 4KiB crossings - task automatic slink_write_generic(input addr_t addr, input longint size, ref byte bytes[]); - // Using `slink_write_beats`, writes must be beat-aligned and beat-sized (strobing is not - // possible). If we have a misaligned transfer of arbitrary size we may have at most two - // incomplete beats (start and end) and one misaligned beat (start). In case of an incomplete - // beat we read-modify-write the full beat. - - // Burst and beat geometry - const int beat_bytes = fix.vip.AxiStrbWidth; - const int beat_mask = beat_bytes - 1; - const int SlinkBurstBeats = fix.vip.SlinkBurstBytes / beat_bytes; - - // Iterate beat-by-beat over the address range [addr, addr+size) - addr_t first_aligned = addr_t'(addr) & ~addr_t'(beat_mask); - addr_t end_addr = addr_t'(addr + size); - addr_t last_aligned = addr_t'((end_addr - 1) & ~addr_t'(beat_mask)); - - // Running index into bytes[]: "how many bytes have we already consumed?" - longint base_idx = 0; - - // Group beats in a burst - addr_t batch_addr = first_aligned; - axi_data_t burst [$]; - burst = {}; - - for (addr_t beat_addr = first_aligned; beat_addr <= last_aligned; beat_addr += beat_bytes) begin - addr_t next_addr; - bit crosses_4k_next, exceeds_burst_length, last_beat_in_section; - - // Window of the current beat that has to be written - int start_off = (beat_addr == first_aligned) ? int'(addr & beat_mask) : 0; - int end_off_excl = (beat_addr == last_aligned) ? int'(end_addr - last_aligned) : beat_bytes; - int win_len = end_off_excl - start_off; - - // Compose beat - axi_data_t beat = '0; - if (win_len == beat_bytes && start_off == 0) begin - // FULL BEAT: write directly, no RMW - for (int e = 0; e < beat_bytes; e++) begin - beat[8*e+:8] = bytes[base_idx+e]; - end - end else begin - // PARTIAL BEAT: RMW - axi_data_t rd[$]; - fix.vip.slink_read_beats(beat_addr, fix.vip.AxiStrbBits, 0, rd); - beat = rd[0]; - for (int i = 0; i < win_len; i++) begin - beat[8*(start_off+i)+:8] = bytes[base_idx+i]; - end - end - - // Accumulate and advance - burst.push_back(beat); - base_idx += win_len; - - // Decide if the next beat would cross a 4 KiB boundary, exceed the maximum burst length - // or this is the last beat - next_addr = beat_addr + win_len; - crosses_4k_next = ((next_addr & 12'hFFF) == 12'h000); // next beat starts a new page - exceeds_burst_length = (burst.size() == SlinkBurstBeats); - last_beat_in_section = (beat_addr == last_aligned); - - if (crosses_4k_next || exceeds_burst_length || last_beat_in_section) begin - // Flush accumulated beats for this page - fix.vip.slink_write_beats(batch_addr, fix.vip.AxiStrbBits, burst); - burst = {}; - batch_addr = next_addr; - end - end - endtask - - task automatic slink_32b_elf_preload(input string binary, output bit [63:0] entry); - longint sec_addr, sec_len; - - $display("[SLINK] Preloading ELF binary: %s", binary); - if (fix.vip.read_elf(binary)) $fatal(1, "[SLINK] Failed to load ELF!"); - - while (fix.vip.get_section( - sec_addr, sec_len - )) begin - byte bf[] = new[sec_len]; - $display("[SLINK] Preloading section at 0x%h (%0d bytes)", sec_addr, sec_len); - if (fix.vip.read_section(sec_addr, bf, sec_len)) - $fatal(1, "[SLINK] Failed to read ELF section!"); - slink_write_generic(sec_addr, sec_len, bf); - end - - void'(fix.vip.get_entry(entry)); - $display("[SLINK] Preload complete"); - endtask - initial begin // Fetch plusargs or use safe (fail-fast) defaults if (!$value$plusargs("BOOTMODE=%d", boot_mode)) boot_mode = 0; From 55aa6b799dbdcdc2145d24a42c8a1109de0e5214 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Tue, 9 Sep 2025 11:26:34 +0200 Subject: [PATCH 32/35] Use new tb tasks in pd tb --- .gitlab/common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/common.yml b/.gitlab/common.yml index 911226a5..ef69a99a 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -4,7 +4,7 @@ variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - PD_COMMIT: "45d076deae8f0d498e2c4e3a8b9a92f809068a9b" + PD_COMMIT: "6b315613e2e7aeaa4d9c35e8c0fcd8fb8648ed53" SPU_COMMIT: "c2e8815487bd713624d74ef3e3e0465196b6d67f" # Check the cache for bender and python dependencies From b7d34726ab6cba0996f347328b3d04bb2434aec0 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Tue, 9 Sep 2025 17:42:38 +0200 Subject: [PATCH 33/35] bender: Bump snitch_cluster (no hw changes) --- Bender.lock | 2 +- Bender.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Bender.lock b/Bender.lock index 4fdb1dbf..02dd028e 100644 --- a/Bender.lock +++ b/Bender.lock @@ -383,7 +383,7 @@ packages: - common_cells - register_interface snitch_cluster: - revision: fa0aaa974fb37a0da49d6bf6f4be165afccf0007 + revision: ef3ece6c9e119fbfc25b26bb89a429ccdaacb5c6 version: null source: Git: https://github.com/pulp-platform/snitch_cluster.git diff --git a/Bender.yml b/Bender.yml index b38346cc..b342a501 100644 --- a/Bender.yml +++ b/Bender.yml @@ -12,7 +12,7 @@ dependencies: axi: { git: "https://github.com/pulp-platform/axi.git", version: "0.39.6" } common_cells: { git: "https://github.com/pulp-platform/common_cells.git", rev: "snitch" } cheshire: { git: "https://github.com/pulp-platform/cheshire.git", rev: "picobello" } - snitch_cluster: { git: "https://github.com/pulp-platform/snitch_cluster.git", rev: "37706b02959c22276bbd16ad8c934d8bdd4d416b" } + snitch_cluster: { git: "https://github.com/pulp-platform/snitch_cluster.git", rev: "ef3ece6c9e119fbfc25b26bb89a429ccdaacb5c6" } floo_noc: { git: "https://github.com/pulp-platform/FlooNoC.git", rev: "develop" } obi: { git: "https://github.com/pulp-platform/obi.git", rev: "acfcd0f80c7539aa8da7821a66d9acf2074a5b4e" } redmule: { git: "https://github.com/pulp-platform/redmule.git", rev: "picobello" } From 6485ded33202fb814221f627f5353bc1c85c770f Mon Sep 17 00:00:00 2001 From: Lorenzo Leone Date: Mon, 15 Sep 2025 11:32:28 +0200 Subject: [PATCH 34/35] Improve picobello runtime fumnctions naming --- sw/snitch/apps/gemm_2d/src/gemm_2d.c | 16 +-- sw/snitch/runtime/src/pb_team.c | 6 +- sw/snitch/runtime/src/pb_team.h | 9 +- util/axi_analyzer.py | 188 --------------------------- 4 files changed, 13 insertions(+), 206 deletions(-) delete mode 100644 util/axi_analyzer.py diff --git a/sw/snitch/apps/gemm_2d/src/gemm_2d.c b/sw/snitch/apps/gemm_2d/src/gemm_2d.c index c8e534ad..57dc4797 100644 --- a/sw/snitch/apps/gemm_2d/src/gemm_2d.c +++ b/sw/snitch/apps/gemm_2d/src/gemm_2d.c @@ -29,16 +29,16 @@ #include "data.h" #pragma clang diagnostic pop -// Allocate data in L3 to better map kernel in NoC system -static inline void allocate_l3_buffers(gemm_args_t *largs ) { +// Allocate data in L2 to better map kernel in NoC system +static inline void allocate_l2_buffers(gemm_args_t *largs ) { uint32_t prec = largs->prec; uint32_t mem_tile_idx = pb_closest_mem_tile(snrt_cluster_idx()); - uintptr_t a_off = pb_l3_tile_offset((uintptr_t) largs -> a); - uintptr_t c_off = pb_l3_tile_offset((uintptr_t) largs -> c); - uintptr_t a_dst = pb_l3_tile_address(mem_tile_idx) + a_off; - uintptr_t c_dst = pb_l3_tile_address(mem_tile_idx) + c_off; + uintptr_t a_off = pb_l2_tile_offset((uintptr_t) largs -> a); + uintptr_t c_off = pb_l2_tile_offset((uintptr_t) largs -> c); + uintptr_t a_dst = pb_l2_tile_address(mem_tile_idx) + a_off; + uintptr_t c_dst = pb_l2_tile_address(mem_tile_idx) + c_off; // Move data in the correct memory tile location uint32_t size_a = (size_t)largs->m * (size_t)largs->k; @@ -61,7 +61,7 @@ static inline void write_back_c_tiles(gemm_args_t* largs, uint32_t m_tile_size, // Position of the first element in the Tile to be written back c_src = (uintptr_t )largs->c + (snrt_cluster_idx() * largs->n * m_tile_size) * largs->prec; - c_dst = pb_l3_tile_address(0) + pb_l3_tile_offset( (uintptr_t) c_src); + c_dst = pb_l2_tile_address(0) + pb_l2_tile_offset( (uintptr_t) c_src); transfer_size = m_tile_size * largs->n * largs->prec; if (c_src != c_dst) snrt_dma_start_1d((void *) c_dst, (void *) c_src, transfer_size); @@ -207,7 +207,7 @@ static inline int gemm_picobello(const gemm_args_t *args) { // TODO (lleone): Improve copying only the necessary information and not the full data stack if (snrt_is_dm_core()) { - allocate_l3_buffers(largs); + allocate_l2_buffers(largs); snrt_dma_wait_all(); } snrt_global_barrier(comm); diff --git a/sw/snitch/runtime/src/pb_team.c b/sw/snitch/runtime/src/pb_team.c index be9aac02..cd280f61 100644 --- a/sw/snitch/runtime/src/pb_team.c +++ b/sw/snitch/runtime/src/pb_team.c @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -extern inline uintptr_t pb_l3_tile_address(uint32_t tile_idx); +extern inline uintptr_t pb_l2_tile_address(uint32_t tile_idx); -extern inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr); +extern inline uintptr_t pb_l2_tile_offset(uintptr_t src_addr); extern inline uint32_t pb_cluster_row(uint32_t cidx); @@ -16,4 +16,4 @@ extern inline uint32_t pb_cluster_col(); extern inline uint32_t pb_closest_mem_tile(uint32_t cidx); -extern inline uint32_t pb_closest_mem_tile(); \ No newline at end of file +extern inline uint32_t pb_closest_mem_tile(); diff --git a/sw/snitch/runtime/src/pb_team.h b/sw/snitch/runtime/src/pb_team.h index adbcfa0f..1fa8d8d0 100644 --- a/sw/snitch/runtime/src/pb_team.h +++ b/sw/snitch/runtime/src/pb_team.h @@ -9,11 +9,6 @@ * @file * @brief This file contains functions and macros related to Picobello team * management. - * - * The functions in this file provide information about the Snitch hardware - * configuration, such as the number of clusters, cores per cluster, and the - * current core's index within the system. These functions can be used for team - * management and core-specific operations. */ /** @@ -21,7 +16,7 @@ * @param tile_idx The memory tile idx in the NoC * @return Start addres of memory tile idx */ -inline uintptr_t pb_l3_tile_address(uint32_t tile_idx) { +inline uintptr_t pb_l2_tile_address(uint32_t tile_idx) { return (uintptr_t) (picobello_addrmap.l2_spm[tile_idx].mem); } @@ -30,7 +25,7 @@ inline uintptr_t pb_l3_tile_address(uint32_t tile_idx) { * @param src_addr The data absolute address * @return Address location offset respect to the tile start address */ -inline uintptr_t pb_l3_tile_offset(uintptr_t src_addr) { +inline uintptr_t pb_l2_tile_offset(uintptr_t src_addr) { return (src_addr - PICOBELLO_ADDRMAP_L2_SPM_0_BASE_ADDR) % PICOBELLO_ADDRMAP_L2_SPM_0_SIZE; } diff --git a/util/axi_analyzer.py b/util/axi_analyzer.py deleted file mode 100644 index 7d6754b6..00000000 --- a/util/axi_analyzer.py +++ /dev/null @@ -1,188 +0,0 @@ -# Copyright 2025 ETH Zurich and University of Bologna. -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 -# -# Lorenzo Leone - -import pandas as pd -import ast - -pd.options.display.float_format = '{:.1f}'.format - -INT_FIELDS = {'atop', 'burst', 'cache', 'id', 'len', 'size', 'user'} - -###################### -# PARSING LOG # -###################### - -# Function to parse the log file from AXI DUMPER -def parse_axi_dump(file_path): - """ - Parses the AXI transaction log file into a pandas DataFrame. - All values (even hex) are converted and stored as integers to simplify processing. - - Parameters: - file_path (str): Path to the AXI dump text file. - - Returns: - pd.DataFrame: A DataFrame where each row is a transaction dictionary entry. - """ - parsed_entries = [] - - with open(file_path, 'r') as file: - for line_num, line in enumerate(file, start=1): - line = line.strip().rstrip(',') - if not line: - continue - - try: - # Safely parse the line as a Python dictionary - entry = ast.literal_eval(line) - - # Convert all values to integers if possible - intified_entry = {} - for k, v in entry.items(): - if isinstance(v, int): - intified_entry[k] = v - elif isinstance(v, str): - intified_entry[k] = v # keep strings like 'type' - else: - intified_entry[k] = int(v) # cast floats or hex-compatible - - parsed_entries.append(intified_entry) - - except Exception as e: - print(f"[Line {line_num}] Failed to parse line: {line}") - print(f" Error: {e}") - - df = pd.DataFrame(parsed_entries) - # Set the time as first column in the dataframe - if 'time' in df.columns: - cols = ['time'] + [col for col in df.columns if col != 'time'] - df = df[cols] - - return df - - -# Function to annotate write addresses in the DataFrame -def resolve_write_addresses(df): - """ - Resolves and fills in the missing 'addr' field in each W (write data) entry - based on the most recent AW (write address) entry before it. - - Modifies the DataFrame in place and returns it. - - Parameters: - df (pd.DataFrame): Parsed AXI transactions - - Returns: - pd.DataFrame: Modified DataFrame with filled 'addr' fields for W entries - """ - - current_aw = None - w_counter = 0 # tracks how many W entries have occurred since last AW - - df = df.copy() # Optional: avoid mutating original DF - - for idx, row in df.iterrows(): - if row.get('type') == 'AW': - current_aw = { - 'addr': row.get('addr'), - 'size': row.get('size'), - } - w_counter = 0 # reset on new AW - print(current_aw) - - elif row.get('type') == 'W': - if current_aw is not None: - bytes_per_transfer = 1 << int(current_aw['size']) - resolved_addr = current_aw['addr'] + w_counter * bytes_per_transfer - df.at[idx, 'addr'] = resolved_addr - w_counter += 1 - - if row.get('last') == 1: - current_aw = None - w_counter = 0 - else: - raise RuntimeError(f"[Line {idx}] Found W entry without preceding AW.") - - return df - - -def format_axi_df_for_display(df): - """ - Returns a new DataFrame with selected fields formatted as strings. - This allows clean display, printing, or export to CSV. - - Only modifies formatting of specific fields; all others are untouched. - """ - df_formatted = df.copy() - - def format_hex(x): - return f"0x{int(x):x}" if pd.notnull(x) else "None" - - def format_int(x): - return str(int(x)) if pd.notnull(x) else "None" - - def format_bool(x): - return str(bool(x)) if pd.notnull(x) else "None" - - # Define format rules to apply to each df column - format_rules = { - 'addr': format_hex, - 'atop': format_hex, - 'burst': format_hex, - 'id': format_int, - 'len': format_hex, - 'size': format_hex, - 'data': format_hex, - 'last': format_bool, - 'strb': format_hex, - } - - for col, formatter in format_rules.items(): - # Apply format rules for the specified columns - if col in df_formatted.columns: - df_formatted[col] = df_formatted[col].apply(formatter) - - return df_formatted - -# Function to select transactions matching a specific criteria -def filter_transactions(df, tx_type, **kwargs): - """ - Filters the DataFrame for entries of a given transaction type (AW, W, etc.) - and optional field-based filters like addr, id, burst, etc. - - Parameters: - df (pd.DataFrame): The AXI transaction log DataFrame. - tx_type (str): Required. Transaction type to filter on (e.g., 'AW', 'W'). - kwargs (dict): Additional field filters, e.g. addr='0x70000000', id=7. - - Returns: - pd.DataFrame: Filtered DataFrame matching the criteria. - """ - df_filtered = df[df['type'] == tx_type] - - for key, val in kwargs.items(): - if key not in df.columns: - raise ValueError(f"Column '{key}' not found in DataFrame.") - - # Convert hex strings to int - if isinstance(val, str) and val.startswith('0x'): - try: - val = int(val, 16) - except ValueError: - raise ValueError(f"Invalid hex value for {key}: '{val}'") - - df_filtered = df_filtered[df_filtered[key] == val] - - return df_filtered.reset_index(drop=True) - - -if __name__ == "__main__": - file_path = "axi_trace_mem_tile_0.log" - df = parse_axi_dump(file_path) - df = resolve_write_addresses(df) - df_formatted = filter_transactions(df, "W", addr="0x700044e8") - df_formatted = format_axi_df_for_display(df_formatted) - print(df_formatted) From bc7d45281f61572b8c7ebab68eea1a44fa3343c0 Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Mon, 15 Sep 2025 11:44:58 +0200 Subject: [PATCH 35/35] Bump PD repo --- .gitlab/common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/common.yml b/.gitlab/common.yml index ef69a99a..26aaca7a 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -4,7 +4,7 @@ variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - PD_COMMIT: "6b315613e2e7aeaa4d9c35e8c0fcd8fb8648ed53" + PD_COMMIT: "245dd3d4a7c33f6052a913691382871bd2c53fc0" SPU_COMMIT: "c2e8815487bd713624d74ef3e3e0465196b6d67f" # Check the cache for bender and python dependencies