feat(tiny): add scripts for SDC generation and packaging

- Introduced  to create pad-aware timing constraints for the Tiny IHP130 chip flow.
- Added  to create a checksummed delivery archive for the Tiny LibreLane implementation.
- Created  for synthesis-only header of the locked IHP SRAM macro physical views.
- Implemented  to limit OpenSTA report enumeration for path counts.
- Updated  to include power pad definitions.
- Enhanced  to handle power pad counts and validation.
- Added tests for Tiny flow and pin map validation, including power pad overrides and constraints.
This commit is contained in:
maksyuki committed 2026-09-27 08:48:12 +08:00
1 parent 2805ea9183
commit c8187284d0
29 files changed
+1520 -63

No files matched your search

+36 -5
View File
@@ -29,6 +29,7 @@ REQUIRED_PROFILES = {"fpga_mini", "tb", "verilator"}
CONNECTION_RE = re.compile(r"(?:[A-Za-z_][A-Za-z0-9_]*)(?:\[[0-9]+\])?$")
CONSTANT_RE = re.compile(r"[0-9]+'[bBdDhHoO][0-9a-fA-F_xXzZ]+$")
IHP130_POWER_PAD_COUNTS = {"vdd": 24, "vss": 24, "iovdd": 16, "iovss": 16}
POWER_PAD_KINDS = tuple(IHP130_POWER_PAD_COUNTS)
POWER_CONNECTIONS = "`RETROSOC_PAD_POWER_CONNECTIONS"
@@ -173,9 +174,33 @@ def validate_connection(value: Any, field: str) -> str | None:
raise ValueError(f"{field} must be a signal, bit-select, constant, or null")
def read_map(path: Path) -> tuple[list[Pad], dict[str, dict[str, str | None]]]:
def read_document(path: Path) -> dict[str, Any]:
with path.open(encoding="utf-8") as source:
document = json.load(source)
return json.load(source)
def parse_power_pads(document: dict[str, Any]) -> dict[str, int]:
counts = document.get("power_pads")
if counts is None:
return dict(IHP130_POWER_PAD_COUNTS)
if not isinstance(counts, dict) or set(counts) != set(POWER_PAD_KINDS):
raise ValueError(f"power_pads must contain exactly {list(POWER_PAD_KINDS)}")
parsed: dict[str, int] = {}
for kind in POWER_PAD_KINDS:
value = counts[kind]
if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
raise ValueError(f"power_pads.{kind} must be a positive integer")
parsed[kind] = value
return parsed
def read_power_pad_counts(path: Path) -> dict[str, int]:
return parse_power_pads(read_document(path))
def read_map(path: Path) -> tuple[list[Pad], dict[str, dict[str, str | None]]]:
document = read_document(path)
parse_power_pads(document)
if document.get("schema_version") != 1:
raise ValueError("schema_version must be 1")
groups = document.get("pads")
@@ -299,7 +324,10 @@ def render_pad_instance(pad: Pad) -> str | None:
return None
def render_pad_bindings(pads: list[Pad]) -> str:
def render_pad_bindings(
pads: list[Pad], power_counts: dict[str, int] | None = None
) -> str:
counts = IHP130_POWER_PAD_COUNTS if power_counts is None else power_counts
lines = [
"// Generated by scripts/rtl/generate_pin_map.py; do not edit.",
"`ifdef PDK_IHP130",
@@ -311,7 +339,7 @@ def render_pad_bindings(pads: list[Pad]) -> str:
" generate",
]
cell_suffixes = {"vdd": "Vdd", "vss": "Vss", "iovdd": "IOVdd", "iovss": "IOVss"}
for kind, count in IHP130_POWER_PAD_COUNTS.items():
for kind, count in counts.items():
lines.extend(
[
f" for (genvar i = 0; i < {count}; i++) begin : {kind}_pads",
@@ -435,9 +463,12 @@ def render_profile_bindings(pads: list[Pad], bindings: dict[str, str | None]) ->
def generate(map_path: Path, output_dir: Path) -> None:
pads, profiles = read_map(map_path)
power_counts = read_power_pad_counts(map_path)
rtl_dir = output_dir / "rtl"
atomic_write(rtl_dir / "retrosoc_asic_ports.svh", render_ports(pads))
atomic_write(rtl_dir / "retrosoc_asic_pad_bindings.svh", render_pad_bindings(pads))
atomic_write(
rtl_dir / "retrosoc_asic_pad_bindings.svh", render_pad_bindings(pads, power_counts)
)
atomic_write(rtl_dir / "retrosoc_core_ports.svh", render_core_ports(pads))
atomic_write(rtl_dir / "retrosoc_core_bindings.svh", render_core_bindings(pads))
for profile, bindings in profiles.items():