mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Updated soc_topology.json to include new fabric links for core, DMA, native, and APB interfaces. - Refactored retrosoc.sv to utilize generated fabric links and included necessary SVH files for better modularity. - Improved check_clock_reset_domains.py to validate hierarchical pins and added support for positive number checks. - Modified regress.py to include OpenSTA in the regression commands. - Enhanced README.md in the STA directory to clarify the timing analysis flow and supported PDK profiles. - Removed outdated gen2.sdc file and replaced it with a dynamic SDC generation approach. - Updated opensta.mk to generate SDC files dynamically based on the current domain map and pin map. - Created generate_sdc.py to automate SDC generation from clock/reset domain inventory. - Added tests for generated core-STA constraints to ensure coverage of current clock domains and validate pin map integrity. - Introduced new timing metrics and warnings for the sky130 PDK in the quality checks.
155 lines
5.7 KiB
Python
155 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate core-STA constraints from the active SoC clock/reset inventory."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
from rtl.mini.pin_map.generate_pin_map import read_map # noqa: E402
|
|
from scripts.check_clock_reset_domains import validate # noqa: E402
|
|
from scripts.setup_helpers import atomic_write # noqa: E402
|
|
|
|
|
|
def require_object(value: Any, field: str) -> dict[str, Any]:
|
|
if not isinstance(value, dict):
|
|
raise ValueError(f"{field} must be an object")
|
|
return value
|
|
|
|
|
|
def require_string(value: Any, field: str) -> str:
|
|
if not isinstance(value, str) or not value:
|
|
raise ValueError(f"{field} must be a non-empty string")
|
|
return value
|
|
|
|
|
|
def parse_document(path: Path, pin_map: Path) -> tuple[list[dict[str, Any]], list[str]]:
|
|
validate(path, ROOT)
|
|
document = require_object(json.loads(path.read_text(encoding="utf-8")), "domain map")
|
|
pads, _ = read_map(pin_map)
|
|
pad_names = {pad.name for pad in pads}
|
|
reset_ports = document.get("reset_ports")
|
|
if not isinstance(reset_ports, list) or not reset_ports:
|
|
raise ValueError("reset_ports must be a non-empty list")
|
|
parsed_resets = [require_string(port, "reset_ports entry") for port in reset_ports]
|
|
if len(parsed_resets) != len(set(parsed_resets)):
|
|
raise ValueError("reset_ports contains duplicates")
|
|
for port in parsed_resets:
|
|
if port not in pad_names:
|
|
raise ValueError(f"reset port is not declared in pin map: {port}")
|
|
|
|
domains = document["domains"]
|
|
names = {domain["name"] for domain in domains}
|
|
for domain in domains:
|
|
sta = domain["sta"]
|
|
source_port = sta.get("source_port")
|
|
if source_port is not None and source_port not in pad_names:
|
|
raise ValueError(f"domain {domain['name']} source port is not declared in pin map")
|
|
source_domain = sta.get("source_domain")
|
|
if source_domain is not None and source_domain not in names:
|
|
raise ValueError(f"domain {domain['name']} source domain is not declared")
|
|
return domains, parsed_resets
|
|
|
|
|
|
def render(domains: list[dict[str, Any]], reset_ports: list[str]) -> str:
|
|
lines = [
|
|
"# Generated by sta/opensta/generate_sdc.py; do not edit.",
|
|
"# Core-STA baseline: pad/package timing is intentionally excluded.",
|
|
"",
|
|
"proc require_pins {label name} {",
|
|
" set objects [get_pins $name]",
|
|
" if {$objects eq \"\"} {",
|
|
" error \"required SDC object is missing: $label ($name)\"",
|
|
" }",
|
|
" return $objects",
|
|
"}",
|
|
"",
|
|
]
|
|
pin_variables: dict[str, str] = {}
|
|
for domain in domains:
|
|
name = domain["name"]
|
|
sta = domain["sta"]
|
|
pin_variable = f"clk_{name}_pin"
|
|
pin_variables[name] = pin_variable
|
|
lines.append(
|
|
f"set {pin_variable} [require_pins \"clock {name}\" {{{sta['pin']}}}]"
|
|
)
|
|
period = f"{float(sta['period_ns']):.12g}"
|
|
source_domain = sta.get("source_domain")
|
|
if source_domain is None:
|
|
lines.append(f"create_clock -name clk_{name} -period {period} ${pin_variable}")
|
|
else:
|
|
lines.append(
|
|
f"create_generated_clock -name clk_{name} -source ${pin_variables[source_domain]} "
|
|
f"-divide_by 1 ${pin_variable}"
|
|
)
|
|
lines.append("")
|
|
|
|
clock_names = " ".join(f"clk_{domain['name']}" for domain in domains)
|
|
lines.extend(
|
|
[
|
|
f"set_clock_uncertainty -setup 0.2 [get_clocks {{{clock_names}}}]",
|
|
f"set_clock_uncertainty -hold 0.1 [get_clocks {{{clock_names}}}]",
|
|
"",
|
|
]
|
|
)
|
|
for domain in domains:
|
|
lines.append(f"set_clock_transition 0.1 [get_clocks {{clk_{domain['name']}}}]")
|
|
lines.append("")
|
|
groups: dict[str, list[str]] = {}
|
|
for domain in domains:
|
|
groups.setdefault(domain["sta"]["async_group"], []).append(f"clk_{domain['name']}")
|
|
if len(groups) > 1:
|
|
lines.append("set_clock_groups -name retrosoc_async -asynchronous \\")
|
|
group_items = list(groups.values())
|
|
for index, clocks in enumerate(group_items):
|
|
continuation = " \\" if index != len(group_items) - 1 else ""
|
|
lines.append(f" -group [get_clocks {{{' '.join(clocks)}}}]{continuation}")
|
|
lines.append("")
|
|
|
|
for reset_port in reset_ports:
|
|
lines.extend(
|
|
[
|
|
f"set reset_{reset_port} [get_ports {{{reset_port}}}]",
|
|
f"if {{$reset_{reset_port} eq \"\"}} {{",
|
|
f" error \"required reset port is missing: {reset_port}\"",
|
|
"}",
|
|
f"set_false_path -from $reset_{reset_port}",
|
|
"",
|
|
]
|
|
)
|
|
lines.extend(
|
|
[
|
|
"# This profile closes sequential core paths only. Board and pad timing",
|
|
"# are supplied by a later board-specific SDC profile.",
|
|
"set_false_path -from [all_inputs]",
|
|
"set_false_path -to [all_outputs]",
|
|
"",
|
|
]
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--domains", type=Path, required=True)
|
|
parser.add_argument("--pin-map", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
domains, reset_ports = parse_document(args.domains, args.pin_map)
|
|
atomic_write(args.output, render(domains, reset_ports))
|
|
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
parser.error(str(error))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|