mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
feat: add PLL controller and related testbench functionality
- Removed legacy clock definitions and added a dynamic PLL timing template in retrosoc_pll.sdc. - Introduced a new SystemVerilog testbench (pll_ctrl_tb.sv) for the PLL controller, including clock generation, register read/write tasks, and validation of PLL configuration and timing. - Updated sysctrl_fault_tb.sv to include PLL controller interface and initialization. - Enhanced memory map tests to verify PLL configuration and status offsets. - Added tests for PLL controller dynamic reconfiguration and fallback to safe clock.
This commit is contained in:
28 files changed
+1112
-477
No files matched your search
@@ -0,0 +1,170 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module pll_ctrl_tb;
|
||||
logic ext_clk_i = 1'b0;
|
||||
logic xtal_clk_i = 1'b0;
|
||||
logic aud_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 ();
|
||||
pll_ctrl_if pll_ctrl ();
|
||||
logic sys_clk_o;
|
||||
logic sys_rst_n_o;
|
||||
logic aud_rst_n_o;
|
||||
logic sys_clkdiv4_o;
|
||||
logic [31:0] read_data;
|
||||
time edge_start;
|
||||
time edge_end;
|
||||
|
||||
always #7 ext_clk_i = ~ext_clk_i;
|
||||
always #21 xtal_clk_i = ~xtal_clk_i;
|
||||
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),
|
||||
`ifdef HAVE_PLL
|
||||
.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)
|
||||
);
|
||||
|
||||
nmi_sysctrl u_sysctrl (
|
||||
.clk_i (sys_clk_o),
|
||||
.rst_n_i (sys_rst_n_o),
|
||||
.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),
|
||||
.pll_ctrl (pll_ctrl)
|
||||
);
|
||||
|
||||
task automatic read_register(input logic [31:0] address, output logic [31:0] data);
|
||||
begin
|
||||
@(negedge sys_clk_o);
|
||||
nmi.addr = address;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
nmi.valid = 1'b1;
|
||||
while (!nmi.ready) @(posedge sys_clk_o);
|
||||
data = nmi.rdata;
|
||||
@(negedge sys_clk_o);
|
||||
nmi.valid = 1'b0;
|
||||
while (nmi.ready) @(posedge sys_clk_o);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic write_register(input logic [31:0] address, input logic [31:0] data);
|
||||
begin
|
||||
@(negedge sys_clk_o);
|
||||
nmi.addr = address;
|
||||
nmi.wdata = data;
|
||||
nmi.wstrb = 4'hF;
|
||||
nmi.valid = 1'b1;
|
||||
while (!nmi.ready) @(posedge sys_clk_o);
|
||||
@(negedge sys_clk_o);
|
||||
nmi.valid = 1'b0;
|
||||
while (nmi.ready) @(posedge sys_clk_o);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic wait_for_completion(output logic [31:0] status);
|
||||
integer index;
|
||||
logic complete;
|
||||
begin
|
||||
status = '0;
|
||||
complete = 1'b0;
|
||||
for (index = 0; index < 3000; index = index + 1) begin
|
||||
if (!complete) begin
|
||||
read_register(32'h1000_B01C, status);
|
||||
complete = !status[4];
|
||||
end
|
||||
end
|
||||
if (!complete) begin
|
||||
$fatal(1, "pll configuration did not complete");
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
nmi.valid = 1'b0;
|
||||
nmi.addr = '0;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
sysctrl.core_sel_i = '0;
|
||||
#100;
|
||||
rst_n_i = 1'b1;
|
||||
repeat (12) @(posedge sys_clk_o);
|
||||
|
||||
read_register(32'h1000_B01C, read_data);
|
||||
`ifdef HAVE_PLL
|
||||
if (read_data !== 32'h0000_0500) begin
|
||||
$fatal(1, "pll reset status is not the external safe clock");
|
||||
end
|
||||
`else
|
||||
if (read_data !== 32'h0000_0100) begin
|
||||
$fatal(1, "unsupported pll reset status is not the external safe clock");
|
||||
end
|
||||
`endif
|
||||
|
||||
write_register(32'h1000_B008, 32'd4);
|
||||
write_register(32'h1000_B00C, 32'd1);
|
||||
wait_for_completion(read_data);
|
||||
|
||||
`ifdef HAVE_PLL
|
||||
if ($test$plusargs("pll_lock_fail")) begin
|
||||
if (read_data !== 32'h0000_05A0) begin
|
||||
$fatal(1, "pll lock timeout did not keep the external safe clock");
|
||||
end
|
||||
$display("pll controller timeout test passed");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (read_data !== 32'h0000_060C) begin
|
||||
$fatal(1, "pll configuration did not select the requested frequency");
|
||||
end
|
||||
repeat (4) @(posedge sys_clk_o);
|
||||
@(posedge sys_clk_o);
|
||||
edge_start = $time;
|
||||
@(posedge sys_clk_o);
|
||||
edge_end = $time;
|
||||
if ((edge_end - edge_start) > 9) begin
|
||||
$fatal(1, "pll behavioral output is slower than 120MHz");
|
||||
end
|
||||
|
||||
write_register(32'h1000_B008, 32'd0);
|
||||
write_register(32'h1000_B00C, 32'd1);
|
||||
wait_for_completion(read_data);
|
||||
if (read_data !== 32'h0000_0608) begin
|
||||
$fatal(1, "pll did not select the 24MHz profile");
|
||||
end
|
||||
repeat (4) @(posedge sys_clk_o);
|
||||
@(posedge sys_clk_o);
|
||||
edge_start = $time;
|
||||
@(posedge sys_clk_o);
|
||||
edge_end = $time;
|
||||
if ((edge_end - edge_start) < 35) begin
|
||||
$fatal(1, "pll behavioral output is faster than the 24MHz profile");
|
||||
end
|
||||
|
||||
$display("pll controller dynamic configuration test passed");
|
||||
`else
|
||||
if (read_data !== 32'h0000_0160) begin
|
||||
$fatal(1, "unsupported pll backend did not report the safe-clock error");
|
||||
end
|
||||
$display("pll controller capability gate test passed");
|
||||
`endif
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -9,6 +9,7 @@ module sysctrl_fault_tb;
|
||||
logic fault_reserved_i = 1'b0;
|
||||
nmi_if nmi ();
|
||||
sysctrl_if sysctrl ();
|
||||
pll_ctrl_if pll_ctrl ();
|
||||
|
||||
always #5 clk_i = ~clk_i;
|
||||
|
||||
@@ -20,7 +21,8 @@ module sysctrl_fault_tb;
|
||||
.fault_wstrb_i (fault_wstrb_i),
|
||||
.fault_reserved_i(fault_reserved_i),
|
||||
.nmi (nmi),
|
||||
.sysctrl (sysctrl)
|
||||
.sysctrl (sysctrl),
|
||||
.pll_ctrl (pll_ctrl)
|
||||
);
|
||||
|
||||
task automatic read_register(input logic [31:0] address, output logic [31:0] data);
|
||||
@@ -54,11 +56,19 @@ module sysctrl_fault_tb;
|
||||
|
||||
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;
|
||||
nmi.valid = 1'b0;
|
||||
nmi.addr = '0;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
sysctrl.core_sel_i = '0;
|
||||
pll_ctrl.req_ready_i = 1'b1;
|
||||
pll_ctrl.rsp_active_sel_i = '0;
|
||||
pll_ctrl.rsp_active_valid_i = 1'b0;
|
||||
pll_ctrl.rsp_safe_clk_i = 1'b1;
|
||||
pll_ctrl.rsp_pll_lock_i = 1'b0;
|
||||
pll_ctrl.rsp_error_i = '0;
|
||||
pll_ctrl.rsp_valid_i = 1'b0;
|
||||
pll_ctrl.capable_i = 1'b0;
|
||||
repeat (2) @(posedge clk_i);
|
||||
rst_n_i = 1'b1;
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
|
||||
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 "SOC_SYSCTRL_PLL_CFG_OFFSET 32'h00000008" in rtl
|
||||
assert "SOC_SYSCTRL_PLL_STATUS_OFFSET 32'h0000001C" in rtl
|
||||
assert "RS_SOC_SYSCTRL_PLL_CFG_OFFSET UINT32_C(0x00000008)" in header
|
||||
assert "RS_SOC_SYSCTRL_PLL_STATUS_OFFSET UINT32_C(0x0000001C)" in header
|
||||
assert "PSRAM (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 0x00800000" in linker
|
||||
|
||||
|
||||
@@ -151,6 +155,7 @@ def test_sysctrl_fault_registers_record_and_clear_pending(tmp_path: Path) -> Non
|
||||
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
|
||||
str(ROOT / "rtl/ip/native/peripheral/pll_ctrl_if.sv"),
|
||||
str(ROOT / "rtl/ip/native/peripheral/sysctrl.sv"),
|
||||
str(ROOT / "tests/rtl/sysctrl_fault_tb.sv"),
|
||||
"",
|
||||
@@ -179,6 +184,98 @@ def test_sysctrl_fault_registers_record_and_clear_pending(tmp_path: Path) -> Non
|
||||
assert "sysctrl fault registers test passed" in result.stdout
|
||||
|
||||
|
||||
def test_pll_controller_reconfigures_and_falls_back_to_the_safe_clock(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 / "pll_ctrl.fl"
|
||||
source_list.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"+define+SV_ASSRT_DISABLE",
|
||||
"+define+PDK_BEHAV",
|
||||
"+define+HAVE_PLL",
|
||||
f"+incdir+{tmp_path / 'rtl'}",
|
||||
f"+incdir+{ROOT / 'rtl/mini/top'}",
|
||||
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_if.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
|
||||
str(ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.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/ip/native/peripheral/pll_ctrl_if.sv"),
|
||||
str(ROOT / "rtl/ip/native/peripheral/sysctrl.sv"),
|
||||
str(ROOT / "rtl/tech/tc_clk.sv"),
|
||||
str(ROOT / "rtl/tech/tc_pll.sv"),
|
||||
str(ROOT / "rtl/mini/top/rcu.sv"),
|
||||
str(ROOT / "tests/rtl/pll_ctrl_tb.sv"),
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
converted = tmp_path / "pll_ctrl_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 / "pll_ctrl_tb"
|
||||
subprocess.run(
|
||||
[iverilog, "-g2012", "-s", "pll_ctrl_tb", "-o", str(simulation), str(converted)],
|
||||
check=True,
|
||||
)
|
||||
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
|
||||
assert "pll controller dynamic configuration test passed" in result.stdout
|
||||
result = subprocess.run(
|
||||
[vvp, str(simulation), "+pll_lock_fail"], text=True, capture_output=True, check=True
|
||||
)
|
||||
assert "pll controller timeout test passed" in result.stdout
|
||||
|
||||
unsupported_list = tmp_path / "pll_unsupported.fl"
|
||||
unsupported_list.write_text(
|
||||
source_list.read_text(encoding="utf-8").replace("+define+HAVE_PLL\n", ""),
|
||||
encoding="utf-8",
|
||||
)
|
||||
unsupported_converted = tmp_path / "pll_unsupported_tb.v"
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
|
||||
"-f",
|
||||
str(unsupported_list),
|
||||
"--output",
|
||||
str(unsupported_converted),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
unsupported_simulation = tmp_path / "pll_unsupported_tb"
|
||||
subprocess.run(
|
||||
[
|
||||
iverilog,
|
||||
"-g2012",
|
||||
"-s",
|
||||
"pll_ctrl_tb",
|
||||
"-o",
|
||||
str(unsupported_simulation),
|
||||
str(unsupported_converted),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
result = subprocess.run(
|
||||
[vvp, str(unsupported_simulation)], text=True, capture_output=True, check=True
|
||||
)
|
||||
assert "pll controller capability gate test passed" in result.stdout
|
||||
|
||||
|
||||
def test_sysctrl_does_not_expose_unused_i2c_or_qspi_select_registers() -> None:
|
||||
rtl = (ROOT / "rtl/ip/native/peripheral/sysctrl.sv").read_text(encoding="utf-8")
|
||||
header = (ROOT / "crt/include/retrosoc/core/soc.h").read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user