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)
|
||||
);
|
||||
`endif
|
||||
|
||||
`include "soc_irq_sva.svh"
|
||||
`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
|
||||
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-IP slots. `generate_user_extensions.py` creates isolated default routing
|
||||
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_CONSTANT_RE = re.compile(r"(?:1'[bB][01]|'[01])$")
|
||||
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)
|
||||
@@ -58,6 +79,23 @@ class GpioFunction:
|
||||
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:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
identifier = require_string(value, field)
|
||||
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)
|
||||
|
||||
|
||||
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(
|
||||
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")
|
||||
if document.get("schema_version") != 1:
|
||||
raise ValueError("schema_version must be 1")
|
||||
gpio_pins = document.get("gpio_pins")
|
||||
if not isinstance(gpio_pins, int) or gpio_pins <= 0:
|
||||
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)
|
||||
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 (
|
||||
parse_native_targets(document.get("native_targets"), memory_regions),
|
||||
parse_apb_targets(document.get("apb_targets"), memory_regions, ip),
|
||||
parse_fabric_links(document.get("fabric_links")),
|
||||
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"
|
||||
|
||||
|
||||
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:
|
||||
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
|
||||
)
|
||||
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"),
|
||||
)
|
||||
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")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"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": [
|
||||
{"slot": 0, "name": "uart0", "interface": "u_uart_nmi_if", "regions": ["NMI_UART0"]},
|
||||
{"slot": 1, "name": "gpio", "interface": "u_gpio_nmi_if", "regions": ["NMI_GPIO"]},
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
`include "pwm_define.svh"
|
||||
`include "ps2_define.svh"
|
||||
`include "mdd_config.svh"
|
||||
`include "soc_irq_config.svh"
|
||||
|
||||
module ip_apb_wrapper (
|
||||
// verilog_format: off
|
||||
@@ -29,7 +30,7 @@ module ip_apb_wrapper (
|
||||
input logic [`USER_IPSEL_WIDTH-1:0] ip_sel_i,
|
||||
user_gpio_if.user_ip user_gpio,
|
||||
`endif
|
||||
output logic [ 6:0] irq_o
|
||||
output logic [`SOC_IRQ_APB_WIDTH-1:0] irq_o
|
||||
// verilog_format: on
|
||||
);
|
||||
|
||||
@@ -60,16 +61,10 @@ module ip_apb_wrapper (
|
||||
apb4_tmr u_apb4_tmr (u_tmr_apb_if, u_tmr_if);
|
||||
// verilog_format: on
|
||||
|
||||
// handle irq signals
|
||||
assign irq_o[0] = uart.irq_o;
|
||||
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;
|
||||
// Generated IRQ ownership and core-vector bit assignments are topology checked.
|
||||
`include "soc_apb_irq_bindings.svh"
|
||||
|
||||
nmi2apb u_nmi2apb (
|
||||
nmi2apb u_nmi2apb (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.nmi (nmi),
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// See the Mulan PSL v2 for more details.
|
||||
|
||||
`include "mmap_define.svh"
|
||||
`include "soc_irq_config.svh"
|
||||
|
||||
module ip_nmi_wrapper (
|
||||
// verilog_format: off
|
||||
@@ -38,7 +39,7 @@ module ip_nmi_wrapper (
|
||||
input logic [31:0] fault_addr_i,
|
||||
input logic [3:0] fault_wstrb_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
|
||||
);
|
||||
|
||||
@@ -61,18 +62,10 @@ simp_clint_if u_clint_if ();
|
||||
// Uses ClusterIP common nmi_if and register.sv dffr through generated bindings.
|
||||
`include "soc_nmi_routes.svh"
|
||||
|
||||
assign irq_o[0] = u_clint_if.sfr_irq_o;
|
||||
assign irq_o[1] = u_clint_if.tmr_irq_o;
|
||||
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;
|
||||
// Generated IRQ ownership and core-vector bit assignments are topology checked.
|
||||
`include "soc_nmi_irq_bindings.svh"
|
||||
|
||||
nmi_gpio u_nmi_gpio (
|
||||
nmi_gpio u_nmi_gpio (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.nmi (u_gpio_nmi_if),
|
||||
|
||||
+11
-11
@@ -10,6 +10,7 @@
|
||||
|
||||
`include "mmap_define.svh"
|
||||
`include "mdd_config.svh"
|
||||
`include "soc_irq_config.svh"
|
||||
|
||||
module retrosoc (
|
||||
// verilog_format: off
|
||||
@@ -53,14 +54,14 @@ module retrosoc (
|
||||
ps2_if u_ps2_if ();
|
||||
// verilog_format: on
|
||||
|
||||
logic s_tmr_capch;
|
||||
logic [31:0] s_irq;
|
||||
logic [ 9:0] s_nmi_irq;
|
||||
logic [ 6:0] s_apb_irq;
|
||||
logic s_bus_fault_valid;
|
||||
logic [31:0] s_bus_fault_addr;
|
||||
logic [ 3:0] s_bus_fault_wstrb;
|
||||
logic s_bus_fault_reserved;
|
||||
logic s_tmr_capch;
|
||||
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;
|
||||
logic s_bus_fault_valid;
|
||||
logic [ 31:0] s_bus_fault_addr;
|
||||
logic [ 3:0] s_bus_fault_wstrb;
|
||||
logic s_bus_fault_reserved;
|
||||
|
||||
gpio_pad_bridge u_gpio_pad_bridge (
|
||||
.inner(u_gpio_if),
|
||||
@@ -78,9 +79,8 @@ module retrosoc (
|
||||
assign u_sysctrl_if.core_sel_i = '0;
|
||||
`endif
|
||||
|
||||
assign s_irq[9:0] = s_nmi_irq;
|
||||
assign s_irq[16:10] = s_apb_irq;
|
||||
assign s_irq[31:17] = 15'd0;
|
||||
// Generated IRQ vector wiring defaults all unallocated core IRQ bits low.
|
||||
`include "soc_irq_wiring.svh"
|
||||
|
||||
// Generated GPIO alternate-function wiring is checked against soc_topology.json.
|
||||
`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")
|
||||
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")
|
||||
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")
|
||||
|
||||
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 ".core_nmi(u_core_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+")
|
||||
|
||||
|
||||
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:
|
||||
none_output = tmp_path / "none"
|
||||
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 "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:
|
||||
iverilog = shutil.which("iverilog")
|
||||
|
||||
Reference in new issue
Block a user