Files
retroSoC/tests/test_librelane_tiny_flow.py
T
maksyuki 83fe263e40 fix(physical): separate Tiny camera timing constraints
Add isolated default and camera LibreLane IO profiles, assigning camera
inputs to the pixel clock and keeping ordinary GPIO constraints in default.
Separate generated output roots and synchronize the commercial clock-domain
inventory with the P9 DVP domain.

Retained validation: nine LibreLane scenario tests and 50 commercial-flow/
Tiny reset-inventory tests pass. Constraints retain development assumptions;
no commercial EDA, physical signoff or board timing qualification is claimed.

Specification: docs/ip/tiny-soc.md, TINY-R2-P9.
2026-10-17 15:33:13 +08:00

234 lines
10 KiB
Python

"""Tests for the Tiny IHP130 LibreLane chip flow."""
from __future__ import annotations
import importlib.util
import re
import subprocess
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[1]
PIN_MAP = ROOT / "rtl/tiny/pin_map/pin_map.json"
DOMAINS = ROOT / "rtl/tiny/integration/clock_reset_domains.json"
FLOW_ROOT = ROOT / "physical/librelane/tiny"
BONDPAD_LEF = ROOT / "physical/librelane/bondpad/bondpad_70x70.lef"
def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def chip_arguments(tmp_path: Path) -> SimpleNamespace:
return SimpleNamespace(
pin_map=PIN_MAP,
rtl=tmp_path / "retrosoc_tiny_asic_sources.sv",
sdc=tmp_path / "retrosoc_tiny_chip.sdc",
pdn=FLOW_ROOT / "pdn_cfg.tcl",
bondpad_gds=tmp_path / "bondpad_70x70.gds",
bondpad_lef=BONDPAD_LEF,
sram_vh=FLOW_ROOT / "sram_blackboxes.vh",
ext_clk_hz=24_000_000,
have_sram_macro=True,
sram_size_kib=128,
output=tmp_path / "config.json",
)
def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> None:
module = load_module(
"retrosoc_tiny_librelane_config", FLOW_ROOT / "scripts/generate_chip_config.py"
)
config = module.build_config(chip_arguments(tmp_path))
sides = {name: config[f"PAD_{name.upper()}"] for name in module.SIDE_ORDER}
placed = [instance for side in module.SIDE_ORDER for instance in sides[side]]
assert len(placed) == 84
assert len(placed) == len(set(placed))
assert {side: len(instances) for side, instances in sides.items()} == {
"south": 19,
"east": 24,
"north": 17,
"west": 24,
}
assert sum(item.startswith("vdd_pads[") for item in placed) == 8
assert sum(item.startswith("vss_pads[") for item in placed) == 8
assert sum(item.startswith("iovdd_pads[") for item in placed) == 8
assert sum(item.startswith("iovss_pads[") for item in placed) == 8
assert "u_extclk_i_pad.u_sg13g2_IOPadIn" in sides["south"]
assert "u_jtag_tck_i_pad.u_sg13g2_IOPadIn" in sides["south"]
assert "u_uart0_tx_o_pad.u_sg13g2_IOPadOut4mA" in sides["south"]
assert "u_xpi_sck_o_pad.u_sg13g2_IOPadOut4mA" in sides["north"]
assert "u_gpio_0_io_pad.u_sg13g2_IOPadInOut4mA" in sides["east"]
assert "u_gpio_15_io_pad.u_sg13g2_IOPadInOut4mA" in sides["east"]
assert "u_gpio_16_io_pad.u_sg13g2_IOPadInOut4mA" in sides["west"]
assert "u_gpio_31_io_pad.u_sg13g2_IOPadInOut4mA" in sides["west"]
assert config["meta"] == {"version": 3, "flow": "Chip"}
assert config["DESIGN_NAME"] == "retrosoc_tiny_asic"
assert config["DIE_AREA"] == [0, 0, 4700, 4700]
assert config["CORE_AREA"] == [365, 365, 4335, 4335]
assert config["CLOCK_PORT"] == ["extclk_i_pad", "jtag_tck_i_pad"]
assert config["CLOCK_PERIOD"] == 1_000_000_000 / 24_000_000
assert config["PL_TARGET_DENSITY_PCT"] == 45
assert config["USE_SLANG"] is True
assert config["SLANG_ARGUMENTS"] == ["--keep-hierarchy"]
assert config["SYNTH_HIERARCHY_MODE"] == "deferred_flatten"
assert config["SYNTH_KEEP_HIERARCHY_MODULES"] == [
"sg13g2_IOPadVdd",
"sg13g2_IOPadVss",
"sg13g2_IOPadIOVdd",
"sg13g2_IOPadIOVss",
]
assert config["RUN_CTS"] is False
assert config["ERROR_ON_SYNTH_CHECKS"] is False
assert config["EXTRA_EXCLUDED_CELLS"] == ["sg13g2_IOPad*"]
assert config["VDD_NETS"] == ["VDD"]
assert config["GND_NETS"] == ["VSS"]
assert config["PAD_CFG"].endswith("physical/librelane/tiny/pad_cfg.tcl")
assert config["PDN_ENABLE_PINS"] is True
assert config["ERROR_ON_PDN_VIOLATIONS"] is True
assert config["STA_EXTRA_CORNER_TCL_FILE"].endswith(
"physical/librelane/tiny/sta_report_limit.tcl"
)
assert config["PAD_BONDPAD_NAME"] == "bondpad_70x70"
assert set(config["MACROS"]) == {"RM_IHPSG13_1P_1024x32_c2_bm_bist"}
def test_chip_config_covers_every_onchip_sram_bank(tmp_path: Path) -> None:
module = load_module(
"retrosoc_tiny_librelane_sram_config", FLOW_ROOT / "scripts/generate_chip_config.py"
)
config = module.build_config(chip_arguments(tmp_path))
macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"]
assert len(macro["instances"]) == 32
assert (
"u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" in macro["instances"]
)
assert (
"u_soc.u_sram.gen_group[3].u_group.gen_bank[7].u_ram.u_mem" in macro["instances"]
)
for instance in macro["instances"].values():
x, y = instance["location"]
assert 365 <= x <= 4335
assert 365 <= y <= 4335
assert instance["orientation"] == "N"
assert len(config["PDN_MACRO_CONNECTIONS"]) == 64
assert any(
"gen_bank.*0.*\\.u_ram" in connection
for connection in config["PDN_MACRO_CONNECTIONS"]
)
blackboxes = (FLOW_ROOT / "sram_blackboxes.vh").read_text(encoding="utf-8")
assert "module RM_IHPSG13_1P_1024x32_c2_bm_bist" in blackboxes
assert "RM_IHPSG13_1P_4096x16_c3_bm_bist" not in blackboxes
def test_chip_sdc_is_pad_aware_and_does_not_false_path_all_io() -> None:
module = load_module(
"retrosoc_tiny_librelane_sdc", FLOW_ROOT / "scripts/generate_sdc.py"
)
arguments = SimpleNamespace(
domains=DOMAINS,
pin_map=PIN_MAP,
ext_clk_hz=24_000_000,
)
sdc = module.render(arguments)
assert "u_extclk_i_pad.u_sg13g2_IOPadIn/p2c" in sdc
assert "u_jtag_tck_i_pad.u_sg13g2_IOPadIn/p2c" in sdc
assert "u_clock_buffer.u_sg13g2_buf_1/X" in sdc
assert "create_clock -name clk_external -period 41.6666666667" in sdc
assert "create_clock -name clk_jtag -period 100" in sdc
assert "create_generated_clock -name clk_system" in sdc
assert "set_clock_groups -name retrosoc_async -asynchronous" in sdc
assert "-group [get_clocks {clk_external clk_system}]" in sdc
assert "-group [get_clocks {clk_jtag}]" in sdc
assert "set_input_delay -max" in sdc
assert "set_output_delay -max" in sdc
assert "set_false_path -from $reset_ext_rst_n_i_pad" in sdc
assert "set_false_path -from $reset_jtag_trst_n_i_pad" in sdc
assert "set_false_path -from [all_inputs]" not in sdc
assert "set_false_path -to [all_outputs]" not in sdc
assert "clk_pixel" not in sdc
@pytest.mark.parametrize("profile", ["default", "camera"])
def test_camera_io_clock_scenario(profile: str, tmp_path: Path) -> None:
module = load_module("tiny_sdc_scenario", FLOW_ROOT / "scripts/generate_sdc.py")
sdc = module.render(SimpleNamespace(domains=DOMAINS, pin_map=PIN_MAP,
ext_clk_hz=24_000_000, io_profile=profile))
groups = {name: set(ports.split()) for name, ports in re.findall(
r"set (\w+_(?:inputs|outputs)) \[get_ports \{([^}]*)\}\]", sdc)}
camera = {f"gpio_{pin}_io_pad" for pin in (*range(12, 20), 21, 22)}
config_module = load_module("tiny_config_scenario", FLOW_ROOT / "scripts/generate_chip_config.py")
arguments = chip_arguments(tmp_path)
arguments.io_profile = profile
config = config_module.build_config(arguments)
if profile == "camera":
assert groups["pixel_inputs"] == camera
assert not camera & groups["external_inputs"]
assert not camera & groups["external_outputs"]
assert "gpio_20_io_pad" not in groups["external_inputs"]
assert "gpio_20_io_pad" not in groups["external_outputs"]
assert "set_input_delay -min 0 -clock clk_pixel $pixel_inputs" in sdc
assert "set_input_delay -max 16.6666666666 -clock clk_pixel $pixel_inputs" in sdc
assert "-group [get_clocks {clk_pixel}]" in sdc
assert config["CLOCK_PORT"] == ["extclk_i_pad", "jtag_tck_i_pad", "gpio_20_io_pad"]
else:
assert "pixel_inputs" not in groups
assert camera | {"gpio_20_io_pad"} <= groups["external_inputs"]
assert camera | {"gpio_20_io_pad"} <= groups["external_outputs"]
assert "clk_pixel" not in sdc
assert config["CLOCK_PORT"] == ["extclk_i_pad", "jtag_tck_i_pad"]
assert "gpio_23_io_pad" in groups["external_outputs"]
def test_camera_io_profile_rejects_unknown_value(tmp_path: Path) -> None:
module = load_module("tiny_sdc_bad_scenario", FLOW_ROOT / "scripts/generate_sdc.py")
with pytest.raises(ValueError, match="IO profile"):
module.render(SimpleNamespace(io_profile="both"))
module = load_module("tiny_config_bad_scenario", FLOW_ROOT / "scripts/generate_chip_config.py")
arguments = chip_arguments(tmp_path)
arguments.io_profile = "both"
with pytest.raises(ValueError, match="IO profile"):
module.build_config(arguments)
@pytest.mark.parametrize("profile", ["default", "camera"])
def test_camera_make_scenario_isolated(profile: str) -> None:
result = subprocess.run([
"make", "--no-print-directory", "-n", "CONFIG=configs/ci/ihp130-tiny.mk",
"SOC=TINY", "PDK=IHP130", f"LIBRELANE_IO_PROFILE={profile}", "librelane-input",
], cwd=ROOT, capture_output=True, text=True, check=False)
assert result.returncode == 0, result.stdout + result.stderr
directory = "chip-camera" if profile == "camera" else "chip"
assert f"physical/librelane/tiny/{directory}/input/retrosoc_tiny_chip.sdc" in result.stdout
assert result.stdout.count(f"--io-profile {profile}") == 2
def test_tiny_flow_exposes_chip_target_only() -> None:
makefile = (FLOW_ROOT / "Makefile").read_text(encoding="utf-8")
top_makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
assert "librelane-chip:" in makefile
assert "librelane-core:" not in makefile
assert "--soc TINY" in makefile
assert "--source-filelist-dir" in makefile
assert re.search(r"ifeq \(\$\(SOC\),TINY\)\ninclude physical/librelane/tiny/Makefile", top_makefile)
assert "include physical/librelane/mini/Makefile" in top_makefile
doctor = (FLOW_ROOT / "scripts/doctor.py").read_text(encoding="utf-8")
assert "EXPECTED_SIGNAL_PADS = 52" in doctor
pad_cfg = (FLOW_ROOT / "pad_cfg.tcl").read_text(encoding="utf-8")
assert "vdd sg13g2_IOPadVdd 8" in pad_cfg
assert "iovss sg13g2_IOPadIOVss 8" in pad_cfg
pdn = (FLOW_ROOT / "pdn_cfg.tcl").read_text(encoding="utf-8")
assert "RM_IHPSG13_1P_1024x32_c2_bm_bist" in pdn
assert "RM_IHPSG13_1P_4096x16_c3_bm_bist" not in pdn