108 lines
4.6 KiB
Python
Executable File
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()
|