mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
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:
46 files changed
+2635
-616
No files matched your search
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user