diff --git a/Bender.lock b/Bender.lock index 61477304..eca18867 100644 --- a/Bender.lock +++ b/Bender.lock @@ -63,7 +63,7 @@ packages: dependencies: - axi floo_noc: - revision: 8413d9b044a51f958a7d4ef261e2b01e73db4544 + revision: 7e5104cd0875c3ded16d53366caaef2dc465c49d version: null source: Git: https://github.com/pulp-platform/FlooNoC.git diff --git a/Bender.yml b/Bender.yml index 59ec724d..5cc78d46 100644 --- a/Bender.yml +++ b/Bender.yml @@ -27,7 +27,6 @@ export_include_dirs: sources: # Level 0 - hardware/src/reqrsp_to_reg.sv - - hardware/src/reqrsp_w2n_converter.sv - hardware/src/reqrsp_xbar.sv - hardware/src/tcdm_cache_interco.sv - hardware/src/tcdm_id_remapper.sv @@ -35,12 +34,15 @@ sources: # Memory-mapped register - hardware/cachepool_peripheral/cachepool_peripheral_reg_pkg.sv - hardware/cachepool_peripheral/cachepool_peripheral_reg_top.sv - - hardware/cachepool_peripheral/cachepool_peripheral.sv # Bootrom - hardware/bootrom/bootrom.sv # Level 1 - hardware/generated/floo_cachepool_noc_pkg.sv - hardware/src/cachepool_pkg.sv + - hardware/cachepool_peripheral/cachepool_peripheral.sv + # Group configuration registers (cluster sender + group-level register) + - hardware/src/cachepool_group_cfg_sender.sv + - hardware/src/cachepool_group_cfg.sv - hardware/src/cachepool_acc_cut.sv - hardware/src/cachepool_cc.sv - hardware/src/cachepool_spatz_lock.sv diff --git a/Makefile b/Makefile index def098f5..6730f1af 100644 --- a/Makefile +++ b/Makefile @@ -309,6 +309,15 @@ CACHEPOOL_DEFS += -DDATA_WIDTH=$(data_width) CACHEPOOL_DEFS += -DADDR_WIDTH=$(addr_width) CACHEPOOL_DEFS += -DNUM_BARRIER_SLOTS=$(num_barrier_slots) +# Group-level configuration registers and the reset values shared with the cluster peripheral +CACHEPOOL_DEFS += -DGROUP_CFG_BASE=$(group_cfg_base) +CACHEPOOL_DEFS += -DGROUP_CFG_STRIDE=$(group_cfg_stride) +CACHEPOOL_DEFS += -DL1D_XBAR_OFFSET_RST=$(l1d_xbar_offset_rst) +CACHEPOOL_DEFS += -DL1D_PRIVATE_RST=$(l1d_private_rst) +CACHEPOOL_DEFS += -DL1D_PRIVATE_ADDR_RST=$(l1d_private_addr_rst) +CACHEPOOL_DEFS += -DICACHE_PREFETCH_RST=$(icache_prefetch_rst) +CACHEPOOL_DEFS += -DBOOTROM_DATA_WIDTH=$(bootrom_data_width) + # Tile configuration CACHEPOOL_DEFS += -DREFILL_DATA_WIDTH=$(refill_data_width) diff --git a/README.md b/README.md index 9e884f9e..2569ac69 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,8 @@ The default boundary is `0xA000_0000`. This means data in `.pdcp_src` (at `0xA00 Flush instructions are issued through the cluster peripheral and dispatched to cache controllers via a two-level controller: -- **Cluster level** (`cachepool_peripheral`): holds the instruction and a per-tile one-hot tile-select mask. Issues the instruction to all tiles simultaneously and tracks completion at tile granularity. +- **Cluster level** (`cachepool_peripheral`): holds the instruction and a per-tile one-hot tile-select mask. On `L1D_INSN_COMMIT` it sends the instruction, with each group's slice of the mask, to the group-level configuration register of every group over the L2 mesh (`cachepool_group_cfg_sender`), and stays busy until every group has answered. +- **Group level** (`cachepool_group_cfg`): starts the instruction in its selected tiles and answers only once all of them have finished. - **Tile level** (`cachepool_tile`): receives the instruction, determines which of its local cache controllers to activate based on the partition field, and returns a single ready pulse to the cluster controller when all targeted controllers finish. ### Flush instruction encoding @@ -342,7 +343,7 @@ l1d_xbar_config(uint32_t offset); Lower-level, single-shot primitives (`l1d_flush()`, `l1d_private_flush()`, `l1d_shared_flush()`, `l1d_commit()`, `l1d_wait()`) exist for building custom sequences but issue only the register write — the caller is responsible for cluster-wide fence/barrier synchronization around them. -`l1d_addr()` has no hardware completion status bit (unlike flush, which polls `L1D_FLUSH_STATUS` via `l1d_wait()`); its peripheral register write is posted over the interconnect, so it bridges the gap with a fixed-cycle delay on core 0 before the final barrier. +Only flush/invalidate instructions are committed. The configuration registers (`L1D_PRIVATE`, `L1D_ADDR`, `XBAR_OFFSET`, `ICACHE_PREFETCH_ENABLE`) need no commit: when a value changes, the peripheral sends it to every group on its own. `L1D_FLUSH_STATUS` stays busy until every group has applied it, so `l1d_part()`, `l1d_addr()` and `l1d_xbar_config()` end with `l1d_wait()`. Writing the value a register already holds sends nothing. > Changing the partition mode, boundary address, or crossbar offset while the cache contains valid data requires a flush first — `l1d_xbar_config()` and `l1d_part()` flush internally; `l1d_addr()` does not (call a cluster flush before it if needed). diff --git a/config/cachepool.hjson.tmpl b/config/cachepool.hjson.tmpl index 8ced3ab5..a4bcb214 100644 --- a/config/cachepool.hjson.tmpl +++ b/config/cachepool.hjson.tmpl @@ -5,6 +5,7 @@ cluster: { mempool: 0, boot_addr: ${boot_addr}, // e.g., 4096 (0x1000) + bootrom_data_width: ${bootrom_data_width}, cluster_base_addr: ${stack_addr}, // 0xBFFF_F800 typically cluster_base_offset: 0, cluster_base_hartid: 0, diff --git a/config/config.mk b/config/config.mk index 9a3c732e..26c0c259 100644 --- a/config/config.mk +++ b/config/config.mk @@ -74,8 +74,9 @@ data_width ?= 32 # Core addrwidth addr_width ?= 32 -# Bootrom datawidth -bootrom_data_width ?= 32 +# Bootrom datawidth: must equal the L2 refill width, the bootrom is only read by full-line +# iCache refills on the wide L2 path +bootrom_data_width ?= $(refill_data_width) ###################### @@ -227,3 +228,21 @@ boot_addr ?= 4096 # UART address (32'hC001_0000) uart_addr ?= 3221291008 + +# Group-level configuration registers, written by the cluster peripheral over the +# L2 mesh. Group g owns one word at group_cfg_base + g * group_cfg_stride. +# Base (32'hD000_0000) +group_cfg_base ?= 3489660928 +# Stride per group (32'h100) +group_cfg_stride ?= 256 + +# Reset values of the L1D/xbar configuration, shared by the cluster peripheral +# registers and the group-level copies so both always come out of reset equal. +# Cache xbar offset (bit position selecting the cache bank) +l1d_xbar_offset_rst ?= 14 +# Private partition size and fold (L1D_PRIVATE) +l1d_private_rst ?= 0 +# Start address of the private L1D partition (32'hA000_0000) +l1d_private_addr_rst ?= 2684354560 +# Instruction cache prefetching enable +icache_prefetch_rst ?= 1 diff --git a/hardware/bootrom/bootrom.S b/hardware/bootrom/bootrom.S index 20749fb2..c3712be8 100644 --- a/hardware/bootrom/bootrom.S +++ b/hardware/bootrom/bootrom.S @@ -9,10 +9,13 @@ .global BOOTDATA _start: - la t1, exception + // lla, not la: la loads the address from the GOT, a 32-bit data read of the bootrom. + // The bootrom is only served on the wide L2 path (instruction refills), so it must + // never see data loads. + lla t1, exception csrw mtvec, t1 csrr a0, mhartid - la a1, BOOTDATA + lla a1, BOOTDATA // Enable MSI interrupt csrw mie, 0xF diff --git a/hardware/cachepool_peripheral/cachepool_peripheral.sv b/hardware/cachepool_peripheral/cachepool_peripheral.sv index 26e60506..46e079af 100644 --- a/hardware/cachepool_peripheral/cachepool_peripheral.sv +++ b/hardware/cachepool_peripheral/cachepool_peripheral.sv @@ -2,13 +2,19 @@ // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 -/// Exposes cluster confugration and information as memory mapped information +/// style-waive: COMMENT_BLOCK: function description +/// Exposes cluster confugration and information as memory mapped information. +/// The L1D/xbar configuration is not wired to the groups: every changed setting and every +/// L1D instruction commit is handed to cachepool_group_cfg_sender as one batch, which writes +/// that register into each group's cachepool_group_cfg over the L2 mesh. L1D_FLUSH_STATUS +/// stays busy until no batch is pending or in flight. `include "common_cells/assertions.svh" `include "common_cells/registers.svh" module cachepool_peripheral import snitch_pkg::*; + import cachepool_pkg::*; import cachepool_peripheral_reg_pkg::*; #( parameter int unsigned AddrWidth = 0, @@ -24,11 +30,9 @@ module cachepool_peripheral parameter int unsigned PrivateWidth = 4, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, - parameter type cache_insn_t = logic, /// Derived parameter *Do not override* parameter type addr_t = logic [AddrWidth-1:0], - parameter type spm_size_t = logic [SPMWidth-1:0], - parameter type private_sel_t = logic [PrivateWidth-1:0] + parameter type spm_size_t = logic [SPMWidth-1:0] ) ( input logic clk_i, input logic rst_ni, @@ -39,18 +43,17 @@ module cachepool_peripheral output logic [3:0] eoc_o, input addr_t tcdm_start_address_i, input addr_t tcdm_end_address_i, - output addr_t private_start_addr_o, - output logic icache_prefetch_enable_o, output logic cluster_probe_o, input logic [9:0] cluster_hart_base_id_i, - /// For cache xbar dynamic configuration - output logic [4:0] dynamic_offset_o, output spm_size_t l1d_spm_size_o, - output private_sel_t l1d_private_o, - output cache_insn_t l1d_insn_o, - output logic l1d_insn_valid_o, - input logic [NumTiles-1:0] l1d_insn_ready_i, + // Per-tile flush interlock, high from an L1D instruction commit until its batch is done output logic [NumTiles-1:0] l1d_busy_o, + // Configuration batch towards cachepool_group_cfg_sender, stable until cfg_done_i + output logic cfg_start_o, + output group_cfg_reg_e cfg_reg_o, + output logic [31:0] cfg_data_o, + output logic [NumTiles-1:0] cfg_tile_sel_o, + input logic cfg_done_i, // One group-participation mask per barrier slot (see HW_BARRIER_PARTICIPATION_MASK multireg) output logic [NumBarrierSlots-1:0][NumGroups-1:0] barrier_part_mask_o ); @@ -59,6 +62,8 @@ module cachepool_peripheral `ASSERT_INIT(CheckBarrierSlotsMatchRegPkg, NumBarrierSlots == cachepool_peripheral_reg_pkg::NumBarrierSlots, "NumBarrierSlots does not match cachepool_peripheral_reg_pkg::NumBarrierSlots -- regenerate the peripheral register file for this config") + `ASSERT_INIT(CheckPrivateWidth, PrivateWidth == L1PrivWidth, + "PrivateWidth does not match cachepool_pkg::L1PrivWidth") cachepool_peripheral_reg2hw_t reg2hw; cachepool_peripheral_hw2reg_t hw2reg; @@ -79,45 +84,19 @@ module cachepool_peripheral //////////// EOC ///////////// assign eoc_o = reg2hw.cluster_eoc_exit.q; - //////////// Cache XBar //////////// - logic [4:0] xbar_offset_d, xbar_offset_q; - assign dynamic_offset_o = xbar_offset_q; - logic xbar_offset_commit; - assign xbar_offset_commit = reg2hw.xbar_offset_commit.q; - always_comb begin : xbar_offset_cfg - xbar_offset_d = xbar_offset_q; - hw2reg.xbar_offset_commit.d = 1'b0; - hw2reg.xbar_offset_commit.de = 1'b0; - - if (xbar_offset_commit) begin - xbar_offset_d = reg2hw.xbar_offset.q; - hw2reg.xbar_offset_commit.d = 1'b0; - hw2reg.xbar_offset_commit.de = 1'b1; - end - end - // Default value is 13 - `FF(xbar_offset_q, xbar_offset_d, 5'd14, clk_i, rst_ni) - - - //////////// L1 DCache //////////// + //////////// L1 DCache SPM //////////// logic [9:0] l1d_spm_size_d, l1d_spm_size_q; - logic [PrivateWidth-1:0] l1d_private_d, l1d_private_q; - addr_t private_start_addr_d, private_start_addr_q; - // L1 is running flush/invalidation - logic [NumTiles-1:0] l1d_lock_d, l1d_lock_q; - logic l1d_spm_commit, l1d_insn_commit; + logic l1d_spm_commit; - // L1D Cache // For committing the cfg, if the cfg is taken, it will be pulled to 0; // Otherwise, it will be kept at 1 until taken. assign l1d_spm_commit = reg2hw.l1d_spm_commit.q; - assign l1d_insn_commit = reg2hw.l1d_insn_commit.q; // TODO: Change it to power of 2 to save space // SPM Size always_comb begin : l1d_spm_cfg l1d_spm_size_d = l1d_spm_size_q; - + hw2reg.l1d_spm_commit.d = 1'b0; hw2reg.l1d_spm_commit.de = 1'b0; @@ -133,9 +112,7 @@ module cachepool_peripheral // 10b is enough for 1024 cache lines, we should not need all of them assign l1d_spm_size_o = l1d_spm_size_q[SPMWidth-1:0]; - assign l1d_private_o = l1d_private_q[PrivateWidth-1:0]; - assign private_start_addr_o = private_start_addr_q; - + //////////// L1D / Cache XBar configuration //////////// // Concatenate all tile-select register words into one wide vector and slice // to NumTiles. Unused upper bits (for small configs) are optimised away. localparam int unsigned NumTileSelWords = cachepool_peripheral_reg_pkg::NumTileSelRegs; @@ -146,66 +123,121 @@ module cachepool_peripheral end end - // One 32-bit register per barrier slot; single register is enough at - // group granularity (max 32 groups). - always_comb begin : barrier_mask_concat - for (int s = 0; s < NumBarrierSlots; s++) begin - barrier_part_mask_o[s] = reg2hw.hw_barrier_participation_mask[s].q[NumGroups-1:0]; - end - end - - // Cache Flush Controller, per tile: l1d_lock_q[t] is set on issue and cleared on ready. - // Busy while any selected tile has not completed. - always_comb begin : l1d_insn_cfg - // Flush takes time, we cannot take next insn while flushing - l1d_insn_o = '0; - l1d_insn_valid_o = '0; - l1d_lock_d = l1d_lock_q; - l1d_private_d = l1d_private_q; - private_start_addr_d = private_start_addr_q; + // Settings as currently written by software + l1d_cfg_t reg_cfg; + assign reg_cfg.l1d_private = {reg2hw.l1d_private.group_fold.q, reg2hw.l1d_private.partition.q}; + assign reg_cfg.private_addr = reg2hw.l1d_addr.q; + assign reg_cfg.xbar_offset = reg2hw.xbar_offset.q; + assign reg_cfg.prefetch_en = reg2hw.icache_prefetch_enable.q; + + // Committed settings, the copy every group-level register holds once a batch is done + l1d_cfg_t cfg_d, cfg_q; + // A batch is in flight from its start until all groups have answered + logic busy_d, busy_q; + logic start_d, start_q; + group_cfg_reg_e reg_d, reg_q; + logic [31:0] data_d, data_q; + logic [NumTiles-1:0] tile_sel_d, tile_sel_q; + // L1 is running flush/invalidation + logic [NumTiles-1:0] l1d_lock_d, l1d_lock_q; + + // A setting differing from its committed copy is a pending update; only the L1D instruction + // waits for an explicit commit + logic l1d_insn_commit, xbar_update, private_update, addr_update, prefetch_update; + + assign l1d_insn_commit = reg2hw.l1d_insn_commit.q; + assign xbar_update = (reg_cfg.xbar_offset != cfg_q.xbar_offset); + assign private_update = (reg_cfg.l1d_private != cfg_q.l1d_private); + assign addr_update = (reg_cfg.private_addr != cfg_q.private_addr); + assign prefetch_update = (reg_cfg.prefetch_en != cfg_q.prefetch_en); + + // A batch carries one register and is only started while no batch is in flight; anything + // else stays pending until the previous batch is done. INSN first. + always_comb begin : cfg_ctrl + cfg_d = cfg_q; + busy_d = busy_q; + start_d = 1'b0; + reg_d = reg_q; + data_d = data_q; + tile_sel_d = tile_sel_q; + l1d_lock_d = l1d_lock_q; hw2reg.l1d_insn_commit.d = 1'b0; hw2reg.l1d_insn_commit.de = 1'b0; - if (l1d_insn_commit) begin - l1d_private_d = {reg2hw.l1d_private.group_fold.q, - reg2hw.l1d_private.partition.q}; - private_start_addr_d = reg2hw.l1d_addr.q; - // User issues a flush/invalidation - if (|l1d_lock_q == '0) begin - // We are ready to accept a new instruction. - // Build the cache_insn_t: pack insn + tile_sel. - // For non-private modes (shared/all/init), tile_sel is forced to '1. - l1d_insn_o.insn = reg2hw.cfg_l1d_insn.q; - l1d_insn_o.tile_sel = (reg2hw.cfg_l1d_insn.q == 2'b00) - ? tile_sel_raw[NumTiles-1:0] - : {NumTiles{1'b1}}; - l1d_insn_valid_o = 1'b1; - // Lock only the tiles that will receive the instruction. - l1d_lock_d = l1d_insn_o.tile_sel; - // Clear the commit - hw2reg.l1d_insn_commit.d = 1'b0; - hw2reg.l1d_insn_commit.de = 1'b1; + if (busy_q) begin + if (cfg_done_i) begin + busy_d = 1'b0; + l1d_lock_d = '0; end - end - - for (int t = 0; t < NumTiles; t++) begin - // Unlock tile t when it signals completion (one-cycle ready pulse). - if (l1d_insn_ready_i[t]) begin - l1d_lock_d[t] = 1'b0; - end - l1d_busy_o[t] = l1d_lock_q[t]; + end else if (l1d_insn_commit) begin + reg_d = GroupCfgL1dInsn; + data_d = '0; + data_d[1:0] = reg2hw.cfg_l1d_insn.q; + // For non-private modes (shared/all/init), tile_sel is forced to '1. + tile_sel_d = (reg2hw.cfg_l1d_insn.q == 2'b00) ? tile_sel_raw[NumTiles-1:0] : '1; + // Lock only the tiles that will receive the instruction. + l1d_lock_d = tile_sel_d; + busy_d = 1'b1; + start_d = 1'b1; + hw2reg.l1d_insn_commit.de = 1'b1; + end else if (xbar_update) begin + cfg_d.xbar_offset = reg_cfg.xbar_offset; + reg_d = GroupCfgXbarOffset; + data_d = '0; + data_d[$bits(xbar_offset_t)-1:0] = reg_cfg.xbar_offset; + busy_d = 1'b1; + start_d = 1'b1; + end else if (private_update) begin + cfg_d.l1d_private = reg_cfg.l1d_private; + reg_d = GroupCfgL1dPrivate; + data_d = '0; + data_d[L1PrivWidth-1:0] = reg_cfg.l1d_private; + busy_d = 1'b1; + start_d = 1'b1; + end else if (addr_update) begin + cfg_d.private_addr = reg_cfg.private_addr; + reg_d = GroupCfgL1dAddr; + data_d = reg_cfg.private_addr; + busy_d = 1'b1; + start_d = 1'b1; + end else if (prefetch_update) begin + cfg_d.prefetch_en = reg_cfg.prefetch_en; + reg_d = GroupCfgPrefetch; + data_d = '0; + data_d[0] = reg_cfg.prefetch_en; + busy_d = 1'b1; + start_d = 1'b1; end end - `FF(private_start_addr_q, private_start_addr_d, 32'hA000_0000, clk_i, rst_ni) - `FF(l1d_private_q, l1d_private_d, 0, clk_i, rst_ni) - `FF(l1d_lock_q, l1d_lock_d, '0, clk_i, rst_ni) - // To show if the current flush/invalidation is complete - assign hw2reg.l1d_flush_status.d = (l1d_lock_q != '0); + `FF(cfg_q, cfg_d, L1dCfgRst, clk_i, rst_ni) + `FF(busy_q, busy_d, 1'b0, clk_i, rst_ni) + `FF(start_q, start_d, 1'b0, clk_i, rst_ni) + `FF(reg_q, reg_d, GroupCfgXbarOffset, clk_i, rst_ni) + `FF(data_q, data_d, '0, clk_i, rst_ni) + `FF(tile_sel_q, tile_sel_d, '0, clk_i, rst_ni) + `FF(l1d_lock_q, l1d_lock_d, '0, clk_i, rst_ni) + + // The batch starts one cycle after it is taken, so the sender sees the updated values + assign cfg_start_o = start_q; + assign cfg_reg_o = reg_q; + assign cfg_data_o = data_q; + assign cfg_tile_sel_o = tile_sel_q; + + assign l1d_busy_o = l1d_lock_q; + // Busy from the cycle a commit or a changed setting is visible, so a status read right behind + // the write never sees idle before the batch has started. + assign hw2reg.l1d_flush_status.d = busy_q | l1d_insn_commit | xbar_update | private_update | + addr_update | prefetch_update; - // Enable icache prefetch - assign icache_prefetch_enable_o = reg2hw.icache_prefetch_enable.q; + // One 32-bit register per barrier slot; single register is enough at + // group granularity (max 32 groups). + always_comb begin : barrier_mask_concat + for (int s = 0; s < NumBarrierSlots; s++) begin + barrier_part_mask_o[s] = reg2hw.hw_barrier_participation_mask[s].q[NumGroups-1:0]; + end + end // Probe assign cluster_probe_o = reg2hw.spatz_status.q; diff --git a/hardware/cachepool_peripheral/cachepool_peripheral_reg.hjson.tmpl b/hardware/cachepool_peripheral/cachepool_peripheral_reg.hjson.tmpl index 0ba3a382..df17724a 100644 --- a/hardware/cachepool_peripheral/cachepool_peripheral_reg.hjson.tmpl +++ b/hardware/cachepool_peripheral/cachepool_peripheral_reg.hjson.tmpl @@ -69,11 +69,11 @@ desc: '''Controls prefetching of the instruction cache.''' swaccess: "wo", hwaccess: "hro", - resval: "1", + resval: "${icache_prefetch_rst}", fields: [{ bits: "0:0", name: "ICACHE_PREFETCH_ENABLE", - desc: "Hardware barrier register." + desc: "Enable instruction cache prefetching." }] }, { @@ -208,7 +208,9 @@ }, { name: "L1D_INSN_COMMIT", - desc: '''Controls the L1 DCache flushing and invalidation.''' + desc: '''Starts the L1D instruction in CFG_L1D_INSN on the tiles selected by + CFG_L1D_TILE_SEL. L1D_PRIVATE, L1D_ADDR, XBAR_OFFSET and ICACHE_PREFETCH_ENABLE need + no commit: a changed value is applied on its own.''' // hwext: "true", // hwqe: "true", swaccess: "rw", @@ -217,12 +219,13 @@ fields: [{ bits: "0", name: "COMMIT", - desc: "Commit the configurations for instructions." + desc: "Commit the L1D instruction." }] }, { name: "L1D_FLUSH_STATUS", - desc: '''Indicate the status of flushing ''' + desc: '''Busy while a committed L1D instruction or a changed setting is still being + applied by the group-level configuration registers.''' hwext: "true", swaccess: "ro", hwaccess: "hwo", @@ -235,10 +238,11 @@ }, { name: "L1D_PRIVATE", - desc: '''L1D partitioning: private bank count and how it is folded ''' + desc: '''L1D partitioning: private bank count and how it is folded. Applied when the + value changes, no commit needed.''' swaccess: "rw", hwaccess: "hro", - resval: "0", + resval: "${l1d_private_rst}", fields: [{ bits: "2:0", name: "PARTITION", @@ -251,10 +255,11 @@ }, { name: "L1D_ADDR", - desc: '''Starting address of private L1D partition ''' + desc: '''Starting address of private L1D partition. Applied when the value changes, no + commit needed.''' swaccess: "rw", hwaccess: "hro", - resval: "2684354560", + resval: "${l1d_private_addr_rst}", fields: [{ bits: "31:0", name: "ADDR", @@ -263,27 +268,16 @@ }, { name: "XBAR_OFFSET", - desc: '''Cache xbar offset setting ''' + desc: '''Cache xbar offset setting. Applied when the value changes, no commit + needed.''' swaccess: "rw", hwaccess: "hro", - resval: "0", + resval: "${l1d_xbar_offset_rst}", fields: [{ bits: "4:0", name: "OFFSET", desc: "range: 0-31" }] - }, - { - name: "XBAR_OFFSET_COMMIT", - desc: '''Cache xbar offset setting ''' - swaccess: "rw", - hwaccess: "hrw", - resval: "0", - fields: [{ - bits: "0", - name: "COMMIT", - desc: "Commit the xbar offset configurations." - }] } ] } diff --git a/hardware/cachepool_peripheral/cachepool_peripheral_reg_pkg.sv b/hardware/cachepool_peripheral/cachepool_peripheral_reg_pkg.sv index 3ace3df2..31804b1c 100644 --- a/hardware/cachepool_peripheral/cachepool_peripheral_reg_pkg.sv +++ b/hardware/cachepool_peripheral/cachepool_peripheral_reg_pkg.sv @@ -90,10 +90,6 @@ package cachepool_peripheral_reg_pkg; logic [4:0] q; } cachepool_peripheral_reg2hw_xbar_offset_reg_t; - typedef struct packed { - logic q; - } cachepool_peripheral_reg2hw_xbar_offset_commit_reg_t; - typedef struct packed { logic [31:0] d; } cachepool_peripheral_hw2reg_hw_barrier_reg_t; @@ -120,42 +116,35 @@ package cachepool_peripheral_reg_pkg; logic d; } cachepool_peripheral_hw2reg_l1d_flush_status_reg_t; - typedef struct packed { - logic d; - logic de; - } cachepool_peripheral_hw2reg_xbar_offset_commit_reg_t; - // Register -> HW type typedef struct packed { - cachepool_peripheral_reg2hw_hw_barrier_reg_t hw_barrier; // [349:318] - cachepool_peripheral_reg2hw_spatz_lock_acquire_reg_t spatz_lock_acquire; // [317:286] - cachepool_peripheral_reg2hw_spatz_lock_release_reg_t spatz_lock_release; // [285:254] - cachepool_peripheral_reg2hw_icache_prefetch_enable_reg_t icache_prefetch_enable; // [253:253] - cachepool_peripheral_reg2hw_spatz_status_reg_t spatz_status; // [252:252] - cachepool_peripheral_reg2hw_spatz_cycle_reg_t spatz_cycle; // [251:220] - cachepool_peripheral_reg2hw_cluster_boot_control_reg_t cluster_boot_control; // [219:188] - cachepool_peripheral_reg2hw_cluster_eoc_exit_reg_t cluster_eoc_exit; // [187:184] - cachepool_peripheral_reg2hw_cfg_l1d_spm_reg_t cfg_l1d_spm; // [183:174] - cachepool_peripheral_reg2hw_cfg_l1d_insn_reg_t cfg_l1d_insn; // [173:172] - cachepool_peripheral_reg2hw_cfg_l1d_tile_sel_mreg_t [1:0] cfg_l1d_tile_sel; // [171:108] - cachepool_peripheral_reg2hw_hw_barrier_participation_mask_mreg_t [1:0] hw_barrier_participation_mask; // [107:44] - cachepool_peripheral_reg2hw_l1d_spm_commit_reg_t l1d_spm_commit; // [43:43] - cachepool_peripheral_reg2hw_l1d_insn_commit_reg_t l1d_insn_commit; // [42:42] - cachepool_peripheral_reg2hw_l1d_private_reg_t l1d_private; // [41:38] - cachepool_peripheral_reg2hw_l1d_addr_reg_t l1d_addr; // [37:6] - cachepool_peripheral_reg2hw_xbar_offset_reg_t xbar_offset; // [5:1] - cachepool_peripheral_reg2hw_xbar_offset_commit_reg_t xbar_offset_commit; // [0:0] + cachepool_peripheral_reg2hw_hw_barrier_reg_t hw_barrier; // [348:317] + cachepool_peripheral_reg2hw_spatz_lock_acquire_reg_t spatz_lock_acquire; // [316:285] + cachepool_peripheral_reg2hw_spatz_lock_release_reg_t spatz_lock_release; // [284:253] + cachepool_peripheral_reg2hw_icache_prefetch_enable_reg_t icache_prefetch_enable; // [252:252] + cachepool_peripheral_reg2hw_spatz_status_reg_t spatz_status; // [251:251] + cachepool_peripheral_reg2hw_spatz_cycle_reg_t spatz_cycle; // [250:219] + cachepool_peripheral_reg2hw_cluster_boot_control_reg_t cluster_boot_control; // [218:187] + cachepool_peripheral_reg2hw_cluster_eoc_exit_reg_t cluster_eoc_exit; // [186:183] + cachepool_peripheral_reg2hw_cfg_l1d_spm_reg_t cfg_l1d_spm; // [182:173] + cachepool_peripheral_reg2hw_cfg_l1d_insn_reg_t cfg_l1d_insn; // [172:171] + cachepool_peripheral_reg2hw_cfg_l1d_tile_sel_mreg_t [1:0] cfg_l1d_tile_sel; // [170:107] + cachepool_peripheral_reg2hw_hw_barrier_participation_mask_mreg_t [1:0] hw_barrier_participation_mask; // [106:43] + cachepool_peripheral_reg2hw_l1d_spm_commit_reg_t l1d_spm_commit; // [42:42] + cachepool_peripheral_reg2hw_l1d_insn_commit_reg_t l1d_insn_commit; // [41:41] + cachepool_peripheral_reg2hw_l1d_private_reg_t l1d_private; // [40:37] + cachepool_peripheral_reg2hw_l1d_addr_reg_t l1d_addr; // [36:5] + cachepool_peripheral_reg2hw_xbar_offset_reg_t xbar_offset; // [4:0] } cachepool_peripheral_reg2hw_t; // HW -> register type typedef struct packed { - cachepool_peripheral_hw2reg_hw_barrier_reg_t hw_barrier; // [102:71] - cachepool_peripheral_hw2reg_spatz_lock_acquire_reg_t spatz_lock_acquire; // [70:39] - cachepool_peripheral_hw2reg_spatz_lock_release_reg_t spatz_lock_release; // [38:7] - cachepool_peripheral_hw2reg_l1d_spm_commit_reg_t l1d_spm_commit; // [6:5] - cachepool_peripheral_hw2reg_l1d_insn_commit_reg_t l1d_insn_commit; // [4:3] - cachepool_peripheral_hw2reg_l1d_flush_status_reg_t l1d_flush_status; // [2:2] - cachepool_peripheral_hw2reg_xbar_offset_commit_reg_t xbar_offset_commit; // [1:0] + cachepool_peripheral_hw2reg_hw_barrier_reg_t hw_barrier; // [100:69] + cachepool_peripheral_hw2reg_spatz_lock_acquire_reg_t spatz_lock_acquire; // [68:37] + cachepool_peripheral_hw2reg_spatz_lock_release_reg_t spatz_lock_release; // [36:5] + cachepool_peripheral_hw2reg_l1d_spm_commit_reg_t l1d_spm_commit; // [4:3] + cachepool_peripheral_hw2reg_l1d_insn_commit_reg_t l1d_insn_commit; // [2:1] + cachepool_peripheral_hw2reg_l1d_flush_status_reg_t l1d_flush_status; // [0:0] } cachepool_peripheral_hw2reg_t; // Register offsets @@ -179,7 +168,6 @@ package cachepool_peripheral_reg_pkg; parameter logic [BlockAw-1:0] CACHEPOOL_PERIPHERAL_L1D_PRIVATE_OFFSET = 7'h 44; parameter logic [BlockAw-1:0] CACHEPOOL_PERIPHERAL_L1D_ADDR_OFFSET = 7'h 48; parameter logic [BlockAw-1:0] CACHEPOOL_PERIPHERAL_XBAR_OFFSET_OFFSET = 7'h 4c; - parameter logic [BlockAw-1:0] CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT_OFFSET = 7'h 50; // Reset values for hwext registers and their fields parameter logic [31:0] CACHEPOOL_PERIPHERAL_HW_BARRIER_RESVAL = 32'h 0; @@ -209,12 +197,11 @@ package cachepool_peripheral_reg_pkg; CACHEPOOL_PERIPHERAL_L1D_FLUSH_STATUS, CACHEPOOL_PERIPHERAL_L1D_PRIVATE, CACHEPOOL_PERIPHERAL_L1D_ADDR, - CACHEPOOL_PERIPHERAL_XBAR_OFFSET, - CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT + CACHEPOOL_PERIPHERAL_XBAR_OFFSET } cachepool_peripheral_id_e; // Register width information to check illegal writes - parameter logic [3:0] CACHEPOOL_PERIPHERAL_PERMIT [21] = '{ + parameter logic [3:0] CACHEPOOL_PERIPHERAL_PERMIT [20] = '{ 4'b 1111, // index[ 0] CACHEPOOL_PERIPHERAL_HW_BARRIER 4'b 1111, // index[ 1] CACHEPOOL_PERIPHERAL_SPATZ_LOCK_ACQUIRE 4'b 1111, // index[ 2] CACHEPOOL_PERIPHERAL_SPATZ_LOCK_RELEASE @@ -234,8 +221,7 @@ package cachepool_peripheral_reg_pkg; 4'b 0001, // index[16] CACHEPOOL_PERIPHERAL_L1D_FLUSH_STATUS 4'b 0001, // index[17] CACHEPOOL_PERIPHERAL_L1D_PRIVATE 4'b 1111, // index[18] CACHEPOOL_PERIPHERAL_L1D_ADDR - 4'b 0001, // index[19] CACHEPOOL_PERIPHERAL_XBAR_OFFSET - 4'b 0001 // index[20] CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT + 4'b 0001 // index[19] CACHEPOOL_PERIPHERAL_XBAR_OFFSET }; endpackage diff --git a/hardware/cachepool_peripheral/cachepool_peripheral_reg_top.sv b/hardware/cachepool_peripheral/cachepool_peripheral_reg_top.sv index b86669b5..94ba3f7e 100644 --- a/hardware/cachepool_peripheral/cachepool_peripheral_reg_top.sv +++ b/hardware/cachepool_peripheral/cachepool_peripheral_reg_top.sv @@ -125,9 +125,6 @@ module cachepool_peripheral_reg_top #( logic [4:0] xbar_offset_qs; logic [4:0] xbar_offset_wd; logic xbar_offset_we; - logic xbar_offset_commit_qs; - logic xbar_offset_commit_wd; - logic xbar_offset_commit_we; // Register instances // R[hw_barrier]: V(True) @@ -633,7 +630,7 @@ module cachepool_peripheral_reg_top #( prim_subreg #( .DW (5), .SWACCESS("RW"), - .RESVAL (5'h0) + .RESVAL (5'he) ) u_xbar_offset ( .clk_i (clk_i ), .rst_ni (rst_ni ), @@ -655,36 +652,9 @@ module cachepool_peripheral_reg_top #( ); - // R[xbar_offset_commit]: V(False) - - prim_subreg #( - .DW (1), - .SWACCESS("RW"), - .RESVAL (1'h0) - ) u_xbar_offset_commit ( - .clk_i (clk_i ), - .rst_ni (rst_ni ), - - // from register interface - .we (xbar_offset_commit_we), - .wd (xbar_offset_commit_wd), - - // from internal hardware - .de (hw2reg.xbar_offset_commit.de), - .d (hw2reg.xbar_offset_commit.d ), - - // to internal hardware - .qe (), - .q (reg2hw.xbar_offset_commit.q ), - - // to register interface (read) - .qs (xbar_offset_commit_qs) - ); - - - logic [20:0] addr_hit; + logic [19:0] addr_hit; always_comb begin addr_hit = '0; addr_hit[ 0] = (reg_addr == CACHEPOOL_PERIPHERAL_HW_BARRIER_OFFSET); @@ -707,7 +677,6 @@ module cachepool_peripheral_reg_top #( addr_hit[17] = (reg_addr == CACHEPOOL_PERIPHERAL_L1D_PRIVATE_OFFSET); addr_hit[18] = (reg_addr == CACHEPOOL_PERIPHERAL_L1D_ADDR_OFFSET); addr_hit[19] = (reg_addr == CACHEPOOL_PERIPHERAL_XBAR_OFFSET_OFFSET); - addr_hit[20] = (reg_addr == CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT_OFFSET); end assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ; @@ -734,8 +703,7 @@ module cachepool_peripheral_reg_top #( (addr_hit[16] & (|(CACHEPOOL_PERIPHERAL_PERMIT[16] & ~reg_be))) | (addr_hit[17] & (|(CACHEPOOL_PERIPHERAL_PERMIT[17] & ~reg_be))) | (addr_hit[18] & (|(CACHEPOOL_PERIPHERAL_PERMIT[18] & ~reg_be))) | - (addr_hit[19] & (|(CACHEPOOL_PERIPHERAL_PERMIT[19] & ~reg_be))) | - (addr_hit[20] & (|(CACHEPOOL_PERIPHERAL_PERMIT[20] & ~reg_be))))); + (addr_hit[19] & (|(CACHEPOOL_PERIPHERAL_PERMIT[19] & ~reg_be))))); end assign hw_barrier_re = addr_hit[0] & reg_re & !reg_error; @@ -797,9 +765,6 @@ module cachepool_peripheral_reg_top #( assign xbar_offset_we = addr_hit[19] & reg_we & !reg_error; assign xbar_offset_wd = reg_wdata[4:0]; - assign xbar_offset_commit_we = addr_hit[20] & reg_we & !reg_error; - assign xbar_offset_commit_wd = reg_wdata[0]; - // Read data return always_comb begin reg_rdata_next = '0; @@ -885,10 +850,6 @@ module cachepool_peripheral_reg_top #( reg_rdata_next[4:0] = xbar_offset_qs; end - addr_hit[20]: begin - reg_rdata_next[0] = xbar_offset_commit_qs; - end - default: begin reg_rdata_next = '1; end diff --git a/hardware/src/cachepool_cluster.sv b/hardware/src/cachepool_cluster.sv index 6afebcfd..32b0fd4d 100644 --- a/hardware/src/cachepool_cluster.sv +++ b/hardware/src/cachepool_cluster.sv @@ -172,19 +172,24 @@ module cachepool_cluster axi_slv_cache_req_t [ClusterWideOutAxiPorts-1:0] wide_axi_slv_req; axi_slv_cache_resp_t [ClusterWideOutAxiPorts-1:0] wide_axi_slv_rsp; - // HBM0 peripheral demux port: narrowed 32b REQRSP with refill_user_t (same user as wide side). - peri_narrow_req_t hbm0_peri_narrow_req; - peri_narrow_rsp_t hbm0_peri_narrow_rsp; + // Narrow L2 mesh endpoint of the cluster peripheral, subordinate side: core accesses to the + // CSRs and the UART. + peri_noc_req_chan_t peri_sbr_req; + logic peri_sbr_req_valid; + logic peri_sbr_req_ready; + peri_narrow_rsp_chan_t peri_sbr_rsp; + logic peri_sbr_rsp_valid; + logic peri_sbr_rsp_ready; // 2. Peripherals - axi_addr_t private_start_addr; - logic icache_prefetch_enable; - logic [$clog2(L1AddrWidth)-1:0] dynamic_offset; - cache_insn_t l1d_insn; - logic l1d_insn_valid; - logic [NumTiles-1:0] l1d_insn_ready; logic [NumTiles-1:0] l1d_busy; - logic [L1PrivWidth-1:0] l1d_private; + + // Configuration batch from the peripheral to the group configuration sender + logic cfg_start; + group_cfg_reg_e cfg_reg; + logic [31:0] cfg_data; + logic [NumTiles-1:0] cfg_tile_sel; + logic cfg_done; // Per-group error signals. logic [NumGroups-1:0] group_error; @@ -250,6 +255,20 @@ module cachepool_cluster logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] l2_rsp_in_valid; logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] l2_rsp_in_ready; + // Per-group narrow L2 mesh signals [NumGroupsX][NumGroupsY][direction 0..3] + peri_noc_req_t [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_out; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_out_valid; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_out_ready; + peri_noc_req_t [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_in; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_in_valid; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_req_in_ready; + peri_noc_rsp_t [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_out; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_out_valid; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_out_ready; + peri_noc_rsp_t [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_in; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_in_valid; + logic [NumGroupsX-1:0][NumGroupsY-1:0][3:0] peri_rsp_in_ready; + assign error_o = |group_error; for (genvar gy = 0; gy < NumGroupsY; gy++) begin : gen_group_y @@ -306,7 +325,6 @@ module cachepool_cluster .hart_base_id_i ( HartBaseId + 10'(g * NumCoreGroup * NumScalarPerCC) ), .tile_base_id_i ( TileIDWidth'(g * NumTilesPerGroup) ), .cluster_base_addr_i ( cluster_base_addr_i ), - .private_start_addr_i ( private_start_addr ), // L2 refill TCDM mesh (4 directions) .l2_req_o ( l2_req_out [gx][gy] ), .l2_req_valid_o ( l2_req_out_valid [gx][gy] ), @@ -320,16 +338,23 @@ module cachepool_cluster .l2_rsp_i ( l2_rsp_in [gx][gy] ), .l2_rsp_valid_i ( l2_rsp_in_valid [gx][gy] ), .l2_rsp_ready_o ( l2_rsp_in_ready [gx][gy] ), + // Narrow L2 mesh (4 directions) + .peri_req_o ( peri_req_out [gx][gy] ), + .peri_req_valid_o ( peri_req_out_valid [gx][gy] ), + .peri_req_ready_i ( peri_req_out_ready [gx][gy] ), + .peri_req_i ( peri_req_in [gx][gy] ), + .peri_req_valid_i ( peri_req_in_valid [gx][gy] ), + .peri_req_ready_o ( peri_req_in_ready [gx][gy] ), + .peri_rsp_o ( peri_rsp_out [gx][gy] ), + .peri_rsp_valid_o ( peri_rsp_out_valid [gx][gy] ), + .peri_rsp_ready_i ( peri_rsp_out_ready [gx][gy] ), + .peri_rsp_i ( peri_rsp_in [gx][gy] ), + .peri_rsp_valid_i ( peri_rsp_in_valid [gx][gy] ), + .peri_rsp_ready_o ( peri_rsp_in_ready [gx][gy] ), .l2_id_i ( floo_cachepool_noc_pkg::id_t'(gx * NumGroupsY + gy) ), .l2_route_table_i ( floo_cachepool_noc_pkg::RoutingTables[gx * NumGroupsY + gy] ), // Peripherals .icache_events_o ( /* unused */ ), - .icache_prefetch_enable_i ( icache_prefetch_enable ), - .dynamic_offset_i ( dynamic_offset ), - .l1d_private_i ( l1d_private ), - .l1d_insn_i ( l1d_insn ), - .l1d_insn_valid_i ( l1d_insn_valid ), - .l1d_insn_ready_o ( l1d_insn_ready[g*NumTilesPerGroup +: NumTilesPerGroup]), .l1d_busy_i ( l1d_busy [g*NumTilesPerGroup +: NumTilesPerGroup]), .group_xy_id_i ( group_xy_id_t'{x: gx, y: gy, @@ -545,6 +570,9 @@ module cachepool_cluster // L2 Refill TCDM Mesh + HBM Edge Adapters // ------------------------------------------------------- + // The group configuration registers are only reachable over the L2 mesh. + `ASSERT_INIT(CheckMultiGroup, NumGroups > 1, "cachepool_cluster: NumGroups must be > 1") + if (NumGroups > 1) begin : gen_l2_refill_mesh // -------------------------------------------------- @@ -591,14 +619,80 @@ module cachepool_cluster end end + // -------------------------------------------------- + // Narrow L2 mesh: same interior links as the wide mesh + // -------------------------------------------------- + + for (genvar gx = 0; gx < NumGroupsX - 1; gx++) begin : gen_peri_ew + for (genvar gy = 0; gy < NumGroupsY; gy++) begin : gen_peri_ew_y + // East(1) output of (gx,gy) <-> West(3) input of (gx+1,gy), both ways + assign peri_req_in [gx+1][gy][3] = peri_req_out [gx] [gy][1]; + assign peri_req_in_valid [gx+1][gy][3] = peri_req_out_valid [gx] [gy][1]; + assign peri_req_out_ready[gx] [gy][1] = peri_req_in_ready [gx+1][gy][3]; + assign peri_rsp_in [gx+1][gy][3] = peri_rsp_out [gx] [gy][1]; + assign peri_rsp_in_valid [gx+1][gy][3] = peri_rsp_out_valid [gx] [gy][1]; + assign peri_rsp_out_ready[gx] [gy][1] = peri_rsp_in_ready [gx+1][gy][3]; + assign peri_req_in [gx] [gy][1] = peri_req_out [gx+1][gy][3]; + assign peri_req_in_valid [gx] [gy][1] = peri_req_out_valid [gx+1][gy][3]; + assign peri_req_out_ready[gx+1][gy][3] = peri_req_in_ready [gx] [gy][1]; + assign peri_rsp_in [gx] [gy][1] = peri_rsp_out [gx+1][gy][3]; + assign peri_rsp_in_valid [gx] [gy][1] = peri_rsp_out_valid [gx+1][gy][3]; + assign peri_rsp_out_ready[gx+1][gy][3] = peri_rsp_in_ready [gx] [gy][1]; + end + end + + for (genvar gx = 0; gx < NumGroupsX; gx++) begin : gen_peri_ns + for (genvar gy = 0; gy < NumGroupsY - 1; gy++) begin : gen_peri_ns_y + // North(0) output of (gx,gy) <-> South(2) input of (gx,gy+1), both ways + assign peri_req_in [gx][gy+1][2] = peri_req_out [gx][gy] [0]; + assign peri_req_in_valid [gx][gy+1][2] = peri_req_out_valid [gx][gy] [0]; + assign peri_req_out_ready[gx][gy] [0] = peri_req_in_ready [gx][gy+1][2]; + assign peri_rsp_in [gx][gy+1][2] = peri_rsp_out [gx][gy] [0]; + assign peri_rsp_in_valid [gx][gy+1][2] = peri_rsp_out_valid [gx][gy] [0]; + assign peri_rsp_out_ready[gx][gy] [0] = peri_rsp_in_ready [gx][gy+1][2]; + assign peri_req_in [gx][gy] [0] = peri_req_out [gx][gy+1][2]; + assign peri_req_in_valid [gx][gy] [0] = peri_req_out_valid [gx][gy+1][2]; + assign peri_req_out_ready[gx][gy+1][2] = peri_req_in_ready [gx][gy] [0]; + assign peri_rsp_in [gx][gy] [0] = peri_rsp_out [gx][gy+1][2]; + assign peri_rsp_in_valid [gx][gy] [0] = peri_rsp_out_valid [gx][gy+1][2]; + assign peri_rsp_out_ready[gx][gy+1][2] = peri_rsp_in_ready [gx][gy] [0]; + end + end + + // Narrow mesh boundary: only the West port of group (0,0) has an endpoint (i_peri_chimney + // below). Every other edge port is tied off and never sees a flit. + for (genvar gx = 0; gx < NumGroupsX; gx++) begin : gen_peri_bnd_x + for (genvar gy = 0; gy < NumGroupsY; gy++) begin : gen_peri_bnd_y + for (genvar d = 0; d < 4; d++) begin : gen_peri_bnd_d + localparam bit IsEdge = ((d == 0) && (gy == NumGroupsY - 1)) || + ((d == 1) && (gx == NumGroupsX - 1)) || + ((d == 2) && (gy == 0)) || + ((d == 3) && (gx == 0)); + localparam bit IsPeriPort = (d == 3) && (gx == 0) && (gy == 0); + if (IsEdge && !IsPeriPort) begin : gen_tieoff + assign peri_req_in [gx][gy][d] = '0; + assign peri_req_in_valid [gx][gy][d] = 1'b0; + assign peri_req_out_ready[gx][gy][d] = 1'b1; + assign peri_rsp_in [gx][gy][d] = '0; + assign peri_rsp_in_valid [gx][gy][d] = 1'b0; + assign peri_rsp_out_ready[gx][gy][d] = 1'b1; + `ASSERT(PeriEdgeNoReq, !peri_req_out_valid[gx][gy][d], clk_i, !rst_ni, + "cachepool_cluster: narrow request lost at a mesh edge") + `ASSERT(PeriEdgeNoRsp, !peri_rsp_out_valid[gx][gy][d], clk_i, !rst_ni, + "cachepool_cluster: narrow response lost at a mesh edge") + end + end + end + end + // -------------------------------------------------- // North/South boundary: tied off (8ch) or hosts extra HBM chimneys for // more refill bandwidth (16ch). Assertion catches a mismatched mesh // shape/channel count. // -------------------------------------------------- + // NumHbmAllSides, HbmPerSide and L2PeriphChannel come from cachepool_pkg. localparam int unsigned NumHbmWestEast = 2 * NumGroupsY; - localparam int unsigned NumHbmAllSides = 2 * NumGroupsY + 2 * NumGroupsX; `ifndef TARGET_SYNTHESIS initial assert (NumL2Channel == NumHbmWestEast || NumL2Channel == NumHbmAllSides) else $fatal(1, "NumL2Channel must equal the West/East or all-sides chimney count."); @@ -608,17 +702,9 @@ module cachepool_cluster // group reaches its own-ID channel in 1 hop. All-sides uses the side-contiguous scheme // below instead. SAM and address scrambling are unaffected either way. localparam bit L2SameIdPlacement = (NumL2Channel == NumGroups); - // All-sides placement packs one aligned block of channels per mesh edge: - // four adjacent channels share one controller IP and cannot be split - // across edges. Order within a side follows the floorplan. - localparam int unsigned HbmPerSide = NumHbmAllSides / 4; - // The peripheral demux stays on the West chimney at gy=0 -- the corner - // next to group 0 -- so its channel ID follows the placement. - localparam int unsigned L2PeriphChannel = - (NumL2Channel == NumHbmAllSides) ? HbmPerSide : 0; - // The side-contiguous IDs below assume four channels per edge on a 4x4 - // mesh, so reject any other mesh shape asking for all-sides chimneys - // rather than mis-wiring it. + // All-sides placement: one aligned block of HbmPerSide channels per edge (one controller IP, + // in floorplan order). The side-contiguous IDs below assume a 4x4 mesh, so reject any other + // mesh shape asking for all-sides chimneys rather than mis-wiring it. `ifndef TARGET_SYNTHESIS initial assert (NumL2Channel != NumHbmAllSides || (NumGroupsX == 4 && NumGroupsY == 4)) else $fatal(1, "All-sides HBM placement is only implemented for a 4x4 group mesh."); @@ -656,7 +742,7 @@ module cachepool_cluster // -------------------------------------------------- // West HBM ejection points, one per mesh row: floo_tcdm_chimney (SbrPort, no router) // unpacks req flits -> reqrsp_to_axi -> DRAM and source-routes responses back. - // HBM0 also splits off peripheral traffic (reqrsp_xbar, see gen_hbm0_demux). + // HBM0 also serves the BootROM (reqrsp_xbar, see gen_hbm0_demux). // -------------------------------------------------- for (genvar gy = 0; gy < NumGroupsY; gy++) begin : gen_hbm_west @@ -713,27 +799,23 @@ module cachepool_cluster // Routing .id_i ( floo_cachepool_noc_pkg::id_t'(HbmEndpointId) ), .route_table_i ( floo_cachepool_noc_pkg::RoutingTables[HbmEndpointId]), - // Request flit from mesh West(3) edge (no router) - .floo_req_o ( ), - .floo_req_valid_o ( ), - .floo_req_ready_i ( 1'b1 ), + // Request flits on the mesh West(3) edge (no router). Without a manager port the + // chimney injects nothing and drains stray responses. + .floo_req_o ( l2_req_in[0][gy][3] ), + .floo_req_valid_o ( l2_req_in_valid[0][gy][3] ), + .floo_req_ready_i ( l2_req_in_ready[0][gy][3] ), .floo_req_i ( l2_req_out[0][gy][3] ), .floo_req_valid_i ( l2_req_out_valid[0][gy][3] ), .floo_req_ready_o ( l2_req_out_ready[0][gy][3] ), - // Response flit into mesh West(3) edge + // Response flits on the mesh West(3) edge .floo_rsp_o ( l2_rsp_in[0][gy][3] ), .floo_rsp_valid_o ( l2_rsp_in_valid[0][gy][3] ), .floo_rsp_ready_i ( l2_rsp_in_ready[0][gy][3] ), - .floo_rsp_i ( '0 ), - .floo_rsp_valid_i ( 1'b0 ), - .floo_rsp_ready_o ( ) + .floo_rsp_i ( l2_rsp_out[0][gy][3] ), + .floo_rsp_valid_i ( l2_rsp_out_valid[0][gy][3] ), + .floo_rsp_ready_o ( l2_rsp_out_ready[0][gy][3] ) ); - // Tie off: no requests injected from HBM, drain stray outgoing responses - assign l2_req_in [0][gy][3] = '0; - assign l2_req_in_valid [0][gy][3] = 1'b0; - assign l2_rsp_out_ready[0][gy][3] = 1'b1; - // Pack chimney sbr signals into REQRSP bundle cache_trans_req_t hbm_reqrsp_req; cache_trans_rsp_t hbm_reqrsp_rsp; @@ -747,18 +829,25 @@ module cachepool_cluster assign hbm_sbr_rsp = hbm_reqrsp_rsp.p; assign hbm_sbr_rsp_valid = hbm_reqrsp_rsp.p_valid; - // HBM0: split DRAM vs peripheral with reqrsp_xbar, not reqrsp_demux, whose shared in-order + // HBM0: split DRAM vs BootROM with reqrsp_xbar, not reqrsp_demux, whose shared in-order // ID FIFO capped outstanding refills. Cross-path reordering is safe (miss info is in - // refill_user_t); a reqrsp_to_axi per path keeps each user-field FIFO in order. + // refill_user_t). if (HbmIdx == L2PeriphChannel) begin : gen_hbm0_demux - // Address-based select: DRAM = port 0, Peripheral = port 1. The - // window is this chimney's own channel slice: under all-sides - // placement that is channel L2PeriphChannel, not channel 0. + // Select DRAM = port 0, BootROM = port 1. The DRAM window is this chimney's channel slice + // (channel L2PeriphChannel under all-sides placement); the interconnect carries scrambled + // addresses, revertAddr recovers the physical one. localparam int unsigned HbmChBase = DramAddr + HbmIdx * DramPerChSize; - logic hbm0_reqrsp_sel; + addr_t hbm0_pa; + logic hbm0_reqrsp_sel; + assign hbm0_pa = revertAddr(hbm_reqrsp_req.q.addr); assign hbm0_reqrsp_sel = !((hbm_reqrsp_req.q.addr >= HbmChBase) && - (hbm_reqrsp_req.q.addr < HbmChBase + DramPerChSize)); + (hbm_reqrsp_req.q.addr < HbmChBase + DramPerChSize)); + + // Everything outside the DRAM slice must be an iCache refill of the BootROM + `ASSERT(Hbm0BootromOnly, (hbm_reqrsp_req.q_valid && hbm0_reqrsp_sel) |-> + ((hbm0_pa >= BootAddr) && (hbm0_pa < BootAddr + BootromSize)), clk_i, !rst_ni, + $sformatf("cachepool_cluster: wide L2 access to unmapped address 0x%08x", hbm0_pa)) cache_trans_req_t [1:0] hbm0_demux_req; cache_trans_rsp_t [1:0] hbm0_demux_rsp; @@ -850,46 +939,54 @@ module cachepool_cluster assign wide_axi_slv_req[HbmIdx] = hbm0_dram_axi_req; assign hbm0_dram_axi_rsp = wide_axi_slv_rsp[HbmIdx]; + // Port 1: BootROM, one full-width word per iCache line refill + `REG_BUS_TYPEDEF_ALL(reg_bootrom, addr_t, data_cache_t, strb_cache_t) + + cache_trans_req_t hbm0_bootrom_req; + reg_bootrom_req_t bootrom_reg_req; + reg_bootrom_rsp_t bootrom_reg_rsp; - // Port 1: Non-DRAM — revert VA back to PA, then DW convert to 32b - // All paths through the interconnect use scrambled (VA) addresses. - // revertAddr recovers the physical address for the peripheral endpoint. - cache_trans_req_t hbm0_peri_reqrsp_req; always_comb begin - hbm0_peri_reqrsp_req = hbm0_demux_req[1]; - hbm0_peri_reqrsp_req.q.addr = revertAddr(hbm0_demux_req[1].q.addr); + hbm0_bootrom_req = hbm0_demux_req[1]; + hbm0_bootrom_req.q.addr = revertAddr(hbm0_demux_req[1].q.addr); end - cache_trans_rsp_t hbm0_peri_reqrsp_rsp; - assign hbm0_demux_rsp[1] = hbm0_peri_reqrsp_rsp; - - // W2N converter: 512b REQRSP → 32b REQRSP - // Same user type (refill_user_t) on both sides. - // We add a small 1-entry buffer with MSHR inside to reduce boot time - reqrsp_w2n_converter #( - .SlvDataWidth ( AxiDataWidth ), - .MstDataWidth ( 32 ), - .AddrWidth ( AxiAddrWidth ), - .SlvUserWidth ( $bits(refill_user_t) ), - .MstUserWidth ( $bits(refill_user_t) ), - .MaxTrans ( 4 ), - .EnRdBuf ( 1'b1 ), - .CacheableBase ( BootAddr ), - .CacheableSize ( 32'h1_0000 ), - .slv_req_t ( cache_trans_req_t ), - .slv_rsp_t ( cache_trans_rsp_t ), - .mst_req_t ( peri_narrow_req_t ), - .mst_rsp_t ( peri_narrow_rsp_t ), - .slv_user_t ( refill_user_t ) - ) i_hbm0_peri_dw ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .slv_req_i ( hbm0_peri_reqrsp_req ), - .slv_rsp_o ( hbm0_peri_reqrsp_rsp ), - .mst_req_o ( hbm0_peri_narrow_req ), - .mst_rsp_i ( hbm0_peri_narrow_rsp ) + reqrsp_to_reg #( + .AddrWidth ( AxiAddrWidth ), + .DataWidth ( AxiDataWidth ), + .UserWidth ( $bits(refill_user_t) ), + .ShiftResponse ( 1'b0 ), + .user_t ( refill_user_t ), + .reqrsp_req_t ( cache_trans_req_t ), + .reqrsp_rsp_t ( cache_trans_rsp_t ), + .reg_req_t ( reg_bootrom_req_t ), + .reg_rsp_t ( reg_bootrom_rsp_t ) + ) i_reqrsp_to_reg_bootrom ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .reqrsp_req_i ( hbm0_bootrom_req ), + .reqrsp_rsp_o ( hbm0_demux_rsp[1] ), + .reg_req_o ( bootrom_reg_req ), + .reg_rsp_i ( bootrom_reg_rsp ) ); + bootrom #( + .DataWidth ( AxiDataWidth ), + .AddrWidth ( AxiAddrWidth ) + ) i_bootrom ( + .clk_i ( clk_i ), + .req_i ( bootrom_reg_req.valid ), + .addr_i ( addr_t'(bootrom_reg_req.addr) ), + .rdata_o ( bootrom_reg_rsp.rdata ) + ); + // The ROM registers its address: ready one cycle after each new address, so + // back-to-back requests get a setup cycle each. + `FF(bootrom_reg_rsp.ready, bootrom_reg_req.valid & ~bootrom_reg_rsp.ready, 1'b0) + assign bootrom_reg_rsp.error = 1'b0; + + `ASSERT_INIT(CheckBootromWidth, BootromDataWidth == AxiDataWidth, + "cachepool_cluster: bootrom_data_width must equal the L2 refill width") + end else begin : gen_hbm_direct // Non-HBM0: single reqrsp_to_axi → DRAM axi_slv_cache_req_t hbm_axi_req; @@ -923,6 +1020,94 @@ module cachepool_cluster end end + // -------------------------------------------------- + // Narrow L2 mesh endpoint of the cluster peripheral, West of group (0,0) with HBM0's ID and + // routes. Subordinate port: core CSR/UART accesses; manager port: configuration batches, + // one register write per group. + // -------------------------------------------------- + + peri_noc_req_chan_t peri_mgr_req; + logic peri_mgr_req_valid; + logic peri_mgr_req_ready; + peri_narrow_rsp_chan_t peri_mgr_rsp; + logic peri_mgr_rsp_valid; + logic peri_mgr_rsp_ready; + + // Requests carry this endpoint's ID so the groups route their responses back here. + cachepool_group_cfg_sender #( + .req_chan_t ( peri_noc_req_chan_t ), + .rsp_chan_t ( peri_narrow_rsp_chan_t ), + .user_t ( peri_narrow_user_t ), + .ReqUser ( '{l2_src_id: floo_cachepool_noc_pkg::id_t'(PeriEndpointId), default: '0} ) + ) i_group_cfg_sender ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .start_i ( cfg_start ), + .reg_i ( cfg_reg ), + .data_i ( cfg_data ), + .tile_sel_i ( cfg_tile_sel ), + .done_o ( cfg_done ), + .req_o ( peri_mgr_req ), + .req_valid_o ( peri_mgr_req_valid ), + .req_ready_i ( peri_mgr_req_ready ), + .rsp_i ( peri_mgr_rsp ), + .rsp_valid_i ( peri_mgr_rsp_valid ), + .rsp_ready_o ( peri_mgr_rsp_ready ) + ); + + floo_tcdm_chimney #( + .RouteCfg ( PeriRouteCfg ), + .EnMgrPort ( 1'b1 ), + .EnSbrPort ( 1'b1 ), + .id_t ( floo_cachepool_noc_pkg::id_t ), + .route_t ( floo_cachepool_noc_pkg::route_t ), + .dst_t ( floo_cachepool_noc_pkg::route_t ), + .hdr_t ( l2_noc_hdr_t ), + .req_chan_t ( peri_noc_req_chan_t ), + .rsp_chan_t ( peri_narrow_rsp_chan_t ), + .floo_req_t ( peri_noc_req_t ), + .floo_rsp_t ( peri_noc_rsp_t ), + .addr_t ( axi_addr_t ), + .sam_rule_t ( floo_cachepool_noc_pkg::sam_rule_t ), + .Sam ( PeriSam ) + ) i_peri_chimney ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_enable_i ( 1'b0 ), + // Manager port: group configuration writes + .mgr_req_i ( peri_mgr_req ), + .mgr_req_valid_i ( peri_mgr_req_valid ), + .mgr_req_ready_o ( peri_mgr_req_ready ), + .mgr_rsp_o ( peri_mgr_rsp ), + .mgr_rsp_valid_o ( peri_mgr_rsp_valid ), + .mgr_rsp_ready_i ( peri_mgr_rsp_ready ), + // Subordinate port: core accesses to the peripheral xbar + .sbr_req_o ( peri_sbr_req ), + .sbr_req_valid_o ( peri_sbr_req_valid ), + .sbr_req_ready_i ( peri_sbr_req_ready ), + .sbr_rsp_i ( peri_sbr_rsp ), + .sbr_rsp_valid_i ( peri_sbr_rsp_valid ), + .sbr_rsp_ready_o ( peri_sbr_rsp_ready ), + .sbr_txn_id_i ( peri_sbr_rsp.user.l2_src_id ), + // Routing + .id_i ( floo_cachepool_noc_pkg::id_t'(PeriEndpointId) ), + .route_table_i ( floo_cachepool_noc_pkg::RoutingTables[PeriEndpointId] ), + // Request flits on the narrow mesh West(3) edge of group (0,0) + .floo_req_o ( peri_req_in[0][0][3] ), + .floo_req_valid_o ( peri_req_in_valid[0][0][3] ), + .floo_req_ready_i ( peri_req_in_ready[0][0][3] ), + .floo_req_i ( peri_req_out[0][0][3] ), + .floo_req_valid_i ( peri_req_out_valid[0][0][3] ), + .floo_req_ready_o ( peri_req_out_ready[0][0][3] ), + // Response flits on the narrow mesh West(3) edge of group (0,0) + .floo_rsp_o ( peri_rsp_in[0][0][3] ), + .floo_rsp_valid_o ( peri_rsp_in_valid[0][0][3] ), + .floo_rsp_ready_i ( peri_rsp_in_ready[0][0][3] ), + .floo_rsp_i ( peri_rsp_out[0][0][3] ), + .floo_rsp_valid_i ( peri_rsp_out_valid[0][0][3] ), + .floo_rsp_ready_o ( peri_rsp_out_ready[0][0][3] ) + ); + // -------------------------------------------------- // East HBM ejection points (one per mesh row, channel ID per HbmIdx below) // floo_tcdm_chimney (SbrPort, no router) at mesh edge. @@ -1318,26 +1503,26 @@ module cachepool_cluster // style-waive: COMMENT_BLOCK: peripheral fabric data path diagram // -------------------------------------------------- - // Peripheral Fabric (REQRSP-based 2×3 xbar) + // Peripheral Fabric (REQRSP-based 2×2 xbar) // -------------------------------------------------- // // Data path (all 32b REQRSP): - // Source [0]: HBM0 demux port 1 -> reqrsp_w2n_converter (512b-32b) -> pad src_id=0 - // Source [1]: peri_ext_req_i (TB/SoC, already 32b REQRSP) -> pad src_id=1 + // Source [0]: narrow L2 mesh (i_peri_chimney subordinate port) -> pad src_id=0 + // Source [1]: peri_ext_req_i (TB/SoC, already 32b REQRSP) -> pad src_id=1 // - // Target [0]: BootROM -> reqrsp_to_reg -> bootrom - // Target [1]: CSR -> reqrsp_to_reg -> cachepool_peripheral - // Target [2]: UART -> reqrsp_to_axi -> axi_narrow_req_o + // Target [0]: CSR -> reqrsp_to_reg -> cachepool_peripheral + // Target [1]: UART -> reqrsp_to_axi -> axi_narrow_req_o + // + // The BootROM is not reachable here: it sits on the wide mesh (HBM0 demux port 1). // // Response routing: mst_sel_i = mst_rsp.user.src_id (1-bit, 0=NoC, 1=TB) localparam int unsigned PeriXbarNumSrc = 2; - localparam int unsigned PeriXbarNumTgt = 3; + localparam int unsigned PeriXbarNumTgt = 2; typedef enum int unsigned { - PeriTgtBootROM = 0, - PeriTgtCSR = 1, - PeriTgtUART = 2 + PeriTgtCSR = 0, + PeriTgtUART = 1 } peri_tgt_e; // Calculate the peripheral base address (needed for xbar rules). @@ -1351,15 +1536,13 @@ module cachepool_cluster // style-waive: AUTOMATIC: boundary checks function automatic peri_tgt_sel_t peri_addr_decode(input addr_t addr); - if (addr >= BootAddr && addr < BootAddr + 32'h1_0000) - return peri_tgt_sel_t'(PeriTgtBootROM); - else if (addr >= UartAddr && addr < UartAddr + 32'h1_0000) + if (addr >= UartAddr && addr < UartAddr + 32'h1_0000) return peri_tgt_sel_t'(PeriTgtUART); else return peri_tgt_sel_t'(PeriTgtCSR); endfunction - // ---- Inline src_id packing: peri_narrow → peri_xbar (add 1-bit src_id) ---- + // ---- Inline src_id packing: narrow mesh / peri_narrow → peri_xbar (add 1-bit src_id) ---- peri_xbar_req_chan_t [PeriXbarNumSrc-1:0] peri_xbar_src_req; logic [PeriXbarNumSrc-1:0] peri_xbar_src_req_valid; @@ -1375,26 +1558,28 @@ module cachepool_cluster logic [PeriXbarNumTgt-1:0] peri_xbar_tgt_rsp_valid; logic [PeriXbarNumTgt-1:0] peri_xbar_tgt_rsp_ready; - // Source [0]: HBM0 (src_id = 0) + // Source [0]: narrow L2 mesh (src_id = 0). The flit has no AMO field. always_comb begin peri_xbar_src_req[0] = '0; - peri_xbar_src_req[0].addr = hbm0_peri_narrow_req.q.addr; - peri_xbar_src_req[0].write = hbm0_peri_narrow_req.q.write; - peri_xbar_src_req[0].amo = hbm0_peri_narrow_req.q.amo; - peri_xbar_src_req[0].data = hbm0_peri_narrow_req.q.data; - peri_xbar_src_req[0].strb = hbm0_peri_narrow_req.q.strb; - peri_xbar_src_req[0].size = hbm0_peri_narrow_req.q.size; - peri_xbar_src_req[0].user = '{src_id: 1'b0, refill: hbm0_peri_narrow_req.q.user}; + peri_xbar_src_req[0].addr = peri_sbr_req.addr; + peri_xbar_src_req[0].write = peri_sbr_req.write; + peri_xbar_src_req[0].amo = reqrsp_pkg::AMONone; + peri_xbar_src_req[0].data = peri_sbr_req.data; + peri_xbar_src_req[0].strb = peri_sbr_req.strb; + peri_xbar_src_req[0].size = peri_sbr_req.size; + peri_xbar_src_req[0].user = '{src_id: 1'b0, narrow: peri_sbr_req.user}; end - assign peri_xbar_src_req_valid[0] = hbm0_peri_narrow_req.q_valid; - assign peri_xbar_src_rsp_ready[0] = hbm0_peri_narrow_req.p_ready; - - assign hbm0_peri_narrow_rsp.q_ready = peri_xbar_src_req_ready[0]; - assign hbm0_peri_narrow_rsp.p.data = peri_xbar_src_rsp[0].data; - assign hbm0_peri_narrow_rsp.p.error = peri_xbar_src_rsp[0].error; - assign hbm0_peri_narrow_rsp.p.write = peri_xbar_src_rsp[0].write; - assign hbm0_peri_narrow_rsp.p.user = peri_xbar_src_rsp[0].user.refill; - assign hbm0_peri_narrow_rsp.p_valid = peri_xbar_src_rsp_valid[0]; + assign peri_xbar_src_req_valid[0] = peri_sbr_req_valid; + assign peri_xbar_src_rsp_ready[0] = peri_sbr_rsp_ready; + + assign peri_sbr_req_ready = peri_xbar_src_req_ready[0]; + assign peri_sbr_rsp = '{ + data: peri_xbar_src_rsp[0].data, + error: peri_xbar_src_rsp[0].error, + write: peri_xbar_src_rsp[0].write, + user: peri_xbar_src_rsp[0].user.narrow + }; + assign peri_sbr_rsp_valid = peri_xbar_src_rsp_valid[0]; // Source [1]: TB/SoC external (src_id = 1) always_comb begin @@ -1405,7 +1590,7 @@ module cachepool_cluster peri_xbar_src_req[1].data = peri_ext_req_i.q.data; peri_xbar_src_req[1].strb = peri_ext_req_i.q.strb; peri_xbar_src_req[1].size = peri_ext_req_i.q.size; - peri_xbar_src_req[1].user = '{src_id: 1'b1, refill: peri_ext_req_i.q.user}; + peri_xbar_src_req[1].user = '{src_id: 1'b1, narrow: peri_ext_req_i.q.user}; end assign peri_xbar_src_req_valid[1] = peri_ext_req_i.q_valid; assign peri_xbar_src_rsp_ready[1] = peri_ext_req_i.p_ready; @@ -1414,19 +1599,35 @@ module cachepool_cluster assign peri_ext_rsp_o.p.data = peri_xbar_src_rsp[1].data; assign peri_ext_rsp_o.p.error = peri_xbar_src_rsp[1].error; assign peri_ext_rsp_o.p.write = peri_xbar_src_rsp[1].write; - assign peri_ext_rsp_o.p.user = peri_xbar_src_rsp[1].user.refill; + assign peri_ext_rsp_o.p.user = peri_xbar_src_rsp[1].user.narrow; assign peri_ext_rsp_o.p_valid = peri_xbar_src_rsp_valid[1]; // ---- Address decode per source ---- peri_tgt_sel_t [PeriXbarNumSrc-1:0] peri_slv_sel; - assign peri_slv_sel[0] = peri_addr_decode(hbm0_peri_narrow_req.q.addr); + assign peri_slv_sel[0] = peri_addr_decode(peri_sbr_req.addr); assign peri_slv_sel[1] = peri_addr_decode(peri_ext_req_i.q.addr); + // Core accesses only reach the CSRs and the UART; anything else is a software bug (stray + // pointer) that would silently read 0 from the CSR target, so flag it with its source. + `ifndef TARGET_SYNTHESIS + logic peri_core_addr_ok; + assign peri_core_addr_ok = + ((peri_sbr_req.addr >= tcdm_end_address) && + (peri_sbr_req.addr < tcdm_end_address + ClusterPeriphSize * 1024)) || + ((peri_sbr_req.addr >= UartAddr) && + (peri_sbr_req.addr < UartAddr + 32'h1_0000)); + `ASSERT(PeriCoreAddrMapped, peri_sbr_req_valid |-> peri_core_addr_ok, clk_i, !rst_ni, + $sformatf("cachepool_cluster: core access to unmapped address 0x%08x from L2 endpoint %0d tile %0d hart %0d", + peri_sbr_req.addr, peri_sbr_req.user.l2_src_id, peri_sbr_req.user.tile_id, + peri_sbr_req.user.hart_id)) + `ASSERT(PeriSingleWord, peri_sbr_req_valid |-> (peri_sbr_req.size <= reqrsp_pkg::size_t'(2)), + clk_i, !rst_ni, "cachepool_cluster: peripheral access wider than 32 bits") + `endif + // ---- Response routing per target: use src_id from user field ---- peri_src_sel_t [PeriXbarNumTgt-1:0] peri_mst_sel; assign peri_mst_sel[0] = peri_xbar_tgt_rsp[0].user.src_id; assign peri_mst_sel[1] = peri_xbar_tgt_rsp[1].user.src_id; - assign peri_mst_sel[2] = peri_xbar_tgt_rsp[2].user.src_id; // ---- 2×3 REQRSP Xbar ---- reqrsp_xbar #( @@ -1458,57 +1659,7 @@ module cachepool_cluster .mst_sel_i ( peri_mst_sel ) ); - // ---- Target [0]: BootROM → reqrsp_to_reg → bootrom ---- - - `REG_BUS_TYPEDEF_ALL(reg_bootrom, addr_t, narrow_data_t, narrow_strb_t) - - peri_xbar_req_t peri_bootrom_req; - peri_xbar_rsp_t peri_bootrom_rsp; - - // Bundle xbar split channels into REQRSP struct - assign peri_bootrom_req.q = peri_xbar_tgt_req[PeriTgtBootROM]; - assign peri_bootrom_req.q_valid = peri_xbar_tgt_req_valid[PeriTgtBootROM]; - assign peri_bootrom_req.p_ready = peri_xbar_tgt_rsp_ready[PeriTgtBootROM]; - assign peri_xbar_tgt_req_ready[PeriTgtBootROM] = peri_bootrom_rsp.q_ready; - assign peri_xbar_tgt_rsp[PeriTgtBootROM] = peri_bootrom_rsp.p; - assign peri_xbar_tgt_rsp_valid[PeriTgtBootROM] = peri_bootrom_rsp.p_valid; - - reg_bootrom_req_t bootrom_reg_req; - reg_bootrom_rsp_t bootrom_reg_rsp; - - reqrsp_to_reg #( - .AddrWidth ( AxiAddrWidth ), - .DataWidth ( 32 ), - .UserWidth ( $bits(peri_xbar_user_t) ), - .FifoDepth ( 2 ), - .ShiftResponse( 1'b0 ), - .user_t ( peri_xbar_user_t ), - .reqrsp_req_t ( peri_xbar_req_t ), - .reqrsp_rsp_t ( peri_xbar_rsp_t ), - .reg_req_t ( reg_bootrom_req_t ), - .reg_rsp_t ( reg_bootrom_rsp_t ) - ) i_reqrsp_to_reg_bootrom ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .reqrsp_req_i ( peri_bootrom_req ), - .reqrsp_rsp_o ( peri_bootrom_rsp ), - .reg_req_o ( bootrom_reg_req ), - .reg_rsp_i ( bootrom_reg_rsp ) - ); - - bootrom #( - .DataWidth ( 32 ), - .AddrWidth ( AxiAddrWidth ) - ) i_bootrom ( - .clk_i ( clk_i ), - .req_i ( bootrom_reg_req.valid ), - .addr_i ( addr_t'(bootrom_reg_req.addr) ), - .rdata_o ( bootrom_reg_rsp.rdata ) - ); - `FF(bootrom_reg_rsp.ready, bootrom_reg_req.valid, 1'b0) - assign bootrom_reg_rsp.error = 1'b0; - - // ---- Target [1]: CSR → reqrsp_to_reg → cachepool_peripheral ---- + // ---- Target [0]: CSR → reqrsp_to_reg → cachepool_peripheral ---- `REG_BUS_TYPEDEF_ALL(reg_csr, addr_t, narrow_data_t, narrow_strb_t) @@ -1530,7 +1681,6 @@ module cachepool_cluster .AddrWidth ( AxiAddrWidth ), .DataWidth ( 32 ), .UserWidth ( $bits(peri_xbar_user_t) ), - .FifoDepth ( 2 ), .ShiftResponse( 1'b0 ), .user_t ( peri_xbar_user_t ), .reqrsp_req_t ( peri_xbar_req_t ), @@ -1554,8 +1704,7 @@ module cachepool_cluster .NumBarrierSlots ( NumBarrierSlots ), .PrivateWidth ( L1PrivWidth ), .reg_req_t ( reg_csr_req_t ), - .reg_rsp_t ( reg_csr_rsp_t ), - .cache_insn_t ( cache_insn_t ) + .reg_rsp_t ( reg_csr_rsp_t ) ) i_cachepool_cluster_peripheral ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), @@ -1564,21 +1713,19 @@ module cachepool_cluster .reg_rsp_o ( reg_rsp ), .tcdm_start_address_i ( tcdm_start_address ), .tcdm_end_address_i ( tcdm_end_address ), - .icache_prefetch_enable_o ( icache_prefetch_enable ), .cluster_hart_base_id_i ( HartBaseId ), .cluster_probe_o ( cluster_probe_o ), - .dynamic_offset_o ( dynamic_offset ), - .private_start_addr_o ( private_start_addr ), .l1d_spm_size_o ( ), - .l1d_private_o ( l1d_private ), - .l1d_insn_o ( l1d_insn ), - .l1d_insn_valid_o ( l1d_insn_valid ), - .l1d_insn_ready_i ( l1d_insn_ready ), .l1d_busy_o ( l1d_busy ), + .cfg_start_o ( cfg_start ), + .cfg_reg_o ( cfg_reg ), + .cfg_data_o ( cfg_data ), + .cfg_tile_sel_o ( cfg_tile_sel ), + .cfg_done_i ( cfg_done ), .barrier_part_mask_o ( barrier_part_mask ) ); - // ---- Target [2]: UART → reqrsp_to_axi → axi_narrow_req_o ---- + // ---- Target [1]: UART → reqrsp_to_axi → axi_narrow_req_o ---- peri_xbar_req_t peri_uart_req; peri_xbar_rsp_t peri_uart_rsp; diff --git a/hardware/src/cachepool_group.sv b/hardware/src/cachepool_group.sv index ccde038b..e95ccc81 100644 --- a/hardware/src/cachepool_group.sv +++ b/hardware/src/cachepool_group.sv @@ -6,6 +6,7 @@ `include "axi/typedef.svh" `include "common_cells/registers.svh" +`include "common_cells/assertions.svh" `include "register_interface/typedef.svh" /// Group implementation for CachePool @@ -136,9 +137,12 @@ module cachepool_group input axi_addr_t cluster_base_addr_i, /// Partitioning address input axi_addr_t private_start_addr_i, - /// L2 refill reqrsp port (single merged output: cache refill + iCache/peripheral) + /// L2 refill reqrsp port (single merged output: cache refill + iCache) output l2_req_t l2_req_o, input l2_rsp_t l2_rsp_i, + /// Narrow peripheral reqrsp port (single merged output of the tiles' core accesses) + output peri_narrow_req_t periph_req_o, + input peri_narrow_rsp_t periph_rsp_i, /// This group's L2 refill mesh endpoint ID, stamped into refill_user_t.l2_src_id so the /// HBM chimney routes responses back without an in-order src_id FIFO. input floo_cachepool_noc_pkg::id_t l2_group_id_i, @@ -268,8 +272,8 @@ module cachepool_group cache_trans_rsp_t [NumL1CacheCtrlLocal-1:0] cache_refill_rsp; // Per-tile peripheral REQRSP (narrow 32b, from tile core mux) - periph_req_t [NumTilesPerGroup-1:0] tile_periph_req; - periph_rsp_t [NumTilesPerGroup-1:0] tile_periph_rsp; + peri_narrow_req_t [NumTilesPerGroup-1:0] tile_periph_req; + peri_narrow_rsp_t [NumTilesPerGroup-1:0] tile_periph_rsp; // L2 Group ICache AXI master output (from axi_hier_interco) axi_mst_cache_req_t axi_l2icache_mst_req; @@ -364,12 +368,11 @@ module cachepool_group // style-waive: COMMENT_BLOCK: port layout table // --------------------- - // Wiring: assemble flat xbar input from refill + peripheral + iCache paths + // Wiring: assemble flat xbar input from refill + iCache paths // --------------------- - // Port layout per tile (NumClusterMst = NumL1CtrlTile + 2): + // Port layout per tile (NumClusterMst = NumL1CtrlTile + 1): // p = 0..NumL1CtrlTile-1 → refill (bank_id = p, set by tile) - // p = NumL1CtrlTile → peripheral (bank_id = BankIdPeriph) - // p = NumL1CtrlTile+1 → iCache (bank_id = BankIdICache, only tile 0) + // p = NumL1CtrlTile → iCache (bank_id = BankIdICache, only tile 0) always_comb begin for (int t = 0; t < NumTilesPerGroup; t++) begin for (int p = 0; p < NumClusterMst; p++) begin @@ -389,32 +392,6 @@ module cachepool_group tile_req_chan [xbar_idx].user.tile_id = t; tile_req_chan [xbar_idx].user.l2_src_id = l2_group_id_i; - end else if (p == BankIdPeriph) begin - // Peripheral path: narrow REQRSP directly connected (data width mismatch — placeholder) - tile_req_chan [xbar_idx] = '0; - tile_req_chan [xbar_idx].addr = scrambleAddr(tile_periph_req[t].q.addr); - tile_req_chan [xbar_idx].write = tile_periph_req[t].q.write; - tile_req_chan [xbar_idx].amo = tile_periph_req[t].q.amo; - tile_req_chan [xbar_idx].size = tile_periph_req[t].q.size; - tile_req_chan [xbar_idx].data = axi_wide_data_t'(tile_periph_req[t].q.data); - tile_req_chan [xbar_idx].strb = axi_wide_strb_t'(tile_periph_req[t].q.strb); - tile_req_chan [xbar_idx].user.tile_id = t; - tile_req_chan [xbar_idx].user.bank_id = BankIdPeriph; - tile_req_chan [xbar_idx].user.l2_src_id = l2_group_id_i; - // Borrow the info field to carry req_id through the mesh - tile_req_chan [xbar_idx].user.info = cache_info_t'(tile_periph_req[t].q.user.req_id); - tile_req_valid [xbar_idx] = tile_periph_req[t].q_valid; - tile_periph_rsp[t].q_ready = tile_req_ready[xbar_idx]; - - tile_periph_rsp[t].p.data = tile_rsp_chan[xbar_idx].data[31:0]; - tile_periph_rsp[t].p.error = tile_rsp_chan[xbar_idx].error; - tile_periph_rsp[t].p.write = tile_rsp_chan[xbar_idx].write; - // Restore req_id from the info field; zero other tcdm_user fields - tile_periph_rsp[t].p.user = '0; - tile_periph_rsp[t].p.user.req_id = reqid_t'(tile_rsp_chan[xbar_idx].user.info); - tile_periph_rsp[t].p_valid = tile_rsp_valid[xbar_idx]; - tile_rsp_ready [xbar_idx] = tile_periph_req[t].p_ready; - end else begin // iCache path: only tile 0 active if (t == 0) begin @@ -513,6 +490,67 @@ module cachepool_group end + // --------------------- + // Peripheral mux: tiles' narrow core accesses -> one narrow port. Each request is stamped with + // its tile and this group's endpoint ID; responses are routed back by user.tile_id. + // --------------------- + typedef logic [cf_math_pkg::idx_width(NumTilesPerGroup)-1:0] periph_tile_sel_t; + + peri_narrow_req_chan_t [NumTilesPerGroup-1:0] periph_tile_req; + logic [NumTilesPerGroup-1:0] periph_tile_req_valid, periph_tile_req_ready; + peri_narrow_rsp_chan_t [NumTilesPerGroup-1:0] periph_tile_rsp; + logic [NumTilesPerGroup-1:0] periph_tile_rsp_valid, periph_tile_rsp_ready; + periph_tile_sel_t periph_rsp_sel; + + for (genvar t = 0; t < NumTilesPerGroup; t++) begin : gen_periph_tile + always_comb begin + periph_tile_req[t] = tile_periph_req[t].q; + periph_tile_req[t].user.tile_id = t; + periph_tile_req[t].user.l2_src_id = l2_group_id_i; + end + assign periph_tile_req_valid[t] = tile_periph_req[t].q_valid; + assign periph_tile_rsp_ready[t] = tile_periph_req[t].p_ready; + assign tile_periph_rsp[t].q_ready = periph_tile_req_ready[t]; + assign tile_periph_rsp[t].p = periph_tile_rsp[t]; + assign tile_periph_rsp[t].p_valid = periph_tile_rsp_valid[t]; + end + + assign periph_rsp_sel = periph_rsp_i.p.user.tile_id; + + reqrsp_xbar #( + .NumInp ( NumTilesPerGroup ), + .NumOut ( 1 ), + .PipeReg ( 1'b1 ), + .tcdm_req_chan_t ( peri_narrow_req_chan_t ), + .tcdm_rsp_chan_t ( peri_narrow_rsp_chan_t ), + .mst_sel_t ( periph_tile_sel_t ) + ) i_periph_xbar ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .slv_req_i ( periph_tile_req ), + .slv_rr_i ( '0 ), + .slv_req_valid_i ( periph_tile_req_valid ), + .slv_req_ready_o ( periph_tile_req_ready ), + .slv_rsp_o ( periph_tile_rsp ), + .slv_rsp_valid_o ( periph_tile_rsp_valid ), + .slv_rsp_ready_i ( periph_tile_rsp_ready ), + .slv_sel_i ( '0 ), + .slv_selected_o ( /* unused */ ), + .mst_req_o ( periph_req_o.q ), + .mst_req_valid_o ( periph_req_o.q_valid ), + .mst_req_ready_i ( periph_rsp_i.q_ready ), + .mst_rsp_i ( periph_rsp_i.p ), + .mst_rr_i ( '0 ), + .mst_rsp_valid_i ( periph_rsp_i.p_valid ), + .mst_rsp_ready_o ( periph_req_o.p_ready ), + .mst_sel_i ( periph_rsp_sel ) + ); + + // The narrow L2 mesh carries no AMO field: atomics to the peripherals are a software bug. + `ASSERT(PeriphNoAmo, periph_req_o.q_valid |-> (periph_req_o.q.amo == reqrsp_pkg::AMONone), + clk_i, !rst_ni, $sformatf("cachepool_group: atomic to peripheral address 0x%08x", + periph_req_o.q.addr)) + // Tile remote access signals // In/Out relative to the tile (out--leave a tile; in--enter a tile) // Tile-side flat layout: index = j + r*NrTCDMPortsPerCore (j=xbar idx, r=remote slot within xbar) diff --git a/hardware/src/cachepool_group_cfg.sv b/hardware/src/cachepool_group_cfg.sv new file mode 100644 index 00000000..b236630d --- /dev/null +++ b/hardware/src/cachepool_group_cfg.sv @@ -0,0 +1,141 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" +`include "common_cells/assertions.svh" + +/// style-waive: COMMENT_BLOCK: function description +/// Group-level configuration register. Receives single-register writes (group_cfg_reg_e) from the +/// cluster peripheral over the narrow L2 mesh (subordinate port of the group chimney) and drives +/// the L1D/xbar configuration of this group's tiles. A GroupCfgL1dInsn write starts its L1D +/// instruction in the selected tiles and is only answered once all of them have finished; any +/// other write is answered at once. The request is always accepted right away, so a pending flush +/// never stalls the mesh. +module cachepool_group_cfg + import cachepool_pkg::*; +#( + parameter type req_chan_t = logic, + parameter type rsp_chan_t = logic, + parameter type user_t = logic +) ( + input logic clk_i, + input logic rst_ni, + // Subordinate port from the L2 mesh chimney + input req_chan_t req_i, + input logic req_valid_i, + output logic req_ready_o, + output rsp_chan_t rsp_o, + output logic rsp_valid_o, + input logic rsp_ready_i, + // Configuration of the group's tiles + output xbar_offset_t dynamic_offset_o, + output logic [L1PrivWidth-1:0] l1d_private_o, + output axi_addr_t private_start_addr_o, + output logic icache_prefetch_enable_o, + output cache_insn_t l1d_insn_o, + output logic l1d_insn_valid_o, + input logic [NumTilesPerGroup-1:0] l1d_insn_ready_i +); + + // style-waive: COMMENT_BLOCK: FSM transition table + // Idle -(GroupCfgL1dInsn write)-> Flush -(selected tiles done)-> Resp -(rsp_ready)-> Idle + // Idle -(other write)----------------------------------------> Resp + typedef enum logic [1:0] { + Idle, + Flush, + Resp + } state_e; + + state_e state_d, state_q; + l1d_cfg_t cfg_d, cfg_q; + cache_insn_t insn_d, insn_q; + logic insn_valid_d, insn_valid_q; + // Tiles that still have to finish the current instruction + logic [NumTilesPerGroup-1:0] lock_d, lock_q; + user_t user_d, user_q; + + // Register index and value of the incoming write + group_cfg_reg_e req_reg; + logic [31:0] req_data; + cache_insn_t req_insn; + assign req_reg = group_cfg_reg_e'(req_i.addr[2 +: $bits(group_cfg_reg_e)]); + assign req_data = req_i.data[31:0]; + assign req_insn = req_data[$bits(cache_insn_t)-1:0]; + + always_comb begin : cfg_fsm + state_d = state_q; + cfg_d = cfg_q; + insn_d = insn_q; + insn_valid_d = 1'b0; + lock_d = lock_q & ~l1d_insn_ready_i; + user_d = user_q; + req_ready_o = 1'b0; + rsp_valid_o = 1'b0; + + unique case (state_q) + Idle: begin + req_ready_o = 1'b1; + if (req_valid_i) begin + user_d = req_i.user; + state_d = Resp; + unique case (req_reg) + GroupCfgXbarOffset: cfg_d.xbar_offset = req_data[$bits(xbar_offset_t)-1:0]; + GroupCfgL1dPrivate: cfg_d.l1d_private = req_data[L1PrivWidth-1:0]; + GroupCfgL1dAddr: cfg_d.private_addr = req_data; + GroupCfgPrefetch: cfg_d.prefetch_en = req_data[0]; + GroupCfgL1dInsn: begin + insn_d = req_insn; + insn_valid_d = 1'b1; + // Only private flushes target a subset of the tiles + lock_d = (req_insn.insn == 2'b00) ? req_insn.tile_sel : '1; + state_d = Flush; + end + default: ; + endcase + end + end + Flush: begin + if (lock_q == '0) state_d = Resp; + end + Resp: begin + rsp_valid_o = 1'b1; + if (rsp_ready_i) state_d = Idle; + end + default: state_d = Idle; + endcase + end + + always_comb begin : rsp_pack + rsp_o = '0; + rsp_o.write = 1'b1; + rsp_o.user = user_q; + end + + `FF(state_q, state_d, Idle, clk_i, rst_ni) + `FF(cfg_q, cfg_d, L1dCfgRst, clk_i, rst_ni) + `FF(insn_q, insn_d, '0, clk_i, rst_ni) + `FF(insn_valid_q, insn_valid_d, 1'b0, clk_i, rst_ni) + `FF(lock_q, lock_d, '0, clk_i, rst_ni) + `FF(user_q, user_d, '0, clk_i, rst_ni) + + assign dynamic_offset_o = cfg_q.xbar_offset; + assign l1d_private_o = cfg_q.l1d_private; + assign private_start_addr_o = cfg_q.private_addr; + assign icache_prefetch_enable_o = cfg_q.prefetch_en; + assign l1d_insn_o = insn_q; + assign l1d_insn_valid_o = insn_valid_q; + + `ASSERT_INIT(CheckRegsFitStride, GroupCfgStride >= 4 * (2 ** $bits(group_cfg_reg_e)), + "cachepool_group_cfg: GroupCfgStride is too small for the register block") + `ASSERT_INIT(CheckInsnFitsWord, $bits(cache_insn_t) <= 32, + "cachepool_group_cfg: cache_insn_t does not fit into one 32-bit register") + `ASSERT(OnlyCfgWrites, req_valid_i |-> req_i.write, clk_i, !rst_ni, + "cachepool_group_cfg: only writes from the cluster peripheral are expected") + `ASSERT(CfgAddrInRange, req_valid_i |-> (req_i.addr >= GroupCfgBase), clk_i, !rst_ni, + "cachepool_group_cfg: request address below GroupCfgBase") + `ASSERT(KnownCfgReg, req_valid_i |-> (req_reg inside {GroupCfgXbarOffset, GroupCfgL1dPrivate, + GroupCfgL1dAddr, GroupCfgPrefetch, GroupCfgL1dInsn}), clk_i, !rst_ni, + "cachepool_group_cfg: write to an unknown register") + +endmodule diff --git a/hardware/src/cachepool_group_cfg_sender.sv b/hardware/src/cachepool_group_cfg_sender.sv new file mode 100644 index 00000000..85714fe2 --- /dev/null +++ b/hardware/src/cachepool_group_cfg_sender.sv @@ -0,0 +1,127 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" +`include "common_cells/assertions.svh" + +/// style-waive: COMMENT_BLOCK: function description +/// Forwards one configuration batch of the cluster peripheral, a single group-level register, to +/// every group-level configuration register (cachepool_group_cfg) as sequential unicast writes +/// over the narrow L2 mesh, and pulses done_o once all NumGroups responses are back. Group g is written +/// at GroupCfgBase + g * GroupCfgStride + 4 * reg_i; an L1D instruction carries the group's own +/// tile_sel slice. The batch inputs must stay stable from start_i until done_o (the peripheral +/// blocks new batches meanwhile). +module cachepool_group_cfg_sender + import cachepool_pkg::*; +#( + parameter type req_chan_t = logic, + parameter type rsp_chan_t = logic, + parameter type user_t = logic, + /// User field of every request; carries this endpoint's l2_src_id for response routing + parameter user_t ReqUser = '0 +) ( + input logic clk_i, + input logic rst_ni, + // Batch from the cluster peripheral + input logic start_i, + input group_cfg_reg_e reg_i, + input logic [31:0] data_i, + input logic [NumTiles-1:0] tile_sel_i, + output logic done_o, + // Manager port into the L2 mesh chimney + output req_chan_t req_o, + output logic req_valid_o, + input logic req_ready_i, + input rsp_chan_t rsp_i, + input logic rsp_valid_i, + output logic rsp_ready_o +); + + localparam int unsigned CntWidth = $clog2(NumGroups + 1); + typedef logic [CntWidth-1:0] cnt_t; + + logic busy_d, busy_q; + cnt_t send_cnt_d, send_cnt_q; + cnt_t rsp_cnt_d, rsp_cnt_q; + axi_addr_t addr_d, addr_q; + // Tile selection of the remaining groups; the current group's slice sits at the LSBs + logic [NumTiles-1:0] tile_sel_d, tile_sel_q; + + logic req_fire; + cache_insn_t group_insn; + logic [31:0] group_data; + + assign req_valid_o = busy_q && (send_cnt_q != NumGroups); + assign req_fire = req_valid_o && req_ready_i; + assign rsp_ready_o = 1'b1; + + // An L1D instruction carries the tile selection of the group it is sent to + always_comb begin : group_data_pack + group_insn.insn = data_i[1:0]; + group_insn.tile_sel = tile_sel_q[NumTilesPerGroup-1:0]; + group_data = data_i; + if (reg_i == GroupCfgL1dInsn) begin + group_data = '0; + group_data[$bits(cache_insn_t)-1:0] = group_insn; + end + end + + always_comb begin : req_pack + req_o = '0; + req_o.addr = addr_q; + req_o.write = 1'b1; + req_o.data[31:0] = group_data; + req_o.strb = '1; + req_o.user = ReqUser; + end + + always_comb begin : batch_ctrl + busy_d = busy_q; + send_cnt_d = send_cnt_q; + rsp_cnt_d = rsp_cnt_q; + addr_d = addr_q; + tile_sel_d = tile_sel_q; + done_o = 1'b0; + + if (!busy_q) begin + if (start_i) begin + busy_d = 1'b1; + send_cnt_d = '0; + rsp_cnt_d = '0; + addr_d = GroupCfgBase + {reg_i, 2'b00}; + tile_sel_d = tile_sel_i; + end + end else begin + if (req_fire) begin + send_cnt_d = send_cnt_q + 1'b1; + addr_d = addr_q + GroupCfgStride; + tile_sel_d = tile_sel_q >> NumTilesPerGroup; + end + if (rsp_valid_i) begin + rsp_cnt_d = rsp_cnt_q + 1'b1; + end + if (rsp_cnt_q == NumGroups) begin + busy_d = 1'b0; + done_o = 1'b1; + end + end + end + + `FF(busy_q, busy_d, 1'b0, clk_i, rst_ni) + `FF(send_cnt_q, send_cnt_d, '0, clk_i, rst_ni) + `FF(rsp_cnt_q, rsp_cnt_d, '0, clk_i, rst_ni) + `FF(addr_q, addr_d, '0, clk_i, rst_ni) + `FF(tile_sel_q, tile_sel_d, '0, clk_i, rst_ni) + + `ASSERT_INIT(CheckNumGroups, NumGroups > 1, "cachepool_group_cfg_sender: NumGroups must be > 1") + `ASSERT_INIT(CheckInsnFitsWord, $bits(cache_insn_t) <= 32, + "cachepool_group_cfg_sender: cache_insn_t does not fit into one 32-bit register") + `ASSERT(NoRspWhenIdle, rsp_valid_i |-> busy_q, clk_i, !rst_ni, + "cachepool_group_cfg_sender: response without an outstanding batch") + `ASSERT(NoExtraRsp, rsp_valid_i |-> (rsp_cnt_q < NumGroups), clk_i, !rst_ni, + "cachepool_group_cfg_sender: more responses than groups") + `ASSERT(NoStartWhenBusy, start_i |-> !busy_q, clk_i, !rst_ni, + "cachepool_group_cfg_sender: new batch while the previous one is in flight") + +endmodule diff --git a/hardware/src/cachepool_group_noc_wrapper.sv b/hardware/src/cachepool_group_noc_wrapper.sv index 9f881660..00bf276c 100644 --- a/hardware/src/cachepool_group_noc_wrapper.sv +++ b/hardware/src/cachepool_group_noc_wrapper.sv @@ -10,6 +10,7 @@ `include "common_cells/registers.svh" +`include "common_cells/assertions.svh" `include "axi/typedef.svh" module cachepool_group_noc_wrapper @@ -75,7 +76,6 @@ module cachepool_group_noc_wrapper input logic [9:0] hart_base_id_i, input logic [TileIDWidth-1:0] tile_base_id_i, input axi_addr_t cluster_base_addr_i, - input axi_addr_t private_start_addr_i, // L2 refill TCDM mesh: 4 directions (North=0, East=1, South=2, West=3) output l2_noc_req_t [3:0] l2_req_o, output logic [3:0] l2_req_valid_o, @@ -89,16 +89,25 @@ module cachepool_group_noc_wrapper input l2_noc_rsp_t [3:0] l2_rsp_i, input logic [3:0] l2_rsp_valid_i, output logic [3:0] l2_rsp_ready_o, - // L2 mesh: endpoint ID and source-routing table from floogen + // Narrow L2 mesh (peripheral accesses, group configuration): same 4 directions + output peri_noc_req_t [3:0] peri_req_o, + output logic [3:0] peri_req_valid_o, + input logic [3:0] peri_req_ready_i, + input peri_noc_req_t [3:0] peri_req_i, + input logic [3:0] peri_req_valid_i, + output logic [3:0] peri_req_ready_o, + output peri_noc_rsp_t [3:0] peri_rsp_o, + output logic [3:0] peri_rsp_valid_o, + input logic [3:0] peri_rsp_ready_i, + input peri_noc_rsp_t [3:0] peri_rsp_i, + input logic [3:0] peri_rsp_valid_i, + output logic [3:0] peri_rsp_ready_o, + // L2 mesh (wide and narrow): endpoint ID and source-routing table from floogen input floo_cachepool_noc_pkg::id_t l2_id_i, input floo_cachepool_noc_pkg::route_t [floo_cachepool_noc_pkg::RouteCfg.NumRoutes-1:0] l2_route_table_i, output icache_l1_events_t [NumCC-1:0] icache_events_o, - input logic icache_prefetch_enable_i, - input logic [$clog2(AxiAddrWidth)-1:0] dynamic_offset_i, - input logic [L1PrivWidth-1:0] l1d_private_i, - input cache_insn_t l1d_insn_i, - input logic l1d_insn_valid_i, - output logic [NumTilesPerGroup-1:0] l1d_insn_ready_o, + // Flush interlock from the cluster peripheral; the rest of the L1D/xbar configuration + // arrives over the narrow L2 mesh at the group configuration register. input logic [NumTilesPerGroup-1:0] l1d_busy_i, input impl_in_t [NrSramCfg-1:0] impl_i, output logic error_o, @@ -156,32 +165,24 @@ module cachepool_group_noc_wrapper remote_group_rsp_t [NumRemoteGroupPortGroup-1:0] remote_group_rsp_to_group; - // Input cuts on some registered signals. The barrier link is not cut + // Input cut on the flush interlock. The barrier link is not cut // here -- it is optionally cut inside cachepool_group itself, controlled // by RegisterBarrier, on top of the group_barrier FSM's own registered state. - logic [$clog2(AxiAddrWidth)-1:0] dynamic_offset_d, dynamic_offset_q; - logic [L1PrivWidth-1:0] l1d_private_d, l1d_private_q; - cache_insn_t l1d_insn_d, l1d_insn_q; - logic l1d_insn_valid_d, l1d_insn_valid_q; - logic [NumTilesPerGroup-1:0] l1d_insn_ready_d, l1d_insn_ready_q; logic [NumTilesPerGroup-1:0] l1d_busy_d, l1d_busy_q; - axi_addr_t private_start_addr_d, private_start_addr_q; - `FF(dynamic_offset_q, dynamic_offset_d, '0, clk_i, rst_ni) - `FF(l1d_private_q, l1d_private_d, '0, clk_i, rst_ni) - `FF(l1d_insn_q, l1d_insn_d, '0, clk_i, rst_ni) - `FF(l1d_insn_valid_q, l1d_insn_valid_d, '0, clk_i, rst_ni) - `FF(l1d_insn_ready_q, l1d_insn_ready_d, '0, clk_i, rst_ni) `FF(l1d_busy_q, l1d_busy_d, '0, clk_i, rst_ni) - `FF(private_start_addr_q, private_start_addr_d, '0, clk_i, rst_ni) - assign dynamic_offset_d = dynamic_offset_i; - assign l1d_private_d = l1d_private_i; - assign l1d_insn_d = l1d_insn_i; - assign l1d_insn_valid_d = l1d_insn_valid_i; - assign l1d_insn_ready_o = l1d_insn_ready_q; assign l1d_busy_d = l1d_busy_i; - assign private_start_addr_d = private_start_addr_i; + + // L1D/xbar configuration of the tiles, held by the group configuration register (i_group_cfg) + // and written by the cluster peripheral over the narrow L2 mesh. + xbar_offset_t dynamic_offset_q; + logic [L1PrivWidth-1:0] l1d_private_q; + axi_addr_t private_start_addr_q; + logic icache_prefetch_enable; + cache_insn_t l1d_insn_q; + logic l1d_insn_valid_q; + logic [NumTilesPerGroup-1:0] l1d_insn_ready; // ------------------------------------------------------------------------- // Mesh signals [tile][ch][dir=3:0] and transposition to/from ports @@ -567,8 +568,8 @@ module cachepool_group_noc_wrapper // ------------------------------------------------------------------------- // L2 path: floo_tcdm_chimney (MgrPort) + floo_router (source routing) // ------------------------------------------------------------------------- - // The chimney maps addresses to destination IDs (SAM), looks up source routes and - // packs/unpacks flits; two floo_routers (req + rsp) form the 5-port source-routed mesh. + // Chimney: address -> destination ID (SAM) -> source route; two floo_routers form the mesh. + // It only carries the group's own refills (no subordinate port); peripherals use the narrow mesh. // Internal L2 reqrsp between group and chimney l2_req_t l2_group_req; @@ -589,7 +590,7 @@ module cachepool_group_noc_wrapper logic [floo_pkg::Eject:floo_pkg::North] l2_router_rsp_valid_out; logic [floo_pkg::Eject:floo_pkg::North] l2_router_rsp_ready_in; - // Manager chimney: group REQRSP ↔ flit inject/eject at router Eject port + // Chimney at the router Eject port: manager for the group's REQRSP. floo_tcdm_chimney #( .RouteCfg ( floo_cachepool_noc_pkg::RouteCfg ), .EnMgrPort ( 1'b1 ), @@ -631,11 +632,11 @@ module cachepool_group_noc_wrapper .floo_req_o ( l2_router_req_in[floo_pkg::Eject] ), .floo_req_valid_o ( l2_router_req_valid_in[floo_pkg::Eject] ), .floo_req_ready_i ( l2_router_req_ready_out[floo_pkg::Eject] ), - // Request flit: eject from request router (unused for MgrPort) + // Request flit: eject from request router (none expected, drained) .floo_req_i ( l2_router_req_out[floo_pkg::Eject] ), .floo_req_valid_i ( l2_router_req_valid_out[floo_pkg::Eject] ), .floo_req_ready_o ( l2_router_req_ready_in[floo_pkg::Eject] ), - // Response flit: inject into response router (unused for MgrPort) + // Response flit: no subordinate port, nothing injected .floo_rsp_o ( l2_router_rsp_in[floo_pkg::Eject] ), .floo_rsp_valid_o ( l2_router_rsp_valid_in[floo_pkg::Eject] ), .floo_rsp_ready_i ( l2_router_rsp_ready_out[floo_pkg::Eject] ), @@ -645,6 +646,9 @@ module cachepool_group_noc_wrapper .floo_rsp_ready_o ( l2_router_rsp_ready_in[floo_pkg::Eject] ) ); + `ASSERT(L2NoReqEject, !l2_router_req_valid_out[floo_pkg::Eject], clk_i, !rst_ni, + "cachepool_group_noc_wrapper: request ejected on the wide L2 mesh") + // Request router (source routing, 5 ports: N/E/S/W/Eject) floo_router #( .NumRoutes ( 5 ), @@ -724,6 +728,204 @@ module cachepool_group_noc_wrapper assign l2_rsp_ready_o[d] = l2_router_rsp_ready_out[d]; end + // ------------------------------------------------------------------------- + // Narrow L2 path: floo_tcdm_chimney (MgrPort + SbrPort) + floo_router pair + // ------------------------------------------------------------------------- + // Same topology, IDs and routes as the wide mesh. Manager port: the group's core accesses to + // the cluster peripheral; subordinate port: its writes to the group configuration register. + + // Narrow reqrsp between group and chimney + peri_narrow_req_t periph_group_req; + peri_narrow_rsp_t periph_group_rsp; + peri_noc_req_chan_t periph_mgr_req; + + // Router port signals [Eject:North] = [4:0] + peri_noc_req_t [floo_pkg::Eject:floo_pkg::North] peri_router_req_in; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_req_valid_in; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_req_ready_out; + peri_noc_req_t [floo_pkg::Eject:floo_pkg::North] peri_router_req_out; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_req_valid_out; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_req_ready_in; + + peri_noc_rsp_t [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_in; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_valid_in; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_ready_out; + peri_noc_rsp_t [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_out; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_valid_out; + logic [floo_pkg::Eject:floo_pkg::North] peri_router_rsp_ready_in; + + // Group configuration register on the chimney's subordinate port + peri_noc_req_chan_t cfg_sbr_req; + logic cfg_sbr_req_valid; + logic cfg_sbr_req_ready; + peri_narrow_rsp_chan_t cfg_sbr_rsp; + logic cfg_sbr_rsp_valid; + logic cfg_sbr_rsp_ready; + + // The flit has no AMO field (checked in cachepool_group) + assign periph_mgr_req = '{ + addr: periph_group_req.q.addr, + write: periph_group_req.q.write, + data: periph_group_req.q.data, + strb: periph_group_req.q.strb, + size: periph_group_req.q.size, + user: periph_group_req.q.user + }; + + floo_tcdm_chimney #( + .RouteCfg ( PeriCoreRouteCfg ), + .EnMgrPort ( 1'b1 ), + .EnSbrPort ( 1'b1 ), + .id_t ( floo_cachepool_noc_pkg::id_t ), + .route_t ( floo_cachepool_noc_pkg::route_t ), + .dst_t ( floo_cachepool_noc_pkg::route_t ), + .hdr_t ( l2_noc_hdr_t ), + .req_chan_t ( peri_noc_req_chan_t ), + .rsp_chan_t ( peri_narrow_rsp_chan_t ), + .floo_req_t ( peri_noc_req_t ), + .floo_rsp_t ( peri_noc_rsp_t ), + .addr_t ( axi_addr_t ), + .sam_rule_t ( floo_cachepool_noc_pkg::sam_rule_t ), + .Sam ( PeriCoreSam ) + ) i_peri_chimney ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_enable_i ( 1'b0 ), + // Manager port: group core accesses + .mgr_req_i ( periph_mgr_req ), + .mgr_req_valid_i ( periph_group_req.q_valid ), + .mgr_req_ready_o ( periph_group_rsp.q_ready ), + .mgr_rsp_o ( periph_group_rsp.p ), + .mgr_rsp_valid_o ( periph_group_rsp.p_valid ), + .mgr_rsp_ready_i ( periph_group_req.p_ready ), + // Subordinate port: group configuration register, responses return to the requester + .sbr_req_o ( cfg_sbr_req ), + .sbr_req_valid_o ( cfg_sbr_req_valid ), + .sbr_req_ready_i ( cfg_sbr_req_ready ), + .sbr_rsp_i ( cfg_sbr_rsp ), + .sbr_rsp_valid_i ( cfg_sbr_rsp_valid ), + .sbr_rsp_ready_o ( cfg_sbr_rsp_ready ), + .sbr_txn_id_i ( cfg_sbr_rsp.user.l2_src_id ), + // Routing + .id_i ( l2_id_i ), + .route_table_i ( l2_route_table_i ), + // Request flits + .floo_req_o ( peri_router_req_in[floo_pkg::Eject] ), + .floo_req_valid_o ( peri_router_req_valid_in[floo_pkg::Eject] ), + .floo_req_ready_i ( peri_router_req_ready_out[floo_pkg::Eject] ), + .floo_req_i ( peri_router_req_out[floo_pkg::Eject] ), + .floo_req_valid_i ( peri_router_req_valid_out[floo_pkg::Eject] ), + .floo_req_ready_o ( peri_router_req_ready_in[floo_pkg::Eject] ), + // Response flits + .floo_rsp_o ( peri_router_rsp_in[floo_pkg::Eject] ), + .floo_rsp_valid_o ( peri_router_rsp_valid_in[floo_pkg::Eject] ), + .floo_rsp_ready_i ( peri_router_rsp_ready_out[floo_pkg::Eject] ), + .floo_rsp_i ( peri_router_rsp_out[floo_pkg::Eject] ), + .floo_rsp_valid_i ( peri_router_rsp_valid_out[floo_pkg::Eject] ), + .floo_rsp_ready_o ( peri_router_rsp_ready_in[floo_pkg::Eject] ) + ); + + cachepool_group_cfg #( + .req_chan_t ( peri_noc_req_chan_t ), + .rsp_chan_t ( peri_narrow_rsp_chan_t ), + .user_t ( peri_narrow_user_t ) + ) i_group_cfg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .req_i ( cfg_sbr_req ), + .req_valid_i ( cfg_sbr_req_valid ), + .req_ready_o ( cfg_sbr_req_ready ), + .rsp_o ( cfg_sbr_rsp ), + .rsp_valid_o ( cfg_sbr_rsp_valid ), + .rsp_ready_i ( cfg_sbr_rsp_ready ), + .dynamic_offset_o ( dynamic_offset_q ), + .l1d_private_o ( l1d_private_q ), + .private_start_addr_o ( private_start_addr_q ), + .icache_prefetch_enable_o ( icache_prefetch_enable ), + .l1d_insn_o ( l1d_insn_q ), + .l1d_insn_valid_o ( l1d_insn_valid_q ), + .l1d_insn_ready_i ( l1d_insn_ready ) + ); + + // Narrow request router (source routing, 5 ports: N/E/S/W/Eject) + floo_router #( + .NumRoutes ( 5 ), + .NumVirtChannels( 1 ), + .InFifoDepth ( 2 ), + .OutFifoDepth ( 2 ), + .RouteAlgo ( floo_pkg::SourceRouting ), + .id_t ( floo_cachepool_noc_pkg::id_t), + .flit_t ( peri_noc_req_t ), + .hdr_t ( l2_noc_hdr_t ), + .addr_rule_t ( logic ), + .red_req_t ( logic ), + .red_rsp_t ( logic ) + ) i_peri_req_router ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_enable_i ( 1'b0 ), + .xy_id_i ( '0 ), + .id_route_map_i ( '0 ), + .valid_i ( peri_router_req_valid_in ), + .ready_o ( peri_router_req_ready_out ), + .data_i ( peri_router_req_in ), + .credit_o ( ), + .valid_o ( peri_router_req_valid_out ), + .ready_i ( peri_router_req_ready_in ), + .data_o ( peri_router_req_out ), + .credit_i ( '0 ), + .offload_req_o ( ), + .offload_rsp_i ( '0 ) + ); + + // Narrow response router (source routing, 5 ports: N/E/S/W/Eject) + floo_router #( + .NumRoutes ( 5 ), + .NumVirtChannels( 1 ), + .InFifoDepth ( 2 ), + .OutFifoDepth ( 2 ), + .RouteAlgo ( floo_pkg::SourceRouting ), + .id_t ( floo_cachepool_noc_pkg::id_t), + .flit_t ( peri_noc_rsp_t ), + .hdr_t ( l2_noc_hdr_t ), + .addr_rule_t ( logic ), + .red_req_t ( logic ), + .red_rsp_t ( logic ) + ) i_peri_rsp_router ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_enable_i ( 1'b0 ), + .xy_id_i ( '0 ), + .id_route_map_i ( '0 ), + .valid_i ( peri_router_rsp_valid_in ), + .ready_o ( peri_router_rsp_ready_out ), + .data_i ( peri_router_rsp_in ), + .credit_o ( ), + .valid_o ( peri_router_rsp_valid_out ), + .ready_i ( peri_router_rsp_ready_in ), + .data_o ( peri_router_rsp_out ), + .credit_i ( '0 ), + .offload_req_o ( ), + .offload_rsp_i ( '0 ) + ); + + // Expose [West:North] narrow router ports (indices 3:0) to cluster for mesh wiring + for (genvar d = 0; d < 4; d++) begin : gen_peri_mesh_ports + assign peri_req_o[d] = peri_router_req_out[d]; + assign peri_req_valid_o[d] = peri_router_req_valid_out[d]; + assign peri_router_req_ready_in[d] = peri_req_ready_i[d]; + assign peri_router_req_in[d] = peri_req_i[d]; + assign peri_router_req_valid_in[d] = peri_req_valid_i[d]; + assign peri_req_ready_o[d] = peri_router_req_ready_out[d]; + + assign peri_rsp_o[d] = peri_router_rsp_out[d]; + assign peri_rsp_valid_o[d] = peri_router_rsp_valid_out[d]; + assign peri_router_rsp_ready_in[d] = peri_rsp_ready_i[d]; + assign peri_router_rsp_in[d] = peri_rsp_i[d]; + assign peri_router_rsp_valid_in[d] = peri_rsp_valid_i[d]; + assign peri_rsp_ready_o[d] = peri_router_rsp_ready_out[d]; + end + // ------------------------------------------------------------------------- // Group instantiation // ------------------------------------------------------------------------- @@ -781,6 +983,8 @@ module cachepool_group_noc_wrapper .l2_req_o ( l2_group_req ), .l2_rsp_i ( l2_group_rsp ), .l2_group_id_i ( l2_id_i ), + .periph_req_o ( periph_group_req ), + .periph_rsp_i ( periph_group_rsp ), .remote_group_req_o ( remote_group_req_from_group ), .remote_group_rsp_i ( remote_group_rsp_to_group ), .remote_group_req_i ( remote_group_req_to_group ), @@ -788,12 +992,12 @@ module cachepool_group_noc_wrapper .group_barrier_o ( group_barrier_o ), .barrier_done_i ( barrier_done_i ), .icache_events_o ( icache_events_o ), - .icache_prefetch_enable_i ( icache_prefetch_enable_i ), + .icache_prefetch_enable_i ( icache_prefetch_enable ), .dynamic_offset_i ( dynamic_offset_q ), .l1d_private_i ( l1d_private_q ), .l1d_insn_i ( l1d_insn_q ), .l1d_insn_valid_i ( l1d_insn_valid_q ), - .l1d_insn_ready_o ( l1d_insn_ready_d ), + .l1d_insn_ready_o ( l1d_insn_ready ), .l1d_busy_i ( l1d_busy_q ) ); diff --git a/hardware/src/cachepool_pkg.sv b/hardware/src/cachepool_pkg.sv index 79481eff..4e63a262 100644 --- a/hardware/src/cachepool_pkg.sv +++ b/hardware/src/cachepool_pkg.sv @@ -64,6 +64,8 @@ package cachepool_pkg; /////////////////// // How many cores for each tile? localparam int unsigned NumCoresTile = NumCC / NumTiles; + // Harts per tile (NumScalarPerCC harts share one CC) + localparam int unsigned NumHartsTile = NumCoresTile * NumScalarPerCC; // Intra-group remote ports per core (to other tiles in the same group). localparam int unsigned NumLGPortCore = `ifdef LG_PORT_PER_CORE `LG_PORT_PER_CORE `else 0 `endif; @@ -124,10 +126,25 @@ package cachepool_pkg; localparam int unsigned PeriStartAddr = 32'hC000_0000; localparam int unsigned BootAddr = 32'h1000; + // Bootrom word width, equal to the L2 refill width (checked in cachepool_cluster) + localparam int unsigned BootromDataWidth = `ifdef BOOTROM_DATA_WIDTH `BOOTROM_DATA_WIDTH `else 0 `endif; + // Bootrom address window + localparam int unsigned BootromSize = 32'h1_0000; // UART Configuration localparam int unsigned UartAddr = 32'hC001_0000; + // Group-level configuration registers: group g owns the block at GroupCfgBase + g * GroupCfgStride. + // Only the cluster peripheral writes them, over the L2 mesh; cores never access them. + localparam logic [31:0] GroupCfgBase = `ifdef GROUP_CFG_BASE 32'd `GROUP_CFG_BASE `else 32'hD000_0000 `endif; + localparam int unsigned GroupCfgStride = `ifdef GROUP_CFG_STRIDE `GROUP_CFG_STRIDE `else 32'h100 `endif; + + // Reset values shared by the cluster peripheral and the group-level configuration registers. + localparam int unsigned L1dXbarOffsetRst = `ifdef L1D_XBAR_OFFSET_RST `L1D_XBAR_OFFSET_RST `else 14 `endif; + localparam int unsigned L1dPrivateRst = `ifdef L1D_PRIVATE_RST `L1D_PRIVATE_RST `else 0 `endif; + localparam logic [31:0] L1dPrivateAddrRst = `ifdef L1D_PRIVATE_ADDR_RST 32'd `L1D_PRIVATE_ADDR_RST `else 32'hA000_0000 `endif; + localparam bit ICachePrefetchRst = `ifdef ICACHE_PREFETCH_RST `ICACHE_PREFETCH_RST `else 1'b1 `endif; + /////////////////////// // CACHEPOOL L1 CFG // /////////////////////// @@ -180,8 +197,8 @@ package cachepool_pkg; // Core id width within a tile => tile ID will be calculated separatly localparam int unsigned CoreIDWidth = idx_width(NumCoresTile); localparam int unsigned TileIDWidth = idx_width(NumTiles); - // Each bank inside a tile needs an unique id, plus two reserved for iCache and peripheral - localparam int unsigned BankIDWidth = idx_width(NumL1CtrlTile + 2); + // Each bank inside a tile needs an unique id, plus one reserved for the iCache + localparam int unsigned BankIDWidth = idx_width(NumL1CtrlTile + 1); localparam int unsigned RefillDataWidth = `ifdef REFILL_DATA_WIDTH `REFILL_DATA_WIDTH `else 0 `endif; localparam int unsigned RefillStrbWidth = RefillDataWidth / 8; @@ -213,12 +230,11 @@ package cachepool_pkg; // AXI ID field structure // ----------------------- // ClusterAxiIdWidth = [cluster_route_bits][tile_index_bits][tile_local_bits] - // Per-tile xbar ports: NumL1CtrlTile refill + 1 peripheral + 1 iCache - localparam int unsigned NumClusterMst = 2 + NumL1CtrlTile; + // Per-tile xbar ports: NumL1CtrlTile refill + 1 iCache (peripherals are on the narrow mesh) + localparam int unsigned NumClusterMst = 1 + NumL1CtrlTile; // Bank ID constants for the refill xbar response demux - // Layout per tile: [0..NumL1CtrlTile-1] = refill, NumL1CtrlTile = peripheral, NumL1CtrlTile+1 = iCache - localparam int unsigned BankIdPeriph = NumL1CtrlTile; - localparam int unsigned BankIdICache = NumL1CtrlTile + 1; + // Layout per tile: [0..NumL1CtrlTile-1] = refill, NumL1CtrlTile = iCache + localparam int unsigned BankIdICache = NumL1CtrlTile; localparam int unsigned ClusterRouteIdWidth = $clog2(NumClusterMst); @@ -394,19 +410,47 @@ package cachepool_pkg; } burst_req_t; // style-waive: COMMENT_BLOCK: instruction encoding table - // Cache flush/invalidation instruction issued by the peripheral flush controller. + // Cache flush/invalidation instruction issued by the group configuration register. // insn encoding: // 2'b00 : flush private banks only // 2'b01 : flush shared banks only // 2'b10 : flush all banks // 2'b11 : invalidate (init) all banks - // tile_sel: one-hot mask over NumTiles. For insn != 2'b00 the peripheral - // sets tile_sel to '1 (all tiles) for consistency. + // tile_sel: one-hot mask over the tiles of the group. For insn != 2'b00 the + // cluster peripheral sets tile_sel to '1 (all tiles) for consistency. typedef struct packed { - logic [1:0] insn; - logic [NumTiles-1:0] tile_sel; + logic [1:0] insn; + logic [NumTilesPerGroup-1:0] tile_sel; } cache_insn_t; + // Bit position of the address selecting the cache bank (cache xbar offset). + typedef logic [$clog2(SpatzAxiAddrWidth)-1:0] xbar_offset_t; + + // Committed L1D/xbar configuration, identical in every group. + typedef struct packed { + logic [L1PrivWidth-1:0] l1d_private; + axi_addr_t private_addr; + xbar_offset_t xbar_offset; + logic prefetch_en; + } l1d_cfg_t; + + localparam l1d_cfg_t L1dCfgRst = '{ + l1d_private: L1dPrivateRst, + private_addr: L1dPrivateAddrRst, + xbar_offset: L1dXbarOffsetRst, + prefetch_en: ICachePrefetchRst + }; + + // Registers of a group-level configuration register block, one 32-bit word each at + // GroupCfgBase + g * GroupCfgStride + 4 * index. Every write carries one register. + typedef enum logic [2:0] { + GroupCfgXbarOffset = 3'd0, + GroupCfgL1dPrivate = 3'd1, + GroupCfgL1dAddr = 3'd2, + GroupCfgPrefetch = 3'd3, + GroupCfgL1dInsn = 3'd4 + } group_cfg_reg_e; + typedef struct packed { logic for_write_pend; cache_ways_entry_ptr_t depth; @@ -517,6 +561,16 @@ package cachepool_pkg; } l2_noc_hdr_t; // l2_noc_req_t / l2_noc_rsp_t defined after REQRSP_TYPEDEF_ALL macros below. + // User field of the narrow (peripheral) L2 mesh: only what routes a response back to its hart. + // tile_id is the tile inside the source group, hart_id the hart inside that tile. + typedef struct packed { + floo_cachepool_noc_pkg::id_t l2_src_id; + logic [idx_width(NumTilesPerGroup)-1:0] tile_id; + logic [idx_width(NumHartsTile)-1:0] hart_id; + logic is_fpu; + reqid_t req_id; + } peri_narrow_user_t; + // Group ICache (L2 read-only cache control) // 2 rules: DRAM (cacheable) + BootROM (cacheable, avoids Bypass path) localparam int unsigned ROCacheNumAddrRules = 2; @@ -583,16 +637,33 @@ package cachepool_pkg; l2_noc_hdr_t hdr; } l2_noc_rsp_t; - // REQRSP: peripheral path (narrow 32b, tcdm_user_t) - `REQRSP_TYPEDEF_ALL (periph, axi_addr_t, narrow_data_t, narrow_strb_t, tcdm_user_t) + // REQRSP: narrow 32b peripheral path (core accesses to CSR/UART, group configuration writes) + `REQRSP_TYPEDEF_ALL (peri_narrow, axi_addr_t, narrow_data_t, narrow_strb_t, peri_narrow_user_t) + + // Narrow L2 mesh flit types. The request carries no AMO: atomics never target the peripherals. + typedef struct packed { + axi_addr_t addr; + logic write; + narrow_data_t data; + narrow_strb_t strb; + reqrsp_pkg::size_t size; + peri_narrow_user_t user; + } peri_noc_req_chan_t; + + typedef struct packed { + peri_noc_req_chan_t payload; + l2_noc_hdr_t hdr; + } peri_noc_req_t; - // REQRSP: narrow 32b with refill_user_t (DW converter output, peripheral path) - `REQRSP_TYPEDEF_ALL (peri_narrow, axi_addr_t, narrow_data_t, narrow_strb_t, refill_user_t) + typedef struct packed { + peri_narrow_rsp_chan_t payload; + l2_noc_hdr_t hdr; + } peri_noc_rsp_t; - // Peripheral xbar user: refill_user_t + 1-bit source ID (0=NoC, 1=TB) + // Peripheral xbar user: narrow user + 1-bit source ID (0=NoC, 1=TB) typedef struct packed { - logic src_id; - refill_user_t refill; + logic src_id; + peri_narrow_user_t narrow; } peri_xbar_user_t; // REQRSP: peripheral xbar path (narrow 32b, peri_xbar_user_t) @@ -732,4 +803,57 @@ package cachepool_pkg; end endfunction + // L2 mesh endpoint of the cluster peripheral: the West chimney at gy=0, next to group 0. Its + // channel ID follows the HBM placement (see gen_hbm_west in cachepool_cluster). + localparam int unsigned NumHbmAllSides = 2 * NumGroupsY + 2 * NumGroupsX; + localparam int unsigned HbmPerSide = NumHbmAllSides / 4; + localparam int unsigned L2PeriphChannel = (NumL2Channel == NumHbmAllSides) ? HbmPerSide : 0; + localparam int unsigned PeriEndpointId = NumGroups + L2PeriphChannel; + + // Narrow L2 mesh route config and SAM of the group side: every core access goes to the cluster + // peripheral endpoint, which decodes CSR/UART itself. + localparam int unsigned PeriCoreSamNumRules = 1; + typedef floo_cachepool_noc_pkg::sam_rule_t [PeriCoreSamNumRules-1:0] peri_core_sam_t; + + function automatic floo_pkg::route_cfg_t gen_peri_core_route_cfg(); + floo_pkg::route_cfg_t cfg; + cfg = floo_cachepool_noc_pkg::RouteCfg; + cfg.NumSamRules = PeriCoreSamNumRules; + return cfg; + endfunction + + localparam floo_pkg::route_cfg_t PeriCoreRouteCfg = gen_peri_core_route_cfg(); + localparam peri_core_sam_t PeriCoreSam = '{ + '{idx: floo_cachepool_noc_pkg::id_t'(PeriEndpointId), + start_addr: 32'h0000_0000, + end_addr: 32'hFFFF_FFFF} + }; + + // Narrow L2 mesh route config and SAM of the cluster peripheral's manager port. It only ever targets + // group configuration words, so its SAM holds just those rules; they stay out of the generated + // Sam so core requests can never be routed to a group configuration register. + localparam int unsigned PeriSamNumRules = NumGroups; + typedef floo_cachepool_noc_pkg::sam_rule_t [PeriSamNumRules-1:0] peri_sam_t; + + function automatic floo_pkg::route_cfg_t gen_peri_route_cfg(); + floo_pkg::route_cfg_t cfg; + cfg = floo_cachepool_noc_pkg::RouteCfg; + cfg.NumSamRules = PeriSamNumRules; + return cfg; + endfunction + + function automatic peri_sam_t gen_peri_sam(); + peri_sam_t sam; + for (int unsigned g = 0; g < NumGroups; g++) begin + // Group index g is row-major (gy * NumGroupsX + gx); L2 mesh endpoint IDs are column-major. + sam[g].idx = floo_cachepool_noc_pkg::id_t'((g % NumGroupsX) * NumGroupsY + g / NumGroupsX); + sam[g].start_addr = GroupCfgBase + g * GroupCfgStride; + sam[g].end_addr = GroupCfgBase + (g + 1) * GroupCfgStride; + end + return sam; + endfunction + + localparam floo_pkg::route_cfg_t PeriRouteCfg = gen_peri_route_cfg(); + localparam peri_sam_t PeriSam = gen_peri_sam(); + endpackage : cachepool_pkg diff --git a/hardware/src/cachepool_tile.sv b/hardware/src/cachepool_tile.sv index e8ee8253..a673148e 100644 --- a/hardware/src/cachepool_tile.sv +++ b/hardware/src/cachepool_tile.sv @@ -152,9 +152,9 @@ module cachepool_tile /// Cache Refill ports output cache_trans_req_t [NumL1CtrlTile-1:0] cache_refill_req_o, input cache_trans_rsp_t [NumL1CtrlTile-1:0] cache_refill_rsp_i, - /// Peripheral REQRSP port (narrow, 32b, bypasses wide AXI xbar) - output periph_req_t periph_req_o, - input periph_rsp_t periph_rsp_i, + /// Peripheral REQRSP port (narrow, 32b, to the narrow L2 mesh) + output peri_narrow_req_t periph_req_o, + input peri_narrow_rsp_t periph_rsp_i, /// Wide AXI port: iCache L2 refill only (BootROM at cluster level) output axi_out_req_t [TileWideAxiPorts-1:0] axi_wide_req_o, input axi_out_resp_t [TileWideAxiPorts-1:0] axi_wide_rsp_i, @@ -972,12 +972,12 @@ module cachepool_tile // l1d_insn_ready_o pulses as cache_flush_d hits zero (no loop: it only feeds a peripheral reg). // Determine whether this tile should act on the incoming instruction. - // Private flush (insn==00): only if our tile_id bit is set in tile_sel. + // Private flush (insn==00): only if our bit within the group is set in tile_sel. // All other modes: always act. logic tile_insn_active; always_comb begin : gen_tile_active if (l1d_insn_i.insn == 2'b00) - tile_insn_active = l1d_insn_i.tile_sel[tile_id_i]; + tile_insn_active = l1d_insn_i.tile_sel[TileIdxInGroup]; else tile_insn_active = 1'b1; end @@ -1688,30 +1688,76 @@ module cachepool_tile .cluster_periph_start_address_i (cluster_periph_start_address) ); - reqrsp_req_t core_to_periph_req; - reqrsp_rsp_t core_to_periph_rsp; - - reqrsp_mux #( - .NrPorts (NrHarts ), - .AddrWidth (AxiAddrWidth ), - .DataWidth (NarrowDataWidth ), - .UserWidth ($bits(tcdm_user_t)), - .req_t (reqrsp_req_t ), - .rsp_t (reqrsp_rsp_t ), - .RespDepth (2 ) - ) i_reqrsp_mux_core ( - .clk_i (clk_i ), - .rst_ni (rst_ni ), - .slv_req_i (filtered_core_req ), - .slv_rsp_o (filtered_core_rsp ), - .mst_req_o (core_to_periph_req ), - .mst_rsp_i (core_to_periph_rsp ), - .idx_o (/*unused*/ ) + // Peripheral accesses of all harts merge into one narrow port. Every request carries its hart + // index so the response is routed back by user.hart_id; the harts accept out-of-order + // responses by user.req_id, so no ordering is kept here. + typedef logic [cf_math_pkg::idx_width(NrHarts)-1:0] periph_hart_sel_t; + + peri_narrow_req_chan_t [NrHarts-1:0] periph_hart_req; + logic [NrHarts-1:0] periph_hart_req_valid, periph_hart_req_ready; + peri_narrow_rsp_chan_t [NrHarts-1:0] periph_hart_rsp; + logic [NrHarts-1:0] periph_hart_rsp_valid, periph_hart_rsp_ready; + periph_hart_sel_t periph_rsp_sel; + + for (genvar i = 0; i < NrHarts; i++) begin : gen_periph_hart + always_comb begin + periph_hart_req[i] = '0; + periph_hart_req[i].addr = filtered_core_req[i].q.addr; + periph_hart_req[i].write = filtered_core_req[i].q.write; + periph_hart_req[i].amo = filtered_core_req[i].q.amo; + periph_hart_req[i].data = filtered_core_req[i].q.data; + periph_hart_req[i].strb = filtered_core_req[i].q.strb; + periph_hart_req[i].size = filtered_core_req[i].q.size; + periph_hart_req[i].user.hart_id = i; + periph_hart_req[i].user.is_fpu = filtered_core_req[i].q.user.is_fpu; + periph_hart_req[i].user.req_id = filtered_core_req[i].q.user.req_id; + + filtered_core_rsp[i] = '0; + filtered_core_rsp[i].q_ready = periph_hart_req_ready[i]; + filtered_core_rsp[i].p.data = periph_hart_rsp[i].data; + filtered_core_rsp[i].p.error = periph_hart_rsp[i].error; + filtered_core_rsp[i].p.write = periph_hart_rsp[i].write; + filtered_core_rsp[i].p.user.is_fpu = periph_hart_rsp[i].user.is_fpu; + filtered_core_rsp[i].p.user.req_id = periph_hart_rsp[i].user.req_id; + filtered_core_rsp[i].p_valid = periph_hart_rsp_valid[i]; + end + assign periph_hart_req_valid[i] = filtered_core_req[i].q_valid; + assign periph_hart_rsp_ready[i] = filtered_core_req[i].p_ready; + end + + assign periph_rsp_sel = periph_rsp_i.p.user.hart_id; + + reqrsp_xbar #( + .NumInp ( NrHarts ), + .NumOut ( 1 ), + .PipeReg ( 1'b0 ), + .tcdm_req_chan_t ( peri_narrow_req_chan_t ), + .tcdm_rsp_chan_t ( peri_narrow_rsp_chan_t ), + .mst_sel_t ( periph_hart_sel_t ) + ) i_periph_xbar ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .slv_req_i ( periph_hart_req ), + .slv_rr_i ( '0 ), + .slv_req_valid_i ( periph_hart_req_valid ), + .slv_req_ready_o ( periph_hart_req_ready ), + .slv_rsp_o ( periph_hart_rsp ), + .slv_rsp_valid_o ( periph_hart_rsp_valid ), + .slv_rsp_ready_i ( periph_hart_rsp_ready ), + .slv_sel_i ( '0 ), + .slv_selected_o ( /* unused */ ), + .mst_req_o ( periph_req_o.q ), + .mst_req_valid_o ( periph_req_o.q_valid ), + .mst_req_ready_i ( periph_rsp_i.q_ready ), + .mst_rsp_i ( periph_rsp_i.p ), + .mst_rr_i ( '0 ), + .mst_rsp_valid_i ( periph_rsp_i.p_valid ), + .mst_rsp_ready_o ( periph_req_o.p_ready ), + .mst_sel_i ( periph_rsp_sel ) ); - // Peripheral REQRSP: directly exposed to group level (no AXI conversion) - assign periph_req_o = core_to_periph_req; - assign core_to_periph_rsp = periph_rsp_i; + `ASSERT_INIT(CheckPeriphHartWidth, NrHarts == NumHartsTile, + "cachepool_tile: hart count does not match cachepool_pkg::NumHartsTile") // ------------- // Sanity Checks diff --git a/hardware/src/reqrsp_to_reg.sv b/hardware/src/reqrsp_to_reg.sv index 0cafe260..c71eba26 100644 --- a/hardware/src/reqrsp_to_reg.sv +++ b/hardware/src/reqrsp_to_reg.sv @@ -4,8 +4,9 @@ // // Based on periph_to_reg from register_interface. // Converts REQRSP protocol to register interface with 1-cycle response latency. -// Assumes in-order, constant-latency target (e.g., BootROM). -// A FIFO preserves the REQRSP user field from request to response. +// Assumes the target answers in the cycle it raises ready (e.g., regtool CSRs, BootROM). +// A request is only issued when the response register is free or drained in the same +// cycle, so a response is held until its handshake and carries its own user field. `include "common_cells/registers.svh" `include "common_cells/assertions.svh" @@ -14,8 +15,6 @@ module reqrsp_to_reg #( parameter int unsigned AddrWidth = 32, parameter int unsigned DataWidth = 32, parameter int unsigned UserWidth = 1, - // FIFO depth for buffering user field (should match max outstanding) - parameter int unsigned FifoDepth = 2, // Shift response data right by the request byte offset so the addressed bytes land at // [31:0], for regbus targets wider than the requester (e.g. 512b BootROM, 32b loads). parameter bit ShiftResponse = 1'b0, @@ -35,31 +34,32 @@ module reqrsp_to_reg #( localparam int unsigned ByteOffset = $clog2(DataWidth / 8); - // Request grant: accepted when both sides are valid/ready - logic gnt; - assign gnt = reqrsp_req_i.q_valid & reg_rsp_i.ready; + // Response register state + logic rsp_valid_q; + logic [DataWidth-1:0] rsp_rdata_d, rsp_rdata_q; + logic rsp_error_q; + logic rsp_write_q; + user_t rsp_user_q; - // Forward request to regbus (combinational) + // The response register can take a new response this cycle + logic rsp_free; + assign rsp_free = ~rsp_valid_q | reqrsp_req_i.p_ready; + + // Request grant: target ready and room for the response + logic req_valid, gnt; + assign req_valid = reqrsp_req_i.q_valid & rsp_free; + assign gnt = req_valid & reg_rsp_i.ready; + + // Forward request to regbus. valid is gated as well, so a write cannot take effect + // while its grant is held back. assign reg_req_o.addr = reqrsp_req_i.q.addr; assign reg_req_o.write = reqrsp_req_i.q.write; assign reg_req_o.wdata = reqrsp_req_i.q.data; assign reg_req_o.wstrb = reqrsp_req_i.q.strb; - assign reg_req_o.valid = reqrsp_req_i.q_valid; + assign reg_req_o.valid = req_valid; - // REQRSP q_ready: grant the request assign reqrsp_rsp_o.q_ready = gnt; - // Register response (1-cycle latency, same as periph_to_reg) - logic rsp_valid_q; - logic [DataWidth-1:0] rsp_rdata_d, rsp_rdata_q; - logic rsp_error_q; - logic rsp_write_q; - - `FF(rsp_valid_q, gnt, 1'b0) - `FF(rsp_rdata_q, rsp_rdata_d, '0) - `FF(rsp_error_q, reg_rsp_i.error, 1'b0) - `FF(rsp_write_q, reqrsp_req_i.q.write, 1'b0) - // Shift rdata by the combinational request address. Safe for constant-latency targets: // q.addr is held until q_ready (= gnt), which fires in the cycle rdata becomes valid. if (ShiftResponse) begin : gen_shift @@ -68,44 +68,27 @@ module reqrsp_to_reg #( assign rsp_rdata_d = reg_rsp_i.rdata; end - // User FIFO: push on grant, pop when response is consumed - user_t user_fifo_out; - logic user_fifo_full, user_fifo_empty; - logic user_fifo_push, user_fifo_pop; - - assign user_fifo_push = gnt; - assign user_fifo_pop = rsp_valid_q & reqrsp_req_i.p_ready; - - fifo_v3 #( - .FALL_THROUGH ( 1'b0 ), - .DEPTH ( FifoDepth ), - .dtype ( user_t ) - ) i_user_fifo ( - .clk_i, - .rst_ni, - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .full_o ( user_fifo_full ), - .empty_o ( user_fifo_empty ), - .usage_o ( /* unused */ ), - .data_i ( reqrsp_req_i.q.user ), - .push_i ( user_fifo_push ), - .data_o ( user_fifo_out ), - .pop_i ( user_fifo_pop ) - ); + // Set on grant, cleared on handshake + `FF(rsp_valid_q, gnt | (rsp_valid_q & ~reqrsp_req_i.p_ready), 1'b0) + `FFL(rsp_rdata_q, rsp_rdata_d, gnt, '0) + `FFL(rsp_error_q, reg_rsp_i.error, gnt, 1'b0) + `FFL(rsp_write_q, reqrsp_req_i.q.write, gnt, 1'b0) + `FFL(rsp_user_q, reqrsp_req_i.q.user, gnt, '0) // REQRSP response channel assign reqrsp_rsp_o.p.data = rsp_rdata_q; assign reqrsp_rsp_o.p.error = rsp_error_q; assign reqrsp_rsp_o.p.write = rsp_write_q; - assign reqrsp_rsp_o.p.user = user_fifo_out; + assign reqrsp_rsp_o.p.user = rsp_user_q; assign reqrsp_rsp_o.p_valid = rsp_valid_q; - // Target is read-only when ShiftResponse is enabled (e.g., BootROM) `ifndef TARGET_SYNTHESIS + // Target is read-only when ShiftResponse is enabled (e.g., BootROM) if (ShiftResponse) begin : gen_no_write_assert `ASSERT(NoWrite, !(reg_req_o.valid && reg_req_o.write), clk_i, rst_ni) end + `ASSERT(RspStable, reqrsp_rsp_o.p_valid && !reqrsp_req_i.p_ready |=> + reqrsp_rsp_o.p_valid && $stable(reqrsp_rsp_o.p), clk_i, rst_ni) `endif endmodule diff --git a/software/snRuntime/README.md b/software/snRuntime/README.md index af85f57e..1997949c 100644 --- a/software/snRuntime/README.md +++ b/software/snRuntime/README.md @@ -137,8 +137,12 @@ across groups. // Requires a flush before changing while valid data is cached. void l1d_addr(uint32_t addr); -// Poll the peripheral until the current flush instruction completes. -// Used by the low-level flush functions; not normally needed in application code. +// Poll the peripheral until the last committed flush instruction and every changed +// configuration (partition, address boundary, xbar offset, iCache prefetch) has been +// applied by every group. Configuration registers need no commit: a changed value is +// applied on its own, only flush/invalidate instructions are committed. +// Used by the low-level flush and configuration functions; not normally needed in +// application code. void l1d_wait(); ``` diff --git a/software/snRuntime/include/cachepool_peripheral.h b/software/snRuntime/include/cachepool_peripheral.h index 750388fb..e4f31d74 100644 --- a/software/snRuntime/include/cachepool_peripheral.h +++ b/software/snRuntime/include/cachepool_peripheral.h @@ -102,15 +102,16 @@ extern "C" { #define CACHEPOOL_PERIPHERAL_L1D_SPM_COMMIT_REG_OFFSET 0x38 #define CACHEPOOL_PERIPHERAL_L1D_SPM_COMMIT_COMMIT_BIT 0 -// Controls the L1 DCache flushing and invalidation. +// Starts the L1D instruction in CFG_L1D_INSN on the tiles selected by #define CACHEPOOL_PERIPHERAL_L1D_INSN_COMMIT_REG_OFFSET 0x3c #define CACHEPOOL_PERIPHERAL_L1D_INSN_COMMIT_COMMIT_BIT 0 -// Indicate the status of flushing +// Busy while a committed L1D instruction or a changed setting is still being #define CACHEPOOL_PERIPHERAL_L1D_FLUSH_STATUS_REG_OFFSET 0x40 #define CACHEPOOL_PERIPHERAL_L1D_FLUSH_STATUS_STATUS_BIT 0 -// L1D partitioning: private bank count and how it is folded +// L1D partitioning: private bank count and how it is folded. Applied when +// the #define CACHEPOOL_PERIPHERAL_L1D_PRIVATE_REG_OFFSET 0x44 #define CACHEPOOL_PERIPHERAL_L1D_PRIVATE_PARTITION_MASK 0x7 #define CACHEPOOL_PERIPHERAL_L1D_PRIVATE_PARTITION_OFFSET 0 @@ -120,10 +121,11 @@ extern "C" { .index = CACHEPOOL_PERIPHERAL_L1D_PRIVATE_PARTITION_OFFSET}) #define CACHEPOOL_PERIPHERAL_L1D_PRIVATE_GROUP_FOLD_BIT 3 -// Starting address of private L1D partition +// Starting address of private L1D partition. Applied when the value changes, +// no #define CACHEPOOL_PERIPHERAL_L1D_ADDR_REG_OFFSET 0x48 -// Cache xbar offset setting +// Cache xbar offset setting. Applied when the value changes, no commit #define CACHEPOOL_PERIPHERAL_XBAR_OFFSET_REG_OFFSET 0x4c #define CACHEPOOL_PERIPHERAL_XBAR_OFFSET_OFFSET_MASK 0x1f #define CACHEPOOL_PERIPHERAL_XBAR_OFFSET_OFFSET_OFFSET 0 @@ -132,10 +134,6 @@ extern "C" { .index = \ CACHEPOOL_PERIPHERAL_XBAR_OFFSET_OFFSET_OFFSET}) -// Cache xbar offset setting -#define CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT_REG_OFFSET 0x50 -#define CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT_COMMIT_BIT 0 - #ifdef __cplusplus } // extern "C" #endif diff --git a/software/snRuntime/include/l1cache.h b/software/snRuntime/include/l1cache.h index aec2c73c..3b0a87f5 100644 --- a/software/snRuntime/include/l1cache.h +++ b/software/snRuntime/include/l1cache.h @@ -12,7 +12,6 @@ extern __thread struct snrt_team *_snrt_team_current; // Used to configure cache xbar's arbitration algorithm // Size in Bytes void l1d_xbar_config(uint32_t offset); -void l1d_xbar_commit(); void l1d_commit(); void l1d_init(uint32_t size); diff --git a/software/snRuntime/src/l1cache.c b/software/snRuntime/src/l1cache.c index ca3a5107..6d51cff3 100644 --- a/software/snRuntime/src/l1cache.c +++ b/software/snRuntime/src/l1cache.c @@ -21,26 +21,20 @@ void l1d_xbar_config(uint32_t offset) { asm volatile("fence" ::: "memory"); snrt_cluster_hw_barrier(); if (snrt_cluster_core_idx() == 0) { + // Flush cache before changing the xbar offset: a changed offset is applied as soon as it + // is written, no commit needed + l1d_flush(); + l1d_wait(); uint32_t *cfg = (uint32_t *)(_snrt_team_current->root->cluster_mem.end + CACHEPOOL_PERIPHERAL_XBAR_OFFSET_REG_OFFSET); *cfg = offset; - // Flush cache before committing xbar changes - l1d_flush(); + // Wait until every group has applied the new offset l1d_wait(); - l1d_xbar_commit(); } snrt_cluster_hw_barrier(); } - -void l1d_xbar_commit() { - uint32_t *commit = - (uint32_t *)(_snrt_team_current->root->cluster_mem.end + - CACHEPOOL_PERIPHERAL_XBAR_OFFSET_COMMIT_REG_OFFSET); - *commit = 1; -} - void l1d_commit() { uint32_t *commit = (uint32_t *)(_snrt_team_current->root->cluster_mem.end + @@ -161,8 +155,10 @@ void l1d_part_folded (uint32_t size, uint32_t fold) { volatile uint32_t *cfg_private = (uint32_t *)(_snrt_team_current->root->cluster_mem.end + CACHEPOOL_PERIPHERAL_L1D_PRIVATE_REG_OFFSET); + // Applied as soon as the value changes, no commit needed *cfg_private = (size & 0x7u) | ((fold & 0x1u) << 3); - l1d_commit(); + // Wait until every group has applied the new partition + l1d_wait(); } snrt_cluster_hw_barrier(); } @@ -182,12 +178,10 @@ void l1d_addr (uint32_t addr) { volatile uint32_t *cfg_private = (uint32_t *)(_snrt_team_current->root->cluster_mem.end + CACHEPOOL_PERIPHERAL_L1D_ADDR_REG_OFFSET); + // Applied as soon as the value changes, no commit needed *cfg_private = addr; - l1d_commit(); - // TODO: replace with a real completion status bit in the peripheral. - uint32_t start = read_csr(mcycle); - while ((read_csr(mcycle) - start) < 100) { - } + // Wait until every group has applied the new start address + l1d_wait(); } snrt_cluster_hw_barrier(); } diff --git a/software/snRuntime/src/platforms/standalone/start_snitch.S b/software/snRuntime/src/platforms/standalone/start_snitch.S index ed9a848c..a7a15851 100644 --- a/software/snRuntime/src/platforms/standalone/start_snitch.S +++ b/software/snRuntime/src/platforms/standalone/start_snitch.S @@ -32,6 +32,9 @@ _snrt_exit: mv s0, t0 # call l1d_flush mv a0, s0 + # Earlier UART/CSR writes may still be in flight: they reach different targets and are not + # ordered with the EOC write, so wait for all of them first. + fence call set_eoc 1: ret diff --git a/util/scripts/generate_bootrom.py b/util/scripts/generate_bootrom.py index 24442770..c0b192bf 100755 --- a/util/scripts/generate_bootrom.py +++ b/util/scripts/generate_bootrom.py @@ -120,8 +120,9 @@ def read_bin(): with open(filename + ".bin", "rb") as f: rom = bytes.hex(f.read()) rom = list(map("".join, zip(rom[::2], rom[1::2]))) - # align to 64 bit - align = (int((len(rom) + 7) / 8)) * 8 + # align to 64 bit and to the bootrom word + word = max(8, int(DataWidth / 8)) + align = (int((len(rom) + word - 1) / word)) * word for i in range(len(rom), align): rom.append("00") return rom