285 lines
14 KiB
Makefile
285 lines
14 KiB
Makefile
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# Copyright (C) 2026 David (Corey) Herrera
|
|
# ft_tt_core — per-core targeting, health probing and the recovery ladder.
|
|
#
|
|
# make PHASE1B=1
|
|
# ./build/ft_tt_core status all
|
|
#
|
|
# PHASE1B=1 is mandatory. Without it every device call returns FT_TT_EPERM before touching
|
|
# the NoC — the fence exists because a wrong NoC coordinate hangs the chip and costs a
|
|
# physical power cycle (spec §7.3). A diagnostic tool is exactly the thing that must not
|
|
# be built with the fence quietly bypassed by default.
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# WHY THIS LINKS TWO FILES OUT OF tests/
|
|
# ---------------------------------------------------------------------------
|
|
# ft_tt_hardware_session.c owns the firmware boot sequence, and ft_tt_phase2_l1_layout.c
|
|
# derives the L1 map from where the firmware images landed. Both are labelled test
|
|
# scaffolding, and both are exactly what an operational tool needs. Copying the boot
|
|
# sequence into tools/ would produce a SECOND, subtly different boot sequence — and every
|
|
# ordering constraint in it is load-bearing (docs/tt_launch_notes.md §6). They are linked,
|
|
# not duplicated. If a second consumer appears they belong in the backend proper.
|
|
|
|
CC ?= cc
|
|
SRC_DIR := ..
|
|
TEST_DIR := ../tests
|
|
BUILD := build
|
|
|
|
CFLAGS := -std=c99 -O1 -g \
|
|
-Wall -Wextra -Werror -pedantic \
|
|
-Wshadow -Wconversion -Wstrict-prototypes -Wformat=2 \
|
|
-fstack-protector-strong \
|
|
-I$(SRC_DIR) -I$(TEST_DIR)
|
|
|
|
DEPFLAGS := -MMD -MP
|
|
|
|
# The regen targets compile nothing for the host and never open the card, so they are
|
|
# exempt: demanding PHASE1B=1 to regenerate a header would be a fence in front of the wrong
|
|
# door. `verify-programs` is exempt for the stronger reason that it must NEVER be built with
|
|
# the fence open — it compares bytes in this binary against files on disk and has no
|
|
# business being able to reach a card at all.
|
|
ifeq ($(filter regen-kernels regen-kernels-check regen-programs regen-programs-check \
|
|
verify-programs explode-programs check-kernel-compile-time-args \
|
|
check-program-directory \
|
|
check-program-provenance,$(MAKECMDGOALS)),)
|
|
ifndef PHASE1B
|
|
$(error ft_tt_core drives the card: build with PHASE1B=1 or every device call is refused)
|
|
endif
|
|
endif
|
|
# Guarded, so the exempt targets above cannot pick the define up by accident. Every
|
|
# non-exempt goal has already been stopped by the $(error) unless PHASE1B is set, so this
|
|
# changes nothing for them.
|
|
ifdef PHASE1B
|
|
CFLAGS += -DFT_TT_PHASE1B_NOC_ENABLED
|
|
endif
|
|
|
|
BACKEND_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
|
|
$(SRC_DIR)/ft_tt_arch.c \
|
|
$(SRC_DIR)/ft_tt_arch_blackhole.c \
|
|
$(SRC_DIR)/ft_tt_device.c \
|
|
$(SRC_DIR)/ft_tt_tlb.c \
|
|
$(SRC_DIR)/ft_tt_coord.c \
|
|
$(SRC_DIR)/ft_tt_arc_telemetry.c \
|
|
$(SRC_DIR)/ft_tt_noc.c \
|
|
$(SRC_DIR)/ft_tt_buffer.c \
|
|
$(SRC_DIR)/ft_tt_elf.c \
|
|
$(SRC_DIR)/ft_tt_kernel_blob.c \
|
|
$(SRC_DIR)/ft_tt_udrv_claim.c \
|
|
$(SRC_DIR)/ft_tt_udrv_reset.c \
|
|
$(SRC_DIR)/ft_tt_udrv_pci_sysfs.c \
|
|
$(SRC_DIR)/ft_tt_udrv_tlb_encode.c \
|
|
$(SRC_DIR)/ft_tt_udrv_noc.c \
|
|
$(SRC_DIR)/ft_tt_udrv_noc_bank.c \
|
|
$(SRC_DIR)/ft_tt_udrv_arc_msg.c \
|
|
$(SRC_DIR)/ft_tt_udrv_arc_probe.c \
|
|
$(SRC_DIR)/ft_tt_runtime.c \
|
|
$(SRC_DIR)/ft_tt_runtime_udrv.c \
|
|
$(SRC_DIR)/ft_tt_tensix_claim.c \
|
|
$(SRC_DIR)/ft_tt_launch.c \
|
|
$(SRC_DIR)/ft_tt_queue.c \
|
|
$(SRC_DIR)/ft_tt_core_status.c \
|
|
$(SRC_DIR)/ft_tt_core_readiness.c
|
|
|
|
SCAFFOLD_SOURCES := $(TEST_DIR)/ft_tt_hardware_session.c \
|
|
$(TEST_DIR)/ft_tt_phase2_l1_layout.c \
|
|
$(TEST_DIR)/ft_tt_program_directory.c
|
|
|
|
# ft_tt_core_status.c LEFT for tt/ on 2026-08-24, and it is in BACKEND_SOURCES above now.
|
|
# Pantheon's arming path became a second consumer of its verdict: it must not boot firmware
|
|
# onto a core that is still running, and duplicating this classification into the serve would
|
|
# have produced a second, subtly different reading of the same registers. What stayed here is
|
|
# the PRESENTATION — the three printers and the operational advice they carry, which is
|
|
# addressed to a person at a terminal.
|
|
TOOL_SOURCES := ft_tt_core_status_print.c \
|
|
ft_tt_core_probe.c \
|
|
ft_tt_core_recover.c \
|
|
ft_tt_device_reset.c \
|
|
ft_tt_card_lock.c \
|
|
ft_tt_core.c
|
|
|
|
SOURCES := $(BACKEND_SOURCES) $(SCAFFOLD_SOURCES) $(TOOL_SOURCES)
|
|
OBJECTS := $(addprefix $(BUILD)/,$(notdir $(SOURCES:.c=.o)))
|
|
TARGET := $(BUILD)/ft_tt_core
|
|
|
|
# ft_tt_power_request links NOTHING from the backend, deliberately. It opens the chardev,
|
|
# issues one SET_POWER_STATE, and reads sysfs; it never aims a TLB and never issues a NoC
|
|
# transaction, so linking the backend into it would only give it the ability to do damage it
|
|
# has no reason to be able to do.
|
|
POWER_TARGET := $(BUILD)/ft_tt_power_request
|
|
|
|
.PHONY: all clean rebuild explode-programs regen-kernels regen-kernels-check regen-programs \
|
|
regen-programs-check verify-programs check-kernel-compile-time-args \
|
|
check-program-directory \
|
|
check-program-provenance
|
|
|
|
all: $(TARGET) $(POWER_TARGET)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The one guard ft_tensix_compile_time_arg() does not have built in: it indexes a
|
|
# `static const` array with NO bounds check, and SFPI GCC 15.1.0 folds an out-of-range
|
|
# read to a literal 0 with no diagnostic at any -Warray-bounds level (measured on a second card host,
|
|
# 2026-08-18 -- see the script's own header). The argument count written beside each
|
|
# kernel's documented build line is the only thing that catches a short build, so this
|
|
# makes that count a real, enforced check instead of a comment someone has to remember to
|
|
# read.
|
|
#
|
|
# make check-kernel-compile-time-args
|
|
#
|
|
# Needs nothing -- no card, no toolchain, pure comment/source audit. Runs on the Mac.
|
|
check-kernel-compile-time-args:
|
|
python3 check_tensix_kernel_compile_time_arg_counts.py
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# IS THE SHIPPING BLOB CURRENT WITH THE KERNEL SOURCES BESIDE IT?
|
|
#
|
|
# The mtime check that used to answer this is defeated by an `explode`, which rewrites
|
|
# every file and every timestamp: all 21 directories reported `current` while `programs/gdn`
|
|
# was five commits behind and the four `rtriad_*` readers had no cooperative multicast at
|
|
# all. This asks the question by CONTENT — each embedded image carries a hash of every
|
|
# in-tree file its compile opened — so nothing about timestamps can change the answer.
|
|
#
|
|
# make check-program-provenance
|
|
#
|
|
# Needs nothing: no card, no toolchain, no program directories. Runs on the Mac, and it is
|
|
# the check to run BEFORE trusting any on-card result, not after.
|
|
#
|
|
# **IT DOES NOT SEE tt/programs/.** It holds the EMBEDDED BLOB against the sources, which is
|
|
# the right question for a serve — an unset `PN_TT_PROGRAM_DIR` loads the blob and nothing
|
|
# else. It is the wrong question for an ARM, because an arm that sets `PN_TT_PROGRAM_DIR`
|
|
# runs the exploded directory instead, and on 2026-08-19 that directory was 22 images behind
|
|
# the blob while this target printed `22 current, 0 stale`. See check-program-directory.
|
|
PROGRAMS_HEADER ?= ../ft_tt_programs.h
|
|
|
|
check-program-provenance:
|
|
python3 check_tensix_program_provenance.py --header $(PROGRAMS_HEADER)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# IS THE EXPLODED DIRECTORY THE SAME PROGRAM THE BLOB CARRIES?
|
|
#
|
|
# A DIFFERENT QUESTION FROM check-program-provenance, and it was answered NO while that one
|
|
# was green. Measured 2026-08-19: `tt/programs/` held 22 images differing from the embedded
|
|
# blob across SIX programs — `gdn` (all five images), `probe`, and all four `rtriad_*`. Both
|
|
# consequences were silent and large:
|
|
#
|
|
# - with the GDN family armed, `PN_TT_PROGRAM_DIR=tt/programs` REFUSED every descriptor
|
|
# ("gdn descriptor 2 did not complete: error", one byte of output), twice, reproducibly,
|
|
# on a card that ran the blob clean immediately before and after;
|
|
# - the on-disk `rtriad` reader is the PRE-COLUMN-BLOCK matmul, so every arm ever taken
|
|
# through a program directory paid 0.181 s/token more than the blob does — the size of
|
|
# the entire column-block win, charged to whatever that arm happened to be testing.
|
|
#
|
|
# `verify-programs` could always answer this. It built the tool and then printed the command
|
|
# for you to run yourself, so nobody ran it. This runs it, and fails on a difference.
|
|
#
|
|
# make check-program-directory
|
|
#
|
|
# Needs a toolchain but no card.
|
|
check-program-directory: verify-programs
|
|
$(BUILD)/verify_tensix_program_blob $(PROGRAMS)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The ONE entry point that rebuilds every embedded Tensix kernel and regenerates
|
|
# ../ft_tt_kernels.h. There was no such entry point, and that is how the committed header
|
|
# went stale: it shipped C++ ELFs from before the Foundry move for weeks. The script also
|
|
# refuses to finish quietly when the firmware in FT_TT_FIRMWARE_DIR is not what these
|
|
# kernels were linked against -- a kernel's .text sits at its own firmware's
|
|
# __fw_export_text_end, so a regen without the matching firmware is a wedged core.
|
|
#
|
|
# make regen-kernels rebuild + write the header; FAIL if firmware moved
|
|
# make regen-kernels INSTALL_FIRMWARE=1 ... and back up + install the new firmware
|
|
# make regen-kernels-check rebuild + diff only; writes nothing
|
|
#
|
|
# Needs the SFPI toolchain, i.e. the card host. OUT defaults to /tmp/ftk_regen.
|
|
OUT ?= /tmp/ftk_regen
|
|
INSTALL_FIRMWARE ?= 0
|
|
|
|
regen-kernels:
|
|
INSTALL_FIRMWARE=$(INSTALL_FIRMWARE) bash regen_tensix_kernel_header.sh $(OUT)
|
|
|
|
regen-kernels-check:
|
|
CHECK=1 bash regen_tensix_kernel_header.sh $(OUT)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The PROGRAM blob: whole build_tensix_program.sh directories -- five kernel ELFs plus the
|
|
# five fw_*.elf they were linked against -- embedded into ../ft_tt_programs.h, so a binary
|
|
# carries its Tensix programs instead of reading them out of somebody's home directory.
|
|
# That hardcoded path (PN_TT_PROGRAM_ROOT_DEFAULT) is why the serve runs on one machine, and
|
|
# it is why rtriad_bfp8 could go missing for a day and cost 1.085x silently.
|
|
#
|
|
# make regen-programs regenerate ../ft_tt_programs.h from ../programs/*
|
|
# make regen-programs-check diff only; writes nothing
|
|
# make verify-programs build the byte-for-byte comparison tool
|
|
#
|
|
# Needs the program directories, i.e. the card host. The regen has NEVER been run for real; the
|
|
# mechanism is gated against synthetic fixtures by tests/Makefile.programblob.
|
|
PROGRAMS ?= ../programs
|
|
|
|
regen-programs:
|
|
bash regen_tensix_program_header.sh
|
|
|
|
regen-programs-check:
|
|
CHECK=1 bash regen_tensix_program_header.sh
|
|
|
|
# Whole-program link, into its own binary and with NO shared objects: every other object in
|
|
# $(BUILD) is compiled with the NoC fence OPEN, and mixing one of those into a tool that is
|
|
# supposed to be unable to reach the card would quietly give it that ability.
|
|
VERIFY_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
|
|
$(SRC_DIR)/ft_tt_arch.c \
|
|
$(SRC_DIR)/ft_tt_arch_blackhole.c \
|
|
$(SRC_DIR)/ft_tt_device.c \
|
|
$(SRC_DIR)/ft_tt_elf.c \
|
|
$(SRC_DIR)/ft_tt_program_blob.c \
|
|
verify_tensix_program_blob.c
|
|
|
|
verify-programs: | $(BUILD)
|
|
rm -f $(BUILD)/verify_tensix_program_blob
|
|
$(CC) $(CFLAGS) $(VERIFY_SOURCES) -o $(BUILD)/verify_tensix_program_blob
|
|
@echo "built $(BUILD)/verify_tensix_program_blob — run it as:"
|
|
@echo " $(BUILD)/verify_tensix_program_blob $(PROGRAMS)"
|
|
|
|
# The blob back out as DIRECTORIES, so a machine with no tt/programs/ can still regenerate
|
|
# it after one kernel changes. Same whole-program link and the same reason: this tool must
|
|
# not be able to reach the card.
|
|
EXPLODE_SOURCES := $(SRC_DIR)/ft_tt_kmd.c \
|
|
$(SRC_DIR)/ft_tt_arch.c \
|
|
$(SRC_DIR)/ft_tt_arch_blackhole.c \
|
|
$(SRC_DIR)/ft_tt_device.c \
|
|
$(SRC_DIR)/ft_tt_elf.c \
|
|
$(SRC_DIR)/ft_tt_program_blob.c \
|
|
explode_tensix_program_blob.c
|
|
|
|
explode-programs: | $(BUILD)
|
|
rm -f $(BUILD)/explode_tensix_program_blob
|
|
$(CC) $(CFLAGS) $(EXPLODE_SOURCES) -o $(BUILD)/explode_tensix_program_blob
|
|
@echo "built $(BUILD)/explode_tensix_program_blob — run it as:"
|
|
@echo " $(BUILD)/explode_tensix_program_blob <outdir> --verify --recipes <file>"
|
|
|
|
$(BUILD):
|
|
mkdir -p $(BUILD)
|
|
|
|
$(BUILD)/%.o: $(SRC_DIR)/%.c | $(BUILD)
|
|
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
|
|
|
$(BUILD)/%.o: $(TEST_DIR)/%.c | $(BUILD)
|
|
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
|
|
|
$(BUILD)/%.o: %.c | $(BUILD)
|
|
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
|
|
|
$(TARGET): $(OBJECTS)
|
|
$(CC) $(CFLAGS) $(OBJECTS) -o $@ -lm
|
|
|
|
$(POWER_TARGET): $(BUILD)/ft_tt_power_request.o
|
|
$(CC) $(CFLAGS) $< -o $@
|
|
|
|
# `rebuild` deletes the tree first. GNU make 3.81 compares mtimes at one-second
|
|
# granularity, so a source edited in the same second as its object is silently not
|
|
# rebuilt and you test the PREVIOUS binary (coding-best-practices §9). rsync -a preserving
|
|
# the Mac's mtimes has the same effect across machines.
|
|
rebuild: clean all
|
|
|
|
clean:
|
|
rm -rf $(BUILD)
|
|
|
|
-include $(wildcard $(BUILD)/*.d)
|