diff --git a/Bender.lock b/Bender.lock index d7128cc4..5e64ceb3 100644 --- a/Bender.lock +++ b/Bender.lock @@ -81,7 +81,7 @@ packages: - fpnew - tech_cells_generic cv32e40x: - revision: a90101211048ba1a16cedbe4db963ab6e12569d7 + revision: 8834f135214f8f25f710ebd77f48d9f03d9548e6 version: null source: Git: https://github.com/pulp-platform/cv32e40x.git diff --git a/Bender.yml b/Bender.yml index b16fcbaa..9f0f6e86 100644 --- a/Bender.yml +++ b/Bender.yml @@ -26,7 +26,7 @@ package: dependencies: redmule : { git: "https://github.com/pulp-platform/redmule.git" , rev: 2.2.1 } - cv32e40x : { git: "https://github.com/pulp-platform/cv32e40x.git" , rev: a90101211048ba1a16cedbe4db963ab6e12569d7 } # branch: vi/redmule_scaleup + cv32e40x : { git: "https://github.com/pulp-platform/cv32e40x.git" , rev: 8834f135214f8f25f710ebd77f48d9f03d9548e6 } # branch: lb/operand_forwarding cv32e40p : { git: "https://github.com/FondazioneChipsIT/cv32e40p.git" , rev: 193270df33f76f2728a364bd2d9b1865e0b7e4f9 } # branch: lb/PulpClusterCV32OHWFPU-divsqrt spatz : { git: "https://github.com/pulp-platform/spatz.git" , rev: 3228a9077cb33b4ee235029e8fcace52ed327334 } # branch: lb/magia-spatz_cc idma : { git: "https://github.com/FondazioneChipsIT/iDMA.git" , rev: 26ac2cd1c535851b14c9c508762203a180961cad } # branch: cc/mcast_magia diff --git a/Makefile b/Makefile index a90e5726..5efa814b 100644 --- a/Makefile +++ b/Makefile @@ -577,7 +577,7 @@ hw-all: hw-clean hw-lib hw-compile hw-opt # Nonfree components MAGIA_NONFREE_REMOTE ?= $(GITLAB_UNIBO_SSH_STRING)/magia/nonfree.git MAGIA_NONFREE_DIR ?= nonfree -MAGIA_NONFREE_COMMIT ?= 5d887bcdf505c0949fa05dcd1b8e6ce3199b912c +MAGIA_NONFREE_COMMIT ?= 3ac0a45bd398808a3357528608ff49a6d1bb6037 MAGIA_NONFREE_DEPS ?= 1 .PHONY: magia-nonfree-init diff --git a/README.md b/README.md index 01ff0f9e..6efd50a2 100644 --- a/README.md +++ b/README.md @@ -181,17 +181,17 @@ To change the thread count, re-run `make verilate` with the new value. ## ⚙️ Architecture -![](doc/MAGIA.png) +![](doc/MAGIA_v4.png) ### Tile -The central piece of the architecture is the MAGIA tile containing a GeMM accelerator, a Vector Processor, a DMA engine, a PULP cluster of RISC-V cores, a multi-banked L1 SPM, an Event Unit, and a lightweight control core. The L1 features interleaved memory banks that compose the Tightly-Coupled Data Memory (TCDM). Each tile has access to the global L2 and to a subset of other tiles' L1, accessing the latter via on-chip remote direct memory access (RDMA). Inter-tile and global communication is carried out through AXI-based narrow (32-bit) and wide (256-bit) NoC channels in [FlooNoC](https://github.com/pulp-platform/FlooNoC). External tiles and the core access the L1 through an OpenBus Interface ([OBI](https://github.com/pulp-platform/obi)) XBAR. +The central piece of the architecture is the MAGIA tile containing a GeMM accelerator, a Vector Processor, a DMA engine, a PULP cluster of RISC-V cores, a multi-banked L1 SPM, an Event Unit, and a lightweight control core. The L1 features interleaved memory banks that compose the Tightly-Coupled Data Memory (TCDM). Each tile has access to the global L2 and to a subset of other tiles' L1, accessing the latter via on-chip remote direct memory access (RDMA). Inter-tile and global communication is carried out through AXI-based narrow (32-bit) and wide (256-bit) NoC channels in [FlooNoC](https://github.com/pulp-platform/FlooNoC). External tiles access the L1 through an OpenBus Interface ([OBI](https://github.com/pulp-platform/obi)) XBAR, the control core through its own HCI port. Each tile is controlled by a [CV32E40P](https://github.com/pulp-platform/cv32e40p) or [CV32E40X](https://github.com/openhwgroup/cv32e40x) main core (`core=`). Control of iDMA, RedMulE, FractalSync, Spatz CC, and the PULP cluster follows a memory-mapped model, with the Event Unit handling event aggregation for system control. `magia_tile` wraps `magia_isle` (cores, accelerators, L1, crossbars and Event Unit) with the FlooNoC network interface and router. What a tile contains is set by its `TileCfg` (`magia_tile_cfg_t` in `hw/mesh/magia_pkg.sv`). #### PULP Cluster -Each tile embeds a cluster of `TileCfg.Cluster.NumCores` (8 by default) [CV32E40P](https://github.com/pulp-platform/cv32e40p) cores. Cluster cores share a Snitch instruction cache with an AXI refill path to L2, each core reaches the tile's L1 through its own HCI port and everything else (accelerator registers, PULP_CTRL, remote memory) through its own OBI master port into the tile crossbar. Cluster cores receive interrupts exclusively from the tile CSR (`PULP_CTRL`) — they are not connected to the Event Unit. The main core dispatches tasks to the cluster via the `PULP_CTRL` register block (`0x1740`), which provides: binary entry point (`PULP_BINARY`), per-core MEI dispatch (`PULP_START`), task function pointer (`PULP_TASKBIN`), data pointer (`PULP_DATA`), completion quorum (`PULP_NB_CORES_TO_WAIT`), and readiness/done handshake registers (`PULP_READY`, `PULP_DONE`). When the done quorum is reached, the tile CSR raises EU bit 12 on the main core's Event Unit, allowing the main core to sleep in WFE until the cluster finishes. +Each tile embeds a cluster of `TileCfg.Cluster.NumCores` (8 by default) [CV32E40P](https://github.com/pulp-platform/cv32e40p) cores. Cluster cores share a Snitch instruction cache with an AXI refill path to L2; each core reaches the tile's L1, stack and reserved region through its own HCI port and everything else (accelerator registers, `PULP_CTRL`, remote memory) through its own OBI master port into the tile crossbar. The cluster has its own [Event Unit](https://github.com/pulp-platform/event_unit_flex), separate from the main core's, with a dispatch FIFO, hardware barriers and a hardware mutex. The main core dispatches a task to cluster core 0 through the `PULP_CTRL` register block (`0x1740`); core 0 can then spread it over the other cores through the cluster Event Unit. When the task ends, `PULP_CTRL` raises EU bit 12 on the main core's Event Unit. ### Mesh Replicating the MAGIA tile, we scale up to a two-dimensional (2D) mesh of compute tiles. Each tile takes its `TileCfg` from the `TILE_CFGS` parameter of `magia` (`HOMO_TILE_CFGS` by default). The NoC allows access to the global west-side L2 through row-side interfaces, while tiles exchange traffic through FlooNoC router. The mesh uses XY routing and carries both AXI narrow channels (32-bit) and AXI wide channels (256-bit), with protocol conversion handled by per-tile Network Interfaces (NIs). @@ -271,11 +271,13 @@ Private map: the same range in every tile, each tile maps it to its own copy: | *RedMulE CTRL* | `0x0000_0100-0x0000_01FF` | | *iDMA CTRL* | `0x0000_0200-0x0000_05FF` | | *FractalSync CTRL*| `0x0000_0600-0x0000_06FF` | -| *Event Unit* | `0x0000_0700-0x0000_16FF` | +| *Ctrl-core Event Unit* | `0x0000_0700-0x0000_16FF` | | *Spatz CTRL* | `0x0000_1700-0x0000_173F` | | *PULP CTRL* | `0x0000_1740-0x0000_17FF` | | *Collective CTRL* | `0x0000_1800-0x0000_18FF` | -| *Reserved* | `0x0000_1900-0x0000_FFFF` | +| *Cluster Event Unit (direct)* | `0x0000_1900-0x0000_28FF` | +| *Cluster Event Unit (SoC-side)* | `0x0000_2900-0x0000_38FF` | +| *Reserved* | `0x0000_3900-0x0000_FFFF` | | *Stack* | `0x0001_0000-0x0001_FFFF` | | *Spatz BootROM* | `0x1000_0000-0x1000_00FF` | @@ -294,7 +296,7 @@ Software/test utility addresses (used by SW runtime and testbench VIP): | Region | Address | Notes | |-------------------|--------------------------------------------|-------| | *Test End* | `0xCCFF_0000` | Exit code location used by SW runtime/tests | -| *String (utoa)* | `0x0000_1900` | String scratch location (`RESERVED_START + STR_OFFSET`) | +| *String (utoa)* | `0x0000_3900` | String scratch location (`RESERVED_START + STR_OFFSET`) | | *Print (stderr)* | `0xFFFF_0000` | Memory-mapped stderr sink in simulation VIP | | *Print (stdio)* | `0xFFFF_0004` | Memory-mapped stdio sink in simulation VIP | | *Synch.* | `tile_base + 0x000F_F000` | Top 4 KiB of L1 (`L1_BASE + SYNC_OFFSET`) | @@ -343,17 +345,16 @@ A store is then tagged collective if it is written at `COLLECTIVE_ADDR_OFFSET` ( 3. A store at address `0xB000_0000` is then interpreted as a collective transaction ### PULP Cluster programming flow -The PULP cluster uses a bare-metal dynamic dispatch model. The cluster binary is compiled as a position-independent ELF (origin `0x0`), converted to a flat binary, and embedded in the CV32 ELF as a byte array in the `.pulp_binary` section (see `sw/kernel_pulp/`). +The PULP cluster uses a bare-metal, two-level dispatch model. The cluster binary is compiled as a position-independent ELF (origin `0x0`), converted to a flat binary, and embedded in the CV32 ELF as a byte array in the `.pulp_binary` section (see `sw/kernel_pulp/`). -**Dispatch flow** (`sw/utils/cluster_utils.h`, `sw/utils/magia_pulp_utils.h`): +**Main core → cluster core 0** (`sw/utils/cluster_utils.h`, `sw/utils/magia_pulp_utils.h`): -1. `cluster_boot(binary_start)` — writes `PULP_BINARY`, asserts `CLK_EN`, polls `PULP_READY` until all cluster cores have armed their dispatcher loop. -2. `cluster_arm_done_event()` — clears the CV32 Event Unit buffer and enables only EU bit 12 (cluster-done), avoiding spurious wakeups from stale RedMulE/iDMA events. -3. `cluster_dispatch_task(task_addr, core_mask)` — writes `NB_CORES_TO_WAIT`, `TASKBIN`, then `PULP_START = core_mask`, which fires a per-core MEI to each selected core. Returns once all selected cores have ACK'd (i.e., `PULP_START` self-clears). -4. `cluster_wait_done_eu()` — the control core sleeps on a blocking Event Unit load (`cv.elw` on CV32E40P, `lw` on CV32E40X) until EU bit 12 fires (PULP_DONE quorum reached). -5. `cluster_stop()` — de-asserts `CLK_EN` to gate the cluster clock. +1. `cluster_boot(binary_start)` — writes `PULP_BINARY`, sets `PULP_FETCH_EN`, polls `PULP_READY` until all cluster cores have booted. +2. `cluster_arm_done_event()` — clears the CV32 Event Unit buffer and enables only EU bit 12 (cluster-done). +3. `cluster_dispatch_task(task_addr)` — writes `PULP_TASKBIN`/`PULP_DATA` and rings `PULP_START`; returns once core 0 has taken the task. +4. `cluster_wait_done_eu()` — the control core sleeps on a blocking Event Unit load (`cv.elw` on CV32E40P, `lw` on CV32E40X) until EU bit 12 fires. -Each cluster core runs a dispatcher loop that waits in `WFI` for the MEI, reads `PULP_TASKBIN`/`PULP_DATA` from the trap handler, calls the task function, writes `PULP_DONE`, and re-enters `WFI`. +**Core 0 → the other cores** (`sw/utils/cluster_utils.h`): `pi_cl_team_fork(n, entry, arg)` runs `entry(arg)` on `n` cores through the cluster Event Unit and ends on a hardware barrier, as in the pulp-sdk API. Cluster task sources live under `sw/tests//pulp_task/`. A test directory containing a `pulp_task/` subdirectory automatically triggers the dual-binary build flow in the Makefile. ## 🧰 Changing number of tiles diff --git a/doc/MAGIA.png b/doc/MAGIA.png deleted file mode 100644 index 1e4750a6..00000000 Binary files a/doc/MAGIA.png and /dev/null differ diff --git a/doc/MAGIA_v4.png b/doc/MAGIA_v4.png new file mode 100644 index 00000000..fe7d75f7 Binary files /dev/null and b/doc/MAGIA_v4.png differ diff --git a/hw/mesh/magia.sv b/hw/mesh/magia.sv index 37a6e21b..49114b0e 100644 --- a/hw/mesh/magia.sv +++ b/hw/mesh/magia.sv @@ -217,12 +217,7 @@ module magia .irq_i ( irq_i[i*N_TILES_X+j] ), - // Tile expects [N_CLUSTER_CORES:0] (1 main + 8 cluster cores). - // Replicate the single top-level debug_req_i bit across all cores; - // implicit name-based connection would leave bits [N:1] unconnected (X) - // and X-propagate into cv32e40p_controller.debug_req_pending, corrupting - // ctrl_fsm_cs (observed: ctrl_fsm_cs=0x1xx, debug_req_pending=x). - .debug_req_i ( '0 ), + .debug_req_i ( debug_req_i ), .debug_havereset_o ( debug_havereset_o[i*N_TILES_X+j] ), .debug_running_o ( debug_running_o[i*N_TILES_X+j] ), .debug_halted_o ( debug_halted_o[i*N_TILES_X+j] ), diff --git a/hw/tile/cluster_event_map.sv b/hw/tile/cluster_event_map.sv index 99601dac..55a9e5ed 100644 --- a/hw/tile/cluster_event_map.sv +++ b/hw/tile/cluster_event_map.sv @@ -1,8 +1,8 @@ /* * Copyright (C) 2023-2024 ETH Zurich and University of Bologna * - * Licensed under the Solderpad Hardware License, Version 0.51 - * (the "License"); you may not use this file except in compliance + * Licensed under the Solderpad Hardware License, Version 0.51 + * (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://solderpad.org/licenses/SHL-0.51 @@ -16,8 +16,7 @@ * * Authors: Luca Balboni * - * Simple cluster event mapping module for MAGIA project - * This module maps various event types to the final cluster event outputs + * Cluster event mapping: control-core layout for NB_CORES == 1, PULP cluster layout otherwise */ module cluster_event_map #( @@ -40,18 +39,31 @@ module cluster_event_map #( output logic [NB_CORES-1:0][31:0] events_mapped_o ); - // Simple event mapping for each core + // NB_CORES == 1 selects the control-core layout, so a 1-core cluster would get it too for (genvar i = 0; i < NB_CORES; i++) begin : gen_event_mapping - assign events_mapped_o[i] = { - cluster_events_i[i][31:16], // [31:16] Custom cluster events (upper 16 bits) - cluster_events_i[i][15:12], // [15:12] PULP cluster events - acc_events_i[i], // [11:8] Accelerator events - 2'b0, // [7:6] Reserved - timer_events_i[i], // [5:4] Timer events - dma_events_i[i], // [3:2] DMA events - dispatch_events_i[i], // [1] Dispatch event - barrier_events_i[i] | mutex_events_i[i] | periph_fifo_event_i // [0] Combined sync/periph events - }; + if (NB_CORES == 1) begin : gen_ctrl_core_map + assign events_mapped_o[i] = { + cluster_events_i[i][31:16], // [31:16] Custom cluster events (pass-through) + cluster_events_i[i][15:12], // [15:12] Custom cluster events (pass-through) + acc_events_i[i], // [11:8] Accelerator events + sw_events_i[i][1:0], // [7:6] Software events + timer_events_i[i], // [5:4] Timer events + dma_events_i[i], // [3:2] DMA events + dispatch_events_i[i], // [1] Dispatch event + barrier_events_i[i] | mutex_events_i[i] | periph_fifo_event_i // [0] Combined sync/periph events + }; + end else begin : gen_cluster_map + assign events_mapped_o[i] = { + cluster_events_i[i][31:19], // [31:19] Custom cluster events (unchanged position) + dispatch_events_i[i], // [18] PULP_DISPATCH_EVENT + mutex_events_i[i], // [17] PULP_MUTEX_EVENT + barrier_events_i[i], // [16] PULP_HW_BAR_EVENT + cluster_events_i[i][15:12], // [15:12] Custom cluster events (unchanged position) + sw_events_i[i], // [11:4] Software events (no acc/timer events in the cluster EU) + dma_events_i[i], // [3:2] DMA events (tied to '0 in the cluster EU) + 2'b0 // [1:0] Reserved + }; + end end endmodule : cluster_event_map \ No newline at end of file diff --git a/hw/tile/ctrl_demux.sv b/hw/tile/ctrl_demux.sv index 594d7dd4..69437272 100644 --- a/hw/tile/ctrl_demux.sv +++ b/hw/tile/ctrl_demux.sv @@ -14,11 +14,10 @@ * limitations under the License. * SPDX-License-Identifier: SHL-0.51 * - * Authors: Luca Balboni, Fondazione Chips-IT + * Author: Luca Balboni, Fondazione Chips-IT * - * MAGIA control demux: demuxes the single control port of the OBI crossbar towards - * the control registers of each enabled unit -*/ + * MAGIA control demux: splits the control port of the OBI crossbar towards the enabled control units + */ module ctrl_demux import magia_tile_pkg::*; diff --git a/hw/tile/magia_cluster_wrap.sv b/hw/tile/magia_cluster_wrap.sv index 8fdf8237..5a762ad2 100644 --- a/hw/tile/magia_cluster_wrap.sv +++ b/hw/tile/magia_cluster_wrap.sv @@ -36,14 +36,10 @@ module magia_cluster_wrap parameter type hci_req_t = magia_tile_pkg::core_hci_data_req_t, parameter type hci_rsp_t = magia_tile_pkg::core_hci_data_rsp_t )( - input logic sys_clk_i, + input logic clk_i, input logic rst_ni, input logic test_mode_i, - // Per-core gated clocks (one clock gate per core, driven in magia_tile). - input logic [NClusterCores-1:0] cluster_clk_i, - input logic [NClusterCores-1:0] debug_req_i, - // Tile hart-id base (control core is 0; cluster cores start after the tiles' ctrl cores) input logic [31:0] mhartid_i, @@ -53,7 +49,11 @@ module magia_cluster_wrap // Control outputs from the cluster CSR (in magia_tile). input logic [31:0] cluster_boot_addr_i [NClusterCores-1:0], input logic [NClusterCores-1:0] cluster_fetch_enable_i, - input logic [NClusterCores-1:0] cluster_start_irq_i, // 1-cycle dispatch IRQ pulse per core + input logic cluster_start_i, // 1-cycle pulse injected into cluster Event Unit as EU_OTHER_CLUSTER_START + + // Memory-mapped (speriph) port of the cluster-private Event Unit, on the tile OBI xbar + input magia_tile_pkg::core_obi_data_req_t cluster_eu_obi_req_i, + output magia_tile_pkg::core_obi_data_rsp_t cluster_eu_obi_rsp_o, // Per-core OBI data manager ports (peripheral path -> tile OBI crossbar) output magia_tile_pkg::core_obi_data_req_t [NClusterCores-1:0] cluster_obi_data_req_o, @@ -68,11 +68,15 @@ module magia_cluster_wrap input magia_tile_pkg::core_instr_rsp_t [NClusterCores-1:0] cluster_instr_rsp_i ); - // Data-demux slave map: TCDM must match idx of tcdm_rules_i - localparam int unsigned CLUSTER_DATA_N_SLV = 2; - localparam int unsigned CLUSTER_DATA_TCDM_IDX = 0; - localparam int unsigned CLUSTER_DATA_OBI_IDX = 1; + // Data-demux slave map: TCDM must match idx of tcdm_rules_i, EU is the per-core Event Unit direct link + localparam int unsigned CLUSTER_DATA_DEMUX_N_SLV = 3; + localparam int unsigned CLUSTER_DATA_DEMUX_TCDM_IDX = 0; + localparam int unsigned CLUSTER_DATA_DEMUX_OBI_IDX = 1; + localparam int unsigned CLUSTER_DATA_DEMUX_EU_IDX = 2; + // ------------------------------------------------------------------------- + // Per-core sleep status, data/instruction interfaces and interrupt signals + // ------------------------------------------------------------------------- logic [NClusterCores-1:0] cluster_core_sleep; // Cluster core data interface (CV32E40P native channel type). @@ -80,42 +84,52 @@ module magia_cluster_wrap magia_tile_pkg::cv32e40p_core_data_rsp_t [NClusterCores-1:0] cluster_data_rsp; // Per-core demux downstream ports ([i][TCDM], [i][OBI]). - magia_tile_pkg::cv32e40p_core_data_req_t [NClusterCores-1:0][CLUSTER_DATA_N_SLV-1:0] cluster_ddemux_req; - magia_tile_pkg::cv32e40p_core_data_rsp_t [NClusterCores-1:0][CLUSTER_DATA_N_SLV-1:0] cluster_ddemux_rsp; + magia_tile_pkg::cv32e40p_core_data_req_t [NClusterCores-1:0][CLUSTER_DATA_DEMUX_N_SLV-1:0] cluster_data_demux_req; + magia_tile_pkg::cv32e40p_core_data_rsp_t [NClusterCores-1:0][CLUSTER_DATA_DEMUX_N_SLV-1:0] cluster_data_demux_rsp; - // Dispatch IRQ of each core and the interrupt lines it drives - logic [NClusterCores-1:0] cluster_irq_ack; - logic [NClusterCores-1:0][magia_tile_pkg::CLIC_ID_W_CLUSTER-1:0] cluster_irq_id; - logic [NClusterCores-1:0] cluster_start_irq_pending_d, cluster_start_irq_pending_q; + // Per-core 32-bit interrupt vector logic [NClusterCores-1:0][31:0] cluster_irq_vec; - // Dispatch IRQ is a 1-cycle pulse from the CSR: hold it until the core acks - assign cluster_start_irq_pending_d = ~cluster_irq_ack & (cluster_start_irq_i | cluster_start_irq_pending_q); + // Event Unit per-core clock enables + logic [NClusterCores-1:0] cluster_eu_clock_en; - always_ff @(posedge sys_clk_i or negedge rst_ni) begin - if (!rst_ni) begin - cluster_start_irq_pending_q <= '0; - end else begin - cluster_start_irq_pending_q <= cluster_start_irq_pending_d; - end - end + // Per-core Event Unit direct link + logic [NClusterCores-1:0] cluster_eu_direct_req; + logic [NClusterCores-1:0][31:0] cluster_eu_direct_addr; + logic [NClusterCores-1:0] cluster_eu_direct_wen; + logic [NClusterCores-1:0][31:0] cluster_eu_direct_wdata; + logic [NClusterCores-1:0][3:0] cluster_eu_direct_be; + logic [NClusterCores-1:0] cluster_eu_direct_gnt; + logic [NClusterCores-1:0] cluster_eu_direct_rvalid; + logic [NClusterCores-1:0][31:0] cluster_eu_direct_rdata; + logic [NClusterCores-1:0] cluster_eu_direct_err; - // Only the machine external interrupt (bit 11) is used, by the pending dispatch IRQ - for (genvar k = 0; k < NClusterCores; k++) begin : gen_cluster_irq_vec - assign cluster_irq_vec[k] = {20'b0, cluster_start_irq_pending_q[k], 11'b0}; - end + // The cluster Event Unit is private: from outside it sees only the dispatch pulse of the cluster CSR + logic [NClusterCores-1:0][31:0] cluster_eu_other_events; + + // Events wake cv.elw through the EU; CPU interrupts are disconnected + assign cluster_irq_vec = '0; // --------------------------------------------------------------------------- // Cluster cores (always CV32E40P) // --------------------------------------------------------------------------- for (genvar i = 0; i < NClusterCores; i++) begin : gen_core + logic core_clk; + + tc_clk_gating i_core_clock_gate ( + .clk_i ( clk_i ), + .en_i ( cluster_eu_clock_en[i] ), + .test_en_i ( test_mode_i ), + .clk_o ( core_clk ) + ); + `ifndef CORE_TRACES cv32e40p_top #( `else cv32e40p_wrapper #( `endif .COREV_PULP ( 1 ), - .COREV_CLUSTER ( 0 ), + .COREV_CLUSTER ( 1 ), .FPU ( FPU ), .ZFINX ( magia_tile_pkg::ZFINX_CLUSTER ), .FPU_ADDMUL_LAT ( 1 ), // Match C_LAT_FP32=1 in fpnew wrapper @@ -123,11 +137,9 @@ module magia_cluster_wrap .NUM_MHPMCOUNTERS ( 29 ) ) i_cv32e40p_core ( // Clock and Reset - .clk_i ( cluster_clk_i[i] ), // Per-core gated clock + .clk_i ( core_clk ), // Per-core EU-controlled clock .rst_ni ( rst_ni ), - - // Clock always enabled; sleep/wake via WFI / MEI (dispatch IRQ) - .pulp_clock_en_i ( 1'b0 ), // WFI/MEI; cluster event loads disabled + .pulp_clock_en_i ( cluster_eu_clock_en[i] ), .scan_cg_en_i ( test_mode_i ), .boot_addr_i ( cluster_boot_addr_i[i] ), // From cluster CSR, dynamic per tile .mtvec_addr_i ( cluster_boot_addr_i[i] ), // mtvec defaults to boot vector; SW can override via csrw @@ -149,16 +161,16 @@ module magia_cluster_wrap .data_gnt_i ( cluster_data_rsp[i].gnt ), .data_rvalid_i ( cluster_data_rsp[i].rvalid ), .data_rdata_i ( cluster_data_rsp[i].rdata ), - // Interrupts: only the dispatch IRQ (MEI bit 11) from the cluster CSR; no event unit + // CPU interrupt inputs are disabled; use EU event waits / polling .irq_i ( cluster_irq_vec[i] ), - .irq_ack_o ( cluster_irq_ack[i] ), - .irq_id_o ( cluster_irq_id[i] ), + .irq_ack_o ( ), + .irq_id_o ( ), - .debug_req_i ( debug_req_i[i] ), + .debug_req_i ( 1'b0 ), // Debug status outputs unused: no tile-level output exposes per-cluster-core debug status. - .debug_havereset_o ( ), - .debug_running_o ( ), - .debug_halted_o ( ), + .debug_havereset_o ( ), + .debug_running_o ( ), + .debug_halted_o ( ), // CPU control .fetch_enable_i ( cluster_fetch_enable_i[i] ), .core_sleep_o ( cluster_core_sleep[i] ) @@ -166,25 +178,34 @@ module magia_cluster_wrap end // --------------------------------------------------------------------------- - // Per-core data demux: TCDM -> dedicated HCI port, rest -> OBI xbar + // Per-core data demux: TCDM -> dedicated HCI port, EU window -> Event Unit direct link, rest -> OBI xbar // --------------------------------------------------------------------------- for (genvar i = 0; i < NClusterCores; i++) begin : gen_cluster_data_demux + magia_tile_pkg::obi_xbar_rule_t [magia_tile_pkg::NUM_TCDM_RULES:0] cluster_data_rules; + + assign cluster_data_rules[magia_tile_pkg::NUM_TCDM_RULES-1:0] = tcdm_rules_i; + assign cluster_data_rules[magia_tile_pkg::NUM_TCDM_RULES] = '{idx: CLUSTER_DATA_DEMUX_EU_IDX, + start_addr: magia_tile_pkg::CLUSTER_EU_DIRECT_START, + end_addr: magia_tile_pkg::CLUSTER_EU_DIRECT_END}; + + // EU accesses serialize, like cv.elw: a pipelined load must not overtake a blocking EU wait core_data_demux #( - .NumSlv ( CLUSTER_DATA_N_SLV ), - .NumRules ( magia_tile_pkg::NUM_TCDM_RULES ), - .DefaultSlv ( CLUSTER_DATA_OBI_IDX ), - .req_t ( magia_tile_pkg::cv32e40p_core_data_req_t ), - .rsp_t ( magia_tile_pkg::cv32e40p_core_data_rsp_t ) + .NumSlv ( CLUSTER_DATA_DEMUX_N_SLV ), + .NumRules ( magia_tile_pkg::NUM_TCDM_RULES + 1 ), + .DefaultSlv ( CLUSTER_DATA_DEMUX_OBI_IDX ), + .SerialSlv ( CLUSTER_DATA_DEMUX_N_SLV'(1 << CLUSTER_DATA_DEMUX_EU_IDX) ), + .req_t ( magia_tile_pkg::cv32e40p_core_data_req_t ), + .rsp_t ( magia_tile_pkg::cv32e40p_core_data_rsp_t ) ) i_cluster_data_demux ( - .clk_i ( cluster_clk_i[i] ), - .rst_ni ( rst_ni ), - .clk_en_i ( 1'b1 ), - .core_data_req_i ( cluster_data_req[i] ), - .core_data_atomic_i ( 1'b0 ), - .core_data_rsp_o ( cluster_data_rsp[i] ), - .addr_map_i ( tcdm_rules_i ), - .slv_data_req_o ( cluster_ddemux_req[i] ), - .slv_data_rsp_i ( cluster_ddemux_rsp[i] ) + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .clk_en_i ( cluster_eu_clock_en[i] ), + .core_data_req_i ( cluster_data_req[i] ), + .core_data_atomic_i ( 1'b0 ), + .core_data_rsp_o ( cluster_data_rsp[i] ), + .addr_map_i ( cluster_data_rules ), + .slv_data_req_o ( cluster_data_demux_req[i] ), + .slv_data_rsp_i ( cluster_data_demux_rsp[i] ) ); end @@ -195,13 +216,13 @@ module magia_cluster_wrap // --- OBI (peripheral) path --- cv32e40p_data2obi_req i_cluster_data2obi ( - .data_req_i ( cluster_ddemux_req[i][CLUSTER_DATA_OBI_IDX] ), + .data_req_i ( cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_OBI_IDX] ), .obi_req_o ( cluster_obi_data_req_o[i] ) ); cv32e40p_obi2data_rsp i_cluster_obi2data ( .obi_rsp_i ( cluster_obi_data_rsp_i[i] ), - .data_rsp_o ( cluster_ddemux_rsp[i][CLUSTER_DATA_OBI_IDX] ) + .data_rsp_o ( cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_OBI_IDX] ) ); // --- TCDM (L1) path: native mem -> OBI -> HCI dedicated interconnect port --- @@ -209,13 +230,13 @@ module magia_cluster_wrap magia_tile_pkg::core_obi_data_rsp_t cluster_tcdm_obi_rsp; cv32e40p_data2obi_req i_cluster_tcdm_data2obi ( - .data_req_i ( cluster_ddemux_req[i][CLUSTER_DATA_TCDM_IDX] ), + .data_req_i ( cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_TCDM_IDX] ), .obi_req_o ( cluster_tcdm_obi_req ) ); cv32e40p_obi2data_rsp i_cluster_tcdm_obi2data ( .obi_rsp_i ( cluster_tcdm_obi_rsp ), - .data_rsp_o ( cluster_ddemux_rsp[i][CLUSTER_DATA_TCDM_IDX] ) + .data_rsp_o ( cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_TCDM_IDX] ) ); obi2hci_req #( @@ -235,4 +256,85 @@ module magia_cluster_wrap ); end + // --------------------------------------------------------------------------- + // Cluster-private Event Unit + // --------------------------------------------------------------------------- + + // Demux EU port -> EU direct link, with the address made relative to the EU window + for (genvar i = 0; i < NClusterCores; i++) begin : gen_cluster_eu_direct + assign cluster_eu_direct_req[i] = cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_EU_IDX].req; + assign cluster_eu_direct_addr[i] = cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_EU_IDX].addr - magia_tile_pkg::CLUSTER_EU_DIRECT_START; + assign cluster_eu_direct_wen[i] = ~cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_EU_IDX].we; + assign cluster_eu_direct_wdata[i] = cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_EU_IDX].wdata; + assign cluster_eu_direct_be[i] = cluster_data_demux_req[i][CLUSTER_DATA_DEMUX_EU_IDX].be; + + assign cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_EU_IDX].gnt = cluster_eu_direct_gnt[i]; + assign cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_EU_IDX].rvalid = cluster_eu_direct_rvalid[i]; + assign cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_EU_IDX].rdata = cluster_eu_direct_rdata[i]; + assign cluster_data_demux_rsp[i][CLUSTER_DATA_DEMUX_EU_IDX].err = cluster_eu_direct_err[i]; + + // Only event source wired from outside the cluster: the control-core pulse, which reaches core 0 only + always_comb begin + cluster_eu_other_events[i] = '0; + if (i == 0) begin + cluster_eu_other_events[i][magia_tile_pkg::EU_OTHER_CLUSTER_START] = cluster_start_i; + end + end + end + + magia_event_unit #( + .NB_CORES ( NClusterCores ), + .NB_SW_EVT ( magia_tile_pkg::CLUSTER_EU_NB_SW_EVT ), + .NB_BARR ( NClusterCores ), // as in pulp_cluster + .NB_HW_MUT ( magia_tile_pkg::CLUSTER_EU_NB_HW_MUT ), + .MUTEX_MSG_W ( magia_tile_pkg::CLUSTER_EU_MUTEX_MSG_W ), + .DISP_FIFO_DEPTH ( magia_tile_pkg::CLUSTER_EU_DISP_FIFO_DEPTH ), + .EVNT_WIDTH ( magia_tile_pkg::CLUSTER_EU_EVNT_WIDTH ), + .SOC_FIFO_DEPTH ( magia_tile_pkg::CLUSTER_EU_SOC_FIFO_DEPTH ), + .EU_ADDR_START ( magia_tile_pkg::CLUSTER_EU_ADDR_START ), + .EU_ADDR_END ( magia_tile_pkg::CLUSTER_EU_ADDR_END ) + ) i_cluster_event_unit ( + // Keep the Event Unit on the parent clock so sleeping cores can wake + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_mode_i ( test_mode_i ), + + // No tile accelerator / DMA / timer event reaches the cluster Event Unit + .acc_events_i ( '0 ), + .dma_events_i ( '0 ), + .timer_events_i ( '0 ), + .other_events_i ( cluster_eu_other_events ), + + // IRQ outputs are disconnected; events are cleared through EU accesses + .core_irq_req_o ( ), + .core_irq_id_o ( ), + .core_irq_ack_i ( '0 ), + .core_irq_ack_id_i ( '0 ), + + .core_busy_i ( ~cluster_core_sleep ), + .core_clock_en_o ( cluster_eu_clock_en ), + + // Cluster cores have no debug interface in MAGIA (debug_req_i tied to 0 above) + .dbg_req_i ( '0 ), + .core_dbg_req_o ( ), + + .eu_direct_req_i ( cluster_eu_direct_req ), + .eu_direct_addr_i ( cluster_eu_direct_addr ), + .eu_direct_wen_i ( cluster_eu_direct_wen ), + .eu_direct_wdata_i ( cluster_eu_direct_wdata ), + .eu_direct_be_i ( cluster_eu_direct_be ), + .eu_direct_gnt_o ( cluster_eu_direct_gnt ), + .eu_direct_rvalid_o ( cluster_eu_direct_rvalid ), + .eu_direct_rdata_o ( cluster_eu_direct_rdata ), + .eu_direct_err_o ( cluster_eu_direct_err ), + + // Unused: ctrl-core -> core 0 notification goes through EU_OTHER_CLUSTER_START + .soc_periph_evt_valid_i ( 1'b0 ), + .soc_periph_evt_ready_o ( ), + .soc_periph_evt_data_i ( '0 ), + + .obi_req_i ( cluster_eu_obi_req_i ), + .obi_rsp_o ( cluster_eu_obi_rsp_o ) + ); + endmodule: magia_cluster_wrap diff --git a/hw/tile/magia_event_unit.sv b/hw/tile/magia_event_unit.sv index 15f1d20c..7c8cc4fb 100644 --- a/hw/tile/magia_event_unit.sv +++ b/hw/tile/magia_event_unit.sv @@ -15,22 +15,25 @@ * SPDX-License-Identifier: SHL-0.51 * * Authors: Luca Balboni - * - * Wrapper module for MAGIA Event Unit optimized for single-core systems -*/ + * + * MAGIA Event Unit wrapper (event_unit_flex), one for the control core and one for the PULP cluster + */ module magia_event_unit import magia_tile_pkg::*; #( - // MAGIA Event Unit Parameters - Optimized for single-core system - parameter int unsigned NB_CORES = 1, // Single core system - parameter int unsigned NB_SW_EVT = 1, // Minimal SW events for basic functionality - parameter int unsigned NB_BARR = 2, // Minimum 2 barriers (workaround: $clog2(1)=0 causes issues) - parameter int unsigned NB_HW_MUT = 2, // Minimum 2 mutexes (workaround: $clog2(1)=0 causes issues) - parameter int unsigned MUTEX_MSG_W = 32, // Mutex message width (unused but kept for compatibility) - parameter int unsigned DISP_FIFO_DEPTH = 1, // Minimal dispatcher FIFO (workaround for synthesis) + parameter int unsigned NB_CORES = 1, // Number of cores served by this instance + parameter int unsigned NB_SW_EVT = 1, // SW events + parameter int unsigned NB_BARR = 2, // HW barriers (minimum 2: $clog2(1)=0 causes issues) + parameter int unsigned NB_HW_MUT = 2, // HW mutexes (minimum 2: $clog2(1)=0 causes issues) + parameter int unsigned MUTEX_MSG_W = 32, // Mutex message width + parameter int unsigned DISP_FIFO_DEPTH = 1, // Dispatcher FIFO depth parameter int unsigned EVNT_WIDTH = 8, // SOC event width (external events) - parameter int unsigned SOC_FIFO_DEPTH = 8 // SOC event FIFO depth (external events) + parameter int unsigned SOC_FIFO_DEPTH = 8, // SOC event FIFO depth (external events) + + // Memory-mapped (speriph) window served by the OBI slave port below + parameter logic [magia_pkg::ADDR_W-1:0] EU_ADDR_START = magia_tile_pkg::CTRL_EU_ADDR_START, + parameter logic [magia_pkg::ADDR_W-1:0] EU_ADDR_END = magia_tile_pkg::CTRL_EU_ADDR_END ) ( // clock and reset @@ -69,6 +72,11 @@ import magia_tile_pkg::*; output logic [NB_CORES-1:0][31:0] eu_direct_rdata_o, output logic [NB_CORES-1:0] eu_direct_err_o, + // SoC/peripheral event + input logic soc_periph_evt_valid_i, + output logic soc_periph_evt_ready_o, + input logic [EVNT_WIDTH-1:0] soc_periph_evt_data_i, + // OBI slave connection input core_obi_data_req_t obi_req_i, output core_obi_data_rsp_t obi_rsp_o @@ -78,9 +86,6 @@ import magia_tile_pkg::*; XBAR_PERIPH_BUS #(.ID_WIDTH(NB_CORES+1)) eu_direct_link[NB_CORES-1:0](); XBAR_PERIPH_BUS #(.ID_WIDTH(NB_CORES+1)) speriph_slave(); - // Internal signals - logic soc_periph_evt_ready_internal; - // Convert abstract eu_direct interface to XBAR_PERIPH_BUS (one per core) // eu_direct_addr_i already contains relative offset (subtracted by demux) for (genvar k = 0; k < NB_CORES; k++) begin : gen_eu_direct_link @@ -98,13 +103,12 @@ import magia_tile_pkg::*; end // Address range check and offset calculation - localparam logic [magia_pkg::ADDR_W-1:0] EU_BASE_ADDR = magia_tile_pkg::EVENT_UNIT_ADDR_START; logic addr_in_range; logic [magia_pkg::ADDR_W-1:0] addr_offset; - - assign addr_in_range = (obi_req_i.a.addr >= magia_tile_pkg::EVENT_UNIT_ADDR_START) & - (obi_req_i.a.addr < magia_tile_pkg::EVENT_UNIT_ADDR_END); - assign addr_offset = obi_req_i.a.addr - EU_BASE_ADDR; + + assign addr_in_range = (obi_req_i.a.addr >= EU_ADDR_START) & + (obi_req_i.a.addr < EU_ADDR_END); + assign addr_offset = obi_req_i.a.addr - EU_ADDR_START; // OBI to XBAR_PERIPH_BUS conversion - pass RELATIVE address (offset from base) assign speriph_slave.req = obi_req_i.req & addr_in_range; @@ -150,9 +154,9 @@ import magia_tile_pkg::*; .core_clock_en_o ( core_clock_en_o ), .dbg_req_i ( dbg_req_i ), .core_dbg_req_o ( core_dbg_req_o ), - .soc_periph_evt_valid_i ( 1'b0 ), - .soc_periph_evt_ready_o ( soc_periph_evt_ready_internal ), - .soc_periph_evt_data_i ( '0 ), + .soc_periph_evt_valid_i ( soc_periph_evt_valid_i ), + .soc_periph_evt_ready_o ( soc_periph_evt_ready_o ), + .soc_periph_evt_data_i ( soc_periph_evt_data_i ), .speriph_slave ( speriph_slave ), .eu_direct_link ( eu_direct_link ) ); diff --git a/hw/tile/magia_isle.sv b/hw/tile/magia_isle.sv index a639043e..73fa797f 100644 --- a/hw/tile/magia_isle.sv +++ b/hw/tile/magia_isle.sv @@ -90,7 +90,7 @@ module magia_isle input logic eu_irq_ack_i, // Clears event eu_irq_ack_id_i input logic[4:0] eu_irq_ack_id_i, - input logic[magia_tile_pkg::N_CLUSTER_CORES:0] debug_req_i, // [0] control core, [N:1] cluster cores + input logic debug_req_i, // Control core, through its Event Unit output logic debug_havereset_o, output logic debug_running_o, output logic debug_halted_o, @@ -218,13 +218,15 @@ module magia_isle assign tile_l1_start_addr = magia_tile_pkg::L1_ADDR_START + mhartid_i*magia_tile_pkg::L1_TILE_OFFSET; assign tile_l1_end_addr = magia_tile_pkg::L1_ADDR_END + mhartid_i*magia_tile_pkg::L1_TILE_OFFSET; - assign tile_event_unit_start_addr = magia_tile_pkg::EVENT_UNIT_ADDR_START; - assign tile_event_unit_end_addr = magia_tile_pkg::EVENT_UNIT_ADDR_END; + assign tile_event_unit_start_addr = magia_tile_pkg::CTRL_EU_ADDR_START; + assign tile_event_unit_end_addr = magia_tile_pkg::CTRL_EU_ADDR_END; - // TCDM ranges of core data, idx 0 of the cluster data demux + // TCDM ranges of core data, idx 0 of every core data demux magia_tile_pkg::obi_xbar_rule_t [magia_tile_pkg::NUM_TCDM_RULES-1:0] tcdm_rules; - assign tcdm_rules[0] = '{idx: 0, start_addr: tile_l1_start_addr, end_addr: tile_l1_end_addr}; + // Reserved and stack are a private alias, the same in every tile, the L1 is shifted per tile + assign tcdm_rules[0] = '{idx: 0, start_addr: magia_tile_pkg::RESERVED_ADDR_START, end_addr: magia_tile_pkg::STACK_ADDR_END}; + assign tcdm_rules[1] = '{idx: 0, start_addr: tile_l1_start_addr, end_addr: tile_l1_end_addr }; /*******************************************************/ /** Tile Address Map End **/ @@ -241,12 +243,13 @@ module magia_isle logic fencei_flush_ack; // No CPU interrupts: the control core waits on the Event Unit - logic[magia_pkg::N_IRQ-1:0] irq; - logic[31:0] core_irq_vec; + logic[31:0] core_irq_vec; - assign irq = '0; assign core_irq_vec = '0; + // Debug request, forwarded by the Event Unit + logic eu_core_dbg_req; + `ifdef CV32E40X logic clic_irq; logic[magia_tile_pkg::CLIC_ID_W-1:0] clic_irq_id; @@ -344,7 +347,7 @@ module magia_isle .xif_mem_result_if ( xif_if.cpu_mem_result ), .xif_result_if ( xif_if.cpu_result ), - .irq_i ( irq ), + .irq_i ( core_irq_vec ), .clic_irq_i ( clic_irq ), .clic_irq_id_i ( clic_irq_id ), @@ -355,7 +358,7 @@ module magia_isle .fencei_flush_req_o ( fencei_flush_req ), .fencei_flush_ack_i ( fencei_flush_ack ), - .debug_req_i ( debug_req_i[0] ), + .debug_req_i ( eu_core_dbg_req ), .debug_havereset_o , .debug_running_o , .debug_halted_o , @@ -410,7 +413,7 @@ module magia_isle .irq_ack_o ( ), .irq_id_o ( ), - .debug_req_i ( debug_req_i[0] ), + .debug_req_i ( eu_core_dbg_req ), .debug_havereset_o ( debug_havereset_o ), .debug_running_o ( debug_running_o ), .debug_halted_o ( debug_halted_o ), @@ -592,71 +595,61 @@ module magia_isle /** Control Core Data Path Beginning **/ /*******************************************************/ - // Event Unit accesses take the direct link, everything else the OBI crossbar - localparam int unsigned CoreDataObi = 0; - localparam int unsigned CoreDataEu = 1; - localparam int unsigned CoreDataNumSlv = 2; + // TCDM ranges take an HCI port, the EU the direct link, atomics and the rest the OBI crossbar + magia_tile_pkg::core_data_req_t [magia_tile_pkg::CORE_DATA_DEMUX_N_SLV-1:0] core_data_demux_req; + magia_tile_pkg::core_data_rsp_t [magia_tile_pkg::CORE_DATA_DEMUX_N_SLV-1:0] core_data_demux_rsp; + magia_tile_pkg::obi_xbar_rule_t [magia_tile_pkg::NUM_TCDM_RULES:0] core_data_rules; + logic core_data_atomic; - magia_tile_pkg::obi_xbar_rule_t [0:0] core_data_rules; - magia_tile_pkg::core_data_req_t [CoreDataNumSlv-1:0] core_data_slv_req; - magia_tile_pkg::core_data_rsp_t [CoreDataNumSlv-1:0] core_data_slv_rsp; - magia_tile_pkg::core_data_rsp_t core_data_eu_rsp; - magia_tile_pkg::eu_direct_req_t eu_direct_req; - magia_tile_pkg::eu_direct_rsp_t eu_direct_rsp; - magia_tile_pkg::eu_direct_req_t eu_direct_req_cut; - magia_tile_pkg::eu_direct_rsp_t eu_direct_rsp_cut; + assign core_data_rules[magia_tile_pkg::NUM_TCDM_RULES-1:0] = tcdm_rules; + assign core_data_rules[magia_tile_pkg::NUM_TCDM_RULES] = '{idx: magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX, + start_addr: tile_event_unit_start_addr, + end_addr: tile_event_unit_end_addr}; - assign core_data_rules[0] = '{idx: CoreDataEu, start_addr: tile_event_unit_start_addr, end_addr: tile_event_unit_end_addr}; +`ifdef CV32E40X + assign core_data_atomic = core_data_req.atop != '0; +`else + assign core_data_atomic = 1'b0; +`endif - // EU accesses serialize, like cv.elw: a pipelined load must not overtake a blocking EU wait + // EU accesses serialize, like cv.elw; TCDM accesses skip the AMO resolver behind the OBI crossbar core_data_demux #( - .NumSlv ( CoreDataNumSlv ), - .NumRules ( 1 ), - .DefaultSlv ( CoreDataObi ), - .SerialSlv ( CoreDataNumSlv'(1 << CoreDataEu) ), - .req_t ( magia_tile_pkg::core_data_req_t ), - .rsp_t ( magia_tile_pkg::core_data_rsp_t ) + .NumSlv ( magia_tile_pkg::CORE_DATA_DEMUX_N_SLV ), + .NumRules ( magia_tile_pkg::NUM_TCDM_RULES + 1 ), + .DefaultSlv ( magia_tile_pkg::CORE_DATA_DEMUX_OBI_IDX ), + .SerialSlv ( magia_tile_pkg::CORE_DATA_DEMUX_N_SLV'(1 << magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX) ), + .BypassSlv ( magia_tile_pkg::CORE_DATA_DEMUX_N_SLV'(1 << magia_tile_pkg::CORE_DATA_DEMUX_TCDM_IDX) ), + .req_t ( magia_tile_pkg::core_data_req_t ), + .rsp_t ( magia_tile_pkg::core_data_rsp_t ) ) i_core_data_demux ( - .clk_i ( sys_clk ), - .rst_ni ( rst_ni ), - .clk_en_i ( core_clk_en ), - .core_data_req_i ( core_data_req ), - .core_data_atomic_i ( 1'b0 ), - .core_data_rsp_o ( core_data_rsp ), - .addr_map_i ( core_data_rules ), - .slv_data_req_o ( core_data_slv_req ), - .slv_data_rsp_i ( core_data_slv_rsp ) + .clk_i ( sys_clk ), + .rst_ni ( rst_ni ), + .clk_en_i ( core_clk_en ), + .core_data_req_i ( core_data_req ), + .core_data_atomic_i ( core_data_atomic ), + .core_data_rsp_o ( core_data_rsp ), + .addr_map_i ( core_data_rules ), + .slv_data_req_o ( core_data_demux_req ), + .slv_data_rsp_i ( core_data_demux_rsp ) ); - assign eu_direct_req.req = core_data_slv_req[CoreDataEu].req; - assign eu_direct_req.addr = core_data_slv_req[CoreDataEu].addr - tile_event_unit_start_addr; - assign eu_direct_req.wen = ~core_data_slv_req[CoreDataEu].we; - assign eu_direct_req.wdata = core_data_slv_req[CoreDataEu].wdata; - assign eu_direct_req.be = core_data_slv_req[CoreDataEu].be; + // Event Unit direct link, addressed relative to the EU window + magia_tile_pkg::eu_direct_req_t eu_direct_req; + magia_tile_pkg::eu_direct_rsp_t eu_direct_rsp; - always_comb begin - core_data_eu_rsp = '0; - core_data_eu_rsp.gnt = eu_direct_rsp.gnt; - core_data_eu_rsp.rvalid = eu_direct_rsp.rvalid; - core_data_eu_rsp.rdata = eu_direct_rsp.rdata; - core_data_eu_rsp.err = eu_direct_rsp.err; - end + assign eu_direct_req.req = core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].req; + assign eu_direct_req.addr = core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].addr - tile_event_unit_start_addr; + assign eu_direct_req.wen = ~core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].we; + assign eu_direct_req.wdata = core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].wdata; + assign eu_direct_req.be = core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].be; - assign core_data_slv_rsp[CoreDataEu] = core_data_eu_rsp; - - eu_direct_cut #( - .Bypass ( 1'b0 ), - .BypassReq ( 1'b0 ), - .BypassRsp ( 1'b0 ), - .NB_CORES ( 1 ) - ) i_eu_direct_cut ( - .clk_i ( sys_clk ), - .rst_ni ( rst_ni ), - .sbr_req_i ( eu_direct_req ), - .sbr_rsp_o ( eu_direct_rsp ), - .mgr_req_o ( eu_direct_req_cut ), - .mgr_rsp_i ( eu_direct_rsp_cut ) - ); + assign core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].gnt = eu_direct_rsp.gnt; + assign core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].rvalid = eu_direct_rsp.rvalid; + assign core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].rdata = eu_direct_rsp.rdata; + assign core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].err = eu_direct_rsp.err; +`ifdef CV32E40X + assign core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_EU_IDX].exokay = 1'b0; +`endif logic eu_direct_req_flat; logic [31:0] eu_direct_addr_flat; @@ -668,41 +661,90 @@ module magia_isle logic [31:0] eu_direct_rdata_flat; logic eu_direct_err_flat; - assign eu_direct_req_flat = eu_direct_req_cut.req; - assign eu_direct_addr_flat = eu_direct_req_cut.addr; - assign eu_direct_wen_flat = eu_direct_req_cut.wen; - assign eu_direct_wdata_flat = eu_direct_req_cut.wdata; - assign eu_direct_be_flat = eu_direct_req_cut.be; - assign eu_direct_rsp_cut.gnt = eu_direct_gnt_flat; - assign eu_direct_rsp_cut.rvalid = eu_direct_rvalid_flat; - assign eu_direct_rsp_cut.rdata = eu_direct_rdata_flat; - assign eu_direct_rsp_cut.err = eu_direct_err_flat; + assign eu_direct_req_flat = eu_direct_req.req; + assign eu_direct_addr_flat = eu_direct_req.addr; + assign eu_direct_wen_flat = eu_direct_req.wen; + assign eu_direct_wdata_flat = eu_direct_req.wdata; + assign eu_direct_be_flat = eu_direct_req.be; + assign eu_direct_rsp.gnt = eu_direct_gnt_flat; + assign eu_direct_rsp.rvalid = eu_direct_rvalid_flat; + assign eu_direct_rsp.rdata = eu_direct_rdata_flat; + assign eu_direct_rsp.err = eu_direct_err_flat; magia_tile_pkg::core_obi_data_req_t core_obi_data_req; magia_tile_pkg::core_obi_data_rsp_t core_obi_data_rsp; + // Control core L1 port, connected to HCI in the L1 section + magia_tile_pkg::core_obi_data_req_t core_l1_direct_obi_req; + magia_tile_pkg::core_obi_data_rsp_t core_l1_direct_obi_rsp; + tile_hci_data_req_t core_l1_direct_req; + tile_hci_data_rsp_t core_l1_direct_rsp; + `ifdef CV32E40X cv32e40x_data2obi_req i_core_data2obi_req ( - .data_req_i ( core_data_slv_req[CoreDataObi] ), - .obi_req_o ( core_obi_data_req ) + .data_req_i ( core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_OBI_IDX] ), + .obi_req_o ( core_obi_data_req ) ); cv32e40x_obi2data_rsp i_core_obi2data_rsp ( - .obi_rsp_i ( core_obi_data_rsp ), - .data_rsp_o ( core_data_slv_rsp[CoreDataObi] ) + .obi_rsp_i ( core_obi_data_rsp ), + .data_rsp_o ( core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_OBI_IDX] ) + ); + + cv32e40x_data2obi_req i_core_l1_direct_data2obi_req ( + .data_req_i ( core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_TCDM_IDX] ), + .obi_req_o ( core_l1_direct_obi_req ) + ); + + cv32e40x_obi2data_rsp i_core_l1_direct_obi2data_rsp ( + .obi_rsp_i ( core_l1_direct_obi_rsp ), + .data_rsp_o ( core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_TCDM_IDX] ) ); `else cv32e40p_data2obi_req i_core_data2obi_req ( - .data_req_i ( core_data_slv_req[CoreDataObi] ), - .obi_req_o ( core_obi_data_req ) + .data_req_i ( core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_OBI_IDX] ), + .obi_req_o ( core_obi_data_req ) ); cv32e40p_obi2data_rsp i_core_obi2data_rsp ( - .obi_rsp_i ( core_obi_data_rsp ), - .data_rsp_o ( core_data_slv_rsp[CoreDataObi] ) + .obi_rsp_i ( core_obi_data_rsp ), + .data_rsp_o ( core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_OBI_IDX] ) + ); + + cv32e40p_data2obi_req i_core_l1_direct_data2obi_req ( + .data_req_i ( core_data_demux_req[magia_tile_pkg::CORE_DATA_DEMUX_TCDM_IDX] ), + .obi_req_o ( core_l1_direct_obi_req ) + ); + + cv32e40p_obi2data_rsp i_core_l1_direct_obi2data_rsp ( + .obi_rsp_i ( core_l1_direct_obi_rsp ), + .data_rsp_o ( core_data_demux_rsp[magia_tile_pkg::CORE_DATA_DEMUX_TCDM_IDX] ) ); `endif +`ifndef SYNTHESIS + assert property (@(posedge sys_clk) disable iff (!rst_ni) + !(core_l1_direct_obi_req.req && core_l1_direct_obi_req.a.a_optional.atop != '0)) + else $fatal(1, "magia_isle: atomic on the control core's direct L1 link at 0x%08x", + core_l1_direct_obi_req.a.addr); +`endif + + obi2hci_req #( + .obi_req_t ( magia_tile_pkg::core_obi_data_req_t ), + .hci_req_t ( tile_hci_data_req_t ) + ) i_core_l1_direct_obi2hci_req ( + .obi_req_i ( core_l1_direct_obi_req ), + .hci_req_o ( core_l1_direct_req ) + ); + + hci2obi_rsp #( + .hci_rsp_t ( tile_hci_data_rsp_t ), + .obi_rsp_t ( magia_tile_pkg::core_obi_data_rsp_t ) + ) i_core_l1_direct_hci2obi_rsp ( + .hci_rsp_i ( core_l1_direct_rsp ), + .obi_rsp_o ( core_l1_direct_obi_rsp ) + ); + /*******************************************************/ /** Control Core Data Path End **/ /*******************************************************/ @@ -715,7 +757,7 @@ module magia_isle magia_tile_pkg::core_obi_data_req_t[ObiMgr.num_mgr-1:0] obi_xbar_slv_cut_req; magia_tile_pkg::core_obi_data_rsp_t[ObiMgr.num_mgr-1:0] obi_xbar_slv_cut_rsp; - // Subordinates, indexed by ObiSbr: l2, l1, eu, ctrl + // Subordinates, indexed by ObiSbr: l2, l1, eu, ctrl, cluster_eu magia_tile_pkg::core_obi_data_req_t[ObiSbr.num_sbr-1:0] core_mem_data_cut_req; magia_tile_pkg::core_obi_data_rsp_t[ObiSbr.num_sbr-1:0] core_mem_data_cut_rsp; magia_tile_pkg::core_obi_data_req_t[ObiSbr.num_sbr-1:0] core_mem_data_req; @@ -734,9 +776,13 @@ module magia_isle localparam int unsigned RuleL1 = 1; localparam int unsigned RuleRes = 2; localparam int unsigned RuleStack = 3; - localparam int unsigned RuleCtrl = 4; - localparam int unsigned RuleEu = 5; - localparam int unsigned RuleCtrlHi = 6; + localparam int unsigned RuleCtrl = 4; + localparam int unsigned RuleEu = 5; + localparam int unsigned RuleCtrlHi = 6; + localparam int unsigned RuleClusterEu = 7; + + localparam logic [magia_pkg::ADDR_W-1:0] CtrlHiEnd = TileCfg.EnCluster ? + magia_tile_pkg::CLUSTER_EU_ADDR_START : magia_tile_pkg::TILE_PERIPH_ADDR_END; magia_tile_pkg::obi_xbar_rule_t[ObiSbr.num_rules-1:0] obi_xbar_rule; logic[ObiMgr.num_mgr-1:0] obi_xbar_en_default_idx; @@ -746,10 +792,18 @@ module magia_isle assign obi_xbar_rule[RuleL1] = '{idx: ObiSbr.l1, start_addr: tile_l1_start_addr, end_addr: tile_l1_end_addr }; assign obi_xbar_rule[RuleRes] = '{idx: ObiSbr.l1, start_addr: magia_tile_pkg::RESERVED_ADDR_START, end_addr: magia_tile_pkg::RESERVED_ADDR_END }; assign obi_xbar_rule[RuleStack] = '{idx: ObiSbr.l1, start_addr: magia_tile_pkg::STACK_ADDR_START, end_addr: magia_tile_pkg::STACK_ADDR_END }; - // The control window is split around the Event Unit: every address matches one rule + // The control window is split around the Event Units: every address matches one rule assign obi_xbar_rule[RuleCtrl] = '{idx: ObiSbr.ctrl, start_addr: magia_tile_pkg::TILE_PERIPH_BASE, end_addr: tile_event_unit_start_addr }; assign obi_xbar_rule[RuleEu] = '{idx: ObiSbr.eu, start_addr: tile_event_unit_start_addr, end_addr: tile_event_unit_end_addr }; - assign obi_xbar_rule[RuleCtrlHi] = '{idx: ObiSbr.ctrl, start_addr: tile_event_unit_end_addr, end_addr: magia_tile_pkg::TILE_PERIPH_ADDR_END}; + assign obi_xbar_rule[RuleCtrlHi] = '{idx: ObiSbr.ctrl, start_addr: tile_event_unit_end_addr, end_addr: CtrlHiEnd }; + + if (TileCfg.EnCluster) begin: gen_cluster_eu_rule + assign obi_xbar_rule[RuleClusterEu] = '{idx: ObiSbr.cluster_eu, start_addr: magia_tile_pkg::CLUSTER_EU_ADDR_START, end_addr: magia_tile_pkg::CLUSTER_EU_ADDR_END}; + if (magia_tile_pkg::CLUSTER_EU_ADDR_END != magia_tile_pkg::TILE_PERIPH_ADDR_END) + begin: gen_err_cluster_eu_not_last + $fatal(1, "magia_isle: peripherals after the cluster EU need a control rule above it"); + end + end // Anything outside L1 and the peripheral window goes to the AXI crossbar assign obi_xbar_en_default_idx = '1; @@ -892,8 +946,8 @@ module magia_isle .core_busy_i ( eu_core_busy ), .core_clock_en_o ( eu_core_clk_en ), - .dbg_req_i ( '0 ), - .core_dbg_req_o ( ), + .dbg_req_i ( debug_req_i ), + .core_dbg_req_o ( eu_core_dbg_req ), .eu_direct_req_i ( eu_direct_req_flat ), .eu_direct_addr_i ( eu_direct_addr_flat ), @@ -905,6 +959,10 @@ module magia_isle .eu_direct_rdata_o ( eu_direct_rdata_flat ), .eu_direct_err_o ( eu_direct_err_flat ), + .soc_periph_evt_valid_i ( 1'b0 ), + .soc_periph_evt_ready_o ( ), + .soc_periph_evt_data_i ( '0 ), + .obi_req_i ( core_mem_data_req[ObiSbr.eu] ), .obi_rsp_o ( core_mem_data_rsp[ObiSbr.eu] ) ); @@ -1154,7 +1212,7 @@ module magia_isle }; `HCI_INTF_ARRAY(hci_tcdm_sram_if, sys_clk, 0:NumMemBanks-1); - // Core ports: [0] control core, then Spatz, then cluster cores + // Core ports: [0] remote accesses from the OBI crossbar, then Spatz, cluster cores and the control core localparam hci_package::hci_size_parameter_t `HCI_SIZE_PARAM(hci_core_if) = '{ DW: magia_tile_pkg::DW_LIC, AW: magia_tile_pkg::AWC, @@ -1201,11 +1259,11 @@ module magia_isle `HCI_INTF_ARRAY(hci_ext_if, sys_clk, 0:NumExt-1); end - // Control core L1 port: atomics are resolved before HCI - magia_tile_pkg::core_obi_data_req_t core_l1_data_amo_req; - magia_tile_pkg::core_obi_data_rsp_t core_l1_data_amo_rsp; - tile_hci_data_req_t core_l1_data_req; - tile_hci_data_rsp_t core_l1_data_rsp; + // L1 accesses from the OBI crossbar, local core atomics included: atomics resolved before HCI + magia_tile_pkg::core_obi_data_req_t ext_l1_data_amo_req; + magia_tile_pkg::core_obi_data_rsp_t ext_l1_data_amo_rsp; + tile_hci_data_req_t ext_l1_data_req; + tile_hci_data_rsp_t ext_l1_data_rsp; obi_atop_resolver #( .SbrPortObiCfg ( magia_tile_pkg::obi_amo_cfg ), @@ -1224,27 +1282,28 @@ module magia_isle .testmode_i ( test_mode_i ), .sbr_port_req_i ( core_mem_data_req[ObiSbr.l1] ), .sbr_port_rsp_o ( core_mem_data_rsp[ObiSbr.l1] ), - .mgr_port_req_o ( core_l1_data_amo_req ), - .mgr_port_rsp_i ( core_l1_data_amo_rsp ) + .mgr_port_req_o ( ext_l1_data_amo_req ), + .mgr_port_rsp_i ( ext_l1_data_amo_rsp ) ); obi2hci_req #( .obi_req_t ( magia_tile_pkg::core_obi_data_req_t ), .hci_req_t ( tile_hci_data_req_t ) - ) i_core_data_obi2hci_req ( - .obi_req_i ( core_l1_data_amo_req ), - .hci_req_o ( core_l1_data_req ) + ) i_ext_l1_data_obi2hci_req ( + .obi_req_i ( ext_l1_data_amo_req ), + .hci_req_o ( ext_l1_data_req ) ); hci2obi_rsp #( .hci_rsp_t ( tile_hci_data_rsp_t ), .obi_rsp_t ( magia_tile_pkg::core_obi_data_rsp_t ) - ) i_core_data_hci2obi_rsp ( - .hci_rsp_i ( core_l1_data_rsp ), - .obi_rsp_o ( core_l1_data_amo_rsp ) + ) i_ext_l1_data_hci2obi_rsp ( + .hci_rsp_i ( ext_l1_data_rsp ), + .obi_rsp_o ( ext_l1_data_amo_rsp ) ); - `HCI_ASSIGN_TO_INTF(hci_core_if[0], core_l1_data_req, core_l1_data_rsp) + `HCI_ASSIGN_TO_INTF(hci_core_if[0], ext_l1_data_req, ext_l1_data_rsp) + `HCI_ASSIGN_TO_INTF(hci_core_if[NumHciCore-1], core_l1_direct_req, core_l1_direct_rsp) logic hci_clear; hci_package::hci_interconnect_ctrl_t hci_ctrl; @@ -1893,11 +1952,9 @@ module magia_isle localparam int unsigned ClusterIcacheLineCount = TileCfg.Cluster.IcacheSharedSize / (TileCfg.Cluster.IcacheLineWidth/8) / TileCfg.Cluster.IcacheNumWays; logic [31:0] cluster_boot_addr [NClusterCores-1:0]; - logic [NClusterCores-1:0] cluster_clk_en; logic [NClusterCores-1:0] cluster_fetch_enable; - logic [NClusterCores-1:0] cluster_start_irq; + logic cluster_start; logic cluster_done; - logic [NClusterCores-1:0] cluster_clk; obi_slave_ctrl_cluster #( .TileCfg ( TileCfg ), @@ -1907,24 +1964,14 @@ module magia_isle .rst_ni ( rst_ni ), .obi_req_i ( ctrl_req[CtrlMap.cluster] ), .obi_rsp_o ( ctrl_rsp[CtrlMap.cluster] ), - .clk_en_o ( cluster_clk_en ), .boot_addr_o ( cluster_boot_addr ), .fetch_en_o ( cluster_fetch_enable ), .done_o ( cluster_done ), - .start_irq_o ( cluster_start_irq ) + .start_irq_o ( cluster_start ) ); assign eu_events.other[magia_tile_pkg::EU_OTHER_CLUSTER_DONE] = cluster_done; - for (genvar j = 0; j < NClusterCores; j++) begin: gen_cluster_clk_gate - tc_clk_gating i_cluster_clk_gate ( - .clk_i ( sys_clk ), - .en_i ( cluster_clk_en[j] ), - .test_en_i ( test_mode_i ), - .clk_o ( cluster_clk[j] ) - ); - end - // Each core reaches L1 through HCI and everything else through the OBI crossbar magia_tile_pkg::core_obi_data_req_t [NClusterCores-1:0] cluster_obi_data_req; magia_tile_pkg::core_obi_data_rsp_t [NClusterCores-1:0] cluster_obi_data_rsp; @@ -1940,22 +1987,22 @@ module magia_isle .hci_req_t ( tile_hci_data_req_t ), .hci_rsp_t ( tile_hci_data_rsp_t ) ) i_cluster ( - .sys_clk_i ( sys_clk ), - .rst_ni ( rst_ni ), - .test_mode_i ( test_mode_i ), - .cluster_clk_i ( cluster_clk ), - .debug_req_i ( debug_req_i[NClusterCores:1] ), - .mhartid_i ( mhartid_i ), - .tcdm_rules_i ( tcdm_rules ), - .cluster_boot_addr_i ( cluster_boot_addr ), - .cluster_fetch_enable_i ( cluster_fetch_enable ), - .cluster_start_irq_i ( cluster_start_irq ), - .cluster_obi_data_req_o ( cluster_obi_data_req ), - .cluster_obi_data_rsp_i ( cluster_obi_data_rsp ), - .cluster_hci_data_req_o ( cluster_hci_data_req ), - .cluster_hci_data_rsp_i ( cluster_hci_data_rsp ), - .cluster_instr_req_o ( cluster_instr_req ), - .cluster_instr_rsp_i ( cluster_instr_rsp ) + .clk_i ( sys_clk ), // The cluster Event Unit gates each core + .rst_ni ( rst_ni ), + .test_mode_i ( test_mode_i ), + .mhartid_i ( mhartid_i ), + .tcdm_rules_i ( tcdm_rules ), + .cluster_boot_addr_i ( cluster_boot_addr ), + .cluster_fetch_enable_i ( cluster_fetch_enable ), + .cluster_start_i ( cluster_start ), + .cluster_eu_obi_req_i ( core_mem_data_req[ObiSbr.cluster_eu] ), + .cluster_eu_obi_rsp_o ( core_mem_data_rsp[ObiSbr.cluster_eu] ), + .cluster_obi_data_req_o ( cluster_obi_data_req ), + .cluster_obi_data_rsp_i ( cluster_obi_data_rsp ), + .cluster_hci_data_req_o ( cluster_hci_data_req ), + .cluster_hci_data_rsp_i ( cluster_hci_data_rsp ), + .cluster_instr_req_o ( cluster_instr_req ), + .cluster_instr_rsp_i ( cluster_instr_rsp ) ); for (genvar idx_core = 0; idx_core < NClusterCores; idx_core++) begin: gen_cluster_obi_port diff --git a/hw/tile/magia_tile.sv b/hw/tile/magia_tile.sv index 36686f84..ac2afc41 100644 --- a/hw/tile/magia_tile.sv +++ b/hw/tile/magia_tile.sv @@ -96,7 +96,7 @@ module magia_tile input logic[magia_pkg::N_IRQ-1:0] irq_i, - input logic[magia_tile_pkg::N_CLUSTER_CORES:0] debug_req_i, + input logic debug_req_i, // Control core, through its Event Unit output logic debug_havereset_o, output logic debug_running_o, output logic debug_halted_o, diff --git a/hw/tile/magia_tile_pkg.sv b/hw/tile/magia_tile_pkg.sv index df13b853..5435784b 100644 --- a/hw/tile/magia_tile_pkg.sv +++ b/hw/tile/magia_tile_pkg.sv @@ -68,10 +68,10 @@ package magia_tile_pkg; localparam logic [magia_pkg::ADDR_W-1:0] FSYNC_CTRL_ADDR_START = IDMA_CTRL_ADDR_END; localparam logic [magia_pkg::ADDR_W-1:0] FSYNC_CTRL_SIZE = 32'h0000_0100; localparam logic [magia_pkg::ADDR_W-1:0] FSYNC_CTRL_ADDR_END = FSYNC_CTRL_ADDR_START + FSYNC_CTRL_SIZE; - localparam logic [magia_pkg::ADDR_W-1:0] EVENT_UNIT_ADDR_START = FSYNC_CTRL_ADDR_END; - localparam logic [magia_pkg::ADDR_W-1:0] EVENT_UNIT_SIZE = 32'h0000_1000; - localparam logic [magia_pkg::ADDR_W-1:0] EVENT_UNIT_ADDR_END = EVENT_UNIT_ADDR_START + EVENT_UNIT_SIZE; - localparam logic [magia_pkg::ADDR_W-1:0] SPATZ_CTRL_ADDR_START = EVENT_UNIT_ADDR_END; + localparam logic [magia_pkg::ADDR_W-1:0] CTRL_EU_ADDR_START = FSYNC_CTRL_ADDR_END; + localparam logic [magia_pkg::ADDR_W-1:0] CTRL_EU_SIZE = 32'h0000_1000; + localparam logic [magia_pkg::ADDR_W-1:0] CTRL_EU_ADDR_END = CTRL_EU_ADDR_START + CTRL_EU_SIZE; + localparam logic [magia_pkg::ADDR_W-1:0] SPATZ_CTRL_ADDR_START = CTRL_EU_ADDR_END; localparam logic [magia_pkg::ADDR_W-1:0] SPATZ_CTRL_SIZE = 32'h0000_0040; localparam logic [magia_pkg::ADDR_W-1:0] SPATZ_CTRL_ADDR_END = SPATZ_CTRL_ADDR_START + SPATZ_CTRL_SIZE; localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_CTRL_ADDR_START = SPATZ_CTRL_ADDR_END; @@ -80,7 +80,14 @@ package magia_tile_pkg; localparam logic [magia_pkg::ADDR_W-1:0] COLL_CTRL_ADDR_START = CLUSTER_CTRL_ADDR_END; localparam logic [magia_pkg::ADDR_W-1:0] COLL_CTRL_SIZE = 32'h0000_0100; localparam logic [magia_pkg::ADDR_W-1:0] COLL_CTRL_ADDR_END = COLL_CTRL_ADDR_START + COLL_CTRL_SIZE; - localparam logic [magia_pkg::ADDR_W-1:0] TILE_PERIPH_ADDR_END = COLL_CTRL_ADDR_END; + // Cluster-private Event Unit: direct link of the cluster cores, and memory-mapped view on the OBI crossbar + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_DIRECT_START = COLL_CTRL_ADDR_END; + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_DIRECT_SIZE = 32'h0000_1000; + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_DIRECT_END = CLUSTER_EU_DIRECT_START + CLUSTER_EU_DIRECT_SIZE; + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_ADDR_START = CLUSTER_EU_DIRECT_END; + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_SIZE = 32'h0000_1000; + localparam logic [magia_pkg::ADDR_W-1:0] CLUSTER_EU_ADDR_END = CLUSTER_EU_ADDR_START + CLUSTER_EU_SIZE; + localparam logic [magia_pkg::ADDR_W-1:0] TILE_PERIPH_ADDR_END = CLUSTER_EU_ADDR_END; // Reserved (private): software scratch, such as the print buffer localparam logic [magia_pkg::ADDR_W-1:0] RESERVED_ADDR_START = TILE_WINDOW_BASE + (TILE_PERIPH_ADDR_END - TILE_PERIPH_BASE); @@ -149,8 +156,8 @@ package magia_tile_pkg; endfunction function automatic int unsigned gen_tile_num_hci_core(magia_tile_cfg_t cfg); - // ctrl core + Spatz TCDM ports + one dedicated L1/TCDM port per cluster core - return 1 + gen_tile_spatz_hci_ports(cfg) + (cfg.EnCluster ? cfg.Cluster.NumCores : 0); + // Remote L1 route from the xbar + ctrl core + Spatz TCDM ports + one port per cluster core + return 1 + 1 + gen_tile_spatz_hci_ports(cfg) + (cfg.EnCluster ? cfg.Cluster.NumCores : 0); endfunction localparam int unsigned SPATZ_HCI_PORTS = gen_spatz_hci_ports(magia_pkg::MagiaSpatzDefaultCfg); @@ -232,7 +239,7 @@ package magia_tile_pkg; // Parameters used by the HCI parameter int unsigned N_CLUSTER_CORES = magia_pkg::MagiaClusterDefaultCfg.NumCores; // Upper bound for TileCfg.Cluster.NumCores parameter int unsigned N_HWPE = 1; // Number of HWPEs attached to the port - parameter int unsigned N_CORE = 1 + SPATZ_HCI_PORTS + N_CLUSTER_CORES; // MAX core-side HCI ports (ctrl + Spatz + cluster); actual count from gen_tile_num_hci_core(), only sizes IW + parameter int unsigned N_CORE = 1 + 1 + SPATZ_HCI_PORTS + N_CLUSTER_CORES; // MAX core-side HCI ports (remote + ctrl + Spatz + cluster); actual count from gen_tile_num_hci_core(), only sizes IW parameter int unsigned N_DMA = 4; // Number of DMA ports (1 out read channel, 1 out write channel, 1 in read channel and 1 in write channel) typedef enum logic[1:0]{ HCI_DMA_OUT_CH_READ_IDX = 2'b00, @@ -293,6 +300,14 @@ package magia_tile_pkg; `endif parameter int unsigned CLIC_ID_W_CLUSTER = 5; // cv32e40p irq_id_o/irq_id_i width (fixed, independent of the ctrl core's CLIC_ID_W) + // Cluster-private Event Unit configuration + parameter int unsigned CLUSTER_EU_NB_SW_EVT = 8; // SW events (pulp-sdk uses up to 8) + parameter int unsigned CLUSTER_EU_NB_HW_MUT = 1; // HW mutexes (pulp_cluster default) + parameter int unsigned CLUSTER_EU_MUTEX_MSG_W = 32; // HW mutex message width + parameter int unsigned CLUSTER_EU_DISP_FIFO_DEPTH = 8; // Dispatch FIFO depth (IP default; >=4 needed for fork/join) + parameter int unsigned CLUSTER_EU_EVNT_WIDTH = 8; // SoC event ID width + parameter int unsigned CLUSTER_EU_SOC_FIFO_DEPTH = 8; // SoC event FIFO depth + parameter int unsigned REDMULE_DW = DWH-32; // RedMulE Data Width (default; per-tile in magia_tile.sv) parameter int unsigned REDMULE_UW = UWH; // RedMulE User Width @@ -390,8 +405,9 @@ package magia_tile_pkg; int unsigned num_rules; // Number of address decode rules int unsigned l2; // AXI crossbar (L2) port - must stay 0: it is the crossbar default port int unsigned l1; // L1 SPM (HCI) port - int unsigned eu; // Event Unit port + int unsigned eu; // Event Unit port (control core) int unsigned ctrl; // Control port (every control-register unit, see ctrl_map_t) + int unsigned cluster_eu; // Cluster-private Event Unit, memory-mapped view (valid iff EnCluster) } obi_sbr_map_t; function automatic obi_sbr_map_t gen_obi_sbr_map(magia_tile_cfg_t cfg); @@ -401,9 +417,12 @@ package magia_tile_pkg; ret.l1 = 1; ret.eu = 2; ret.ctrl = 3; - ret.num_sbr = 4; - // L2, L1, reserved, stack, EU and the control window around it - ret.num_rules = 7; + if (cfg.EnCluster) begin + ret.cluster_eu = 4; + end + ret.num_sbr = 4 + 32'(cfg.EnCluster); + // L2, L1, reserved, stack, EU and the control window around it, plus the cluster EU + ret.num_rules = 7 + 32'(cfg.EnCluster); return ret; endfunction @@ -418,6 +437,7 @@ package magia_tile_pkg; localparam int unsigned EU_DMA_O2A_DONE = 1; localparam int unsigned EU_OTHER_CLUSTER_DONE = 12; + localparam int unsigned EU_OTHER_CLUSTER_START = 13; localparam int unsigned EU_OTHER_SPATZ_START = 23; localparam int unsigned EU_OTHER_FSYNC_DONE = 24; localparam int unsigned EU_OTHER_A2O_START = 28; @@ -537,12 +557,12 @@ package magia_tile_pkg; logic[magia_pkg::ADDR_W-1:0] end_addr; } obi_xbar_rule_t; - // TCDM ranges taken by the dedicated HCI ports: the shared L1, reserved and stack go through OBI - localparam int unsigned NUM_TCDM_RULES = 1; + // TCDM ranges taken by the dedicated HCI ports: the private reserved and stack regions, the shared L1 + localparam int unsigned NUM_TCDM_RULES = 2; typedef obi_xbar_rule_t [CTRL_MAX_UNITS-1:0] ctrl_rules_t; - // One rule per ctrl_demux port as in gen_ctrl_map(); a disabled unit's range falls to L2 and is flagged + // One rule per ctrl_demux port as in gen_ctrl_map(); a disabled unit's range gets an OBI error function automatic ctrl_rules_t gen_ctrl_rules(magia_tile_cfg_t cfg); ctrl_rules_t ret; ctrl_map_t map; @@ -626,6 +646,12 @@ package magia_tile_pkg; typedef cv32e40p_core_data_rsp_t core_data_rsp_t; `endif + // Core data demux signals: [0] = TCDM (L1) direct path, [1] = OBI xbar (default), [2] = EU direct link window + localparam int unsigned CORE_DATA_DEMUX_N_SLV = 3; + localparam int unsigned CORE_DATA_DEMUX_TCDM_IDX = 0; + localparam int unsigned CORE_DATA_DEMUX_OBI_IDX = 1; + localparam int unsigned CORE_DATA_DEMUX_EU_IDX = 2; + // EU Direct Link interface types typedef struct packed { logic req; diff --git a/hw/tile/obi_slave_ctrl_cluster.sv b/hw/tile/obi_slave_ctrl_cluster.sv index 58831e78..030feb57 100644 --- a/hw/tile/obi_slave_ctrl_cluster.sv +++ b/hw/tile/obi_slave_ctrl_cluster.sv @@ -33,37 +33,31 @@ module obi_slave_ctrl_cluster output core_obi_data_rsp_t obi_rsp_o, // Control outputs to PULP cluster cores - output logic [NumCores-1:0] clk_en_o, // broadcast (replicated) output logic [31:0] boot_addr_o [NumCores-1:0], output logic [NumCores-1:0] fetch_en_o, // broadcast (replicated) - output logic done_o, // DONE IRQ pulse when all selected cores complete - output logic [NumCores-1:0] start_irq_o // per-core 1-cycle dispatch IRQ pulse + output logic done_o, // 1-cycle pulse on a DONE write + output logic start_irq_o // 1-cycle event pulse, wired directly into core 0's Cluster Event Unit Buffer ); //----------------------------------------------------------------------------- // Register map (offsets from BaseAddr; instantiated inside gen_pulp_cluster at CLUSTER_CTRL_ADDR_START = 0x1740) -// 0x00 CLK_EN : RW broadcast (1=enable all cores, 0=disable all) -// writing CLK_EN also resets the READY counter +// 0x00 FETCH_EN : RW broadcast fetch_enable, resets READY; sticky in cv32e40p, so 0 does not stop the cores // 0x04 BINARY : RW PULP binary entry point (boot address) -// 0x08 NB_CORES_TO_WAIT: RW number of cores expected to ACK + DONE per dispatch -// 0x0C DONE : W each PULP core writes 1 after task returns; -// after NB_CORES_TO_WAIT writes done_o pulses -// 0x10 TASKBIN : RW task function address; read by PULP cores -// 0x14 DATA : RW context pointer passed as arg0 to the task -// 0x18 START : RW CV32 writes one-hot core_mask -> 1-cycle IRQ pulse -// PULP cores write 0 to ACK (before task); -// register clears when all NB_CORES_TO_WAIT ACKs received -// 0x1C READY : R reads as 1 once NumCores cores have written; -// W each PULP core writes 1 after boot (counter increment) +// 0x08 DONE : W the dispatcher core writes 1 after the task returns -> done_o pulses +// 0x0C TASKBIN : RW task function address; read by PULP cores +// 0x10 DATA : RW context pointer passed as arg0 to the task +// 0x14 START : RW CV32 writes non-zero -> 1-cycle dispatch event on Cluster core 0's Event Unit; core 0 writes 0 to ACK +// 0x18 READY : R 1 once NumCores cores have written, W each PULP core writes 1 after boot +// 0x1C RETURN : RW task exit code; the dispatcher core writes it right before DONE //----------------------------------------------------------------------------- -localparam logic [31:0] CLUSTER_CLK_EN = 32'h00; +localparam logic [31:0] CLUSTER_FETCH_EN = 32'h00; localparam logic [31:0] CLUSTER_BINARY = 32'h04; -localparam logic [31:0] CLUSTER_NB_CORES_TO_WAIT = 32'h08; -localparam logic [31:0] CLUSTER_DONE = 32'h0C; -localparam logic [31:0] CLUSTER_TASKBIN = 32'h10; -localparam logic [31:0] CLUSTER_DATA = 32'h14; -localparam logic [31:0] CLUSTER_START = 32'h18; -localparam logic [31:0] CLUSTER_READY = 32'h1C; +localparam logic [31:0] CLUSTER_DONE = 32'h08; +localparam logic [31:0] CLUSTER_TASKBIN = 32'h0C; +localparam logic [31:0] CLUSTER_DATA = 32'h10; +localparam logic [31:0] CLUSTER_START = 32'h14; +localparam logic [31:0] CLUSTER_READY = 32'h18; +localparam logic [31:0] CLUSTER_RETURN = 32'h1C; localparam int unsigned NumCoresW = magia_tile_pkg::gen_idx_width(NumCores); localparam logic [NumCoresW:0] NumCoresCnt = (NumCoresW+1)'(NumCores); // NumCores at the counter width @@ -77,21 +71,19 @@ assign addr_valid = (obi_req_i.a.addr >= BaseAddr) & (obi_req_i.a.addr < (BaseAddr + 32)); // 8 registers * 4 bytes // Registers -logic clk_en_q, clk_en_d; +logic fetch_en_q, fetch_en_d; logic [31:0] entry_point_q, entry_point_d; -logic [NumCoresW:0] nb_cores_to_wait_q, nb_cores_to_wait_d; logic done_q, done_d; logic [31:0] taskbin_q, taskbin_d; logic [31:0] data_q, data_d; -logic [NumCores-1:0] start_q, start_d; +logic start_q, start_d; +logic [31:0] retval_q, retval_d; -// Counters -logic [NumCoresW:0] nb_recv_done_reqs_q, nb_recv_done_reqs_d; -logic [NumCoresW:0] nb_recv_ack_reqs_q, nb_recv_ack_reqs_d; +// Boot counter (READY): still per-core, every hart reports once logic [NumCoresW:0] nb_recv_ready_reqs_q, nb_recv_ready_reqs_d; // One-cycle start IRQ pulse register -logic [NumCores-1:0] start_irq_q, start_irq_d; +logic start_irq_q, start_irq_d; // Response pipeline logic rvalid_q, rvalid_d; @@ -109,35 +101,30 @@ assign obi_rsp_o.r.r_optional = '0; // ============================================ always_comb begin // Defaults: hold - clk_en_d = clk_en_q; + fetch_en_d = fetch_en_q; entry_point_d = entry_point_q; - nb_cores_to_wait_d = nb_cores_to_wait_q; done_d = 1'b0; taskbin_d = taskbin_q; data_d = data_q; start_d = start_q; - nb_recv_done_reqs_d = nb_recv_done_reqs_q; - nb_recv_ack_reqs_d = nb_recv_ack_reqs_q; + retval_d = retval_q; nb_recv_ready_reqs_d = nb_recv_ready_reqs_q; start_irq_d = '0; // default: no IRQ pulse this cycle if (obi_req_i.req && addr_valid && obi_req_i.a.we) begin unique case (addr_offset) - CLUSTER_CLK_EN: begin + CLUSTER_FETCH_EN: begin // Broadcast: any non-zero enables all cores; 0 disables all. - clk_en_d = |obi_req_i.a.wdata; + fetch_en_d = |obi_req_i.a.wdata; // Reset READY counter so CV32 can re-poll after each init. nb_recv_ready_reqs_d = '0; end CLUSTER_BINARY: begin entry_point_d = obi_req_i.a.wdata; end - CLUSTER_NB_CORES_TO_WAIT: begin - nb_cores_to_wait_d = obi_req_i.a.wdata[NumCoresW:0]; - end CLUSTER_DONE: begin - // Each PULP core writes 1 here on completion (write data ignored) - nb_recv_done_reqs_d = nb_recv_done_reqs_q + 1; + // One write per completed dispatch, data ignored + done_d = 1'b1; end CLUSTER_TASKBIN: begin taskbin_d = obi_req_i.a.wdata; @@ -145,16 +132,18 @@ always_comb begin CLUSTER_DATA: begin data_d = obi_req_i.a.wdata; end + CLUSTER_RETURN: begin + // The dispatcher core writes the task's exit code here, right before DONE + retval_d = obi_req_i.a.wdata; + end CLUSTER_START: begin if (obi_req_i.a.wdata != 32'h0) begin - // CV32 dispatch: latch core_mask, reset counters, fire 1-cycle IRQ pulses - start_d = obi_req_i.a.wdata[NumCores-1:0]; - nb_recv_ack_reqs_d = '0; - nb_recv_done_reqs_d = '0; - start_irq_d = obi_req_i.a.wdata[NumCores-1:0]; + // CV32 dispatch: latch the request and fire the 1-cycle event pulse + start_d = 1'b1; + start_irq_d = 1'b1; end else begin - // PULP core ACK (write 0): count; when all done, clear register - nb_recv_ack_reqs_d = nb_recv_ack_reqs_q + 1; + // ACK from core 0: clear the register, unblocking the CV32 poll + start_d = 1'b0; end end CLUSTER_READY: begin @@ -166,18 +155,6 @@ always_comb begin default: ; endcase end - - // Fire a one-cycle DONE pulse when all expected cores have completed. - if (nb_recv_done_reqs_d >= nb_cores_to_wait_q && nb_cores_to_wait_q != '0) begin - done_d = 1'b1; - nb_recv_done_reqs_d = '0; - end - - // Clear PULP_START when all expected cores have ACK'd (write 0) - if (nb_recv_ack_reqs_d >= nb_cores_to_wait_q && nb_cores_to_wait_q != '0) begin - start_d = '0; - nb_recv_ack_reqs_d = '0; - end end // ============================================ @@ -185,29 +162,25 @@ end // ============================================ always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin - clk_en_q <= 1'b0; + fetch_en_q <= 1'b0; entry_point_q <= 32'hCC000080; - nb_cores_to_wait_q <= NumCoresCnt; done_q <= 1'b0; taskbin_q <= 32'h0; data_q <= 32'h0; - start_q <= '0; - nb_recv_done_reqs_q <= '0; - nb_recv_ack_reqs_q <= '0; + start_q <= 1'b0; + retval_q <= 32'h0; nb_recv_ready_reqs_q <= '0; - start_irq_q <= '0; + start_irq_q <= 1'b0; rvalid_q <= 1'b0; rdata_q <= 32'h0; end else begin - clk_en_q <= clk_en_d; + fetch_en_q <= fetch_en_d; entry_point_q <= entry_point_d; - nb_cores_to_wait_q <= nb_cores_to_wait_d; done_q <= done_d; taskbin_q <= taskbin_d; data_q <= data_d; start_q <= start_d; - nb_recv_done_reqs_q <= nb_recv_done_reqs_d; - nb_recv_ack_reqs_q <= nb_recv_ack_reqs_d; + retval_q <= retval_d; nb_recv_ready_reqs_q <= nb_recv_ready_reqs_d; start_irq_q <= start_irq_d; rvalid_q <= rvalid_d; @@ -227,22 +200,21 @@ always_comb begin if (obi_req_i.req && addr_valid && !obi_req_i.a.we) begin unique case (addr_offset) - CLUSTER_CLK_EN: rdata_d = {31'h0, clk_en_q}; + CLUSTER_FETCH_EN: rdata_d = {31'h0, fetch_en_q}; CLUSTER_BINARY: rdata_d = entry_point_q; - CLUSTER_NB_CORES_TO_WAIT: rdata_d = {{(32-NumCoresW-1){1'b0}}, nb_cores_to_wait_q}; CLUSTER_DONE: rdata_d = {31'h0, done_q}; CLUSTER_TASKBIN: rdata_d = taskbin_q; CLUSTER_DATA: rdata_d = data_q; - CLUSTER_START: rdata_d = {{(32-NumCores){1'b0}}, start_q}; + CLUSTER_RETURN: rdata_d = retval_q; + CLUSTER_START: rdata_d = {31'h0, start_q}; CLUSTER_READY: rdata_d = {31'h0, ready_reg_val}; default: rdata_d = 32'hDEADBEEF; endcase end end -// Outputs: broadcast clk_en and fetch_en to all cores (replicated bit) -assign clk_en_o = {NumCores{clk_en_q}}; -assign fetch_en_o = {NumCores{clk_en_q}}; +// Output: fetch_enable replicated per core +assign fetch_en_o = {NumCores{fetch_en_q}}; assign done_o = done_q; assign start_irq_o = start_irq_q; diff --git a/magia-sdk b/magia-sdk index a87c5e36..95a443da 160000 --- a/magia-sdk +++ b/magia-sdk @@ -1 +1 @@ -Subproject commit a87c5e36f11209dec1908ab2aad3439f03e7199a +Subproject commit 95a443da88a4ee698f61098ab55e5a9bbe6f06cd diff --git a/sw/kernel_pulp/Makefile b/sw/kernel_pulp/Makefile index bb8005c2..a9341d0a 100644 --- a/sw/kernel_pulp/Makefile +++ b/sw/kernel_pulp/Makefile @@ -48,6 +48,7 @@ task ?= PULP_TASK_DIR ?= ARCH_FLAGS = -march=$(ARCH)$(XLEN)$(XTEN) -mabi=$(ABI)$(XLEN)$(XABI) +# -fPIE, not -fPIC # -msmall-data-limit=0: never use gp/zero-relative addressing for globals. # Required because the PIC binary is linked with ORIGIN=0, so .sdata # symbols would otherwise be accessed via `lw rd, imm(zero)`, which @@ -66,9 +67,10 @@ ARCH_FLAGS = -march=$(ARCH)$(XLEN)$(XTEN) -mabi=$(ABI)$(XLEN)$(XABI) CFLAGS = $(ARCH_FLAGS) -O2 -g -Wall -Wextra -Wno-unused-parameter \ -Wno-unused-variable -Wno-unused-function -Wundef \ -ffunction-sections -fdata-sections \ - -fPIC -mcmodel=medany \ + -fPIE -mcmodel=medany \ -fno-jump-tables \ -msmall-data-limit=0 \ + -mno-relax \ -nostartfiles -nostdlib \ -DPULP_CORE_COUNT=$(PULP_CORE_COUNT) \ -D$(core) -U__riscv__ diff --git a/sw/kernel_pulp/pulp_crt0.S b/sw/kernel_pulp/pulp_crt0.S index b81078b8..beb5973c 100644 --- a/sw/kernel_pulp/pulp_crt0.S +++ b/sw/kernel_pulp/pulp_crt0.S @@ -14,63 +14,30 @@ * limitations under the License. * SPDX-License-Identifier: Apache-2.0 * - * PULP cluster CRT0 for embedded binary in CV32 instrram (magia_v3). - * - * Bare-metal dynamic dispatch programming model. - * - * Boot sequence per hart: - * 1. CV32 writes _pulp_binary_start to PULP_BINARY (0x1744). - * 2. CV32 writes 1 to PULP_CLK_EN (0x1740) -> fetch_enable goes high to - * all cluster cores, which reset to _pulp_binary_start and execute - * _start. - * 3. Each hart computes its local ID, sets up its 2 KB stack slice and - * clears BSS (all-harts race, benign zero-store). - * 4. Install trap_handler in mtvec (direct mode, mtvec[1:0] = 00). - * 5. Enable mstatus.MIE and mie.MEIE (bit 11). - * 6. Write 1 to PULP_READY (0x175C) so CV32's pulp_init() can advance. - * 7. Enter dispatcher_loop: WFI; on MEI, jump to trap_handler. - * - * trap_handler (per dispatch): - * - Read PULP_TASKBIN (0x1750) -> task function pointer. - * - Read PULP_DATA (0x1754) -> first argument (a0). - * - Write 0 to PULP_START (0x1758) to ACK *before* calling the task; this - * is what unblocks CV32's pulp_run_task() poll. - * - jalr the task function. - * - Write 1 to PULP_DONE (0x174C) so ClusterRegs counts the completion; - * after NB_CORES_TO_WAIT writes it fires PULP_DONE -> CV32 EU bit 12. - * - mret back into the dispatcher_loop (WFI). - * - * NOTE on alignment: CV32E40P's mtvec has mtvec[7:2] hardwired to 0, so the - * trap base is always 256-byte aligned regardless of what we write. We must - * therefore align trap_handler to 256 bytes; otherwise the trap entry would - * jump to a lower address inside _start and execute garbage. - * - * Stack layout (absolute tile addresses, grows downward): - * Slot 0 (CV32 main): [0x1B800, 0x1C000) — reserved by sw/kernel/crt0.S - * PULP STACK_TOP = 0x1B800 - * PULP core 0: [0x1B000, 0x1B800) (2 KB) - * ... - * PULP core 7: [0x17800, 0x18000) (2 KB) - * - * NOTE: stack and PULP_* MMIO addresses are loaded with 'li' (not 'la') - * because the binary is position-independent (ORIGIN=0x0) and 'la' would - * produce a PC-relative reference that breaks when loaded at the instrram - * offset. + * PULP cluster CRT0: core 0 serves the CV32 mailbox (dispatcher_loop), the other cores the EU dispatch FIFO (worker_wait) */ #ifndef PULP_CORE_COUNT #define PULP_CORE_COUNT 8 #endif -#define PULP_DONE_ADDR 0x0000174C /* PULP_CTRL_BASE + 0x0C */ -#define PULP_TASKBIN_ADDR 0x00001750 /* PULP_CTRL_BASE + 0x10 */ -#define PULP_DATA_ADDR 0x00001754 /* PULP_CTRL_BASE + 0x14 */ -#define PULP_START_ADDR 0x00001758 /* PULP_CTRL_BASE + 0x18 */ -#define PULP_READY_ADDR 0x0000175C /* PULP_CTRL_BASE + 0x1C */ - -/* PULP STACK_TOP sits 2 KB below the tile-wide top (0x1C000) so the slot - * at [0x1B800, 0x1C000) stays reserved for the CV32 main core (slot 0). */ -#define STACK_TOP 0x0001B800 +#define PULP_DONE_ADDR 0x00001748 /* PULP_CTRL_BASE + 0x08 */ +#define PULP_TASKBIN_ADDR 0x0000174C /* PULP_CTRL_BASE + 0x0C */ +#define PULP_DATA_ADDR 0x00001750 /* PULP_CTRL_BASE + 0x10 */ +#define PULP_START_ADDR 0x00001754 /* PULP_CTRL_BASE + 0x14 */ +#define PULP_READY_ADDR 0x00001758 /* PULP_CTRL_BASE + 0x18 */ +#define PULP_RETURN_ADDR 0x0000175C /* PULP_CTRL_BASE + 0x1C */ + +#define EU_DEMUX_BASE 0x00001900 /* CLUSTER_EU_DIRECT_BASE */ +#define EU_CORE_MASK_OR (EU_DEMUX_BASE + 0x08) +#define EU_EVENT_WAIT_CLR (EU_DEMUX_BASE + 0x3C) +#define EU_DISPATCH_FIFO (EU_DEMUX_BASE + 0x80) +#define EU_BARRIER0_WAIT_CLEAR (EU_DEMUX_BASE + 0x200 + 0x1C) +#define EU_DISPATCH_EVT_MASK (1 << 13) +#define EU_MASK_BAR_MUTEX_DISPATCH ((1 << 16) | (1 << 17) | (1 << 18)) + +/* PIC binary: MMIO and stack addresses are absolute, so they are loaded with li, not la */ +#define STACK_TOP 0x0001B800 /* 2KB below tile top (0x1C000): reserves CV32's slot */ #define LOG2_SLICE 11 /* log2(2 KB) — each hart gets 2 KB */ .section .text.start, "ax" @@ -86,14 +53,7 @@ _start: slli t0, a1, LOG2_SLICE sub x2, x2, t0 - /* Clear BSS — all harts write 0, benign race. - * - * NOTE: __bss_start / __bss_end are PIC symbols (linker script has - * ORIGIN=0), so their resolved values are small (a few hundred bytes). - * Without 'norelax' the linker collapses 'la' (auipc+addi) into 'li' - * because the absolute value fits in a 12-bit immediate — that would - * make the loop write zeros to tile-local MMIO (iDMA CTRL region at - * 0x200-0x5FF), hanging the bus. Force PC-relative addressing here. */ + /* Clear BSS (benign all-harts race); norelax keeps la PC-relative, relaxed to li it would zero tile MMIO */ .option push .option norelax la t0, __bss_start @@ -104,51 +64,43 @@ _start: addi t0, t0, 4 j 1b 2: - /* Install trap_handler in mtvec, direct mode (mtvec[1:0] = 00). - * CV32E40P hardwires mtvec[7:2]=0 (only mtvec[31:8] and mtvec[0] are - * writable), so the trap base address is rounded down to a 256-byte - * boundary. trap_handler must therefore be 256-byte aligned. */ + /* Install trap_handler in mtvec, direct mode: mtvec[7:2] is hardwired to 0, hence the 256-byte alignment */ .option push .option norelax la t0, trap_handler .option pop csrw mtvec, t0 - /* Enable MEIE (bit 11 of mie) */ - li t0, 0x800 - csrrs zero, mie, t0 - /* Enable MIE (bit 3 of mstatus) */ - li t0, 0x8 - csrrs zero, mstatus, t0 + /* Unmask barrier/mutex/dispatch events for every core */ + li t0, EU_CORE_MASK_OR + li t1, EU_MASK_BAR_MUTEX_DISPATCH + sw t1, 0(t0) + + /* Only core 0 takes dispatches from the control core, the others park on the EU dispatch FIFO */ + bnez a1, worker_start + + /* Unmask the CV32 dispatch event on core 0 */ + li t0, EU_CORE_MASK_OR + li t1, EU_DISPATCH_EVT_MASK + sw t1, 0(t0) #ifdef __riscv_f li t0, 0x2000 csrrs zero, mstatus, t0 #endif - /* Signal "core armed": write 1 to PULP_READY. CV32's pulp_init() polls - * this until ClusterRegs has counted N_CLUSTER_CORES writes. */ + /* Core armed: CV32 polls PULP_READY until every core has written it */ li t0, PULP_READY_ADDR li t1, 1 sw t1, 0(t0) -dispatcher_loop: - wfi - j dispatcher_loop - /*-------------------------------------------------------------------------- - * Machine External Interrupt trap handler (target of the MEI vector slot). - * - * The dispatcher loop only executes WFI + jump, so no caller state needs - * preserving — we may freely clobber t0/t1/a0/ra inside the handler. - * - * NOTE on mepc semantics: when MEI is taken from WFI, mepc points to the - * instruction *after* the WFI (the back-jump). mret therefore returns to - * that jump, which loops us back to WFI. Equivalent to "wfi again". + * Dispatcher loop (core 0): sleep on the dispatch event, run the task, signal DONE *--------------------------------------------------------------------------*/ -.balign 256 -.global trap_handler -trap_handler: +dispatcher_loop: + li t0, EU_EVENT_WAIT_CLR + cv.elw t1, 0(t0) /* sleep until the dispatch event arrives */ + /* a0 = *PULP_DATA (first argument to task) */ li t0, PULP_DATA_ADDR lw a0, 0(t0) @@ -157,21 +109,62 @@ trap_handler: li t0, PULP_TASKBIN_ADDR lw t1, 0(t0) - /* ACK: write 0 to PULP_START before invoking the task. This unblocks - * CV32's pulp_run_task() poll once all selected cores have written. */ + /* ACK before the task runs: unblocks the CV32 dispatch poll */ li t0, PULP_START_ADDR sw zero, 0(t0) /* Call task(data) */ jalr ra, 0(t1) - /* Signal completion: write 1 to PULP_DONE. ClusterRegs accumulates and - * fires cluster_done when NB_CORES_TO_WAIT writes have arrived. */ + /* Exit code (meaningful only if the task returns int) */ + li t0, PULP_RETURN_ADDR + sw a0, 0(t0) + + /* Completion: each write pulses EU bit 12 on the CV32 */ li t0, PULP_DONE_ADDR li t1, 1 sw t1, 0(t0) - mret + j dispatcher_loop + +/*-------------------------------------------------------------------------- + * Worker cores (local id != 0) + *--------------------------------------------------------------------------*/ +worker_start: + /* Signal "core armed" as well, so PULP_READY counts every hart. */ + li t0, PULP_READY_ADDR + li t1, 1 + sw t1, 0(t0) + +worker_wait: + li t0, EU_DISPATCH_FIFO + cv.elw t1, 0(t0) /* entry PC (bit 0 = "other entry" flag) */ + cv.elw a0, 0(t0) /* argument */ + andi s1, t1, 1 /* s1 = other-entry flag (callee-saved) */ + jalr ra, 0(t1) /* call entry(arg); jalr drops bit 0 */ + bnez s1, worker_wait /* other entry: no barrier, straight back */ + + /* Fork entry: rendez-vous on the team barrier before parking again */ + li t0, EU_BARRIER0_WAIT_CLEAR + cv.elw x0, 0(t0) + j worker_wait + +/*-------------------------------------------------------------------------- + * Only exceptions get here: report the tagged mcause in PULP_RETURN, signal DONE and park + *--------------------------------------------------------------------------*/ +.balign 256 +.global trap_handler +trap_handler: + csrr t0, mcause + li t2, 0x80000000 /* poison tag: real mcause never sets bit 31 here */ + or t0, t0, t2 + li t1, PULP_RETURN_ADDR + sw t0, 0(t1) + li t1, PULP_DONE_ADDR + li t2, 1 + sw t2, 0(t1) +trap_halt: + j trap_halt .global _init .global _fini diff --git a/sw/tests/cluster_tests/fpu_cluster_test/main.c b/sw/tests/cluster_tests/fpu_cluster_test/main.c index 745c7796..5a7f5081 100644 --- a/sw/tests/cluster_tests/fpu_cluster_test/main.c +++ b/sw/tests/cluster_tests/fpu_cluster_test/main.c @@ -34,7 +34,8 @@ /* Result slot: 4 bytes per cluster core. * Cluster cores write 0xFEEDxxxx where xx = error count. */ -#define FPU_RESULT_BASE (L2_BASE + 0x00060000) +#define FPU_RESULT_BASE (L2_BASE + 0x00060000 + get_hartid() * FPU_RESULT_STRIDE) +#define FPU_RESULT_STRIDE (0x00000100) /* One block per tile, must match pulp_task/ */ #define FPU_RESULT_MAGIC (0xFEED0000u) #define FPU_RESULT_MASK (0xFFFF0000u) @@ -55,7 +56,7 @@ int main(void) { /* Arm EU before dispatching the task to avoid missing DONE. */ cluster_arm_done_event(); - cluster_dispatch_task(FPU_CLUSTER_TEST_TASK, 0xFFu); + cluster_dispatch_task(FPU_CLUSTER_TEST_TASK); /* Sleep (cv.elw) until all cluster cores have signalled task done. */ cluster_wait_done_eu(); @@ -90,6 +91,13 @@ int main(void) { total_errors, passed_cores, PULP_CORE_COUNT); } + /* Cross-check with the total error count the cluster returns through PULP_RETURN */ + int returned_errors = cluster_get_return_value(); + if (print_summary && returned_errors != (int)total_errors) { + printf("[fpu_cluster_test] PULP_RETURN=%d != L2 total_errors=%u FAIL\n", + returned_errors, total_errors); + total_errors++; + } + return (int)total_errors; - } diff --git a/sw/tests/cluster_tests/fpu_cluster_test/pulp_task/fpu_cluster_test_task.c b/sw/tests/cluster_tests/fpu_cluster_test/pulp_task/fpu_cluster_test_task.c index 86121424..a4a0c73b 100644 --- a/sw/tests/cluster_tests/fpu_cluster_test/pulp_task/fpu_cluster_test_task.c +++ b/sw/tests/cluster_tests/fpu_cluster_test/pulp_task/fpu_cluster_test_task.c @@ -20,25 +20,26 @@ /* * fpu_cluster_test - PULP cluster-core binary. * - * Mirrors sw/tests/fpu_test.c but runs on all 8 cluster cores concurrently. + * Mirrors sw/tests/fpu_test.c but runs on all 8 cluster cores, forked by core 0 with pi_cl_team_fork(). * Each core performs: - * fadd.s: 12.34 + 56.78 ≈ 69.12 (tolerance 0.1) - * fsub.s: 56.78 - 12.34 ≈ 44.44 (tolerance 0.1) - * fmul.s: 12.34 * 2.0 ≈ 24.68 (tolerance 0.1) + * fadd.s: 12.34 + 56.78 ~ 69.12 (tolerance 0.1) + * fsub.s: 56.78 - 12.34 ~ 44.44 (tolerance 0.1) + * fmul.s: 12.34 * 2.0 ~ 24.68 (tolerance 0.1) * * 12.34 and 56.78 are NOT exact in float32, so comparisons use the same * 0.1 tolerance as fpu_test.c rather than bit-exact equality. * - * Each core writes a 32-bit word to L2: - * 0xFEED0000 | - * The CV32 main reads these back to determine pass/fail. + * Each core writes a 32-bit word to L2: 0xFEED0000 | . + * Core 0 also returns the total error count through PULP_RETURN. */ #include #include "magia_tile_utils.h" +#include "cluster_utils.h" /* L2 result area: 4 bytes per cluster core (local_id 0..7). */ -#define FPU_RESULT_BASE (L2_BASE + 0x00060000) +#define FPU_RESULT_BASE (L2_BASE + 0x00060000 + cluster_tile_id() * FPU_RESULT_STRIDE) +#define FPU_RESULT_STRIDE (0x00000100) /* One block per tile, must match main.c */ #define FPU_RESULT_MAGIC (0xFEED0000u) #define A_VAL (12.34f) @@ -50,12 +51,6 @@ #define abs_diff(x, y) (((x) > (y)) ? ((x) - (y)) : ((y) - (x))) -static inline uint32_t get_hartid(void) { - uint32_t id; - asm volatile("csrr %0, mhartid" : "=r"(id)); - return id; -} - /* Enable the FPU: set mstatus.FS to INITIAL (01). */ static inline void enable_fpu(void) { asm volatile ( @@ -65,28 +60,26 @@ static inline void enable_fpu(void) { ); } -void fpu_cluster_test_task(void *data) { +static void fpu_cluster_test_fork_entry(void *data) { (void)data; enable_fpu(); - uint32_t hartid = get_hartid(); - uint32_t pulp_gid = hartid - PULP_HARTID_BASE; /* 0..127 */ - uint32_t local_id = pulp_gid % PULP_CORE_COUNT; /* 0..7 */ + uint32_t local_id = cluster_core_id(); unsigned int errors = 0; volatile float a = A_VAL; volatile float b = B_VAL; - /* fadd.s: a + b ≈ 69.12 */ + /* fadd.s: a + b ~ 69.12 */ float c_add = a + b; if (abs_diff(c_add, ADD_EXP) > FP_TH) errors++; - /* fsub.s: b - a ≈ 44.44 */ + /* fsub.s: b - a ~ 44.44 */ float c_sub = b - a; if (abs_diff(c_sub, SUB_EXP) > FP_TH) errors++; - /* fmul.s: a * 2.0 ≈ 24.68 */ + /* fmul.s: a * 2.0 ~ 24.68 */ volatile float two = 2.0f; float c_mul = a * two; if (abs_diff(c_mul, MUL_EXP) > FP_TH) errors++; @@ -102,3 +95,14 @@ void fpu_cluster_test_task(void *data) { /* Write result to L2 slot for this core. */ mmio32(FPU_RESULT_BASE + 4 * local_id) = FPU_RESULT_MAGIC | (errors & 0xFFFFu); } + +int fpu_cluster_test_task(void *data) { + pi_cl_team_fork(PULP_CORE_COUNT, fpu_cluster_test_fork_entry, data); + + /* The fork's barrier guarantees every core has written its slot */ + unsigned int total_errors = 0; + for (int i = 0; i < PULP_CORE_COUNT; i++) { + total_errors += mmio32(FPU_RESULT_BASE + 4 * i) & 0xFFFFu; + } + return (int)total_errors; +} diff --git a/sw/tests/cluster_tests/hello_pulp/main.c b/sw/tests/cluster_tests/hello_pulp/main.c index f97cacf7..6cac7de6 100644 --- a/sw/tests/cluster_tests/hello_pulp/main.c +++ b/sw/tests/cluster_tests/hello_pulp/main.c @@ -27,11 +27,10 @@ * 1) Print a "hello" banner. * 2) Boot the PULP cluster cores into their dispatcher loop * (cluster_boot -> pulp_init: programs PULP_BINARY, broadcasts - * CLK_EN, polls PULP_READY). + * FETCH_EN once after reset, polls PULP_READY). * 3) Arm the CV32 Event Unit for PULP_DONE (EU bit 12). - * 4) Dispatch the hello task to all 8 PULP cores by programming - * NB_CORES_TO_WAIT, TASKBIN and START. - * 5) Sleep in WFE until the DONE quorum reaches the Event Unit. + * 4) Dispatch the hello task to PULP core 0 by programming TASKBIN and START. + * 5) Sleep in WFE until PULP_DONE reaches the Event Unit. * 6) Print the "done" message. */ @@ -57,8 +56,8 @@ int main(void) { /* Arm EU before dispatching the task to avoid missing DONE. */ cluster_arm_done_event(); - /* Dispatch the hello task to all 8 cluster cores of this tile. */ - cluster_dispatch_task(HELLO_PULP_TASK, 0xFFu); + /* Dispatch the hello task to cluster core 0 of this tile. */ + cluster_dispatch_task(HELLO_PULP_TASK); /* Sleep (cv.elw) until every cluster core of this tile has signalled * task completion. */ diff --git a/sw/tests/cluster_tests/hello_pulp/pulp_task/hello_pulp_task.c b/sw/tests/cluster_tests/hello_pulp/pulp_task/hello_pulp_task.c index 4cd12428..890e1b7a 100644 --- a/sw/tests/cluster_tests/hello_pulp/pulp_task/hello_pulp_task.c +++ b/sw/tests/cluster_tests/hello_pulp/pulp_task/hello_pulp_task.c @@ -21,13 +21,12 @@ * hello_pulp — PULP cluster-core task. * * Linked at 0x0 (PIC, ORIGIN=0) and embedded as .pulp_binary inside the - * CV32 ELF (single-binary flow). The PULP cores boot here via crt0 and - * stay in their dispatcher loop until the CV32 dispatches this function - * with cluster_dispatch_task(HELLO_PULP_TASK, mask). + * CV32 ELF (single-binary flow). + * Runs on core 0 only: it never forks, so cores 1-7 stay parked in worker_wait. * * The task is entered as `void hello_pulp_task(void *data)`; `data` is * whatever pointer the CV32 wrote to PULP_DATA (NULL here). When it - * returns, the trap handler writes 1 to PULP_DONE and re-enters WFI. + * returns, pulp_crt0.S's dispatcher_loop writes 1 to PULP_DONE. */ #include "magia_tile_utils.h" diff --git a/sw/tests/cluster_tests/hello_pulp_barrier/main.c b/sw/tests/cluster_tests/hello_pulp_barrier/main.c new file mode 100644 index 00000000..3b1bb2c2 --- /dev/null +++ b/sw/tests/cluster_tests/hello_pulp_barrier/main.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * hello_pulp_barrier - main core: boots the cluster and dispatches one task to core 0 + */ + +#include "magia_tile_utils.h" +#include "cluster_utils.h" +#include "hello_pulp_barrier_pulp_task_bin.h" + +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" + :"=r"(hartid):); + return hartid; +} + +int main(void) { + uint32_t hartid = get_hartid(); + + printf("[Main core %u] Hello World!\n", hartid); + + /* Boot the PULP cluster cores into their dispatcher loop. */ + cluster_boot(PULP_BINARY_START); + + /* Arm EU before dispatching the task to avoid missing DONE. */ + cluster_arm_done_event(); + + /* Dispatch the task to core 0 (the usual single-mailbox model). */ + cluster_dispatch_task(HELLO_PULP_BARRIER_TASK); + + /* Sleep (cv.elw) until core 0 has signalled task completion. */ + cluster_wait_done_eu(); + + return 0; +} diff --git a/sw/tests/cluster_tests/hello_pulp_barrier/pulp_task/hello_pulp_barrier_task.c b/sw/tests/cluster_tests/hello_pulp_barrier/pulp_task/hello_pulp_barrier_task.c new file mode 100644 index 00000000..a6d30665 --- /dev/null +++ b/sw/tests/cluster_tests/hello_pulp_barrier/pulp_task/hello_pulp_barrier_task.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * hello_pulp_barrier - PULP cluster task: core 0 forks a barrier-only entry onto all cores + */ + +#include "magia_tile_utils.h" +#include "cluster_utils.h" + +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" + :"=r"(hartid):); + return hartid; +} + +/* Runs on every core: just the barrier rendez-vous */ +static void barrier_entry(void *arg) { + (void)arg; + pi_cl_team_barrier(); +} + +void hello_pulp_barrier_task(void *data) { + (void)data; + + uint32_t hartid = get_hartid(); + uint32_t local_id = pi_core_id(); /* pulp-sdk-compatible, cluster_utils.h */ + uint32_t tile_id = cluster_tile_id(); /* MAGIA multi-tile extension, no pulp-sdk equivalent */ + + printf("[Tile %u PULP-%u mhartid %u] Hello World!\n", + tile_id, local_id, hartid); + + printf("[Tile %u PULP-%u mhartid %u] Starting cluster-wide Barrier (%d cores)\n", + tile_id, local_id, hartid, PULP_CORE_COUNT); + + /* pi_cl_team_fork() programs the barrier masks: it returns once every core has reached the barrier */ + pi_cl_team_fork(PULP_CORE_COUNT, barrier_entry, NULL); + + printf("[Tile %u PULP-%u mhartid %u] Barrier Complete\n", + tile_id, local_id, hartid); +} diff --git a/sw/tests/cluster_tests/hello_redmule_pulp/main.c b/sw/tests/cluster_tests/hello_redmule_pulp/main.c index 1778bfa6..e9fb3077 100644 --- a/sw/tests/cluster_tests/hello_redmule_pulp/main.c +++ b/sw/tests/cluster_tests/hello_redmule_pulp/main.c @@ -41,17 +41,28 @@ * X, W, Y live in tile-local L1 (private per tile, no cross-tile race). * The golden reference Z is read directly from z_oup[] in the main * ELF .rodata (already in L2), so no per-tile L2 copy is needed. */ -#define X_BASE (L1_BASE + 0x00012048) /* shared, read-only */ -#define W_BASE (L1_BASE + 0x00016048) /* shared, read-only */ +#define X_BASE l1(0x00012048) /* read-only */ +#define W_BASE l1(0x00016048) /* read-only */ /* PER-CORE output Y: each of the 8 cluster cores computes into its OWN private slot at Y_BASE + c*Y_STRIDE, so no two cores ever touch the same word -> the result is fully deterministic (no shared-buffer race). The CV32 main pre-loads every slot with the y_inp bias before dispatch, so the cores never reload the bias themselves. MUST match Y_BASE/Y_STRIDE in pulp_task/hello_redmule_pulp_task.c. */ -#define Y_BASE (L1_BASE + 0x0001A048) +#define Y_BASE l1(0x0001A048) #define Y_STRIDE (0x00001000) /* 4 KB per core (8 slots) */ +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" : "=r"(hartid)); + return hartid; +} + +/* L1_BASE is tile 0's L1 in the global map, add our own slice */ +static inline uint32_t l1(uint32_t off) { + return L1_BASE + get_hartid() * L1_TILE_OFFSET + off; +} + /* Enable the FPU: set mstatus.FS to INITIAL (01). * Without this any FPU instruction faults. CV32E40P (and the cluster * cores) need this even though FP is enabled at compile time. */ @@ -169,7 +180,7 @@ int main(void) { /* Arm EU before dispatching the task to avoid missing DONE. */ cluster_arm_done_event(); - cluster_dispatch_task(HELLO_REDMULE_PULP_TASK, 0xFFu); + cluster_dispatch_task(HELLO_REDMULE_PULP_TASK); /* Step 3: sleep (cv.elw) until all cluster cores have exited. */ cluster_wait_done_eu(); diff --git a/sw/tests/cluster_tests/hello_redmule_pulp/pulp_task/hello_redmule_pulp_task.c b/sw/tests/cluster_tests/hello_redmule_pulp/pulp_task/hello_redmule_pulp_task.c index b9120eaa..5f9d20fb 100644 --- a/sw/tests/cluster_tests/hello_redmule_pulp/pulp_task/hello_redmule_pulp_task.c +++ b/sw/tests/cluster_tests/hello_redmule_pulp/pulp_task/hello_redmule_pulp_task.c @@ -22,11 +22,13 @@ * All 8 cores serialize on hwpe_acquire_job(); the winner programs RedMulE * and polls STATUS. Each core writes into its own Y slot (Y_BASE + id*Y_STRIDE) * so there is no shared-buffer race. crt0 writes PULP_DONE on return. + * Core 0 forks the body onto all 8 cores with pi_cl_team_fork(). */ #include "magia_tile_utils.h" #include "redmule_mm_utils.h" #include "magia_utils.h" +#include "cluster_utils.h" /* Do NOT include x_input.h/w_input.h/y_input.h: with ORIGIN=0 their symbol addresses alias tile MMIO at runtime. Use X_BASE/W_BASE/Y_BASE instead. */ @@ -36,9 +38,10 @@ #define N_SIZE (64) #define K_SIZE (64) -#define X_BASE (L1_BASE + 0x00012048) /* shared, read-only */ -#define W_BASE (L1_BASE + 0x00016048) /* shared, read-only */ -#define Y_BASE (L1_BASE + 0x0001A048) +#define TILE_L1 (L1_BASE + cluster_tile_id() * L1_TILE_OFFSET) /* L1_BASE is tile 0's L1 in the global map */ +#define X_BASE (TILE_L1 + 0x00012048) /* read-only */ +#define W_BASE (TILE_L1 + 0x00016048) /* read-only */ +#define Y_BASE (TILE_L1 + 0x0001A048) #define Y_STRIDE (0x00001000) /* Per-core marker area in L2: 4B per cluster core, 8 cluster cores per @@ -59,7 +62,7 @@ static inline void enable_fpu(void) { ); } -void hello_redmule_pulp_task(void *data) { +static void hello_redmule_pulp_fork_entry(void *data) { (void)data; enable_fpu(); @@ -105,5 +108,9 @@ void hello_redmule_pulp_task(void *data) { printf("[Tile %u PULP-%u mhartid %u] RedMulE done, exiting\n", tile_id, local_id, hartid); } - /* trap_handler writes 1 to PULP_DONE on return. */ +} + +void hello_redmule_pulp_task(void *data) { + pi_cl_team_fork(PULP_CORE_COUNT, hello_redmule_pulp_fork_entry, data); + /* pulp_crt0.S dispatcher writes 1 to PULP_DONE on return. */ } diff --git a/sw/tests/cluster_tests/hello_spatz_pulp/main.c b/sw/tests/cluster_tests/hello_spatz_pulp/main.c index 5b89f0fa..c76a96ec 100644 --- a/sw/tests/cluster_tests/hello_spatz_pulp/main.c +++ b/sw/tests/cluster_tests/hello_spatz_pulp/main.c @@ -50,13 +50,25 @@ * ------------------------------------------------------------------------- */ #define VLEN 256 /* number of FP16 elements */ -#define X_BASE (L1_BASE + 0x00000000) /* X[256] : 512 B */ -#define Y_BASE (L1_BASE + 0x00001000) /* Y[256] : 512 B */ -#define Z_BASE (L1_BASE + 0x00002000) /* Z[256] : 512 B (Spatz out) */ -#define PARAMS_BASE (L1_BASE + 0x00003000) /* vecsum_params_t : 16 B */ +#define X_BASE l1(0x00000000) /* X[256] : 512 B */ +#define Y_BASE l1(0x00001000) /* Y[256] : 512 B */ +#define Z_BASE l1(0x00002000) /* Z[256] : 512 B (Spatz out) */ +#define PARAMS_BASE l1(0x00003000) /* vecsum_params_t : 16 B */ + +/* One uint32 slot per cluster core in L2, one block per tile: must match pulp_task/ */ +#define RESULT_BASE (L2_BASE + 0x00060000 + get_hartid() * RESULT_STRIDE) +#define RESULT_STRIDE (0x00000100) + +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" : "=r"(hartid)); + return hartid; +} -/* One uint32 slot per cluster core in L2. */ -#define RESULT_BASE (L2_BASE + 0x00060000) +/* L1_BASE is tile 0's L1 in the global map, add our own slice */ +static inline uint32_t l1(uint32_t off) { + return L1_BASE + get_hartid() * L1_TILE_OFFSET + off; +} /* ------------------------------------------------------------------------- * FP16 constants @@ -142,7 +154,7 @@ int main(void) { printf("[CV32] Booting PULP cluster cores...\n"); cluster_boot(PULP_BINARY_START); cluster_arm_done_event(); - cluster_dispatch_task(HELLO_SPATZ_PULP_TASK, 0xFFu); + cluster_dispatch_task(HELLO_SPATZ_PULP_TASK); cluster_wait_done_eu(); /* ------------------------------------------------------------------ @@ -170,5 +182,13 @@ int main(void) { errors++; } + /* Cross-check with the grand total the cluster returns through PULP_RETURN */ + uint32_t returned_total = (uint32_t)cluster_get_return_value(); + if (returned_total != grand_total) { + printf("[CV32] PULP_RETURN=0x%08x != L2 grand_total=0x%08x FAIL\n", + returned_total, grand_total); + errors++; + } + return (int)errors; } diff --git a/sw/tests/cluster_tests/hello_spatz_pulp/pulp_task/hello_spatz_pulp_task.c b/sw/tests/cluster_tests/hello_spatz_pulp/pulp_task/hello_spatz_pulp_task.c index 3cdcd0f2..061bdfda 100644 --- a/sw/tests/cluster_tests/hello_spatz_pulp/pulp_task/hello_spatz_pulp_task.c +++ b/sw/tests/cluster_tests/hello_spatz_pulp/pulp_task/hello_spatz_pulp_task.c @@ -20,26 +20,25 @@ /* * hello_spatz_pulp — PULP cluster-core task. * - * Dispatched via cluster_dispatch_task(HELLO_SPATZ_PULP_TASK, 0xFF). Each of the - * 8 cluster cores reads its 1/8th slice of the Z vector (filled by Spatz on - * the CV32 side: Z[i] = 1.0 + 2.0 = 3.0 FP16 = 0x4200), sums the raw uint16 - * bit-patterns into a uint32 partial sum, and writes the result to its - * per-core L2 slot. On return the trap handler writes 1 to PULP_DONE. + * Core 0 forks hello_spatz_pulp_fork_entry onto all 8 cores with pi_cl_team_fork(). + * Each core sums its slice of Z (Spatz output, 3.0 FP16 = 0x4200) as raw uint16 into its L2 slot. + * Core 0 then returns the grand total through PULP_RETURN. * * Memory layout (shared with main.c): - * Z_BASE = L1_BASE + 0x00002000 (256 FP16 elements, Spatz output) - * RESULT_BASE = L2_BASE + 0x00060000 (8 × uint32, one per cluster core) + * Z_BASE = this tile's L1 + 0x00002000 (256 FP16 elements, Spatz output) + * RESULT_BASE = L2_BASE + 0x00060000 + tile * RESULT_STRIDE (8 x uint32, one per cluster core) */ #include #include "magia_tile_utils.h" #include "cluster_utils.h" -#define VLEN 256 -#define Z_BASE (L1_BASE + 0x00002000) -#define RESULT_BASE (L2_BASE + 0x00060000) +#define VLEN 256 +#define Z_BASE (L1_BASE + cluster_tile_id() * L1_TILE_OFFSET + 0x00002000) +#define RESULT_BASE (L2_BASE + 0x00060000 + cluster_tile_id() * RESULT_STRIDE) +#define RESULT_STRIDE (0x00000100) -void hello_spatz_pulp_task(void *data) { +static void hello_spatz_pulp_fork_entry(void *data) { (void)data; uint32_t local_id = cluster_core_id(); @@ -57,3 +56,14 @@ void hello_spatz_pulp_task(void *data) { if (local_id == 0) printf("[PULP core 0] partial_sum=0x%08x\n", partial_sum); } + +int hello_spatz_pulp_task(void *data) { + pi_cl_team_fork(PULP_CORE_COUNT, hello_spatz_pulp_fork_entry, data); + + /* The fork's barrier guarantees every core has written its partial */ + uint32_t grand_total = 0; + for (int i = 0; i < PULP_CORE_COUNT; i++) { + grand_total += mmio32(RESULT_BASE + 4 * i); + } + return (int)grand_total; +} diff --git a/sw/tests/cluster_tests/parallel_groups/main.c b/sw/tests/cluster_tests/parallel_groups/main.c new file mode 100644 index 00000000..67a82ed2 --- /dev/null +++ b/sw/tests/cluster_tests/parallel_groups/main.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * parallel_groups - main core: dispatches one task that runs two disjoint teams concurrently, then checks both outputs + */ + +#include "magia_tile_utils.h" +#include "cluster_utils.h" + +#include "parallel_groups_pulp_task_bin.h" + +#define N 64 +#define X_BASE l1(0x00000000) +#define Y_BASE l1(0x00001000) +#define OUT_A_BASE l1(0x00002000) +#define OUT_B_BASE l1(0x00003000) + +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" : "=r"(hartid)); + return hartid; +} + +/* L1_BASE is tile 0's L1 in the global map, add our own slice */ +static inline uint32_t l1(uint32_t off) { + return L1_BASE + get_hartid() * L1_TILE_OFFSET + off; +} + +int main(void) { + uint32_t hartid = get_hartid(); + + printf("[Main core %u] Hello World!\n", (unsigned)hartid); + + volatile int32_t *X = (volatile int32_t *)X_BASE; + volatile int32_t *Y = (volatile int32_t *)Y_BASE; + for (int i = 0; i < N; i++) { + X[i] = i + 1; + Y[i] = 100; + } + + cluster_boot(PULP_BINARY_START); + cluster_arm_done_event(); + + printf("[Main core %u] Dispatching parallel_groups_task " + "(cores 0-3: X+Y, cores 4-7: X-Y, concurrently)...\n", + (unsigned)hartid); + cluster_dispatch_task(PARALLEL_GROUPS_TASK); + cluster_wait_done_eu(); + + volatile int32_t *OA = (volatile int32_t *)OUT_A_BASE; + volatile int32_t *OB = (volatile int32_t *)OUT_B_BASE; + uint32_t errors = 0; + + for (int i = 0; i < N; i++) { + int32_t exp_a = X[i] + Y[i]; + int32_t exp_b = X[i] - Y[i]; + if (OA[i] != exp_a) errors++; + if (OB[i] != exp_b) errors++; + } + + if (errors == 0) { + printf("[Main core %u] parallel_groups PASS (%d elements x 2 groups)\n", + (unsigned)hartid, N); + } else { + printf("[Main core %u] parallel_groups FAIL (%u mismatches)\n", + (unsigned)hartid, (unsigned)errors); + } + + return (int)errors; +} diff --git a/sw/tests/cluster_tests/parallel_groups/pulp_task/parallel_groups_task.c b/sw/tests/cluster_tests/parallel_groups/pulp_task/parallel_groups_task.c new file mode 100644 index 00000000..ed79f28a --- /dev/null +++ b/sw/tests/cluster_tests/parallel_groups/pulp_task/parallel_groups_task.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * parallel_groups - PULP cluster task: cores 0-3 compute X + Y while cores 4-7 compute X - Y + */ + +#include "magia_tile_utils.h" +#include "cluster_utils.h" + +#define N 64 +#define X_BASE l1(0x00000000) /* X[64] : 256 B */ +#define Y_BASE l1(0x00001000) /* Y[64] : 256 B */ +#define OUT_A_BASE l1(0x00002000) /* OUT_A[64] = X + Y */ +#define OUT_B_BASE l1(0x00003000) /* OUT_B[64] = X - Y */ + +/* L1_BASE is tile 0's L1 in the global map, add this cluster's tile slice */ +static inline uint32_t l1(uint32_t off) { + return L1_BASE + cluster_tile_id() * L1_TILE_OFFSET + off; +} + +#define GROUP_A_MASK 0x0F /* cores 0-3 */ +#define GROUP_A_SIZE 4 +#define GROUP_B_MASK 0xF0 /* cores 4-7 */ +#define GROUP_B_SIZE 4 +#define GROUP_B_BASE_ID 4 /* lowest core id in group B */ +#define GROUP_B_LEADER_ID 4 /* who reports completion */ +#define GROUP_B_BARRIER_ID 1 /* barrier 0 is group A's (pi_cl_team_fork) */ +#define GROUP_B_DONE_SW_EVT 0 /* SW event id used to notify core 0 */ +#define CORE0_MASK 0x01 + +static void group_a_entry(void *arg) { + (void)arg; + uint32_t local_id = pi_core_id(); /* 0..3 */ + uint32_t off = cluster_chunk_offset(N, GROUP_A_SIZE, local_id); + uint32_t size = cluster_chunk_size(N, GROUP_A_SIZE, local_id); + + volatile int32_t *X = (volatile int32_t *)X_BASE; + volatile int32_t *Y = (volatile int32_t *)Y_BASE; + volatile int32_t *OUT = (volatile int32_t *)OUT_A_BASE; + + for (uint32_t i = 0; i < size; i++) { + OUT[off + i] = X[off + i] + Y[off + i]; + } +} + +static void group_b_entry(void *arg) { + (void)arg; + uint32_t local_id = pi_core_id() - GROUP_B_BASE_ID; /* 0..3 */ + uint32_t off = cluster_chunk_offset(N, GROUP_B_SIZE, local_id); + uint32_t size = cluster_chunk_size(N, GROUP_B_SIZE, local_id); + + volatile int32_t *X = (volatile int32_t *)X_BASE; + volatile int32_t *Y = (volatile int32_t *)Y_BASE; + volatile int32_t *OUT = (volatile int32_t *)OUT_B_BASE; + + for (uint32_t i = 0; i < size; i++) { + OUT[off + i] = X[off + i] - Y[off + i]; + } + + /* Barrier private to cores 4-7, distinct from group A's barrier 0 */ + pi_cl_team_barrier_id(GROUP_B_BARRIER_ID); + + if (pi_core_id() == GROUP_B_LEADER_ID) { + pi_cl_sw_event_trigger(GROUP_B_DONE_SW_EVT, CORE0_MASK); + } +} + +void parallel_groups_task(void *data) { + (void)data; + + /* Push group B first: core 0 is not a member, so cores 4-7 start right away */ + pi_cl_team_barrier_setup(GROUP_B_BARRIER_ID, GROUP_B_MASK); + pi_cl_team_push_other(GROUP_B_MASK, group_b_entry, (void *)0); + + /* Group A on cores 0-3 with barrier 0, while group B is still running */ + pi_cl_team_fork(GROUP_A_SIZE, group_a_entry, (void *)0); + + /* Two barriers share one completion bit per core, so group B reports to core 0 with a SW event */ + pi_cl_sw_event_wait(GROUP_B_DONE_SW_EVT); +} diff --git a/sw/tests/cluster_tests/vector_add/main.c b/sw/tests/cluster_tests/vector_add/main.c new file mode 100644 index 00000000..8cbc15a7 --- /dev/null +++ b/sw/tests/cluster_tests/vector_add/main.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * vector_add - main core: bare-metal port of PLAY's vector_add, checks the cluster sums bit-exactly + */ + +#include +#include "magia_tile_utils.h" +#include "cluster_utils.h" +#include "vector_add_pulp_task_bin.h" + +#define LEN (2048) + +#define SRC_A_BASE l1(0x00000000) /* LEN*4 = 8 KB */ +#define SRC_B_BASE l1(0x00002000) /* LEN*4 = 8 KB */ +#define DST_BASE l1(0x00004000) /* LEN*4 = 8 KB */ +#define PARAMS_BASE l1(0x00006000) /* vector_add_params_t */ + +typedef struct { + uint32_t src_a; + uint32_t src_b; + uint32_t dst; + uint32_t len; +} vector_add_params_t; + +static inline uint32_t get_hartid(void) { + uint32_t hartid; + asm volatile("csrr %0, mhartid" : "=r"(hartid)); + return hartid; +} + +/* L1_BASE is tile 0's L1 in the global map, add our own slice */ +static inline uint32_t l1(uint32_t off) { + return L1_BASE + get_hartid() * L1_TILE_OFFSET + off; +} + +/* Multiples of 0.25 well inside the float32 exact range: every sum is exact */ +static inline float src_a_val(int i) { return (float)i * 0.5f; } +static inline float src_b_val(int i) { return (float)(LEN - i) * 0.25f; } + +int main(void) { + uint32_t hartid = get_hartid(); + + volatile float *src_a = (volatile float *)SRC_A_BASE; + volatile float *src_b = (volatile float *)SRC_B_BASE; + volatile float *dst = (volatile float *)DST_BASE; + for (int i = 0; i < LEN; i++) { + src_a[i] = src_a_val(i); + src_b[i] = src_b_val(i); + dst[i] = -1.0f; + } + + volatile vector_add_params_t *params = (volatile vector_add_params_t *)PARAMS_BASE; + params->src_a = SRC_A_BASE; + params->src_b = SRC_B_BASE; + params->dst = DST_BASE; + params->len = LEN; + + printf("[vector_add] %d elements on %d PULP cores\n", LEN, PULP_CORE_COUNT); + cluster_boot(PULP_BINARY_START); + + /* Arm EU before dispatching the task to avoid missing DONE. */ + cluster_arm_done_event(); + + cluster_dispatch_task_with_params(VECTOR_ADD_TASK, PARAMS_BASE); + + /* Sleep (cv.elw) until core 0 signals the fork has completed. */ + cluster_wait_done_eu(); + + if (cluster_task_crashed()) { + printf("[vector_add] FAIL: cluster task trapped (mcause=0x%08x)\n", + cluster_get_mcause()); + return 1; + } + + uint32_t errors = 0; + for (int i = 0; i < LEN; i++) { + float exp = src_a_val(i) + src_b_val(i); + if (dst[i] != exp) { + if (errors < 8) + printf("[vector_add] dst[%d] mismatch\n", i); + errors++; + } + } + + if (errors == 0) + printf("[Main core %u] vector_add PASS (%d elements)\n", (unsigned)hartid, LEN); + else + printf("[Main core %u] vector_add FAIL (%u/%d mismatches)\n", + (unsigned)hartid, (unsigned)errors, LEN); + + return (int)errors; +} diff --git a/sw/tests/cluster_tests/vector_add/pulp_task/vector_add_task.c b/sw/tests/cluster_tests/vector_add/pulp_task/vector_add_task.c new file mode 100644 index 00000000..8ccfb6cc --- /dev/null +++ b/sw/tests/cluster_tests/vector_add/pulp_task/vector_add_task.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * vector_add - PULP cluster task: PLAY's vector_add_pulp_open_cluster() forked onto all cores + */ + +#include +#include "magia_tile_utils.h" +#include "cluster_utils.h" + +#define NUM_CORES PULP_CORE_COUNT + +typedef struct { + uint32_t src_a; + uint32_t src_b; + uint32_t dst; + uint32_t len; +} vector_add_params_t; + +static int vector_add_cluster(const float *src_a, const float *src_b, float *dst, const int len) +{ + int rem_ops; + int tot_ops; + int block; + int start; + int left; + int end; + int op; + int id; + int i; + + id = pi_core_id(); + block = len / NUM_CORES; + left = len % NUM_CORES; + start = id * block + (id < left ? id : left); + end = start + block + (id < left ? 1 : 0); + tot_ops = (end - start) / 2; + rem_ops = tot_ops % 2; + i = start; + op = 0; + + do { + int idx1, idx2; + float a1, a2; + float b1, b2; + + idx1 = i; + idx2 = i + 1; + a1 = src_a[idx1]; + b1 = src_b[idx1]; + a2 = src_a[idx2]; + b2 = src_b[idx2]; + + dst[idx1] = a1 + b1; + dst[idx2] = a2 + b2; + + i += 2; + op++; + } while (op < tot_ops); + + if (rem_ops) + dst[end - 1] = src_a[end - 1] + src_b[end - 1]; + +#if NUM_CORES > 1 + pi_cl_team_barrier(); +#endif + + return 0; +} + +static void vector_add_fork_entry(void *arg) { + vector_add_params_t *p = (vector_add_params_t *)arg; + vector_add_cluster((const float *)p->src_a, (const float *)p->src_b, + (float *)p->dst, (int)p->len); +} + +int vector_add_task(void *data) { + pi_cl_team_fork(PULP_CORE_COUNT, vector_add_fork_entry, data); + return 0; +} diff --git a/sw/tests/hetero_mesh_test/main.c b/sw/tests/hetero_mesh_test/main.c index a51e9985..55a95713 100644 --- a/sw/tests/hetero_mesh_test/main.c +++ b/sw/tests/hetero_mesh_test/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2026 Fondazione Chips-IT + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. * SPDX-License-Identifier: Apache-2.0 - * - * Authors: Luca Balboni, Fondazione Chips-IT - * - * Test for the heterogeneous mesh + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * Test for the heterogeneous mesh */ @@ -156,7 +156,7 @@ static unsigned int run_cluster(void) { eu_init(); cluster_boot(PULP_BINARY_START); eu_cluster_done_init(); - cluster_dispatch_task(HETERO_CLUSTER_TASK, (1u << PULP_CORE_COUNT) - 1u); + cluster_dispatch_task(HETERO_CLUSTER_TASK); eu_cluster_done_wait(EU_WAIT_MODE_WFE); uint32_t base = CL_RESULT_BASE + get_hartid() * CL_RESULT_STRIDE; diff --git a/sw/tests/hetero_mesh_test/pulp_task/hetero_cluster_task.c b/sw/tests/hetero_mesh_test/pulp_task/hetero_cluster_task.c index 8f9c2bec..9cafcadb 100644 --- a/sw/tests/hetero_mesh_test/pulp_task/hetero_cluster_task.c +++ b/sw/tests/hetero_mesh_test/pulp_task/hetero_cluster_task.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2026 Fondazione Chips-IT + * Copyright (C) 2026 ETH Zurich, University of Bologna and Fondazione Chips-IT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. * SPDX-License-Identifier: Apache-2.0 - * - * Authors: Luca Balboni, Fondazione Chips-IT - * - * hetero_mesh_test - PULP cluster-core task. + * + * Author: Luca Balboni, Fondazione Chips-IT + * + * hetero_mesh_test - PULP cluster-core task */ #include @@ -27,7 +27,7 @@ #define RESULT_BASE (L2_BASE + 0x00080000) #define RESULT_STRIDE (0x00000100) -void hetero_cluster_task(void *data) { +static void hetero_cluster_fork_entry(void *data) { (void)data; uint32_t tile_id = cluster_tile_id(); @@ -40,3 +40,7 @@ void hetero_cluster_task(void *data) { mmio32(RESULT_BASE + tile_id * RESULT_STRIDE + 4 * local_id) = acc; } + +void hetero_cluster_task(void *data) { + pi_cl_team_fork(PULP_CORE_COUNT, hetero_cluster_fork_entry, data); +} diff --git a/sw/tests/tests.yml b/sw/tests/tests.yml index 64094a8c..1eeb0642 100644 --- a/sw/tests/tests.yml +++ b/sw/tests/tests.yml @@ -324,6 +324,11 @@ spatz_tests: # PULP cluster cluster_tests: + hello_pulp_barrier: + testbench: mesh + cores: [CV32E40P, CV32E40X] + sims: [questa, verilator] + requires: [] hello_pulp: testbench: tile cores: [CV32E40P, CV32E40X] @@ -344,3 +349,13 @@ cluster_tests: cores: [CV32E40P, CV32E40X] sims: [questa, verilator] requires: [] + parallel_groups: + testbench: tile + cores: [CV32E40P, CV32E40X] + sims: [questa, verilator] + requires: [] + vector_add: + testbench: tile + cores: [CV32E40P, CV32E40X] + sims: [questa, verilator] + requires: [] diff --git a/sw/utils/cluster_utils.h b/sw/utils/cluster_utils.h index 1b8d29fa..7e6fedbc 100644 --- a/sw/utils/cluster_utils.h +++ b/sw/utils/cluster_utils.h @@ -16,26 +16,26 @@ * * Bare-metal PULP Cluster Utilities for MAGIA. * - * Two usage perspectives: - * * CV32 (main core) — orchestrator: - * cluster_boot(binary) boot all PULP cores into the dispatcher - * loop (= pulp_init); polls PULP_READY. + * cluster_boot(binary) boot all PULP cores and poll PULP_READY (= pulp_init) * cluster_arm_done_event() clear/enable the CV32 EU done event - * cluster_dispatch_task() write NB_CORES_TO_WAIT, TASKBIN, START; - * returns once selected cores have ACK'd + * cluster_dispatch_task() write TASKBIN, ring START, return once core 0 has ACK'd * cluster_wait_done_polling() spin on the CV32 EU done event * cluster_done_pending() non-blocking EU done-event check * cluster_wait_done_eu() WFE on PULP_DONE (EU bit 12) - * cluster_stop() de-assert PULP CLK_EN + * cluster_task_crashed() true if core 0 trapped instead of returning * * PULP cluster core — worker: * cluster_core_id() local index within the cluster (0..N-1) * cluster_tile_id() tile index of this core * cluster_chunk_offset/size() data partition helpers * - * Hardware (obi_slave_ctrl_cluster.sv) memory map @ PULP_CTRL_BASE = 0x1740: - * see magia_tile_utils.h. EU bit 12 = PULP_DONE quorum. + * PULP cluster core — team fork/barrier (pulp-sdk API): + * pi_core_id() alias of cluster_core_id() + * pi_cl_team_fork(n, fn, arg) run fn(arg) on n cores, then rendez-vous + * pi_cl_team_barrier() rendez-vous on barrier 0 + * + * FETCH_EN is sticky in the cores: boot once, then dispatch without rewriting it (it clears READY). */ #ifndef CLUSTER_UTILS_H @@ -54,18 +54,13 @@ static inline void cluster_boot(uint32_t binary_start) { pulp_init(binary_start); } -static inline void cluster_dispatch_task(uint32_t task_addr, uint32_t core_mask) { - pulp_run_task(task_addr, core_mask); +static inline void cluster_dispatch_task(uint32_t task_addr) { + pulp_run_task(task_addr); } static inline void cluster_dispatch_task_with_params(uint32_t task_addr, - uint32_t params_ptr, - uint32_t core_mask) { - pulp_run_task_with_params(task_addr, params_ptr, core_mask); -} - -static inline void cluster_stop(void) { - pulp_clk_dis(); + uint32_t params_ptr) { + pulp_run_task_with_params(task_addr, params_ptr); } static inline void cluster_wait_done_polling(void) { @@ -84,6 +79,18 @@ static inline void cluster_wait_done_eu(void) { (void)eu_cluster_done_wait(EU_WAIT_MODE_WFE); } +static inline int32_t cluster_get_return_value(void) { + return (int32_t)mmio32(PULP_RETURN); +} + +static inline int cluster_task_crashed(void) { + return (mmio32(PULP_RETURN) & PULP_RETURN_CRASHED_BIT) != 0; +} + +static inline uint32_t cluster_get_mcause(void) { + return mmio32(PULP_RETURN) & ~PULP_RETURN_CRASHED_BIT; +} + // ============================================================================= // Cluster core (worker) — identity helpers // ============================================================================= @@ -115,4 +122,97 @@ static inline uint32_t cluster_chunk_size(uint32_t total, uint32_t n_cores, return (core_id == n_cores - 1) ? (total - base * core_id) : base; } +// ============================================================================= +// Cluster core — team fork/barrier, bare-metal reimplementation of the pulp-sdk API +// ============================================================================= + +#define CLUSTER_EU_DISPATCH_OFFSET (0x80) +#define CLUSTER_EU_DISPATCH_FIFO (CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_DISPATCH_OFFSET + 0x00) +#define CLUSTER_EU_DISPATCH_TEAM_CFG (CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_DISPATCH_OFFSET + 0x04) + +#define CLUSTER_EU_BARRIER_OFFSET (0x200) +#define CLUSTER_EU_BARRIER_SIZE (0x20) +#define CLUSTER_EU_BARRIER0_ADDR (CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_BARRIER_OFFSET) +#define CLUSTER_EU_BARR_TRIGGER_MASK (0x00) +#define CLUSTER_EU_BARR_TARGET_MASK (0x0C) +#define CLUSTER_EU_BARR_WAIT_CLEAR (0x1C) + +#define CLUSTER_EU_MUTEX_OFFSET (0xC0) +#define CLUSTER_EU_MUTEX0_ADDR (CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_MUTEX_OFFSET) + + +#define CLUSTER_EU_SW_EVT_TRIG_OFFSET (0x100) +#define CLUSTER_EU_SW_EVT_TRIG(id) (CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_SW_EVT_TRIG_OFFSET + ((id) << 2)) + +#define CLUSTER_EU_SW_EVT_BIT(id) (4u + (id)) + +static inline void pi_cl_sw_event_trigger(unsigned int id, uint32_t core_mask) { + mmio32(CLUSTER_EU_SW_EVT_TRIG(id)) = core_mask; +} + +static inline void pi_cl_sw_event_wait(unsigned int id) { + uint32_t bit = 1u << CLUSTER_EU_SW_EVT_BIT(id); + mmio32(CLUSTER_EU_DIRECT_BASE + 0x08) = bit; /* EU_CORE_MASK_OR */ + (void)evt_read32(CLUSTER_EU_DIRECT_BASE, 0x3C); /* EU_EVENT_WAIT_CLR */ +} + + +static inline uint32_t pi_core_id(void) { + return cluster_core_id(); +} + +static inline void pi_cl_team_critical_enter(void) { + (void)evt_read32(CLUSTER_EU_MUTEX0_ADDR, 0); +} + +static inline void pi_cl_team_critical_exit(void) { + mmio32(CLUSTER_EU_MUTEX0_ADDR) = 0; + __asm__ __volatile__("" ::: "memory"); /* compiler barrier: the unlock store has no memory clobber */ +} + +static inline void pi_cl_team_barrier_id(unsigned int barrier_id) { + uint32_t addr = CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_BARRIER_OFFSET + + barrier_id * CLUSTER_EU_BARRIER_SIZE; + (void)evt_read32(addr, CLUSTER_EU_BARR_WAIT_CLEAR); +} + + +static inline void pi_cl_team_barrier(void) { + (void)evt_read32(CLUSTER_EU_BARRIER0_ADDR, CLUSTER_EU_BARR_WAIT_CLEAR); +} + +static inline void pi_cl_team_fork(int nb_cores, void (*entry)(void *), void *arg) { + uint32_t core_mask = (nb_cores >= 32) ? 0xFFFFFFFFu : ((1u << nb_cores) - 1u); + + if (nb_cores) { + mmio32(CLUSTER_EU_DISPATCH_TEAM_CFG) = core_mask; + mmio32(CLUSTER_EU_BARRIER0_ADDR + CLUSTER_EU_BARR_TRIGGER_MASK) = core_mask; + mmio32(CLUSTER_EU_BARRIER0_ADDR + CLUSTER_EU_BARR_TARGET_MASK) = core_mask; + } + + mmio32(CLUSTER_EU_DISPATCH_FIFO) = (uint32_t)entry; + mmio32(CLUSTER_EU_DISPATCH_FIFO) = (uint32_t)arg; + + /* The calling core is itself a member of the team. */ + entry(arg); + + pi_cl_team_barrier(); +} + +/* Not in the pulp-sdk API: push a team on a disjoint core subset, with no auto barrier */ + +static inline void pi_cl_team_push_other(uint32_t core_mask, void (*entry)(void *), void *arg) { + mmio32(CLUSTER_EU_DISPATCH_TEAM_CFG) = core_mask; + mmio32(CLUSTER_EU_DISPATCH_FIFO) = ((uint32_t)entry) | 1u; /* bit 0 set: other entry, no auto barrier */ + mmio32(CLUSTER_EU_DISPATCH_FIFO) = (uint32_t)arg; +} + + +static inline void pi_cl_team_barrier_setup(unsigned int barrier_id, uint32_t core_mask) { + uint32_t addr = CLUSTER_EU_DIRECT_BASE + CLUSTER_EU_BARRIER_OFFSET + + barrier_id * CLUSTER_EU_BARRIER_SIZE; + mmio32(addr + CLUSTER_EU_BARR_TRIGGER_MASK) = core_mask; + mmio32(addr + CLUSTER_EU_BARR_TARGET_MASK) = core_mask; +} + #endif /* CLUSTER_UTILS_H */ diff --git a/sw/utils/magia_pulp_utils.h b/sw/utils/magia_pulp_utils.h index b04bd519..c1de13f5 100644 --- a/sw/utils/magia_pulp_utils.h +++ b/sw/utils/magia_pulp_utils.h @@ -14,22 +14,7 @@ * limitations under the License. * SPDX-License-Identifier: Apache-2.0 * - * Bare-metal PULP Cluster Utility Functions (CV32 control side). - * - * Dynamic dispatch model (CV32 -> PULP cluster): - * 1. pulp_init(binary) boot all cores into the PULP dispatcher loop; - * polls PULP_READY until every core is armed. - * 2. pulp_run_task(task,mask) dispatch the task function to a subset of - * cores: writes NB_CORES_TO_WAIT = popcount(mask), - * TASKBIN = task, then PULP_START = mask which - * fires per-core MEI pulses. CV32 polls - * PULP_START until it self-clears (all ACKs). - * 3. cluster_wait_done_eu()/_polling() wait for DONE quorum (PULP_DONE). - * - * pulp_run_task_with_params() also writes PULP_DATA so the task receives a - * context pointer as its first argument (a0 in the trap handler). - * - * Register map: see magia_tile_utils.h (PULP_CTRL_BASE). + * Bare-metal PULP Cluster Utility Functions (CV32 control side): boot the cluster and dispatch tasks to core 0 */ #ifndef MAGIA_PULP_UTILS_H #define MAGIA_PULP_UTILS_H @@ -39,17 +24,13 @@ /* ---- Low-level register helpers ---------------------------------------- */ -static inline void pulp_clk_en(void) { mmio32(PULP_CLK_EN) = 1; } -static inline void pulp_clk_dis(void) { mmio32(PULP_CLK_EN) = 0; } +/* Start all cores once after reset: FETCH_EN is sticky in the cores and every write clears READY */ +static inline void pulp_fetch_en(void) { mmio32(PULP_FETCH_EN) = 1; } static inline void pulp_set_binary(uint32_t addr) { mmio32(PULP_BINARY) = addr; } -static inline void pulp_set_nb_cores_to_wait(uint32_t nb_cores) { - mmio32(PULP_NB_CORES_TO_WAIT) = nb_cores; -} - static inline void pulp_set_func(uint32_t task_addr) { mmio32(PULP_TASKBIN) = task_addr; } @@ -58,47 +39,27 @@ static inline void pulp_pass_params(uint32_t params_ptr) { mmio32(PULP_DATA) = params_ptr; } -static inline uint32_t _pulp_popcount(uint32_t x) { - x = x - ((x >> 1) & 0x55555555u); - x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); - x = (x + (x >> 4)) & 0x0f0f0f0fu; - return (x * 0x01010101u) >> 24; -} - /* ---- High-level dispatch API ------------------------------------------- */ -/** - * @brief Boot the PULP cluster: write the binary entry point, enable all - * cores (CLK_EN broadcast), then wait until every core has armed its - * dispatcher (PULP_READY == 1). - */ +/* Boot once after reset: set the entry point, broadcast FETCH_EN, wait for PULP_READY */ static inline void pulp_init(uint32_t binary_start) { pulp_set_binary(binary_start); - pulp_clk_en(); + pulp_fetch_en(); while ((mmio32(PULP_READY) & 1u) == 0u) { } } -/** - * @brief Dispatch @p task_addr to the cores selected by @p core_mask. - * Returns once every selected core has ACK'd the start (write-0 to - * PULP_START), i.e. once the cores have entered the task function. - * Use cluster_wait_done_eu()/cluster_wait_done_polling() for completion. - */ -static inline void pulp_run_task(uint32_t task_addr, uint32_t core_mask) { - pulp_set_nb_cores_to_wait(_pulp_popcount(core_mask)); +/* Dispatch a task to core 0, returning once core 0 has ACK'd PULP_START */ +static inline void pulp_run_task(uint32_t task_addr) { pulp_set_func(task_addr); - mmio32(PULP_START) = core_mask; + mmio32(PULP_START) = 1; while (mmio32(PULP_START) != 0u) { } } -/** - * @brief Dispatch a task with a context pointer passed as first argument. - */ +/* Dispatch a task with a context pointer passed as first argument */ static inline void pulp_run_task_with_params(uint32_t task_addr, - uint32_t params_ptr, - uint32_t core_mask) { + uint32_t params_ptr) { pulp_pass_params(params_ptr); - pulp_run_task(task_addr, core_mask); + pulp_run_task(task_addr); } #endif /* MAGIA_PULP_UTILS_H */ diff --git a/sw/utils/magia_tile_utils.h b/sw/utils/magia_tile_utils.h index 63040896..3e4ac2a7 100644 --- a/sw/utils/magia_tile_utils.h +++ b/sw/utils/magia_tile_utils.h @@ -41,41 +41,27 @@ #define EVENT_UNIT_END (0x000016FF) #define SPATZ_CTRL_BASE (0x00001700) #define SPATZ_CTRL_END (0x0000173F) -/* PULP Cluster Control registers (tile_csr + 0x40), bare-metal dispatch model - * +0x00 PULP_CLK_EN : R/W broadcast enable. CV32 writes 1 to start - * ALL cores fetching from PULP_BINARY; writes 0 - * to disable. Writes also reset READY counter. - * +0x04 PULP_BINARY : entry point address (boot vector) for all - * cluster cores - * +0x08 PULP_NB_CORES_TO_WAIT : popcount of dispatch mask (ACK + DONE quorum) - * +0x0C PULP_DONE : W = each PULP hart signals completion; - * after the quorum the CSR emits EU bit 12 - * +0x10 PULP_TASKBIN : R/W per-dispatch task function address read - * by each PULP core in its trap handler - * +0x14 PULP_DATA : R/W per-dispatch opaque data ptr passed as - * first argument to the task - * +0x18 PULP_START : R/W CV32 writes one-hot mask -> per-core - * 1-cycle MEI pulse; cores write 0 to ACK; the - * register self-clears when all N ACKs arrive - * +0x1C PULP_READY : R = 1 once N_CLUSTER_CORES cores have booted; - * W = each core posts 1 when its dispatcher is - * armed (counter increment) - */ +/* PULP Cluster Control registers (tile_csr + 0x40), bare-metal dispatch model */ #define PULP_CTRL_BASE (0x00001740) -#define PULP_CLK_EN (PULP_CTRL_BASE + 0x00) -#define PULP_BINARY (PULP_CTRL_BASE + 0x04) -#define PULP_NB_CORES_TO_WAIT (PULP_CTRL_BASE + 0x08) -#define PULP_DONE (PULP_CTRL_BASE + 0x0C) -#define PULP_TASKBIN (PULP_CTRL_BASE + 0x10) -#define PULP_DATA (PULP_CTRL_BASE + 0x14) -#define PULP_START (PULP_CTRL_BASE + 0x18) -#define PULP_READY (PULP_CTRL_BASE + 0x1C) +#define PULP_FETCH_EN (PULP_CTRL_BASE + 0x00) /* R/W boot enable, sticky in the cores; every write resets READY */ +#define PULP_BINARY (PULP_CTRL_BASE + 0x04) /* R/W boot address of all cluster cores */ +#define PULP_DONE (PULP_CTRL_BASE + 0x08) /* W core 0 signals completion, pulsing EU bit 12 on the CV32 */ +#define PULP_TASKBIN (PULP_CTRL_BASE + 0x0C) /* R/W task function address, read by core 0 */ +#define PULP_DATA (PULP_CTRL_BASE + 0x10) /* R/W task argument (a0) */ +#define PULP_START (PULP_CTRL_BASE + 0x14) /* R/W CV32 rings core 0 (EU bit 13), core 0 writes 0 to ACK */ +#define PULP_READY (PULP_CTRL_BASE + 0x18) /* R 1 once all cores booted, W each core posts 1 once armed */ +#define PULP_RETURN (PULP_CTRL_BASE + 0x1C) /* R/W task exit code, written by core 0 before PULP_DONE */ +#define PULP_RETURN_CRASHED_BIT (0x80000000u) /* Set when core 0 trapped instead of returning */ #define PULP_CTRL_END (0x000017FF) #define PULP_CORE_COUNT (8) #define PULP_HARTID_BASE (32) /* 2 * NUM_CLUSTERS (16) */ #define COLL_CTRL_BASE (0x00001800) #define COLL_CTRL_END (0x000018FF) -#define RESERVED_START (0x00001900) +#define CLUSTER_EU_DIRECT_BASE (0x00001900) +#define CLUSTER_EU_DIRECT_END (0x000028FF) +#define CLUSTER_EU_BASE (0x00002900) +#define CLUSTER_EU_END (0x000038FF) +#define RESERVED_START (0x00003900) #define RESERVED_END (0x0000FFFF) #define STACK_START (0x00010000) #define STACK_END (0x0001FFFF) diff --git a/target/verilator/src/magia_tile_hier.sv b/target/verilator/src/magia_tile_hier.sv index 45eaa9d8..ccdfafeb 100644 --- a/target/verilator/src/magia_tile_hier.sv +++ b/target/verilator/src/magia_tile_hier.sv @@ -45,7 +45,7 @@ module magia_tile_hier output logic [63:0] mcycle_o, input logic [63:0] time_i, input logic [magia_pkg::N_IRQ-1:0] irq_i, - input logic [magia_tile_pkg::N_CLUSTER_CORES:0] debug_req_i, + input logic debug_req_i, output logic debug_havereset_o, debug_running_o, debug_halted_o, output logic debug_pc_valid_o, output logic [31:0] debug_pc_o,