feat: add WS2812 support and related tests

- Replace onewire interface with ws2812 interface in retrosoc module.
- Include ws2812_math.c in C test sources.
- Add ws2812 header in test_runtime.c and implement ws2812 helper tests.
- Update ribp_topology_tb and soc_irq_topology_tb to handle ws2812 interface.
- Create ws2812_dma_tb and ws2812_tb for testing WS2812 DMA and waveform functionality.
- Enhance test_script_tools.py to include ws2812 in formal filelists.
- Modify test_soc_topology.py to ensure proper IRQ mapping for ws2812.
- Introduce test_ws2812.py for WS2812 register, waveform, and streaming regression tests.
This commit is contained in:
maksyuki committed 2026-08-07 18:18:51 +08:00
1 parent 17d486d2ef
commit 1eb342245c
46 files changed
+2635 -616

No files matched your search

+35
View File
@@ -5,6 +5,7 @@
#include <retrosoc/core/wait.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/ws2812.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/lib/stdlib.h>
#include <retrosoc/lib/string.h>
@@ -152,6 +153,39 @@ static int test_wait_helper(void)
return 0;
}
static int test_ws2812_helpers(void)
{
const rs_ws2812_config_t config = {
72000000U,
RS_WS2812_DEFAULT_BIT_PERIOD_NS,
RS_WS2812_DEFAULT_T0H_NS,
RS_WS2812_DEFAULT_T1H_NS,
RS_WS2812_DEFAULT_RESET_NS,
4U,
};
rs_ws2812_config_t invalid = config;
rs_ws2812_timing_t timing;
if ((rs_ws2812_pack_grb(0x12U, 0x34U, 0x56U) != UINT32_C(0x341256)) ||
(rs_ws2812_timing_from_ns(&config, &timing) != RS_OK) ||
(timing.bit_cycles != 90U) || (timing.t0h_cycles != 25U) ||
(timing.t1h_cycles != 50U) || (timing.reset_cycles != 21600U)) {
return 1;
}
invalid.t0h_ns = invalid.t1h_ns;
if ((rs_ws2812_timing_from_ns(&invalid, &timing) != RS_EINVAL) ||
(rs_ws2812_timing_from_ns(NULL, &timing) != RS_EINVAL) ||
(rs_ws2812_timing_from_ns(&config, NULL) != RS_EINVAL)) {
return 2;
}
invalid = config;
invalid.source_clock_hz = 1U;
if (rs_ws2812_timing_from_ns(&invalid, &timing) != RS_EINVAL) {
return 3;
}
return 0;
}
static int test_wav_parser(void)
{
static const uint8_t wav[] = {
@@ -200,6 +234,7 @@ int main(void)
test_formatter(),
test_compiler_helpers(),
test_wait_helper(),
test_ws2812_helpers(),
test_wav_parser(),
test_video_parser(),
};
+3 -3
View File
@@ -87,7 +87,7 @@ module ribp_topology_tb;
u_spisd_ribp_if.ready = 1'b1;
u_i2c0_ribp_if.ready = 1'b1;
u_i2s_ribp_if.ready = 1'b1;
u_onewire_ribp_if.ready = 1'b1;
u_ws2812_ribp_if.ready = 1'b1;
u_xpi_ribp_if.ready = 1'b1;
u_dma_ribp_if.ready = 1'b1;
u_sysctrl_ribp_if.ready = 1'b1;
@@ -106,7 +106,7 @@ module ribp_topology_tb;
u_spisd_ribp_if.rdata = 32'h5555_5555;
u_i2c0_ribp_if.rdata = 32'h6666_6666;
u_i2s_ribp_if.rdata = 32'h7777_7777;
u_onewire_ribp_if.rdata = 32'h8888_8888;
u_ws2812_ribp_if.rdata = 32'h8888_8888;
u_xpi_ribp_if.rdata = 32'h9999_9999;
u_dma_ribp_if.rdata = 32'hAAAA_AAAA;
u_sysctrl_ribp_if.rdata = 32'hBBBB_BBBB;
@@ -125,7 +125,7 @@ module ribp_topology_tb;
u_spisd_ribp_if.resp_err = 1'b0;
u_i2c0_ribp_if.resp_err = 1'b0;
u_i2s_ribp_if.resp_err = 1'b0;
u_onewire_ribp_if.resp_err = 1'b0;
u_ws2812_ribp_if.resp_err = 1'b0;
u_xpi_ribp_if.resp_err = 1'b0;
u_dma_ribp_if.resp_err = 1'b0;
u_sysctrl_ribp_if.resp_err = 1'b0;
+7 -3
View File
@@ -23,13 +23,17 @@ module soc_irq_topology_tb;
s_apb_irq = '0;
expect_irq('0);
for (int bit_index = 0; bit_index < `SOC_IRQ_RIBP_WIDTH; bit_index++) begin
for (int bit_index = 0; bit_index < 10; bit_index++) begin
s_ribp_irq = '0;
s_ribp_irq[bit_index] = 1'b1;
s_apb_irq = '0;
expect_irq(32'd1 << bit_index);
end
s_ribp_irq = '0;
s_ribp_irq[10] = 1'b1;
expect_irq(32'd1 << 17);
for (int bit_index = 0; bit_index < `SOC_IRQ_APB_WIDTH; bit_index++) begin
s_ribp_irq = '0;
s_apb_irq = '0;
@@ -37,8 +41,8 @@ module soc_irq_topology_tb;
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]);
if (s_irq[31:18] !== '0) begin
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq[31:18]);
end
$display("SoC topology IRQ routing test passed");
+257
View File
@@ -0,0 +1,257 @@
`timescale 1ns / 1ps
`include "rib_defs.svh"
`include "ws2812_define.svh"
module ws2812_dma_tb;
localparam logic [31:0] SOURCE_BASE = 32'h4000_0000;
localparam logic [31:0] WS2812_TXDATA = 32'h1000_8010;
localparam int BIT_CYCLES = 4;
localparam int T0H_CYCLES = 1;
localparam int T1H_CYCLES = 3;
localparam int RESET_CYCLES = 5;
typedef enum logic [1:0] {
BUS_IDLE,
BUS_READ_RESP,
BUS_WRITE_DATA,
BUS_WRITE_RESP
} bus_state_t;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic dma_start = 1'b0;
logic dma_done;
logic dma_error;
logic [ 2:0] dma_error_code;
logic [31:0] dma_error_addr;
logic [ 1:0] dma_fsm;
logic host_active = 1'b1;
logic host_valid = 1'b0;
logic [31:0] host_addr = '0;
logic [31:0] host_wdata = '0;
logic [ 3:0] host_wstrb = '0;
logic dma_ws_valid;
bus_state_t bus_state_q = BUS_IDLE;
logic [31:0] bus_addr_q = '0;
integer dma_write_count = 0;
integer dma_backpressure_cycles = 0;
logic [23:0] pixels [0:5];
integer observed_pixel = 0;
integer observed_bit = 0;
integer observed_cycle = 0;
integer reset_low_cycles = 0;
dma_hw_trg_if hw_trg ();
rib_if dma_rib ();
ribp_if ws_ribp ();
ws2812_if ws2812 ();
always #5 clk_i = ~clk_i;
initial begin
repeat (5000) @(posedge clk_i);
$fatal(1, "WS2812 DMA integration test timeout");
end
assign ws_ribp.valid = host_active ? host_valid : dma_ws_valid;
assign ws_ribp.addr = host_active ? host_addr : bus_addr_q;
assign ws_ribp.wdata = host_active ? host_wdata : dma_rib.wdata;
assign ws_ribp.wstrb = host_active ? host_wstrb : dma_rib.wstrb;
assign dma_rib.cmd_ready = bus_state_q == BUS_IDLE;
assign dma_rib.w_ready = (bus_state_q == BUS_WRITE_DATA) && ws_ribp.ready;
assign dma_rib.rsp_valid = (bus_state_q == BUS_READ_RESP) || (bus_state_q == BUS_WRITE_RESP);
assign dma_rib.rdata = pixels[bus_addr_q[4:2]];
assign dma_rib.resp_err = 1'b0;
assign dma_rib.resp_code = `RIB_RESP_OK;
assign dma_rib.rsp_beat = 2'd0;
assign dma_rib.rsp_last = 1'b1;
assign dma_ws_valid = (bus_state_q == BUS_WRITE_DATA) && dma_rib.w_valid;
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
bus_state_q <= BUS_IDLE;
bus_addr_q <= '0;
dma_write_count <= 0;
dma_backpressure_cycles <= 0;
end else begin
if ((bus_state_q == BUS_WRITE_DATA) && dma_rib.w_valid && !dma_rib.w_ready) begin
dma_backpressure_cycles <= dma_backpressure_cycles + 1;
end
unique case (bus_state_q)
BUS_IDLE: begin
if (dma_rib.cmd_valid && dma_rib.cmd_ready) begin
bus_addr_q <= dma_rib.cmd_addr;
if (dma_rib.cmd_len != `RIB_LEN_INCR1) begin
$fatal(1, "fixed-destination WS2812 DMA transfer used a burst");
end
if (dma_rib.cmd_write) begin
if (dma_rib.cmd_addr != WS2812_TXDATA) begin
$fatal(1, "DMA wrote unexpected destination %h", dma_rib.cmd_addr);
end
bus_state_q <= BUS_WRITE_DATA;
end else begin
if ((dma_rib.cmd_addr < (SOURCE_BASE + 32'd4)) ||
(dma_rib.cmd_addr > (SOURCE_BASE + 32'd20))) begin
$fatal(1, "DMA read unexpected source %h", dma_rib.cmd_addr);
end
bus_state_q <= BUS_READ_RESP;
end
end
end
BUS_READ_RESP: begin
if (dma_rib.rsp_valid && dma_rib.rsp_ready) begin
bus_state_q <= BUS_IDLE;
end
end
BUS_WRITE_DATA: begin
if (dma_rib.w_valid && dma_rib.w_ready) begin
if (!dma_rib.wlast) begin
$fatal(1, "INCR1 WS2812 write did not assert wlast");
end
dma_write_count <= dma_write_count + 1;
bus_state_q <= BUS_WRITE_RESP;
end
end
BUS_WRITE_RESP: begin
if (dma_rib.rsp_valid && dma_rib.rsp_ready) begin
bus_state_q <= BUS_IDLE;
end
end
default: bus_state_q <= BUS_IDLE;
endcase
end
end
dma_core u_dma_core (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.mode_i (3'd0),
.srcaddr_i (SOURCE_BASE + 32'd4),
.srcincr_i (1'b1),
.dstaddr_i (WS2812_TXDATA),
.dstincr_i (1'b0),
.xferlen_i (32'd5),
.start_i (dma_start),
.stop_i (1'b0),
.reset_i (1'b0),
.done_o (dma_done),
.error_o (dma_error),
.error_code_o(dma_error_code),
.error_addr_o(dma_error_addr),
.fsm_o (dma_fsm),
.hw_trg (hw_trg),
.rib (dma_rib)
);
ribp_ws2812 #(
.TX_FIFO_DEPTH(4)
) u_ws2812 (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.ribp (ws_ribp),
.ws2812 (ws2812)
);
task automatic host_write(input logic [31:0] address, input logic [31:0] data);
begin
@(negedge clk_i);
host_addr = address;
host_wdata = data;
host_wstrb = 4'hF;
host_valid = 1'b1;
while (!ws_ribp.ready) begin
@(posedge clk_i);
#1;
end
if (ws_ribp.resp_err) begin
$fatal(1, "host write %h failed", address);
end
@(negedge clk_i);
host_valid = 1'b0;
host_wstrb = '0;
end
endtask
always @(posedge clk_i) begin
#1;
if (rst_n_i && u_ws2812.s_busy && !u_ws2812.s_reset_active) begin
integer high_cycles;
logic expected_data;
high_cycles = pixels[observed_pixel][23-observed_bit] ? T1H_CYCLES : T0H_CYCLES;
expected_data = observed_cycle < high_cycles;
if (ws2812.dat_o !== expected_data) begin
$fatal(1, "DMA waveform mismatch pixel=%0d bit=%0d cycle=%0d", observed_pixel,
observed_bit, observed_cycle);
end
observed_cycle = observed_cycle + 1;
if (observed_cycle == BIT_CYCLES) begin
observed_cycle = 0;
observed_bit = observed_bit + 1;
if (observed_bit == 24) begin
observed_bit = 0;
observed_pixel = observed_pixel + 1;
end
end
end
if (rst_n_i && u_ws2812.s_reset_active) begin
if (ws2812.dat_o !== 1'b0) begin
$fatal(1, "DMA frame reset interval drove the output high");
end
reset_low_cycles = reset_low_cycles + 1;
end
if (dma_error) begin
$fatal(1, "DMA failed code=%0d address=%h", dma_error_code, dma_error_addr);
end
end
initial begin
pixels[0] = 24'h800001;
pixels[1] = 24'h010203;
pixels[2] = 24'hA5A5A5;
pixels[3] = 24'h5A5A5A;
pixels[4] = 24'hFFFFFF;
pixels[5] = 24'h000000;
hw_trg.i2s_tx_proc = 1'b0;
hw_trg.i2s_rx_proc = 1'b0;
hw_trg.qspi_tx_proc = 1'b0;
hw_trg.qspi_rx_proc = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
host_write(`RIBP_WS2812_BIT_CYCLES, BIT_CYCLES);
host_write(`RIBP_WS2812_T0H_CYCLES, T0H_CYCLES);
host_write(`RIBP_WS2812_T1H_CYCLES, T1H_CYCLES);
host_write(`RIBP_WS2812_RESET_CYCLES, RESET_CYCLES);
host_write(`RIBP_WS2812_FIFO_WATERMARK, 3);
host_write(`RIBP_WS2812_INTR_ENABLE, 1);
host_write(`RIBP_WS2812_TXDATA, pixels[0]);
host_write(`RIBP_WS2812_FRAME_WORDS, 6);
host_write(`RIBP_WS2812_CTRL, 1);
host_active = 1'b0;
@(negedge clk_i);
dma_start = 1'b1;
@(negedge clk_i);
dma_start = 1'b0;
wait (dma_done);
wait (!u_ws2812.s_busy);
@(posedge clk_i);
#1;
if (dma_write_count != 5) begin
$fatal(1, "DMA completed %0d WS2812 writes, expected 5", dma_write_count);
end
if (dma_backpressure_cycles < 40) begin
$fatal(1, "DMA did not observe full-FIFO backpressure: %0d cycles", dma_backpressure_cycles);
end
if ((observed_pixel != 6) || (reset_low_cycles < RESET_CYCLES) || !ws2812.irq_o) begin
$fatal(1, "DMA frame did not complete pixels=%0d reset=%0d irq=%b", observed_pixel,
reset_low_cycles, ws2812.irq_o);
end
$display("WS2812 DMA backpressure integration test passed");
$finish;
end
endmodule
+298
View File
@@ -0,0 +1,298 @@
`timescale 1ns / 1ps
`include "ws2812_define.svh"
module ws2812_tb;
localparam int FIFO_DEPTH = 4;
localparam int BIT_CYCLES = 4;
localparam int T0H_CYCLES = 1;
localparam int T1H_CYCLES = 3;
localparam int RESET_CYCLES = 5;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
ribp_if ribp ();
ws2812_if ws2812 ();
logic monitor_enable = 1'b0;
logic [23:0] expected_pixels [0:7];
integer expected_pixel_count = 0;
integer observed_pixel = 0;
integer observed_bit = 0;
integer observed_cycle = 0;
integer expected_bit_cycles = BIT_CYCLES;
integer expected_t0h_cycles = T0H_CYCLES;
integer expected_t1h_cycles = T1H_CYCLES;
integer reset_low_cycles = 0;
integer test_stage = 0;
always #5 clk_i = ~clk_i;
initial begin
repeat (10000) @(posedge clk_i);
$fatal(1, "WS2812 test timeout at stage %0d state=%0d valid=%b ready=%b", test_stage,
u_dut.u_ws2812_core.s_state_q, ribp.valid, ribp.ready);
end
ribp_ws2812 #(
.TX_FIFO_DEPTH(FIFO_DEPTH)
) u_dut (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.ribp (ribp),
.ws2812 (ws2812)
);
task automatic ribp_write(input logic [31:0] address, input logic [31:0] data,
input logic [3:0] strobe, input logic expected_error,
output integer wait_cycles);
begin
@(negedge clk_i);
ribp.addr = address;
ribp.wdata = data;
ribp.wstrb = strobe;
ribp.valid = 1'b1;
wait_cycles = 0;
while (!ribp.ready) begin
@(posedge clk_i);
#1;
wait_cycles = wait_cycles + 1;
end
if (ribp.resp_err !== expected_error) begin
$fatal(1, "write %h error=%b expected=%b", address, ribp.resp_err, expected_error);
end
@(negedge clk_i);
ribp.valid = 1'b0;
ribp.wstrb = '0;
end
endtask
task automatic ribp_read(input logic [31:0] address, input logic expected_error,
output logic [31:0] data);
begin
@(negedge clk_i);
ribp.addr = address;
ribp.wdata = '0;
ribp.wstrb = '0;
ribp.valid = 1'b1;
while (!ribp.ready) begin
@(posedge clk_i);
#1;
end
if (ribp.resp_err !== expected_error) begin
$fatal(1, "read %h error=%b expected=%b", address, ribp.resp_err, expected_error);
end
data = ribp.rdata;
@(negedge clk_i);
ribp.valid = 1'b0;
end
endtask
task automatic write_ok(input logic [31:0] address, input logic [31:0] data);
integer ignored_wait;
begin
ribp_write(address, data, 4'hF, 1'b0, ignored_wait);
end
endtask
task automatic wait_until_idle;
logic [31:0] status;
begin
status = 32'h1;
while (status[`WS2812_STATUS_BUSY]) begin
ribp_read(`RIBP_WS2812_STATUS, 1'b0, status);
end
end
endtask
task automatic configure_timing;
begin
write_ok(`RIBP_WS2812_BIT_CYCLES, BIT_CYCLES);
write_ok(`RIBP_WS2812_T0H_CYCLES, T0H_CYCLES);
write_ok(`RIBP_WS2812_T1H_CYCLES, T1H_CYCLES);
write_ok(`RIBP_WS2812_RESET_CYCLES, RESET_CYCLES);
write_ok(`RIBP_WS2812_FIFO_WATERMARK, 3);
write_ok(`RIBP_WS2812_INTR_ENABLE, 4'hF);
end
endtask
always @(posedge clk_i) begin
#1;
if (!rst_n_i && ws2812.dat_o !== 1'b0) begin
$fatal(1, "WS2812 output is not low during reset");
end
if (monitor_enable && u_dut.s_busy && !u_dut.s_reset_active) begin
integer high_cycles;
logic expected_data;
high_cycles = expected_pixels[observed_pixel][23-observed_bit] ? expected_t1h_cycles :
expected_t0h_cycles;
expected_data = observed_cycle < high_cycles;
if (ws2812.dat_o !== expected_data) begin
$fatal(1, "waveform mismatch pixel=%0d bit=%0d cycle=%0d", observed_pixel, observed_bit,
observed_cycle);
end
observed_cycle = observed_cycle + 1;
if (observed_cycle == expected_bit_cycles) begin
observed_cycle = 0;
observed_bit = observed_bit + 1;
if (observed_bit == 24) begin
observed_bit = 0;
observed_pixel = observed_pixel + 1;
end
end
end
if (monitor_enable && u_dut.s_reset_active) begin
if (ws2812.dat_o !== 1'b0) begin
$fatal(1, "WS2812 output is high during latch/reset");
end
reset_low_cycles = reset_low_cycles + 1;
end
end
task automatic begin_monitor(input integer pixel_count);
begin
observed_pixel = 0;
observed_bit = 0;
observed_cycle = 0;
reset_low_cycles = 0;
expected_pixel_count = pixel_count;
monitor_enable = 1'b1;
end
endtask
task automatic end_monitor(input logic expect_complete_frame);
begin
monitor_enable = 1'b0;
if (expect_complete_frame && observed_pixel != expected_pixel_count) begin
$fatal(1, "observed %0d pixels, expected %0d", observed_pixel, expected_pixel_count);
end
if (reset_low_cycles < RESET_CYCLES) begin
$fatal(1, "latch/reset low interval was only %0d cycles", reset_low_cycles);
end
end
endtask
initial begin
logic [31:0] value;
integer wait_cycles;
ribp.valid = 1'b0;
ribp.addr = '0;
ribp.wdata = '0;
ribp.wstrb = '0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
test_stage = 1;
ribp_read(`RIBP_WS2812_IP_INFO, 1'b0, value);
if (value !== 32'h1804_0100) begin
$fatal(1, "unexpected IP_INFO %h", value);
end
ribp_read(`RIBP_WS2812_STATUS, 1'b0, value);
if (value[`WS2812_STATUS_CONFIG_VALID] || !value[`WS2812_STATUS_FIFO_EMPTY]) begin
$fatal(1, "unexpected reset status %h", value);
end
ribp_read(`RIBP_WS2812_CTRL, 1'b1, value);
ribp_write(8'h02, 32'd1, 4'hF, 1'b1, wait_cycles);
ribp_read(32'h00000100, 1'b1, value);
ribp_write(`RIBP_WS2812_TXDATA, 24'h112233, 4'h7, 1'b1, wait_cycles);
write_ok(`RIBP_WS2812_TXDATA, 24'h112233);
write_ok(`RIBP_WS2812_FRAME_WORDS, 1);
ribp_write(`RIBP_WS2812_CTRL, 32'h1, 4'hF, 1'b1, wait_cycles);
ribp_read(`RIBP_WS2812_ERROR_STATUS, 1'b0, value);
if (!value[`WS2812_ERROR_CONFIG]) begin
$fatal(1, "invalid timing did not set CONFIG error");
end
write_ok(`RIBP_WS2812_ERROR_STATUS, 32'h7);
write_ok(`RIBP_WS2812_CTRL, 32'h4);
configure_timing();
ribp_read(`RIBP_WS2812_STATUS, 1'b0, value);
if (!value[`WS2812_STATUS_CONFIG_VALID]) begin
$fatal(1, "legal timing was not accepted");
end
test_stage = 2;
for (integer index = 0; index < FIFO_DEPTH; index = index + 1) begin
write_ok(`RIBP_WS2812_TXDATA, index);
end
ribp_write(`RIBP_WS2812_TXDATA, 32'h55, 4'hF, 1'b1, wait_cycles);
write_ok(`RIBP_WS2812_CTRL, 32'h4);
write_ok(`RIBP_WS2812_ERROR_STATUS, 32'h7);
write_ok(`RIBP_WS2812_INTR_STATE, 32'hF);
test_stage = 3;
expected_pixels[0] = 24'h800001;
expected_pixels[1] = 24'h010203;
expected_pixels[2] = 24'hA5A5A5;
expected_pixels[3] = 24'h5A5A5A;
expected_pixels[4] = 24'hFFFFFF;
expected_pixels[5] = 24'h000000;
for (integer index = 0; index < FIFO_DEPTH; index = index + 1) begin
write_ok(`RIBP_WS2812_TXDATA, expected_pixels[index]);
end
write_ok(`RIBP_WS2812_FRAME_WORDS, 6);
begin_monitor(6);
write_ok(`RIBP_WS2812_CTRL, 32'h1);
write_ok(`RIBP_WS2812_TXDATA, expected_pixels[4]);
ribp_write(`RIBP_WS2812_TXDATA, expected_pixels[5], 4'hF, 1'b0, wait_cycles);
if (wait_cycles < (BIT_CYCLES * 20)) begin
$fatal(1, "full FIFO write was not backpressured: %0d cycles", wait_cycles);
end
wait_until_idle();
end_monitor(1'b1);
ribp_read(`RIBP_WS2812_INTR_STATE, 1'b0, value);
if ((value & 32'h3) != 32'h3 || !ws2812.irq_o) begin
$fatal(1, "done/low-watermark interrupt missing: %h", value);
end
write_ok(`RIBP_WS2812_INTR_STATE, 32'hF);
if (ws2812.irq_o) begin
$fatal(1, "IRQ did not clear");
end
test_stage = 4;
expected_pixels[0] = 24'h123456;
write_ok(`RIBP_WS2812_TXDATA, expected_pixels[0]);
write_ok(`RIBP_WS2812_FRAME_WORDS, 2);
begin_monitor(1);
write_ok(`RIBP_WS2812_CTRL, 32'h1);
wait_until_idle();
end_monitor(1'b1);
ribp_read(`RIBP_WS2812_ERROR_STATUS, 1'b0, value);
if (!value[`WS2812_ERROR_UNDERFLOW]) begin
$fatal(1, "underflow was not reported");
end
ribp_read(`RIBP_WS2812_FIFO_LEVEL, 1'b0, value);
if (value != 0) begin
$fatal(1, "underflow did not flush FIFO");
end
write_ok(`RIBP_WS2812_ERROR_STATUS, 32'h7);
write_ok(`RIBP_WS2812_INTR_STATE, 32'hF);
test_stage = 5;
expected_pixels[0] = 24'hABCDEF;
expected_pixels[1] = 24'h765432;
write_ok(`RIBP_WS2812_TXDATA, expected_pixels[0]);
write_ok(`RIBP_WS2812_TXDATA, expected_pixels[1]);
write_ok(`RIBP_WS2812_FRAME_WORDS, 2);
begin_monitor(2);
write_ok(`RIBP_WS2812_CTRL, 32'h1);
repeat (8) @(posedge clk_i);
write_ok(`RIBP_WS2812_CTRL, 32'h2);
wait_until_idle();
end_monitor(1'b0);
ribp_read(`RIBP_WS2812_INTR_STATE, 1'b0, value);
if (!value[`WS2812_INTR_ABORTED]) begin
$fatal(1, "abort completion was not reported");
end
write_ok(`RIBP_WS2812_INTR_TEST, 32'h1);
if (!ws2812.irq_o) begin
$fatal(1, "interrupt test did not assert IRQ");
end
write_ok(`RIBP_WS2812_INTR_STATE, 32'hF);
$display("WS2812 register, waveform, streaming, and error test passed");
$finish;
end
endmodule
+11 -1
View File
@@ -204,6 +204,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
sysctrl_filelist = tmp_path / "sysctrl.fl"
pll_rcu_filelist = tmp_path / "pll_rcu.fl"
gpio_user_filelist = tmp_path / "gpio_user.fl"
ws2812_filelist = tmp_path / "ws2812.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
@@ -220,6 +221,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
assert generate_formal_filelist(
"gpio_user", gpio_user_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"ws2812", ws2812_filelist, memory_map, topology, user_extensions
)
bus = parse_filelists([bus_filelist], require_files=False)
rib_adapter = parse_filelists([rib_adapter_filelist], require_files=False)
@@ -227,6 +231,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
sysctrl = parse_filelists([sysctrl_filelist], require_files=False)
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)
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
@@ -246,6 +251,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
assert ROOT / "rtl/ip/ribp/peripheral/gpio.sv" in gpio_user.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv" in gpio_user.files
assert ROOT / "rtl/mini/formal/gpio_user_formal.sv" in gpio_user.files
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
def test_sysctrl_formal_properties_use_exported_user_core_shape() -> None:
@@ -291,7 +299,7 @@ def test_fatfs_release_script_uses_the_locked_archive_contract() -> None:
def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> None:
proofs = ("bus", "rib2apb", "sysctrl", "pll_rcu", "gpio_user")
proofs = ("bus", "rib2apb", "sysctrl", "pll_rcu", "gpio_user", "ws2812")
for proof in proofs:
directory = tmp_path / proof
directory.mkdir()
@@ -320,6 +328,8 @@ def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> No
f"pll_rcu={tmp_path / 'pll_rcu'}",
"--proof",
f"gpio_user={tmp_path / 'gpio_user'}",
"--proof",
f"ws2812={tmp_path / 'ws2812'}",
)
result = json.loads(output.read_text(encoding="utf-8"))
assert result["status"] == "passed"
+7 -4
View File
@@ -83,6 +83,7 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert "assign u_sdio_ribp_if.valid = 1'b0;" in routes
assert gpio.count("// GPIO") == 64
assert "assign u_uart1_if.rx_i = u_gpio_if.di_i[0];" in gpio
assert "assign u_gpio_if.alt1_do_i[2] = u_ws2812_if.dat_o;" in gpio
assert "assign u_gpio_if.alt1_do_i[22] = u_psram_if.nss_o[0];" in gpio
assert apb_interfaces.count("apb4_if u_") == 10
assert "apb4_pure_if u_archinfo_apb_pure_if ();" in apb_interfaces
@@ -96,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_IRQ_RIBP_WIDTH 10" 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[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
assert "irq_i[31] == 1'b0" in irq_sva
@@ -119,7 +121,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
)
for interrupt in document["interrupts"]
]
assert mappings[:17] == [
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"),
("uart0", "ribp", 2, 2, "uart.irq_o"),
@@ -137,8 +139,9 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
("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"),
("ws2812", "ribp", 10, 17, "ws2812.irq_o"),
]
assert all(mapping[3] >= 17 for mapping in mappings[17:])
assert all(mapping[3] >= 18 for mapping in mappings[18:])
def test_topology_always_adds_the_user_apb_target(tmp_path: Path) -> None:
@@ -249,7 +252,7 @@ def test_topology_rejects_invalid_irq_groups_and_bindings(tmp_path: Path) -> Non
assert "core interrupt bit 0 is duplicated" in result.stderr
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
document["interrupts"].pop()
document["interrupts"].pop(16)
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
+132
View File
@@ -0,0 +1,132 @@
"""WS2812 register, waveform, and streaming regression tests."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_ws2812_register_waveform_streaming_and_errors(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"
serial = ROOT / "rtl/ip/ribp/serial"
source_list = tmp_path / "ws2812.fl"
source_list.write_text(
"\n".join(
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{common}",
f"+incdir+{common / 'interface'}",
f"+incdir+{common / 'utils'}",
f"+incdir+{serial}",
str(common / "interface/ribp_if.sv"),
str(common / "utils/register.sv"),
str(common / "utils/fifo.sv"),
str(serial / "ws2812_if.sv"),
str(serial / "ws2812_reg.sv"),
str(serial / "ws2812_core.sv"),
str(serial / "ribp_ws2812.sv"),
str(ROOT / "tests/rtl/ws2812_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "ws2812_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 / "ws2812_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "ws2812_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "WS2812 register, waveform, streaming, and error test passed" in result.stdout
def test_ws2812_accepts_dma_fixed_destination_backpressure(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
memory_map = tmp_path / "memory_map"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/address_map/generate_memory_map.py"),
"--map",
str(ROOT / "rtl/mini/address_map/memory_map.json"),
"--output-dir",
str(memory_map),
"--have-sram-if",
"NO",
],
check=True,
)
common = ROOT / "rtl/managed/clusterip/common/rtl"
serial = ROOT / "rtl/ip/ribp/serial"
source_list = tmp_path / "ws2812_dma.fl"
source_list.write_text(
"\n".join(
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{memory_map / 'rtl'}",
f"+incdir+{common}",
f"+incdir+{common / 'interface'}",
f"+incdir+{common / 'utils'}",
f"+incdir+{ROOT / 'rtl/mini/top'}",
f"+incdir+{serial}",
str(common / "interface/ribp_if.sv"),
str(ROOT / "rtl/mini/top/rib_if.sv"),
str(common / "utils/register.sv"),
str(common / "utils/fifo.sv"),
str(ROOT / "rtl/ip/ribp/peripheral/dma.sv"),
str(ROOT / "rtl/ip/ribp/peripheral/dma_core.sv"),
str(serial / "ws2812_if.sv"),
str(serial / "ws2812_reg.sv"),
str(serial / "ws2812_core.sv"),
str(serial / "ribp_ws2812.sv"),
str(ROOT / "tests/rtl/ws2812_dma_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "ws2812_dma_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 / "ws2812_dma_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "ws2812_dma_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "WS2812 DMA backpressure integration test passed" in result.stdout