Foundry/tt/tools/regen_tensix_kernel_header.sh

214 lines
10 KiB
Bash

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
#
# regen_tensix_kernel_header.sh -- the ONE entry point that rebuilds every embedded Tensix
# kernel and regenerates foundry/tt/ft_tt_kernels.h from the sources under tt/kernels/.
#
# WHY THIS EXISTS. There was no single command that did this, and that is exactly how the
# committed header went stale: it shipped C++ ELFs from before the Foundry move for weeks
# after the C kernels replaced them, because "regenerate the blob" was four shell commands
# nobody had written down together. A header nobody can regenerate in one step is a header
# that drifts from its sources silently -- and the drift is not a build error, it is the
# card running last month's kernel.
#
# THE FIRMWARE MOVES WITH THE KERNELS, AND THAT IS THE DANGEROUS PART.
# A kernel's .text is linked at its own firmware's __fw_export_text_end (build_tensix_
# kernel.sh 3-4). Rebuild the kernels, leave the firmware in FT_TT_FIRMWARE_DIR alone, and
# the kernel entry addresses no longer match the firmware the tests boot -- which is a
# wedged core, not a failed call. On 2026-08-13 four of five installed images were stale by
# exactly this route (entry 0x4dd0 where the new build wanted 0x5000). So this script
# ALWAYS compares the firmware it just built against the installed set and REFUSES to
# finish quietly when they differ:
#
# * default -- regenerate the header, then FAIL loudly if the firmware in
# FT_TT_FIRMWARE_DIR is not what these kernels were linked
# against, naming every image that moved.
# * INSTALL_FIRMWARE=1 -- back the installed set up FIRST (to a directory that is
# created fresh, never overwriting an existing backup), then
# install the new images.
# * CHECK=1 -- build and diff against the committed header; write nothing.
#
# Usage:
# tools/regen_tensix_kernel_header.sh [OUT_DIR]
#
# OUT_DIR scratch for the built ELFs (default /tmp/ftk_regen)
# FT_TT_FIRMWARE_DIR where the tests boot firmware from (default $HOME/ttprog)
# CHECK=1 diff only; do not write ft_tt_kernels.h and do not touch firmware
# INSTALL_FIRMWARE=1 back up and install the rebuilt firmware
#
# Runs on a host with the SFPI toolchain installed.
#
set -u
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
TT=$(cd -- "$HERE/.." && pwd)
OUT=${1:-/tmp/ftk_regen}
FIRMWARE_DIR=${FT_TT_FIRMWARE_DIR:-$HOME/ttprog}
CHECK=${CHECK:-0}
INSTALL_FIRMWARE=${INSTALL_FIRMWARE:-0}
TOOLCHAIN=${TOOLCHAIN:-7.68.0}
HEADER="$TT/ft_tt_kernels.h"
STAGED="$OUT/ft_tt_kernels.h.new"
# The five firmware images a resident program boots. All five or none: they are one build.
FIRMWARE_IMAGES="fw_brisc.elf fw_ncrisc.elf fw_trisc0.elf fw_trisc1.elf fw_trisc2.elf"
say() { printf '\n=== %s\n' "$*"; }
die() { printf '\nregen-kernels: FAILED -- %s\n' "$*" >&2; exit 1; }
cd "$TT" || die "cannot cd to $TT"
# ---------------------------------------------------------------------------
# 1. Build every embedded kernel. A fresh OUT, so a stale ELF cannot be picked up
# and re-embedded -- the exact failure this script exists to end.
# ---------------------------------------------------------------------------
say "building into $OUT (removed first, so nothing stale can be embedded)"
rm -rf "$OUT" || die "cannot clear $OUT"
mkdir -p "$OUT" || die "cannot create $OUT"
log() { printf '%s\n' "--- $1"; }
log "noop_compute.c"
bash build_tensix_kernel.sh kernels/noop_compute.c "$OUT/noop" \
> "$OUT/noop.log" 2>&1 || die "noop build (see $OUT/noop.log)"
log "delay_compute.c"
bash build_tensix_kernel.sh kernels/delay_compute.c "$OUT/delay" \
> "$OUT/delay.log" 2>&1 || die "delay build (see $OUT/delay.log)"
log "runtime_arg_probe_compute.c"
bash build_tensix_kernel.sh kernels/runtime_arg_probe_compute.c "$OUT/runtime_arg_probe" \
> "$OUT/runtime_arg_probe.log" 2>&1 || die "runtime_arg_probe build"
log "vector_add triad"
READER_CTARGS=0,1,2048 COMPUTE_CTARGS=0,1,16 WRITER_CTARGS=16,2048 \
bash build_tensix_program.sh kernels/vector_add_reader.c kernels/vector_add_compute.c \
kernels/vector_add_writer.c "$OUT/vector_add" \
> "$OUT/vector_add.log" 2>&1 || die "vector_add build (see $OUT/vector_add.log)"
for image in noop/trisc0.elf noop/trisc1.elf noop/trisc2.elf \
delay/trisc0.elf delay/trisc1.elf delay/trisc2.elf \
runtime_arg_probe/trisc0.elf runtime_arg_probe/trisc1.elf \
runtime_arg_probe/trisc2.elf \
vector_add/brisc.elf vector_add/ncrisc.elf vector_add/trisc0.elf \
vector_add/trisc1.elf vector_add/trisc2.elf; do
[ -s "$OUT/$image" ] || die "no ELF at $OUT/$image -- the build did not produce it"
done
# ---------------------------------------------------------------------------
# 2. The four builds must have produced ONE firmware, not four. A kernel links
# against its own tree's firmware, so if the trees disagree the header would
# carry images with mutually incompatible entry addresses.
# ---------------------------------------------------------------------------
say "the four builds agree on the TRISC firmware"
for id in 0 1 2; do
reference="$OUT/vector_add/fw_trisc$id.elf"
for tree in noop delay runtime_arg_probe; do
cmp -s "$reference" "$OUT/$tree/fw_trisc$id.elf" \
|| die "$tree/fw_trisc$id.elf differs from vector_add's -- the trees were not
built against one firmware, and the embedded kernels' entry addresses cannot all
be right"
done
printf ' fw_trisc%s.elf identical across all four trees\n' "$id"
done
# ---------------------------------------------------------------------------
# 3. Emit the header.
# ---------------------------------------------------------------------------
say "emitting the header"
python3 tools/emit_tensix_kernel_header.py -o "$STAGED" \
--program noop --toolchain "$TOOLCHAIN" --ctargs none \
--image MATH0="$OUT/noop/trisc0.elf" --image MATH1="$OUT/noop/trisc1.elf" \
--image MATH2="$OUT/noop/trisc2.elf" \
--program delay --toolchain "$TOOLCHAIN" --ctargs none \
--image MATH0="$OUT/delay/trisc0.elf" --image MATH1="$OUT/delay/trisc1.elf" \
--image MATH2="$OUT/delay/trisc2.elf" \
--program runtime_arg_probe --toolchain "$TOOLCHAIN" --ctargs none \
--image MATH0="$OUT/runtime_arg_probe/trisc0.elf" \
--image MATH1="$OUT/runtime_arg_probe/trisc1.elf" \
--image MATH2="$OUT/runtime_arg_probe/trisc2.elf" \
--program vector_add --toolchain "$TOOLCHAIN" \
--ctargs "DM0=0,1,2048 DM1=16,2048 MATH0..2=0,1,16" \
--image DM0="$OUT/vector_add/brisc.elf" --image DM1="$OUT/vector_add/ncrisc.elf" \
--image MATH0="$OUT/vector_add/trisc0.elf" --image MATH1="$OUT/vector_add/trisc1.elf" \
--image MATH2="$OUT/vector_add/trisc2.elf" \
|| die "the emitter refused"
# ---------------------------------------------------------------------------
# 4. Say what moved, ALWAYS -- before anything is written.
# ---------------------------------------------------------------------------
say "against the committed header"
if cmp -s "$STAGED" "$HEADER"; then
printf ' IDENTICAL: same sources reproduced the committed ft_tt_kernels.h byte for byte\n'
header_changed=0
else
printf ' DIFFERS. Changed lines (the ELF payload is one byte-array line per 12 bytes):\n'
diff "$HEADER" "$STAGED" | head -40
printf ' ... %s changed lines total\n' "$(diff "$HEADER" "$STAGED" | grep -c '^[<>]')"
header_changed=1
fi
# ---------------------------------------------------------------------------
# 5. The firmware. Compared ALWAYS, because a regen that silently desyncs the
# firmware is worse than no regen at all.
# ---------------------------------------------------------------------------
say "against the installed firmware in $FIRMWARE_DIR"
firmware_stale=0
for image in $FIRMWARE_IMAGES; do
built="$OUT/vector_add/$image"
installed="$FIRMWARE_DIR/$image"
if [ ! -f "$installed" ]; then
printf ' %-16s MISSING from %s\n' "$image" "$FIRMWARE_DIR"
firmware_stale=1
elif cmp -s "$built" "$installed"; then
printf ' %-16s matches\n' "$image"
else
printf ' %-16s STALE -- installed differs from what these kernels link against\n' \
"$image"
firmware_stale=1
fi
done
if [ "$CHECK" != "0" ]; then
say "CHECK=1: nothing written"
[ "$firmware_stale" = 0 ] || die "the installed firmware does not match this build"
exit $header_changed
fi
# ---------------------------------------------------------------------------
# 6. Install. Firmware FIRST and only on request, because installing it is the
# step that can wedge a core.
# ---------------------------------------------------------------------------
if [ "$firmware_stale" != 0 ]; then
if [ "$INSTALL_FIRMWARE" = "0" ]; then
cp "$STAGED" "$HEADER" || die "cannot write $HEADER"
printf '\nregen-kernels: wrote %s\n' "$HEADER"
die "the firmware in $FIRMWARE_DIR is NOT what these kernels were linked against.
The header is regenerated but the pair is DESYNCED: booting that firmware with
these kernels is a wedged core, not a failed call. Re-run with INSTALL_FIRMWARE=1
to back the old set up and install the new one, or point FT_TT_FIRMWARE_DIR at a
directory built from this same tree."
fi
stamp=$(date +%Y%m%d)
backup="$FIRMWARE_DIR.bak.$stamp"
suffix=1
# NEVER overwrite an existing backup: the previous good set is the only way back from
# a firmware install that turns out to be wrong.
while [ -e "$backup" ]; do
backup="$FIRMWARE_DIR.bak.$stamp.$suffix"
suffix=$((suffix + 1))
done
say "backing the installed firmware up to $backup"
cp -a "$FIRMWARE_DIR" "$backup" || die "backup failed -- nothing installed"
for image in $FIRMWARE_IMAGES; do
cp "$OUT/vector_add/$image" "$FIRMWARE_DIR/$image" || die "installing $image failed"
printf ' installed %s\n' "$image"
done
fi
cp "$STAGED" "$HEADER" || die "cannot write $HEADER"
printf '\nregen-kernels: wrote %s\n' "$HEADER"
exit 0