feat: refactor NMI interface and remove unused variables

- Removed CORE and IP variables from common.tcl and synth.tcl scripts.
- Updated yosys.mk to exclude CORE and IP from environment variables.
- Modified bus_fault_tb to enhance NMI interface with management and user NMI signals.
- Added user access denial handling in bus_fault_tb.
- Updated pll_ctrl_tb and sysctrl_fault_tb to reflect changes in NMI interface.
- Adjusted test_gpio_user_mux.py to remove IP_MDD definition.
- Enhanced memory map tests to include user extensions and ensure proper generation.
- Updated test_script_tools.py to reflect changes in core management and user extensions.
- Refined topology tests to ensure user APB target is always added.
- Adjusted tech cell configuration tests to use simplified profile paths.
- Improved user extensions tests for scalar bindings and slot validation.
This commit is contained in:
maksyuki committed 2026-07-26 09:03:33 +08:00
1 parent 3f98ad646c
commit 7a604ec86b
111 files changed
+3729 -2012

No files matched your search

+58 -24
View File
@@ -7,7 +7,11 @@ module bus_fault_tb;
logic [31:0] fault_addr_o;
logic [ 3:0] fault_wstrb_o;
logic fault_reserved_o;
nmi_if core_nmi ();
logic fault_access_o;
logic [ 1:0] fault_master_o;
logic user_bus_idle_o;
nmi_if mgmt_nmi ();
nmi_if user_nmi ();
nmi_if dma_nmi ();
nmi_if natv_nmi ();
nmi_if apb_nmi ();
@@ -20,47 +24,76 @@ module bus_fault_tb;
assign apb_nmi.rdata = 32'h1234_5678;
bus u_bus (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.core_nmi (core_nmi),
.dma_nmi (dma_nmi),
.natv_nmi (natv_nmi),
.apb_nmi (apb_nmi),
.fault_valid_o (fault_valid_o),
.fault_addr_o (fault_addr_o),
.fault_wstrb_o (fault_wstrb_o),
.fault_reserved_o(fault_reserved_o)
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.mgmt_nmi (mgmt_nmi),
.user_nmi (user_nmi),
.dma_nmi (dma_nmi),
.user_bus_enable_i(1'b1),
.user_bus_idle_o (user_bus_idle_o),
.natv_nmi (natv_nmi),
.apb_nmi (apb_nmi),
.fault_valid_o (fault_valid_o),
.fault_addr_o (fault_addr_o),
.fault_wstrb_o (fault_wstrb_o),
.fault_reserved_o (fault_reserved_o),
.fault_access_o (fault_access_o),
.fault_master_o (fault_master_o)
);
task automatic expect_fault(input logic [31:0] address, input logic [3:0] write_strobes,
input logic reserved);
begin
@(negedge clk_i);
core_nmi.addr = address;
core_nmi.wdata = 32'hA5A5_5A5A;
core_nmi.wstrb = write_strobes;
core_nmi.valid = 1'b1;
mgmt_nmi.addr = address;
mgmt_nmi.wdata = 32'hA5A5_5A5A;
mgmt_nmi.wstrb = write_strobes;
mgmt_nmi.valid = 1'b1;
while (!fault_valid_o) @(posedge clk_i);
if (fault_addr_o !== address || fault_wstrb_o !== write_strobes ||
fault_reserved_o !== reserved || natv_nmi.valid || apb_nmi.valid) begin
fault_reserved_o !== reserved || fault_access_o || natv_nmi.valid || apb_nmi.valid) begin
$fatal(1, "unexpected fault response for address %h", address);
end
while (!core_nmi.ready) @(posedge clk_i);
if (core_nmi.rdata !== 32'd0) begin
while (!mgmt_nmi.ready) @(posedge clk_i);
if (mgmt_nmi.rdata !== 32'd0) begin
$fatal(1, "fault response data must be zero");
end
@(negedge clk_i);
core_nmi.valid = 1'b0;
mgmt_nmi.valid = 1'b0;
while (fault_valid_o) @(posedge clk_i);
end
endtask
task automatic expect_user_denied(input logic [31:0] address, input logic [3:0] write_strobes);
begin
@(negedge clk_i);
user_nmi.addr = address;
user_nmi.wdata = 32'h5A5A_A5A5;
user_nmi.wstrb = write_strobes;
user_nmi.valid = 1'b1;
while (!fault_valid_o) @(posedge clk_i);
if (!fault_access_o || fault_master_o != 2'd1 || natv_nmi.valid || apb_nmi.valid) begin
$fatal(1, "user access was not denied locally");
end
while (!user_nmi.ready) @(posedge clk_i);
if (user_nmi.rdata !== 32'd0) begin
$fatal(1, "denied user response data must be zero");
end
@(negedge clk_i);
user_nmi.valid = 1'b0;
while (fault_valid_o) @(posedge clk_i);
end
endtask
initial begin
core_nmi.valid = 1'b0;
core_nmi.addr = '0;
core_nmi.wdata = '0;
core_nmi.wstrb = '0;
mgmt_nmi.valid = 1'b0;
mgmt_nmi.addr = '0;
mgmt_nmi.wdata = '0;
mgmt_nmi.wstrb = '0;
user_nmi.valid = 1'b0;
user_nmi.addr = '0;
user_nmi.wdata = '0;
user_nmi.wstrb = '0;
dma_nmi.valid = 1'b0;
dma_nmi.addr = '0;
dma_nmi.wdata = '0;
@@ -71,6 +104,7 @@ module bus_fault_tb;
expect_fault(32'h1000_F000, 4'hF, 1'b1);
expect_fault(32'hA000_0000, 4'h0, 1'b0);
expect_user_denied(32'h1000_B000, 4'hF);
$display("bus fault responder test passed");
$finish;
end
+7 -5
View File
@@ -98,11 +98,13 @@ module pll_ctrl_tb;
endtask
initial begin
nmi.valid = 1'b0;
nmi.addr = '0;
nmi.wdata = '0;
nmi.wstrb = '0;
sysctrl.core_sel_i = '0;
nmi.valid = 1'b0;
nmi.addr = '0;
nmi.wdata = '0;
nmi.wstrb = '0;
sysctrl.user_bus_idle_i = 1'b1;
sysctrl.fault_access_i = 1'b0;
sysctrl.fault_master_i = '0;
#100;
rst_n_i = 1'b1;
repeat (12) @(posedge sys_clk_o);
+24 -2
View File
@@ -60,7 +60,9 @@ module sysctrl_fault_tb;
nmi.addr = '0;
nmi.wdata = '0;
nmi.wstrb = '0;
sysctrl.core_sel_i = '0;
sysctrl.user_bus_idle_i = 1'b1;
sysctrl.fault_access_i = 1'b0;
sysctrl.fault_master_i = '0;
pll_ctrl.req_ready_i = 1'b1;
pll_ctrl.rsp_active_sel_i = '0;
pll_ctrl.rsp_active_valid_i = 1'b0;
@@ -90,7 +92,27 @@ module sysctrl_fault_tb;
read_register(32'h1000_B010, read_data);
if (read_data !== 32'h0000_000A) $fatal(1, "fault W1C did not clear pending");
$display("sysctrl fault registers test passed");
read_register(32'h1000_B020, read_data);
if (read_data !== 32'h0000_003F) $fatal(1, "user cores were not held in reset");
write_register(32'h1000_B000, 32'h0000_0006);
read_register(32'h1000_B024, read_data);
if ((read_data & 32'h0000_081F) !== 32'h0000_0800) begin
$fatal(1, "out-of-range user core selection was accepted");
end
write_register(32'h1000_B024, 32'h0000_0800);
write_register(32'h1000_B000, 32'h0000_0001);
write_register(32'h1000_B020, 32'h0000_003D);
read_register(32'h1000_B024, read_data);
if ((read_data & 32'h0000_0300) !== 32'h0000_0300 || (read_data & 32'h1F) != 1) begin
$fatal(1, "user core start state was not recorded");
end
write_register(32'h1000_B020, 32'h0000_003F);
read_register(32'h1000_B024, read_data);
if ((read_data & 32'h0000_0300) !== 32'h0000_0200) begin
$fatal(1, "user core stop state was not recorded");
end
$display("sysctrl fault and user core control test passed");
$finish;
end
endmodule
-1
View File
@@ -23,7 +23,6 @@ def test_user_ip_can_own_native_gpio_pads_with_a_safe_handoff(tmp_path: Path) ->
"\n".join(
[
"+define+SV_ASSRT_DISABLE",
"+define+IP_MDD",
f"+incdir+{ROOT / 'rtl/ip/native/peripheral'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
+25 -15
View File
@@ -12,9 +12,11 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
GENERATOR = ROOT / "rtl/mini/address_map/generate_memory_map.py"
MEMORY_MAP = ROOT / "rtl/mini/address_map/memory_map.json"
USER_EXTENSIONS = ROOT / "rtl/mini/integration/user_extensions.json"
USER_GENERATOR = ROOT / "rtl/mini/integration/generate_user_extensions.py"
def generate(output_dir: Path, *, ip: str = "NONE", sram: str = "NO") -> None:
def generate(output_dir: Path, *, sram: str = "NO") -> None:
subprocess.run(
[
sys.executable,
@@ -25,8 +27,17 @@ def generate(output_dir: Path, *, ip: str = "NONE", sram: str = "NO") -> None:
str(output_dir),
"--have-sram-if",
sram,
"--ip",
ip,
],
check=True,
)
subprocess.run(
[
sys.executable,
str(USER_GENERATOR),
"--map",
str(USER_EXTENSIONS),
"--output-dir",
str(output_dir / "user_extensions"),
],
check=True,
)
@@ -41,6 +52,7 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "`define SOC_ADDR_PSRAM_END 32'h407FFFFF" in rtl
assert "`define SOC_ADDR_XPI_END 32'h5FFFFFFF" in rtl
assert "`define SOC_ADDR_IS_FLASH(addr) ((addr) <= `SOC_ADDR_FLASH_END)" in rtl
assert "`define CPU_RESET_ADDR `SOC_CPU_RESET_ADDR" in rtl
assert "`define SOC_ADDR_IS_RESERVED(addr)" in rtl
assert "RS_SOC_PSRAM_SIZE UINT32_C(0x00800000)" in header
@@ -56,19 +68,14 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "PSRAM (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 0x00800000" in linker
def test_user_ip_is_emitted_only_for_the_matching_profile(tmp_path: Path) -> None:
none_output = tmp_path / "none"
mdd_output = tmp_path / "mdd"
generate(none_output)
generate(mdd_output, ip="MDD")
def test_user_ip_is_always_emitted_for_the_fixed_platform(tmp_path: Path) -> None:
generate(tmp_path)
none_rtl = (none_output / "rtl/mmap_define.svh").read_text(encoding="utf-8")
mdd_header = (mdd_output / "include/retrosoc/generated/memory_map.h").read_text(
encoding="utf-8"
)
rtl = (tmp_path / "rtl/mmap_define.svh").read_text(encoding="utf-8")
header = (tmp_path / "include/retrosoc/generated/memory_map.h").read_text(encoding="utf-8")
assert "SOC_ADDR_APB_USER_IP_BASE" not in none_rtl
assert "RS_SOC_APB_USER_IP_BASE" in mdd_header
assert "SOC_ADDR_APB_USER_IP_BASE" in rtl
assert "RS_SOC_APB_USER_IP_BASE" in header
def test_map_validation_rejects_overlaps(tmp_path: Path) -> None:
@@ -100,6 +107,7 @@ def test_bus_fault_responder_handles_reserved_and_unmapped_addresses(tmp_path: P
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{tmp_path / 'rtl'}",
f"+incdir+{tmp_path / 'user_extensions' / 'rtl'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
@@ -151,6 +159,7 @@ def test_sysctrl_fault_registers_record_and_clear_pending(tmp_path: Path) -> Non
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{tmp_path / 'rtl'}",
f"+incdir+{tmp_path / 'user_extensions' / 'rtl'}",
f"+incdir+{ROOT / 'rtl/mini/top'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
@@ -181,7 +190,7 @@ def test_sysctrl_fault_registers_record_and_clear_pending(tmp_path: Path) -> Non
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "sysctrl fault registers test passed" in result.stdout
assert "sysctrl fault and user core control test passed" in result.stdout
def test_pll_controller_reconfigures_and_falls_back_to_the_safe_clock(tmp_path: Path) -> None:
@@ -198,6 +207,7 @@ def test_pll_controller_reconfigures_and_falls_back_to_the_safe_clock(tmp_path:
"+define+PDK_BEHAV",
"+define+HAVE_PLL",
f"+incdir+{tmp_path / 'rtl'}",
f"+incdir+{tmp_path / 'user_extensions' / 'rtl'}",
f"+incdir+{ROOT / 'rtl/mini/top'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
+163 -20
View File
@@ -11,6 +11,7 @@ import sys
import tarfile
import time
from pathlib import Path
from types import SimpleNamespace
from zipfile import ZipFile
@@ -30,7 +31,10 @@ from scripts.analyze_warnings import normalize # noqa: E402
from scripts.check_c_warnings import self_owned_warnings # noqa: E402
from scripts.check_format import format_files # noqa: E402
from scripts.dependency_lock import LockError, load_lock # noqa: E402
from scripts.generate_mpw import migrate_user_gpio_interfaces # noqa: E402
from scripts.generate_mpw import ( # noqa: E402
migrate_user_gpio_interfaces,
remove_legacy_picorv32_entry,
)
from scripts.install_toolchain import safe_extract # noqa: E402
from scripts.regress import ( # noqa: E402
NIGHTLY_COMMANDS,
@@ -95,7 +99,7 @@ def test_nested_filelist_and_space_path_round_trip(tmp_path: Path) -> None:
def test_generate_all_is_stable_and_expands_paths(tmp_path: Path) -> None:
defines = ["+define+PDK_IHP130", "+define+CORE_HAZARD3"]
defines = ["+define+PDK_IHP130"]
generated = generate_all(tmp_path, defines)
mtimes = {path: path.stat().st_mtime_ns for path in generated}
generate_all(tmp_path, defines)
@@ -103,30 +107,88 @@ def test_generate_all_is_stable_and_expands_paths(tmp_path: Path) -> None:
assert (tmp_path / "def.fl").read_text(encoding="utf-8") == " ".join(defines) + "\n"
cluster = (tmp_path / "clusterip.fl").read_text(encoding="utf-8")
assert str(ROOT / "rtl/managed/clusterip") in cluster
assert (tmp_path / "core_hazard3.fl").is_file()
assert (tmp_path / "core_picorv32.fl").is_file()
def test_source_export_selects_the_requested_management_core(tmp_path: Path) -> None:
module_path = ROOT / "syn/tools/export_soc_sources.py"
spec = importlib.util.spec_from_file_location("retrosoc_source_export", module_path)
assert spec is not None and spec.loader is not None
source_export = importlib.util.module_from_spec(spec)
spec.loader.exec_module(source_export)
dynamic_core = tmp_path / "core.fl"
dynamic_ip = tmp_path / "ip.fl"
dynamic_core.write_text("", encoding="utf-8")
dynamic_ip.write_text("", encoding="utf-8")
for core, source_name in (
("HAZARD3", "hazard3_cpu_1port.v"),
("PICORV32", "picorv32_ver.v"),
):
arguments = SimpleNamespace(
pdk="IHP130",
simu="IVERILOG",
core=core,
have_pll=False,
have_sram_if=False,
have_sram_macro=False,
have_sva=False,
dynamic_core_filelist=dynamic_core,
dynamic_ip_filelist=dynamic_ip,
)
generated_dir = tmp_path / core.lower()
user_extensions_dir = generated_dir / "user_extensions"
source_export.generate_all(generated_dir, source_export.build_defines(arguments))
source_export.generate_user_extensions(
ROOT / "rtl/mini/integration/user_extensions.json", user_extensions_dir
)
filelist = source_export.configured_filelist(arguments, generated_dir, user_extensions_dir)
assert f"+define+CORE_{core}" in filelist.defines
assert any(path.name == source_name for path in filelist.files)
def test_package_forwards_manifest_management_core() -> None:
package_source = (ROOT / "scripts/package.py").read_text(encoding="utf-8")
assert '"--core"' in package_source
assert 'config.get("CORE", "HAZARD3")' in package_source
def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> None:
memory_map = tmp_path / "memory_map"
topology = tmp_path / "soc_topology"
user_extensions = tmp_path / "user_extensions"
(memory_map / "rtl").mkdir(parents=True)
(topology / "rtl").mkdir(parents=True)
(user_extensions / "rtl").mkdir(parents=True)
bus_filelist = tmp_path / "bus.fl"
nmi2apb_filelist = tmp_path / "nmi2apb.fl"
sysctrl_filelist = tmp_path / "sysctrl.fl"
pll_rcu_filelist = tmp_path / "pll_rcu.fl"
gpio_mdd_filelist = tmp_path / "gpio_mdd.fl"
assert generate_formal_filelist("bus", bus_filelist, memory_map, topology)
assert generate_formal_filelist("nmi2apb", nmi2apb_filelist, memory_map, topology)
assert generate_formal_filelist("sysctrl", sysctrl_filelist, memory_map, topology)
assert generate_formal_filelist("pll_rcu", pll_rcu_filelist, memory_map, topology)
assert generate_formal_filelist("gpio_mdd", gpio_mdd_filelist, memory_map, topology)
gpio_user_filelist = tmp_path / "gpio_user.fl"
assert generate_formal_filelist("bus", bus_filelist, memory_map, topology, user_extensions)
assert generate_formal_filelist(
"nmi2apb", nmi2apb_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"sysctrl", sysctrl_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"pll_rcu", pll_rcu_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"gpio_user", gpio_user_filelist, memory_map, topology, user_extensions
)
bus = parse_filelists([bus_filelist], require_files=False)
nmi2apb = parse_filelists([nmi2apb_filelist], require_files=False)
sysctrl = parse_filelists([sysctrl_filelist], require_files=False)
pll_rcu = parse_filelists([pll_rcu_filelist], require_files=False)
gpio_mdd = parse_filelists([gpio_mdd_filelist], require_files=False)
gpio_user = parse_filelists([gpio_user_filelist], require_files=False)
assert "+define+SV_ASSRT_DISABLE" in bus.defines
assert ROOT / "rtl/mini/top/bus.sv" in bus.files
assert ROOT / "rtl/ip/native/interconnect/nmi_regslice.sv" in bus.files
@@ -138,11 +200,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
assert ROOT / "rtl/mini/formal/sysctrl_formal.sv" in sysctrl.files
assert ROOT / "rtl/mini/top/rcu.sv" in pll_rcu.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_2phase.sv" in pll_rcu.files
assert ROOT / "rtl/ip/native/peripheral/gpio.sv" in gpio_mdd.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv" in gpio_mdd.files
assert ROOT / "rtl/mini/formal/gpio_mdd_formal.sv" in gpio_mdd.files
assert "+define+IP_MDD" in gpio_mdd.defines
assert "+define+IP_MDD" not in sysctrl.defines
assert ROOT / "rtl/ip/native/peripheral/gpio.sv" in gpio_user.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv" in gpio_user.files
assert ROOT / "rtl/mini/formal/gpio_user_formal.sv" in gpio_user.files
def test_sby_config_uses_prove_and_cover_with_bitwuzla(tmp_path: Path) -> None:
@@ -175,7 +235,7 @@ def test_fatfs_release_script_uses_the_locked_archive_contract() -> None:
def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> None:
proofs = ("bus", "nmi2apb", "sysctrl", "pll_rcu", "gpio_mdd")
proofs = ("bus", "nmi2apb", "sysctrl", "pll_rcu", "gpio_user")
for proof in proofs:
directory = tmp_path / proof
directory.mkdir()
@@ -203,7 +263,7 @@ def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> No
"--proof",
f"pll_rcu={tmp_path / 'pll_rcu'}",
"--proof",
f"gpio_mdd={tmp_path / 'gpio_mdd'}",
f"gpio_user={tmp_path / 'gpio_user'}",
)
result = json.loads(output.read_text(encoding="utf-8"))
assert result["status"] == "passed"
@@ -213,7 +273,8 @@ def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> No
def test_legacy_user_gpio_interface_is_migrated_in_generated_source(tmp_path: Path) -> None:
source = tmp_path / "user_ip_design.sv"
source.write_text(
"""module user_ip_design (gpio_if.dut gpio);
"""`include "user_extensions.svh"
module user_ip_design (gpio_if.dut gpio);
logic value;
assign gpio.gpio_oe = value;
assign gpio.gpio_cs = value;
@@ -231,12 +292,34 @@ endmodule
migrated = source.read_text(encoding="utf-8")
assert "gpio_if.dut" not in migrated
assert "gpio.gpio_" not in migrated
assert '`include "user_extensions.svh"' in migrated
assert "user_gpio_if.user_ip gpio" in migrated
assert "gpio.oe_o" in migrated
assert "gpio.do_o" in migrated
assert "gpio.di_i" in migrated
def test_generated_user_core_filelist_excludes_the_legacy_picorv32_entry(
tmp_path: Path,
) -> None:
management_core_dir = tmp_path / "rtl/managed/picorv32/rtl"
management_core_dir.mkdir(parents=True)
picorv32 = management_core_dir / "picorv32.v"
picorv32_ver = management_core_dir / "picorv32_ver.v"
picorv32.touch()
picorv32_ver.touch()
filelist = tmp_path / "generated/mpw/core/core.fl"
filelist.parent.mkdir(parents=True)
filelist.write_text(
f"{picorv32.resolve()}\n{picorv32_ver.resolve()}\nuser_core_design.sv\n",
encoding="utf-8",
)
remove_legacy_picorv32_entry(filelist, management_core_dir)
assert filelist.read_text(encoding="utf-8") == "user_core_design.sv\n"
def test_prepare_norflash_and_missing_firmware(tmp_path: Path) -> None:
models = tmp_path / "models"
models.mkdir()
@@ -361,7 +444,7 @@ def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
run(
"make",
"-n",
"CONFIG=configs/ci/hazard3-rv32im-ihp130.mk",
"CONFIG=configs/ci/ihp130.mk",
"SIMU=IVERILOG",
"RTL_SIM_TIMEOUT=5200000",
"sim-asm",
@@ -377,6 +460,30 @@ def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
assert invalid.returncode != 0
assert "Invalid SIMU='UNKNOWN'" in invalid.stderr
invalid_core = subprocess.run(
["make", "CORE=alternate", "config"],
cwd=ROOT,
text=True,
capture_output=True,
)
assert invalid_core.returncode != 0
assert "Invalid CORE='alternate'" in invalid_core.stderr
default_core = run("make", "config").stdout
assert "CORE HAZARD3" in default_core
picorv32_core = run("make", "CORE=PICORV32", "config").stdout
assert "CORE PICORV32" in picorv32_core
removed_ip = subprocess.run(
["make", "IP=MDD", "config"],
cwd=ROOT,
text=True,
capture_output=True,
)
assert removed_ip.returncode != 0
assert "IP is no longer configurable" in removed_ip.stderr
def test_format_file_scope_is_tracked_and_self_owned() -> None:
paths = [
@@ -419,7 +526,7 @@ def test_dependency_lock_and_config_key_include_a_fixed_timestamp(tmp_path: Path
"--timestamp",
"2026-07-21-10-39",
"--value",
"CORE=HAZARD3",
"ARCHITECTURE=fixed-management-and-user-extensions",
"--value",
"PDK=IHP130",
)
@@ -481,6 +588,28 @@ def test_verilator_simulations_use_uniform_timeout() -> None:
assert "HAVE_SVA=YES" in values
def test_verilator_has_no_external_core_selection() -> None:
command_line = (ROOT / "rtl/mini/dv/verilator/csrc/main.cpp").read_text(encoding="utf-8")
wrapper = (ROOT / "rtl/mini/dv/verilator/rtl/retrosoc_top.sv").read_text(encoding="utf-8")
testbench = (ROOT / "rtl/mini/dv/tb/retrosoc_tb.sv").read_text(encoding="utf-8")
assert "core-sel" not in command_line
assert "core_sel_i" not in wrapper
assert "core_sel_i" not in testbench
def test_management_core_selection_is_limited_to_hazard3_and_picorv32() -> None:
wrapper = (ROOT / "rtl/mini/top/mgmt_core_wrapper.sv").read_text(encoding="utf-8")
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
assert "VALID_CORE := HAZARD3 PICORV32" in makefile
assert "DEF_LIST += +define+CORE_$(CORE)" in makefile
assert "`ifdef CORE_PICORV32" in wrapper
assert "`elsif CORE_HAZARD3" in wrapper
assert "ahbl2nmi u_ahbl2nmi" in wrapper
assert ".RESET_VECTOR (`SOC_CPU_RESET_ADDR)" in wrapper
def test_smoke_regression_uses_ihp130_behavioral_coverage_only() -> None:
commands, profiles = select_regression("smoke", "IHP130")
@@ -506,7 +635,7 @@ def test_smoke_regression_uses_ihp130_behavioral_coverage_only() -> None:
"IHP130",
"--dry-run",
)
assert "+ make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk firmware" in dry_run.stdout
assert "+ make CONFIG=configs/ci/ihp130.mk firmware" in dry_run.stdout
assert "netsim" not in dry_run.stdout
invalid = subprocess.run(
@@ -788,6 +917,20 @@ def test_warning_normalization_keeps_ranges_and_removes_variant_hash(tmp_path: P
)
def test_warning_normalization_maps_isolated_mpw_sources_to_managed_sources(
tmp_path: Path,
) -> None:
message = (
f"{tmp_path}/build/ihp130-deadbeef/generated/mpw/verilator/core/username3/"
"./Hazard3/hazard3_core_username3.v:42: unused signal"
)
normalized = normalize(tmp_path, "UNUSEDSIGNAL", message)
assert normalized == (
"UNUSEDSIGNAL:$ROOT/rtl/managed/mpw/core/username3/Hazard3/"
"hazard3_core.v:<line>: unused signal"
)
def test_c_warning_filter_excludes_vendored_sources(tmp_path: Path) -> None:
policy_dir = tmp_path / "quality"
policy_dir.mkdir()
+18 -33
View File
@@ -16,7 +16,7 @@ MEMORY_MAP = ROOT / "rtl/mini/address_map/memory_map.json"
MEMORY_MAP_GENERATOR = ROOT / "rtl/mini/address_map/generate_memory_map.py"
def generate(output_dir: Path, ip: str = "NONE") -> None:
def generate(output_dir: Path) -> None:
subprocess.run(
[
sys.executable,
@@ -27,14 +27,12 @@ def generate(output_dir: Path, ip: str = "NONE") -> None:
str(MEMORY_MAP),
"--output-dir",
str(output_dir),
"--ip",
ip,
],
check=True,
)
def validate(topology: Path, ip: str = "NONE") -> subprocess.CompletedProcess[str]:
def validate(topology: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[
sys.executable,
@@ -43,8 +41,6 @@ def validate(topology: Path, ip: str = "NONE") -> subprocess.CompletedProcess[st
str(topology),
"--memory-map",
str(MEMORY_MAP),
"--ip",
ip,
"--check",
],
text=True,
@@ -84,13 +80,14 @@ def test_topology_generates_complete_native_apb_and_gpio_bindings(tmp_path: Path
assert gpio.count("// GPIO") == 64
assert "assign u_uart1_if.rx_i = u_gpio_if.di_i[0];" in gpio
assert "assign u_gpio_if.alt1_do_i[22] = u_psram_if.nss_o[0];" in gpio
assert apb_interfaces.count("apb4_if u_") == 9
assert apb_interfaces.count("apb4_if u_") == 10
assert "apb4_pure_if u_archinfo_apb_pure_if ();" in apb_interfaces
assert "assign tmr.paddr = nmi.addr;" in apb_routes
assert "({32{s_psel_q[8]}} & tmr.prdata)" in apb_response
assert "localparam int NSLV = 9;" in apb_declarations
assert fabric.count("nmi_if u_") == 4
assert ".core_nmi(u_core_nmi_if)" in bus_fabric
assert "localparam int NSLV = 10;" in apb_declarations
assert fabric.count("nmi_if u_") == 5
assert ".mgmt_nmi(u_mgmt_nmi_if)" in bus_fabric
assert ".user_nmi(u_user_nmi_if)" in bus_fabric
assert ".apb_nmi(u_apb_nmi_if)" in bus_fabric
assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config
assert "`define SOC_IRQ_NMI_WIDTH 10" in irq_config
@@ -138,21 +135,16 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
assert all(mapping[3] >= 17 for mapping in mappings[17:])
def test_topology_adds_user_apb_only_for_the_mdd_ip(tmp_path: Path) -> None:
none_output = tmp_path / "none"
mdd_output = tmp_path / "mdd"
generate(none_output)
generate(mdd_output, ip="MDD")
def test_topology_always_adds_the_user_apb_target(tmp_path: Path) -> None:
generate(tmp_path)
none_interfaces = (none_output / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
mdd_interfaces = (mdd_output / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
mdd_declarations = (mdd_output / "rtl/soc_apb_declarations.svh").read_text(encoding="utf-8")
mdd_response = (mdd_output / "rtl/soc_apb_response_mux.svh").read_text(encoding="utf-8")
interfaces = (tmp_path / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
declarations = (tmp_path / "rtl/soc_apb_declarations.svh").read_text(encoding="utf-8")
response = (tmp_path / "rtl/soc_apb_response_mux.svh").read_text(encoding="utf-8")
assert "u_user_ip_apb_if" not in none_interfaces
assert "apb4_if u_user_ip_apb_if (clk_i, rst_n_i);" in mdd_interfaces
assert "localparam int NSLV = 10;" in mdd_declarations
assert "s_psel_q[9]" in mdd_response
assert "apb4_if u_user_ip_apb_if (clk_i, rst_n_i);" in interfaces
assert "localparam int NSLV = 10;" in declarations
assert "s_psel_q[9]" in response
def test_topology_rejects_unknown_or_non_native_regions(tmp_path: Path) -> None:
@@ -183,10 +175,10 @@ def test_topology_rejects_duplicate_region_and_disabled_owner(tmp_path: Path) ->
assert "disabled but declares regions" in result.stderr
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
document["fabric_links"][1]["name"] = "core"
document["fabric_links"][1]["name"] = "mgmt"
result = validate(write_invalid_topology(tmp_path, document))
assert result.returncode != 0
assert "fabric role core is duplicated" in result.stderr
assert "fabric role mgmt is duplicated" in result.stderr
def test_topology_rejects_invalid_apb_target_ownership(tmp_path: Path) -> None:
@@ -202,12 +194,6 @@ def test_topology_rejects_invalid_apb_target_ownership(tmp_path: Path) -> None:
assert result.returncode != 0
assert "APB target slot 0 is duplicated" in result.stderr
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
document["apb_targets"][9]["requires_ip"] = None
result = validate(write_invalid_topology(tmp_path, document), ip="MDD")
assert result.returncode != 0
assert "requires_ip does not match" in result.stderr
def test_topology_rejects_invalid_gpio_coverage_and_expression(tmp_path: Path) -> None:
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
@@ -228,6 +214,7 @@ def test_topology_rejects_invalid_gpio_coverage_and_expression(tmp_path: Path) -
assert result.returncode != 0
assert "scalar SystemVerilog reference or constant" in result.stderr
def test_topology_rejects_invalid_irq_groups_and_bindings(tmp_path: Path) -> None:
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
document["irq_groups"][1]["name"] = "nmi"
@@ -311,8 +298,6 @@ def test_generated_nmi_routes_select_and_return_the_expected_target(tmp_path: Pa
str(memory_map_output),
"--have-sram-if",
"NO",
"--ip",
"NONE",
],
check=True,
)
+2 -2
View File
@@ -12,8 +12,8 @@ ROOT = Path(__file__).resolve().parents[1]
@pytest.mark.parametrize(
("profile", "pdk"),
(
("configs/ci/hazard3-rv32im-gf180.mk", "GF180"),
("configs/ci/hazard3-rv32im-sky130.mk", "SKY130"),
("configs/ci/gf180.mk", "GF180"),
("configs/ci/sky130.mk", "SKY130"),
),
)
def test_unqualified_pll_profiles_are_rejected(profile: str, pdk: str) -> None:
+4 -2
View File
@@ -50,7 +50,9 @@ def test_extensions_generate_isolated_scalar_bindings(tmp_path: Path) -> None:
assert core.count("nmi_if u_user_") == 6
assert "nmi.valid = '0;" in core
assert "5'd6: begin" in core
assert "5'd0: begin" in core
assert "5'd5: begin" in core
assert "core_reset_i[0]" in core
assert "user_core_design_username1 #(" in core
assert ip.count("user_gpio_if #(`USER_GPIO_NUM)") == 2
assert "gpio.do_o = '0;" in ip
@@ -64,7 +66,7 @@ def test_extensions_reject_noncontiguous_slots_and_invalid_modules(tmp_path: Pat
document["core_targets"][2]["slot"] = 7
result = validate(write_invalid_extensions(tmp_path, document))
assert result.returncode != 0
assert "slots must be contiguous from one" in result.stderr
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"