"""Tests for the generated Mini SoC internal integration bindings.""" from __future__ import annotations import json import shutil import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] TOPOLOGY = ROOT / "rtl/mini/integration/soc_topology.json" GENERATOR = ROOT / "rtl/mini/integration/generate_soc_topology.py" 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) -> None: subprocess.run( [ sys.executable, str(GENERATOR), "--map", str(TOPOLOGY), "--memory-map", str(MEMORY_MAP), "--output-dir", str(output_dir), ], check=True, ) def validate(topology: Path) -> subprocess.CompletedProcess[str]: return subprocess.run( [ sys.executable, str(GENERATOR), "--map", str(topology), "--memory-map", str(MEMORY_MAP), "--check", ], text=True, capture_output=True, ) def write_invalid_topology(tmp_path: Path, document: dict[str, object]) -> Path: path = tmp_path / "invalid_topology.json" path.write_text(json.dumps(document), encoding="utf-8") return path def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -> None: generate(tmp_path) interfaces = (tmp_path / "rtl/ribp_interfaces.svh").read_text(encoding="utf-8") routes = (tmp_path / "rtl/ribp_routes.svh").read_text(encoding="utf-8") gpio = (tmp_path / "rtl/soc_gpio_alt_bindings.svh").read_text(encoding="utf-8") apb_interfaces = (tmp_path / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8") apb_declarations = (tmp_path / "rtl/soc_apb_declarations.svh").read_text(encoding="utf-8") apb_routes = (tmp_path / "rtl/soc_apb_request_routes.svh").read_text(encoding="utf-8") apb_response = (tmp_path / "rtl/soc_apb_response_mux.svh").read_text(encoding="utf-8") fabric = (tmp_path / "rtl/soc_fabric_interfaces.svh").read_text(encoding="utf-8") bus_fabric = (tmp_path / "rtl/soc_bus_fabric.svh").read_text(encoding="utf-8") irq_config = (tmp_path / "rtl/soc_irq_config.svh").read_text(encoding="utf-8") rib_irq = (tmp_path / "rtl/ribp_irq_bindings.svh").read_text(encoding="utf-8") apb_irq = (tmp_path / "rtl/soc_apb_irq_bindings.svh").read_text(encoding="utf-8") irq_wiring = (tmp_path / "rtl/soc_irq_wiring.svh").read_text(encoding="utf-8") irq_sva = (tmp_path / "rtl/soc_irq_sva.svh").read_text(encoding="utf-8") filelist = (tmp_path / "soc_topology.fl").read_text(encoding="utf-8") assert interfaces.count("ribp_if u_") == 18 assert "nmi_if" not in interfaces assert "soc_nmi" not in interfaces assert "assign s_slv_sel_d[17] = u_i2c1_ribp_if.valid;" in routes assert "assign s_slv_resp_err[17] = u_i2c1_ribp_if.resp_err;" in routes assert "assign ribp.resp_err = |(s_slv_sel_q & s_slv_ready & s_slv_resp_err);" in routes assert "SOC_ADDR_FLASH" not in routes assert "assign u_sdio_ribp_if.valid = 1'b0;" in routes assert gpio.count("// GPIO") == 64 assert "u_uart1_if" not in gpio assert "assign u_uart0_if.cts_n_i = u_gpio_if.di_i[0];" in gpio assert "assign u_gpio_if.alt0_do_i[0] = 1'b0;" in gpio assert "assign u_gpio_if.alt0_oe_i[0] = 1'b0;" in gpio assert "assign u_gpio_if.alt0_do_i[1] = u_uart0_if.rts_n_o;" in gpio assert "assign u_gpio_if.alt0_oe_i[1] = 1'b1;" in gpio assert "assign u_gpio_if.alt1_do_i[0] = u_ps2_if.ps2_clk_o;" in gpio assert "assign u_gpio_if.alt1_oe_i[0] = u_ps2_if.ps2_clk_oe_o;" in gpio assert "assign u_gpio_if.alt1_do_i[1] = u_ps2_if.ps2_dat_o;" in gpio assert "assign u_gpio_if.alt1_oe_i[1] = u_ps2_if.ps2_dat_oe_o;" in gpio assert "assign u_gpio_if.alt1_do_i[2] = u_ws2812_if.dat_o;" in gpio assert "assign u_pwm_if.sync_i = u_gpio_if.di_i[2];" in gpio assert "assign u_gpio_if.alt0_do_i[3] = u_pwm_if.pwm_o[0];" in gpio assert "assign u_gpio_if.alt0_oe_i[3] = u_pwm_if.oe_o[0];" in gpio assert "assign u_pwm_if.fault_i = u_gpio_if.di_i[9];" in gpio assert "assign u_pwm_if.capture_i[0] = u_gpio_if.di_i[30];" in gpio assert "assign u_pwm_if.capture_i[1] = u_gpio_if.di_i[31];" in gpio assert "assign u_gpio_if.alt1_do_i[22] = u_psram_if.nss_o[0];" in gpio assert "s_tmr_capch" not in gpio assert apb_interfaces.count("apb4_if u_") == 8 assert "apb4_pure_if u_archinfo_apb_pure_if ();" in apb_interfaces assert "assign user_ip.paddr = s_addr_q;" in apb_routes assert "({32{s_psel_q[7]}} & user_ip.prdata)" in apb_response assert "localparam int NSLV = 8;" in apb_declarations assert fabric.count("axi4_if #(") == 5 assert ".mgmt_axi4(u_mgmt_axi4_if)" in bus_fabric assert ".user_axi4(u_user_axi4_if)" in bus_fabric assert ".dma_axi4(u_dma_axi4_if)" in bus_fabric assert ".cfg_axi4(u_cfg_axi4_if)" in bus_fabric assert ".apb_axi4(u_apb_axi4_if)" in bus_fabric assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config assert "`define SOC_USER_IRQ_MASK 32'h000EFBFC" in irq_config assert "`define SOC_IRQ_RIBP_WIDTH 14" in irq_config assert "`define SOC_IRQ_APB_WIDTH 5" in irq_config assert "assign irq_o[0] = u_clint_if.software_irq_o[0];" in rib_irq assert "assign irq_o[10] = ws2812.irq_o;" in rib_irq assert "assign irq_o[11] = gpio.irq_o;" in rib_irq assert "assign irq_o[12] = i2c1.irq_o;" in rib_irq assert "assign irq_o[13] = s_dvp_irq;" in rib_irq assert "assign irq_o[0] = pwm.irq_o;" in apb_irq assert "assign irq_o[4] = s_rng_irq;" in apb_irq assert "s_irq[10]" not in irq_wiring assert "s_irq[15] = s_ribp_irq[13];" in irq_wiring assert "s_irq[16] = s_apb_irq[4];" in irq_wiring assert "irq_i[10] == 1'b0" in irq_sva assert "irq_i[15] == ribp_irq_i[13]" in irq_sva assert "irq_i[31] == 1'b0" in irq_sva assert "bind retrosoc soc_irq_topology_sva" in irq_sva assert filelist.startswith("+incdir+") def test_topology_preserves_default_irq_compatibility_mapping() -> None: document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) assert document["irq_vector_width"] == 32 mappings = [ ( interrupt["name"], interrupt["group"], interrupt["group_bit"], interrupt["core_bit"], interrupt["signal"], ) for interrupt in document["interrupts"] ] assert mappings == [ ("clint_software", "ribp", 0, 0, "u_clint_if.software_irq_o[0]"), ("clint_timer", "ribp", 1, 1, "u_clint_if.timer_irq_o[0]"), ("uart0", "ribp", 2, 2, "uart.irq_o"), ("timer0", "ribp", 3, 3, "s_tim0_irq"), ("timer1", "ribp", 4, 4, "s_tim1_irq"), ("psram", "ribp", 5, 5, "psram.irq_o"), ("spisd", "ribp", 6, 6, "spisd.irq_o"), ("i2c0", "ribp", 7, 7, "i2c0.irq_o"), ("i2s", "ribp", 8, 8, "i2s.irq_o"), ("xpi", "ribp", 9, 9, "xpi.irq_o"), ("pwm", "apb", 0, 11, "pwm.irq_o"), ("ps2", "apb", 1, 12, "ps2.irq_o"), ("rtc", "apb", 2, 13, "u_rtc_if.irq_o"), ("watchdog_early_warning", "apb", 3, 14, "u_wdg_if.irq_o"), ("rng", "apb", 4, 16, "s_rng_irq"), ("ws2812", "ribp", 10, 17, "ws2812.irq_o"), ("gpio", "ribp", 11, 18, "gpio.irq_o"), ("i2c1", "ribp", 12, 19, "i2c1.irq_o"), ("dvp", "ribp", 13, 15, "s_dvp_irq"), ] def test_topology_always_adds_the_user_apb_target(tmp_path: Path) -> None: generate(tmp_path) 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") formal_design = (ROOT / "rtl/mini/formal/rib2apb_formal.sv").read_text(encoding="utf-8") formal_properties = (ROOT / "rtl/mini/formal/rib2apb_formal_props.sv").read_text( encoding="utf-8" ) assert "apb4_if u_user_ip_apb_if (clk_i, rst_n_i);" in interfaces assert "localparam int NSLV = 8;" in declarations assert "s_psel_q[7]" in response assert "apb4_pure_if user_ip ();" in formal_design assert ".user_ip (user_ip)" in formal_design assert "logic [ 7:0] psel_comb" in formal_design assert "wire [ 7:0] psel_comb" in formal_properties def test_topology_rejects_unknown_or_non_rib_regions(tmp_path: Path) -> None: document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["ribp_targets"][0]["regions"] = ["UNKNOWN"] result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "unknown region" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["ribp_targets"][0]["regions"] = ["SRAM"] result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "not an active RIBP region" in result.stderr def test_topology_rejects_duplicate_region_and_disabled_owner(tmp_path: Path) -> None: document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["ribp_targets"][1]["regions"] = ["RIBP_UART0"] result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "multiple targets" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["ribp_targets"][15]["regions"] = ["RIBP_SDIO"] result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "disabled but declares regions" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["fabric_links"][1]["name"] = "mgmt" result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "fabric role mgmt is duplicated" in result.stderr def test_topology_rejects_invalid_apb_target_ownership(tmp_path: Path) -> None: document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["apb_targets"][0]["region"] = "SRAM" result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "not an active APB region" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["apb_targets"][7]["slot"] = 0 result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "APB target slot 0 is duplicated" 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")) document["gpio_alt_functions"][1]["pin"] = 0 result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "gpio pin 0 is duplicated" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["gpio_alt_functions"].pop() result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "one entry for every GPIO pin" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["gpio_alt_functions"][0]["alt0"]["do"] = "left + right" result = validate(write_invalid_topology(tmp_path, document)) 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"] = "ribp" result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "interrupt group ribp is duplicated" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["interrupts"][1]["group_bit"] = 0 result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "interrupt group ribp bit 0 is duplicated" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["interrupts"][1]["core_bit"] = 0 result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "core interrupt bit 0 is duplicated" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) rng_index = next( index for index, interrupt in enumerate(document["interrupts"]) if interrupt["name"] == "rng" ) document["interrupts"].pop(rng_index) result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "interrupt group apb does not cover every group bit" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["interrupts"][0]["signal"] = "left | right" result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "scalar SystemVerilog reference or constant" in result.stderr document = json.loads(TOPOLOGY.read_text(encoding="utf-8")) document["interrupts"][0]["signal"] = "uart.irq_o" result = validate(write_invalid_topology(tmp_path, document)) assert result.returncode != 0 assert "core interrupt bit 0 must retain its compatibility binding" in result.stderr def test_generated_irq_wiring_preserves_the_expected_core_vector(tmp_path: Path) -> None: iverilog = shutil.which("iverilog") vvp = shutil.which("vvp") if iverilog is None or vvp is None: return topology_output = tmp_path / "topology" generate(topology_output) simulation = tmp_path / "soc_irq_topology_tb" subprocess.run( [ iverilog, "-g2012", "-I", str(topology_output / "rtl"), "-s", "soc_irq_topology_tb", "-o", str(simulation), str(ROOT / "tests/rtl/soc_irq_topology_tb.sv"), ], check=True, ) result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True) assert "SoC topology IRQ routing test passed" in result.stdout def test_generated_rib_routes_select_and_return_the_expected_target(tmp_path: Path) -> None: iverilog = shutil.which("iverilog") sv2v = shutil.which("sv2v") vvp = shutil.which("vvp") if iverilog is None or sv2v is None or vvp is None: return memory_map_output = tmp_path / "memory_map" subprocess.run( [ sys.executable, str(MEMORY_MAP_GENERATOR), "--map", str(MEMORY_MAP), "--output-dir", str(memory_map_output), "--have-sram-if", "NO", ], check=True, ) topology_output = tmp_path / "topology" generate(topology_output) source_list = tmp_path / "ribp_topology.fl" source_list.write_text( "\n".join( [ "+define+SV_ASSRT_DISABLE", f"+incdir+{memory_map_output / 'rtl'}", f"+incdir+{topology_output / 'rtl'}", f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}", str(ROOT / "rtl/managed/clusterip/common/rtl/interface/ribp_if.sv"), str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"), str(ROOT / "tests/rtl/ribp_topology_tb.sv"), "", ] ), encoding="utf-8", ) converted = tmp_path / "ribp_topology_tb.v" subprocess.run( [ sys.executable, str(ROOT / "rtl/mini/script/convt_sv2v.py"), "-f", str(source_list), "--output", str(converted), ], check=True, ) simulation = tmp_path / "ribp_topology_tb" subprocess.run( [ iverilog, "-g2012", "-s", "ribp_topology_tb", "-o", str(simulation), str(converted), ], check=True, ) result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True) assert "SoC topology RIBP routing test passed" in result.stdout