mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- 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.
194 lines
7.2 KiB
Python
Executable File
194 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate pad-aware multi-clock constraints for the IHP130 Chip flow."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
sys.path.insert(0, str(ROOT))
|
|
from scripts.rtl.generate_pin_map import Pad, ihp130_pad_instance, read_map # noqa: E402
|
|
from scripts.check_clock_reset_domains import validate # noqa: E402
|
|
from scripts.setup_helpers import atomic_write # noqa: E402
|
|
|
|
|
|
CLOCK_PADS = {
|
|
"external": "extclk_i_pad",
|
|
"audio": "audclk_i_pad",
|
|
"jtag": "jtag_tck_i_pad",
|
|
"dvp": "gpio_10_io_pad",
|
|
"usb2_ulpi": "usb2_ulpi_clk_i_pad",
|
|
}
|
|
RESET_PADS = {"ext_rst_n_i_pad", "jtag_trst_n_i_pad"}
|
|
IHP130_SYSTEM_CLOCK_PIN = "u_rcu.u_sys_clk_buf.u_sg13g2_buf_1/X"
|
|
|
|
|
|
def positive_integer(value: str) -> int:
|
|
parsed = int(value)
|
|
if parsed <= 0:
|
|
raise argparse.ArgumentTypeError("value must be positive")
|
|
return parsed
|
|
|
|
|
|
def timing_clock(pad: Pad) -> str:
|
|
if pad.name.startswith("jtag_"):
|
|
return "jtag"
|
|
if pad.name.startswith("usb2_ulpi_"):
|
|
return "usb2_ulpi"
|
|
if pad.name.startswith("gpio_") and pad.index is not None and 11 <= pad.index <= 20:
|
|
return "dvp"
|
|
return "external"
|
|
|
|
|
|
def logical_directions(pad: Pad) -> tuple[bool, bool]:
|
|
input_kinds = {"tri_input", "schmitt_input"}
|
|
output_kinds = {"tri_output", "peripheral_output"}
|
|
bidir_kinds = {"full", "peripheral_bidir", "sdram_dq", "xpi_data"}
|
|
if pad.kind in input_kinds:
|
|
return True, False
|
|
if pad.kind in output_kinds:
|
|
return False, True
|
|
if pad.kind in bidir_kinds:
|
|
return True, True
|
|
return False, False
|
|
|
|
|
|
def render(args: argparse.Namespace) -> str:
|
|
validate(args.domains, ROOT)
|
|
target = getattr(args, "target", "chip")
|
|
if target not in {"chip", "core"}:
|
|
raise ValueError("target must be chip or core")
|
|
pads, _ = read_map(args.pin_map)
|
|
active = [pad for pad in pads if pad.feature is None or args.have_pll]
|
|
by_name = {pad.name: pad for pad in active}
|
|
for name in CLOCK_PADS.values():
|
|
if name not in by_name:
|
|
raise ValueError(f"clock PAD is missing from the active pin map: {name}")
|
|
|
|
periods = {
|
|
"external": 1_000_000_000 / args.ext_clk_hz,
|
|
"audio": 1_000_000_000 / args.aud_clk_hz,
|
|
"jtag": 100.0,
|
|
"dvp": 41.666666667,
|
|
"usb2_ulpi": 16.666666667,
|
|
}
|
|
groups: dict[str, dict[str, list[str]]] = {
|
|
name: {"inputs": [], "outputs": []} for name in periods
|
|
}
|
|
excluded = set(CLOCK_PADS.values()) | RESET_PADS
|
|
for pad in active:
|
|
if pad.name in excluded or (pad.kind == "xtal" and not pad.bind):
|
|
continue
|
|
is_input, is_output = logical_directions(pad)
|
|
clock = timing_clock(pad)
|
|
if is_input:
|
|
groups[clock]["inputs"].append(pad.name)
|
|
if is_output:
|
|
groups[clock]["outputs"].append(pad.name)
|
|
|
|
lines = [
|
|
"# Generated by physical/librelane/mini/scripts/generate_sdc.py; do not edit.",
|
|
"current_design $::env(DESIGN_NAME)",
|
|
"set_units -time ns",
|
|
"",
|
|
"proc require_pins {label name} {",
|
|
" set objects [get_pins -quiet $name]",
|
|
' if {$objects eq ""} { error "required SDC pin is missing: $label ($name)" }',
|
|
" return $objects",
|
|
"}",
|
|
"proc require_ports {label name} {",
|
|
" set objects [get_ports -quiet $name]",
|
|
' if {$objects eq ""} { error "required SDC port is missing: $label ($name)" }',
|
|
" return $objects",
|
|
"}",
|
|
"",
|
|
]
|
|
clock_vars: dict[str, str] = {}
|
|
for name, pad_name in CLOCK_PADS.items():
|
|
variable = f"clk_{name}_pin"
|
|
if target == "chip":
|
|
instance = ihp130_pad_instance(by_name[pad_name])
|
|
if instance is None:
|
|
raise ValueError(f"clock PAD has no IHP130 physical instance: {pad_name}")
|
|
lines.append(f'set {variable} [require_pins "clock {name}" {{{instance}/p2c}}]')
|
|
else:
|
|
lines.append(f'set {variable} [require_ports "clock {name}" {{{pad_name}}}]')
|
|
lines.append(f"create_clock -name clk_{name} -period {periods[name]:.12g} ${variable}")
|
|
lines.append("")
|
|
clock_vars[name] = variable
|
|
|
|
lines.extend(
|
|
[
|
|
f'set clk_system_pin [require_pins "clock system" {{{IHP130_SYSTEM_CLOCK_PIN}}}]',
|
|
"create_generated_clock -name clk_system -source $clk_external_pin "
|
|
"-divide_by 1 $clk_system_pin",
|
|
"",
|
|
"set_clock_groups -name retrosoc_async -asynchronous \\",
|
|
" -group [get_clocks {clk_external clk_system}] \\",
|
|
" -group [get_clocks {clk_audio}] \\",
|
|
" -group [get_clocks {clk_jtag}] \\",
|
|
" -group [get_clocks {clk_dvp}] \\",
|
|
" -group [get_clocks {clk_usb2_ulpi}]",
|
|
"",
|
|
"set all_retrosoc_clocks [get_clocks {clk_external clk_system clk_audio clk_jtag clk_dvp clk_usb2_ulpi}]",
|
|
"set_clock_uncertainty -setup 0.2 $all_retrosoc_clocks",
|
|
"set_clock_uncertainty -hold 0.1 $all_retrosoc_clocks",
|
|
"set_clock_transition 0.1 $all_retrosoc_clocks",
|
|
"set_timing_derate -early 0.95",
|
|
"set_timing_derate -late 1.05",
|
|
"set_max_fanout 10 [current_design]",
|
|
"",
|
|
]
|
|
)
|
|
|
|
for reset in sorted(RESET_PADS):
|
|
lines.extend(
|
|
[
|
|
f'set reset_{reset} [require_ports "reset {reset}" {{{reset}}}]',
|
|
f"set_false_path -from $reset_{reset}",
|
|
"",
|
|
]
|
|
)
|
|
|
|
for clock, port_groups in groups.items():
|
|
delay = periods[clock] * 0.2
|
|
for direction, ports in port_groups.items():
|
|
if not ports:
|
|
continue
|
|
variable = f"{clock}_{direction}"
|
|
lines.append(f"set {variable} [get_ports {{{' '.join(ports)}}}]")
|
|
if direction == "inputs":
|
|
lines.append(f"set_input_delay -min 0 -clock clk_{clock} ${variable}")
|
|
lines.append(f"set_input_delay -max {delay:.12g} -clock clk_{clock} ${variable}")
|
|
else:
|
|
lines.append(f"set_output_delay -min 0 -clock clk_{clock} ${variable}")
|
|
lines.append(f"set_output_delay -max {delay:.12g} -clock clk_{clock} ${variable}")
|
|
lines.append(f"set_load 0.006 ${variable}")
|
|
lines.append("")
|
|
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("--ext-clk-hz", type=positive_integer, required=True)
|
|
parser.add_argument("--aud-clk-hz", type=positive_integer, required=True)
|
|
parser.add_argument("--have-pll", action="store_true")
|
|
parser.add_argument("--target", choices=("chip", "core"), default="chip")
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
atomic_write(args.output, render(args))
|
|
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
parser.error(str(error))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|