Files
retroSoC/tests/test_commercial_flow.py
T
maksyuki bc48cec74a feat(tiny): implement R2-P7 clock and reset lifecycle
Integrate the Tiny RCU, normalized REF24 control, external-input profiles,
single PLL backend, SYS/MEM/PCLK clocks, CPU-only WFI gating, peripheral
lifecycle control and retained debug behavior. Keep CPU and all 32 main
SRAM macros on SYS and the six private Crypto macros on PCLK.

Add the handwritten RCU register/HAL contract, RS_EBUSY, bounded SDK
operations and focused firmware. Preserve shared-IP defaults and the
ICS55/PLL SAFE24 boot and explicit IHP130/no-PLL compatibility profiles.

Use APB/AXI domain bridges and source-owned response holders to preserve
backpressured responses, complete aborted bursts and prevent stale replay.
Verify production DMA source-reset drain and common XPI endpoint reset
with extracted integration bindings and an independent scoreboard.

Add source-bound macro/clock audits, SAFE24 observational STA constraints
and commercial-adapter host checks. No P8 functionality or operating-point
qualification is included. See TINY-R2-P7 in docs/ip/tiny-soc.md.

Validation: 54 P7 directed tests pass, including both simulators, ten
source-reset ratio runs and two expected-negative drain mutations.
RTL format/style, clock/reset inventory, Ruff and both Tiny PDK PR dry-runs
pass. Retained 06-00 synthesis and strict netlist boots pass for both PDKs;
STA remains negative and observational. Source comparison confirms only
the follow-up tests changed after that backend campaign.

Evidence: build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/
tiny-r2-p7-source-reset/summary.json. Earlier firmware, HAL and compatibility
results and the non-clean full-suite attempt retain their original
attribution. Full Icarus firmware is deferred to final Tiny qualification;
this commit does not constitute human phase acceptance or physical signoff.
2026-10-14 16:56:01 +08:00

751 lines
25 KiB
Python

# Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
# retroSoC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
from __future__ import annotations
import importlib.util
import io
import os
import shutil
import subprocess
import sys
import tarfile
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
FLOW = ROOT / "physical/commercial"
def load_script(name: str):
path = FLOW / "scripts" / f"{name}.py"
spec = importlib.util.spec_from_file_location(f"commercial_{name}", path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_translate_eco_accepts_only_reviewed_operations(tmp_path: Path) -> None:
translator = load_script("translate_eco")
supported = tmp_path / "supported.tcl"
supported.write_text(
"current_instance {u_core}\n"
"size_cell {u_reg} {DFFQX2H7R}\n"
"insert_buffer [get_pins {u_src/Q}] BUFX2H7R "
"-new_net_names {eco_net} -new_cell_names {eco_buf}\n",
encoding="utf-8",
)
assert translator.translate([str(supported)]) == [
"ecoChangeCell -inst {u_core/u_reg} -cell {DFFQX2H7R}",
"ecoAddRepeater -term {u_core/u_src/Q} -cell {BUFX2H7R} "
"-newNetName {eco_net} -name {eco_buf}",
]
unsupported = tmp_path / "unsupported.tcl"
unsupported.write_text("disconnect_net old [get_pins u/A]\n", encoding="utf-8")
with pytest.raises(ValueError, match="unsupported PrimeTime ECO command"):
translator.translate([str(unsupported)])
def test_translate_eco_merges_scenarios_and_drops_duplicates(tmp_path: Path) -> None:
translator = load_script("translate_eco")
first = tmp_path / "func_TYP_TYP_25.icc2.tcl"
first.write_text(
"current_instance {u_core}\n"
"size_cell {u_reg} {DFFQX2H7R}\n"
"insert_buffer [get_pins {u_src/Q}] BUFX2H7R "
"-new_net_names {eco_net} -new_cell_names {eco_buf}\n",
encoding="utf-8",
)
second = tmp_path / "func_MIN_Cworst_m40.icc2.tcl"
second.write_text(
"current_instance {u_core}\n"
"size_cell {u_reg} {DFFQX2H7R}\n"
"size_cell {u_hold} {DFFQX1H7R}\n",
encoding="utf-8",
)
assert translator.translate([str(first), str(second)]) == [
"ecoChangeCell -inst {u_core/u_reg} -cell {DFFQX2H7R}",
"ecoAddRepeater -term {u_core/u_src/Q} -cell {BUFX2H7R} "
"-newNetName {eco_net} -name {eco_buf}",
"ecoChangeCell -inst {u_core/u_hold} -cell {DFFQX1H7R}",
]
def test_translate_eco_supports_removal_and_placed_insert(tmp_path: Path) -> None:
translator = load_script("translate_eco")
source = tmp_path / "changes.icc2.tcl"
source.write_text(
"current_instance {u_core}\n"
"remove_buffer [get_cells {u_buf}]\n"
"insert_buffer [get_pins {u_src/Q}] BUFX2H7R -new_net_names {eco_net} "
"-new_cell_names {eco_buf} -location {10.5 20.25}\n",
encoding="utf-8",
)
assert translator.translate([str(source)]) == [
"ecoDeleteRepeater -inst {u_core/u_buf}",
"ecoAddRepeater -term {u_core/u_src/Q} -cell {BUFX2H7R} "
"-newNetName {eco_net} -name {eco_buf} -loc {10.5 20.25}",
]
ambiguous = tmp_path / "ambiguous.icc2.tcl"
ambiguous.write_text("remove_buffer [get_cells {u_a u_b}]\n", encoding="utf-8")
with pytest.raises(ValueError, match="unsupported PrimeTime ECO command"):
translator.translate([str(ambiguous)])
def test_spef_checker_requires_every_named_corner(tmp_path: Path) -> None:
checker = load_script("check_outputs")
for corner in checker.spef_corners():
(tmp_path / f"retrosoc_asic.{corner}.spef.gz").write_bytes(b"spef")
assert checker.apply_check("spef", str(tmp_path), 0, "retrosoc_asic") is None
(tmp_path / "retrosoc_asic.TYP_25.spef.gz").unlink()
assert "missing SPEF corners" in checker.apply_check(
"spef", str(tmp_path), 0, "retrosoc_asic"
)
def test_calibre_checkers_accept_actual_clean_report_format(tmp_path: Path) -> None:
checker = load_script("check_outputs")
(tmp_path / "retrosoc_asic.drc.summary").write_text(
"TOTAL DRC Results Generated: 0 (0)\n",
encoding="utf-8",
)
assert checker.apply_check("calibre-drc", str(tmp_path), 0) is None
(tmp_path / "retrosoc_asic.drc.summary").unlink()
(tmp_path / "retrosoc_asic.lvs.rpt").write_text(
"OVERALL COMPARISON RESULTS\n\n"
" ###################\n"
" # CORRECT #\n"
" ###################\n",
encoding="utf-8",
)
assert checker.apply_check("calibre-lvs", str(tmp_path), 0) is None
def test_prepare_input_rejects_archive_escape(tmp_path: Path) -> None:
archive_path = tmp_path / "escape.tar"
payload = tmp_path / "payload"
payload.write_text("not allowed\n", encoding="utf-8")
with tarfile.open(archive_path, "w") as archive:
archive.add(payload, arcname="../escape")
output = tmp_path / "output"
result = subprocess.run(
[
sys.executable,
str(FLOW / "scripts/prepare_input.py"),
"--archive",
str(archive_path),
"--output-dir",
str(output),
"--manifest",
str(tmp_path / "manifest.json"),
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode != 0
assert "escapes destination" in result.stderr
def test_prepare_input_refreshes_archived_filelist(tmp_path: Path) -> None:
archive_path = tmp_path / "rtl.tar"
payload = b"rtl/top.sv\n"
contract = b"# contract\n"
with tarfile.open(archive_path, "w") as archive:
member = tarfile.TarInfo("rtl/filelist.fl")
member.size = len(payload)
member.mtime = 1
archive.addfile(member, io.BytesIO(payload))
member = tarfile.TarInfo("rtl/contracts/commercial_timing_contract.tcl")
member.size = len(contract)
member.mtime = 1
archive.addfile(member, io.BytesIO(contract))
output = tmp_path / "output"
manifest = tmp_path / "manifest.json"
started = archive_path.stat().st_mtime
subprocess.run(
[
sys.executable,
str(FLOW / "scripts/prepare_input.py"),
"--archive",
str(archive_path),
"--output-dir",
str(output),
"--manifest",
str(manifest),
],
check=True,
)
assert (output / "rtl/filelist.fl").stat().st_mtime >= started
def test_stage_runner_forwards_environment_and_requires_outputs(tmp_path: Path) -> None:
expected = tmp_path / "expected"
stamp = tmp_path / "stage.stamp"
command = [
sys.executable,
str(FLOW / "scripts/run_stage.py"),
"--stage",
"unit",
"--cwd",
str(tmp_path),
"--log",
str(tmp_path / "stage.log"),
"--result",
str(tmp_path / "result.json"),
"--stamp",
str(stamp),
"--expect",
str(expected),
"--",
"COMMERCIAL_TEST_VALUE=forwarded",
sys.executable,
"-c",
(
"import os, pathlib; "
"pathlib.Path(r'{0}').write_text(os.environ['COMMERCIAL_TEST_VALUE'])"
).format(expected),
]
subprocess.run(command, check=True)
assert expected.read_text(encoding="utf-8") == "forwarded"
assert stamp.is_file()
expected.unlink()
failed = subprocess.run(command[:-3] + [sys.executable, "-c", "pass"], check=False)
assert failed.returncode != 0
assert not stamp.exists()
def test_lsf_submission_modes_preserve_blocking_and_logs(tmp_path: Path) -> None:
submitter = load_script("submit_job")
tool = ["dc_shell", "-64", "-f", "main.tcl"]
tool_log = str(tmp_path / "tool.log")
batch = submitter.build_submission(
"batch", "bsub", '-q normal -R "span[hosts=1]"', tool_log, tool
)
assert batch[:4] == ["bsub", "-K", "-q", "normal"]
assert batch[-len(tool) :] == tool
assert batch.count(tool_log) == 2
interactive = submitter.build_submission(
"interactive", "bsub", '-q m-q -R "span[hosts=1]"', tool_log, tool
)
assert interactive[:4] == ["bsub", "-I", "-q", "m-q"]
assert "-K" not in interactive
assert "-oo" not in interactive
assert interactive[-len(tool) :] == tool
assert "run_logged.py" in " ".join(interactive)
def test_remote_logger_propagates_output_and_status(tmp_path: Path) -> None:
log = tmp_path / "tool.log"
result = subprocess.run(
[
sys.executable,
str(FLOW / "scripts/run_logged.py"),
"--log",
str(log),
"--",
sys.executable,
"-c",
"import sys; print('tool output'); sys.exit(7)",
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode == 7
assert "tool output" in result.stdout
assert "tool output" in log.read_text(encoding="utf-8")
def test_pt_runner_consumes_every_common_scenario(tmp_path: Path) -> None:
fake_pt = tmp_path / "fake_pt.py"
fake_pt.write_text(
"import os\n"
"from pathlib import Path\n"
"base = Path(os.environ['RUN_ROOT']) / 'sta' / os.environ['STA_TAG']\n"
"scenario = os.environ['STA_SCENARIO']\n"
"(base / 'output').mkdir(parents=True, exist_ok=True)\n"
"(base / 'output' / (scenario + '.pass')).write_text('PASS\\n')\n"
"(base / 'output' / (scenario + '.summary.tsv')).write_text(\n"
" scenario + '\\t0\\t0\\t0\\n')\n"
"(base / 'work' / 'sessions' / scenario).mkdir(parents=True)\n",
encoding="utf-8",
)
run_root = tmp_path / "run"
for name in ("work", "log", "reports", "output"):
(run_root / "sta/route" / name).mkdir(parents=True)
environment = dict(**os.environ, RUN_ROOT=str(run_root))
subprocess.run(
[
sys.executable,
str(FLOW / "scripts/run_pt_scenarios.py"),
"--pt-command",
f"{sys.executable} {fake_pt}",
"--tcl-command",
"tclsh",
"--scenario-script",
str(FLOW / "tcl/common/list_scenarios.tcl"),
"--main",
str(FLOW / "tcl/sta/main.tcl"),
"--tag",
"route",
],
check=True,
env=environment,
)
summary = (run_root / "sta/route/output/summary.tsv").read_text(
encoding="utf-8"
)
assert len(summary.splitlines()) == 14
assert (run_root / "sta/route/output/verdict.pass").is_file()
def test_boundary_audit_scans_untracked_candidates(tmp_path: Path) -> None:
audit = load_script("audit_boundary")
tracked = tmp_path / "physical/commercial/config/example.mk"
tracked.parent.mkdir(parents=True)
tracked.write_text("LIB := /site/lib/example.db\n", encoding="utf-8")
errors = audit.violations(tmp_path, [tracked])
assert any("site-specific absolute path" in error for error in errors)
def test_commercial_makefile_is_posix_and_make_382_compatible() -> None:
makefile = (FLOW / "Makefile").read_text(encoding="utf-8")
assert "SHELL := /bin/sh" in makefile
assert "{work," not in makefile
assert ".ONESHELL" not in makefile
assert "$(file " not in makefile
def test_dc_analyze_receives_one_source_list_argument() -> None:
synthesis = (FLOW / "tcl/syn/main.tcl").read_text(encoding="utf-8")
assert "lappend analyze_command [dict get $rtl sources]" in synthesis
assert "concat $analyze_command [dict get $rtl sources]" not in synthesis
def test_dc_check_timing_uses_supported_options() -> None:
synthesis = (FLOW / "tcl/syn/main.tcl").read_text(encoding="utf-8")
assert synthesis.count("check_timing\n") == 2
assert "check_timing -verbose" not in synthesis
assert synthesis.count(
"check_timing -include {unconstrained_endpoints}"
) == 2
assert "no_clock" not in synthesis
def test_commercial_timing_contract_covers_canonical_domains(tmp_path: Path) -> None:
output = tmp_path / "commercial_timing_contract.tcl"
subprocess.run(
[
sys.executable,
str(FLOW / "scripts/generate_timing_contract.py"),
"--soc",
"MINI",
"--domains",
str(ROOT / "rtl/mini/integration/clock_reset_domains.json"),
"--pin-map",
str(ROOT / "rtl/mini/pin_map/pin_map.json"),
"--output",
str(output),
],
check=True,
)
script = (
"namespace eval flow {}\n"
f"source {{{output}}}\n"
"puts [join [lsort [dict keys $flow::canonical_clock_domains]] ,]\n"
"puts [join $flow::canonical_reset_ports ,]\n"
)
result = subprocess.run(
["tclsh"],
input=script,
text=True,
capture_output=True,
check=True,
)
assert result.stdout.splitlines() == [
"aon,audio,dvp,hp,jtag,lp,memory,pclk,usb2_ulpi",
"ext_rst_n_i_pad,jtag_trst_n_i_pad",
]
assert "u_retrosoc/u_apb4_periph/u_axi4_dvp" in output.read_text(
encoding="utf-8"
)
assert "object_type {net_driver}" in output.read_text(encoding="utf-8")
assert "observation {s_sys_clk}" in output.read_text(encoding="utf-8")
def test_commercial_timing_contract_supports_tiny(tmp_path: Path) -> None:
output = tmp_path / "commercial_timing_contract.tcl"
subprocess.run(
[
sys.executable,
str(FLOW / "scripts/generate_timing_contract.py"),
"--soc",
"TINY",
"--domains",
str(ROOT / "rtl/tiny/integration/clock_reset_domains.json"),
"--pin-map",
str(ROOT / "rtl/tiny/pin_map/pin_map.json"),
"--output",
str(output),
],
check=True,
)
script = (
"namespace eval flow {}\n"
f"source {{{output}}}\n"
"puts [join [lsort [dict keys $flow::canonical_clock_domains]] ,]\n"
"puts [join $flow::canonical_reset_ports ,]\n"
)
result = subprocess.run(
["tclsh"],
input=script,
text=True,
capture_output=True,
check=True,
)
assert result.stdout.splitlines() == [
"aon,audio,jtag,memory,peripheral,system",
"ext_rst_n_i_pad,jtag_trst_n_i_pad",
]
assert "observation {u_clock_buffer/clk_o}" in output.read_text(
encoding="utf-8"
)
def test_commercial_timing_contract_rejects_domain_drift(tmp_path: Path) -> None:
result = subprocess.run(
[
sys.executable,
str(FLOW / "scripts/generate_timing_contract.py"),
"--soc",
"TINY",
"--domains",
str(ROOT / "rtl/mini/integration/clock_reset_domains.json"),
"--pin-map",
str(ROOT / "rtl/mini/pin_map/pin_map.json"),
"--output",
str(tmp_path / "drift.tcl"),
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode != 0
assert "differ from the canonical set" in result.stderr
def test_commercial_constraints_do_not_apply_global_io_delays() -> None:
constraints = "\n".join(
path.read_text(encoding="utf-8")
for path in (FLOW / "tcl/common").glob("*constraints.tcl")
)
assert "set_input_delay" in constraints
assert "set_output_delay" in constraints
assert "set_input_delay" + " -clock clk_external" not in constraints
assert "set_output_delay" + " -clock clk_external" not in constraints
assert "set_input_delay" + " -clock $clock" in constraints
assert "set_output_delay" + " -clock $clock" in constraints
def test_dc_uses_typ_link_set_and_svt_lvt_target_subset() -> None:
synthesis = (FLOW / "tcl/syn/main.tcl").read_text(encoding="utf-8")
common = (FLOW / "tcl/common/common.tcl").read_text(encoding="utf-8")
example = (FLOW / "config/ics55-mini.example.mk").read_text(encoding="utf-8")
assert "flow::synthesis_library_files" in synthesis
assert "flow::all_library_files" not in synthesis
assert "return [flow::library_files TYP]" in common
assert "SYN_STD_DB_TYP" in example
assert "H7CR (SVT) and H7CL (LVT)" in example
def test_synthesis_summary_separates_flow_and_qor_status() -> None:
reporting = (FLOW / "tcl/syn/reporting.tcl").read_text(encoding="utf-8")
for metric in (
"flow_pass",
"constraints_complete",
"io_qualified",
"timing_met",
"drv_met",
):
assert f'"{metric}\\t' in reporting
assert "lvt_area_percent" not in reporting
assert "[string tolower $group]_area_percent" in reporting
def test_doctor_normalizes_h7c_nldm_liberty_names() -> None:
doctor = load_script("doctor")
db = "/local/ics55_LLSC_H7CR_typ_tt_1p2_25.db"
lib = "/local/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib"
assert doctor.normalized_library_stem(db) == doctor.normalized_library_stem(lib)
assert doctor.h7c_variants([db, lib]) == {"H7CR"}
def test_internal_qor_doctor_does_not_require_backend_collateral(
tmp_path: Path,
) -> None:
def touch(name: str) -> str:
path = tmp_path / name
path.write_bytes(b"view")
return str(path)
h7ch = touch("ics55_LLSC_H7CH_typ_tt_1p2_25.db")
h7cl = touch("ics55_LLSC_H7CL_typ_tt_1p2_25.db")
h7cr = touch("ics55_LLSC_H7CR_typ_tt_1p2_25.db")
archive = touch("rtl.tar.gz")
environment = {
"PATH": os.environ["PATH"],
"SOC": "MINI",
"TOP": "retrosoc_asic",
"TECHNOLOGY": "ICS55",
"PRODUCT_PLL_MODE": "qualified",
"RTL_ARCHIVE": archive,
"LSF_MODE": "batch",
"LSF_SYN_ARGS": "-q synth",
"LSF_FM_ARGS": "-q formal",
"SYN_DONT_USE": "none",
"SYN_OPERATING_CONDITION_LIBRARY": (
"ics55_LLSC_H7CR_typ_tt_1p2_25"
),
"SYN_OPERATING_CONDITION": "typical",
"STD_DB_TYP": f"{h7ch} {h7cl} {h7cr}",
"SYN_STD_DB_TYP": f"{h7cl} {h7cr}",
"IO_DB_TYP": touch("io_typ.db"),
"SRAM_DB_TYP": touch("sram_typ.db"),
"PLL_DB": touch("pll_typ.db"),
"ICS55_PLL_SUPPORTED_SEL": "0",
"ICS55_PLL_N": "2",
"ICS55_PLL_OD": "2",
"IO_TIMING_QUALIFIED": "NO",
}
result = subprocess.run(
[
sys.executable,
str(FLOW / "scripts/doctor.py"),
"--dev",
"--allow-internal-qor",
"--output",
str(tmp_path / "doctor.json"),
],
env=environment,
text=True,
capture_output=True,
check=False,
)
assert result.returncode == 0, result.stderr
def test_ics55_pll_wrapper(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
pytest.skip("iverilog and vvp are not installed")
output = tmp_path / "tc_pll"
subprocess.run(
[
iverilog,
"-g2012",
"-DPDK_ICS55",
"-DHAVE_PLL",
"-s",
"tc_pll_ics55_tb",
"-o",
str(output),
str(ROOT / "rtl/tech/tc_pll.sv"),
str(ROOT / "tests/rtl/tc_pll_ics55_tb.sv"),
],
check=True,
cwd=ROOT,
)
result = subprocess.run(
[vvp, str(output)],
text=True,
capture_output=True,
check=True,
cwd=ROOT,
)
assert "TC_PLL_ICS55_PASS" in result.stdout
def test_local_production_configuration_is_ignored() -> None:
for name in ("ics55-production.mk", "ics55-mini.mk", "ics55-tiny.mk"):
local = FLOW / "local" / name
result = subprocess.run(
["git", "check-ignore", str(local.relative_to(ROOT))],
cwd=ROOT,
check=False,
)
assert result.returncode == 0
CONSTRAINT_SMOKE_TCL = r"""
set soc [lindex $argv 0]
set ::env(SOC) $soc
set ::env(TOP) [expr {$soc eq "TINY" ? "retrosoc_tiny_asic" : "retrosoc_asic"}]
set ::env(RUN_ROOT) [lindex $argv 1]
set ::env(CLOCK_SETUP_UNCERTAINTY_NS) 0.2
set ::env(CLOCK_HOLD_UNCERTAINTY_NS) 0.1
set ::env(CLOCK_TRANSITION_NS) 0.1
set ::env(IO_TIMING_QUALIFIED) NO
set ::env(MAX_TRANSITION_NS) 0.5
set ::env(MAX_FANOUT) 32
if {$soc eq "MINI"} { set ::env(PLL_OUTPUT_PERIOD_NS) 13.888888889 }
set ::created_clocks_named {}
set ::generated_clocks {}
set ::clock_groups {}
set ::false_paths 0
set ::case_selections {}
proc get_ports {args} { return [lindex $args end] }
proc get_pins {args} { return [list pin_[string map {/ _} [lindex $args end]]] }
proc get_nets {args} { return [list net_[lindex $args end]] }
proc filter_collection {args} { return [list driver] }
proc sizeof_collection {c} { return [llength $c] }
proc get_clocks {args} { return [lindex $args end] }
proc create_clock {args} {
lappend ::created_clocks_named [lindex $args [expr {[lsearch $args -name] + 1}]]
}
proc create_generated_clock {args} {
lappend ::generated_clocks [lindex $args [expr {[lsearch $args -name] + 1}]]
}
proc set_clock_groups {args} { lappend ::clock_groups $args }
proc set_clock_uncertainty {args} {}
proc set_clock_transition {args} {}
proc set_false_path {args} { incr ::false_paths }
proc set_case_analysis {value pins} { lappend ::case_selections [list $value $pins] }
proc all_inputs {} { return {} }
proc all_outputs {} { return {} }
proc current_design {args} { return {} }
proc set_max_transition {args} {}
proc set_max_fanout {args} {}
source [file join $::env(FLOW_ROOT) tcl common common.tcl]
source [file join $::env(FLOW_ROOT) tcl common constraints.tcl]
flow::apply_constraints
puts "masters=$::created_clocks_named"
puts "generated=$::generated_clocks"
puts "groups=[llength $::clock_groups]"
puts "false_paths=$::false_paths"
puts "case_selections=$::case_selections"
flow::require_commercial_clock_inventory
puts "inventory-ok"
"""
CONSTRAINT_SMOKE_CASES = {
"MINI": {
"domains": "rtl/mini/integration/clock_reset_domains.json",
"pin_map": "rtl/mini/pin_map/pin_map.json",
"masters": (
"clk_aon clk_hp clk_memory clk_audio clk_jtag clk_dvp "
"clk_usb2_ulpi clk_pll"
),
"generated": "clk_lp_ext clk_lp_pll clk_pclk_ext clk_pclk_pll",
"groups": "2",
},
"TINY": {
"domains": "rtl/tiny/integration/clock_reset_domains.json",
"pin_map": "rtl/tiny/pin_map/pin_map.json",
"masters": "clk_aon clk_jtag clk_audio",
"generated": "clk_system clk_memory clk_peripheral clk_sys_div2 clk_sys_div4 clk_clkout_source",
"groups": "1",
},
}
@pytest.mark.parametrize("soc", sorted(CONSTRAINT_SMOKE_CASES))
def test_constraint_stack_models_product_clocks(tmp_path: Path, soc: str) -> None:
case = CONSTRAINT_SMOKE_CASES[soc]
run_root = tmp_path / soc.lower()
contract = run_root / "input/rtl/contracts/commercial_timing_contract.tcl"
contract.parent.mkdir(parents=True)
subprocess.run(
[
sys.executable,
str(FLOW / "scripts/generate_timing_contract.py"),
"--soc",
soc,
"--domains",
str(ROOT / case["domains"]),
"--pin-map",
str(ROOT / case["pin_map"]),
"--output",
str(contract),
],
check=True,
)
environment = dict(os.environ)
environment["FLOW_ROOT"] = str(FLOW)
script = CONSTRAINT_SMOKE_TCL.replace(
"set soc [lindex $argv 0]", f"set soc {soc}"
).replace(
"set ::env(RUN_ROOT) [lindex $argv 1]", f"set ::env(RUN_ROOT) {run_root}"
)
result = subprocess.run(
["tclsh"],
input=script,
text=True,
capture_output=True,
check=False,
env=environment,
)
assert result.returncode == 0, result.stderr
lines = dict(
line.split("=", 1)
for line in result.stdout.strip().splitlines()
if "=" in line
)
assert lines["masters"] == case["masters"]
assert lines["generated"].strip("-") == case["generated"]
assert lines["groups"] == case["groups"]
assert "inventory-ok" in result.stdout
if soc == "TINY":
for selection in ("u_sys_mux", "u_mem_mux", "u_pclk_div2_mux", "u_pclk_mux", "u_PLL_TOP_EN"):
assert selection in lines["case_selections"]
assert lines["case_selections"].count("{0 ") == 11
assert lines["case_selections"].count("{1 ") == 4
def test_commercial_make_graph_covers_both_products(tmp_path: Path) -> None:
stub = tmp_path / "local.mk"
stub.write_text("", encoding="utf-8")
for soc in ("MINI", "TINY"):
result = subprocess.run(
[
"make",
"-C",
str(FLOW),
"-n",
"signoff",
f"SOC={soc}",
f"LOCAL_CONFIG={stub}",
"RUN_ID=pytest",
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode == 0, result.stderr
assert f"build/commercial/ics55/{soc.lower()}/pytest" in result.stdout
for stage in ("syn", "apr-route", "extract", "sta", "pv-lvs", "pv-macro-lvs"):
assert f"--stage {stage} " in result.stdout or f"--stage {stage}\n" in (
result.stdout + "\n"
)