commit 06b6b116934cc8341676f52f5f6040a038020c32
parent 36f4ec74c5d1247362bed901dbaf0a36970b4cd0
Author: vin <git@vineetk.net>
Date: Wed, 10 Dec 2025 16:35:42 -0500
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.
Diffstat:
6 files changed, 631 insertions(+), 510 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,3 +2,5 @@ rtl/lisp_coproc_sim
*.vcd
*~
#*
+runs/
+obj_dir/
diff --git a/Makefile b/Makefile
@@ -0,0 +1,65 @@
+# ==============================================================================
+# CONFIGURATION
+# ==============================================================================
+PROJECT_NAME = lisp_coproc
+CONFIG_FILE = config.json
+
+# Tool Paths
+LIBRELANE = librelane
+VERILATOR = verilator
+
+# PDK Paths (Standard Volare location)
+PDK_ROOT ?= $(HOME)/.ciel/ciel/sky130/versions/0fe599b2afb6708d281543108caf8310912f54af/
+PDK = sky130A
+LIB_VERILOG = $(PDK_ROOT)/$(PDK)/libs.ref/sky130_fd_sc_hd/verilog/sky130_fd_sc_hd.v
+PRIM_VERILOG = $(PDK_ROOT)/$(PDK)/libs.ref/sky130_fd_sc_hd/verilog/primitives.v
+
+# Simulation Flags
+VERILATOR_FLAGS = --binary -j 0 --timing --trace --top-module tb_$(PROJECT_NAME) \
+ -Wno-fatal -Wno-style -Wno-lint
+
+# ==============================================================================
+# TARGETS
+# ==============================================================================
+.PHONY: all rtl harden gls clean
+
+# 1. Default: Run the full pipeline (RTL -> GDS -> Check)
+all: rtl harden gls
+
+# 2. RTL Verification: Runs your Golden SystemVerilog Testbench
+rtl:
+ @echo "\n=== [1/3] Running RTL Verification ==="
+ $(VERILATOR) $(VERILATOR_FLAGS) \
+ -DGL_SIM=0 \
+ rtl/tb_$(PROJECT_NAME).sv rtl/$(PROJECT_NAME).sv
+ ./obj_dir/Vtb_$(PROJECT_NAME)
+ @echo ">>> RTL Verification Passed <<<"
+
+# 3. Hardening: Runs LibreLane (Synthesis, Place & Route)
+# Note: Uses --run-tag to create a predictable folder name for the GLS step
+harden:
+ @echo "\n=== [2/3] Running LibreLane Hardening ==="
+ $(LIBRELANE) --flow Classic \
+ --run-tag automated_run \
+ --overwrite \
+ $(CONFIG_FILE)
+ @echo ">>> Hardening Complete <<<"
+
+# 4. Gate-Level Simulation (GLS): Verifies the final GDSII netlist
+# Finds the netlist generated by the 'harden' step
+GL_NETLIST = runs/automated_run/final/nl/$(PROJECT_NAME).nl.v
+
+gls:
+ @echo "\n=== [3/3] Running Gate-Level Simulation (GLS) ==="
+ $(VERILATOR) $(VERILATOR_FLAGS) \
+ -DGL_SIM=1 -DFUNCTIONAL -DUNIT_DELAY=\#1 \
+ -I$(PDK_ROOT)/$(PDK)/libs.ref/sky130_fd_sc_hd/verilog \
+ rtl/tb_$(PROJECT_NAME).sv \
+ $(GL_NETLIST) \
+ $(LIB_VERILOG) $(PRIM_VERILOG)
+ ./obj_dir/Vtb_$(PROJECT_NAME)
+ @echo ">>> GLS Verification Passed <<<"
+
+# Clean up build artifacts
+clean:
+ rm -rf obj_dir runs/automated_run $(PROJECT_NAME).vcd
diff --git a/config.json b/config.json
@@ -0,0 +1,6 @@
+{
+ "DESIGN_NAME": "lisp_coproc",
+ "VERILOG_FILES": ["dir::rtl/lisp_coproc.sv"],
+ "CLOCK_PERIOD": 100,
+ "CLOCK_PORT": "clk"
+}
diff --git 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
@@ -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
diff --git a/vlsi/tb_chip_core.v b/vlsi/tb_chip_core.v
@@ -115,13 +115,13 @@ module tb_chip_core;
// ========================================================================
initial begin
- $dumpfile("chip_core_robust.vcd");
+ $dumpfile("chip_core.vcd");
$dumpvars(0, tb_chip_core);
// --- Initialize ---
rst = 1; cs = 0; rw = 0; addr = 0; data_in = 0;
#20 rst = 0; #20;
- $display("\n=== STARTING ROBUST VERIFICATION ===\n");
+ $display("\n=== STARTING VERIFICATION ===\n");
// --------------------------------------------------------
// SCENARIO 1: ALU Boundary & Overflow