mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
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:
111 files changed
+3729
-2012
No files matched your search
+58
-24
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user