`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 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] <= 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 // ======================================================================== 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 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'hZZ; end end endmodule