Files
NewInstrCalBoard_CPLD/CPLD2/tb_RelayConTop.sv
T
2026-05-27 17:25:48 +08:00

965 lines
37 KiB
Systemverilog

//------------------------------------------------------------------------------
//
// Testbench: CPLD2 - RelayConTop
// Project: NewCalBoard DIG
// Tool: Lattice Diamond Verilog-2001 simulator
// Purpose: Verify SPI interface, register file, and relay control logic
// for CPLD2 (RC_F/RC_S/RC_T relay control + OC channels + PMU_OC).
//
// CPLD2-specific:
// - Outputs: RC_F[7:0], RC_S[15:0], RC_T[25:0], OC_x09[3:0], OC_x25[3:0]
// - PMU_OC_1-4[31:0] (from OC_x09/OC_x25 mapping)
// - CPLD_Con handles freq relays only (DC slots are stubs)
// - Signal names: i_wvaild (typo), i_rvaild (typo)
//
// Architecture (3 sub-modules):
// RelayConTop
// - BUS_Con - SPI Mode 0 master interface (32-bit transactions)
// - Reg_file - 16-register file + execution FSM
// - CPLD_Con - Freq relay control logic (no DC handling)
//
// SPI Protocol:
// Frame: [WR:1][Addr:7][Data:24] (MSB first, CPOL=0 CPHA=0)
// Read response: [8'h00][24-bit rdata]
//
//------------------------------------------------------------------------------
`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 CPLD2 RelayConTop pinout
//==========================================================================
// 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 (CPLD2 uses typo names: i_wvaild, i_rvaild)
reg i_rready;
reg i_wvaild;
// Status outputs (monitored by testbench)
// Note: CPLD2 does NOT expose o_con_done as a top-level output
wire o_miso;
wire o_rvalid;
wire o_wready;
wire o_err;
// Relay control outputs (CPLD2 specific)
wire [7:0] o_RC_F; // RC_F relays (2 bits used per slot)
wire [15:0] o_RC_S; // RC_S relays (4 bits used per slot)
wire [25:0] o_RC_T; // RC_T relays (8 bits used per slot, only 26 bits)
// OC channel selectors
wire [3:0] o_OC_x09; // OC channel selector (range 65-72)
wire [3:0] o_OC_x25; // OC channel selector (range 193-200)
// PMU output channels (4 x 32-bit, mapped from OC_x09/OC_x25)
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;
//==========================================================================
// DUT instantiation - CPLD2 specific pin names
// Note: CPLD2 does NOT expose o_con_done as a top-level output
//==========================================================================
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),
.o_rvalid (o_rvalid),
.i_rready (i_rready),
.i_wvaild (i_wvaild), // CPLD2 typo: wvaild
.o_wready (o_wready),
.o_err (o_err),
.o_RC_F (o_RC_F),
.o_RC_S (o_RC_S),
.o_RC_T (o_RC_T),
.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
task tb_pass;
input string 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 string msg;
input string detail;
begin
test_fail = test_fail + 1;
$display("[FAIL] Test %0d: %s >> %s", test_num, msg, detail);
end
endtask
// Helper: assert a condition
task tb_assert;
input condition;
input string msg;
input string 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);
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 o_wready for HIGH (CPLD2 doesn't expose con_done)
// When execution completes, the SPI interface becomes ready again (o_wready=1)
task wait_con_done;
input [15:0] max_cycles;
output [1:0] result;
integer i;
begin
result = 2'b0;
// Wait for o_wready to go HIGH (SPI interface ready after execution)
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
// --- 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_wvaild = 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(" CPLD2 RelayConTop - Comprehensive Testbench");
$display(" System clock: %d MHz | SPI clock: %d MHz",
1000 / `SYS_CLK_PERIOD, 1000 / `SPI_CLK_PERIOD);
$display(" Outputs: RC_F[7:0], RC_S[15:0], RC_T[25:0], OC_x09/25, PMU_OC[4x32]");
$display("=============================================================");
$display("");
test_reset_behavior();
test_register_rw();
test_exec_protocol();
test_freq_slots();
test_rc_relay_outputs();
test_dc_stub_behavior();
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_wready === 1'b1,
"o_wready is HIGH after reset (SPI ready, 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: CPLD2=bit22, version=1.1.0 = 24'h400010
spi_read(mk_read(7'd0), r_ident);
r_ident = r_ident[23:0];
tb_assert(r_ident === 24'h400010,
"IDENT register returns 24'h400010 (CPLD2, v1.1.0)", "");
// All relay outputs should be zero after reset
tb_assert(o_RC_F === 8'b0,
"o_RC_F is all zeros after reset", "");
tb_assert(o_RC_S === 16'b0,
"o_RC_S is all zeros after reset", "");
tb_assert(o_RC_T[25:0] === 26'b0,
"o_RC_T[25:0] is all zeros after reset", "");
tb_assert(o_OC_x09 === 4'b0,
"o_OC_x09 is all zeros after reset", "");
tb_assert(o_OC_x25 === 4'b0,
"o_OC_x25 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", "");
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 with Freq_Slot1
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 (should complete but produce no output)
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 (stub behavior)", "");
tb_assert(o_err === 1'b0,
"No error after DC slot execution (stub)", "");
#(`SPI_CLK_PERIOD * 2);
end
endtask
//==========================================================================
// Test Group 4: Frequency slot decoding (all 4 slots)
//
// Freq_Slot N bit layout:
// Bit 23:10: Per Relay_Control (14 bits)
// Bit 9:1: Channel_Number (9 bits)
// Bit 0: Slot_EN (1=Enable)
//
// Channel ranges:
// 1-8: OC_x09 = slot_bit, PMU_OC[0] bit 0
// 9-72: (no OC_x09/OC_x25), PMU_OC[0] from channel
// 73-136: OC_x25 = slot_bit, PMU_OC[0] bit 16+
// 137-200: (no OC_x09/OC_x25), PMU_OC[0] bit 24+
//==========================================================================
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_x09 = 4'b0001
// = (1<<23) | (0<<22) | (5<<1) | 1 = 24'h08000B (sets RC_F[1:0]=01)
$display(" Slot1 Ch5 (range 1-8): OC_x09=0001");
spi_write(mk_write(7'd2, 24'h08000B));
@(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_OC_x09[0] === 1'b1, "Slot1 Ch5: OC_x09[0]=1", "OC_x09[0] mismatch");
tb_assert(o_OC_x25 === 4'b0, "Slot1 Ch5: OC_x25=0", "OC_x25 mismatch");
tb_assert(o_RC_F[1:0] == 2'b01, "Slot1 Ch5: RC_F[1:0] has relay bits", "RC_F mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch50 (range 9-72): no OC_x09/OC_x25
// = (0<<10) | (50<<1) | 1 = 24'h000065
$display(" Slot1 Ch50 (range 9-72): OC_x09=0, OC_x25=0");
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_OC_x09 === 4'b0, "Slot1 Ch50: OC_x09=0", "OC_x09 mismatch");
tb_assert(o_OC_x25 === 4'b0, "Slot1 Ch50: OC_x25=0", "OC_x25 mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch100 (range 73-136): OC_x25 = 4'b0001
// = (0<<10) | (100<<1) | 1 = 24'h0000C9
$display(" Slot1 Ch100 (range 73-136): OC_x25=0001");
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_OC_x09 === 4'b0, "Slot1 Ch100: OC_x09=0", "OC_x09 mismatch");
tb_assert(o_OC_x25[0] === 1'b1, "Slot1 Ch100: OC_x25[0]=1", "OC_x25[0] mismatch");
tb_assert(o_RC_T[15:8] != 8'b0, "Slot1 Ch100: RC_T has relay bits", "RC_T mismatch");
#(`SPI_CLK_PERIOD);
// Slot1 Ch150 (range 137-200): no OC_x09/OC_x25
// = (0<<10) | (150<<1) | 1 = 24'h00012D
$display(" Slot1 Ch150 (range 137-200): OC_x09=0, OC_x25=0");
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_OC_x09 === 4'b0, "Slot1 Ch150: OC_x09=0", "OC_x09 mismatch");
tb_assert(o_OC_x25 === 4'b0, "Slot1 Ch150: OC_x25=0", "OC_x25 mismatch");
#(`SPI_CLK_PERIOD);
// Slot2 Ch5 (range 1-8): OC_x09 = 4'b0010
// = (1<<23) | (0<<22) | (5<<1) | 1 = 24'h08000B (sets RC_F[3:2]=01)
$display(" Slot2 Ch5 (range 1-8): OC_x09=0010");
spi_write(mk_write(7'd3, 24'h08000B));
@(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_OC_x09[1] === 1'b1, "Slot2 Ch5: OC_x09[1]=1", "OC_x09[1] mismatch");
tb_assert(o_RC_F[5:4] == 2'b01, "Slot2 Ch5: RC_F[5:4] has relay bits", "RC_F mismatch");
#(`SPI_CLK_PERIOD);
// Slot3 Ch100 (range 73-136): OC_x25 = 4'b0100
// = (0xAA<<10) | (100<<1) | 1 = 24'h02A8C9 (sets RC_T bits)
$display(" Slot3 Ch100 (range 73-136): OC_x25=0100");
spi_write(mk_write(7'd4, 24'h02A8C9));
@(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_OC_x25[2] === 1'b1, "Slot3 Ch100: OC_x25[2]=1", "OC_x25[2] mismatch");
#(`SPI_CLK_PERIOD);
// Slot4 Ch50 (range 9-72): no OC_x09/OC_x25
// = (0<<10) | (50<<1) | 1 = 24'h000065
$display(" Slot4 Ch50 (range 9-72): OC_x09=0, OC_x25=0");
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_OC_x09 === 4'b0, "Slot4 Ch50: OC_x09=0", "OC_x09 mismatch");
tb_assert(o_OC_x25 === 4'b0, "Slot4 Ch50: OC_x25=0", "OC_x25 mismatch");
#(`SPI_CLK_PERIOD * 2);
end
endtask
//==========================================================================
// Test Group 5: RC relay output patterns (RC_F, RC_S, RC_T)
//
// CPLD2 CPLD_Con relay mapping (frequency mode):
// Slot1: RC_F[1:0], RC_S[3:0], RC_T[7:0]
// Slot2: RC_F[5:4], RC_S[11:8], RC_T[23:16]
// Slot3: RC_F[7:6], RC_S[15:12], RC_T[15:8] (bit-reordered)
// Slot4: RC_F[1:0], RC_S[3:0], RC_T[7:0]
//
// Timing: relay_l1 at t=0, relay_l2 at t=500, relay_l3 at t=1000
// exec_done at t=1080
//==========================================================================
task test_rc_relay_outputs;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 5: RC relay output patterns (RC_F/RC_S/RC_T)");
$display("-------------------------------------------------------------");
// 5a. Slot4 Ch5: Verify RC_F, RC_S, RC_T relay bit extraction
// Freq_Slot4 with RC_F[1:0]={1,0}, RC_S[3:0]={1,0,1,0}, Ch=5
// = (1<<23) | (0xA<<18) | (5<<1) | 1 = 24'hA8000B
$display(" Slot4: RC_F/RC_S/RC_T relay extraction");
spi_write(mk_write(7'd5, 24'hA8000B));
@(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 relay exec done", "timeout");
// RC_F[1:0] should have relay bits from freq4[23:22]
tb_assert(o_RC_F[1:0] != 2'b00,
"Slot4: RC_F[1:0] has relay bits from freq4[23:22]",
$sformatf("RC_F[1:0]=%b", o_RC_F[1:0]));
// RC_S[3:0] should have relay bits from freq4[21:18]
tb_assert(o_RC_S[3:0] != 4'b0000,
"Slot4: RC_S[3:0] has relay bits from freq4[21:18]",
$sformatf("RC_S[3:0]=%b", o_RC_S[3:0]));
// RC_T[7:0] should have relay bits from freq4[17:10]
tb_assert(o_RC_T[7:0] != 8'b00000000,
"Slot4: RC_T[7:0] has relay bits from freq4[17:10]",
$sformatf("RC_T[7:0]=%b", o_RC_T[7:0]));
// 5b. Slot4 with specific relay pattern: bits 23=1, 22=0, 21=1, 20=0, 19=1, 18=0, Ch=5
// = (1<<23) | (0<<22) | (1<<21) | (0<<20) | (1<<19) | (0<<18) | (5<<1) | 1
// = 24'hA8000B
$display(" Slot4: RC_F/RC_S/RC_T specific relay pattern");
spi_write(mk_write(7'd5, 24'hA8000B));
@(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 pattern exec done", "timeout");
// Verify RC_F[1:0] = {freq4[22], freq4[23]} = {0, 1} = 2'b01
tb_assert(o_RC_F[1:0] === 2'b01,
"Slot4: RC_F[1:0] = {freq4[22], freq4[23]} = 01",
$sformatf("RC_F[1:0]=%b", o_RC_F[1:0]));
// Verify RC_S[3:0] = {freq4[21], freq4[20], freq4[19], freq4[18]} = {1, 0, 1, 0} = 4'b1010
tb_assert(o_RC_S[3:0] === 4'b1010,
"Slot4: RC_S[3:0] = freq4[21:18] = 1010",
$sformatf("RC_S[3:0]=%b", o_RC_S[3:0]));
#(`SPI_CLK_PERIOD * 2);
end
endtask
//==========================================================================
// Test Group 6: DC stub behavior
//
// CPLD2 CPLD_Con has DC slot cases but they are stubs:
// - No relay output changes
// - No PMU_OC output changes
// - Just sets exec_flag=0, exec_done_flag=1
//
// The top module maps OC_x09/OC_x25 to PMU_OC via:
// PMU_OC_N[8] = OC_x09[N-1]
// PMU_OC_N[24] = OC_x25[N-1]
// PMU_OC_N[7:0] = PMU_OC_internal[7:0] (always zero for DC stubs)
// PMU_OC_N[23:9] = PMU_OC_internal[23:8] (always zero for DC stubs)
// PMU_OC_N[31:25] = PMU_OC_internal[31:25] (always zero for DC stubs)
//==========================================================================
task test_dc_stub_behavior;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 6: DC stub behavior (no output changes)");
$display("-------------------------------------------------------------");
// 6a. DC_Slot1: Should complete but produce no relay/PMU output
$display(" DC1: Stub behavior - no relay or PMU output");
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_r);
tb_assert(done_r === 1'b1, "DC1 exec done (stub)", "timeout");
// RC outputs should remain at their previous state or be zero
// (DC stub doesn't change relay outputs)
tb_assert(o_err === 1'b0,
"DC1 stub: no error flag", "");
// 6b. DC_Slot2: Same stub behavior
$display(" DC2: Stub behavior - no relay or PMU output");
spi_write(mk_write(7'd7, 24'h004005));
@(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 exec done (stub)", "timeout");
tb_assert(o_err === 1'b0,
"DC2 stub: no error flag", "");
// 6c. DC_Slot3: Same stub behavior
$display(" DC3: Stub behavior - no relay or PMU output");
spi_write(mk_write(7'd8, 24'h006007));
@(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 exec done (stub)", "timeout");
tb_assert(o_err === 1'b0,
"DC3 stub: no error flag", "");
// 6d. DC_Slot4: Same stub behavior
$display(" DC4: Stub behavior - no relay or PMU output");
spi_write(mk_write(7'd9, 24'h008009));
@(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 exec done (stub)", "timeout");
tb_assert(o_err === 1'b0,
"DC4 stub: no error flag", "");
#(`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");
#(`SPI_CLK_PERIOD);
// 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 enable bits (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");
// 8d. Write STATE with EXEC=1 while already executing (should error)
$display(" Edge 8d: Write STATE while already executing");
spi_write(mk_write(7'd2, 24'h000005));
@(negedge i_sclk);
spi_write(mk_write(7'd1, 24'h000001)); // EXEC=1
@(negedge i_sclk);
// Try to write another register while EXEC is still active
spi_write(mk_write(7'd3, 24'h000006));
@(negedge i_sclk);
#(`SYS_CLK_PERIOD * 5);
tb_assert(o_err === 1'b1,
"o_err HIGH when writing during execution", "");
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
//==========================================================================
// 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