146 lines
7.1 KiB
Bash
Executable File
146 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# Copyright (C) 2026 David (Corey) Herrera
|
|
#
|
|
# regen_tensix_program_header.sh -- the ONE entry point that regenerates
|
|
# foundry/tt/ft_tt_programs.h from the program directories under foundry/tt/programs/.
|
|
#
|
|
# WHY THIS EXISTS, IN ONE SENTENCE: a binary that reads its Tensix programs out of
|
|
# one absolute programs directory runs on exactly one machine, and this is the command that
|
|
# puts them inside the binary instead.
|
|
#
|
|
# It is the program-level twin of regen_tensix_kernel_header.sh, and it inherits that
|
|
# script's discipline for the same reasons:
|
|
#
|
|
# * IT REFUSES A STALE DIRECTORY rather than embedding it, BY CONTENT. Every image
|
|
# carries a `.prov` record hashing the files its compile opened and the recipe that
|
|
# reached code generation, and the emitter re-hashes them against the tree before it
|
|
# writes anything (tools/tensix_image_provenance.py). The `.src` sidecar's mtime
|
|
# comparison is still run and is still useful for a source that has VANISHED, but it is
|
|
# not the staleness check any more: an `explode` resets every mtime, which is how 21
|
|
# directories reported `current` with two of them provably stale. A stale directory on
|
|
# disk costs a re-run. A stale directory BAKED INTO THE BINARY survives a rebuild.
|
|
# * IT WRITES NOTHING WHEN IT REFUSES. The emitter builds the whole header in memory
|
|
# before it opens the output, so a refusal cannot leave a half-written header behind.
|
|
# * IT SAYS WHAT MOVED, ALWAYS, before anything is written.
|
|
#
|
|
# WHAT IT DOES **NOT** DO, so the guarantee is not overread: it does not build the programs.
|
|
# `build_tensix_program.sh` does that, per program, with its own CTARGS and CB table — there
|
|
# is no single recipe that reconstructs all of them, and inventing one here would be a
|
|
# second source of truth for how each program is built. This script embeds what is there and
|
|
# refuses what it cannot vouch for.
|
|
#
|
|
# Usage:
|
|
# tools/regen_tensix_program_header.sh [program-dir ...]
|
|
#
|
|
# With no arguments it takes every directory under $PROGRAMS that holds a brisc.elf.
|
|
#
|
|
# PROGRAMS where the program directories are (default ../programs)
|
|
# KERNELS the kernel source tree the sidecars name (default ../kernels)
|
|
# TOOLCHAIN recorded in the blob for logs (default 7.68.0)
|
|
# CHECK=1 diff against the committed header only; write nothing
|
|
#
|
|
# Runs on the card host: the program directories and the SFPI builds that produced them are
|
|
# there. THIS HAS NEVER BEEN RUN FOR REAL -- it was written on the Mac and gated against
|
|
# synthetic fixtures (tests/Makefile.programblob). The first real run needs a bank session
|
|
# and should be followed by `verify_tensix_program_blob ../programs`.
|
|
#
|
|
set -u
|
|
|
|
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
|
|
TT=$(cd -- "$HERE/.." && pwd)
|
|
|
|
PROGRAMS=${PROGRAMS:-$TT/programs}
|
|
KERNELS=${KERNELS:-$TT/kernels}
|
|
TOOLCHAIN=${TOOLCHAIN:-7.68.0}
|
|
CHECK=${CHECK:-0}
|
|
|
|
HEADER="$TT/ft_tt_programs.h"
|
|
STAGED=$(mktemp "${TMPDIR:-/tmp}/ft_tt_programs.XXXXXX.h")
|
|
|
|
say() { printf '\n=== %s\n' "$*"; }
|
|
die() { printf '\nregen-programs: FAILED -- %s\n' "$*" >&2; rm -f "$STAGED"; exit 1; }
|
|
|
|
[ -d "$KERNELS" ] || die "no kernel source tree at $KERNELS -- nothing can be checked
|
|
against it, and a program directory nobody can check must not be embedded"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Which directories. Named on the command line, or every one that looks like a
|
|
# build_tensix_program.sh output.
|
|
# ---------------------------------------------------------------------------
|
|
DIRS=("$@")
|
|
if [ ${#DIRS[@]} -eq 0 ]; then
|
|
[ -d "$PROGRAMS" ] || die "no program directory tree at $PROGRAMS"
|
|
while IFS= read -r elf; do
|
|
DIRS+=("$(dirname "$elf")")
|
|
done < <(find "$PROGRAMS" -mindepth 2 -maxdepth 2 -name brisc.elf | sort)
|
|
fi
|
|
[ ${#DIRS[@]} -gt 0 ] || die "no program directories found under $PROGRAMS. An EMPTY blob is
|
|
a real state, but it is not something a glob should be able to produce by accident:
|
|
ask for it explicitly with
|
|
python3 $HERE/emit_tensix_program_header.py -o $HEADER --allow-empty"
|
|
|
|
say "embedding ${#DIRS[@]} program director(y|ies)"
|
|
for dir in "${DIRS[@]}"; do
|
|
printf ' %s\n' "$dir"
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Emit. The emitter runs check_tensix_program_sources.sh itself and refuses on a
|
|
# stale sidecar, a missing firmware image, a duplicate name or an unloadable ELF.
|
|
# ---------------------------------------------------------------------------
|
|
say "emitting (the stale-source check runs inside this step)"
|
|
ARGS=()
|
|
for dir in "${DIRS[@]}"; do
|
|
ARGS+=(--program "$dir")
|
|
done
|
|
python3 "$HERE/emit_tensix_program_header.py" -o "$STAGED" --recorded-output "$HEADER" \
|
|
--kernels "$KERNELS" --toolchain "$TOOLCHAIN" \
|
|
--recipe "built by build_tensix_program.sh; see the log beside the directory" \
|
|
"${ARGS[@]}" || die "the emitter refused. Nothing was written."
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. The firmware sets, reported ALWAYS. Programs built from one tree share one set; more
|
|
# than one set is legal and is carried correctly, but it means a run that arms two
|
|
# families from different sets will be refused at load, which is worth knowing here
|
|
# rather than on the card.
|
|
# ---------------------------------------------------------------------------
|
|
say "firmware sets in this blob"
|
|
grep -c 'static const ft_tt_program_blob_firmware' "$STAGED" \
|
|
| awk '{printf " %s distinct firmware set(s)\n", $1}'
|
|
grep 'first seen in' "$STAGED" | sed 's/^/ /'
|
|
if [ "$(grep -c 'static const ft_tt_program_blob_firmware' "$STAGED")" -gt 1 ]; then
|
|
printf ' NOTE: these programs were NOT all built against one firmware. A kernel is
|
|
linked at its own firmware'"'"'s __fw_export_text_end, so any run that boots one
|
|
set and loads another set'"'"'s kernels is a wedged core -- and is refused. If that
|
|
was not intended, rebuild them together.\n'
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Against the committed header, before anything is written.
|
|
# ---------------------------------------------------------------------------
|
|
say "against the committed header"
|
|
if cmp -s "$STAGED" "$HEADER"; then
|
|
printf ' IDENTICAL: these directories reproduce the committed ft_tt_programs.h\n'
|
|
header_changed=0
|
|
else
|
|
printf ' DIFFERS. %s changed lines (the payload is one line per 12 bytes):\n' \
|
|
"$(diff "$HEADER" "$STAGED" 2>/dev/null | grep -c '^[<>]')"
|
|
diff "$HEADER" "$STAGED" 2>/dev/null | grep '^[<>]' | grep -v '0x' | head -30
|
|
header_changed=1
|
|
fi
|
|
|
|
if [ "$CHECK" != "0" ]; then
|
|
say "CHECK=1: nothing written"
|
|
rm -f "$STAGED"
|
|
exit $header_changed
|
|
fi
|
|
|
|
cp "$STAGED" "$HEADER" || die "cannot write $HEADER"
|
|
rm -f "$STAGED"
|
|
printf '\nregen-programs: wrote %s\n' "$HEADER"
|
|
printf 'regen-programs: NOW PROVE IT, by comparison and not by construction:\n'
|
|
printf ' make -f tools/Makefile verify-programs\n'
|
|
printf ' tools/build/verify_tensix_program_blob %s\n' "$PROGRAMS"
|
|
exit 0
|