commit 4dd69d2913152d80af58d7d68fd195c031180606
Author: vin <git@vineetk.net>
Date: Thu, 20 Nov 2025 11:01:48 -0500
initial golden rtl
Diffstat:
3 files changed, 443 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+rtl/lisp_coproc_sim
+*~
+#*
+\ No newline at end of file
diff --git a/rtl/lisp_coproc.sv b/rtl/lisp_coproc.sv
@@ -0,0 +1,268 @@
+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
+);
+
+ // FSM States (One-Hot Encoding)
+ 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;
+
+ // Internal Registers
+ reg [4:0] state, next_state;
+ reg [7:0] opcode_reg, arg1_reg, arg2_reg, result_reg, status_reg;
+ reg [7:0] heap [0:15]; // 16 entries x 8 bits
+
+ // FIX: Bump allocator must be 5 bits to hold the value '16' (Full)
+ // without wrapping around to 0.
+ reg [4:0] bump_alloc;
+
+ // Status Register Bits
+ wire busy = (state != IDLE);
+ reg err_heap_full, err_type, carry, zero;
+
+ // ALU Signals
+ reg [5:0] alu_a, alu_b;
+
+ // Temporary registers for operations
+ reg [7:0] temp_result;
+ reg [3:0] temp_ptr;
+
+ // FSM State Transition
+ always @(posedge clk or posedge rst) begin
+ if (rst) begin
+ state <= RESET;
+ end else begin
+ state <= next_state;
+ end
+ end
+
+ // FSM Combinational Logic
+ always @(*) begin
+ next_state = state;
+
+ case (state)
+ RESET: begin
+ next_state = IDLE;
+ end
+
+ IDLE: begin
+ if (cs && !rw && addr == 3'h0) begin // Writing to OPCODE triggers operation
+ next_state = DECODE;
+ end
+ end
+
+ DECODE: begin
+ next_state = EXECUTE;
+ end
+
+ EXECUTE: begin
+ next_state = WRITEBACK;
+ end
+
+ WRITEBACK: begin
+ next_state = IDLE;
+ end
+
+ default: begin
+ next_state = IDLE;
+ end
+ endcase
+ end
+
+ // Register File and Memory Interface
+ 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;
+ status_reg <= 8'h00;
+ bump_alloc <= 5'h00; // Reset 5-bit register
+ err_heap_full <= 1'b0;
+ err_type <= 1'b0;
+ carry <= 1'b0;
+ zero <= 1'b0;
+ end else begin
+ // Memory-mapped register 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; // Direct write to result
+ 3'h4: status_reg <= data_in; // Direct write to status
+ endcase
+ end
+
+ // FSM State-specific operations
+ case (state)
+ RESET: begin
+ // Clear heap on reset
+ integer i;
+ for (i = 0; i < 16; i = i + 1) begin
+ heap[i] <= 8'h00;
+ end
+ bump_alloc <= 5'h00;
+ opcode_reg <= 8'h00;
+ arg1_reg <= 8'h00;
+ arg2_reg <= 8'h00;
+ result_reg <= 8'h00;
+ status_reg <= 8'h00;
+ end
+
+ IDLE: begin
+ // Only clear flags when a NEW operation starts.
+ if (cs && !rw && addr == 3'h0) begin
+ err_heap_full <= 1'b0;
+ err_type <= 1'b0;
+ carry <= 1'b0;
+ zero <= 1'b0;
+ end
+ end
+
+ EXECUTE: begin
+ // Assign ALU inputs for ADD operation using BLOCKING assignment
+ alu_a = arg1_reg[5:0];
+ alu_b = arg2_reg[5:0];
+
+ case (opcode_reg)
+ // CONS operation
+ 8'h01: begin
+ // 5-bit arithmetic: 16 + 2 = 18. 18 > 16 is TRUE.
+ if (bump_alloc + 2 > 16) begin
+ err_heap_full <= 1'b1;
+ end else begin
+ // Store ARG1 and ARG2 in heap
+ heap[bump_alloc[3:0]] <= arg1_reg;
+ heap[bump_alloc[3:0] + 1] <= arg2_reg;
+ // Return CONS tag with pointer as value
+ temp_ptr <= bump_alloc[3:0];
+ bump_alloc <= bump_alloc + 2;
+ end
+ end
+
+ // CAR operation
+ 8'h02: begin
+ if (arg1_reg[7:6] != 2'b11) begin // Not a CONS
+ err_type <= 1'b1;
+ end else begin
+ temp_result <= heap[arg1_reg[3:0]];
+ end
+ end
+
+ // CDR operation
+ 8'h03: begin
+ if (arg1_reg[7:6] != 2'b11) begin // Not a CONS
+ err_type <= 1'b1;
+ end else begin
+ temp_result <= heap[arg1_reg[3:0] + 1];
+ end
+ end
+
+ // ATOM operation
+ 8'h04: begin
+ if (arg1_reg[7:6] == 2'b11) begin // Is a CONS
+ temp_result <= 8'h00; // NIL
+ end else begin
+ temp_result <= 8'h41; // 'T' (01_000001)
+ end
+ end
+
+ // EQ operation
+ 8'h05: begin
+ if (arg1_reg == arg2_reg) begin
+ temp_result <= 8'h41; // 'T' (01_000001)
+ zero <= 1'b1;
+ end else begin
+ temp_result <= 8'h00; // NIL
+ end
+ end
+
+ // ADD operation
+ 8'h06: begin
+ if (arg1_reg[7:6] != 2'b10 || arg2_reg[7:6] != 2'b10) begin // Not both NUMBERs
+ err_type <= 1'b1;
+ end else begin
+ if ((alu_a + alu_b) > 6'd63) begin
+ carry <= 1'b1;
+ end
+ if ((alu_a + alu_b) == 6'd0) begin
+ zero <= 1'b1;
+ end
+ temp_result <= {2'b10, alu_a + alu_b}; // NUMBER tag with sum
+ end
+ end
+ endcase
+ end
+
+ WRITEBACK: begin
+ // Write the result based on the operation
+ case (opcode_reg)
+ 8'h01: begin // CONS
+ if (!err_heap_full) begin
+ // Tag [7:6] must be set correctly.
+ result_reg <= {2'b11, 2'b00, temp_ptr};
+ end
+ end
+
+ 8'h02: begin // CAR
+ if (!err_type) begin
+ result_reg <= temp_result;
+ end
+ end
+
+ 8'h03: begin // CDR
+ if (!err_type) begin
+ result_reg <= temp_result;
+ end
+ end
+
+ 8'h04: begin // ATOM
+ result_reg <= temp_result;
+ end
+
+ 8'h05: begin // EQ
+ result_reg <= temp_result;
+ end
+
+ 8'h06: begin // ADD
+ if (!err_type) begin
+ result_reg <= temp_result;
+ end
+ end
+ endcase
+ end
+ endcase
+ end
+ end
+
+ // Update status register
+ always @(*) begin
+ status_reg = {3'b000, zero, carry, err_type, err_heap_full, busy};
+ end
+
+ // Output Logic
+ 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 = status_reg;
+ default: data_out = 8'h00;
+ endcase
+ end else begin
+ data_out = 8'hZZ; // High impedance when not reading
+ end
+ end
+
+endmodule
diff --git a/rtl/tb_lisp_coproc.sv b/rtl/tb_lisp_coproc.sv
@@ -0,0 +1,171 @@
+`timescale 1ns/1ps
+
+module tb_lisp_coproc;
+
+ // Testbench signals
+ reg clk;
+ reg rst;
+ reg cs;
+ reg rw; // 0=Write, 1=Read
+ reg [2:0] addr;
+ reg [7:0] data_in;
+ wire [7:0] data_out;
+
+ // Test variables
+ reg [7:0] read_data;
+ integer i;
+
+ // Instantiate the DUT
+ lisp_coproc dut (
+ .clk(clk),
+ .rst(rst),
+ .cs(cs),
+ .rw(rw),
+ .addr(addr),
+ .data_in(data_in),
+ .data_out(data_out)
+ );
+
+ // Clock generation
+ initial begin
+ clk = 0;
+ forever #5 clk = ~clk;
+ end
+
+ // CPU Write Task
+ task cpu_write;
+ input [2:0] addr_in;
+ input [7:0] data_in_in;
+ begin
+ @(posedge clk);
+ cs = 1'b1;
+ rw = 1'b0; // Write
+ addr = addr_in;
+ data_in = data_in_in;
+ @(posedge clk);
+ cs = 1'b0;
+ end
+ endtask
+
+ // CPU Read Task
+ task cpu_read;
+ input [2:0] addr_in;
+ output [7:0] data_out_out;
+ reg [7:0] status;
+ begin
+ // Poll until not busy
+ do begin
+ @(posedge clk);
+ cs = 1'b1;
+ rw = 1'b1; // Read
+ addr = 3'h4; // Status register
+ @(posedge clk);
+ status = data_out;
+ cs = 1'b0;
+ end while (status[0]); // Check BUSY bit
+
+ // Read the requested address
+ @(posedge clk);
+ cs = 1'b1;
+ rw = 1'b1; // Read
+ addr = addr_in;
+ @(posedge clk);
+ data_out_out = data_out;
+ cs = 1'b0;
+ end
+ endtask
+
+ // Test Sequence
+ initial begin
+ // Initialize signals
+ rst = 1'b1;
+ cs = 1'b0;
+ rw = 1'b0;
+ addr = 3'h0;
+ data_in = 8'h00;
+
+ // Apply reset
+ #20;
+ rst = 1'b0;
+ #20;
+
+ // Test 1: Reset Test
+ $display("Test 1: Reset Test");
+ cpu_read(3'h4, read_data); // Read STATUS
+ $display("STATUS after reset: 0x%02h (expected: 0x00)", read_data);
+ if (read_data != 8'h00) $display("ERROR: Reset test failed");
+
+ // Test 2: ADD Test
+ $display("\nTest 2: ADD Test");
+ cpu_write(3'h1, 8'h85); // ARG1 = NUMBER(5)
+ cpu_write(3'h2, 8'h86); // ARG2 = NUMBER(6)
+ cpu_write(3'h0, 8'h06); // OPCODE = ADD
+
+ cpu_read(3'h3, read_data); // Read RESULT
+ $display("ADD Result: 0x%02h (expected: 0x8B, NUMBER(11))", read_data);
+ if (read_data != 8'h8B) $display("ERROR: ADD test failed");
+
+ cpu_read(3'h4, read_data); // Read STATUS
+ $display("STATUS after ADD: 0x%02h (expected: 0x00, ZERO=0)", read_data);
+ if (read_data != 8'h00) $display("ERROR: ADD status test failed");
+
+ // Test 3: CONS Test
+ $display("\nTest 3: CONS Test");
+ cpu_write(3'h1, 8'h41); // ARG1 = ATOM('T')
+ cpu_write(3'h2, 8'h42); // ARG2 = ATOM('B')
+ cpu_write(3'h0, 8'h01); // OPCODE = CONS
+
+ cpu_read(3'h3, read_data); // Read RESULT
+ $display("CONS Result: 0x%02h (expected: 0xC0, CONS(0))", read_data);
+ if (read_data != 8'hC0) $display("ERROR: CONS test failed");
+
+ // Test CAR and CDR
+ cpu_write(3'h1, read_data); // Use the CONS result as ARG1
+ cpu_write(3'h0, 8'h02); // OPCODE = CAR
+
+ cpu_read(3'h3, read_data); // Read RESULT
+ $display("CAR Result: 0x%02h (expected: 0x41, ATOM('T'))", read_data);
+ if (read_data != 8'h41) $display("ERROR: CAR test failed");
+
+ cpu_write(3'h1, 8'hC0); // ARG1 = CONS(0)
+ cpu_write(3'h0, 8'h03); // OPCODE = CDR
+
+ cpu_read(3'h3, read_data); // Read RESULT
+ $display("CDR Result: 0x%02h (expected: 0x42, ATOM('B'))", read_data);
+ if (read_data != 8'h42) $display("ERROR: CDR test failed");
+
+ // Test 4: Error Test (CAR on a Number)
+ $display("\nTest 4: Error Test (CAR on a Number)");
+ cpu_write(3'h1, 8'h85); // ARG1 = NUMBER(5)
+ cpu_write(3'h0, 8'h02); // OPCODE = CAR
+
+ cpu_read(3'h4, read_data); // Read STATUS
+ $display("STATUS after CAR on Number: 0x%02h (expected: 0x04, ERR_TYPE=1)", read_data);
+ if (read_data != 8'h04) $display("ERROR: Error test failed");
+
+ // Test 5: Heap Full Test
+ $display("\nTest 5: Heap Full Test");
+ // Fill the heap with CONS operations
+ // Since bump_alloc is now 2, we have used 2 cells.
+ // 16 cells total. 14 left. 7 more CONS possible.
+ for (i = 0; i < 7; i = i + 1) begin
+ cpu_write(3'h1, 8'h41);
+ cpu_write(3'h2, 8'h42);
+ cpu_write(3'h0, 8'h01);
+ cpu_read(3'h3, read_data);
+ end
+
+ // One more CONS should cause heap full error
+ cpu_write(3'h1, 8'h41);
+ cpu_write(3'h2, 8'h42);
+ cpu_write(3'h0, 8'h01);
+
+ cpu_read(3'h4, read_data); // Read STATUS
+ $display("STATUS after Heap Full: 0x%02h (expected: 0x02, ERR_HEAP_FULL=1)", read_data);
+ if (read_data != 8'h02) $display("ERROR: Heap Full test failed");
+
+ $display("\nAll tests completed.");
+ $finish;
+ end
+
+endmodule