mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Implemented a new TCL script for Tiny R2 feasibility checks, reporting clock properties, path delays, and reset loads. - Created a SystemVerilog module for the Tiny R2 reset tree, managing multiple reset signals for the system and its leaves. - Developed a Python script for capturing and analyzing feasibility metrics, ensuring compliance with reset-load and clock frequency requirements. - Added testbenches for the Tiny R2 reset tree and FIFO, validating reset behavior and timing contracts. - Introduced unit tests for feasibility analysis, ensuring correct handling of clock rates and reset load conditions.
164 lines
8.6 KiB
Python
164 lines
8.6 KiB
Python
#!/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 <stdint.h>",
|
|
"#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]
|
|
reset_assignments = []
|
|
for index, target in enumerate(document["apb_targets"]):
|
|
name = target["name"]
|
|
ports.append(f"apb4_if.master {name}")
|
|
declarations.append(f"logic s_{name}_rst_n;")
|
|
declarations.append(f"apb4_if u_{name}_apb4_if (.pclk(clk_i), .presetn(s_{name}_rst_n));")
|
|
reset_assignments.append(f"assign s_{name}_rst_n = s_leaf_rst_n[{index + 1}];")
|
|
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 + reset_assignments) + "\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"(?<![^\s+])/{prefix}(?=/|\s|$)", str(directory), content)
|
|
atomic_write(output / template.name, content)
|
|
include = [generated / "rtl", generated / "memory_map/rtl", generated / "pin_map/rtl",
|
|
generated / "archinfo"]
|
|
atomic_write(output / "def.fl", " ".join([*defines, *(f"+incdir+{p}" for p in include)]) + "\n")
|
|
atomic_write(output / "pdk_local.fl", "")
|
|
|
|
|
|
def main() -> 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())
|