Files

840 lines
31 KiB
Systemverilog

//------------------------------------------------------------------------------
//
// Testbench: CPLD3 - RelayConTop
// Project: NewCalBoard DIG
// Tool: Lattice Diamond Verilog-2001 simulator
// Purpose: Verify SPI interface, register file, and PMU relay control logic
// for CPLD3 (PMU_RC[3:0] only).
//
// CPLD3-specific:
// - Outputs: PMU_RC[3:0] only (PMU relay control)
// - CPLD_Con handles PMU_RC only (no relay outputs, no DC handling)
// - Minimal CPLD_Con: freq slot masks 2 PMU channels, DC slot selects 1
//
// Architecture (3 sub-modules):
// RelayConTop
// - BUS_Con - SPI Mode 0 master interface (32-bit transactions)
// - Reg_file - 16-register file + execution FSM
// - CPLD_Con - PMU_RC relay control logic
//
// 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 CPLD3 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 (CPLD3 top module uses typo: i_wvaild)
reg i_rready;
reg i_wvaild;
// Status outputs (monitored by testbench)
wire o_miso;
wire o_rvalid;
wire o_wready;
wire o_err;
wire o_con_done;
// PMU relay control (CPLD3 specific)
wire [3:0] o_PMU_RC; // PMU relay control (4-bit)
//==========================================================================
// DUT instantiation - CPLD3 specific pin names
// Note: CPLD3 top module port: i_wvaild (typo), o_rvalid (correct)
//==========================================================================
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),
.o_wready (o_wready),
.o_err (o_err),
.o_con_done (o_con_done),
.o_PMU_RC (o_PMU_RC)
);
//==========================================================================
// 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 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_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(" CPLD3 RelayConTop - Comprehensive Testbench");
$display(" System clock: %d MHz | SPI clock: %d MHz",
1000 / `SYS_CLK_PERIOD, 1000 / `SPI_CLK_PERIOD);
$display(" Outputs: PMU_RC[3:0] (PMU relay control)");
$display("=============================================================");
$display("");
test_reset_behavior();
test_register_rw();
test_exec_protocol();
test_pmu_rc_freq_slots();
test_pmu_rc_dc_slots();
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: CPLD3=bit23, version=1.0.0 = 24'h800100
spi_read(mk_read(7'd0), r_ident);
r_ident = r_ident[23:0];
tb_assert(r_ident === 24'h800100,
"IDENT register returns 24'h800100 (CPLD3, v1.0.0)", "");
// PMU_RC should be 4'b1111 after reset (CPLD_Con initializes to 1111)
tb_assert(o_PMU_RC === 4'b1111,
"o_PMU_RC is 4'b1111 after reset (initial state)", "");
#(`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
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: PMU_RC freq slot decoding
//
// CPLD3 CPLD_Con PMU_RC logic (frequency mode):
// Slot1 (freq_slot_flag=0001): PMU_RC = 4'b1111 (mask all 4 PMU)
// Slot2 (freq_slot_flag=0010): PMU_RC = 4'b1100 (mask PMU 3,4)
// Slot3 (freq_slot_flag=0100): PMU_RC = 4'b1010 (mask PMU 1,3)
// Slot4 (freq_slot_flag=1000): PMU_RC = 4'b0110 (mask PMU 2,4)
//
// CPLD3 CPLD_Con PMU_RC logic (DC mode):
// Slot1 (dc_slot_flag=0001): PMU_RC = 4'b0001 (select PMU 1)
// Slot2 (dc_slot_flag=0010): PMU_RC = 4'b0010 (select PMU 2)
// Slot3 (dc_slot_flag=0100): PMU_RC = 4'b0100 (select PMU 3)
// Slot4 (dc_slot_flag=1000): PMU_RC = 4'b1000 (select PMU 4)
//
// Note: PMU_RC is 4-bit where each bit represents a PMU channel.
// Freq mode: bits are MASK bits (1 = disabled/masked)
// DC mode: bits are SELECT bits (1 = selected channel)
//==========================================================================
task test_pmu_rc_freq_slots;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 4: PMU_RC freq slot decoding");
$display("-------------------------------------------------------------");
// 4a. Freq_Slot1: PMU_RC = 4'b1111 (all masked)
// Freq_Slot1 = (0<<10) | (5<<1) | 1 = 24'h000005
$display(" Freq Slot1: PMU_RC = 1111 (all masked)");
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, "Freq Slot1 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b1111,
"Freq Slot1: PMU_RC = 4'b1111",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 4b. Freq_Slot2: PMU_RC = 4'b1100 (mask PMU 3,4)
// Freq_Slot2 = (0<<10) | (5<<1) | 1 = 24'h000006
$display(" Freq Slot2: PMU_RC = 1100 (mask PMU3,4)");
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, "Freq Slot2 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b1100,
"Freq Slot2: PMU_RC = 4'b1100",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 4c. Freq_Slot3: PMU_RC = 4'b1010 (mask PMU 1,3)
// Freq_Slot3 = (0<<10) | (5<<1) | 1 = 24'h000007
$display(" Freq Slot3: PMU_RC = 1010 (mask PMU1,3)");
spi_write(mk_write(7'd4, 24'h000007));
@(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, "Freq Slot3 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b1010,
"Freq Slot3: PMU_RC = 4'b1010",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 4d. Freq_Slot4: PMU_RC = 4'b0110 (mask PMU 2,4)
// Freq_Slot4 = (0<<10) | (5<<1) | 1 = 24'h000008
$display(" Freq Slot4: PMU_RC = 0110 (mask PMU2,4)");
spi_write(mk_write(7'd5, 24'h000008));
@(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, "Freq Slot4 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b0110,
"Freq Slot4: PMU_RC = 4'b0110",
$sformatf("PMU_RC=%b", o_PMU_RC));
#(`SPI_CLK_PERIOD * 2);
end
endtask
//==========================================================================
// Test Group 5: PMU_RC DC slot decoding
//
// CPLD3 CPLD_Con PMU_RC logic (DC mode):
// Slot1: PMU_RC = 4'b0001 (select PMU 1)
// Slot2: PMU_RC = 4'b0010 (select PMU 2)
// Slot3: PMU_RC = 4'b0100 (select PMU 3)
// Slot4: PMU_RC = 4'b1000 (select PMU 4)
//
// DC_Slot N bit layout:
// Bit 23: PMU(0)/DPS(1)
// Bit 22: V(0)/I(1)
// Bit 21:17: Rload_Sel (0-31)
// Bit 9:1: Channel_Number (1-256)
// Bit 0: Slot_EN (1=Enable)
//==========================================================================
task test_pmu_rc_dc_slots;
reg [1:0] done_r;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 5: PMU_RC DC slot decoding");
$display("-------------------------------------------------------------");
// 5a. DC_Slot1: PMU_RC = 4'b0001 (select PMU 1)
// DC_Slot1 = PMU mode, V mode, Rload=3, Ch=3
// = (0<<23) | (0<<22) | (3<<17) | (3<<1) | 1 = 24'h060007
$display(" DC Slot1: PMU_RC = 0001 (select PMU1)");
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, "DC Slot1 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b0001,
"DC Slot1: PMU_RC = 4'b0001",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 5b. DC_Slot2: PMU_RC = 4'b0010 (select PMU 2)
// DC_Slot2 = PMU mode, V mode, Rload=5, Ch=19
// = (0<<23) | (0<<22) | (5<<17) | (19<<1) | 1 = 24'h0A0027
$display(" DC Slot2: PMU_RC = 0010 (select PMU2)");
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, "DC Slot2 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b0010,
"DC Slot2: PMU_RC = 4'b0010",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 5c. DC_Slot3: PMU_RC = 4'b0100 (select PMU 3)
// DC_Slot3 = PMU mode, V mode, Rload=10, Ch=27
// = (0<<23) | (0<<22) | (10<<17) | (27<<1) | 1 = 24'h140037
$display(" DC Slot3: PMU_RC = 0100 (select PMU3)");
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, "DC Slot3 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b0100,
"DC Slot3: PMU_RC = 4'b0100",
$sformatf("PMU_RC=%b", o_PMU_RC));
// 5d. DC_Slot4: PMU_RC = 4'b1000 (select PMU 4)
// DC_Slot4 = PMU mode, V mode, Rload=18, Ch=35
// = (0<<23) | (0<<22) | (18<<17) | (35<<1) | 1 = 24'h240047
$display(" DC Slot4: PMU_RC = 1000 (select PMU4)");
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, "DC Slot4 exec done", "timeout");
tb_assert(o_PMU_RC === 4'b1000,
"DC Slot4: PMU_RC = 4'b1000",
$sformatf("PMU_RC=%b", o_PMU_RC));
#(`SPI_CLK_PERIOD * 2);
end
endtask
//==========================================================================
// Test Group 6: Error conditions
//==========================================================================
task test_error_conditions;
reg [1:0] done_r;
reg [31:0] rdata;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 6: Error conditions");
$display("-------------------------------------------------------------");
// 6a. Short SPI transaction (16 bits)
$display(" Error 6a: 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);
// 6b. Write to IDENT register (ADDR 0, read-only)
$display(" Error 6b: 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);
// 6c. Read out-of-bounds address (ADDR 16)
$display(" Error 6c: 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);
// 6d. Multiple slot enable bits (en_t > 1)
$display(" Error 6d: 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);
// 6e. Write to out-of-bounds address (ADDR 31)
$display(" Error 6e: 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 7: Edge cases
//==========================================================================
task test_edge_cases;
reg [31:0] rdata;
begin
$display("");
$display("-------------------------------------------------------------");
$display(" Group 7: Edge cases");
$display("-------------------------------------------------------------");
// 7a. Brief CS pulse (too short for 32-bit)
$display(" Edge 7a: 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);
// 7b. Rapid successive SPI writes
$display(" Edge 7b: 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);
// 7c. Write to reserved registers (ADDR 10-15) - no error
$display(" Edge 7c: 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");
// 7d. Write STATE with EXEC=1 while already executing (should error)
$display(" Edge 7d: 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