Files
retroSoC/tests/test_ics55_pll_dependency.py
T
maksyuki ecac3559b0 docs: refactor Tiny SoC documentation and integrate ICS55 PLL support
- Updated tiny-soc.md to reflect the 2026-10-07 refreeze, establishing ICS55 as the default target PDK with HAVE_PLL=YES.
- Added detailed sections on final timing gate policies, PLL integration, and execution order for Tiny SoC.
- Modified soc-family-positioning.md to indicate the new default target for Tiny as ICS55/PLL while maintaining IHP130 compatibility.
- Enhanced pdk/README.md and setup.py to support the integration of the locked ICS55 PLL views, including setup commands and validation.
- Introduced tests for the ICS55 PLL dependency to ensure proper integration and validation of PLL views.
- Updated rtl/tiny/README.md to clarify the current executable baseline and the pending integration of ICS55.
2026-10-07 19:24:55 +08:00

142 lines
5.6 KiB
Python

"""Locked PLL acquisition is independent of product integration and other PDK setup."""
from __future__ import annotations
import hashlib
import subprocess
import sys
from pathlib import Path
import pytest
from physical.pdk import setup
from scripts import setup_helpers
from scripts.dependency_lock import source
def git(directory: Path, *args: str) -> str:
return subprocess.check_output(["git", "-C", str(directory), *args], text=True).strip()
def commit(directory: Path) -> str:
git(directory, "add", ".")
git(directory, "-c", "commit.gpgsign=false", "-c", "core.hooksPath=/dev/null",
"-c", "user.name=PLL fixture", "-c", "user.email=fixture@example.invalid",
"commit", "-qm", "PLL fixture")
return git(directory, "rev-parse", "HEAD")
@pytest.fixture
def locked_pll(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, dict]:
upstream = tmp_path / "upstream"
upstream.mkdir()
git(upstream, "init", "-q")
for name in setup.PLL_VIEWS:
path = upstream / name
path.parent.mkdir(parents=True, exist_ok=True)
if path.suffix == ".v":
content = "module PLL_TOP (input REFCLK, output CKOUT1); endmodule\n"
elif path.suffix == ".lef":
content = "MACRO PLL_TOP\nEND PLL_TOP\n"
elif path.suffix == ".lib":
content = "library (fixture) { cell (PLL_TOP) {} }\n"
else:
content = "PLL integration fixture; no timing qualification.\n"
path.write_text(content)
dependency = {"url": str(upstream), "revision": commit(upstream),
"destination": "cache/pll"}
monkeypatch.setattr(setup, "ROOT", tmp_path)
def selected(name: str) -> dict:
assert name == "pdk_ics55_pll", "PLL-only setup touched another dependency"
return dependency
monkeypatch.setattr(setup, "source", selected)
return upstream, dependency
def test_selected_pin_is_a_full_revision_outside_replaceable_liberty_cache() -> None:
dependency = source("pdk_ics55_pll")
assert dependency["revision"] == "6ebb1a8f7f4ccbccdb7f587664fdfe63cd39e61b"
assert dependency["url"] == "https://github.com/openecos-projects/ics55_ecos_pll.git"
assert dependency["destination"] == ".cache/retrosoc/sources/ics55_ecos_pll"
assert dependency["license"] == "NOASSERTION"
def test_restore_records_hashes_and_reuses_clean_checkout_without_fetch(
locked_pll, monkeypatch: pytest.MonkeyPatch
) -> None:
upstream, dependency = locked_pll
first = setup.setup_ics55_pll()
destination = Path(first["destination"])
assert git(destination, "rev-parse", "HEAD") == dependency["revision"]
assert git(destination, "status", "--porcelain") == ""
assert first["sha256"] == {
name: hashlib.sha256((upstream / name).read_bytes()).hexdigest()
for name in setup.PLL_VIEWS
}
monkeypatch.setattr(setup_helpers, "run", lambda *_args, **_kwargs: pytest.fail("unexpected mutation"))
assert setup.setup_ics55_pll() == first
def test_dirty_checkout_is_preserved(locked_pll) -> None:
record = setup.setup_ics55_pll()
readme = Path(record["destination"]) / "README.md"
readme.write_text("user changes\n")
with pytest.raises(RuntimeError, match="local changes"):
setup.setup_ics55_pll(update=True)
assert readme.read_text() == "user changes\n"
def test_wrong_revision_requires_update_and_dirty_update_is_rejected(locked_pll) -> None:
upstream, dependency = locked_pll
record = setup.setup_ics55_pll()
destination = Path(record["destination"])
previous = dependency["revision"]
(upstream / "README.md").write_text("next reviewed revision\n")
dependency["revision"] = commit(upstream)
with pytest.raises(RuntimeError, match="rerun with --update"):
setup.setup_ics55_pll()
assert git(destination, "rev-parse", "HEAD") == previous
(destination / "README.md").write_text("local change\n")
with pytest.raises(RuntimeError, match="dirty dependency"):
setup.setup_ics55_pll(update=True)
assert git(destination, "rev-parse", "HEAD") == previous
@pytest.mark.parametrize("missing", setup.PLL_VIEWS)
def test_missing_view_cannot_be_accepted(locked_pll, missing: str) -> None:
upstream, dependency = locked_pll
(upstream / missing).unlink()
dependency["revision"] = commit(upstream)
with pytest.raises(ValueError, match="missing or empty"):
setup.setup_ics55_pll()
@pytest.mark.parametrize("view", ["verilog/PLL_TOP.blackbox.v", "lef/PLL_TOP.lef", "lib/PLL_TOP_typ.lib"])
def test_wrong_cell_identity_is_rejected(locked_pll, view: str) -> None:
upstream, dependency = locked_pll
path = upstream / view
path.write_text(path.read_text().replace("PLL_TOP", "OTHER_CELL"))
dependency["revision"] = commit(upstream)
with pytest.raises(ValueError, match="does not define PLL_TOP"):
setup.setup_ics55_pll()
@pytest.mark.parametrize("pdks", [[], ["IHP130"], ["ICS55", "IHP130"], ["ICS55", "ICS55"]])
def test_pll_component_rejects_ambiguous_or_wrong_pdk(pdks, monkeypatch) -> None:
argv = ["setup.py", "--component", "pll"]
for pdk in pdks:
argv += ["--pdk", pdk]
monkeypatch.setattr(sys, "argv", argv)
monkeypatch.setattr(setup, "setup_ics55_pll", lambda **_: pytest.fail("invalid selection installed PLL"))
with pytest.raises(SystemExit) as error:
setup.main()
assert error.value.code == 2
def test_pll_only_does_not_prepare_other_inputs(locked_pll, monkeypatch) -> None:
monkeypatch.setattr(sys, "argv", ["setup.py", "--pdk", "ICS55", "--component", "pll"])
monkeypatch.setattr(setup, "archive", lambda *_: pytest.fail("PLL-only setup requested an archive"))
assert setup.main() == 0