mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Adjusted slot assignments in to reflect new peripheral mappings. - Updated to validate new OpenC906 configurations, including generator and profile checks. - Modified to reference the new Std-series VexiiRiscv generator. - Changed cache maintenance instructions in to align with C906 specifications. - Revised assertions in to account for internal unit removals. - Updated to reflect changes in peripheral interface counts and policies. - Added new test file to ensure preservation of the frozen HP configuration in the Std-series generator.
241 lines
14 KiB
Python
241 lines
14 KiB
Python
"""Source-bound data for representative field, instruction, memory and circuit diagrams."""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import importlib.util
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from publications.implementation_reference import without_comments
|
|
from publications.register_reference import number
|
|
from publications.circuit_reference import validate_instance_connections
|
|
from publications.instruction_reference import instruction_families
|
|
from publications.format_reference import collect_layouts
|
|
from publications.storage_reference import collect_storage
|
|
from publications.soc_diagram_reference import collect_soc_diagram
|
|
|
|
|
|
def dependencies(spec: dict) -> set[str]:
|
|
paths = set(spec.get("sources", []))
|
|
if spec.get("soc_architecture_catalog"):
|
|
paths.add(spec["soc_architecture_catalog"])
|
|
for binding in spec.get("bindings", []):
|
|
paths.add(binding["file"])
|
|
for circuit in spec.get("circuits", {}).values():
|
|
for node in circuit["nodes"]:
|
|
paths.update(node.get("sources", []))
|
|
for edge in circuit["edges"]:
|
|
paths.update(binding["file"] for binding in edge["bindings"])
|
|
return paths
|
|
|
|
|
|
def validate_bindings(root: Path, bindings: list[dict]) -> None:
|
|
for binding in bindings:
|
|
source = (root / binding["file"]).read_text(encoding="utf-8")
|
|
if re.sub(r"\s+", "", without_comments(binding["text"])) not in re.sub(r"\s+", "", without_comments(source)):
|
|
raise ValueError(f"diagram source binding changed: {binding['file']}")
|
|
|
|
|
|
def validate_fields(fields: list[dict], total_bits: int) -> None:
|
|
occupied, names = set(), set()
|
|
for field in fields:
|
|
name, lsb, bits = field["name"], field["lsb"], field["bits"]
|
|
if name in names or type(lsb) is not int or type(bits) is not int or lsb < 0 or bits < 1:
|
|
raise ValueError("invalid or duplicate diagram field")
|
|
positions = set(range(lsb, lsb + bits))
|
|
if occupied & positions or max(positions) >= total_bits:
|
|
raise ValueError("diagram fields overlap or exceed the layout")
|
|
names.add(name)
|
|
occupied.update(positions)
|
|
if occupied != set(range(total_bits)):
|
|
raise ValueError("diagram layout has undocumented gaps")
|
|
|
|
|
|
def dma_fields(root: Path) -> list[dict]:
|
|
source = (root / "crt/include/retrosoc/hal/dma.h").read_text(encoding="utf-8")
|
|
matches = re.findall(r"typedef\s+struct\s*\{([^}]+)\}\s*rs_dma_tcd_t\s*;", source)
|
|
if len(matches) != 1:
|
|
raise ValueError("DMA TCD structure missing or ambiguous")
|
|
members = re.findall(r"(u?int)(16|32)_t\s+(\w+)\s*;", matches[0])
|
|
if re.sub(r"(?:u?int)(?:16|32)_t\s+\w+\s*;", "", matches[0]).strip():
|
|
raise ValueError("unreviewed DMA TCD member type")
|
|
fields, offset = [], 0
|
|
for signed, width, name in members:
|
|
bits = int(width)
|
|
role = "reserved" if name.startswith("reserved") else "writeback" if name in {
|
|
"crc_result", "status", "bytes_done", "error_status"} else "configuration"
|
|
fields.append({"name": name, "lsb": offset * 8, "bits": bits, "offset": offset,
|
|
"role": role, "signed": signed == "int"})
|
|
offset += bits // 8
|
|
validate_fields(fields, 512)
|
|
return fields
|
|
|
|
|
|
def apu_formats(root: Path) -> dict:
|
|
path = root / "scripts/apu_isa.py"
|
|
source = path.read_text(encoding="utf-8")
|
|
declarations = re.findall(r"\(self\.(\w+),\s*(\d+),\s*(\d+)\)", source)
|
|
aliases = {"instruction_class": "class", "opcode": "opcode", "predicate": "predicate",
|
|
"dst": "dst", "src0": "src0", "src1": "src1", "aux": "aux", "immediate": "immediate"}
|
|
if len(declarations) != 8 or {row[0] for row in declarations} != aliases.keys():
|
|
raise ValueError("APU encoder field inventory changed")
|
|
rtl = (root / "rtl/ip/multimedia/apu_microcode_pkg.sv").read_text(encoding="utf-8")
|
|
fields = []
|
|
for name, width, shift in declarations:
|
|
bits, lsb = int(width), int(shift)
|
|
slices = set(re.findall(r"s_" + aliases[name] + r"\s*=\s*instruction_i\[(\d+):(\d+)\]", rtl))
|
|
if slices != {(str(lsb + bits - 1), str(lsb))}:
|
|
raise ValueError("APU encoder/RTL diagram slice mismatch")
|
|
fields.append({"name": name, "label": aliases[name].upper(), "bits": bits, "lsb": lsb})
|
|
validate_fields(fields, 64)
|
|
spec = importlib.util.spec_from_file_location("publication_apu_diagram_isa", path)
|
|
if spec is None or spec.loader is None:
|
|
raise ValueError("APU encoder could not be loaded")
|
|
isa = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = isa
|
|
spec.loader.exec_module(isa)
|
|
defines = (root / "rtl/ip/multimedia/apu_define.svh").read_text(encoding="utf-8")
|
|
for suffix, value in (("CLASS_CONTROL", int(isa.InstructionClass.CONTROL)),
|
|
("CLASS_SCALAR", int(isa.InstructionClass.SCALAR)),
|
|
("CONTROL_WAIT", int(isa.ControlOpcode.WAIT)),
|
|
("SCALAR_MOVI", int(isa.ScalarOpcode.MOVI)),
|
|
("PRED_ALWAYS", 0)):
|
|
found = re.findall(r"^`define\s+APB4_APU__MC_" + suffix + r"\s+(\S+)", defines, re.M)
|
|
if len(found) != 1 or number(found[0]) != value:
|
|
raise ValueError("APU example opcode/predicate differs from RTL")
|
|
limits = {}
|
|
legacy = re.findall(r"^`define\s+APB4_APU__APUMC_MAX_INSTRUCTIONS\s+(\d+)", defines, re.M)
|
|
if legacy != [str(isa.APUMC_MAX_INSTRUCTIONS)]:
|
|
raise ValueError("APU legacy image capacity differs from the encoder")
|
|
for version in ("V1", "V2"):
|
|
maximum = re.findall(r"^`define\s+APB4_APU__APUMC_MAX_INSTRUCTIONS_" + version + r"\s+(\d+)", defines, re.M)
|
|
if maximum != [str(getattr(isa, "APUMC_MAX_INSTRUCTIONS_" + version))]:
|
|
raise ValueError("APU control-store capacity differs from the encoder")
|
|
limits[version.lower()] = int(maximum[0])
|
|
instructions = (
|
|
("SCALAR.MOVI: r1 = 0x12345678", isa.Instruction(int(isa.InstructionClass.SCALAR), int(isa.ScalarOpcode.MOVI), dst=1, immediate=0x12345678)),
|
|
("CONTROL.WAIT: DMA source", isa.Instruction(int(isa.InstructionClass.CONTROL), int(isa.ControlOpcode.WAIT), aux=isa.WAIT_SOURCES.index("dma"))),
|
|
)
|
|
examples = []
|
|
for title, instruction in instructions:
|
|
word = instruction.encode()
|
|
if isa.Instruction.decode(word) != instruction:
|
|
raise ValueError("APU instruction diagram failed round-trip encoding")
|
|
examples.append({"title": title, "word": f"0x{word:016X}",
|
|
"values": {name: getattr(instruction, name) for name in aliases}})
|
|
return {"bits": 64, "fields": fields, "examples": examples, "max_instructions": limits["v2"], "image_limits": limits,
|
|
"families": instruction_families(isa, defines)}
|
|
|
|
|
|
def uart_fifo(root: Path) -> dict:
|
|
source = (root / "rtl/ip/serial/uart_reg.sv").read_text(encoding="utf-8")
|
|
result = {}
|
|
for direction in ("Tx", "Rx"):
|
|
depth = re.findall(r"parameter\s+int\s+" + direction + r"FifoDepth\s*=\s*(\d+)", source)
|
|
fifo = re.findall(r"fifo\s*#\s*\((.*?)\)\s+u_" + direction.lower() + r"_fifo\s*\(", source, re.S)
|
|
if len(depth) != 1 or len(fifo) != 1:
|
|
raise ValueError("UART FIFO instance missing or ambiguous")
|
|
# Select the final declaration before the named instance, not a preceding FIFO.
|
|
widths = re.findall(r"\.DATA_WIDTH\s*\((\d+)\)", fifo[0])
|
|
result[direction.lower()] = {"depth": int(depth[0]), "bits": int(widths[-1])}
|
|
if result != {"tx": {"depth": 64, "bits": 8}, "rx": {"depth": 64, "bits": 12}}:
|
|
raise ValueError("review UART storage diagram after FIFO geometry change")
|
|
return result
|
|
|
|
|
|
def validate_circuit(root: Path, circuit: dict) -> None:
|
|
nodes, ports = {}, {}
|
|
for node in circuit["nodes"]:
|
|
if node["id"] in nodes:
|
|
raise ValueError("duplicate circuit node")
|
|
nodes[node["id"]] = node
|
|
if not node.get("sources") or not node.get("title"):
|
|
raise ValueError("circuit node lacks source/title")
|
|
instance = node.get("instance_source", {})
|
|
if any(instance[key] not in node["sources"] for key in ("file", "declaration_file") if key in instance):
|
|
raise ValueError("circuit instance declaration is outside its source inventory")
|
|
for port in node["ports"]:
|
|
key = node["id"] + "." + port["id"]
|
|
if key in ports or port["side"] not in {"north", "south", "east", "west"}:
|
|
raise ValueError("duplicate or invalid circuit port")
|
|
width_valid = (type(port["width"]) is int and port["width"] > 0) or (
|
|
port["width"] is None and port.get("bundle") is True)
|
|
if port["direction"] not in {"in", "out"} or not width_valid:
|
|
raise ValueError("invalid circuit port direction/width")
|
|
ports[key] = port
|
|
seen = set()
|
|
for edge in circuit["edges"]:
|
|
src, dst = edge["from"], edge["to"]
|
|
if src not in ports or dst not in ports or (src, dst) in seen:
|
|
raise ValueError("missing or duplicate circuit connection")
|
|
if ports[src]["direction"] != "out" or ports[dst]["direction"] != "in":
|
|
raise ValueError("circuit connection direction mismatch")
|
|
if ports[src]["width"] != ports[dst]["width"] or edge["kind"] not in {"control", "data"}:
|
|
raise ValueError("circuit connection width/kind mismatch")
|
|
displayed_width = re.search(r"/\s*(\d+)$", edge.get("label", ""))
|
|
if displayed_width and ports[src]["width"] is not None and int(displayed_width[1]) != ports[src]["width"]:
|
|
raise ValueError("circuit label width differs from the bound port width")
|
|
if not edge.get("bindings") or not edge.get("label"):
|
|
raise ValueError("circuit connection lacks source binding")
|
|
validate_bindings(root, edge["bindings"])
|
|
seen.add((src, dst))
|
|
validate_instance_connections(root, circuit)
|
|
|
|
|
|
def collect_diagrams(root: Path, spec: dict, regions: list[dict], system: dict) -> dict:
|
|
for relative in dependencies(spec):
|
|
path = root / relative
|
|
if Path(relative).is_absolute() or ".." in Path(relative).parts or not path.resolve().is_relative_to(root.resolve()) or not path.is_file():
|
|
raise ValueError("missing or invalid diagram source")
|
|
validate_bindings(root, spec["bindings"])
|
|
additional = json.loads((root / spec["circuit_catalog"]).read_text(encoding="utf-8"))
|
|
if set(additional) & set(spec["circuits"]):
|
|
raise ValueError("duplicate circuit illustration identifier")
|
|
circuits = {**spec["circuits"], **additional}
|
|
index = json.loads((root / "publications/datasheets/chapter-index.json").read_text(encoding="utf-8"))
|
|
expected = {row["id"] for row in index} | {"system-fabric", "system-clocks", "system-media", "system-mpw"}
|
|
if set(circuits) != expected:
|
|
raise ValueError("full-document circuit coverage changed")
|
|
for circuit in circuits.values():
|
|
validate_circuit(root, circuit)
|
|
validate_fields(spec["sdio_command"], 48)
|
|
command_fields = [(f["name"], f["lsb"], f["bits"]) for f in spec["sdio_command"]]
|
|
if command_fields != [("start", 47, 1), ("direction", 46, 1), ("command", 40, 6),
|
|
("argument", 8, 32), ("crc7", 1, 7), ("end", 0, 1)]:
|
|
raise ValueError("SDIO field order differs from the bound transmitter assembly")
|
|
dts = (root / "app/ports/linux/linux/retrosoc_hp.dts").read_text(encoding="utf-8")
|
|
if "riscv,cbom-block-size" in dts:
|
|
raise ValueError("review cache-maintenance diagram after platform granule change")
|
|
smoke = (root / "app/ports/linux/smoke/start.S").read_text(encoding="utf-8")
|
|
if (".insn r 0x0b, 0x0, 0x01, x0, a0, x5" not in smoke
|
|
or ".insn r 0x0b, 0x0, 0x01, x0, a0, x6" not in smoke
|
|
or "csrs 0x7c0, t0" not in smoke):
|
|
raise ValueError("review cache-maintenance diagram after platform maintenance-operation change")
|
|
windows = [{k: r[k] for k in ("symbol", "base", "size", "base_hex", "end_hex", "size_label")}
|
|
for r in regions if r["size"] > 4096 and r["route"] in {"axi4", "ram"} and r["kind"] == "active"]
|
|
if not windows or any(r["base"] + r["size"] > 1 << 32 for r in windows):
|
|
raise ValueError("memory diagram has an invalid address window")
|
|
if any(int(r["base_hex"], 16) != r["base"] or int(r["end_hex"], 16) != r["base"] + r["size"] - 1 for r in windows):
|
|
raise ValueError("memory diagram range labels disagree with numeric bounds")
|
|
layouts = collect_layouts(root, spec["layout_catalog"], system)
|
|
for layout in layouts.values():
|
|
validate_bindings(root, layout.get("bindings", []))
|
|
for row in layout.get("rows", [layout]):
|
|
validate_fields(row["fields"], row["bits"])
|
|
groups = row.get("display_groups", layout.get("display_groups"))
|
|
if groups is not None:
|
|
indexes = [index for lo, hi in groups for index in range(lo, hi + 1)]
|
|
if indexes != list(range(len(row["fields"]))):
|
|
raise ValueError("compressed binary diagram drops or repeats fields")
|
|
storage = collect_storage(root, spec["storage_catalog"], regions, system, layouts)
|
|
for record in storage.values():
|
|
validate_bindings(root, record.get("bindings", []))
|
|
return {"sources": sorted(dependencies(spec)), "dma_tcd": dma_fields(root),
|
|
"sdio_command": copy.deepcopy(spec["sdio_command"]), "apu": apu_formats(root),
|
|
"uart_fifo": uart_fifo(root), "windows": sorted(windows, key=lambda r: r["base"]),
|
|
"cache": {"granule": 64, "offset": 16, "length": 64, "end": 80, "covered_bytes": 128},
|
|
"circuits": circuits, "layouts": layouts, "storage": storage,
|
|
"soc_architecture": collect_soc_diagram(root, spec["soc_architecture_catalog"], regions) if spec.get("soc_architecture_catalog") else {}}
|