mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: refactor memory map definitions and enhance fault handling in Mini SoC
- Updated core_wrapper and mgmt_core_wrapper to use new SOC reset and IRQ addresses. - Removed obsolete mmap_define.svh file and integrated memory map generation into the build process. - Enhanced ip_nmi_wrapper to handle fault signals and updated NMI interface assignments. - Added bus fault and sysctrl fault testbenches to validate fault handling mechanisms. - Improved memory map generation script and added tests for memory map validation and fault responder behavior.
This commit is contained in:
28 files changed
+1139
-482
No files matched your search
@@ -0,0 +1,77 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module bus_fault_tb;
|
||||
logic clk_i = 1'b0;
|
||||
logic rst_n_i = 1'b0;
|
||||
logic fault_valid_o;
|
||||
logic [31:0] fault_addr_o;
|
||||
logic [ 3:0] fault_wstrb_o;
|
||||
logic fault_reserved_o;
|
||||
nmi_if core_nmi ();
|
||||
nmi_if dma_nmi ();
|
||||
nmi_if natv_nmi ();
|
||||
nmi_if apb_nmi ();
|
||||
|
||||
always #5 clk_i = ~clk_i;
|
||||
|
||||
assign natv_nmi.ready = 1'b1;
|
||||
assign natv_nmi.rdata = 32'hCAFE_BABE;
|
||||
assign apb_nmi.ready = 1'b1;
|
||||
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)
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
$fatal(1, "unexpected fault response for address %h", address);
|
||||
end
|
||||
while (!core_nmi.ready) @(posedge clk_i);
|
||||
if (core_nmi.rdata !== 32'd0) begin
|
||||
$fatal(1, "fault response data must be zero");
|
||||
end
|
||||
@(negedge clk_i);
|
||||
core_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;
|
||||
dma_nmi.valid = 1'b0;
|
||||
dma_nmi.addr = '0;
|
||||
dma_nmi.wdata = '0;
|
||||
dma_nmi.wstrb = '0;
|
||||
|
||||
repeat (2) @(posedge clk_i);
|
||||
rst_n_i = 1'b1;
|
||||
|
||||
expect_fault(32'h1000_F000, 4'hF, 1'b1);
|
||||
expect_fault(32'hA000_0000, 4'h0, 1'b0);
|
||||
$display("bus fault responder test passed");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,86 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module sysctrl_fault_tb;
|
||||
logic clk_i = 1'b0;
|
||||
logic rst_n_i = 1'b0;
|
||||
logic fault_valid_i = 1'b0;
|
||||
logic [31:0] fault_addr_i = '0;
|
||||
logic [ 3:0] fault_wstrb_i = '0;
|
||||
logic fault_reserved_i = 1'b0;
|
||||
nmi_if nmi ();
|
||||
sysctrl_if sysctrl ();
|
||||
|
||||
always #5 clk_i = ~clk_i;
|
||||
|
||||
nmi_sysctrl u_sysctrl (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.fault_valid_i (fault_valid_i),
|
||||
.fault_addr_i (fault_addr_i),
|
||||
.fault_wstrb_i (fault_wstrb_i),
|
||||
.fault_reserved_i(fault_reserved_i),
|
||||
.nmi (nmi),
|
||||
.sysctrl (sysctrl)
|
||||
);
|
||||
|
||||
task automatic read_register(input logic [31:0] address, output logic [31:0] data);
|
||||
begin
|
||||
@(negedge clk_i);
|
||||
nmi.addr = address;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
nmi.valid = 1'b1;
|
||||
while (!nmi.ready) @(posedge clk_i);
|
||||
data = nmi.rdata;
|
||||
@(negedge clk_i);
|
||||
nmi.valid = 1'b0;
|
||||
while (nmi.ready) @(posedge clk_i);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic write_register(input logic [31:0] address, input logic [31:0] data);
|
||||
begin
|
||||
@(negedge clk_i);
|
||||
nmi.addr = address;
|
||||
nmi.wdata = data;
|
||||
nmi.wstrb = 4'hF;
|
||||
nmi.valid = 1'b1;
|
||||
while (!nmi.ready) @(posedge clk_i);
|
||||
@(negedge clk_i);
|
||||
nmi.valid = 1'b0;
|
||||
while (nmi.ready) @(posedge clk_i);
|
||||
end
|
||||
endtask
|
||||
|
||||
logic [31:0] read_data;
|
||||
initial begin
|
||||
nmi.valid = 1'b0;
|
||||
nmi.addr = '0;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
sysctrl.core_sel_i = '0;
|
||||
repeat (2) @(posedge clk_i);
|
||||
rst_n_i = 1'b1;
|
||||
|
||||
@(negedge clk_i);
|
||||
fault_valid_i = 1'b1;
|
||||
fault_addr_i = 32'h1000_F000;
|
||||
fault_wstrb_i = 4'hF;
|
||||
fault_reserved_i = 1'b1;
|
||||
@(negedge clk_i);
|
||||
fault_valid_i = 1'b0;
|
||||
|
||||
read_register(32'h1000_B010, read_data);
|
||||
if (read_data !== 32'h0000_000B) $fatal(1, "fault status was not recorded");
|
||||
read_register(32'h1000_B014, read_data);
|
||||
if (read_data !== 32'h1000_F000) $fatal(1, "fault address was not recorded");
|
||||
read_register(32'h1000_B018, read_data);
|
||||
if (read_data !== 32'h0000_0001) $fatal(1, "fault count was not incremented");
|
||||
write_register(32'h1000_B010, 32'h0000_0001);
|
||||
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");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,179 @@
|
||||
"""Tests for the canonical Mini SoC address-map generator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
GENERATOR = ROOT / "rtl/mini/script/generate_memory_map.py"
|
||||
MEMORY_MAP = ROOT / "rtl/mini/memory_map.json"
|
||||
|
||||
|
||||
def generate(output_dir: Path, *, ip: str = "NONE", sram: str = "NO") -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(GENERATOR),
|
||||
"--map",
|
||||
str(MEMORY_MAP),
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
"--have-sram-if",
|
||||
sram,
|
||||
"--ip",
|
||||
ip,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None:
|
||||
generate(tmp_path, sram="YES")
|
||||
|
||||
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")
|
||||
linker = (tmp_path / "linker/memory_regions.ld").read_text(encoding="utf-8")
|
||||
|
||||
assert "`define SOC_ADDR_PSRAM_END 32'h407FFFFF" in rtl
|
||||
assert "`define SOC_ADDR_XPI_END 32'h5FFFFFFF" 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
|
||||
assert "RS_SOC_SDRAM_SIZE UINT32_C(0x02000000)" in header
|
||||
assert "RS_SOC_SPISD_SIZE UINT32_C(0x40000000)" in header
|
||||
assert "RS_SOC_NMI_SDIO_BASE" not in header
|
||||
assert "RS_SOC_OPIPSRAM_BASE" not in header
|
||||
assert "RS_SOC_HAS_SRAM 1U" in header
|
||||
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")
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
assert "SOC_ADDR_APB_USER_IP_BASE" not in none_rtl
|
||||
assert "RS_SOC_APB_USER_IP_BASE" in mdd_header
|
||||
|
||||
|
||||
def test_map_validation_rejects_overlaps(tmp_path: Path) -> None:
|
||||
document = json.loads(MEMORY_MAP.read_text(encoding="utf-8"))
|
||||
document["regions"][1]["base"] = document["regions"][0]["base"]
|
||||
invalid_map = tmp_path / "overlap.json"
|
||||
invalid_map.write_text(json.dumps(document), encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(GENERATOR), "--map", str(invalid_map), "--check"],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "address-map overlap" in result.stderr
|
||||
|
||||
|
||||
def test_bus_fault_responder_handles_reserved_and_unmapped_addresses(tmp_path: Path) -> None:
|
||||
iverilog = shutil.which("iverilog")
|
||||
vvp = shutil.which("vvp")
|
||||
if iverilog is None or vvp is None:
|
||||
return
|
||||
generate(tmp_path)
|
||||
simulation = tmp_path / "bus_fault_tb"
|
||||
source_list = tmp_path / "bus_fault.fl"
|
||||
source_list.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"+define+SV_ASSRT_DISABLE",
|
||||
f"+incdir+{tmp_path / 'rtl'}",
|
||||
f"+incdir+{ROOT / 'rtl/clusterip/common/rtl'}",
|
||||
str(ROOT / "rtl/clusterip/common/rtl/interface/nmi_if.sv"),
|
||||
str(ROOT / "rtl/clusterip/common/rtl/utils/register.sv"),
|
||||
str(ROOT / "rtl/ip/native/nmi_regslice.sv"),
|
||||
str(ROOT / "rtl/mini/top/bus.sv"),
|
||||
str(ROOT / "tests/rtl/bus_fault_tb.sv"),
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
converted = tmp_path / "bus_fault_tb.v"
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
|
||||
"-f",
|
||||
str(source_list),
|
||||
"--output",
|
||||
str(converted),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
iverilog,
|
||||
"-g2012",
|
||||
"-s",
|
||||
"bus_fault_tb",
|
||||
"-o",
|
||||
str(simulation),
|
||||
str(converted),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
|
||||
assert "bus fault responder test passed" in result.stdout
|
||||
|
||||
|
||||
def test_sysctrl_fault_registers_record_and_clear_pending(tmp_path: Path) -> None:
|
||||
iverilog = shutil.which("iverilog")
|
||||
vvp = shutil.which("vvp")
|
||||
if iverilog is None or vvp is None:
|
||||
return
|
||||
generate(tmp_path)
|
||||
source_list = tmp_path / "sysctrl_fault.fl"
|
||||
source_list.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"+define+SV_ASSRT_DISABLE",
|
||||
f"+incdir+{tmp_path / 'rtl'}",
|
||||
f"+incdir+{ROOT / 'rtl/mini/top'}",
|
||||
f"+incdir+{ROOT / 'rtl/clusterip/common/rtl'}",
|
||||
str(ROOT / "rtl/clusterip/common/rtl/interface/nmi_if.sv"),
|
||||
str(ROOT / "rtl/clusterip/common/rtl/utils/register.sv"),
|
||||
str(ROOT / "rtl/ip/native/sysctrl.sv"),
|
||||
str(ROOT / "tests/rtl/sysctrl_fault_tb.sv"),
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
converted = tmp_path / "sysctrl_fault_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 / "sysctrl_fault_tb"
|
||||
subprocess.run(
|
||||
[iverilog, "-g2012", "-s", "sysctrl_fault_tb", "-o", str(simulation), str(converted)],
|
||||
check=True,
|
||||
)
|
||||
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
|
||||
assert "sysctrl fault registers test passed" in result.stdout
|
||||
Reference in new issue
Block a user