`timescale 1ns/1ps module tb_lisp_coproc; // ======================================================================== // 1. CONFIGURATION & CONSTANTS // ======================================================================== // Opcodes localparam [7:0] OP_CONS = 8'h01; localparam [7:0] OP_CAR = 8'h02; localparam [7:0] OP_CDR = 8'h03; localparam [7:0] OP_ATOM = 8'h04; localparam [7:0] OP_EQ = 8'h05; localparam [7:0] OP_ADD = 8'h06; // Tags localparam [1:0] TAG_NIL = 2'b00; localparam [1:0] TAG_ATOM = 2'b01; localparam [1:0] TAG_NUM = 2'b10; localparam [1:0] TAG_CONS = 2'b11; // Standard Values for Testing localparam [7:0] VAL_NIL = {TAG_NIL, 6'h00}; localparam [7:0] VAL_TRUE = {TAG_ATOM, 6'h01}; // 'T' localparam [7:0] VAL_A = {TAG_ATOM, 6'h0A}; localparam [7:0] VAL_B = {TAG_ATOM, 6'h0B}; localparam [7:0] VAL_C = {TAG_ATOM, 6'h0C}; // Testbench Signals reg clk, rst, cs, rw; reg [2:0] addr; reg [7:0] data_in; wire [7:0] data_out; // Verification Variables reg [7:0] read_val; reg [7:0] status_val; reg [7:0] ptr_node3, ptr_node2, ptr_node1; integer errors = 0; integer i; // Instantiate DUT lisp_coproc dut ( .clk(clk), .rst(rst), .cs(cs), .rw(rw), .addr(addr), .data_in(data_in), .data_out(data_out) ); // Clock Generation (10MHz) initial begin clk = 0; forever #50 clk = ~clk; end // ======================================================================== // 2. HELPER TASKS // ======================================================================== task cpu_write(input [2:0] w_addr, input [7:0] w_data); begin @(posedge clk); cs = 1; rw = 0; addr = w_addr; data_in = w_data; @(posedge clk); cs = 0; data_in = 8'h00; end endtask // Read with auto-polling for BUSY flag task cpu_exec_and_read(input [2:0] r_addr, output [7:0] r_data); integer timeout; begin timeout = 0; // Poll Status Bit 0 (BUSY) do begin @(posedge clk); cs = 1; rw = 1; addr = 3'h4; // Status @(posedge clk); status_val = data_out; cs = 0; // Panic button: Break if stuck for 100 cycles timeout = timeout + 1; if (timeout > 100) begin $display("ERROR: Timed out polling BUSY bit! Status: %b", status_val); break; end end while (status_val[0] === 1'b1); // Perform Read @(posedge clk); cs = 1; rw = 1; addr = r_addr; @(posedge clk); r_data = data_out; cs = 0; end endtask task check(input [7:0] expected, input [7:0] actual, input string name); if (expected !== actual) begin $display("FAIL: %s | Exp: 0x%h, Got: 0x%h", name, expected, actual); errors = errors + 1; end else begin $display("PASS: %s", name); end endtask task check_status(input bit exp_heap, input bit exp_type, input bit exp_carry, input bit exp_zero, input string name); // Status Reg: [7:5]Rsrv, [4]Zero, [3]Carry, [2]Type, [1]Heap, [0]Busy reg [7:0] expected_mask; expected_mask = {3'b000, exp_zero, exp_carry, exp_type, exp_heap, 1'b0}; cpu_exec_and_read(3'h4, status_val); // Mask out the busy bit for comparison as it should be 0 now if ((status_val & 8'hFE) !== expected_mask) begin $display("FAIL: %s (Status) | Exp: %b, Got: %b", name, expected_mask, status_val); errors = errors + 1; end else begin $display("PASS: %s (Status)", name); end endtask // ======================================================================== // 3. MAIN TEST SCENARIOS // ======================================================================== initial begin $dumpfile("lisp_coproc.vcd"); $dumpvars(0, tb_lisp_coproc); // --- Initialize --- clk = 0; cs = 0; rw = 0; addr = 0; data_in = 0; // --- AGGRESSIVE RESET SEQUENCE --- rst = 1; // Assert Reset #500; // Hold for 50 cycles (allows X propagation to clear) rst = 0; // Release Reset #100; // Wait for logic to settle into IDLE $display("\n=== STARTING VERIFICATION ===\n"); // -------------------------------------------------------- // SCENARIO 1: ALU Boundary & Overflow // -------------------------------------------------------- $display("--- Scenario 1: ALU Mathematics ---"); // 1.1 Simple Add: 10 + 15 = 25 cpu_write(3'h1, {TAG_NUM, 6'd10}); cpu_write(3'h2, {TAG_NUM, 6'd15}); cpu_write(3'h0, OP_ADD); cpu_exec_and_read(3'h3, read_val); check({TAG_NUM, 6'd25}, read_val, "Add 10+15"); check_status(0,0,0,0, "Add Normal Status"); // 1.2 Zero Check: 0 + 0 = 0 (Should set Zero flag) cpu_write(3'h1, {TAG_NUM, 6'd0}); cpu_write(3'h2, {TAG_NUM, 6'd0}); cpu_write(3'h0, OP_ADD); cpu_exec_and_read(3'h3, read_val); check({TAG_NUM, 6'd0}, read_val, "Add 0+0"); check_status(0,0,0,1, "Add Zero Status"); // Expect Zero=1 // 1.3 Overflow Check: 63 + 1 = 0 (Should set Carry flag) // Max 6-bit unsigned is 63. 63+1 wraps to 0. cpu_write(3'h1, {TAG_NUM, 6'd63}); cpu_write(3'h2, {TAG_NUM, 6'd1}); cpu_write(3'h0, OP_ADD); cpu_exec_and_read(3'h3, read_val); check({TAG_NUM, 6'd0}, read_val, "Add 63+1 (Wrap)"); check_status(0,0,1,1, "Add Overflow Status"); // Expect Carry=1, Zero=1 // -------------------------------------------------------- // SCENARIO 2: Equality (EQ) Logic // -------------------------------------------------------- $display("\n--- Scenario 2: EQ Logic ---"); // 2.1 Atom Equality (True) cpu_write(3'h1, VAL_A); cpu_write(3'h2, VAL_A); cpu_write(3'h0, OP_EQ); cpu_exec_and_read(3'h3, read_val); check(VAL_TRUE, read_val, "EQ(A, A)"); check_status(0,0,0,1, "EQ True Status"); // Zero flag used for equality? Spec says "ZERO (From ADD or EQ op)" // 2.2 Atom Inequality (False) cpu_write(3'h1, VAL_A); cpu_write(3'h2, VAL_B); cpu_write(3'h0, OP_EQ); cpu_exec_and_read(3'h3, read_val); check(VAL_NIL, read_val, "EQ(A, B)"); check_status(0,0,0,0, "EQ False Status"); // 2.3 Mixed Type Equality (Number 10 vs Atom 10) -> Should be NIL (Bits differ in Tag) cpu_write(3'h1, {TAG_NUM, 6'd10}); cpu_write(3'h2, {TAG_ATOM, 6'd10}); cpu_write(3'h0, OP_EQ); cpu_exec_and_read(3'h3, read_val); check(VAL_NIL, read_val, "EQ(Num, Atom)"); // -------------------------------------------------------- // SCENARIO 3: Linked List Construction (Chain Verification) // -------------------------------------------------------- $display("\n--- Scenario 3: Linked List (A B C) ---"); // Goal: Construct (A . (B . (C . NIL))) // Steps: // 1. Node3 = CONS(C, NIL) // 2. Node2 = CONS(B, Node3) // 3. Node1 = CONS(A, Node2) // Step 1: Node 3 cpu_write(3'h1, VAL_C); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, ptr_node3); check({TAG_CONS, 2'b00, 4'h0}, ptr_node3, "Alloc Node 3 (Ptr=0)"); // Step 2: Node 2 cpu_write(3'h1, VAL_B); cpu_write(3'h2, ptr_node3); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, ptr_node2); check({TAG_CONS, 2'b00, 4'h2}, ptr_node2, "Alloc Node 2 (Ptr=2)"); // Step 3: Node 1 cpu_write(3'h1, VAL_A); cpu_write(3'h2, ptr_node2); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, ptr_node1); check({TAG_CONS, 2'b00, 4'h4}, ptr_node1, "Alloc Node 1 (Ptr=4)"); // Step 4: Traverse! CAR(CDR(ptr_node1)) should be B // CDR(Node1) -> Should get Node2 Ptr cpu_write(3'h1, ptr_node1); cpu_write(3'h0, OP_CDR); cpu_exec_and_read(3'h3, read_val); check(ptr_node2, read_val, "Traverse: CDR(Node1)"); // CAR(Result) -> Should get B cpu_write(3'h1, read_val); cpu_write(3'h0, OP_CAR); cpu_exec_and_read(3'h3, read_val); check(VAL_B, read_val, "Traverse: CAR(Node2)"); // -------------------------------------------------------- // SCENARIO 4: Error Type Matrix // -------------------------------------------------------- $display("\n--- Scenario 4: Type Safety ---"); // 4.1 CAR on ATOM (Fail) cpu_write(3'h1, VAL_A); cpu_write(3'h0, OP_CAR); check_status(0,1,0,0, "Err: CAR on Atom"); // Expect ErrType=1 // 4.2 CDR on NUMBER (Fail) cpu_write(3'h1, {TAG_NUM, 6'd5}); cpu_write(3'h0, OP_CDR); check_status(0,1,0,0, "Err: CDR on Number"); // 4.3 ADD on CONS (Fail) cpu_write(3'h1, {TAG_NUM, 6'd5}); cpu_write(3'h2, ptr_node1); cpu_write(3'h0, OP_ADD); check_status(0,1,0,0, "Err: ADD on CONS"); // -------------------------------------------------------- // SCENARIO 5: Heap Full Boundary // -------------------------------------------------------- $display("\n--- Scenario 5: Heap Full Boundary ---"); // Current Alloc Pointer is at 6 (We did 3 CONS ops: 0, 2, 4). // Capacity is 16. Addresses 6, 8, 10, 12, 14 are free. // That is 5 more CONS operations allowed. // Fill 1 (Ptr 6) cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, read_val); // Wait // Fill 2 (Ptr 8) cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, read_val); // Fill 3 (Ptr 10) cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, read_val); // Fill 4 (Ptr 12) cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, read_val); // Fill 5 (Ptr 14) - THE LAST VALID ONE cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); cpu_exec_and_read(3'h3, read_val); check({TAG_CONS, 2'b00, 4'hE}, read_val, "Last Valid Alloc (Ptr=14)"); check_status(0,0,0,0, "Status at Capacity"); // ATTEMPT OVERFLOW cpu_write(3'h1, VAL_NIL); cpu_write(3'h2, VAL_NIL); cpu_write(3'h0, OP_CONS); // Check Status cpu_exec_and_read(3'h4, status_val); // Expect ErrHeap=1 if (status_val[1] !== 1'b1) begin $display("FAIL: Heap Full Detection | Got Status: %b", status_val); errors = errors + 1; end else begin $display("PASS: Heap Full Detection"); end // -------------------------------------------------------- // RESULT SUMMARY // -------------------------------------------------------- $display("\n=================================="); if (errors == 0) $display(" SUCCESS: All Tests Passed"); else $display(" FAILURE: %0d Errors Found", errors); $display("=================================="); $finish; end endmodule