mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: add CLINT peripheral support and related enhancements
- Updated the formal file list generator to include CLINT source files. - Added CLINT timebase to the clock and reset domains configuration. - Enhanced the SOC topology generation to include CLINT interrupts and their mappings. - Introduced a new CLINT testbench to validate multi-hart and timebase functionality. - Modified the software build configuration to define CLINT timebase frequency. - Updated the RCU module to integrate CLINT timebase generation. - Adjusted the IRQ configuration to reflect changes in CLINT interrupt signals. - Added user-visible flags for interrupts in the topology configuration. - Enhanced the clock reset domain checks to include CLINT timebase. - Updated the memory map tests to validate CLINT peripheral integration.
This commit is contained in:
55 files changed
+1706
-956
No files matched your search
@@ -0,0 +1,135 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module clint_tb;
|
||||
logic clk_i = 1'b0;
|
||||
logic ref_clk_i = 1'b0;
|
||||
logic rst_n_i = 1'b0;
|
||||
logic tick_i = 1'b0;
|
||||
logic timebase_tick;
|
||||
logic [31:0] value;
|
||||
integer timebase_tick_count = 0;
|
||||
ribp_if ribp ();
|
||||
clint_if #(.HART_NUM(2)) clint ();
|
||||
|
||||
always #5 clk_i = ~clk_i;
|
||||
always #2 ref_clk_i = ~ref_clk_i;
|
||||
|
||||
always @(posedge clk_i) begin
|
||||
if (timebase_tick) timebase_tick_count <= timebase_tick_count + 1;
|
||||
end
|
||||
|
||||
clint_timebase #(
|
||||
.REF_CLK_HZ (4),
|
||||
.TIMEBASE_HZ(1)
|
||||
) u_timebase (
|
||||
.ref_clk_i (ref_clk_i),
|
||||
.ref_rst_n_i(rst_n_i),
|
||||
.sys_clk_i (clk_i),
|
||||
.sys_rst_n_i(rst_n_i),
|
||||
.tick_o (timebase_tick)
|
||||
);
|
||||
|
||||
ribp_clint #(
|
||||
.HART_NUM(2)
|
||||
) u_dut (
|
||||
.clk_i,
|
||||
.rst_n_i,
|
||||
.timebase_tick_i(tick_i),
|
||||
.ribp,
|
||||
.clint
|
||||
);
|
||||
|
||||
task automatic ribp_write(input logic [31:0] address, input logic [31:0] data,
|
||||
input logic [3:0] strobe, input logic expected_error);
|
||||
begin
|
||||
@(negedge clk_i);
|
||||
ribp.valid = 1'b1;
|
||||
ribp.addr = address;
|
||||
ribp.wdata = data;
|
||||
ribp.wstrb = strobe;
|
||||
do @(negedge clk_i); while (!ribp.ready);
|
||||
if (ribp.resp_err !== expected_error) begin
|
||||
$fatal(1, "write %h error=%b expected=%b", address, ribp.resp_err, expected_error);
|
||||
end
|
||||
ribp.valid = 1'b0;
|
||||
ribp.wstrb = '0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic ribp_read(input logic [31:0] address, output logic [31:0] data,
|
||||
input logic expected_error);
|
||||
begin
|
||||
@(negedge clk_i);
|
||||
ribp.valid = 1'b1;
|
||||
ribp.addr = address;
|
||||
ribp.wdata = '0;
|
||||
ribp.wstrb = '0;
|
||||
do @(negedge clk_i); while (!ribp.ready);
|
||||
data = ribp.rdata;
|
||||
if (ribp.resp_err !== expected_error) begin
|
||||
$fatal(1, "read %h error=%b expected=%b", address, ribp.resp_err, expected_error);
|
||||
end
|
||||
ribp.valid = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic pulse_tick;
|
||||
begin
|
||||
@(negedge clk_i);
|
||||
tick_i = 1'b1;
|
||||
@(negedge clk_i);
|
||||
tick_i = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
ribp.valid = 1'b0;
|
||||
ribp.addr = '0;
|
||||
ribp.wdata = '0;
|
||||
ribp.wstrb = '0;
|
||||
|
||||
repeat (4) @(posedge clk_i);
|
||||
rst_n_i = 1'b1;
|
||||
|
||||
ribp_read(32'h0000_0000, value, 1'b0);
|
||||
if (value != 32'd0 || clint.software_irq_o != 2'b00) begin
|
||||
$fatal(1, "MSIP reset state is invalid");
|
||||
end
|
||||
ribp_read(32'h0000_4000, value, 1'b0);
|
||||
if (value != 32'hFFFF_FFFF) $fatal(1, "MTIMECMP low reset state is invalid");
|
||||
ribp_read(32'h0000_4004, value, 1'b0);
|
||||
if (value != 32'hFFFF_FFFF || clint.timer_irq_o != 2'b00) begin
|
||||
$fatal(1, "MTIMECMP high reset state is invalid");
|
||||
end
|
||||
|
||||
ribp_write(32'h0000_0004, 32'h0000_0001, 4'h1, 1'b0);
|
||||
if (clint.software_irq_o != 2'b10) $fatal(1, "hart 1 MSIP did not assert");
|
||||
ribp_write(32'h0000_0004, 32'h0000_0000, 4'h1, 1'b0);
|
||||
if (clint.software_irq_o != 2'b00) $fatal(1, "hart 1 MSIP did not clear");
|
||||
|
||||
ribp_write(32'h0000_BFFC, 32'd0, 4'hF, 1'b0);
|
||||
ribp_write(32'h0000_BFF8, 32'd10, 4'hF, 1'b0);
|
||||
ribp_read(32'h0000_BFF8, value, 1'b0);
|
||||
if (value != 32'd10) $fatal(1, "MTIME write/read failed: %h", value);
|
||||
|
||||
ribp_write(32'h0000_4004, 32'd0, 4'hF, 1'b0);
|
||||
ribp_write(32'h0000_4000, 32'd12, 4'hF, 1'b0);
|
||||
pulse_tick();
|
||||
pulse_tick();
|
||||
repeat (2) @(posedge clk_i);
|
||||
if (clint.timer_irq_o[0] != 1'b1) $fatal(1, "hart 0 timer IRQ did not assert");
|
||||
if (clint.timer_irq_o[1] != 1'b0) $fatal(1, "hart 1 timer IRQ asserted unexpectedly");
|
||||
|
||||
ribp_write(32'h0000_4001, 32'd0, 4'hF, 1'b1);
|
||||
ribp_read(32'h0000_0010, value, 1'b1);
|
||||
if (value != 32'd0) $fatal(1, "invalid read did not return zero");
|
||||
|
||||
repeat (20) @(posedge clk_i);
|
||||
if (timebase_tick_count < 2) begin
|
||||
$fatal(1, "reference-clock timebase did not produce synchronized ticks");
|
||||
end
|
||||
|
||||
$display("CLINT standard map, multi-hart, IRQ, error, and timebase test passed");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -25,17 +25,18 @@ module pll_ctrl_tb;
|
||||
always #27 aud_clk_i = ~aud_clk_i;
|
||||
|
||||
rcu u_rcu (
|
||||
.ext_clk_i (ext_clk_i),
|
||||
.aud_clk_i (aud_clk_i),
|
||||
.ext_rst_n_i (rst_n_i),
|
||||
.ext_clk_i (ext_clk_i),
|
||||
.aud_clk_i (aud_clk_i),
|
||||
.ext_rst_n_i (rst_n_i),
|
||||
`ifdef HAVE_PLL
|
||||
.xtal_clk_i (xtal_clk_i),
|
||||
.xtal_clk_i (xtal_clk_i),
|
||||
`endif
|
||||
.pll_ctrl (pll_ctrl),
|
||||
.sys_clk_o (sys_clk_o),
|
||||
.sys_rst_n_o (sys_rst_n_o),
|
||||
.aud_rst_n_o (aud_rst_n_o),
|
||||
.sys_clkdiv4_o(sys_clkdiv4_o)
|
||||
.pll_ctrl (pll_ctrl),
|
||||
.sys_clk_o (sys_clk_o),
|
||||
.sys_rst_n_o (sys_rst_n_o),
|
||||
.aud_rst_n_o (aud_rst_n_o),
|
||||
.sys_clkdiv4_o (sys_clkdiv4_o),
|
||||
.timebase_tick_o()
|
||||
);
|
||||
|
||||
ribp_sysctrl u_sysctrl (
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""CLINT RTL regression tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PERIPHERAL = ROOT / "rtl/ip/ribp/peripheral"
|
||||
|
||||
|
||||
def test_clint_standard_map_multi_hart_and_timebase(tmp_path: Path) -> None:
|
||||
iverilog = shutil.which("iverilog")
|
||||
vvp = shutil.which("vvp")
|
||||
if iverilog is None or vvp is None:
|
||||
return
|
||||
|
||||
common = ROOT / "rtl/managed/clusterip/common/rtl"
|
||||
source_list = tmp_path / "clint.fl"
|
||||
source_list.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"+define+SV_ASSRT_DISABLE",
|
||||
f"+incdir+{common}",
|
||||
f"+incdir+{common / 'interface'}",
|
||||
f"+incdir+{common / 'utils'}",
|
||||
f"+incdir+{common / 'clkrst'}",
|
||||
f"+incdir+{PERIPHERAL}",
|
||||
str(common / "interface/ribp_if.sv"),
|
||||
str(common / "utils/register.sv"),
|
||||
str(common / "utils/edge_det.sv"),
|
||||
str(common / "cdc/cdc_sync.sv"),
|
||||
str(common / "clkrst/counter.sv"),
|
||||
str(PERIPHERAL / "clint_if.sv"),
|
||||
str(PERIPHERAL / "clint_timebase.sv"),
|
||||
str(PERIPHERAL / "clint_reg.sv"),
|
||||
str(PERIPHERAL / "clint_core.sv"),
|
||||
str(PERIPHERAL / "ribp_clint.sv"),
|
||||
str(ROOT / "tests/rtl/clint_tb.sv"),
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
converted = tmp_path / "clint_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 / "clint_tb"
|
||||
subprocess.run(
|
||||
[iverilog, "-g2012", "-s", "clint_tb", "-o", str(simulation), str(converted)],
|
||||
check=True,
|
||||
)
|
||||
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
|
||||
assert "CLINT standard map, multi-hart, IRQ, error, and timebase test passed" in result.stdout
|
||||
@@ -42,7 +42,14 @@ def test_clock_reset_domain_inventory_matches_the_rcu() -> None:
|
||||
assert {
|
||||
(crossing["name"], crossing["source"], crossing["destination"])
|
||||
for crossing in document["crossings"]
|
||||
} >= {("jtag_dmi", "jtag", "system")}
|
||||
} >= {
|
||||
("clint_timebase", "external", "system"),
|
||||
("jtag_dmi", "jtag", "system"),
|
||||
}
|
||||
clint = next(
|
||||
crossing for crossing in document["crossings"] if crossing["name"] == "clint_timebase"
|
||||
)
|
||||
assert clint["primitive"] == "edge_det"
|
||||
|
||||
|
||||
def test_clock_reset_domain_inventory_rejects_unknown_domain_and_instance(tmp_path: Path) -> None:
|
||||
|
||||
@@ -227,7 +227,10 @@ def test_pll_controller_reconfigures_and_falls_back_to_the_safe_clock(tmp_path:
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_rst_ctrlr.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_2phase.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/clkrst/rst_sync.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/clkrst/counter.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/edge_det.sv"),
|
||||
str(ROOT / "rtl/ip/ribp/peripheral/pll_ctrl_if.sv"),
|
||||
str(ROOT / "rtl/ip/ribp/peripheral/clint_timebase.sv"),
|
||||
str(ROOT / "rtl/ip/ribp/peripheral/sysctrl.sv"),
|
||||
str(ROOT / "rtl/tech/tc_clk.sv"),
|
||||
str(ROOT / "rtl/tech/tc_pll.sv"),
|
||||
|
||||
@@ -205,6 +205,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
pll_rcu_filelist = tmp_path / "pll_rcu.fl"
|
||||
gpio_user_filelist = tmp_path / "gpio_user.fl"
|
||||
ws2812_filelist = tmp_path / "ws2812.fl"
|
||||
clint_filelist = tmp_path / "clint.fl"
|
||||
assert generate_formal_filelist("bus", bus_filelist, memory_map, topology, user_extensions)
|
||||
assert generate_formal_filelist(
|
||||
"rib_adapter", rib_adapter_filelist, memory_map, topology, user_extensions
|
||||
@@ -224,6 +225,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
assert generate_formal_filelist(
|
||||
"ws2812", ws2812_filelist, memory_map, topology, user_extensions
|
||||
)
|
||||
assert generate_formal_filelist(
|
||||
"clint", clint_filelist, memory_map, topology, user_extensions
|
||||
)
|
||||
|
||||
bus = parse_filelists([bus_filelist], require_files=False)
|
||||
rib_adapter = parse_filelists([rib_adapter_filelist], require_files=False)
|
||||
@@ -232,6 +236,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
pll_rcu = parse_filelists([pll_rcu_filelist], require_files=False)
|
||||
gpio_user = parse_filelists([gpio_user_filelist], require_files=False)
|
||||
ws2812 = parse_filelists([ws2812_filelist], require_files=False)
|
||||
clint = parse_filelists([clint_filelist], require_files=False)
|
||||
assert "+define+SV_ASSRT_DISABLE" in bus.defines
|
||||
assert ROOT / "rtl/mini/top/bus.sv" in bus.files
|
||||
assert ROOT / "rtl/mini/top/rib_if.sv" in bus.files
|
||||
@@ -254,6 +259,8 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
assert ROOT / "rtl/ip/ribp/serial/ribp_ws2812.sv" in ws2812.files
|
||||
assert ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv" in ws2812.files
|
||||
assert ROOT / "rtl/mini/formal/ws2812_formal.sv" in ws2812.files
|
||||
assert ROOT / "rtl/ip/ribp/peripheral/ribp_clint.sv" in clint.files
|
||||
assert ROOT / "rtl/mini/formal/clint_formal.sv" in clint.files
|
||||
|
||||
|
||||
def test_sysctrl_formal_properties_use_exported_user_core_shape() -> None:
|
||||
@@ -751,8 +758,8 @@ def test_benchmark_profile_uses_functional_sram_and_reserved_data() -> None:
|
||||
assert re.search(r"^PDK_BEHAV\s+\?= NO$", makefile, re.MULTILINE)
|
||||
assert "PDK_BEHAV HAVE_SVA" in makefile
|
||||
assert "PDK_BEHAV=YES is for functional simulation" in makefile
|
||||
assert "HAVE_SRAM_MACRO := YES" in profile
|
||||
assert "PDK_BEHAV := YES" in profile
|
||||
assert re.search(r"^HAVE_SRAM_MACRO\s*:= YES$", profile, re.MULTILINE)
|
||||
assert re.search(r"^PDK_BEHAV\s*:= YES$", profile, re.MULTILINE)
|
||||
assert "RS_BENCHMARK_SRAM_OFFSET UINT32_C(0x10000)" in benchmark
|
||||
|
||||
|
||||
|
||||
@@ -97,9 +97,10 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
|
||||
assert ".apb_rib(u_apb_rib_if)" in bus_fabric
|
||||
assert ".rib(u_rib_if)" in bus_fabric
|
||||
assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config
|
||||
assert "`define SOC_USER_IRQ_MASK 32'h0003FFFC" in irq_config
|
||||
assert "`define SOC_IRQ_RIBP_WIDTH 11" 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 rib_irq
|
||||
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[5] = u_tmr_if.irq_o;" in apb_irq
|
||||
assert "s_irq[16] = s_apb_irq[6];" in irq_wiring
|
||||
@@ -122,8 +123,8 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
|
||||
for interrupt in document["interrupts"]
|
||||
]
|
||||
assert mappings[:18] == [
|
||||
("clint_software", "ribp", 0, 0, "u_clint_if.sfr_irq_o"),
|
||||
("clint_timer", "ribp", 1, 1, "u_clint_if.tmr_irq_o"),
|
||||
("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"),
|
||||
|
||||
Reference in new issue
Block a user