Foundry/tt/tests/Makefile.programblob

143 lines
6.4 KiB
Makefile

# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
# Foundry Blackhole backend — the EMBEDDED PROGRAM BLOB's off-device suite.
#
# make -f Makefile.programblob check # fixtures, build, run both gates
# make -f Makefile.programblob red # clean first, then the above
# make -f Makefile.programblob gates # the whole mutation battery (slow, thorough)
#
# NO DEVICE. Nothing here calls ft_tt_runtime_open(), ft_tt_runtime_boot_core() or anything
# else that reaches a card, and the build does NOT define FT_TT_PHASE1B_NOC_ENABLED, so the
# NoC fence is closed: every device call in the linked backend returns FT_TT_EPERM before
# issuing a transaction. ft_tt_runtime.c is linked because the OVERRIDE arm calls its two
# file loaders, and linking it drags the backend in with it — the same arrangement
# test_launch already uses to run off-card.
#
# WHOLE-PROGRAM LINKS, NO OBJECT FILES. Apple ships GNU make 3.81, whose one-second mtime
# granularity silently skips a rebuild of a source edited in the same second as its object,
# so a mutation loop over incremental objects can be testing the PREVIOUS binary
# (coding-best-practices.md §9). There are no .o files to go stale here, and `red` deletes
# the binaries as well.
#
# WHY THERE ARE TWO BINARIES. test_program_blob is compiled against a GENERATED FIXTURE
# blob and checks the mechanism. test_program_blob_empty checks that the EMPTY state refuses
# loudly instead
# of passing over zero programs. Each asserts WHICH header it got, so neither can quietly
# become a re-run of the other.
CC ?= cc
SRC_DIR := ..
TOOLS := ../tools
BUILD := build-programblob
# Where make_program_blob_fixtures.py writes, and where the generated fixture header lands.
FIXTURES ?= /tmp/ft_tt_program_blob_fixtures
FIXTURE_HEADER := $(FIXTURES)/ft_tt_programs_fixture.h
CFLAGS := -std=c99 -O1 -g \
-Wall -Wextra -Werror -pedantic \
-Wshadow -Wconversion -Wstrict-prototypes -Wformat=2 \
-fstack-protector-strong \
-I$(SRC_DIR) -I.
# ft_tt_runtime.c is the reason for most of this list: it is the file the override arm
# calls, and it references the launch, NoC, TLB and coordinate layers. Since 2026-08-17 it
# also dispatches to the USERSPACE DRIVER, so ft_tt_runtime_udrv.c and the ft_tt_udrv_*
# modules under it come with it. None of them is reached here — no card is opened — but they
# must LINK, and that is itself a gate: a seam that only builds where the card is, is a seam
# nobody finds broken until the card has moved.
BACKEND := $(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_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_tensix_claim.c \
$(SRC_DIR)/ft_tt_launch.c \
$(SRC_DIR)/ft_tt_runtime.c \
$(SRC_DIR)/ft_tt_runtime_udrv.c \
$(SRC_DIR)/ft_tt_udrv_reset.c \
$(SRC_DIR)/ft_tt_program_blob.c \
$(SRC_DIR)/ft_tt_program_resolve.c \
ft_tt_test_report.c
FIXTURE_TARGET := $(BUILD)/test_program_blob
EMPTY_TARGET := $(BUILD)/test_program_blob_empty
.PHONY: all check red gates fixtures clean
all: $(FIXTURE_TARGET) $(EMPTY_TARGET)
$(BUILD):
mkdir -p $(BUILD)
# The fixtures and the fixture blob are ONE step: a generated header without the directories
# it was generated from cannot be byte-compared against anything.
fixtures: $(FIXTURE_HEADER)
$(FIXTURE_HEADER): make_program_blob_fixtures.py $(TOOLS)/emit_tensix_program_header.py
rm -rf $(FIXTURES)
python3 make_program_blob_fixtures.py $(FIXTURES)
python3 $(TOOLS)/emit_tensix_program_header.py -o $@ \
--kernels $(FIXTURES)/kernels --toolchain fixture-1.0 \
--recipe 'make_program_blob_fixtures.py' \
--program $(FIXTURES)/programs/prog_alpha \
--program $(FIXTURES)/programs/prog_beta \
--program $(FIXTURES)/programs/prog_gamma
$(FIXTURE_TARGET): $(FIXTURE_HEADER) test_program_blob.c $(BACKEND) | $(BUILD)
rm -f $@
$(CC) $(CFLAGS) -I$(FIXTURES) \
-DFT_TT_PROGRAMS_HEADER='"ft_tt_programs_fixture.h"' \
test_program_blob.c $(BACKEND) -o $@
# The empty arm builds against its OWN empty header, not the shipping one. It used to compile
# against $(SRC_DIR)/ft_tt_programs.h and pass only because that file was still the placeholder
# -- so the moment a real blob was generated (2026-08-17) its six checks failed, having asserted
# a property of the artifact rather than of the code. An empty-state gate that can only pass
# before the work is done is backwards.
$(BUILD)/ft_tt_programs_empty.h: | $(BUILD)
printf '%s\n' \
'/* GENERATED for test_program_blob_empty -- the empty-blob arm needs an empty blob' \
' * regardless of what the shipping header holds. Not the placeholder; a fixture. */' \
'#ifndef FT_TT_PROGRAMS_H' \
'#define FT_TT_PROGRAMS_H' \
'static const ft_tt_program_blob_entry *const ft_tt_program_blob_table_storage[1] = { 0 };' \
'#define ft_tt_program_blob_table ft_tt_program_blob_table_storage' \
'#define FT_TT_PROGRAM_BLOB_TABLE_COUNT 0u' \
'#endif' > $@
$(EMPTY_TARGET): test_program_blob_empty.c $(BACKEND) $(BUILD)/ft_tt_programs_empty.h | $(BUILD)
rm -f $@
$(CC) $(CFLAGS) -I$(BUILD) -DFT_TT_PROGRAMS_HEADER='"ft_tt_programs_empty.h"' \
test_program_blob_empty.c $(BACKEND) -o $@
check: $(FIXTURE_TARGET) $(EMPTY_TARGET)
@rc=0; \
echo "=== $(FIXTURE_TARGET) ==="; ./$(FIXTURE_TARGET) $(FIXTURES) || rc=1; \
echo "=== $(EMPTY_TARGET) ==="; ./$(EMPTY_TARGET) $(FIXTURES) || rc=1; \
exit $$rc
# The battery. Every gate above is re-run against a header that has been damaged in one
# specific way, and each damaged build MUST fail for the stated reason — a red for some
# other reason proves nothing about the check it was aimed at.
gates: fixtures
bash run_program_blob_gates.sh
red: clean check
clean:
rm -rf $(BUILD) $(FIXTURES)