"""Tests for the generated scalar user-extension bindings.""" from __future__ import annotations import json import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] EXTENSIONS = ROOT / "rtl/mini/integration/user_extensions.json" GENERATOR = ROOT / "rtl/mini/integration/generate_user_extensions.py" def generate(output_dir: Path) -> None: subprocess.run( [ sys.executable, str(GENERATOR), "--map", str(EXTENSIONS), "--output-dir", str(output_dir), ], check=True, ) def validate(extensions: Path) -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, str(GENERATOR), "--map", str(extensions), "--check"], text=True, capture_output=True, ) def write_invalid_extensions(tmp_path: Path, document: dict[str, object]) -> Path: path = tmp_path / "invalid_extensions.json" path.write_text(json.dumps(document), encoding="utf-8") return path def test_extensions_generate_isolated_scalar_bindings(tmp_path: Path) -> None: generate(tmp_path) core = (tmp_path / "rtl/user_core_bindings.svh").read_text(encoding="utf-8") ip = (tmp_path / "rtl/user_ip_bindings.svh").read_text(encoding="utf-8") filelist = (tmp_path / "user_extensions.fl").read_text(encoding="utf-8") config = (tmp_path / "rtl/user_extensions_config.svh").read_text(encoding="utf-8") assert core.count("rib_if u_user_") == 6 assert core.count("ribp_if u_user_") == 6 assert core.count("ribp2rib #(") == 6 assert core.count(".SyncReset(1'b1)") == 3 assert core.count(".SyncReset(1'b0)") == 3 assert "u_user_rib_if.cmd_valid = '0;" in core assert "5'd0: begin" in core assert "5'd4: begin" in core assert "5'd5: begin" in core assert "core_reset_i[0]" in core assert "mpw_c0 #(0)" in core assert "mpw_c5 #(5)" in core assert "mpw_i1 #(1)" in ip assert "User core 0 uses the RIBP contract" in core assert ip.count("user_gpio_if #(`USER_GPIO_NUM)") == 2 assert "gpio.do_o = '0;" in ip assert "8'd2: begin" in ip assert "u_user_2_apb_if.psel = apb.psel;" in ip assert filelist.startswith("+incdir+") assert "`define USER_CORE_COUNT 6" in config def test_extensions_reject_noncontiguous_slots_and_invalid_modules(tmp_path: Path) -> None: document = json.loads(EXTENSIONS.read_text(encoding="utf-8")) document["core_targets"][2]["slot"] = 7 result = validate(write_invalid_extensions(tmp_path, document)) assert result.returncode != 0 assert "slots must be contiguous from 0" in result.stderr document = json.loads(EXTENSIONS.read_text(encoding="utf-8")) document["ip_targets"][0]["module"] = "user-ip" result = validate(write_invalid_extensions(tmp_path, document)) assert result.returncode != 0 assert "must be a SystemVerilog identifier" in result.stderr def test_extensions_reject_invalid_core_reset_style(tmp_path: Path) -> None: document = json.loads(EXTENSIONS.read_text(encoding="utf-8")) document["core_targets"][0]["reset"] = "mixed" result = validate(write_invalid_extensions(tmp_path, document)) assert result.returncode != 0 assert ".reset must be sync or async" in result.stderr