diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | rtl/lisp_coproc.sv | 268 | ||||
| -rw-r--r-- | rtl/tb_lisp_coproc.sv | 171 |
3 files changed, 442 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..472c55f --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | rtl/lisp_coproc_sim | ||
| 2 | *~ | ||
| 3 | #* \ No newline at end of file | ||
diff --git a/rtl/lisp_coproc.sv b/rtl/lisp_coproc.sv new file mode 100644 index 0000000..bd3057a --- /dev/null +++ b/rtl/lisp_coproc.sv | |||
| @@ -0,0 +1,268 @@ | |||
| 1 | module lisp_coproc ( | ||
| 2 | input wire clk, | ||
| 3 | input wire rst, | ||
| 4 | input wire cs, | ||
| 5 | input wire rw, // 0=Write, 1=Read | ||
| 6 | input wire [2:0] addr, | ||
| 7 | input wire [7:0] data_in, | ||
| 8 | output reg [7:0] data_out | ||
| 9 | ); | ||
| 10 | |||
| 11 | // FSM States (One-Hot Encoding) | ||
| 12 | parameter [4:0] RESET = 5'b00001; | ||
| 13 | parameter [4:0] IDLE = 5'b00010; | ||
| 14 | parameter [4:0] DECODE = 5'b00100; | ||
| 15 | parameter [4:0] EXECUTE = 5'b01000; | ||
| 16 | parameter [4:0] WRITEBACK= 5'b10000; | ||
| 17 | |||
| 18 | // Internal Registers | ||
| 19 | reg [4:0] state, next_state; | ||
| 20 | reg [7:0] opcode_reg, arg1_reg, arg2_reg, result_reg, status_reg; | ||
| 21 | reg [7:0] heap [0:15]; // 16 entries x 8 bits | ||
| 22 | |||
| 23 | // FIX: Bump allocator must be 5 bits to hold the value '16' (Full) | ||
| 24 | // without wrapping around to 0. | ||
| 25 | reg [4:0] bump_alloc; | ||
| 26 | |||
| 27 | // Status Register Bits | ||
| 28 | wire busy = (state != IDLE); | ||
| 29 | reg err_heap_full, err_type, carry, zero; | ||
| 30 | |||
| 31 | // ALU Signals | ||
| 32 | reg [5:0] alu_a, alu_b; | ||
| 33 | |||
| 34 | // Temporary registers for operations | ||
| 35 | reg [7:0] temp_result; | ||
| 36 | reg [3:0] temp_ptr; | ||
| 37 | |||
| 38 | // FSM State Transition | ||
| 39 | always @(posedge clk or posedge rst) begin | ||
| 40 | if (rst) begin | ||
| 41 | state <= RESET; | ||
| 42 | end else begin | ||
| 43 | state <= next_state; | ||
| 44 | end | ||
| 45 | end | ||
| 46 | |||
| 47 | // FSM Combinational Logic | ||
| 48 | always @(*) begin | ||
| 49 | next_state = state; | ||
| 50 | |||
| 51 | case (state) | ||
| 52 | RESET: begin | ||
| 53 | next_state = IDLE; | ||
| 54 | end | ||
| 55 | |||
| 56 | IDLE: begin | ||
| 57 | if (cs && !rw && addr == 3'h0) begin // Writing to OPCODE triggers operation | ||
| 58 | next_state = DECODE; | ||
| 59 | end | ||
| 60 | end | ||
| 61 | |||
| 62 | DECODE: begin | ||
| 63 | next_state = EXECUTE; | ||
| 64 | end | ||
| 65 | |||
| 66 | EXECUTE: begin | ||
| 67 | next_state = WRITEBACK; | ||
| 68 | end | ||
| 69 | |||
| 70 | WRITEBACK: begin | ||
| 71 | next_state = IDLE; | ||
| 72 | end | ||
| 73 | |||
| 74 | default: begin | ||
| 75 | next_state = IDLE; | ||
| 76 | end | ||
| 77 | endcase | ||
| 78 | end | ||
| 79 | |||
| 80 | // Register File and Memory Interface | ||
| 81 | always @(posedge clk or posedge rst) begin | ||
| 82 | if (rst) begin | ||
| 83 | opcode_reg <= 8'h00; | ||
| 84 | arg1_reg <= 8'h00; | ||
| 85 | arg2_reg <= 8'h00; | ||
| 86 | result_reg <= 8'h00; | ||
| 87 | status_reg <= 8'h00; | ||
| 88 | bump_alloc <= 5'h00; // Reset 5-bit register | ||
| 89 | err_heap_full <= 1'b0; | ||
| 90 | err_type <= 1'b0; | ||
| 91 | carry <= 1'b0; | ||
| 92 | zero <= 1'b0; | ||
| 93 | end else begin | ||
| 94 | // Memory-mapped register writes | ||
| 95 | if (cs && !rw) begin | ||
| 96 | case (addr) | ||
| 97 | 3'h0: opcode_reg <= data_in; | ||
| 98 | 3'h1: arg1_reg <= data_in; | ||
| 99 | 3'h2: arg2_reg <= data_in; | ||
| 100 | 3'h3: result_reg <= data_in; // Direct write to result | ||
| 101 | 3'h4: status_reg <= data_in; // Direct write to status | ||
| 102 | endcase | ||
| 103 | end | ||
| 104 | |||
| 105 | // FSM State-specific operations | ||
| 106 | case (state) | ||
| 107 | RESET: begin | ||
| 108 | // Clear heap on reset | ||
| 109 | integer i; | ||
| 110 | for (i = 0; i < 16; i = i + 1) begin | ||
| 111 | heap[i] <= 8'h00; | ||
| 112 | end | ||
| 113 | bump_alloc <= 5'h00; | ||
| 114 | opcode_reg <= 8'h00; | ||
| 115 | arg1_reg <= 8'h00; | ||
| 116 | arg2_reg <= 8'h00; | ||
| 117 | result_reg <= 8'h00; | ||
| 118 | status_reg <= 8'h00; | ||
| 119 | end | ||
| 120 | |||
| 121 | IDLE: begin | ||
| 122 | // Only clear flags when a NEW operation starts. | ||
| 123 | if (cs && !rw && addr == 3'h0) begin | ||
| 124 | err_heap_full <= 1'b0; | ||
| 125 | err_type <= 1'b0; | ||
| 126 | carry <= 1'b0; | ||
| 127 | zero <= 1'b0; | ||
| 128 | end | ||
| 129 | end | ||
| 130 | |||
| 131 | EXECUTE: begin | ||
| 132 | // Assign ALU inputs for ADD operation using BLOCKING assignment | ||
| 133 | alu_a = arg1_reg[5:0]; | ||
| 134 | alu_b = arg2_reg[5:0]; | ||
| 135 | |||
| 136 | case (opcode_reg) | ||
| 137 | // CONS operation | ||
| 138 | 8'h01: begin | ||
| 139 | // 5-bit arithmetic: 16 + 2 = 18. 18 > 16 is TRUE. | ||
| 140 | if (bump_alloc + 2 > 16) begin | ||
| 141 | err_heap_full <= 1'b1; | ||
| 142 | end else begin | ||
| 143 | // Store ARG1 and ARG2 in heap | ||
| 144 | heap[bump_alloc[3:0]] <= arg1_reg; | ||
| 145 | heap[bump_alloc[3:0] + 1] <= arg2_reg; | ||
| 146 | // Return CONS tag with pointer as value | ||
| 147 | temp_ptr <= bump_alloc[3:0]; | ||
| 148 | bump_alloc <= bump_alloc + 2; | ||
| 149 | end | ||
| 150 | end | ||
| 151 | |||
| 152 | // CAR operation | ||
| 153 | 8'h02: begin | ||
| 154 | if (arg1_reg[7:6] != 2'b11) begin // Not a CONS | ||
| 155 | err_type <= 1'b1; | ||
| 156 | end else begin | ||
| 157 | temp_result <= heap[arg1_reg[3:0]]; | ||
| 158 | end | ||
| 159 | end | ||
| 160 | |||
| 161 | // CDR operation | ||
| 162 | 8'h03: begin | ||
| 163 | if (arg1_reg[7:6] != 2'b11) begin // Not a CONS | ||
| 164 | err_type <= 1'b1; | ||
| 165 | end else begin | ||
| 166 | temp_result <= heap[arg1_reg[3:0] + 1]; | ||
| 167 | end | ||
| 168 | end | ||
| 169 | |||
| 170 | // ATOM operation | ||
| 171 | 8'h04: begin | ||
| 172 | if (arg1_reg[7:6] == 2'b11) begin // Is a CONS | ||
| 173 | temp_result <= 8'h00; // NIL | ||
| 174 | end else begin | ||
| 175 | temp_result <= 8'h41; // 'T' (01_000001) | ||
| 176 | end | ||
| 177 | end | ||
| 178 | |||
| 179 | // EQ operation | ||
| 180 | 8'h05: begin | ||
| 181 | if (arg1_reg == arg2_reg) begin | ||
| 182 | temp_result <= 8'h41; // 'T' (01_000001) | ||
| 183 | zero <= 1'b1; | ||
| 184 | end else begin | ||
| 185 | temp_result <= 8'h00; // NIL | ||
| 186 | end | ||
| 187 | end | ||
| 188 | |||
| 189 | // ADD operation | ||
| 190 | 8'h06: begin | ||
| 191 | if (arg1_reg[7:6] != 2'b10 || arg2_reg[7:6] != 2'b10) begin // Not both NUMBERs | ||
| 192 | err_type <= 1'b1; | ||
| 193 | end else begin | ||
| 194 | if ((alu_a + alu_b) > 6'd63) begin | ||
| 195 | carry <= 1'b1; | ||
| 196 | end | ||
| 197 | if ((alu_a + alu_b) == 6'd0) begin | ||
| 198 | zero <= 1'b1; | ||
| 199 | end | ||
| 200 | temp_result <= {2'b10, alu_a + alu_b}; // NUMBER tag with sum | ||
| 201 | end | ||
| 202 | end | ||
| 203 | endcase | ||
| 204 | end | ||
| 205 | |||
| 206 | WRITEBACK: begin | ||
| 207 | // Write the result based on the operation | ||
| 208 | case (opcode_reg) | ||
| 209 | 8'h01: begin // CONS | ||
| 210 | if (!err_heap_full) begin | ||
| 211 | // Tag [7:6] must be set correctly. | ||
| 212 | result_reg <= {2'b11, 2'b00, temp_ptr}; | ||
| 213 | end | ||
| 214 | end | ||
| 215 | |||
| 216 | 8'h02: begin // CAR | ||
| 217 | if (!err_type) begin | ||
| 218 | result_reg <= temp_result; | ||
| 219 | end | ||
| 220 | end | ||
| 221 | |||
| 222 | 8'h03: begin // CDR | ||
| 223 | if (!err_type) begin | ||
| 224 | result_reg <= temp_result; | ||
| 225 | end | ||
| 226 | end | ||
| 227 | |||
| 228 | 8'h04: begin // ATOM | ||
| 229 | result_reg <= temp_result; | ||
| 230 | end | ||
| 231 | |||
| 232 | 8'h05: begin // EQ | ||
| 233 | result_reg <= temp_result; | ||
| 234 | end | ||
| 235 | |||
| 236 | 8'h06: begin // ADD | ||
| 237 | if (!err_type) begin | ||
| 238 | result_reg <= temp_result; | ||
| 239 | end | ||
| 240 | end | ||
| 241 | endcase | ||
| 242 | end | ||
| 243 | endcase | ||
| 244 | end | ||
| 245 | end | ||
| 246 | |||
| 247 | // Update status register | ||
| 248 | always @(*) begin | ||
| 249 | status_reg = {3'b000, zero, carry, err_type, err_heap_full, busy}; | ||
| 250 | end | ||
| 251 | |||
| 252 | // Output Logic | ||
| 253 | always @(*) begin | ||
| 254 | if (cs && rw) begin | ||
| 255 | case (addr) | ||
| 256 | 3'h0: data_out = opcode_reg; | ||
| 257 | 3'h1: data_out = arg1_reg; | ||
| 258 | 3'h2: data_out = arg2_reg; | ||
| 259 | 3'h3: data_out = result_reg; | ||
| 260 | 3'h4: data_out = status_reg; | ||
| 261 | default: data_out = 8'h00; | ||
| 262 | endcase | ||
| 263 | end else begin | ||
| 264 | data_out = 8'hZZ; // High impedance when not reading | ||
| 265 | end | ||
| 266 | end | ||
| 267 | |||
| 268 | endmodule | ||
diff --git a/rtl/tb_lisp_coproc.sv b/rtl/tb_lisp_coproc.sv new file mode 100644 index 0000000..6c39aa4 --- /dev/null +++ b/rtl/tb_lisp_coproc.sv | |||
| @@ -0,0 +1,171 @@ | |||
| 1 | `timescale 1ns/1ps | ||
| 2 | |||
| 3 | module tb_lisp_coproc; | ||
| 4 | |||
| 5 | // Testbench signals | ||
| 6 | reg clk; | ||
| 7 | reg rst; | ||
| 8 | reg cs; | ||
| 9 | reg rw; // 0=Write, 1=Read | ||
| 10 | reg [2:0] addr; | ||
| 11 | reg [7:0] data_in; | ||
| 12 | wire [7:0] data_out; | ||
| 13 | |||
| 14 | // Test variables | ||
| 15 | reg [7:0] read_data; | ||
| 16 | integer i; | ||
| 17 | |||
| 18 | // Instantiate the DUT | ||
| 19 | lisp_coproc dut ( | ||
| 20 | .clk(clk), | ||
| 21 | .rst(rst), | ||
| 22 | .cs(cs), | ||
| 23 | .rw(rw), | ||
| 24 | .addr(addr), | ||
| 25 | .data_in(data_in), | ||
| 26 | .data_out(data_out) | ||
| 27 | ); | ||
| 28 | |||
| 29 | // Clock generation | ||
| 30 | initial begin | ||
| 31 | clk = 0; | ||
| 32 | forever #5 clk = ~clk; | ||
| 33 | end | ||
| 34 | |||
| 35 | // CPU Write Task | ||
| 36 | task cpu_write; | ||
| 37 | input [2:0] addr_in; | ||
| 38 | input [7:0] data_in_in; | ||
| 39 | begin | ||
| 40 | @(posedge clk); | ||
| 41 | cs = 1'b1; | ||
| 42 | rw = 1'b0; // Write | ||
| 43 | addr = addr_in; | ||
| 44 | data_in = data_in_in; | ||
| 45 | @(posedge clk); | ||
| 46 | cs = 1'b0; | ||
| 47 | end | ||
| 48 | endtask | ||
| 49 | |||
| 50 | // CPU Read Task | ||
| 51 | task cpu_read; | ||
| 52 | input [2:0] addr_in; | ||
| 53 | output [7:0] data_out_out; | ||
| 54 | reg [7:0] status; | ||
| 55 | begin | ||
| 56 | // Poll until not busy | ||
| 57 | do begin | ||
| 58 | @(posedge clk); | ||
| 59 | cs = 1'b1; | ||
| 60 | rw = 1'b1; // Read | ||
| 61 | addr = 3'h4; // Status register | ||
| 62 | @(posedge clk); | ||
| 63 | status = data_out; | ||
| 64 | cs = 1'b0; | ||
| 65 | end while (status[0]); // Check BUSY bit | ||
| 66 | |||
| 67 | // Read the requested address | ||
| 68 | @(posedge clk); | ||
| 69 | cs = 1'b1; | ||
| 70 | rw = 1'b1; // Read | ||
| 71 | addr = addr_in; | ||
| 72 | @(posedge clk); | ||
| 73 | data_out_out = data_out; | ||
| 74 | cs = 1'b0; | ||
| 75 | end | ||
| 76 | endtask | ||
| 77 | |||
| 78 | // Test Sequence | ||
| 79 | initial begin | ||
| 80 | // Initialize signals | ||
| 81 | rst = 1'b1; | ||
| 82 | cs = 1'b0; | ||
| 83 | rw = 1'b0; | ||
| 84 | addr = 3'h0; | ||
| 85 | data_in = 8'h00; | ||
| 86 | |||
| 87 | // Apply reset | ||
| 88 | #20; | ||
| 89 | rst = 1'b0; | ||
| 90 | #20; | ||
| 91 | |||
| 92 | // Test 1: Reset Test | ||
| 93 | $display("Test 1: Reset Test"); | ||
| 94 | cpu_read(3'h4, read_data); // Read STATUS | ||
| 95 | $display("STATUS after reset: 0x%02h (expected: 0x00)", read_data); | ||
| 96 | if (read_data != 8'h00) $display("ERROR: Reset test failed"); | ||
| 97 | |||
| 98 | // Test 2: ADD Test | ||
| 99 | $display("\nTest 2: ADD Test"); | ||
| 100 | cpu_write(3'h1, 8'h85); // ARG1 = NUMBER(5) | ||
| 101 | cpu_write(3'h2, 8'h86); // ARG2 = NUMBER(6) | ||
| 102 | cpu_write(3'h0, 8'h06); // OPCODE = ADD | ||
| 103 | |||
| 104 | cpu_read(3'h3, read_data); // Read RESULT | ||
| 105 | $display("ADD Result: 0x%02h (expected: 0x8B, NUMBER(11))", read_data); | ||
| 106 | if (read_data != 8'h8B) $display("ERROR: ADD test failed"); | ||
| 107 | |||
| 108 | cpu_read(3'h4, read_data); // Read STATUS | ||
| 109 | $display("STATUS after ADD: 0x%02h (expected: 0x00, ZERO=0)", read_data); | ||
| 110 | if (read_data != 8'h00) $display("ERROR: ADD status test failed"); | ||
| 111 | |||
| 112 | // Test 3: CONS Test | ||
| 113 | $display("\nTest 3: CONS Test"); | ||
| 114 | cpu_write(3'h1, 8'h41); // ARG1 = ATOM('T') | ||
| 115 | cpu_write(3'h2, 8'h42); // ARG2 = ATOM('B') | ||
| 116 | cpu_write(3'h0, 8'h01); // OPCODE = CONS | ||
| 117 | |||
| 118 | cpu_read(3'h3, read_data); // Read RESULT | ||
| 119 | $display("CONS Result: 0x%02h (expected: 0xC0, CONS(0))", read_data); | ||
| 120 | if (read_data != 8'hC0) $display("ERROR: CONS test failed"); | ||
| 121 | |||
| 122 | // Test CAR and CDR | ||
| 123 | cpu_write(3'h1, read_data); // Use the CONS result as ARG1 | ||
| 124 | cpu_write(3'h0, 8'h02); // OPCODE = CAR | ||
| 125 | |||
| 126 | cpu_read(3'h3, read_data); // Read RESULT | ||
| 127 | $display("CAR Result: 0x%02h (expected: 0x41, ATOM('T'))", read_data); | ||
| 128 | if (read_data != 8'h41) $display("ERROR: CAR test failed"); | ||
| 129 | |||
| 130 | cpu_write(3'h1, 8'hC0); // ARG1 = CONS(0) | ||
| 131 | cpu_write(3'h0, 8'h03); // OPCODE = CDR | ||
| 132 | |||
| 133 | cpu_read(3'h3, read_data); // Read RESULT | ||
| 134 | $display("CDR Result: 0x%02h (expected: 0x42, ATOM('B'))", read_data); | ||
| 135 | if (read_data != 8'h42) $display("ERROR: CDR test failed"); | ||
| 136 | |||
| 137 | // Test 4: Error Test (CAR on a Number) | ||
| 138 | $display("\nTest 4: Error Test (CAR on a Number)"); | ||
| 139 | cpu_write(3'h1, 8'h85); // ARG1 = NUMBER(5) | ||
| 140 | cpu_write(3'h0, 8'h02); // OPCODE = CAR | ||
| 141 | |||
| 142 | cpu_read(3'h4, read_data); // Read STATUS | ||
| 143 | $display("STATUS after CAR on Number: 0x%02h (expected: 0x04, ERR_TYPE=1)", read_data); | ||
| 144 | if (read_data != 8'h04) $display("ERROR: Error test failed"); | ||
| 145 | |||
| 146 | // Test 5: Heap Full Test | ||
| 147 | $display("\nTest 5: Heap Full Test"); | ||
| 148 | // Fill the heap with CONS operations | ||
| 149 | // Since bump_alloc is now 2, we have used 2 cells. | ||
| 150 | // 16 cells total. 14 left. 7 more CONS possible. | ||
| 151 | for (i = 0; i < 7; i = i + 1) begin | ||
| 152 | cpu_write(3'h1, 8'h41); | ||
| 153 | cpu_write(3'h2, 8'h42); | ||
| 154 | cpu_write(3'h0, 8'h01); | ||
| 155 | cpu_read(3'h3, read_data); | ||
| 156 | end | ||
| 157 | |||
| 158 | // One more CONS should cause heap full error | ||
| 159 | cpu_write(3'h1, 8'h41); | ||
| 160 | cpu_write(3'h2, 8'h42); | ||
| 161 | cpu_write(3'h0, 8'h01); | ||
| 162 | |||
| 163 | cpu_read(3'h4, read_data); // Read STATUS | ||
| 164 | $display("STATUS after Heap Full: 0x%02h (expected: 0x02, ERR_HEAP_FULL=1)", read_data); | ||
| 165 | if (read_data != 8'h02) $display("ERROR: Heap Full test failed"); | ||
| 166 | |||
| 167 | $display("\nAll tests completed."); | ||
| 168 | $finish; | ||
| 169 | end | ||
| 170 | |||
| 171 | endmodule | ||
