Foundry/tt/tools/emit_tensix_linker_script.py

108 lines
4.6 KiB
Python
Executable File

#!/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 <gcc> --memmap <ft_tt_memmap.h> \\
--processor brisc|ncrisc|trisc0|trisc1|trisc2 --type firmware|kernel \\
--template <ft_tensix_*.ld> -o <out.ld>
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()