mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: implement IRQ topology and bindings for NMI and APB groups
This commit is contained in:
9 files changed
+509
-35
No files matched your search
@@ -148,6 +148,8 @@ bind nmi_gpio soc_gpio_user_handoff_sva #(
|
|||||||
.gpio_do_i (gpio.do_o)
|
.gpio_do_i (gpio.do_o)
|
||||||
);
|
);
|
||||||
`endif
|
`endif
|
||||||
|
|
||||||
|
`include "soc_irq_sva.svh"
|
||||||
`endif
|
`endif
|
||||||
|
|
||||||
`endif
|
`endif
|
||||||
@@ -23,6 +23,18 @@ interface names, and corresponding active APB address-map region. A target that
|
|||||||
declares `requires_ip: "MDD"` is generated only for `IP=MDD`; its presence is
|
declares `requires_ip: "MDD"` is generated only for `IP=MDD`; its presence is
|
||||||
checked against the same condition in the canonical memory map.
|
checked against the same condition in the canonical memory map.
|
||||||
|
|
||||||
|
`irq_vector_width`, `irq_groups`, and `interrupts` define the complete core
|
||||||
|
interrupt topology. `nmi` and `apb` group entries declare the wrapper output
|
||||||
|
width and the associated `retrosoc.sv` signal. Every group bit must be present
|
||||||
|
exactly once, each core-vector bit must have one source, and sources are limited
|
||||||
|
to scalar signal references or one-bit constants. The generator emits the two
|
||||||
|
wrapper bindings, the core-vector wiring, and simulation-only assertions. Core
|
||||||
|
IRQ bits 0 through 16 retain their current compatibility mapping; additions
|
||||||
|
must use currently unallocated bits and preserve the existing entries. Bits
|
||||||
|
without an interrupt entry are driven low. Firmware does not expose a generic
|
||||||
|
external interrupt API until the SoC includes a claim/complete interrupt
|
||||||
|
controller.
|
||||||
|
|
||||||
`user_extensions.json` separately defines the supported scalar user-core and
|
`user_extensions.json` separately defines the supported scalar user-core and
|
||||||
user-IP slots. `generate_user_extensions.py` creates isolated default routing
|
user-IP slots. `generate_user_extensions.py` creates isolated default routing
|
||||||
and one explicit instance per selected slot for `user_core_top.sv` and
|
and one explicit instance per selected slot for `user_core_top.sv` and
|
||||||
|
|||||||
@@ -17,6 +17,27 @@ SV_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
|
|||||||
SV_REFERENCE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[0-9]+\])?$")
|
SV_REFERENCE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[0-9]+\])?$")
|
||||||
SV_CONSTANT_RE = re.compile(r"(?:1'[bB][01]|'[01])$")
|
SV_CONSTANT_RE = re.compile(r"(?:1'[bB][01]|'[01])$")
|
||||||
FABRIC_LINK_NAMES = ("core", "dma", "native", "apb")
|
FABRIC_LINK_NAMES = ("core", "dma", "native", "apb")
|
||||||
|
IRQ_GROUP_NAMES = ("nmi", "apb")
|
||||||
|
IRQ_VECTOR_WIDTH = 32
|
||||||
|
COMPATIBILITY_IRQ_BINDINGS = (
|
||||||
|
("clint_software", "nmi", 0, 0, "u_clint_if.sfr_irq_o"),
|
||||||
|
("clint_timer", "nmi", 1, 1, "u_clint_if.tmr_irq_o"),
|
||||||
|
("uart0", "nmi", 2, 2, "uart.irq_o"),
|
||||||
|
("timer0", "nmi", 3, 3, "s_tim0_irq"),
|
||||||
|
("timer1", "nmi", 4, 4, "s_tim1_irq"),
|
||||||
|
("psram", "nmi", 5, 5, "psram.irq_o"),
|
||||||
|
("spisd", "nmi", 6, 6, "spisd.irq_o"),
|
||||||
|
("i2c0", "nmi", 7, 7, "i2c0.irq_o"),
|
||||||
|
("i2s", "nmi", 8, 8, "i2s.irq_o"),
|
||||||
|
("xpi", "nmi", 9, 9, "xpi.irq_o"),
|
||||||
|
("uart1", "apb", 0, 10, "uart.irq_o"),
|
||||||
|
("pwm", "apb", 1, 11, "pwm.irq_o"),
|
||||||
|
("ps2", "apb", 2, 12, "ps2.irq_o"),
|
||||||
|
("rtc", "apb", 3, 13, "u_rtc_if.irq_o"),
|
||||||
|
("watchdog_reset", "apb", 4, 14, "u_wdg_if.rst_o"),
|
||||||
|
("advanced_timer", "apb", 5, 15, "u_tmr_if.irq_o"),
|
||||||
|
("reserved", "apb", 6, 16, "1'b0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -58,6 +79,23 @@ class GpioFunction:
|
|||||||
alt1: GpioMode
|
alt1: GpioMode
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class IrqGroup:
|
||||||
|
name: str
|
||||||
|
width: int
|
||||||
|
top_signal: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Interrupt:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
group: str
|
||||||
|
group_bit: int
|
||||||
|
core_bit: int
|
||||||
|
signal: str
|
||||||
|
|
||||||
|
|
||||||
def atomic_write(path: Path, content: str) -> None:
|
def atomic_write(path: Path, content: str) -> None:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
if path.exists() and path.read_text(encoding="utf-8") == content:
|
if path.exists() and path.read_text(encoding="utf-8") == content:
|
||||||
@@ -84,6 +122,13 @@ def require_string(value: Any, field: str) -> str:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def require_comment_text(value: Any, field: str) -> str:
|
||||||
|
text = require_string(value, field)
|
||||||
|
if "\n" in text or "\r" in text:
|
||||||
|
raise ValueError(f"{field} must be a single-line string")
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
def require_identifier(value: Any, field: str) -> str:
|
def require_identifier(value: Any, field: str) -> str:
|
||||||
identifier = require_string(value, field)
|
identifier = require_string(value, field)
|
||||||
if SV_IDENTIFIER_RE.fullmatch(identifier) is None:
|
if SV_IDENTIFIER_RE.fullmatch(identifier) is None:
|
||||||
@@ -362,21 +407,142 @@ def parse_gpio_functions(value: Any, gpio_pins: int) -> list[GpioFunction]:
|
|||||||
return sorted(parsed, key=lambda item: item.pin)
|
return sorted(parsed, key=lambda item: item.pin)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_irq_groups(value: Any) -> list[IrqGroup]:
|
||||||
|
if not isinstance(value, list) or len(value) != len(IRQ_GROUP_NAMES):
|
||||||
|
raise ValueError("irq_groups must define every supported interrupt group exactly once")
|
||||||
|
groups: list[IrqGroup] = []
|
||||||
|
names: set[str] = set()
|
||||||
|
top_signals: set[str] = set()
|
||||||
|
for index, entry in enumerate(value):
|
||||||
|
group = require_object(entry, f"irq_groups[{index}]")
|
||||||
|
name = require_identifier(group.get("name"), f"irq_groups[{index}].name")
|
||||||
|
width = group.get("width")
|
||||||
|
if not isinstance(width, int) or width <= 0:
|
||||||
|
raise ValueError(f"irq_groups[{index}].width must be a positive integer")
|
||||||
|
top_signal = require_identifier(
|
||||||
|
group.get("top_signal"), f"irq_groups[{index}].top_signal"
|
||||||
|
)
|
||||||
|
if name not in IRQ_GROUP_NAMES:
|
||||||
|
raise ValueError(f"irq_groups[{index}].name is not a supported interrupt group")
|
||||||
|
if name in names:
|
||||||
|
raise ValueError(f"interrupt group {name} is duplicated")
|
||||||
|
if top_signal in top_signals:
|
||||||
|
raise ValueError(f"interrupt group top signal {top_signal} is duplicated")
|
||||||
|
names.add(name)
|
||||||
|
top_signals.add(top_signal)
|
||||||
|
groups.append(IrqGroup(name, width, top_signal))
|
||||||
|
if names != set(IRQ_GROUP_NAMES):
|
||||||
|
missing = ", ".join(sorted(set(IRQ_GROUP_NAMES) - names))
|
||||||
|
raise ValueError(f"irq_groups does not cover required groups: {missing}")
|
||||||
|
return sorted(groups, key=lambda item: IRQ_GROUP_NAMES.index(item.name))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_interrupts(
|
||||||
|
value: Any, groups: list[IrqGroup], vector_width: int
|
||||||
|
) -> list[Interrupt]:
|
||||||
|
if not isinstance(value, list) or not value:
|
||||||
|
raise ValueError("interrupts must be a non-empty list")
|
||||||
|
group_widths = {group.name: group.width for group in groups}
|
||||||
|
interrupts: list[Interrupt] = []
|
||||||
|
names: set[str] = set()
|
||||||
|
group_bits: dict[str, set[int]] = {group.name: set() for group in groups}
|
||||||
|
core_bits: set[int] = set()
|
||||||
|
for index, entry in enumerate(value):
|
||||||
|
interrupt = require_object(entry, f"interrupts[{index}]")
|
||||||
|
name = require_identifier(interrupt.get("name"), f"interrupts[{index}].name")
|
||||||
|
description = require_comment_text(
|
||||||
|
interrupt.get("description"), f"interrupts[{index}].description"
|
||||||
|
)
|
||||||
|
group = require_identifier(interrupt.get("group"), f"interrupts[{index}].group")
|
||||||
|
group_bit = interrupt.get("group_bit")
|
||||||
|
core_bit = interrupt.get("core_bit")
|
||||||
|
signal = require_value(interrupt.get("signal"), f"interrupts[{index}].signal")
|
||||||
|
if group not in group_widths:
|
||||||
|
raise ValueError(f"interrupts[{index}].group is not a supported interrupt group")
|
||||||
|
if not isinstance(group_bit, int) or not 0 <= group_bit < group_widths[group]:
|
||||||
|
raise ValueError(f"interrupts[{index}].group_bit is out of range")
|
||||||
|
if not isinstance(core_bit, int) or not 0 <= core_bit < vector_width:
|
||||||
|
raise ValueError(f"interrupts[{index}].core_bit is out of range")
|
||||||
|
if name in names:
|
||||||
|
raise ValueError(f"interrupt name {name} is duplicated")
|
||||||
|
if group_bit in group_bits[group]:
|
||||||
|
raise ValueError(f"interrupt group {group} bit {group_bit} is duplicated")
|
||||||
|
if core_bit in core_bits:
|
||||||
|
raise ValueError(f"core interrupt bit {core_bit} is duplicated")
|
||||||
|
names.add(name)
|
||||||
|
group_bits[group].add(group_bit)
|
||||||
|
core_bits.add(core_bit)
|
||||||
|
interrupts.append(Interrupt(name, description, group, group_bit, core_bit, signal))
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
expected = set(range(group.width))
|
||||||
|
if group_bits[group.name] != expected:
|
||||||
|
missing = sorted(expected - group_bits[group.name])
|
||||||
|
extra = sorted(group_bits[group.name] - expected)
|
||||||
|
details: list[str] = []
|
||||||
|
if missing:
|
||||||
|
details.append(f"missing {', '.join(str(bit) for bit in missing)}")
|
||||||
|
if extra:
|
||||||
|
details.append(f"extra {', '.join(str(bit) for bit in extra)}")
|
||||||
|
raise ValueError(
|
||||||
|
f"interrupt group {group.name} does not cover every group bit "
|
||||||
|
f"({'; '.join(details)})"
|
||||||
|
)
|
||||||
|
return sorted(interrupts, key=lambda item: item.core_bit)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_compatibility_irq_bindings(interrupts: list[Interrupt]) -> None:
|
||||||
|
by_core_bit = {interrupt.core_bit: interrupt for interrupt in interrupts}
|
||||||
|
for name, group, group_bit, core_bit, signal in COMPATIBILITY_IRQ_BINDINGS:
|
||||||
|
interrupt = by_core_bit.get(core_bit)
|
||||||
|
actual = None
|
||||||
|
if interrupt is not None:
|
||||||
|
actual = (
|
||||||
|
interrupt.name,
|
||||||
|
interrupt.group,
|
||||||
|
interrupt.group_bit,
|
||||||
|
interrupt.core_bit,
|
||||||
|
interrupt.signal,
|
||||||
|
)
|
||||||
|
expected = (name, group, group_bit, core_bit, signal)
|
||||||
|
if actual != expected:
|
||||||
|
raise ValueError(
|
||||||
|
f"core interrupt bit {core_bit} must retain its compatibility binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def read_topology(
|
def read_topology(
|
||||||
topology_path: Path, memory_map_path: Path, ip: str
|
topology_path: Path, memory_map_path: Path, ip: str
|
||||||
) -> tuple[list[NativeTarget], list[ApbTarget], list[FabricLink], list[GpioFunction]]:
|
) -> tuple[
|
||||||
|
list[NativeTarget],
|
||||||
|
list[ApbTarget],
|
||||||
|
list[FabricLink],
|
||||||
|
list[GpioFunction],
|
||||||
|
int,
|
||||||
|
list[IrqGroup],
|
||||||
|
list[Interrupt],
|
||||||
|
]:
|
||||||
document = require_object(json.loads(topology_path.read_text(encoding="utf-8")), "topology")
|
document = require_object(json.loads(topology_path.read_text(encoding="utf-8")), "topology")
|
||||||
if document.get("schema_version") != 1:
|
if document.get("schema_version") != 1:
|
||||||
raise ValueError("schema_version must be 1")
|
raise ValueError("schema_version must be 1")
|
||||||
gpio_pins = document.get("gpio_pins")
|
gpio_pins = document.get("gpio_pins")
|
||||||
if not isinstance(gpio_pins, int) or gpio_pins <= 0:
|
if not isinstance(gpio_pins, int) or gpio_pins <= 0:
|
||||||
raise ValueError("gpio_pins must be a positive integer")
|
raise ValueError("gpio_pins must be a positive integer")
|
||||||
|
irq_vector_width = document.get("irq_vector_width")
|
||||||
|
if irq_vector_width != IRQ_VECTOR_WIDTH:
|
||||||
|
raise ValueError(f"irq_vector_width must be {IRQ_VECTOR_WIDTH}")
|
||||||
memory_regions = read_memory_regions(memory_map_path)
|
memory_regions = read_memory_regions(memory_map_path)
|
||||||
|
irq_groups = parse_irq_groups(document.get("irq_groups"))
|
||||||
|
interrupts = parse_interrupts(document.get("interrupts"), irq_groups, irq_vector_width)
|
||||||
|
validate_compatibility_irq_bindings(interrupts)
|
||||||
return (
|
return (
|
||||||
parse_native_targets(document.get("native_targets"), memory_regions),
|
parse_native_targets(document.get("native_targets"), memory_regions),
|
||||||
parse_apb_targets(document.get("apb_targets"), memory_regions, ip),
|
parse_apb_targets(document.get("apb_targets"), memory_regions, ip),
|
||||||
parse_fabric_links(document.get("fabric_links")),
|
parse_fabric_links(document.get("fabric_links")),
|
||||||
parse_gpio_functions(document.get("gpio_alt_functions"), gpio_pins),
|
parse_gpio_functions(document.get("gpio_alt_functions"), gpio_pins),
|
||||||
|
irq_vector_width,
|
||||||
|
irq_groups,
|
||||||
|
interrupts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -600,8 +766,117 @@ def render_gpio_bindings(functions: list[GpioFunction]) -> str:
|
|||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def irq_width_macro(group: IrqGroup) -> str:
|
||||||
|
return f"SOC_IRQ_{group.name.upper()}_WIDTH"
|
||||||
|
|
||||||
|
|
||||||
|
def render_irq_config(vector_width: int, groups: list[IrqGroup]) -> str:
|
||||||
|
lines = [
|
||||||
|
"// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit.",
|
||||||
|
"`ifndef RETROSOC_SOC_IRQ_CONFIG_SVH",
|
||||||
|
"`define RETROSOC_SOC_IRQ_CONFIG_SVH",
|
||||||
|
"",
|
||||||
|
f"`define SOC_IRQ_VECTOR_WIDTH {vector_width}",
|
||||||
|
]
|
||||||
|
lines.extend(f"`define {irq_width_macro(group)} {group.width}" for group in groups)
|
||||||
|
lines.extend(["", "`endif"])
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def render_irq_group_bindings(group: IrqGroup, interrupts: list[Interrupt]) -> str:
|
||||||
|
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||||
|
for interrupt in interrupts:
|
||||||
|
if interrupt.group != group.name:
|
||||||
|
continue
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f" // Core IRQ {interrupt.core_bit}: {interrupt.name} ({interrupt.description}).",
|
||||||
|
f" assign irq_o[{interrupt.group_bit}] = {interrupt.signal};",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def render_irq_wiring(
|
||||||
|
vector_width: int, groups: list[IrqGroup], interrupts: list[Interrupt]
|
||||||
|
) -> str:
|
||||||
|
group_signals = {group.name: group.top_signal for group in groups}
|
||||||
|
lines = [
|
||||||
|
"// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit.",
|
||||||
|
" always_comb begin",
|
||||||
|
" s_irq = '0;",
|
||||||
|
]
|
||||||
|
for interrupt in interrupts:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f" // {interrupt.name}: {interrupt.description}",
|
||||||
|
f" s_irq[{interrupt.core_bit}] = {group_signals[interrupt.group]}[{interrupt.group_bit}];",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend([" end", ""])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def render_irq_sva(vector_width: int, groups: list[IrqGroup], interrupts: list[Interrupt]) -> str:
|
||||||
|
group_signals = {group.name: group.top_signal for group in groups}
|
||||||
|
lines = [
|
||||||
|
"// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit.",
|
||||||
|
'`include "soc_irq_config.svh"',
|
||||||
|
"",
|
||||||
|
"module soc_irq_topology_sva (",
|
||||||
|
" input logic clk_i,",
|
||||||
|
" input logic rst_n_i,",
|
||||||
|
" input logic [`SOC_IRQ_VECTOR_WIDTH-1:0] irq_i,",
|
||||||
|
]
|
||||||
|
lines.extend(
|
||||||
|
f" input logic [`{irq_width_macro(group)}-1:0] {group.name}_irq_i,"
|
||||||
|
for group in groups
|
||||||
|
)
|
||||||
|
lines[-1] = lines[-1].removesuffix(",")
|
||||||
|
lines.extend([");", ""])
|
||||||
|
for interrupt in interrupts:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f" // Core IRQ {interrupt.core_bit} remains bound to {interrupt.name}.",
|
||||||
|
" assert property (@(posedge clk_i) disable iff (!rst_n_i)",
|
||||||
|
f" irq_i[{interrupt.core_bit}] == {interrupt.group}_irq_i[{interrupt.group_bit}]);",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
used_core_bits = {interrupt.core_bit for interrupt in interrupts}
|
||||||
|
for core_bit in range(vector_width):
|
||||||
|
if core_bit not in used_core_bits:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f" // Unallocated core IRQ {core_bit} must remain inactive.",
|
||||||
|
" assert property (@(posedge clk_i) disable iff (!rst_n_i)",
|
||||||
|
f" irq_i[{core_bit}] == 1'b0);",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend(["", "endmodule", "", "bind retrosoc soc_irq_topology_sva u_soc_irq_topology_sva ("])
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
" .clk_i(clk_i),",
|
||||||
|
" .rst_n_i(rst_n_i),",
|
||||||
|
" .irq_i(s_irq),",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for index, group in enumerate(groups):
|
||||||
|
separator = "," if index != len(groups) - 1 else ""
|
||||||
|
lines.append(f" .{group.name}_irq_i({group_signals[group.name]}){separator}")
|
||||||
|
lines.append(");")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def generate(topology_path: Path, memory_map_path: Path, output_dir: Path, ip: str) -> None:
|
def generate(topology_path: Path, memory_map_path: Path, output_dir: Path, ip: str) -> None:
|
||||||
targets, apb_targets, fabric_links, gpio_functions = read_topology(
|
(
|
||||||
|
targets,
|
||||||
|
apb_targets,
|
||||||
|
fabric_links,
|
||||||
|
gpio_functions,
|
||||||
|
irq_vector_width,
|
||||||
|
irq_groups,
|
||||||
|
interrupts,
|
||||||
|
) = read_topology(
|
||||||
topology_path, memory_map_path, ip
|
topology_path, memory_map_path, ip
|
||||||
)
|
)
|
||||||
memory_regions = read_memory_regions(memory_map_path)
|
memory_regions = read_memory_regions(memory_map_path)
|
||||||
@@ -632,6 +907,20 @@ def generate(topology_path: Path, memory_map_path: Path, output_dir: Path, ip: s
|
|||||||
render_fabric_connection(fabric_links, "apb", "nmi"),
|
render_fabric_connection(fabric_links, "apb", "nmi"),
|
||||||
)
|
)
|
||||||
atomic_write(rtl_dir / "soc_gpio_alt_bindings.svh", render_gpio_bindings(gpio_functions))
|
atomic_write(rtl_dir / "soc_gpio_alt_bindings.svh", render_gpio_bindings(gpio_functions))
|
||||||
|
atomic_write(rtl_dir / "soc_irq_config.svh", render_irq_config(irq_vector_width, irq_groups))
|
||||||
|
for group in irq_groups:
|
||||||
|
atomic_write(
|
||||||
|
rtl_dir / f"soc_{group.name}_irq_bindings.svh",
|
||||||
|
render_irq_group_bindings(group, interrupts),
|
||||||
|
)
|
||||||
|
atomic_write(
|
||||||
|
rtl_dir / "soc_irq_wiring.svh",
|
||||||
|
render_irq_wiring(irq_vector_width, irq_groups, interrupts),
|
||||||
|
)
|
||||||
|
atomic_write(
|
||||||
|
rtl_dir / "soc_irq_sva.svh",
|
||||||
|
render_irq_sva(irq_vector_width, irq_groups, interrupts),
|
||||||
|
)
|
||||||
atomic_write(output_dir / "soc_topology.fl", f"+incdir+{rtl_dir}\n")
|
atomic_write(output_dir / "soc_topology.fl", f"+incdir+{rtl_dir}\n")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"gpio_pins": 32,
|
"gpio_pins": 32,
|
||||||
|
"irq_vector_width": 32,
|
||||||
|
"irq_groups": [
|
||||||
|
{"name": "nmi", "width": 10, "top_signal": "s_nmi_irq"},
|
||||||
|
{"name": "apb", "width": 7, "top_signal": "s_apb_irq"}
|
||||||
|
],
|
||||||
|
"interrupts": [
|
||||||
|
{"name": "clint_software", "description": "CLINT machine software interrupt", "group": "nmi", "group_bit": 0, "core_bit": 0, "signal": "u_clint_if.sfr_irq_o"},
|
||||||
|
{"name": "clint_timer", "description": "CLINT machine timer interrupt", "group": "nmi", "group_bit": 1, "core_bit": 1, "signal": "u_clint_if.tmr_irq_o"},
|
||||||
|
{"name": "uart0", "description": "Native UART0 interrupt", "group": "nmi", "group_bit": 2, "core_bit": 2, "signal": "uart.irq_o"},
|
||||||
|
{"name": "timer0", "description": "Native timer0 interrupt", "group": "nmi", "group_bit": 3, "core_bit": 3, "signal": "s_tim0_irq"},
|
||||||
|
{"name": "timer1", "description": "Native timer1 interrupt", "group": "nmi", "group_bit": 4, "core_bit": 4, "signal": "s_tim1_irq"},
|
||||||
|
{"name": "psram", "description": "PSRAM controller interrupt", "group": "nmi", "group_bit": 5, "core_bit": 5, "signal": "psram.irq_o"},
|
||||||
|
{"name": "spisd", "description": "SPI SD controller interrupt", "group": "nmi", "group_bit": 6, "core_bit": 6, "signal": "spisd.irq_o"},
|
||||||
|
{"name": "i2c0", "description": "Native I2C0 interrupt", "group": "nmi", "group_bit": 7, "core_bit": 7, "signal": "i2c0.irq_o"},
|
||||||
|
{"name": "i2s", "description": "Native I2S interrupt", "group": "nmi", "group_bit": 8, "core_bit": 8, "signal": "i2s.irq_o"},
|
||||||
|
{"name": "xpi", "description": "XPI controller interrupt", "group": "nmi", "group_bit": 9, "core_bit": 9, "signal": "xpi.irq_o"},
|
||||||
|
{"name": "uart1", "description": "APB UART1 interrupt", "group": "apb", "group_bit": 0, "core_bit": 10, "signal": "uart.irq_o"},
|
||||||
|
{"name": "pwm", "description": "APB PWM interrupt", "group": "apb", "group_bit": 1, "core_bit": 11, "signal": "pwm.irq_o"},
|
||||||
|
{"name": "ps2", "description": "APB PS2 interrupt", "group": "apb", "group_bit": 2, "core_bit": 12, "signal": "ps2.irq_o"},
|
||||||
|
{"name": "rtc", "description": "APB RTC interrupt", "group": "apb", "group_bit": 3, "core_bit": 13, "signal": "u_rtc_if.irq_o"},
|
||||||
|
{"name": "watchdog_reset", "description": "APB watchdog reset request", "group": "apb", "group_bit": 4, "core_bit": 14, "signal": "u_wdg_if.rst_o"},
|
||||||
|
{"name": "advanced_timer", "description": "APB advanced timer interrupt", "group": "apb", "group_bit": 5, "core_bit": 15, "signal": "u_tmr_if.irq_o"},
|
||||||
|
{"name": "reserved", "description": "Reserved interrupt slot", "group": "apb", "group_bit": 6, "core_bit": 16, "signal": "1'b0"}
|
||||||
|
],
|
||||||
"native_targets": [
|
"native_targets": [
|
||||||
{"slot": 0, "name": "uart0", "interface": "u_uart_nmi_if", "regions": ["NMI_UART0"]},
|
{"slot": 0, "name": "uart0", "interface": "u_uart_nmi_if", "regions": ["NMI_UART0"]},
|
||||||
{"slot": 1, "name": "gpio", "interface": "u_gpio_nmi_if", "regions": ["NMI_GPIO"]},
|
{"slot": 1, "name": "gpio", "interface": "u_gpio_nmi_if", "regions": ["NMI_GPIO"]},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
`include "pwm_define.svh"
|
`include "pwm_define.svh"
|
||||||
`include "ps2_define.svh"
|
`include "ps2_define.svh"
|
||||||
`include "mdd_config.svh"
|
`include "mdd_config.svh"
|
||||||
|
`include "soc_irq_config.svh"
|
||||||
|
|
||||||
module ip_apb_wrapper (
|
module ip_apb_wrapper (
|
||||||
// verilog_format: off
|
// verilog_format: off
|
||||||
@@ -29,7 +30,7 @@ module ip_apb_wrapper (
|
|||||||
input logic [`USER_IPSEL_WIDTH-1:0] ip_sel_i,
|
input logic [`USER_IPSEL_WIDTH-1:0] ip_sel_i,
|
||||||
user_gpio_if.user_ip user_gpio,
|
user_gpio_if.user_ip user_gpio,
|
||||||
`endif
|
`endif
|
||||||
output logic [ 6:0] irq_o
|
output logic [`SOC_IRQ_APB_WIDTH-1:0] irq_o
|
||||||
// verilog_format: on
|
// verilog_format: on
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -60,16 +61,10 @@ module ip_apb_wrapper (
|
|||||||
apb4_tmr u_apb4_tmr (u_tmr_apb_if, u_tmr_if);
|
apb4_tmr u_apb4_tmr (u_tmr_apb_if, u_tmr_if);
|
||||||
// verilog_format: on
|
// verilog_format: on
|
||||||
|
|
||||||
// handle irq signals
|
// Generated IRQ ownership and core-vector bit assignments are topology checked.
|
||||||
assign irq_o[0] = uart.irq_o;
|
`include "soc_apb_irq_bindings.svh"
|
||||||
assign irq_o[1] = pwm.irq_o;
|
|
||||||
assign irq_o[2] = ps2.irq_o;
|
|
||||||
assign irq_o[3] = u_rtc_if.irq_o;
|
|
||||||
assign irq_o[4] = u_wdg_if.rst_o;
|
|
||||||
assign irq_o[5] = u_tmr_if.irq_o;
|
|
||||||
assign irq_o[6] = 1'b0;
|
|
||||||
|
|
||||||
nmi2apb u_nmi2apb (
|
nmi2apb u_nmi2apb (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
.rst_n_i(rst_n_i),
|
.rst_n_i(rst_n_i),
|
||||||
.nmi (nmi),
|
.nmi (nmi),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
// See the Mulan PSL v2 for more details.
|
// See the Mulan PSL v2 for more details.
|
||||||
|
|
||||||
`include "mmap_define.svh"
|
`include "mmap_define.svh"
|
||||||
|
`include "soc_irq_config.svh"
|
||||||
|
|
||||||
module ip_nmi_wrapper (
|
module ip_nmi_wrapper (
|
||||||
// verilog_format: off
|
// verilog_format: off
|
||||||
@@ -38,7 +39,7 @@ module ip_nmi_wrapper (
|
|||||||
input logic [31:0] fault_addr_i,
|
input logic [31:0] fault_addr_i,
|
||||||
input logic [3:0] fault_wstrb_i,
|
input logic [3:0] fault_wstrb_i,
|
||||||
input logic fault_reserved_i,
|
input logic fault_reserved_i,
|
||||||
output logic [9:0] irq_o
|
output logic [`SOC_IRQ_NMI_WIDTH-1:0] irq_o
|
||||||
// verilog_format: on
|
// verilog_format: on
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -61,18 +62,10 @@ simp_clint_if u_clint_if ();
|
|||||||
// Uses ClusterIP common nmi_if and register.sv dffr through generated bindings.
|
// Uses ClusterIP common nmi_if and register.sv dffr through generated bindings.
|
||||||
`include "soc_nmi_routes.svh"
|
`include "soc_nmi_routes.svh"
|
||||||
|
|
||||||
assign irq_o[0] = u_clint_if.sfr_irq_o;
|
// Generated IRQ ownership and core-vector bit assignments are topology checked.
|
||||||
assign irq_o[1] = u_clint_if.tmr_irq_o;
|
`include "soc_nmi_irq_bindings.svh"
|
||||||
assign irq_o[2] = uart.irq_o;
|
|
||||||
assign irq_o[3] = s_tim0_irq;
|
|
||||||
assign irq_o[4] = s_tim1_irq;
|
|
||||||
assign irq_o[5] = psram.irq_o;
|
|
||||||
assign irq_o[6] = spisd.irq_o;
|
|
||||||
assign irq_o[7] = i2c0.irq_o;
|
|
||||||
assign irq_o[8] = i2s.irq_o;
|
|
||||||
assign irq_o[9] = xpi.irq_o;
|
|
||||||
|
|
||||||
nmi_gpio u_nmi_gpio (
|
nmi_gpio u_nmi_gpio (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
.rst_n_i (rst_n_i),
|
.rst_n_i (rst_n_i),
|
||||||
.nmi (u_gpio_nmi_if),
|
.nmi (u_gpio_nmi_if),
|
||||||
|
|||||||
+11
-11
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
`include "mmap_define.svh"
|
`include "mmap_define.svh"
|
||||||
`include "mdd_config.svh"
|
`include "mdd_config.svh"
|
||||||
|
`include "soc_irq_config.svh"
|
||||||
|
|
||||||
module retrosoc (
|
module retrosoc (
|
||||||
// verilog_format: off
|
// verilog_format: off
|
||||||
@@ -53,14 +54,14 @@ module retrosoc (
|
|||||||
ps2_if u_ps2_if ();
|
ps2_if u_ps2_if ();
|
||||||
// verilog_format: on
|
// verilog_format: on
|
||||||
|
|
||||||
logic s_tmr_capch;
|
logic s_tmr_capch;
|
||||||
logic [31:0] s_irq;
|
logic [`SOC_IRQ_VECTOR_WIDTH-1:0] s_irq;
|
||||||
logic [ 9:0] s_nmi_irq;
|
logic [ `SOC_IRQ_NMI_WIDTH-1:0] s_nmi_irq;
|
||||||
logic [ 6:0] s_apb_irq;
|
logic [ `SOC_IRQ_APB_WIDTH-1:0] s_apb_irq;
|
||||||
logic s_bus_fault_valid;
|
logic s_bus_fault_valid;
|
||||||
logic [31:0] s_bus_fault_addr;
|
logic [ 31:0] s_bus_fault_addr;
|
||||||
logic [ 3:0] s_bus_fault_wstrb;
|
logic [ 3:0] s_bus_fault_wstrb;
|
||||||
logic s_bus_fault_reserved;
|
logic s_bus_fault_reserved;
|
||||||
|
|
||||||
gpio_pad_bridge u_gpio_pad_bridge (
|
gpio_pad_bridge u_gpio_pad_bridge (
|
||||||
.inner(u_gpio_if),
|
.inner(u_gpio_if),
|
||||||
@@ -78,9 +79,8 @@ module retrosoc (
|
|||||||
assign u_sysctrl_if.core_sel_i = '0;
|
assign u_sysctrl_if.core_sel_i = '0;
|
||||||
`endif
|
`endif
|
||||||
|
|
||||||
assign s_irq[9:0] = s_nmi_irq;
|
// Generated IRQ vector wiring defaults all unallocated core IRQ bits low.
|
||||||
assign s_irq[16:10] = s_apb_irq;
|
`include "soc_irq_wiring.svh"
|
||||||
assign s_irq[31:17] = 15'd0;
|
|
||||||
|
|
||||||
// Generated GPIO alternate-function wiring is checked against soc_topology.json.
|
// Generated GPIO alternate-function wiring is checked against soc_topology.json.
|
||||||
`include "soc_gpio_alt_bindings.svh"
|
`include "soc_gpio_alt_bindings.svh"
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
`include "soc_irq_config.svh"
|
||||||
|
|
||||||
|
module soc_irq_topology_tb;
|
||||||
|
logic [`SOC_IRQ_VECTOR_WIDTH-1:0] s_irq;
|
||||||
|
logic [ `SOC_IRQ_NMI_WIDTH-1:0] s_nmi_irq;
|
||||||
|
logic [ `SOC_IRQ_APB_WIDTH-1:0] s_apb_irq;
|
||||||
|
|
||||||
|
`include "soc_irq_wiring.svh"
|
||||||
|
|
||||||
|
task automatic expect_irq(input logic [`SOC_IRQ_VECTOR_WIDTH-1:0] expected);
|
||||||
|
begin
|
||||||
|
#1;
|
||||||
|
if (s_irq !== expected) begin
|
||||||
|
$fatal(1, "unexpected IRQ vector: got %h, expected %h", s_irq, expected);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
s_nmi_irq = '0;
|
||||||
|
s_apb_irq = '0;
|
||||||
|
expect_irq('0);
|
||||||
|
|
||||||
|
for (int bit_index = 0; bit_index < `SOC_IRQ_NMI_WIDTH; bit_index++) begin
|
||||||
|
s_nmi_irq = '0;
|
||||||
|
s_nmi_irq[bit_index] = 1'b1;
|
||||||
|
s_apb_irq = '0;
|
||||||
|
expect_irq(32'd1 << bit_index);
|
||||||
|
end
|
||||||
|
|
||||||
|
for (int bit_index = 0; bit_index < `SOC_IRQ_APB_WIDTH; bit_index++) begin
|
||||||
|
s_nmi_irq = '0;
|
||||||
|
s_apb_irq = '0;
|
||||||
|
s_apb_irq[bit_index] = 1'b1;
|
||||||
|
expect_irq(32'd1 << (10 + bit_index));
|
||||||
|
end
|
||||||
|
|
||||||
|
if (s_irq[31:17] !== '0) begin
|
||||||
|
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq[31:17]);
|
||||||
|
end
|
||||||
|
|
||||||
|
$display("SoC topology IRQ routing test passed");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
@@ -70,6 +70,11 @@ def test_topology_generates_complete_native_apb_and_gpio_bindings(tmp_path: Path
|
|||||||
apb_response = (tmp_path / "rtl/soc_apb_response_mux.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")
|
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")
|
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")
|
||||||
|
nmi_irq = (tmp_path / "rtl/soc_nmi_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")
|
filelist = (tmp_path / "soc_topology.fl").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert interfaces.count("nmi_if u_") == 18
|
assert interfaces.count("nmi_if u_") == 18
|
||||||
@@ -87,9 +92,52 @@ def test_topology_generates_complete_native_apb_and_gpio_bindings(tmp_path: Path
|
|||||||
assert fabric.count("nmi_if u_") == 4
|
assert fabric.count("nmi_if u_") == 4
|
||||||
assert ".core_nmi(u_core_nmi_if)" in bus_fabric
|
assert ".core_nmi(u_core_nmi_if)" in bus_fabric
|
||||||
assert ".apb_nmi(u_apb_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
|
||||||
|
assert "`define SOC_IRQ_APB_WIDTH 7" in irq_config
|
||||||
|
assert "assign irq_o[0] = u_clint_if.sfr_irq_o;" in nmi_irq
|
||||||
|
assert "assign irq_o[5] = u_tmr_if.irq_o;" in apb_irq
|
||||||
|
assert "s_irq[16] = s_apb_irq[6];" in irq_wiring
|
||||||
|
assert "irq_i[31] == 1'b0" in irq_sva
|
||||||
|
assert "bind retrosoc soc_irq_topology_sva" in irq_sva
|
||||||
assert filelist.startswith("+incdir+")
|
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[:17] == [
|
||||||
|
("clint_software", "nmi", 0, 0, "u_clint_if.sfr_irq_o"),
|
||||||
|
("clint_timer", "nmi", 1, 1, "u_clint_if.tmr_irq_o"),
|
||||||
|
("uart0", "nmi", 2, 2, "uart.irq_o"),
|
||||||
|
("timer0", "nmi", 3, 3, "s_tim0_irq"),
|
||||||
|
("timer1", "nmi", 4, 4, "s_tim1_irq"),
|
||||||
|
("psram", "nmi", 5, 5, "psram.irq_o"),
|
||||||
|
("spisd", "nmi", 6, 6, "spisd.irq_o"),
|
||||||
|
("i2c0", "nmi", 7, 7, "i2c0.irq_o"),
|
||||||
|
("i2s", "nmi", 8, 8, "i2s.irq_o"),
|
||||||
|
("xpi", "nmi", 9, 9, "xpi.irq_o"),
|
||||||
|
("uart1", "apb", 0, 10, "uart.irq_o"),
|
||||||
|
("pwm", "apb", 1, 11, "pwm.irq_o"),
|
||||||
|
("ps2", "apb", 2, 12, "ps2.irq_o"),
|
||||||
|
("rtc", "apb", 3, 13, "u_rtc_if.irq_o"),
|
||||||
|
("watchdog_reset", "apb", 4, 14, "u_wdg_if.rst_o"),
|
||||||
|
("advanced_timer", "apb", 5, 15, "u_tmr_if.irq_o"),
|
||||||
|
("reserved", "apb", 6, 16, "1'b0"),
|
||||||
|
]
|
||||||
|
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:
|
def test_topology_adds_user_apb_only_for_the_mdd_ip(tmp_path: Path) -> None:
|
||||||
none_output = tmp_path / "none"
|
none_output = tmp_path / "none"
|
||||||
mdd_output = tmp_path / "mdd"
|
mdd_output = tmp_path / "mdd"
|
||||||
@@ -180,6 +228,70 @@ def test_topology_rejects_invalid_gpio_coverage_and_expression(tmp_path: Path) -
|
|||||||
assert result.returncode != 0
|
assert result.returncode != 0
|
||||||
assert "scalar SystemVerilog reference or constant" in result.stderr
|
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"
|
||||||
|
result = validate(write_invalid_topology(tmp_path, document))
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert "interrupt group nmi 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 nmi 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"))
|
||||||
|
document["interrupts"].pop()
|
||||||
|
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_nmi_routes_select_and_return_the_expected_target(tmp_path: Path) -> None:
|
def test_generated_nmi_routes_select_and_return_the_expected_target(tmp_path: Path) -> None:
|
||||||
iverilog = shutil.which("iverilog")
|
iverilog = shutil.which("iverilog")
|
||||||
|
|||||||
Reference in new issue
Block a user