#!/usr/bin/env python3 """Generate Tiny integration bindings and absolute source lists from owned maps.""" from __future__ import annotations import argparse import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from scripts.rtl.generate_memory_map import generate as generate_memory_map # noqa: E402 from scripts.rtl.generate_pin_map import generate as generate_pin_map # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402 TINY = ROOT / "rtl/tiny" def read_topology(path: Path, memory_map: Path) -> dict: document = json.loads(path.read_text()) regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]} if document.get("schema_version") != 1 or document.get("dma_channels") != 4: raise ValueError("Tiny requires topology schema 1 and four DMA channels") if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") names: set[str] = set() bits: set[int] = set() for irq in document["interrupts"]: if irq["name"] in names or irq["core_bit"] in bits: raise ValueError("duplicate Tiny interrupt") if not 0 <= irq["core_bit"] < 32: raise ValueError("Tiny IRQ outside vector") names.add(irq["name"]) bits.add(irq["core_bit"]) assigned: set[str] = set() for target in document["apb_targets"]: for region in target["regions"]: if region not in regions or region in assigned: raise ValueError(f"invalid or duplicate APB region {region}") assigned.add(region) if assigned != {r for r in regions if r.startswith("APB4_")}: raise ValueError("Tiny topology must route every APB region") if len(document["apb_targets"]) != 16: raise ValueError("Tiny's fixed APB bridge requires sixteen targets") target_names = [target["name"] for target in document["apb_targets"]] if len(set(target_names)) != 16 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None for name in target_names): raise ValueError("Tiny APB target names must be unique identifiers") if sorted(item["pin"] for item in document["gpio_alt_functions"]) != list(range(32)): raise ValueError("Tiny GPIO map must contain all 32 pins exactly once") return document def generate_bindings(document: dict, output: Path) -> None: comment = "// Generated by scripts/generate_tiny.py; do not edit.\n" irq = [comment, "always_comb begin", " s_irq = '0;"] header = ["#ifndef RETROSOC_GENERATED_IRQ_METADATA_H", "#define RETROSOC_GENERATED_IRQ_METADATA_H", "#include ", "#define RS_SOC_IRQ_VECTOR_WIDTH UINT32_C(32)", "#define RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)", f"#define RS_SOC_ALLOCATED_IRQ_COUNT UINT32_C({len(document['interrupts']) - 2})"] for item in document["interrupts"]: bit, name = item["core_bit"], item["name"].upper() irq.append(f" s_irq[{bit}] = {item['signal']};") header.append(f"#define RS_SOC_IRQ_{name} UINT32_C({bit})") if bit >= 2: header.append(f"#define RS_SOC_EXT_IRQ_{name} UINT32_C({bit - 2})") irq.append("end\n") header.append("#endif\n") atomic_write(output / "rtl/tiny_irq.svh", "\n".join(irq)) atomic_write(output / "include/retrosoc/generated/irq_metadata.h", "\n".join(header)) atomic_write(output / "include/retrosoc/generated/user_extensions.h", "#ifndef RETROSOC_GENERATED_USER_EXTENSIONS_H\n" "#define RETROSOC_GENERATED_USER_EXTENSIONS_H\n" "#define RS_SOC_USER_CORE_COUNT 0U\n#define RS_SOC_USER_IP_COUNT 0U\n" "#define RS_SOC_HAS_USER_CORES 0U\n#define RS_SOC_HAS_USER_IP_MUX 0U\n#endif\n") atomic_write(output / "include/retrosoc/generated/soc_features.h", "#ifndef RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RS_SOC_TINY 1\n#define RS_SOC_HAS_PSRAM 0U\n" "#define RS_SOC_DMA_CHANNELS 4U\n#define RS_SOC_ID 0x54494E59U\n#endif\n") gpio = [comment] for function in document["gpio_alt_functions"]: pin = function["pin"] for mode in ("alt0", "alt1"): value = function[mode] gpio.extend(f"assign {signal} = u_gpio_if.di_i[{pin}];" for signal in value["inputs"]) gpio.extend((f"assign u_gpio_if.{mode}_do_i[{pin}] = {value['do']};", f"assign u_gpio_if.{mode}_oe_i[{pin}] = {value['oe']};")) atomic_write(output / "rtl/tiny_gpio.svh", "\n".join(gpio) + "\n") ports, declarations, routes, decode = [], [comment], [comment], [comment] for index, target in enumerate(document["apb_targets"]): name = target["name"] ports.append(f"apb4_if.master {name}") declarations.append(f"apb4_if u_{name}_apb4_if (.pclk(clk_i), .presetn(s_rst_n));") routes.append(f".{name}(u_{name}_apb4_if)") decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(s_addr_q)' for r in target['regions'])}) " f"s_select = {len(document['apb_targets'])}'b{1 << index:016b};") atomic_write(output / "rtl/tiny_apb_ports.svh", ",\n".join(ports) + "\n") atomic_write(output / "rtl/tiny_apb_interfaces.svh", "\n".join(declarations) + "\n") atomic_write(output / "rtl/tiny_apb_connections.svh", ",\n".join(routes[1:]) + "\n") atomic_write(output / "rtl/tiny_apb_decode.svh", "\n".join(decode) + "\n") wiring = [comment] for index, target in enumerate(document["apb_targets"]): name = target["name"] for field, value in {"paddr": "s_addr_q", "pwrite": "s_write_q", "pwdata": "s_wdata_q", "pstrb": "s_wstrb_q", "pprot": "3'd0", "psel": f"s_select[{index}] && s_access", "penable": "s_enable"}.items(): wiring.append(f"assign {name}.{field} = {value};") wiring.extend((f"assign s_ready[{index}] = {name}.pready;", f"assign s_error[{index}] = {name}.pslverr;", f"assign s_rdata[{index}] = {name}.prdata;")) atomic_write(output / "rtl/tiny_apb_wiring.svh", "\n".join(wiring) + "\n") def generate_filelists(output: Path, generated: Path, defines: list[str]) -> None: prefixes = {"top": TINY / "top", "dv": TINY / "dv", "ip": ROOT / "rtl/ip", "tech": ROOT / "rtl/tech", "clusterip": ROOT / "rtl/managed/clusterip", "hazard3": ROOT / "rtl/managed/hazard3/hdl", "physical": ROOT / "physical"} templates = [*sorted((TINY / "filelist").glob("*.fl")), ROOT / "rtl/filelist/pdk_ihp130.fl"] for template in templates: content = template.read_text() for prefix, directory in prefixes.items(): content = re.sub(rf"(? int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path) parser.add_argument("--filelists", type=Path) parser.add_argument("--define", action="append", default=[]) parser.add_argument("--check", action="store_true") args = parser.parse_args() document = read_topology(TINY / "integration/soc_topology.json", TINY / "address_map/memory_map.json") if args.check: return 0 if args.output is None or args.filelists is None: parser.error("--output and --filelists are required unless --check is used") if any(not value.startswith("+define+") for value in args.define): parser.error("--define requires a +define+ token") output = args.output.resolve() generate_memory_map(TINY / "address_map/memory_map.json", output / "memory_map", "YES", 128) generate_pin_map(TINY / "pin_map/pin_map.json", output / "pin_map") generate_bindings(document, output) generate_filelists(args.filelists.resolve(), output, args.define) return 0 if __name__ == "__main__": raise SystemExit(main())