From 06b6b116934cc8341676f52f5f6040a038020c32 Mon Sep 17 00:00:00 2001 From: vin Date: Wed, 10 Dec 2025 16:35:42 -0500 Subject: migrate from electric vlsi to librelane/sky130 flow this is quite a major shift. moves the project from a manual, full-custom layout workflow to a more modern automated flow and a more modern process (130nm vs 350nm). also improved the testbench. migration also resolves previous manual routing-caused errors (like the CONS logic bug and difficulty in implementing TG logic). this nearly halved the transistor count from 15k to 8k and reduced area size by 300x. --- rtl/lisp_coproc.sv | 453 ++++++++++++++++++++----------------- rtl/tb_lisp_coproc.sv | 611 ++++++++++++++++++++++++++------------------------ 2 files changed, 556 insertions(+), 508 deletions(-) (limited to 'rtl') diff --git a/rtl/lisp_coproc.sv b/rtl/lisp_coproc.sv index 6bd8574..7d1c8a8 100644 --- a/rtl/lisp_coproc.sv +++ b/rtl/lisp_coproc.sv @@ -1,220 +1,251 @@ -`timescale 1ns/1ps // be consistent with testbench - module lisp_coproc ( - input wire clk, - input wire rst, - input wire cs, - input wire rw, // 0=Write, 1=Read - input wire [2:0] addr, - input wire [7:0] data_in, - output reg [7:0] data_out -); - - // ======================================================================== - // 1. DATAPATH Signals & Storage - // ======================================================================== - - // Registers - reg [7:0] opcode_reg, arg1_reg, arg2_reg, result_reg; - reg [7:0] heap [0:15]; - - // Bump Allocator: Uniform 4-bit register (0-15) - reg [3:0] bump_alloc; - - // Heap Status: Sticky bit to track if we have wrapped around (Full) - reg heap_filled; - - // Internal Flags (Transient for current OP) - reg flag_err_heap, flag_err_type, flag_carry, flag_zero; - - // ALU Signals - wire [5:0] alu_val_a = arg1_reg[5:0]; - wire [5:0] alu_val_b = arg2_reg[5:0]; - wire [6:0] alu_sum = alu_val_a + alu_val_b; - wire alu_eq = (arg1_reg == arg2_reg); - - // Type Checkers - wire is_cons_a = (arg1_reg[7:6] == 2'b11); - wire is_num_a = (arg1_reg[7:6] == 2'b10); - wire is_num_b = (arg2_reg[7:6] == 2'b10); - - // Allocation Logic (Datapath Adder) - // We use a 5-bit wire to capture the carry out. - // If bump_alloc is 14 (1110) + 2 = 16 (10000). - // alloc_sum[4] (Carry) is 1. alloc_sum[3:0] is 0000. - wire [4:0] alloc_sum = {1'b0, bump_alloc} + 5'd2; - wire alloc_carry = alloc_sum[4]; - - // ======================================================================== - // 2. FSM CONTROLLER - // ======================================================================== - - parameter [4:0] RESET = 5'b00001; - parameter [4:0] IDLE = 5'b00010; - parameter [4:0] DECODE = 5'b00100; - parameter [4:0] EXECUTE = 5'b01000; - parameter [4:0] WRITEBACK = 5'b10000; - - reg [4:0] state, next_state; - - always @(posedge clk or posedge rst) begin - if (rst) state <= RESET; - else state <= next_state; - end - - always @(*) begin - next_state = state; - case (state) - RESET: next_state = IDLE; - IDLE: if (cs && !rw && addr == 3'h0) next_state = DECODE; - DECODE: next_state = EXECUTE; - EXECUTE: next_state = WRITEBACK; - WRITEBACK: next_state = IDLE; - default: next_state = IDLE; - endcase - end - - // ======================================================================== - // 3. SEQUENTIAL LOGIC - // ======================================================================== - - integer i; - always @(posedge clk or posedge rst) begin - if (rst) begin - opcode_reg <= 8'h00; - arg1_reg <= 8'h00; - arg2_reg <= 8'h00; - result_reg <= 8'h00; - bump_alloc <= 4'h0; - heap_filled <= 1'b0; - - flag_err_heap <= 1'b0; - flag_err_type <= 1'b0; - flag_carry <= 1'b0; - flag_zero <= 1'b0; - - for (i=0; i<16; i=i+1) heap[i] <= 8'h00; - - end else begin - - // --- MMIO Writes --- - if (cs && !rw) begin - case (addr) - 3'h0: opcode_reg <= data_in; - 3'h1: arg1_reg <= data_in; - 3'h2: arg2_reg <= data_in; - 3'h3: result_reg <= data_in; - default: ; - endcase - end - - // --- State Actions --- - case (state) - RESET: begin - bump_alloc <= 4'h0; - heap_filled <= 1'b0; - end + input wire clk, + input wire rst, + input wire cs, + input wire rw, // 0=Write, 1=Read + input wire [2:0] addr, + input wire [7:0] data_in, + output reg [7:0] data_out + ); - IDLE: begin - if (cs && !rw && addr == 3'h0) begin - flag_err_heap <= 1'b0; - flag_err_type <= 1'b0; - flag_carry <= 1'b0; - flag_zero <= 1'b0; - end - end + // ======================================================================== + // 1. DATAPATH Signals & Storage + // ======================================================================== + + // Registers + reg [7:0] opcode_reg, arg1_reg, arg2_reg, result_reg; + reg [7:0] heap [0:15]; + + // Bump Allocator: Uniform 4-bit register (0-15) + reg [3:0] bump_alloc; + + // Heap Status: Sticky bit to track if we have wrapped around (Full) + reg heap_filled; + + // Internal Flags (Transient for current OP) + reg flag_err_heap, flag_err_type, flag_carry, flag_zero; + + // ALU Signals + wire [5:0] alu_val_a = arg1_reg[5:0]; + wire [5:0] alu_val_b = arg2_reg[5:0]; + wire [6:0] alu_sum = alu_val_a + alu_val_b; + wire alu_eq = (arg1_reg == arg2_reg); + + // Type Checkers + wire is_cons_a = (arg1_reg[7:6] == 2'b11); + wire is_num_a = (arg1_reg[7:6] == 2'b10); + wire is_num_b = (arg2_reg[7:6] == 2'b10); + + // Allocation Logic (Datapath Adder) + // We use a 5-bit wire to capture the carry out. + // If bump_alloc is 14 (1110) + 2 = 16 (10000). + // alloc_sum[4] (Carry) is 1. alloc_sum[3:0] is 0000. + wire [4:0] alloc_sum = {1'b0, bump_alloc} + 5'd2; + wire alloc_carry = alloc_sum[4]; - EXECUTE: begin - case (opcode_reg) - 8'h01: begin // CONS - if (heap_filled) begin - // If sticky flag is set, we are full. Error. - flag_err_heap <= 1'b1; - end else begin - // Perform allocation - heap[bump_alloc] <= arg1_reg; - heap[bump_alloc + 1] <= arg2_reg; - - // Update pointer (wraps automatically due to 4-bit) - bump_alloc <= alloc_sum[3:0]; - - // If we generated a carry (14->16), mark heap as filled - if (alloc_carry) heap_filled <= 1'b1; - end - end - 8'h02: begin // CAR - if (!is_cons_a) flag_err_type <= 1'b1; - end - 8'h03: begin // CDR - if (!is_cons_a) flag_err_type <= 1'b1; - end - 8'h05: begin // EQ - if (alu_eq) flag_zero <= 1'b1; - end - 8'h06: begin // ADD - if (!is_num_a || !is_num_b) begin - flag_err_type <= 1'b1; - end else begin - if (alu_sum[6]) flag_carry <= 1'b1; - if (alu_sum[5:0] == 6'd0) flag_zero <= 1'b1; - end - end - default: ; - endcase - end + // ======================================================================== + // 2. FSM CONTROLLER (Safe Binary Encoding) + // ======================================================================== + + // Explicit 3-bit encoding avoids optimization ambiguity + localparam [2:0] RESET = 3'd0; + localparam [2:0] IDLE = 3'd1; + localparam [2:0] DECODE = 3'd2; + localparam [2:0] EXECUTE = 3'd3; + localparam [2:0] WRITEBACK = 3'd4; - WRITEBACK: begin - case (opcode_reg) - 8'h01: begin // CONS - if (!flag_err_heap) - // Math trick: If bump_alloc wrapped to 0, - // 0 - 2 = 14 (1110 in 2's comp), which is the correct pointer. - result_reg <= {2'b11, 2'b00, bump_alloc - 4'd2}; - end - 8'h02: begin // CAR - if (!flag_err_type) result_reg <= heap[arg1_reg[3:0]]; - end - 8'h03: begin // CDR - if (!flag_err_type) result_reg <= heap[arg1_reg[3:0] + 1]; - end - 8'h04: begin // ATOM - result_reg <= is_cons_a ? 8'h00 : 8'h41; - end - 8'h05: begin // EQ - result_reg <= alu_eq ? 8'h41 : 8'h00; - end - 8'h06: begin // ADD - if (!flag_err_type) result_reg <= {2'b10, alu_sum[5:0]}; - end - default: ; - endcase - end - default: ; - endcase + reg [2:0] state, next_state; + reg busy_bit; + + // Sequential Logic + always @(posedge clk or posedge rst) begin + if (rst) state <= RESET; + else state <= next_state; + end + + // Combinational Next-State Logic + always @(*) begin + // 1. Default assignments to prevent latches + next_state = IDLE; // Default to IDLE (Safe recovery) + busy_bit = 1'b1; // Default to BUSY + + case (state) + RESET: begin + next_state = IDLE; + busy_bit = 1'b1; + end + + IDLE: begin + busy_bit = 1'b0; // Not Busy + // Transition Logic + if (cs && !rw && addr == 3'h0) + next_state = DECODE; + else + next_state = IDLE; + end + + DECODE: begin + next_state = EXECUTE; + busy_bit = 1'b1; + end + + EXECUTE: begin + next_state = WRITEBACK; + busy_bit = 1'b1; + end + + WRITEBACK: begin + next_state = IDLE; + busy_bit = 1'b1; + end + + default: begin + next_state = IDLE; + busy_bit = 1'b1; end - end - - // ======================================================================== - // 4. OUTPUT LOGIC - // ======================================================================== - - wire busy_bit = (state != IDLE); - // Note: bit 1 is the transient error flag, not the internal sticky state - wire [7:0] current_status = {3'b000, flag_zero, flag_carry, flag_err_type, flag_err_heap, busy_bit}; - - always @(*) begin - if (cs && rw) begin + endcase + end + + // ======================================================================== + // 3. SEQUENTIAL LOGIC + // ======================================================================== + + integer i; + always @(posedge clk or posedge rst) begin + if (rst) begin + opcode_reg <= 8'h00; + arg1_reg <= 8'h00; + arg2_reg <= 8'h00; + result_reg <= 8'h00; + bump_alloc <= 4'h0; + heap_filled <= 1'b0; + + flag_err_heap <= 1'b0; + flag_err_type <= 1'b0; + flag_carry <= 1'b0; + flag_zero <= 1'b0; + + for (i=0; i<16; i=i+1) heap[i] <= 8'h00; + + end else begin + + // --- MMIO Writes --- + if (cs && !rw) begin case (addr) - 3'h0: data_out = opcode_reg; - 3'h1: data_out = arg1_reg; - 3'h2: data_out = arg2_reg; - 3'h3: data_out = result_reg; - 3'h4: data_out = current_status; - default: data_out = 8'h00; + 3'h0: opcode_reg <= data_in; + 3'h1: arg1_reg <= data_in; + 3'h2: arg2_reg <= data_in; + 3'h3: result_reg <= data_in; + default: ; endcase - end else begin - data_out = 8'hZZ; - end - end + end + + // --- State Actions --- + case (state) + RESET: begin + bump_alloc <= 4'h0; + heap_filled <= 1'b0; + end + + IDLE: begin + if (cs && !rw && addr == 3'h0) begin + flag_err_heap <= 1'b0; + flag_err_type <= 1'b0; + flag_carry <= 1'b0; + flag_zero <= 1'b0; + end + end + + EXECUTE: begin + case (opcode_reg) + 8'h01: begin // CONS + if (heap_filled) begin + // If sticky flag is set, we are full. Error. + flag_err_heap <= 1'b1; + end else begin + // Perform allocation + heap[bump_alloc] <= arg1_reg; + heap[(bump_alloc + 1) % 16] <= arg2_reg; + + // Update pointer (wraps automatically due to 4-bit) + bump_alloc <= alloc_sum[3:0]; + + // If we generated a carry (14->16), mark heap as filled + if (alloc_carry) heap_filled <= 1'b1; + end + end + 8'h02: begin // CAR + if (!is_cons_a) flag_err_type <= 1'b1; + end + 8'h03: begin // CDR + if (!is_cons_a) flag_err_type <= 1'b1; + end + 8'h05: begin // EQ + if (alu_eq) flag_zero <= 1'b1; + end + 8'h06: begin // ADD + if (!is_num_a || !is_num_b) begin + flag_err_type <= 1'b1; + end else begin + if (alu_sum[6]) flag_carry <= 1'b1; + if (alu_sum[5:0] == 6'd0) flag_zero <= 1'b1; + end + end + default: ; + endcase + end + + WRITEBACK: begin + case (opcode_reg) + 8'h01: begin // CONS + if (!flag_err_heap) + // Math trick: If bump_alloc wrapped to 0, + // 0 - 2 = 14 (1110 in 2's comp), which is the correct pointer. + result_reg <= {2'b11, 2'b00, bump_alloc - 4'd2}; + end + 8'h02: begin // CAR + if (!flag_err_type) result_reg <= heap[arg1_reg[3:0]]; + end + 8'h03: begin // CDR + if (!flag_err_type) result_reg <= heap[arg1_reg[3:0] + 1]; + end + 8'h04: begin // ATOM + result_reg <= is_cons_a ? 8'h00 : 8'h41; + end + 8'h05: begin // EQ + result_reg <= alu_eq ? 8'h41 : 8'h00; + end + 8'h06: begin // ADD + if (!flag_err_type) result_reg <= {2'b10, alu_sum[5:0]}; + end + default: ; + endcase + end + default: ; + endcase + end + end + + // ======================================================================== + // 4. OUTPUT LOGIC + // ======================================================================== + + // Status Register: [7:5]Rsrv, [4]Zero, [3]Carry, [2]Type, [1]Heap, [0]Busy + wire [7:0] current_status = {3'b000, flag_zero, flag_carry, flag_err_type, flag_err_heap, busy_bit}; + + always @(*) begin + if (cs && rw) begin + case (addr) + 3'h0: data_out = opcode_reg; + 3'h1: data_out = arg1_reg; + 3'h2: data_out = arg2_reg; + 3'h3: data_out = result_reg; + 3'h4: data_out = current_status; + default: data_out = 8'h00; + endcase + end else begin + data_out = 8'h00; // Drive 0 instead of Z to satisfy internal logic checks + end + end endmodule diff --git a/rtl/tb_lisp_coproc.sv b/rtl/tb_lisp_coproc.sv index 08e3627..4e7b28d 100644 --- a/rtl/tb_lisp_coproc.sv +++ b/rtl/tb_lisp_coproc.sv @@ -2,305 +2,322 @@ 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 (100MHz) - initial begin - clk = 0; - forever #5 clk = ~clk; - end - - // ======================================================================== - // 2. HELPER TASKS - // ======================================================================== - - task cpu_write(input [2:0] w_addr, input [7:0] w_data); - begin + // ======================================================================== + // 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 = 0; addr = w_addr; data_in = w_data; + cs = 1; rw = 1; addr = 3'h4; // Status @(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); - begin - // 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; - end while (status_val[0] === 1'b1); - - // Perform Read - @(posedge clk); - cs = 1; rw = 1; addr = r_addr; - @(posedge clk); - r_data = data_out; + status_val = 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_robust.vcd"); - $dumpvars(0, tb_lisp_coproc); - - // --- Initialize --- - rst = 1; cs = 0; rw = 0; addr = 0; data_in = 0; - #20 rst = 0; #20; - $display("\n=== STARTING ROBUST 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 + + // 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 -- cgit v1.2.3