#!/usr/bin/env python3 # SPDX-License-Identifier: AGPL-3.0-or-later """ @file emit_tensix_linker_script.py @brief Write the linker script for one Tensix processor and image type: Foundry's template (fw/ft_tensix_firmware.ld or fw/ft_tensix_kernel.ld) preceded by that processor's L1 and local-memory constants, evaluated from ft_tt_memmap.h. WHY GENERATED. ft_tt_memmap.h is the one source of truth for the L1 map (the host side reads it too). Its macros are C (`1536u * 1024u`, `(x + 31u) & ~31u`), which ld cannot parse, so the macros are taken from the compiler (`cc -E -dM`) and evaluated here into plain assignments. Nothing is retyped. Usage: emit_tensix_linker_script.py --cc --memmap \\ --processor brisc|ncrisc|trisc0|trisc1|trisc2 --type firmware|kernel \\ --template -o Env: FT_LINKER_SCRIPT_RED=wrong-gp red builds only: firmware gp 4 bytes off @status Added 2026-10-08, clean-room plan Phase 3. @issues None @todo None """ import argparse import os import re import subprocess import sys PROCESSOR_MACRO = { "brisc": "BRISC", "ncrisc": "NCRISC", "trisc0": "TRISC0", "trisc1": "TRISC1", "trisc2": "TRISC2", } LOCAL_SIZE_MACRO = { "brisc": "FT_TT_MEM_BRISC_LOCAL_SIZE", "ncrisc": "FT_TT_MEM_NCRISC_LOCAL_SIZE", "trisc0": "FT_TT_MEM_TRISC_LOCAL_SIZE", "trisc1": "FT_TT_MEM_TRISC_LOCAL_SIZE", "trisc2": "FT_TT_MEM_TRISC_LOCAL_SIZE", } NUMBER = re.compile(r"\b(0[xX][0-9a-fA-F]+|\d+)[uUlL]*\b") IDENTIFIER = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b") def read_macros(cc, header): """name -> replacement text, for every object-like macro the header defines.""" output = subprocess.run([cc, "-E", "-dM", "-x", "c", header], check=True, capture_output=True, text=True).stdout macros = {} for line in output.splitlines(): match = re.match(r"#define ([A-Za-z_][A-Za-z0-9_]*) (.*)$", line) if match: macros[match.group(1)] = match.group(2) return macros def evaluate(name, macros, depth=0): """Integer value of macro `name`, expanding the macros it refers to.""" if depth > 32 or name not in macros: sys.exit("emit_tensix_linker_script: cannot evaluate %s" % name) text = macros[name] if "/" in text or "?" in text: sys.exit("emit_tensix_linker_script: %s = %r uses an operator this evaluator refuses" % (name, text)) text = NUMBER.sub(lambda match: match.group(1), text) text = IDENTIFIER.sub(lambda match: str(evaluate(match.group(0), macros, depth + 1)), text) if not re.fullmatch(r"[0-9xXa-fA-F\s()+\-*&|~<>]*", text): sys.exit("emit_tensix_linker_script: %s expands to %r" % (name, text)) return eval(text, {"__builtins__": {}}) & 0xFFFFFFFF # noqa: S307 -- arithmetic only def main(): parser = argparse.ArgumentParser() parser.add_argument("--cc", required=True) parser.add_argument("--memmap", required=True) parser.add_argument("--processor", required=True, choices=sorted(PROCESSOR_MACRO)) parser.add_argument("--type", required=True, choices=("firmware", "kernel")) parser.add_argument("--template", required=True) parser.add_argument("-o", dest="output", required=True) args = parser.parse_args() macros = read_macros(args.cc, args.memmap) tag = PROCESSOR_MACRO[args.processor] constants = [ ("FT_FIRMWARE_BASE", "FT_TT_MEM_%s_FIRMWARE_BASE" % tag), ("FT_FIRMWARE_SIZE", "FT_TT_MEM_%s_FIRMWARE_SIZE" % tag), ("FT_KERNEL_SIZE", "FT_TT_MEM_%s_KERNEL_SIZE" % tag), ("FT_STACK_MIN_SIZE", "FT_TT_MEM_%s_STACK_MIN_SIZE" % tag), ("FT_LOCAL_BASE", "FT_TT_MEM_LOCAL_BASE"), ("FT_LOCAL_SIZE", LOCAL_SIZE_MACRO[args.processor]), ] lines = ["/* GENERATED by tools/emit_tensix_linker_script.py -- do not edit.", " * %s %s image; constants evaluated from ft_tt_memmap.h. */" % (args.processor, args.type)] for (symbol, macro) in constants: lines.append("%s = 0x%X; /* %s */" % (symbol, evaluate(macro, macros), macro)) with open(args.template) as handle: template = handle.read() red = "" if os.environ.get("FT_LINKER_SCRIPT_RED") == "wrong-gp" and args.type == "firmware": # RED ONLY (the Phase 3 gate's non-vacuous red): a gp 4 bytes off, a wrong VALUE that # changes every gp-relative access; the load comparison must report it. Never shipped. red = "\n__global_pointer$ = FT_LOCAL_BASE + 0x7F4; /* RED: FT_LINKER_SCRIPT_RED */\n" with open(args.output, "w") as handle: handle.write("\n".join(lines) + "\n\n" + template + red) if __name__ == "__main__": main()