`timescale 1ns/1ps module tb_spi_controller; reg clk; reg rst_n; reg start; reg cpol; reg cpha; reg [7:0] data_in; reg [7:0] current_rx; reg miso; wire [7:0] data_out; wire busy; wire spi_clk; wire mosi; integer errors; integer checks; reg saw_error; localparam IDLE = 3'd0; localparam LEAD = 3'd2; localparam SAMPLE = 3'd3; localparam ERROR = 3'd6; spi_controller dut ( .clk(clk), .rst_n(rst_n), .data_in(data_in), .start(start), .cpol(cpol), .cpha(cpha), .data_out(data_out), .busy(busy), .spi_clk(spi_clk), .mosi(mosi), .miso(miso) ); always #5 clk = ~clk; always @(*) begin miso = 1'b1; if (dut.state == LEAD || dut.state == SAMPLE) begin miso = current_rx[dut.bit_count]; end end always @(posedge clk) begin if (dut.state == ERROR) begin saw_error <= 1'b1; end end task check; input cond; input string message; begin checks = checks + 1; if (!cond) begin errors = errors + 1; $display("CHECK FAILED: %0s", message); end end endtask task reset_dut; begin rst_n = 1'b0; start = 1'b0; cpol = 1'b0; cpha = 1'b0; data_in = 8'h00; current_rx = 8'h00; repeat (3) @(posedge clk); rst_n = 1'b1; @(posedge clk); end endtask task launch_transfer; input [7:0] tx_byte; input [7:0] rx_byte; input mode_cpol; input mode_cpha; begin current_rx = rx_byte; data_in = tx_byte; cpol = mode_cpol; cpha = mode_cpha; start = 1'b1; @(posedge clk); start = 1'b0; end endtask task wait_for_idle; integer timeout; begin timeout = 0; while ((busy || dut.state != IDLE) && timeout < 200) begin @(posedge clk); timeout = timeout + 1; end check(timeout < 200, "SPI transfer timed out"); end endtask initial begin clk = 1'b0; rst_n = 1'b0; start = 1'b0; cpol = 1'b0; cpha = 1'b0; data_in = 8'h00; current_rx = 8'h00; errors = 0; checks = 0; saw_error = 1'b0; reset_dut(); launch_transfer(8'hA5, 8'h3C, 1'b0, 1'b0); wait_for_idle(); check(data_out == 8'h3C, "CPOL=0 CPHA=0 readback mismatch"); check(spi_clk == 1'b0, "spi_clk should idle low when cpol=0"); check(busy == 1'b0, "busy should deassert after transfer"); launch_transfer(8'h5A, 8'hC3, 1'b1, 1'b1); wait_for_idle(); check(data_out == 8'hC3, "CPOL=1 CPHA=1 readback mismatch"); check(spi_clk == 1'b1, "spi_clk should idle high when cpol=1"); saw_error = 1'b0; launch_transfer(8'h96, 8'hF0, 1'b0, 1'b1); @(posedge clk); start = 1'b1; @(posedge clk); start = 1'b0; wait_for_idle(); check(saw_error, "start during busy should reach ERROR recovery"); check(busy == 1'b0, "busy should clear after error recovery"); if (errors == 0) begin $display("Hint: Total mismatched samples is 0 out of %0d samples", checks); end else begin $display("Hint: Total mismatched samples is %0d out of %0d samples", errors, checks); end $display("Mismatches: %0d in %0d samples", errors, checks); $finish; end endmodule