Files
NewInstrCalBoard_CPLD/CPLD1/tb_RelayConTop.sv
T
2026-05-27 16:52:05 +08:00

915 lines
35 KiB
Systemverilog

//------------------------------------------------------------------------------
//
// Testbench: CPLD1 - RelayConTop
// Project: NewCalBoard DIG
// Tool: Lattice Diamond Verilog-2001 simulator
// Purpose: Verify SPI interface, register file, and relay control logic
// for CPLD1 (main relay controller + PMU output + DMM enable).
//
// Architecture (3 sub-modules):
// RelayConTop
// - BUS_Con - SPI Mode 0 master interface (32-bit transactions)
// - Reg_file - 16-register file + execution FSM
// - CPLD_Con - Relay control logic (freq + DC/PMU decoding)
//
// SPI Protocol:
// Frame: [WR:1][Addr:7][Data:24] (MSB first, CPOL=0 CPHA=0)
// Read response: [8'h00][24-bit rdata]
//
// DPS path is NOT implemented (stubbed out) - all DC tests use PMU mode.
//
//------------------------------------------------------------------------------
`timescale 1ns / 1ps
`define SYS_CLK_PERIOD 20 // 50 MHz system clock
`define SPI_CLK_PERIOD 400 // 1 MHz SPI clock (200x slower than sys clk)
module tb_RelayConTop;
////==========================================================================
// Signal declarations - mirrors RelayConTop pinout exactly
////==========================================================================
// Clock & reset
reg i_sys_clk;
reg i_rst_n;
// SPI interface (Zynq PS drives these)
reg i_sclk;
reg i_mosi;
reg i_cs;
// Flow control (from Zynq PS / SPI_Con IP)
reg i_rready;
reg i_wvalid;
// Status outputs (monitored by testbench)
wire o_miso;
wire o_rvalid;
wire o_wready;
wire o_err;
wire o_con_done;
// Relay control outputs
wire o_IO_RC1;
wire o_RC_VSel;
wire o_RC_ISel;
wire [17:0] o_RC_RLSel;
wire [1:0] o_RC_LOF;
wire [1:0] o_RC_LOS;
wire o_RC_T27;
wire o_RC_T28;
wire o_RC_T29;
wire o_RC_T30;
wire o_RC_T31;
wire o_RC_T32;
// PMU output channels (4 x 32-bit)
wire [31:0] o_PMU_OC_1;
wire [31:0] o_PMU_OC_2;
wire [31:0] o_PMU_OC_3;
wire [31:0] o_PMU_OC_4;
// DMM enable bus (19 bits)
wire [18:0] o_DMM_EN;
////==========================================================================
// DUT instantiation - connect every pin
////==========================================================================
RelayConTop DUT (
.i_sys_clk (i_sys_clk),
.i_rst_n (i_rst_n),
.i_sclk (i_sclk),
.i_mosi (i_mosi),
.i_cs (i_cs),
.o_miso (o_miso),
.i_rready (i_rready),
.i_wvalid (i_wvalid),
.o_wready (o_wready),
.o_err (o_err),
.o_con_done (o_con_done),
.o_DMM_EN (o_DMM_EN),
.o_IO_RC1 (o_IO_RC1),
.o_RC_T27 (o_RC_T27),
.o_RC_T28 (o_RC_T28),
.o_RC_T29 (o_RC_T29),
.o_RC_T30 (o_RC_T30),
.o_RC_T31 (o_RC_T31),
.o_RC_T32 (o_RC_T32),
.o_RC_RLSel (o_RC_RLSel),
.o_RC_VSel (o_RC_VSel),
.o_RC_ISel (o_RC_ISel),
.o_RC_LOF (o_RC_LOF),
.o_RC_LOS (o_RC_LOS),
.o_PMU_OC_1 (o_PMU_OC_1),
.o_PMU_OC_2 (o_PMU_OC_2),
.o_PMU_OC_3 (o_PMU_OC_3),
.o_PMU_OC_4 (o_PMU_OC_4)
);
////==========================================================================
// Clock generation
////==========================================================================
// System clock: 50 MHz (period = 20 ns)
initial begin
i_sys_clk = 0;
forever #(`SYS_CLK_PERIOD / 2) i_sys_clk = ~i_sys_clk;
end
// SPI clock: 1 MHz (period = 400 ns)
initial begin
i_sclk = 0;
forever #(`SPI_CLK_PERIOD / 2) i_sclk = ~i_sclk;
end
////==========================================================================
// Test statistics
////==========================================================================
integer test_pass;
integer test_fail;
integer test_num;
initial begin
test_pass = 0;
test_fail = 0;
test_num = 0;
end
// Helper: log a pass (Verilog-2001 compatible)
task tb_pass;
input [132:1] msg;
begin
test_pass = test_pass + 1;
$display("[PASS] Test %0d: %s", test_num, msg);
end
endtask
// Helper: log a fail with optional detail
task tb_fail;
input [132:1] msg;
input [132:1] detail;
begin
test_fail = test_fail + 1;
$display("[FAIL] Test %0d: %s >> %s", test_num, msg, detail);
end
endtask
// Helper: assert a condition (Verilog-2001 compatible)
task tb_assert;
input condition;
input [132:1] msg;
input [132:1] detail;
begin
test_num = test_num + 1;
if (condition) begin
tb_pass(msg);
end else begin
tb_fail(msg, detail);
end
end
endtask
////==========================================================================
// SPI helper tasks
//
// SPI Mode 0 timing:
// - CPOL = 0: SCLK idle LOW
// - CPHA = 0: data sampled on RISING edge, shifted on FALLING edge
// - MSB first, 32 bits per transaction
//
// Frame layout: [bit31: WR] [bit30:24: Addr(7b)] [bit23:0: Data(24b)]
// Read response: [8'h00][24-bit rdata] shifted out on falling SCLK edges
//
// MISO is tri-stated (high-Z) when CS is HIGH.
////==========================================================================
// --- spi_send: Drive a 32-bit word out through MOSI ---
task spi_send;
input [31:0] data;
integer i;
begin
@(negedge i_sclk);
i_cs = 1'b0;
@(negedge i_sclk);
for (i = 31; i >= 0; i = i - 1) begin
i_mosi = data[i];
@(posedge i_sclk);
end
@(negedge i_sclk);
i_cs = 1'b1;
i_mosi = 1'b0;
@(negedge i_sclk);
end
endtask
// --- spi_read: Send command, capture 32-bit response on MISO ---
task spi_read;
input [31:0] cmd;
output [31:0] rdata;
integer i;
begin
@(negedge i_sclk);
i_cs = 1'b0;
@(negedge i_sclk);
// CPLD uses sys_clk domain for internal logic. Add #1ns delay
// after falling edge to let CPLD update o_miso before we sample.
rdata = 32'b0;
for (i = 31; i >= 0; i = i - 1) begin
i_mosi = cmd[i];
@(posedge i_sclk);
@(negedge i_sclk);
#1;
rdata[i] = o_miso;
end
@(negedge i_sclk);
i_cs = 1'b1;
i_mosi = 1'b0;
@(negedge i_sclk);
end
endtask
// --- spi_write: Send a write command (no response capture needed) ---
task spi_write;
input [31:0] data;
begin
spi_send(data);
end
endtask
// --- wait_con_done: Poll con_done for LOW->HIGH cycle ---
task wait_con_done;
input [15:0] max_cycles;
output [1:0] result;
integer i;
begin
result = 2'b0;
// Phase 1: Wait for con_done to go LOW (exec started)
for (i = 0; i < max_cycles; i = i + 1) begin
#(`SYS_CLK_PERIOD);
if (!o_con_done) break;
end
// Phase 2: Wait for con_done to go HIGH (exec finished)
for (i = 0; i < max_cycles; i = i + 1) begin
#(`SYS_CLK_PERIOD);
if (o_con_done) begin
result = 2'b1;
return;
end
end
end
endtask
// --- wait_wready: Poll o_wready until HIGH ---
task wait_wready;
input [15:0] max_cycles;
output [1:0] result;
integer i;
begin
result = 2'b0;
for (i = 0; i < max_cycles; i = i + 1) begin
#(`SYS_CLK_PERIOD);
if (o_wready) begin
result = 2'b1;
return;
end
end
end
endtask
////==========================================================================
// Convenience: build SPI frame fields
////==========================================================================
function [31:0] mk_write;
input [6:0] addr;
input [23:0] data;
begin
mk_write = {1'b1, addr, data};
end
endfunction
function [31:0] mk_read;
input [6:0] addr;
begin
mk_read = {1'b0, addr, 24'b0};
end
endfunction
////==========================================================================
// Main test sequence
////==========================================================================
initial begin
// Initialize all signals to safe defaults
i_rst_n = 1'b0;
i_cs = 1'b1;
i_mosi = 1'b0;
i_rready = 1'b0;
i_wvalid = 1'b0;
// Hold reset for 4 system clock cycles (80 ns)
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
// Allow CPLD internal state machines to settle
#(`SYS_CLK_PERIOD * 10);
$display("");
$display("=============================================================");
$display(" CPLD1 RelayConTop - Comprehensive Testbench");
$display(" System clock: %d MHz | SPI clock: %d MHz",
1000 / `SYS_CLK_PERIOD, 1000 / `SPI_CLK_PERIOD);
$display("=============================================================");
$display("");
test_reset_behavior();
test_register_rw();
test_exec_protocol();
test_freq_slots();
test_dc_slots_pmu();
test_rc_tx_output();
test_error_conditions();
test_edge_cases();
print_summary();
#(`SYS_CLK_PERIOD * 20);
$finish;
end
////==========================================================================
// Test Group 1: Reset behavior & IDENT register (ADDR 0)
////==========================================================================
task test_reset_behavior;
reg [31:0] r_ident;
begin
$display("-------------------------------------------------------------");
$display(" Group 1: Reset behavior & IDENT register");
$display("-------------------------------------------------------------");
tb_assert(o_con_done === 1'b1,
"o_con_done is HIGH after reset (controller idle)", "");
tb_assert(o_err === 1'b0,
"o_err is LOW after reset", "");
tb_assert(o_wready === 1'b1,
"o_wready is HIGH after reset (SPI ready)", "");
// IDENT register: CPLD1=bit21, version=1.1.0 = 24'h200010
spi_read(mk_read(7'd0), r_ident);
r_ident = r_ident[23:0];
tb_assert(r_ident === 24'h200010,
"IDENT register returns 24'h200010 (CPLD1, v1.1.0)", "");
tb_assert(o_DMM_EN === 19'b0,
"o_DMM_EN is all zeros after reset", "");
tb_assert(o_RC_RLSel === 18'b0,
"o_RC_RLSel is all zeros after reset", "");
tb_assert(o_PMU_OC_1 === 32'b0 && o_PMU_OC_2 === 32'b0 &&
o_PMU_OC_3 === 32'b0 && o_PMU_OC_4 === 32'b0,
"All PMU_OC outputs are zero after reset", "");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 2: Register read/write (ADDR 0-9)
////==========================================================================
task test_register_rw;
reg [31:0] rdata;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 2: Register read/write (ADDR 0-9)");
$display("-------------------------------------------------------------");
spi_write(mk_write(7'd2, 24'h000005));
@(negedge i_sclk);
spi_read(mk_read(7'd2), rdata);
tb_assert(rdata === 24'h000005,
"Freq_Slot1 (ADDR 2) write/read back", "");
spi_write(mk_write(7'd3, 24'h003001));
@(negedge i_sclk);
spi_read(mk_read(7'd3), rdata);
tb_assert(rdata === 24'h003001,
"Freq_Slot2 (ADDR 3) write/read back", "");
spi_write(mk_write(7'd4, 24'h001040));
@(negedge i_sclk);
spi_read(mk_read(7'd4), rdata);
tb_assert(rdata === 24'h001040,
"Freq_Slot3 (ADDR 4) write/read back", "");
spi_write(mk_write(7'd5, 24'h000802));
@(negedge i_sclk);
spi_read(mk_read(7'd5), rdata);
tb_assert(rdata === 24'h000802,
"Freq_Slot4 (ADDR 5) write/read back", "");
spi_write(mk_write(7'd6, 24'h002003));
@(negedge i_sclk);
spi_read(mk_read(7'd6), rdata);
tb_assert(rdata === 24'h002003,
"DC_Slot1 (ADDR 6) write/read back (PMU mode)", "");
spi_write(mk_write(7'd7, 24'h004005));
@(negedge i_sclk);
spi_read(mk_read(7'd7), rdata);
tb_assert(rdata === 24'h004005,
"DC_Slot2 (ADDR 7) write/read back", "");
spi_write(mk_write(7'd8, 24'h006007));
@(negedge i_sclk);
spi_read(mk_read(7'd8), rdata);
tb_assert(rdata === 24'h006007,
"DC_Slot3 (ADDR 8) write/read back", "");
spi_write(mk_write(7'd9, 24'h008009));
@(negedge i_sclk);
spi_read(mk_read(7'd9), rdata);
tb_assert(rdata === 24'h008009,
"DC_Slot4 (ADDR 9) write/read back", "");
spi_read(mk_read(7'd1), rdata);
tb_assert(rdata === 24'h800000,
"STATE register (ADDR 1) default (RDY=1)", "");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 3: Execution protocol
////==========================================================================
task test_exec_protocol;
reg [1:0] done_result;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 3: Execution protocol");
$display("-------------------------------------------------------------");
// Configure and trigger execution
spi_write(mk_write(7'd2, 24'h000005));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001)); // EXEC=1
@(negedge i_sclk);
wait_con_done(500, done_result);
tb_assert(done_result === 1'b1,
"con_done goes HIGH after execution completes", "");
tb_assert(o_err === 1'b0,
"No error flag set after successful execution", "");
wait_wready(100, done_result);
tb_assert(done_result === 1'b1,
"o_wready is HIGH after execution (SPI ready)", "");
// Execute with DC_Slot1
spi_write(mk_write(7'd6, 24'h002003));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_result);
tb_assert(done_result === 1'b1,
"DC slot execution completes successfully", "");
tb_assert(o_err === 1'b0,
"No error after DC slot execution", "");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 4: Frequency slot decoding (all 4 slots)
////==========================================================================
task test_freq_slots;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 4: Frequency slot decoding (all 4 slots)");
$display("-------------------------------------------------------------");
// Slot1 Ch5 (range 1-8): OC_x01=1, DMM_EN[0]=1
$display(" Slot1 Ch5 (range 1-8): OC_x01=1, DMM_EN[0]=1");
spi_write(mk_write(7'd2, 24'h000005));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot1 Ch5 exec done", "timeout");
tb_assert(o_RC_T27 === 1'b0, "Slot1: RC_T27 idle", "RC_T27 mismatch");
tb_assert(o_PMU_OC_1[0] === 1'b1, "Slot1 Ch5: PMU_OC_1[0]=1", "PMU_OC_1[0] mismatch");
tb_assert(o_DMM_EN[0] === 1'b1, "Slot1 Ch5: DMM_EN[0]=1", "DMM_EN[0] mismatch");
tb_assert(o_PMU_OC_2[0] === 1'b0, "Slot1 Ch5: PMU_OC_2[0]=0", "PMU_OC_2[0] mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch50 (range 9-72): DMM_EN[1]=1
// = (0<<10) | (50<<1) | 1 = 24'h000065
$display(" Slot1 Ch50 (range 9-72): DMM_EN[1]=1");
spi_write(mk_write(7'd2, 24'h000065));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot1 Ch50 exec done", "timeout");
tb_assert(o_DMM_EN[1] === 1'b1, "Slot1 Ch50: DMM_EN[1]=1", "DMM_EN[1] mismatch");
tb_assert(o_PMU_OC_1[0] === 1'b0, "Slot1 Ch50: PMU_OC_1[0]=0", "PMU_OC_1[0] mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch100 (range 73-136): OC_x17=1, DMM_EN[2]=1
// = (0<<10) | (100<<1) | 1 = 24'h0000C9
$display(" Slot1 Ch100 (range 73-136): OC_x17=1, DMM_EN[2]=1");
spi_write(mk_write(7'd2, 24'h0000C9));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot1 Ch100 exec done", "timeout");
tb_assert(o_PMU_OC_1[16] === 1'b1, "Slot1 Ch100: PMU_OC_1[16]=1", "PMU_OC_1[16] mismatch");
tb_assert(o_DMM_EN[2] === 1'b1, "Slot1 Ch100: DMM_EN[2]=1", "DMM_EN[2] mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch150 (range 137-200): DMM_EN[3]=1
// = (0<<10) | (150<<1) | 1 = 24'h00012D
$display(" Slot1 Ch150 (range 137-200): DMM_EN[3]=1");
spi_write(mk_write(7'd2, 24'h00012D));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot1 Ch150 exec done", "timeout");
tb_assert(o_DMM_EN[3] === 1'b1, "Slot1 Ch150: DMM_EN[3]=1", "DMM_EN[3] mismatch");
#(`SPI_CLK_PERIOD);
// Slot2 Ch5 (range 1-8): OC_x01=2, DMM_EN[4]=1
// = (0<<10) | (5<<1) | 1 = 24'h000006
$display(" Slot2 Ch5 (range 1-8): OC_x01=2, DMM_EN[4]=1");
spi_write(mk_write(7'd3, 24'h000006));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot2 Ch5 exec done", "timeout");
tb_assert(o_PMU_OC_2[0] === 1'b1, "Slot2 Ch5: PMU_OC_2[0]=1", "PMU_OC_2[0] mismatch");
tb_assert(o_DMM_EN[4] === 1'b1, "Slot2 Ch5: DMM_EN[4]=1", "DMM_EN[4] mismatch");
#(`SPI_CLK_PERIOD);
// Slot3 Ch100 (range 73-136): OC_x17=4, DMM_EN[10]=1
// = (0<<10) | (100<<1) | 1 = 24'h0000C9
$display(" Slot3 Ch100 (range 73-136): OC_x17=4, DMM_EN[10]=1");
spi_write(mk_write(7'd4, 24'h0000C9));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot3 Ch100 exec done", "timeout");
tb_assert(o_PMU_OC_3[16] === 1'b1, "Slot3 Ch100: PMU_OC_3[16]=1", "PMU_OC_3[16] mismatch");
tb_assert(o_DMM_EN[10] === 1'b1, "Slot3 Ch100: DMM_EN[10]=1", "DMM_EN[10] mismatch");
#(`SPI_CLK_PERIOD);
// Slot4 Ch50 (range 9-72): DMM_EN[5]=1
// = (0<<10) | (50<<1) | 1 = 24'h000065
$display(" Slot4 Ch50 (range 9-72): DMM_EN[5]=1");
spi_write(mk_write(7'd5, 24'h000065));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot4 Ch50 exec done", "timeout");
tb_assert(o_DMM_EN[5] === 1'b1, "Slot4 Ch50: DMM_EN[5]=1", "DMM_EN[5] mismatch");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 5: DC slot PMU mode
////==========================================================================
task test_dc_slots_pmu;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 5: DC slot PMU mode (DPS is stubbed, not tested)");
$display("-------------------------------------------------------------");
// 5a. DC_Slot1: V mode, Rload=3, Ch=3, PMU
// = (3<<17) | (3<<1) | 1 = 24'h060007
$display(" DC1: V mode, Rload=3, Ch=3, PMU");
spi_write(mk_write(7'd6, 24'h060007));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "DC1 V-mode exec done", "timeout");
tb_assert(o_RC_VSel === 1'b1, "DC1: RC_VSel=1 (V mode)", "RC_VSel mismatch");
tb_assert(o_RC_ISel === 1'b0, "DC1: RC_ISel=0 (V mode)", "RC_ISel mismatch");
tb_assert(o_RC_RLSel === 18'b100, "DC1: RC_RLSel=18'b100 (Rload=3)", "RC_RLSel mismatch");
tb_assert(o_PMU_OC_1[0] === 1'b1, "DC1: PMU_OC_1[0]=1 (Ch=3)", "PMU_OC_1[0] mismatch");
#(`SPI_CLK_PERIOD);
// 5b. DC_Slot1: I mode, Rload=1, Ch=11, PMU
// = (1<<22) | (1<<17) | (11<<1) | 1 = 24'h420017
$display(" DC1: I mode, Rload=1, Ch=11, PMU");
spi_write(mk_write(7'd6, 24'h420017));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "DC1 I-mode exec done", "timeout");
tb_assert(o_RC_VSel === 1'b0, "DC1: RC_VSel=0 (I mode)", "RC_VSel mismatch");
tb_assert(o_RC_ISel === 1'b1, "DC1: RC_ISel=1 (I mode)", "RC_ISel mismatch");
tb_assert(o_RC_RLSel[0] === 1'b1, "DC1: RC_RLSel[0]=1 (Rload=1)", "RC_RLSel mismatch");
tb_assert(o_PMU_OC_1[1] === 1'b1, "DC1: PMU_OC_1[1]=1 (Ch=11)", "PMU_OC_1[1] mismatch");
#(`SPI_CLK_PERIOD);
// 5c. DC_Slot2: V mode, Rload=5, Ch=19, PMU
// = (5<<17) | (19<<1) | 1 = 24'h0A0027
$display(" DC2: V mode, Rload=5, Ch=19, PMU");
spi_write(mk_write(7'd7, 24'h0A0027));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "DC2 V-mode exec done", "timeout");
tb_assert(o_RC_VSel === 1'b1, "DC2: RC_VSel=1 (V mode)", "RC_VSel mismatch");
tb_assert(o_PMU_OC_2[2] === 1'b1, "DC2: PMU_OC_2[2]=1 (Ch=19)", "PMU_OC_2[2] mismatch");
#(`SPI_CLK_PERIOD);
// 5d. DC_Slot3: V mode, Rload=10, Ch=27, PMU
// = (10<<17) | (27<<1) | 1 = 24'h140037
$display(" DC3: V mode, Rload=10, Ch=27, PMU");
spi_write(mk_write(7'd8, 24'h140037));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "DC3 V-mode exec done", "timeout");
tb_assert(o_PMU_OC_3[3] === 1'b1, "DC3: PMU_OC_3[3]=1 (Ch=27)", "PMU_OC_3[3] mismatch");
#(`SPI_CLK_PERIOD);
// 5e. DC_Slot4: V mode, Rload=18, Ch=35, PMU
// = (18<<17) | (35<<1) | 1 = 24'h240047
$display(" DC4: V mode, Rload=18, Ch=35, PMU");
spi_write(mk_write(7'd9, 24'h240047));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "DC4 V-mode exec done", "timeout");
tb_assert(o_RC_RLSel[17] === 1'b1, "DC4: RC_RLSel[17]=1 (Rload=18)", "RC_RLSel mismatch");
tb_assert(o_PMU_OC_4[4] === 1'b1, "DC4: PMU_OC_4[4]=1 (Ch=35)", "PMU_OC_4[4] mismatch");
#(`SPI_CLK_PERIOD);
// 5f. RC_LOF/LOS when RC_VSel=1 (V mode)
tb_assert(o_RC_LOF === 2'b01 && o_RC_LOS === 2'b01,
"RC_LOF/LOS=01 when RC_VSel=1", "");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 6: RC_Tx relay output (slot 4)
////==========================================================================
task test_rc_tx_output;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 6: RC_Tx relay output (slot 4)");
$display("-------------------------------------------------------------");
// 6a. RC_Tx pattern: bits 17:12 = 6'b101010 (42)
// = (42<<12) | (5<<1) | 1 = 24'h02A00B
$display(" Slot4: RC_Tx = 6'b101010");
spi_write(mk_write(7'd5, 24'h02A00B));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Slot4 RC_Tx exec done", "timeout");
tb_assert(o_RC_T27 === 1'b1, "RC_T27=1 (RC_Tx[5])", "RC_T27 mismatch");
tb_assert(o_RC_T28 === 1'b0, "RC_T28=0 (RC_Tx[4])", "RC_T28 mismatch");
tb_assert(o_RC_T29 === 1'b1, "RC_T29=1 (RC_Tx[3])", "RC_T29 mismatch");
tb_assert(o_RC_T30 === 1'b0, "RC_T30=0 (RC_Tx[2])", "RC_T30 mismatch");
tb_assert(o_RC_T31 === 1'b1, "RC_T31=1 (RC_Tx[1])", "RC_T31 mismatch");
tb_assert(o_RC_T32 === 1'b0, "RC_T32=0 (RC_Tx[0])", "RC_T32 mismatch");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 7: Error conditions
////==========================================================================
task test_error_conditions;
reg [1:0] done_r;
reg [31:0] rdata;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 7: Error conditions");
$display("-------------------------------------------------------------");
// 7a. Short SPI transaction (16 bits)
$display(" Error 7a: Short SPI transaction (16 bits)");
i_cs = 1'b0;
@(negedge i_sclk);
@(negedge i_sclk);
for (integer bi = 0; bi < 16; bi = bi + 1) begin
i_mosi = 1'b0;
@(posedge i_sclk);
@(negedge i_sclk);
end
i_cs = 1'b1;
@(negedge i_sclk);
#(`SYS_CLK_PERIOD * 5);
tb_assert(o_err === 1'b1,
"o_err HIGH after short SPI transaction", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
// 7b. Write to IDENT register (ADDR 0, read-only)
$display(" Error 7b: Write to IDENT register (ADDR 0)");
spi_write(mk_write(7'd0, 24'hDEADBEE >> 8));
@(negedge i_sclk);
tb_assert(o_err === 1'b1,
"o_err HIGH after write to IDENT register", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
#(`SPI_CLK_PERIOD);
// 7c. Read out-of-bounds address (ADDR 16)
$display(" Error 7c: Read out-of-bounds address (ADDR 16)");
spi_read(mk_read(7'd16), rdata);
@(negedge i_sclk);
#(`SYS_CLK_PERIOD * 5);
tb_assert(o_err === 1'b1,
"o_err HIGH after reading ADDR 16", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
#(`SPI_CLK_PERIOD);
// 7d. Multiple slot enables (en_t > 1)
$display(" Error 7d: Multiple slot enable bits");
spi_write(mk_write(7'd2, 24'h000005));
@(negedge i_sclk);
spi_write(mk_write(7'd3, 24'h000006));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001));
@(negedge i_sclk);
wait_con_done(500, done_r);
tb_assert(done_r === 1'b1, "Execution completes (even with error)", "");
tb_assert(o_err === 1'b1,
"o_err HIGH with multiple slot enables", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
#(`SPI_CLK_PERIOD);
// 7e. Write to out-of-bounds address (ADDR 31)
$display(" Error 7e: Write to out-of-bounds address (ADDR 31)");
spi_write(mk_write(7'd31, 24'h123456));
@(negedge i_sclk);
#(`SYS_CLK_PERIOD * 5);
tb_assert(o_err === 1'b1,
"o_err HIGH after writing ADDR 31", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Test Group 8: Edge cases
////==========================================================================
task test_edge_cases;
reg [31:0] rdata;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 8: Edge cases");
$display("-------------------------------------------------------------");
// 8a. Brief CS pulse (too short for 32-bit)
$display(" Edge 8a: Brief CS pulse");
i_cs = 1'b0;
@(negedge i_sclk);
@(negedge i_sclk);
i_cs = 1'b1;
@(negedge i_sclk);
#(`SYS_CLK_PERIOD * 5);
tb_assert(o_err === 1'b1,
"o_err detected brief CS pulse", "");
i_rst_n = 1'b0;
#(`SYS_CLK_PERIOD * 4);
i_rst_n = 1'b1;
#(`SYS_CLK_PERIOD * 4);
tb_assert(o_err === 1'b0, "o_err cleared after reset", "o_err not cleared");
#(`SPI_CLK_PERIOD);
// 8b. Rapid successive SPI writes
$display(" Edge 8b: Rapid successive SPI writes");
spi_write(mk_write(7'd2, 24'h000001));
spi_write(mk_write(7'd3, 24'h000002));
spi_write(mk_write(7'd4, 24'h000003));
spi_write(mk_write(7'd5, 24'h000004));
@(negedge i_sclk);
spi_read(mk_read(7'd2), rdata);
tb_assert(rdata === 24'h000001, "Rapid write: ADDR 2 correct", "ADDR 2 mismatch");
spi_read(mk_read(7'd3), rdata);
tb_assert(rdata === 24'h000002, "Rapid write: ADDR 3 correct", "ADDR 3 mismatch");
spi_read(mk_read(7'd4), rdata);
tb_assert(rdata === 24'h000003, "Rapid write: ADDR 4 correct", "ADDR 4 mismatch");
spi_read(mk_read(7'd5), rdata);
tb_assert(rdata === 24'h000004, "Rapid write: ADDR 5 correct", "ADDR 5 mismatch");
#(`SPI_CLK_PERIOD);
// 8c. Write to reserved registers (ADDR 10-15) - no error
$display(" Edge 8c: Write to reserved registers");
spi_write(mk_write(7'd10, 24'hABCDEF));
@(negedge i_sclk);
spi_write(mk_write(7'd15, 24'h123456));
@(negedge i_sclk);
tb_assert(o_err === 1'b0, "No error writing to reserved ADDR 10 and 15",
"Unexpected error");
#(`SPI_CLK_PERIOD * 2);
end
endtask
////==========================================================================
// Summary
////==========================================================================
task print_summary;
begin
$display("");
$display("=============================================================");
$display(" Test Summary");
$display("=============================================================");
$display(" Total tests: %0d", test_pass + test_fail);
$display(" Passed: %0d", test_pass);
$display(" Failed: %0d", test_fail);
$display("=============================================================");
if (test_fail == 0) begin
$display(" *** ALL TESTS PASSED ***");
end else begin
$display(" *** SOME TESTS FAILED - CHECK DESIGN ***");
end
$display("=============================================================");
$display("");
end
endtask
endmodule