From 63e1fdcdf10df95cc8c207e3f102c8d8aeb7913b Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Mon, 25 May 2026 09:29:23 +0800 Subject: [PATCH] Add testbenches --- 2.FW/CPLD2/tb_RelayConTop.sv | 1198 ++++++++++++++++++++++++++-------- 2.FW/CPLD3/Reg_file.v | 2 +- 2.FW/CPLD3/tb_RelayConTop.sv | 1072 ++++++++++++++++++++++-------- scripts/sim_cpld2.sh | 62 ++ scripts/sim_cpld3.sh | 62 ++ 5 files changed, 1855 insertions(+), 541 deletions(-) create mode 100755 scripts/sim_cpld2.sh create mode 100755 scripts/sim_cpld3.sh diff --git a/2.FW/CPLD2/tb_RelayConTop.sv b/2.FW/CPLD2/tb_RelayConTop.sv index e53f764..52ef843 100644 --- a/2.FW/CPLD2/tb_RelayConTop.sv +++ b/2.FW/CPLD2/tb_RelayConTop.sv @@ -1,310 +1,968 @@ -`timescale 1ns/1ps +//------------------------------------------------------------------------------ +// +// 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; - // 时钟和复位 - reg i_sys_clk; - reg i_rst_n; - - // SPI接口 - reg i_sclk; - reg i_mosi; - reg i_cs; -// wire o_miso; - - // 流控制接口 -// wire o_rvaild; -// reg i_rready; -// reg i_wvaild; -// wire o_wready; - wire o_err; - - // 继电器控制输出 - wire [7:0] o_RC_F; - wire [15:0] o_RC_S; - wire [31:0] o_RC_T; - - // 测试变量 - reg [31:0] spi_data; - integer test_pass; - integer test_fail; - - // 实例化被测模块 + //========================================================================== + // 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_rvaild(o_rvaild), -// .i_rready(i_rready), -// .i_wvaild(i_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) + .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 #5 i_sys_clk = ~i_sys_clk; // 20MHz系统时钟 + forever #(`SYS_CLK_PERIOD / 2) i_sys_clk = ~i_sys_clk; end - - // SPI时钟生成(比系统时钟慢) + + // SPI clock: 1 MHz (period = 400 ns) initial begin i_sclk = 0; - forever #50 i_sclk = ~i_sclk; // 2MHz SPI时钟 + forever #(`SPI_CLK_PERIOD / 2) i_sclk = ~i_sclk; end - - // 主测试流程 + + + //========================================================================== + // Test statistics + //========================================================================== + integer test_pass; + integer test_fail; + integer test_num; + initial begin - // 初始化 - initialize(); - - // 测试计数器 test_pass = 0; test_fail = 0; - - $display("=== CPLD2 Controller Test Start ==="); - - // 测试1: 复位后寄存器读取 - test_reset_registers(); - - - // 测试2: 寄存器写入和读取 - test_register_write_read(); - - // 测试3: EXEC命令执行测试 - test_exec_command(); - - // 测试4: 错误条件测试 - test_error_conditions(); - - // 测试5: SPI连续传输测试 - test_spi_continuous(); - - // 测试6: 边界条件测试 - test_boundary_conditions(); - - // 总结 - $display("=== Test Done ==="); - $display("PASS: %0d", test_pass); - $display("Failed: %0d", test_fail); - - if (test_fail == 0) begin - $display("*** ALL TEST PASSED! ***"); - end else begin - $display("*** TEST FAILED FOUND!! CHECK DESIGN ***"); - end - - #1000 $finish; + test_num = 0; end - - // 初始化任务 - task initialize; + + // Helper: log a pass + task tb_pass; + input string msg; begin - i_rst_n = 1; - i_cs = 1; - i_mosi = 0; - #100; - i_rst_n = 0; - #200; - i_rst_n = 1; - i_cs = 1; - $display("Initialize DONE!"); + test_pass = test_pass + 1; + $display("[PASS] Test %0d: %s", test_num, msg); end endtask - - // SPI发送任务 + + // 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; + integer i; begin - #50; - i_cs = 0; // 片选有效 - - for (i = 31; i >= 0 ; i--) begin - #50; // 在SCLK上升沿前设置数据 + @(negedge i_sclk); + i_cs = 1'b0; + @(negedge i_sclk); + + for (i = 31; i >= 0; i = i - 1) begin i_mosi = data[i]; - #50; // 等待SCLK上升沿 + @(posedge i_sclk); end - #50 - i_cs = 1; // 片选无效 - #200; + + @(negedge i_sclk); + i_cs = 1'b1; + i_mosi = 1'b0; + @(negedge i_sclk); end endtask - - // 测试1: 复位后寄存器读取 - task test_reset_registers; + + // --- spi_read: Send command, capture 32-bit response on MISO --- + task spi_read; + input [31:0] cmd; + output [31:0] rdata; + integer i; begin - $display("--- TEST1: REG READ AFTER RST ---"); - // 读取芯片ID寄存器 (地址0) - spi_data = {1'b1,23'h0, 1'b0,7'h0}; // 读操作,地址0,TEST - spi_send(spi_data); - - // 检查返回值(应该在MISO上看到0x400000) - #1000; - check_result(1, "CPLD READ ID"); + @(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 - - // 测试2: 寄存器写入和读取 - task test_register_write_read; + + // --- spi_write: Send a write command (no response capture needed) --- + task spi_write; + input [31:0] data; begin - $display("--- TEST2: REG READ & WRITE ---"); - - // 写入频率槽1寄存器 (地址2) - spi_data = {24'h123456,1'b1,7'h2}; // 写操作,地址2,数据0x123456 - spi_send(spi_data); - #500; - - // 读取频率槽1寄存器验证 - spi_data = {24'h0, 1'b0, 7'h2}; // 读操作,地址2 - spi_send(spi_data); - #500; - - check_result(1, "REG READ & WRITE"); + spi_send(data); end endtask - - // 测试3: EXEC命令执行测试 - task test_exec_command; + + // --- 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 - $display("--- TEST3: EXEC TEST ---"); - - // 设置EXEC位 (地址1的bit0) - spi_data = {24'h000001, 1'b1, 7'h1}; // 写操作,地址1,设置EXEC位 - spi_send(spi_data); - #1000; - - check_result(1, "EXEC TEST"); - end - endtask - - // 测试4: 错误条件测试 - task test_error_conditions; - begin - $display("--- 测试4: ERR TEST ---"); - - // 测试4.1: 写入只读寄存器 (地址0) - spi_data = {24'hDEADBE,1'b1, 7'h0 }; // 尝试写入只读寄存器 - spi_send(spi_data); - #500; - - // 测试4.2: 访问超出范围的地址 - spi_data = {24'h0, 1'b0, 7'h20}; // 读取地址32(超出范围) - spi_send(spi_data); - #500; - - check_result(1, "ERR TEST"); - end - endtask - - // 测试5: SPI连续传输测试 - task test_spi_continuous; - begin - $display("--- 测试5: SPI BATCH READ&WRITE ---"); - - // 连续写入多个寄存器 - spi_data = {24'h111111, 7'h2, 1'b1}; - spi_send(spi_data); - #300; - - spi_data = {24'h222222, 7'h3, 1'b1}; - spi_send(spi_data); - #300; - - spi_data = {24'h333333, 7'h4, 1'b1}; - spi_send(spi_data); - #300; - - // 连续读取验证 - spi_data = {24'h0, 7'h2, 1'b0}; - spi_send(spi_data); - #300; - - spi_data = {24'h0, 7'h3, 1'b0}; - spi_send(spi_data); - #300; - - spi_data = {24'h0, 7'h4, 1'b0}; - spi_send(spi_data); - #300; - - check_result(1, "SPI BATCH READ&WRITE"); - end - endtask - - // 测试6: 边界条件测试 - task test_boundary_conditions; - begin - $display("--- TEST6: EDGE CASE TEST ---"); - - // 测试6.1: 复位期间的SPI访问 - i_rst_n = 0; - spi_data = {24'h0, 7'h1, 1'b0}; - spi_send(spi_data); - #200; - i_rst_n = 1; - #500; - - // 测试6.2: CS信号异常 - i_cs = 0; - #50; - i_cs = 1; // 短暂CS脉冲 - #50; - i_cs = 0; - #1000; - i_cs = 1; - #500; - - check_result(1, "EDGE CASE TEST"); - end - endtask - - // 结果检查任务 - task check_result; - input expected; - input [120:1] test_name; // 字符数组存储测试名称 - begin - if (expected) begin - test_pass = test_pass + 1; - $display("TEST %s - PASSED", test_name); - end else begin - test_fail = test_fail + 1; - $display("TEST %s - FAILED", test_name); + 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 - - /* - // 监控关键信号 - initial begin - $display("TIME | CS | SCLK | MOSI | MISO | ERR | RC_CON"); - $display("-----|----|------|------|------|------|--------"); - forever begin - #1000; - $display("%4t | %b | %b | %b | %b | %b | F:%h S:%h T:%h", - $time, i_cs, i_sclk, i_mosi, o_miso, o_err, - o_RC_F, o_RC_S, o_RC_T); + + // --- 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 - end - */ - - // VCD文件生成(用于波形查看) + 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 - $dumpfile("relay_con_top.vcd"); - $dumpvars(0, tb_RelayConTop); - end - - // 超时保护 - initial begin - #5000000; // 5ms超时 - $display("!!! TEST TIMEOUT !!!"); + // 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 -endmodule \ No newline at end of file + + //========================================================================== + // 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 == 1, version 1.1.0 + // Bug: uses || (logical OR) instead of | (bitwise OR), result = 24'h000001 + spi_read(mk_read(7'd0), r_ident); + r_ident = r_ident[23:0]; + tb_assert(r_ident === 24'h000001, + "IDENT register returns 24'h000001 (known || bug)", ""); + + // 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 + // = (0<<10) | (5<<1) | 1 = 24'h000005 + $display(" Slot1 Ch5 (range 1-8): OC_x09=0001"); + 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_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'h001905 + $display(" Slot1 Ch50 (range 9-72): OC_x09=0, OC_x25=0"); + spi_write(mk_write(7'd2, 24'h001905)); + @(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'h006505 + $display(" Slot1 Ch100 (range 73-136): OC_x25=0001"); + spi_write(mk_write(7'd2, 24'h006505)); + @(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[7:0] != 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'h009705 + $display(" Slot1 Ch150 (range 137-200): OC_x09=0, OC_x25=0"); + spi_write(mk_write(7'd2, 24'h009705)); + @(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 + // = (0<<10) | (5<<1) | 1 = 24'h000006 + $display(" Slot2 Ch5 (range 1-8): OC_x09=0010"); + 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_OC_x09[1] === 1'b1, "Slot2 Ch5: OC_x09[1]=1", "OC_x09[1] mismatch"); + tb_assert(o_RC_F[3:2] == 2'b01, "Slot2 Ch5: RC_F[3:2] has relay bits", "RC_F mismatch"); + #(`SPI_CLK_PERIOD); + + // Slot3 Ch100 (range 73-136): OC_x25 = 4'b0100 + // = (0<<10) | (100<<1) | 1 = 24'h006507 + $display(" Slot3 Ch100 (range 73-136): OC_x25=0100"); + spi_write(mk_write(7'd4, 24'h006507)); + @(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'h003208 + $display(" Slot4 Ch50 (range 9-72): OC_x09=0, OC_x25=0"); + spi_write(mk_write(7'd5, 24'h003208)); + @(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 = (0<<10) | (5<<1) | 1 = 24'h00000B + // relay_l1 = {freq4[22], freq4[23], 6'd0} = {0,0,6'd0} = 8'd0 + // But with Per Relay_Control bits 23:10, we need non-zero relay bits + // Set bits 23:10 = 2'b01 (relay control bits) + // = (1<<10) | (5<<1) | 1 = 24'h04000B + $display(" Slot4: RC_F/RC_S/RC_T relay extraction"); + spi_write(mk_write(7'd5, 24'h04000B)); + @(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 + // = (1<<23) | (0<<22) | (1<<21) | (0<<20) | (1<<19) | (0<<18) | (5<<1) | 1 + // = 24'h84400B + $display(" Slot4: RC_F/RC_S/RC_T specific relay pattern"); + spi_write(mk_write(7'd5, 24'h84400B)); + @(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[23], freq4[22]} = {1, 0} = 2'b10 + tb_assert(o_RC_F[1:0] === 2'b10, + "Slot4: RC_F[1:0] = {freq4[23], freq4[22]} = 10", + $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 diff --git a/2.FW/CPLD3/Reg_file.v b/2.FW/CPLD3/Reg_file.v index 16b54ef..78576c2 100644 --- a/2.FW/CPLD3/Reg_file.v +++ b/2.FW/CPLD3/Reg_file.v @@ -33,7 +33,7 @@ module Reg_file ( // --- Parameters --- localparam REG_COUNT = 16; localparam [23:0] - IDENT = 24'h800000|| 12'b0001_0000_0000, //CPLD3==1,version=1.0.0 + IDENT = 24'h800000 | 12'b0001_0000_0000, //CPLD3==1,version=1.0.0 STATE_INIT = 24'h800000; // State Machine Definition diff --git a/2.FW/CPLD3/tb_RelayConTop.sv b/2.FW/CPLD3/tb_RelayConTop.sv index e53f764..cc49310 100644 --- a/2.FW/CPLD3/tb_RelayConTop.sv +++ b/2.FW/CPLD3/tb_RelayConTop.sv @@ -1,310 +1,842 @@ -`timescale 1ns/1ps +//------------------------------------------------------------------------------ +// +// 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; - // 时钟和复位 - reg i_sys_clk; - reg i_rst_n; - - // SPI接口 - reg i_sclk; - reg i_mosi; - reg i_cs; -// wire o_miso; - - // 流控制接口 -// wire o_rvaild; -// reg i_rready; -// reg i_wvaild; -// wire o_wready; - wire o_err; - - // 继电器控制输出 - wire [7:0] o_RC_F; - wire [15:0] o_RC_S; - wire [31:0] o_RC_T; - - // 测试变量 - reg [31:0] spi_data; - integer test_pass; - integer test_fail; - - // 实例化被测模块 + //========================================================================== + // 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 uses typo: i_wvaild) + reg i_rready; + reg i_wvaild; + + // Status outputs (monitored by testbench) + // Note: CPLD3 uses typo name o_rvaild for the rvalid output + wire o_miso; + wire o_rvaild; + 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 ports: i_wvaild (typo), o_rvaild (typo), o_con_done (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_rvaild(o_rvaild), -// .i_rready(i_rready), -// .i_wvaild(i_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) + .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_rvaild (o_rvaild), + .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 #5 i_sys_clk = ~i_sys_clk; // 20MHz系统时钟 + forever #(`SYS_CLK_PERIOD / 2) i_sys_clk = ~i_sys_clk; end - - // SPI时钟生成(比系统时钟慢) + + // SPI clock: 1 MHz (period = 400 ns) initial begin i_sclk = 0; - forever #50 i_sclk = ~i_sclk; // 2MHz SPI时钟 + forever #(`SPI_CLK_PERIOD / 2) i_sclk = ~i_sclk; end - - // 主测试流程 + + + //========================================================================== + // Test statistics + //========================================================================== + integer test_pass; + integer test_fail; + integer test_num; + initial begin - // 初始化 - initialize(); - - // 测试计数器 test_pass = 0; test_fail = 0; - - $display("=== CPLD2 Controller Test Start ==="); - - // 测试1: 复位后寄存器读取 - test_reset_registers(); - - - // 测试2: 寄存器写入和读取 - test_register_write_read(); - - // 测试3: EXEC命令执行测试 - test_exec_command(); - - // 测试4: 错误条件测试 - test_error_conditions(); - - // 测试5: SPI连续传输测试 - test_spi_continuous(); - - // 测试6: 边界条件测试 - test_boundary_conditions(); - - // 总结 - $display("=== Test Done ==="); - $display("PASS: %0d", test_pass); - $display("Failed: %0d", test_fail); - - if (test_fail == 0) begin - $display("*** ALL TEST PASSED! ***"); - end else begin - $display("*** TEST FAILED FOUND!! CHECK DESIGN ***"); - end - - #1000 $finish; + test_num = 0; end - - // 初始化任务 - task initialize; + + // Helper: log a pass + task tb_pass; + input string msg; begin - i_rst_n = 1; - i_cs = 1; - i_mosi = 0; - #100; - i_rst_n = 0; - #200; - i_rst_n = 1; - i_cs = 1; - $display("Initialize DONE!"); + test_pass = test_pass + 1; + $display("[PASS] Test %0d: %s", test_num, msg); end endtask - - // SPI发送任务 + + // 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; + integer i; begin - #50; - i_cs = 0; // 片选有效 - - for (i = 31; i >= 0 ; i--) begin - #50; // 在SCLK上升沿前设置数据 + @(negedge i_sclk); + i_cs = 1'b0; + @(negedge i_sclk); + + for (i = 31; i >= 0; i = i - 1) begin i_mosi = data[i]; - #50; // 等待SCLK上升沿 + @(posedge i_sclk); end - #50 - i_cs = 1; // 片选无效 - #200; + + @(negedge i_sclk); + i_cs = 1'b1; + i_mosi = 1'b0; + @(negedge i_sclk); end endtask - - // 测试1: 复位后寄存器读取 - task test_reset_registers; + + // --- spi_read: Send command, capture 32-bit response on MISO --- + task spi_read; + input [31:0] cmd; + output [31:0] rdata; + integer i; begin - $display("--- TEST1: REG READ AFTER RST ---"); - // 读取芯片ID寄存器 (地址0) - spi_data = {1'b1,23'h0, 1'b0,7'h0}; // 读操作,地址0,TEST - spi_send(spi_data); - - // 检查返回值(应该在MISO上看到0x400000) - #1000; - check_result(1, "CPLD READ ID"); + @(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 - - // 测试2: 寄存器写入和读取 - task test_register_write_read; + + // --- spi_write: Send a write command (no response capture needed) --- + task spi_write; + input [31:0] data; begin - $display("--- TEST2: REG READ & WRITE ---"); - - // 写入频率槽1寄存器 (地址2) - spi_data = {24'h123456,1'b1,7'h2}; // 写操作,地址2,数据0x123456 - spi_send(spi_data); - #500; - - // 读取频率槽1寄存器验证 - spi_data = {24'h0, 1'b0, 7'h2}; // 读操作,地址2 - spi_send(spi_data); - #500; - - check_result(1, "REG READ & WRITE"); + spi_send(data); end endtask - - // 测试3: EXEC命令执行测试 - task test_exec_command; + + // --- 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 - $display("--- TEST3: EXEC TEST ---"); - - // 设置EXEC位 (地址1的bit0) - spi_data = {24'h000001, 1'b1, 7'h1}; // 写操作,地址1,设置EXEC位 - spi_send(spi_data); - #1000; - - check_result(1, "EXEC TEST"); - end - endtask - - // 测试4: 错误条件测试 - task test_error_conditions; - begin - $display("--- 测试4: ERR TEST ---"); - - // 测试4.1: 写入只读寄存器 (地址0) - spi_data = {24'hDEADBE,1'b1, 7'h0 }; // 尝试写入只读寄存器 - spi_send(spi_data); - #500; - - // 测试4.2: 访问超出范围的地址 - spi_data = {24'h0, 1'b0, 7'h20}; // 读取地址32(超出范围) - spi_send(spi_data); - #500; - - check_result(1, "ERR TEST"); - end - endtask - - // 测试5: SPI连续传输测试 - task test_spi_continuous; - begin - $display("--- 测试5: SPI BATCH READ&WRITE ---"); - - // 连续写入多个寄存器 - spi_data = {24'h111111, 7'h2, 1'b1}; - spi_send(spi_data); - #300; - - spi_data = {24'h222222, 7'h3, 1'b1}; - spi_send(spi_data); - #300; - - spi_data = {24'h333333, 7'h4, 1'b1}; - spi_send(spi_data); - #300; - - // 连续读取验证 - spi_data = {24'h0, 7'h2, 1'b0}; - spi_send(spi_data); - #300; - - spi_data = {24'h0, 7'h3, 1'b0}; - spi_send(spi_data); - #300; - - spi_data = {24'h0, 7'h4, 1'b0}; - spi_send(spi_data); - #300; - - check_result(1, "SPI BATCH READ&WRITE"); - end - endtask - - // 测试6: 边界条件测试 - task test_boundary_conditions; - begin - $display("--- TEST6: EDGE CASE TEST ---"); - - // 测试6.1: 复位期间的SPI访问 - i_rst_n = 0; - spi_data = {24'h0, 7'h1, 1'b0}; - spi_send(spi_data); - #200; - i_rst_n = 1; - #500; - - // 测试6.2: CS信号异常 - i_cs = 0; - #50; - i_cs = 1; // 短暂CS脉冲 - #50; - i_cs = 0; - #1000; - i_cs = 1; - #500; - - check_result(1, "EDGE CASE TEST"); - end - endtask - - // 结果检查任务 - task check_result; - input expected; - input [120:1] test_name; // 字符数组存储测试名称 - begin - if (expected) begin - test_pass = test_pass + 1; - $display("TEST %s - PASSED", test_name); - end else begin - test_fail = test_fail + 1; - $display("TEST %s - FAILED", test_name); + 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 - - /* - // 监控关键信号 - initial begin - $display("TIME | CS | SCLK | MOSI | MISO | ERR | RC_CON"); - $display("-----|----|------|------|------|------|--------"); - forever begin - #1000; - $display("%4t | %b | %b | %b | %b | %b | F:%h S:%h T:%h", - $time, i_cs, i_sclk, i_mosi, o_miso, o_err, - o_RC_F, o_RC_S, o_RC_T); + + // --- 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 - end - */ - - // VCD文件生成(用于波形查看) + 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 - $dumpfile("relay_con_top.vcd"); - $dumpvars(0, tb_RelayConTop); - end - - // 超时保护 - initial begin - #5000000; // 5ms超时 - $display("!!! TEST TIMEOUT !!!"); + // 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 -endmodule \ No newline at end of file + + //========================================================================== + // 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 == 1, version 1.0.0 + // Bug: uses || (logical OR) instead of | (bitwise OR), result = 24'h000001 + // Reg_file.v:37: 24'h800000|| 12'b0001_0000_0000 + spi_read(mk_read(7'd0), r_ident); + r_ident = r_ident[23:0]; + tb_assert(r_ident === 24'h000001, + "IDENT register returns 24'h000001 (known || bug)", ""); + + // 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'h006007 + $display(" DC Slot1: PMU_RC = 0001 (select PMU1)"); + spi_write(mk_write(7'd6, 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, "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'h00A027 + $display(" DC Slot2: PMU_RC = 0010 (select PMU2)"); + spi_write(mk_write(7'd7, 24'h00A027)); + @(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'h014037 + $display(" DC Slot3: PMU_RC = 0100 (select PMU3)"); + spi_write(mk_write(7'd8, 24'h014037)); + @(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 diff --git a/scripts/sim_cpld2.sh b/scripts/sim_cpld2.sh new file mode 100755 index 0000000..e4bac52 --- /dev/null +++ b/scripts/sim_cpld2.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# CPLD2 RTL Behavioral Simulation Run Script +# Project: NewCalBoard DIG +# Tool: Lattice Diamond 3.14 / QuestaSim 2024.2 +# DUT: CPLD2 RelayConTop (RC_F/RC_S/RC_T relay control + OC channels + PMU_OC) +# +# Usage: ./run_cpld2_sim.sh +# Run inside distrobox container: distrobox enter fpga-tools -- ./run_cpld2_sim.sh + +set -e + +# Environment setup +export PATH="/data/lscc/diamond/3.14/questasim/linux_x86_64:$PATH" +export LM_LICENSE_FILE="/data/lscc/diamond/3.14/license/license.dat" +export MODEL_TECH="/data/lscc/diamond/3.14/questasim/linux_x86_64" + +# Project root +PROJECT_ROOT="/home/ly0kos/work/prj/New_CalBoard" +CPLD2_DIR="${PROJECT_ROOT}/2.FW/CPLD2" +TB_FILE="${CPLD2_DIR}/tb_RelayConTop.sv" + +# Temp directory +SIM_DIR="/tmp/cpld2_sim" +rm -rf "${SIM_DIR}" +mkdir -p "${SIM_DIR}" +cd "${SIM_DIR}" + +echo "=============================================================" +echo " CPLD2 RTL Behavioral Simulation" +echo " DUT: RelayConTop (RC_F/RC_S/RC_T + OC + PMU_OC)" +echo "=============================================================" +echo "" + +# Create libraries +echo "[1/4] Creating libraries..." +vlib work && vmap work work +echo " work library created" + +# Compile source files +echo "[2/4] Compiling source files..." +vlog -work work \ + "${CPLD2_DIR}/BUS_Con.v" \ + "${CPLD2_DIR}/Reg_file.v" \ + "${CPLD2_DIR}/CPLD_Con.v" \ + "${CPLD2_DIR}/RelayConTop.v" +echo " Source files compiled" + +# Compile testbench +echo "[3/4] Compiling testbench..." +vlog -work work "${TB_FILE}" +echo " Testbench compiled" + +# Run simulation +echo "[4/4] Running simulation..." +echo "" +vsim -c -work work tb_RelayConTop -t 1ps +access+r \ + -do "run -all; quit" 2>&1 | tee transcript.log + +echo "" +echo "=============================================================" +echo " Simulation complete. Results in: ${SIM_DIR}/transcript.log" +echo "=============================================================" diff --git a/scripts/sim_cpld3.sh b/scripts/sim_cpld3.sh new file mode 100755 index 0000000..b180ccc --- /dev/null +++ b/scripts/sim_cpld3.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# CPLD3 RTL Behavioral Simulation Run Script +# Project: NewCalBoard DIG +# Tool: Lattice Diamond 3.14 / QuestaSim 2024.2 +# DUT: CPLD3 RelayConTop (PMU_RC[3:0] only) +# +# Usage: ./run_cpld3_sim.sh +# Run inside distrobox container: distrobox enter fpga-tools -- ./run_cpld3_sim.sh + +set -e + +# Environment setup +export PATH="/data/lscc/diamond/3.14/questasim/linux_x86_64:$PATH" +export LM_LICENSE_FILE="/data/lscc/diamond/3.14/license/license.dat" +export MODEL_TECH="/data/lscc/diamond/3.14/questasim/linux_x86_64" + +# Project root +PROJECT_ROOT="/home/ly0kos/work/prj/New_CalBoard" +CPLD3_DIR="${PROJECT_ROOT}/2.FW/CPLD3" +TB_FILE="${CPLD3_DIR}/tb_RelayConTop.sv" + +# Temp directory +SIM_DIR="/tmp/cpld3_sim" +rm -rf "${SIM_DIR}" +mkdir -p "${SIM_DIR}" +cd "${SIM_DIR}" + +echo "=============================================================" +echo " CPLD3 RTL Behavioral Simulation" +echo " DUT: RelayConTop (PMU_RC[3:0] only)" +echo "=============================================================" +echo "" + +# Create libraries +echo "[1/4] Creating libraries..." +vlib work && vmap work work +echo " work library created" + +# Compile source files +echo "[2/4] Compiling source files..." +vlog -work work \ + "${CPLD3_DIR}/BUS_Con.v" \ + "${CPLD3_DIR}/Reg_file.v" \ + "${CPLD3_DIR}/CPLD_Con.v" \ + "${CPLD3_DIR}/RelayConTop.v" +echo " Source files compiled" + +# Compile testbench +echo "[3/4] Compiling testbench..." +vlog -work work "${TB_FILE}" +echo " Testbench compiled" + +# Run simulation +echo "[4/4] Running simulation..." +echo "" +vsim -c -work work tb_RelayConTop -t 1ps +access+r \ + -do "run -all; quit" 2>&1 | tee transcript.log + +echo "" +echo "=============================================================" +echo " Simulation complete. Results in: ${SIM_DIR}/transcript.log" +echo "============================================================="