/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file ft_tt_op_l1_layout.h * @brief Where a Phase 4 op puts things in one Tensix's L1 — kernel text included, which * is the difference from the Phase 2 layout. * * --------------------------------------------------------------------------- * WHY KERNEL TEXT MOVED, AND WHERE UPSTREAM PUTS IT * --------------------------------------------------------------------------- * Phase 2's layout header says "kernel TEXT is not in this map — it is linked just after * its processor's firmware." That is true of `vector_add` and it does not survive contact * with a real op. A firmware hole's tail is 1360 B on TRISC0, 1952 B on TRISC1, 1392 B on * TRISC2 and 1296 B on NCRISC, and what is above it is the NEXT processor's firmware, not * free L1 (docs/tt_launch_notes.md §2, corrected). Measured on the card host 2026-08-12: * * vector_add_compute trisc0 1292 trisc1 780 trisc2 1280 fits, 68 B to spare * matmul_compute trisc0 1404 trisc1 900 trisc2 1316 TRISC0 over by 44 * rms_norm_compute trisc0 2448 trisc1 2504 trisc2 2548 all three over * * **Upstream never had this problem, because upstream does not put kernels there.** * `ConfigureKernelGroup()` writes every * kernel binary at `kernel_config_base + kernel_text_offsets[processor]`, where * `kernel_config_base = hal.get_dev_addr(TENSIX, HalL1MemAddrType::KERNEL_CONFIG)`. On * Blackhole that is `MEM_MAP_END` with a **69 KiB** region above it * (`default_l1_kernel_config_size`), sub-allocated per * processor, with the buffer arena (`DEFAULT_UNRESERVED`) starting past its end. So the * structure below is theirs: one region, per-processor slabs inside it, CB config and * runtime args in the same region, arena after it. * * **The two deliberate divergences, both stated so nobody "fixes" them:** * * 1. **Our base is 0x14000, not `MEM_MAP_END` (0x9F80).** `MEM_MAP_END` is also * `MEM_BRISC_INIT_LOCAL_L1_BASE_SCRATCH`: the per-processor firmware `.data` staging * that `do_crt1()` reads at boot. Upstream overwrites it once firmware is up, because * its cores are booted once by the runtime. Ours are booted by whichever test is * running, and a core re-deasserted after its staging was overwritten boots into * garbage globals with no error anywhere. 0x14000 is above the staging AND the * bank→NoC tables (0x137A0), so a re-boot is always safe. It costs ~41 KiB of L1. * 2. **We do not XIP.** `ElfFile::MakeExecuteInPlace()` is one line — * `GetSegments().front().address = 0` — which works because their * text is position-independent and the runtime then places it wherever the program's * allocator chose. We instead LINK each kernel at its slab address * (`--defsym=__fw_export_text_end=`, build_tensix_kernel.sh), so the linker * resolves everything and nothing depends on the text being relocatable. The cost is * that a binary is bound to one slab, which is exactly right while one program at a * time runs on a core, and is what Phase 5 will have to revisit if it wants several * resident programs. * * A happy consequence of using a real KERNEL_CONFIG region: `kernel_text_offset` is now a * small POSITIVE number, the same as upstream's, instead of leaning on the 32-bit unsigned * wraparound docs/tt_launch_notes.md §3 describes. The wraparound still works and is still * tested; it is simply no longer load-bearing. * * **THIS HEADER IS THE ONE SOURCE OF THE RELOCATION MAP.** `build_tensix_program.sh` * used to carry its own copy of these five addresses with nothing enforcing agreement; * since 2026-08-12 it PARSES `FT_TT_OP_KERNEL_CONFIG_BASE` and the five * `FT_TT_OP_TEXT_OFFSET_*` out of this file instead, and stops if either is missing. * Editing the map here is therefore enough. A disagreement could never corrupt anything * — the ELF carries its own load addresses, so the loader writes where the ELF says — * but it would leave the launch message pointing at empty L1, which * ft_tt_op_image_load()'s slab check turns into a refusal rather than a silent no-op. * * --------------------------------------------------------------------------- * THE MAP * --------------------------------------------------------------------------- * 0x14000 kernel-config region, 69 KiB, ends 0x25400 * +0x0000 CB descriptor array, 64 CBs x 4 words * +0x0400 runtime args, 5 blocks of 0x80 * +0x1000 BRISC kernel text 0x15000 \ * +0x4000 NCRISC kernel text 0x18000 | 12 KiB slabs. Largest image so far is * +0x7000 TRISC0 kernel text 0x1B000 | 2548 B; the slack is there so growing * +0xA000 TRISC1 kernel text 0x1E000 | an op is not a relayout. * +0xD000 TRISC2 kernel text 0x21000 / * 0x26000 circular-buffer arena (upstream's DEFAULT_UNRESERVED), 1,351,680 B * 0x170000 Phase 5 work queue, 64 KiB, to the top of L1 (ft_tt_queue.h) * 0x180000 end of L1 * * **What the arena is worth as a PREFETCH DEPTH**, since that is the only thing in a * Phase 4 op whose size is not fixed: the matmul gives each input CB two blocks of * `depth` pages and the output CB two pages, so * `2*depth*(in0_tile + in1_tile) + 2*out_tile <= 1,351,680` — depth <= **164** for a * bf16 weight, **214** for Bfp8_b, **256** for Bfp4_b. MiniCPM5-1B's deepest K is * `ffn_down` at Kt = 144, and depth is shrunk to a divisor of Kt, so **this model can * never reach the ceiling**. Measured, not only derived: depth 144 on `ffn_down` places * and runs, PCC unchanged (docs/tt_bringup_log.md §17). * * Every offset inside the region is under 64 KiB, which the 16-bit `local_cb_offset` and * `rta_offset` wire fields require. * * @status Used by ft_tt_op_matmul.c, ft_tt_op_rms_norm.c and tests/test_ops.c. * @issues None * @todo None */ #ifndef FT_TT_OP_L1_LAYOUT_H #define FT_TT_OP_L1_LAYOUT_H #include #include "ft_tt_memmap.h" /** Everything in the region is an offset from here, including the 16-bit wire fields. * Upstream's equivalent is MEM_MAP_END; divergence 1 in the header note says why ours is * higher. 16-byte aligned (FT_TT_MAX_NOC_WRITE_ALIGNMENT). */ #define FT_TT_OP_KERNEL_CONFIG_BASE 0x00014000u /** `default_l1_kernel_config_size`. Taken as-is: it is upstream's * own budget for exactly this content, and our five images plus config fit inside it. */ #define FT_TT_OP_KERNEL_CONFIG_BYTES (69u * 1024u) /** 64 CBs x 4 words, at offset 0 from the config base. */ #define FT_TT_OP_CB_CONFIG_ADDRESS FT_TT_OP_KERNEL_CONFIG_BASE /** Per-processor runtime-arg blocks. 0x80 apart is 32 args each, more than any Phase 4 * kernel takes; every ENABLED processor needs a real block, not just the interesting one * (docs/tt_launch_notes.md §3, "the trap that cost a run"). */ #define FT_TT_OP_RTA_BASE (FT_TT_OP_KERNEL_CONFIG_BASE + 0x0400u) #define FT_TT_OP_RTA_STRIDE 0x80u #define FT_TT_OP_RTA_ADDRESS(processor) \ (FT_TT_OP_RTA_BASE + (uint32_t)(processor) * FT_TT_OP_RTA_STRIDE) /** Per-processor kernel text slabs, as offsets from the config base. Mirrors the map * build_tensix_program.sh applies under RELOCATE_KERNEL_TEXT=1. */ #define FT_TT_OP_TEXT_SLAB_BYTES 0x3000u #define FT_TT_OP_TEXT_OFFSET_DM0 0x1000u #define FT_TT_OP_TEXT_OFFSET_DM1 0x4000u #define FT_TT_OP_TEXT_OFFSET_MATH0 0x7000u #define FT_TT_OP_TEXT_OFFSET_MATH1 0xA000u #define FT_TT_OP_TEXT_OFFSET_MATH2 0xD000u /** Where circular-buffer FIFOs start, and the first address past the arena. The base is * the region end rounded up, i.e. upstream's DEFAULT_UNRESERVED rule. * * The arena ENDS at the Phase 5 work queue rather than at the top of L1. Putting the * queue at the top and growing CBs up from the bottom is what keeps the two from * needing to know about each other: an op's CB placement is unchanged, and the only * coupling is this one subtraction. A resident kernel that also uses CBs (Phase 6) gets * the same arena every Phase 4 op had, minus 64 KiB. */ #define FT_TT_OP_CB_ARENA_BASE 0x00026000u #define FT_TT_OP_CB_ARENA_END FT_TT_OP_QUEUE_BASE /** The Phase 5 work-queue region: control block plus descriptor ring, at the TOP of L1. * 64 KiB holds a 1024-slot ring of 32-byte descriptors with room over. Its internal * layout is ft_tt_queue.h's business, not this map's. */ #define FT_TT_OP_QUEUE_BYTES 0x00010000u #define FT_TT_OP_QUEUE_BASE (FT_TT_MEM_L1_SIZE - FT_TT_OP_QUEUE_BYTES) /* 0x170000 */ /** A 32x32 tile, per format. The block-float sizes are mantissa bytes plus a 64-byte * shared-exponent section, and they are what foundry/convert/ft_bfp.c * emits — so an artifact tile is a byte copy into a CB, with no conversion at either * end. That is Route B's payoff (spec §6.3) stated as a number. */ #define FT_TT_OP_TILE_BYTES_FP32 4096u #define FT_TT_OP_TILE_BYTES_BF16 2048u #define FT_TT_OP_TILE_BYTES_BFP8_B 1088u #define FT_TT_OP_TILE_BYTES_BFP4_B 576u /** Elements down and across one tile. */ #define FT_TT_OP_TILE_DIM 32u /** The kernel text base for an FT_TT_PROC_* index, or 0 if the index is out of range. */ static inline uint32_t ft_tt_op_text_base(unsigned processor) { switch (processor) { case FT_TT_PROC_DM0: return FT_TT_OP_KERNEL_CONFIG_BASE + FT_TT_OP_TEXT_OFFSET_DM0; case FT_TT_PROC_DM1: return FT_TT_OP_KERNEL_CONFIG_BASE + FT_TT_OP_TEXT_OFFSET_DM1; case FT_TT_PROC_MATH0: return FT_TT_OP_KERNEL_CONFIG_BASE + FT_TT_OP_TEXT_OFFSET_MATH0; case FT_TT_PROC_MATH1: return FT_TT_OP_KERNEL_CONFIG_BASE + FT_TT_OP_TEXT_OFFSET_MATH1; case FT_TT_PROC_MATH2: return FT_TT_OP_KERNEL_CONFIG_BASE + FT_TT_OP_TEXT_OFFSET_MATH2; default: return 0u; } } #endif /* FT_TT_OP_L1_LAYOUT_H */