Files
retroSoC/tests/test_tiny.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

191 lines
9.4 KiB
Python

"""Tiny product isolation, generated ABI and native bus acceptance."""
from __future__ import annotations
import json
import os
import shutil
import subprocess
from pathlib import Path
import pytest
from scripts.generate_tiny import generate_bindings, generate_filelists, read_topology
from scripts.rtl.filelist import parse_filelists
from scripts.rtl.generate_memory_map import generate as generate_memory_map
ROOT = Path(__file__).resolve().parents[1]
TINY = ROOT / "rtl/tiny"
COMMON = ROOT / "rtl/managed/clusterip/common/rtl"
def run(command: list[str], **kwargs) -> subprocess.CompletedProcess:
result = subprocess.run(command, text=True, capture_output=True, **kwargs)
assert result.returncode == 0, result.stdout + result.stderr
return result
def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None:
output = tmp_path / "generated"
filelists = tmp_path / "filelists"
document = read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json")
generate_bindings(document, output)
interfaces = (output / "rtl/tiny_apb_interfaces.svh").read_text()
for index, target in enumerate(document["apb_targets"], start=1):
name = target["name"]
assert f".presetn(s_{name}_rst_n)" in interfaces
if name in {"gpio", "uart0", "i2c0", "timer0", "timer1", "pwm", "i2s", "sdio",
"dma", "ws2812", "rng", "crc", "crypto"}:
assert f"tiny_peripheral_leaf u_{name}_lifecycle" in interfaces
assert f".rst_n_i(s_leaf_rst_n[{index}])" in interfaces
assert f".rst_n_o(s_{name}_rst_n)" in interfaces
else:
assert f"assign s_{name}_rst_n = s_leaf_rst_n[{index}];" in interfaces
generate_filelists(filelists, output, ["+define+RETROSOC_SOC__TINY"])
generate_memory_map(TINY / "address_map/memory_map.json", output / "memory_map", "YES", 128)
resolved = parse_filelists(filelists / name for name in
("commonip.fl", "clusterip.fl", "core_hazard3.fl", "ip.fl", "top.fl"))
for source in resolved.files:
assert "/rtl/mini/" not in str(source)
assert not any(token in source.name.lower() for token in ("rib", "vexii", "radio"))
rtl = (output / "memory_map/rtl/mmap_define.svh").read_text()
sdk = (output / "memory_map/include/retrosoc/generated/memory_map.h").read_text()
assert "RIB" not in rtl
assert "SOC_ADDR_SRAM_END 32'h3001FFFF" in rtl
assert "RS_SOC_SRAM_SIZE UINT32_C(0x00020000)" in sdk
assert "RS_SOC_PSRAM_BASE" not in sdk
irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text()
assert "RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)" in irq
assert "RS_SOC_EXT_IRQ_UART1" not in irq
assert "RS_SOC_EXT_IRQ_I2C1" not in irq
assert "RS_SOC_EXT_IRQ_SDIO UINT32_C(8)" in irq
assert "RS_SOC_EXT_IRQ_DMA UINT32_C(18)" in irq
assert "RS_SOC_EXT_IRQ_NPU" not in irq
# A consumer of public SDK headers must see Tiny capabilities without
# reconstructing the firmware build's private preprocessor command line.
if shutil.which("cc"):
probe = tmp_path / "consumer.c"
probe.write_text('#include <retrosoc/hal/dma.h>\n'
'_Static_assert(RS_DMA_CHANNEL_COUNT == 8, "wrong product");\n')
run(["cc", "-std=c11", "-Werror", "-I" + str(ROOT / "crt/include"),
"-I" + str(output / "memory_map/include"), "-I" + str(output / "include"),
"-c", str(probe), "-o", str(tmp_path / "consumer.o")])
def test_tiny_topology_rejects_duplicate_irqs_and_missing_routes(tmp_path: Path) -> None:
source = json.loads((TINY / "integration/soc_topology.json").read_text())
path = tmp_path / "invalid.json"
source["interrupts"][1]["core_bit"] = source["interrupts"][0]["core_bit"]
path.write_text(json.dumps(source))
with pytest.raises(ValueError, match="duplicate"):
read_topology(path, TINY / "address_map/memory_map.json")
source = json.loads((TINY / "integration/soc_topology.json").read_text())
source["apb_targets"].pop()
path.write_text(json.dumps(source))
with pytest.raises(ValueError, match="every APB"):
read_topology(path, TINY / "address_map/memory_map.json")
@pytest.mark.parametrize("override", ["HAVE_HP=YES", "PDK=GF180", "MINI_MODE=PRODUCT",
"LINK_TYPE=ld2_psram", "APP=hp_boot", "HAVE_PLL=YES"])
def test_tiny_rejects_incompatible_profiles(override: str) -> None:
result = subprocess.run(
["make", "CONFIG=configs/ci/ihp130-tiny.mk", override, "config"],
cwd=ROOT, text=True, capture_output=True,
)
assert result.returncode != 0
assert "TINY" in result.stderr
@pytest.mark.parametrize("simulator", ["verilator", "iverilog"])
@pytest.mark.parametrize("block", ["fabric", "sysctrl"])
def test_tiny_protocol_contracts(tmp_path: Path, simulator: str, block: str) -> None:
if not shutil.which(simulator) or not (COMMON / "interface/axi4_if.sv").exists():
pytest.skip("locked RTL/tool dependency unavailable")
generated = tmp_path / "map"
generate_memory_map(TINY / "address_map/memory_map.json", generated, "YES", 128)
generate_bindings(read_topology(TINY / "integration/soc_topology.json",
TINY / "address_map/memory_map.json"), tmp_path / "bindings")
top = "tiny_axi4_fabric_tb" if block == "fabric" else "tiny_sysctrl_tb"
sources = [COMMON / "utils/register.sv"]
if block == "fabric":
sources += [COMMON / "interface/axi4_if.sv", COMMON / "stream/round_robin_arbiter.sv",
COMMON / "utils/spill_register.sv",
ROOT / "rtl/ip/interconnect/axi4_error_slave.sv",
TINY / "top/tiny_axi4_target.sv", TINY / "top/tiny_axi4_fabric.sv"]
else:
sources += [COMMON / "interface/apb4_if.sv", COMMON / "cdc/cdc_sync.sv",
COMMON / "cdc/cdc_rst_ctrlr.sv", COMMON / "cdc/cdc_2phase.sv",
COMMON / "clkrst/rst_sync.sv", TINY / "top/tiny_apb_async_bridge.sv",
TINY / "top/tiny_sysctrl.sv"]
sources.append(ROOT / "tests/rtl" / f"{top}.sv")
includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", tmp_path / "bindings/rtl",
ROOT / "rtl/ip/peripheral", TINY / "top", ROOT / "tests/rtl")]
output = tmp_path / "sim"
if simulator == "verilator":
cache = tmp_path / "cache"
cache.mkdir()
run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal",
"-DSV_ASSRT_DISABLE", "--top-module", top,
*includes, *(str(p) for p in sources), "--Mdir", str(tmp_path / "obj"),
"-o", str(output)],
env={**os.environ, "CCACHE_DIR": str(cache), "CCACHE_TEMPDIR": str(cache)})
command = [str(output)]
else:
if not shutil.which("sv2v"):
pytest.skip("sv2v unavailable")
converted = tmp_path / "converted.v"
conversion = run(["sv2v", "-DSV_ASSRT_DISABLE", *includes, *(str(p) for p in sources)])
converted.write_text(conversion.stdout)
run([simulator, "-g2012", "-s", top, "-o", str(output), str(converted)])
command = ["vvp", str(output)]
result = run(command, timeout=60)
assert ("SIM_TEST_PASS Tiny fabric" if block == "fabric" else "SIM_TEST_PASS Tiny SYSCTRL") in result.stdout
assert not any(marker in result.stdout for marker in ("SIM_TEST_FAIL", "FATAL", "%Error"))
def test_tiny_product_prose_does_not_restore_legacy_sdk_names(tmp_path: Path) -> None:
from scripts.check_embedded_c import load_policy, policy_issues
path = tmp_path / "crt/probe.c"
path.parent.mkdir()
policy = load_policy(ROOT)
path.write_text('/* Tiny product */\nconst char *rs_name = "Tiny MCU";\n')
assert policy_issues(tmp_path, path, policy) == []
path.write_text('void tiny_uart_init(void);\n')
assert any("retired tiny" in issue for issue in policy_issues(tmp_path, path, policy))
path.write_text('#include <tiny_uart.h>\n')
assert any("legacy include" in issue for issue in policy_issues(tmp_path, path, policy))
def test_tiny_metrics_use_product_top_and_timestamped_yosys_report(tmp_path: Path) -> None:
import argparse
from scripts.metrics import collect
(tmp_path / "meta").mkdir()
(tmp_path / "meta/manifest.json").write_text('{"configuration": {"SOC": "TINY"}}')
report = tmp_path / "syn/yosys/rpt"
report.mkdir(parents=True)
(report / "retrosoc_tiny_asic_area.json").write_text(
'[00001.250000] {"design": {"area": 123.5, "num_cells": 42}}\n')
output = tmp_path / "meta/metrics.json"
assert collect(argparse.Namespace(variant_root=tmp_path, synth_root=None, sta_root=None,
recipe="balanced", output=output)) == 0
data = json.loads(output.read_text())
assert data["synthesis"]["top_area"] == 123.5
assert data["synthesis"]["top_cells"] == 42
def test_tiny_regression_does_not_require_mini_sources() -> None:
import sys
result = run([sys.executable, str(ROOT / "scripts/regress.py"), "--root", str(ROOT),
"--suite", "pr", "--pdk", "IHP130", "--soc", "TINY", "--dry-run"])
commands = result.stdout.splitlines()
assert commands
assert all("CONFIG=configs/ci/ihp130-tiny.mk" in line for line in commands)
assert any("SYNTH=YOSYS synth" in line for line in commands)
assert any("netsim-boot" in line for line in commands)
assert any("STA=OPENSTA sta" in line for line in commands)