/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file ft_tt_phase2_l1_layout.h * @brief Where the Phase 2 bring-up tests put things in one Tensix's L1 — DERIVED at run * time from where the firmware images actually landed, not hand-chosen. * * One header rather than per-test constants, because the tests have to agree: * test_phase2_images.c checks that no loaded kernel reaches the allocation floor, * test_phase2_launch.c plants runtime args here, and test_phase2_vector_add.c puts the * circular buffers and the data buffers here. * * The base used to be the literal 0x14000, picked by hand to clear the firmware `.data` * staging and the bank tables. ft_tt_image_placement_l1_floor() exists to compute * exactly that number, so the base now comes from * ft_tt_phase2_l1_layout_derive(): the highest address the five FIRMWARE images * reached, or the end of the regions the stock memory map reserves above MEM_MAP_END, * whichever is higher. Everything else is a fixed offset from it. * * Constraints the offsets satisfy, all from docs/tt_launch_notes.md §2: * - the base is at or above FT_TT_L1_USABLE_BASE (0x9F80), the allocator floor; * - and above the firmware `.data` staging, NCRISC's IRAM staging and the two bank * tables, which the usable-base macro does NOT cover — see * FT_TT_PHASE2_RESERVED_L1_END; * - the CB-config array and every runtime-arg block sit within 64 KiB of the base, * because those wire fields are 16-bit; * - the base is 16-byte aligned (FT_TT_MAX_NOC_WRITE_ALIGNMENT), and so is every * offset, because a CB FIFO address is shifted by 4 bits on a TRISC. * * Kernel TEXT is not in this map. It is linked at a fixed low address just after its * processor's firmware, not allocated from here — see the notes' §2. * * @status Used by the on-card Phase 2 tests. * @issues None * @todo None */ #ifndef FT_TT_PHASE2_L1_LAYOUT_H #define FT_TT_PHASE2_L1_LAYOUT_H #include #include "ft_tt_launch.h" #include "ft_tt_memmap.h" /** * First L1 address above every region the stock memory map stages something into. * * FT_TT_L1_USABLE_BASE is MEM_MAP_END, and it is NOT this number: the five per-processor * `.data` staging regions, NCRISC's IRAM staging and the two bank tables all live ABOVE * it (ft_tt_memmap.h). A floor derived from the loaded firmware images alone would sit * under the bank tables, because those are written by firmware and are in no ELF. */ #define FT_TT_PHASE2_RESERVED_L1_END \ (FT_TT_MEM_LOGICAL_TO_VIRTUAL_SCRATCH + FT_TT_MEM_LOGICAL_TO_VIRTUAL_SIZE) /** * What the derived base is rounded up to — the DRAM alignment, 64 B, NOT L1's 16 B. * * **This cost a debugging session and is the non-obvious part of this file.** A circular * buffer in this map is the LOCAL endpoint of the reader's `noc_async_read` from DRAM, * and the binding alignment for a transfer is the stricter of the two ends. The * hand-chosen 0x14000 satisfied it by accident; the first derived base, 0x137A0, is * 16 B aligned and 32 B aligned but not 64, and every tile came back with its LAST 64 * BYTES wrong — a silent, partial, byte-exact-until-element-992 corruption, not a * refusal. Every offset below is a multiple of 64, so aligning the base is enough to * align the whole map. */ #define FT_TT_PHASE2_LAYOUT_ALIGNMENT FT_TT_DRAM_ALIGNMENT /* Offsets from the derived base. Kept as macros so the relationship between them is * readable in one place; the addresses themselves are struct fields below. */ #define FT_TT_PHASE2_CB_CONFIG_OFFSET 0x0000u /**< 64 CBs x 4 words */ #define FT_TT_PHASE2_RTA_OFFSET 0x0400u #define FT_TT_PHASE2_RTA_STRIDE 0x0080u #define FT_TT_PHASE2_PROBE_OFFSET 0x2000u #define FT_TT_PHASE2_CB_IN0_OFFSET 0x4000u #define FT_TT_PHASE2_CB_IN1_OFFSET 0x5000u #define FT_TT_PHASE2_CB_OUT_OFFSET 0x6000u #define FT_TT_PHASE2_SOURCE0_OFFSET 0xC000u #define FT_TT_PHASE2_SOURCE1_OFFSET 0xE000u #define FT_TT_PHASE2_DESTINATION_OFFSET 0x10000u /** Scratch stride for the runtime-arg probe kernel, one word per TRISC. */ #define FT_TT_PHASE2_PROBE_STRIDE 4u /** A 32x32 Float16_b tile, the default tile shape the generated CB descriptors describe * (build_tensix_kernel.sh emits Float16_b / 32x32 / 4 faces). */ #define FT_TT_PHASE2_TILE_BYTES 2048u /** CB indices. These MUST match the compile-time args the kernels were built with — * vector_add is built 0,1 in / 16 out. */ #define FT_TT_PHASE2_CB_IN0 0u #define FT_TT_PHASE2_CB_IN1 1u #define FT_TT_PHASE2_CB_OUT 16u /** Two pages per CB: enough for the FIFO to be a ring rather than a single slot, small * enough that a wrong page count shows up as a wrap bug rather than silence. */ #define FT_TT_PHASE2_CB_PAGE_COUNT 2u #define FT_TT_PHASE2_CB_BYTES (FT_TT_PHASE2_CB_PAGE_COUNT * FT_TT_PHASE2_TILE_BYTES) /** One core's Phase 2 L1 map. Every address is the derived base plus a fixed offset. */ typedef struct ft_tt_phase2_l1_layout { uint32_t kernel_config_base; /**< everything the launch message's offsets are relative to */ uint32_t cb_config_address; uint32_t runtime_args_base; uint32_t probe_address; uint32_t cb_in0_address; uint32_t cb_in1_address; uint32_t cb_out_address; uint32_t source0_address; uint32_t source1_address; uint32_t destination_address; /** No loaded kernel image may reach this address. Same as kernel_config_base; named * separately because that is the question test_phase2_images.c asks. */ uint32_t allocation_floor; } ft_tt_phase2_l1_layout; /** * Build the map from where the five FIRMWARE images landed. * * The base is max(ft_tt_image_placement_l1_floor(placements, count), * FT_TT_PHASE2_RESERVED_L1_END), which is the whole point: the first term is what the * loader measured on this core with this firmware build, the second is what the memory * map reserves whether or not an ELF covers it. * * @param placements firmware placements from ft_tt_core_boot_firmware(); entries with * bytes_written == 0 are ignored. NULL is allowed with count 0, which * yields the reserved-region floor alone * @param count number of entries * @param out_layout written only on success * @return FT_TT_OK; FT_TT_EINVAL for a NULL out_layout, a floor * ft_tt_image_placement_l1_floor() refuses, or a base whose highest * offset would leave L1 * * Thread-safe: yes Reentrant: yes Signal-safe: no (logs on failure) */ int ft_tt_phase2_l1_layout_derive(const ft_tt_image_placement *placements, unsigned count, ft_tt_phase2_l1_layout *out_layout); /** * Runtime-arg block address for one processor: the base plus FT_TT_PHASE2_RTA_OFFSET plus * `processor` strides. Returns 0 for a NULL layout or an out-of-range processor, which is * the "this processor has no args" value the launch encoder already understands. */ uint32_t ft_tt_phase2_runtime_args_address(const ft_tt_phase2_l1_layout *layout, unsigned processor); #endif /* FT_TT_PHASE2_L1_LAYOUT_H */