Foundry/tt/build_tensix_program.sh

126 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
#
# build_tensix_program.sh
# Builds a whole Tensix program -- all five processor images of one reader/compute/writer
# triad -- into a single output directory, by driving the two per-class builders:
#
# build_tensix_kernel.sh compute -> trisc0.elf trisc1.elf trisc2.elf
# build_tensix_dataflow_kernel.sh reader -> brisc.elf (NoC 0)
# build_tensix_dataflow_kernel.sh writer -> ncrisc.elf (NoC 1)
#
# Reader on BRISC and writer on NCRISC is a choice, not a constraint: what matters is
# that the two data-movement kernels sit on DIFFERENT NoCs so they do not contend
# (docs/tt_kernel_model.md 1). NOC_INDEX is baked into each ELF at compile time and the
# host must pass the matching brisc_noc_id in the launch message.
#
# Usage: build_tensix_program.sh <reader.cpp> <compute.cpp> <writer.cpp> [outdir]
# Env: READER_CTARGS compile-time args for the reader (default 0,1,2048)
# COMPUTE_CTARGS compile-time args for the compute (default 0,1,16)
# WRITER_CTARGS compile-time args for the writer (default 16,2048)
# CB_TABLE CB table driving the compute descriptors; unset means the
# legacy uniform Float16_b 32x32 set. Data movement has no
# descriptor header at all, so a CB table changes three of the
# five images -- the other two learn the tile size through
# READER_CTARGS / WRITER_CTARGS, and the two MUST agree
# WATCHER must be 0: Foundry's firmware has no watcher, and both
# per-class builders refuse anything else
# RELOCATE_KERNEL_TEXT
# 1 links all five kernels into the free L1 arena instead of the
# tail of their own firmware holes. Required for every op bigger
# than vector_add -- build_tensix_kernel.sh's KERNEL TEXT
# RELOCATION block has the measurements that make it mandatory.
# The five addresses are PARSED out of ft_tt_op_l1_layout.h, which
# is the same header that declares them to the host — so there is
# one source of truth and no second copy to keep in step
# Output: <outdir>/{brisc,ncrisc,trisc0,trisc1,trisc2}.elf, each with a .src sidecar,
# plus the fw_*.elf they were linked against.
#
# Feed the result to tools/emit_tensix_kernel_header.py to get ft_tt_kernels.h.
#
# Status: verified on the p150a card host 2026-08-11 building kernels/vector_add_{reader,compute,
# writer}.cpp; that build produced the committed ../ft_tt_kernels.h.
# Issues: None
# Todo: None
#
set -euo pipefail
HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
READER=${1:?usage: build_tensix_program.sh <reader.cpp> <compute.cpp> <writer.cpp> [outdir]}
COMPUTE=${2:?usage: build_tensix_program.sh <reader.cpp> <compute.cpp> <writer.cpp> [outdir]}
WRITER=${3:?usage: build_tensix_program.sh <reader.cpp> <compute.cpp> <writer.cpp> [outdir]}
OUT=${4:-/tmp/ttk_program}
mkdir -p "$OUT"
# Passed through explicitly rather than relied on being inherited, so that reading this
# file tells you every knob that reaches the per-class builders.
WATCHER=${WATCHER:-0}
# ---- THE RELOCATION MAP: READ FROM THE HEADER, NOT RETYPED HERE ------------------------
# Per-processor slabs inside the KERNEL_CONFIG region, which is where the stock loader puts kernel
# binaries too (each at kernel_config_base + a per-processor offset; the Blackhole region
# is 69 KiB from MEM_MAP_END, measured). Ours starts above MEM_MAP_END so it
# does not sit on the firmware .data staging; ft_tt_op_l1_layout.h has the full note.
#
# These addresses used to be TYPED HERE as well as declared in that header, with nothing
# enforcing agreement -- the same class of duplicated contract that FT_TT_KERNEL_BUILD_*
# has a check for. They are now PARSED out of the header, so there is one source of truth
# and a disagreement is impossible rather than merely unlikely. A missing header or a
# missing macro stops the build; it never falls back to a literal, because a build that
# quietly links kernel text where the launch message does not point produces five images
# that load fine and a core that runs nothing.
kernel_text_base_from_header() {
local header="$HERE/ft_tt_op_l1_layout.h"
local macro="FT_TT_OP_TEXT_OFFSET_$1"
local config_base offset
[ -f "$header" ] || { echo "missing $header — cannot derive the relocation map" >&2; exit 1; }
# `#define NAME 0x1234u` -> 0x1234. The u suffix and any trailing comment go.
config_base=$(awk '$1=="#define" && $2=="FT_TT_OP_KERNEL_CONFIG_BASE" {gsub(/[uU]$/,"",$3); print $3; exit}' "$header")
offset=$(awk -v m="$macro" '$1=="#define" && $2==m {gsub(/[uU]$/,"",$3); print $3; exit}' "$header")
if [ -z "$config_base" ] || [ -z "$offset" ]; then
echo "$header does not define FT_TT_OP_KERNEL_CONFIG_BASE and $macro" >&2
exit 1
fi
printf '0x%X\n' $(( config_base + offset ))
}
# Empty means "link in the firmware hole", i.e. exactly the pre-2026-08-12 behaviour.
BRISC_TEXT=""; NCRISC_TEXT=""; TRISC0_TEXT=""; TRISC1_TEXT=""; TRISC2_TEXT=""
if [ "${RELOCATE_KERNEL_TEXT:-0}" = 1 ]; then
BRISC_TEXT=$(kernel_text_base_from_header DM0)
NCRISC_TEXT=$(kernel_text_base_from_header DM1)
TRISC0_TEXT=$(kernel_text_base_from_header MATH0)
TRISC1_TEXT=$(kernel_text_base_from_header MATH1)
TRISC2_TEXT=$(kernel_text_base_from_header MATH2)
echo "### KERNEL TEXT RELOCATED (from ft_tt_op_l1_layout.h):" \
"brisc=$BRISC_TEXT ncrisc=$NCRISC_TEXT" \
"trisc0=$TRISC0_TEXT trisc1=$TRISC1_TEXT trisc2=$TRISC2_TEXT"
fi
echo "===== reader -> BRISC (NoC 0) ====="
CTARGS=${READER_CTARGS:-0,1,2048} NOC=0 WATCHER=$WATCHER KERNEL_TEXT_BASE=$BRISC_TEXT \
bash "$HERE/build_tensix_dataflow_kernel.sh" "$READER" brisc "$OUT"
echo "===== writer -> NCRISC (NoC 1) ====="
CTARGS=${WRITER_CTARGS:-16,2048} NOC=1 WATCHER=$WATCHER KERNEL_TEXT_BASE=$NCRISC_TEXT \
bash "$HERE/build_tensix_dataflow_kernel.sh" "$WRITER" ncrisc "$OUT"
echo "===== compute -> TRISC0/1/2 ====="
CTARGS=${COMPUTE_CTARGS:-0,1,16} WATCHER=$WATCHER CB_TABLE=${CB_TABLE:-} \
KERNEL_TEXT_BASE_TRISC0=$TRISC0_TEXT KERNEL_TEXT_BASE_TRISC1=$TRISC1_TEXT \
KERNEL_TEXT_BASE_TRISC2=$TRISC2_TEXT \
bash "$HERE/build_tensix_kernel.sh" "$COMPUTE" "$OUT"
echo "===== program images ====="
for image in brisc ncrisc trisc0 trisc1 trisc2; do
if [ ! -f "$OUT/$image.elf" ]; then
echo "MISSING $OUT/$image.elf" >&2
exit 1
fi
/opt/tenstorrent/sfpi/compiler/bin/riscv-tt-elf-size "$OUT/$image.elf" | tail -1 \
| awk -v n="$image.elf" -v s="$(cat "$OUT/$image.elf.src")" \
'{printf "%-12s text=%-6s data=%-4s bss=%-5s from %s\n", n, $1, $2, $3, s}'
done
echo __PROGRAM_DONE__