# ChipFoundry Contest Submission Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Integrate the LDPC decoder into the Caravel chip_ignite template, verify with cocotb, harden with OpenLane, and submit proposal by March 25. **Architecture:** Wire existing `ldpc_decoder_top` into `user_project_wrapper.v`, inverting reset polarity and connecting lower 8 address bits. PicoRV32 firmware drives test vectors over Wishbone. cocotb monitors UART/GPIO for pass/fail. **Tech Stack:** SystemVerilog RTL, OpenLane 2.0 (LibreLane), cocotb + caravel_cocotb, PicoRV32 bare-metal C, Python 3 (test vector generation), KiCad (PCBA, later weeks) --- ## Week 1: RTL Integration & Standalone Verification ### Task 1: Copy RTL into chip_ignite and adapt for Caravel **Files:** - Copy: `rtl/ldpc_decoder_core.sv` -> `chip_ignite/verilog/rtl/ldpc_decoder_core.sv` - Copy: `rtl/wishbone_interface.sv` -> `chip_ignite/verilog/rtl/wishbone_interface.sv` - Create: `chip_ignite/verilog/rtl/ldpc_decoder_top.sv` (adapted from `rtl/ldpc_decoder_top.sv`) - Modify: `chip_ignite/verilog/rtl/user_project_wrapper.v` **Step 1: Copy unmodified RTL files** ```bash cp rtl/ldpc_decoder_core.sv chip_ignite/verilog/rtl/ cp rtl/wishbone_interface.sv chip_ignite/verilog/rtl/ ``` **Step 2: Create Caravel-adapted ldpc_decoder_top.sv** The original `ldpc_decoder_top.sv` uses `rst_n` (active-low) and 8-bit address. Caravel provides `wb_rst_i` (active-high) and 32-bit address. Create an adapted version: ```systemverilog // LDPC Decoder Top - Caravel-adapted wrapper // Handles reset polarity inversion and address bus width adaptation // // Changes from standalone version: // - rst_n derived from wb_rst_i (active-high -> active-low) // - wb_adr_i connected from lower 8 bits of 32-bit Caravel address // - USE_POWER_PINS support for Caravel integration module ldpc_decoder_top #( parameter N_BASE = 8, parameter M_BASE = 7, parameter Z = 32, parameter N = N_BASE * Z, // 256 parameter K = Z, // 32 parameter M = M_BASE * Z, // 224 parameter Q = 6, parameter MAX_ITER = 30, parameter DC = 8, parameter DV_MAX = 7 )( `ifdef USE_POWER_PINS inout vccd1, inout vssd1, `endif input logic clk, input logic rst_n, // Wishbone B4 slave interface input logic wb_cyc_i, input logic wb_stb_i, input logic wb_we_i, input logic [3:0] wb_sel_i, // byte selects (unused, for Caravel compat) input logic [31:0] wb_adr_i, // full 32-bit address from Caravel input logic [31:0] wb_dat_i, output logic [31:0] wb_dat_o, output logic wb_ack_o, // Interrupt output logic irq_o ); // Internal signals logic ctrl_start; logic ctrl_early_term; logic [4:0] ctrl_max_iter; logic stat_busy; logic stat_converged; logic [4:0] stat_iter_used; logic signed [Q-1:0] llr_input [N]; logic [K-1:0] decoded_bits; logic [7:0] syndrome_weight; wishbone_interface #( .N(N), .K(K), .Q(Q) ) u_wb ( .clk (clk), .rst_n (rst_n), .wb_cyc_i (wb_cyc_i), .wb_stb_i (wb_stb_i), .wb_we_i (wb_we_i), .wb_adr_i (wb_adr_i[7:0]), // lower 8 bits only .wb_dat_i (wb_dat_i), .wb_dat_o (wb_dat_o), .wb_ack_o (wb_ack_o), .ctrl_start (ctrl_start), .ctrl_early_term(ctrl_early_term), .ctrl_max_iter (ctrl_max_iter), .stat_busy (stat_busy), .stat_converged (stat_converged), .stat_iter_used (stat_iter_used), .llr_input (llr_input), .decoded_bits (decoded_bits), .syndrome_weight(syndrome_weight), .irq_o (irq_o) ); ldpc_decoder_core #( .N_BASE (N_BASE), .M_BASE (M_BASE), .Z (Z), .Q (Q), .MAX_ITER (MAX_ITER), .DC (DC), .DV_MAX (DV_MAX) ) u_core ( .clk (clk), .rst_n (rst_n), .start (ctrl_start), .early_term_en (ctrl_early_term), .max_iter (ctrl_max_iter), .llr_in (llr_input), .busy (stat_busy), .converged (stat_converged), .iter_used (stat_iter_used), .decoded_bits (decoded_bits), .syndrome_weight(syndrome_weight) ); endmodule ``` **Step 3: Modify user_project_wrapper.v** Replace the `user_proj_example` instantiation with `ldpc_decoder_top`: ```verilog /* In user_project_wrapper.v, replace the user_proj_example instantiation block with: */ ldpc_decoder_top mprj ( `ifdef USE_POWER_PINS .vccd1(vccd1), .vssd1(vssd1), `endif .clk (wb_clk_i), .rst_n (~wb_rst_i), // Caravel active-high -> LDPC active-low // Wishbone slave .wb_cyc_i (wbs_cyc_i), .wb_stb_i (wbs_stb_i), .wb_we_i (wbs_we_i), .wb_sel_i (wbs_sel_i), .wb_adr_i (wbs_adr_i), .wb_dat_i (wbs_dat_i), .wb_dat_o (wbs_dat_o), .wb_ack_o (wbs_ack_o), // Interrupt (active high, directly to Caravel IRQ[0]) .irq_o (user_irq[0]) ); // Tie off unused outputs assign la_data_out = 128'b0; assign io_out = {`MPRJ_IO_PADS{1'b0}}; assign io_oeb = {`MPRJ_IO_PADS{1'b1}}; // all inputs assign user_irq[2:1] = 2'b0; ``` **Step 4: Update RTL include file** Edit `chip_ignite/verilog/includes/includes.rtl.caravel_user_project`: ``` -v $(USER_PROJECT_VERILOG)/rtl/defines.v -v $(USER_PROJECT_VERILOG)/rtl/user_project_wrapper.v -v $(USER_PROJECT_VERILOG)/rtl/ldpc_decoder_top.sv -v $(USER_PROJECT_VERILOG)/rtl/ldpc_decoder_core.sv -v $(USER_PROJECT_VERILOG)/rtl/wishbone_interface.sv ``` Remove the `user_proj_example.v` line. **Step 5: Lint check** ```bash cd chip_ignite verilator --lint-only -Wall \ verilog/rtl/ldpc_decoder_top.sv \ verilog/rtl/ldpc_decoder_core.sv \ verilog/rtl/wishbone_interface.sv \ -DUSE_POWER_PINS \ --top-module ldpc_decoder_top ``` Expected: 0 errors, warnings acceptable (unused `wb_sel_i`). **Step 6: Commit** ```bash cd chip_ignite git add verilog/rtl/ldpc_decoder_top.sv \ verilog/rtl/ldpc_decoder_core.sv \ verilog/rtl/wishbone_interface.sv \ verilog/rtl/user_project_wrapper.v \ verilog/includes/includes.rtl.caravel_user_project git commit -m "feat: integrate LDPC decoder into Caravel wrapper" ``` --- ### Task 2: Generate test vectors from Python model **Files:** - Run: `model/ldpc_sim.py --gen-vectors` - Create: `model/gen_firmware_vectors.py` - Output: `data/test_vectors.json` - Output: `chip_ignite/verilog/dv/cocotb/ldpc_tests/test_data.py` **Step 1: Generate test vectors** ```bash cd /home/cah/r2d2/code/fpga/claude_project/ldpc_optical python3 model/ldpc_sim.py --gen-vectors --lam-s 2.0 --lam-b 0.1 --seed 42 ``` Expected: Creates `data/test_vectors.json` with 20 test vectors. **Step 2: Verify test vectors are usable** ```bash python3 -c " import json with open('data/test_vectors.json') as f: vecs = json.load(f) print(f'Vectors: {len(vecs)}') v = vecs[0] print(f'LLR count: {len(v[\"llr_quantized\"])}') print(f'Info bits: {len(v[\"info_bits\"])}') print(f'Converged: {v[\"converged\"]}') print(f'Iterations: {v[\"iterations\"]}') " ``` Expected: 20 vectors, 256 LLRs each, 32 info bits each. **Step 3: Create Python converter for cocotb test data** Create `model/gen_firmware_vectors.py`: ```python #!/usr/bin/env python3 """Convert test_vectors.json to Python test data module for cocotb tests and C header for PicoRV32 firmware.""" import json import sys import os def pack_llrs_to_words(llr_quantized, q_bits=6): """Pack 256 6-bit signed LLRs into 52 32-bit words (5 LLRs per word).""" words = [] for i in range(0, 256, 5): word = 0 for p in range(5): idx = i + p if idx < 256: # Convert signed to unsigned 6-bit val = llr_quantized[idx] & 0x3F word |= val << (p * q_bits) words.append(word) return words def gen_cocotb_test_data(vectors, output_path): """Generate Python module with test vector data for cocotb.""" with open(output_path, 'w') as f: f.write("# Auto-generated test vectors from ldpc_sim.py\n") f.write("# DO NOT EDIT - regenerate with: python3 model/gen_firmware_vectors.py\n\n") f.write("TEST_VECTORS = [\n") for i, v in enumerate(vectors): llr_words = pack_llrs_to_words(v['llr_quantized']) info_bits = v['info_bits'] # Pack 32 info bits into a single uint32 decoded_word = sum(b << j for j, b in enumerate(info_bits)) f.write(f" {{\n") f.write(f" 'index': {i},\n") f.write(f" 'llr_words': {llr_words},\n") f.write(f" 'decoded_word': 0x{decoded_word:08X},\n") f.write(f" 'converged': {v['converged']},\n") f.write(f" 'iterations': {v['iterations']},\n") f.write(f" 'syndrome_weight': {v['syndrome_weight']},\n") f.write(f" }},\n") f.write("]\n") def gen_c_header(vectors, output_path): """Generate C header with test vector data for PicoRV32 firmware.""" with open(output_path, 'w') as f: f.write("// Auto-generated test vectors from ldpc_sim.py\n") f.write("// DO NOT EDIT - regenerate with: python3 model/gen_firmware_vectors.py\n\n") f.write("#ifndef TEST_VECTORS_H\n#define TEST_VECTORS_H\n\n") f.write(f"#define NUM_TEST_VECTORS {len(vectors)}\n") f.write("#define LLR_WORDS_PER_VECTOR 52\n\n") for i, v in enumerate(vectors): llr_words = pack_llrs_to_words(v['llr_quantized']) info_bits = v['info_bits'] decoded_word = sum(b << j for j, b in enumerate(info_bits)) f.write(f"// Vector {i}: converged={v['converged']}, " f"iters={v['iterations']}, syndrome={v['syndrome_weight']}\n") f.write(f"static const uint32_t tv{i}_llr[52] = {{\n ") for j, w in enumerate(llr_words): f.write(f"0x{w:08X}") if j < len(llr_words) - 1: f.write(", ") if (j + 1) % 8 == 0 and j < len(llr_words) - 1: f.write("\n ") f.write(f"\n}};\n") f.write(f"static const uint32_t tv{i}_decoded = 0x{decoded_word:08X};\n") f.write(f"static const int tv{i}_converged = {int(v['converged'])};\n\n") f.write("#endif // TEST_VECTORS_H\n") if __name__ == '__main__': project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) json_path = os.path.join(project_root, 'data', 'test_vectors.json') with open(json_path) as f: vectors = json.load(f) # Generate cocotb test data cocotb_path = os.path.join(project_root, 'chip_ignite/verilog/dv/cocotb/ldpc_tests/test_data.py') os.makedirs(os.path.dirname(cocotb_path), exist_ok=True) gen_cocotb_test_data(vectors, cocotb_path) print(f"Generated {cocotb_path}") # Generate C header c_path = os.path.join(project_root, 'chip_ignite/firmware/ldpc_demo/test_vectors.h') os.makedirs(os.path.dirname(c_path), exist_ok=True) gen_c_header(vectors, c_path) print(f"Generated {c_path}") ``` **Step 4: Run the converter** ```bash python3 model/gen_firmware_vectors.py ``` Expected: Creates `chip_ignite/verilog/dv/cocotb/ldpc_tests/test_data.py` and `chip_ignite/firmware/ldpc_demo/test_vectors.h`. **Step 5: Commit** ```bash git add model/gen_firmware_vectors.py cd chip_ignite git add verilog/dv/cocotb/ldpc_tests/test_data.py firmware/ldpc_demo/test_vectors.h git commit -m "feat: add test vector generation for cocotb and firmware" ``` --- ### Task 3: Write standalone Verilator testbench **Files:** - Create: `tb/tb_ldpc_decoder.sv` - Create: `tb/Makefile` This is a fast-iteration standalone testbench (no Caravel dependency). Tests the decoder core directly via Wishbone. **Step 1: Write the testbench** Create `tb/tb_ldpc_decoder.sv`: ```systemverilog // Standalone Verilator testbench for LDPC decoder // Tests Wishbone register access and decode operation // Uses test vectors generated by model/ldpc_sim.py `timescale 1ns/1ps module tb_ldpc_decoder; parameter CLK_PERIOD = 20; // 50 MHz logic clk; logic rst_n; logic wb_cyc; logic wb_stb; logic wb_we; logic [7:0] wb_adr; logic [31:0] wb_dat_w; logic [31:0] wb_dat_r; logic wb_ack; logic irq; // DUT: standalone ldpc_decoder_top (original, not Caravel-adapted) ldpc_decoder_top dut ( .clk (clk), .rst_n (rst_n), .wb_cyc_i (wb_cyc), .wb_stb_i (wb_stb), .wb_we_i (wb_we), .wb_adr_i (wb_adr), .wb_dat_i (wb_dat_w), .wb_dat_o (wb_dat_r), .wb_ack_o (wb_ack), .irq_o (irq) ); // Clock generation initial clk = 0; always #(CLK_PERIOD/2) clk = ~clk; // Wishbone write task task automatic wb_write(input [7:0] addr, input [31:0] data); @(posedge clk); wb_cyc <= 1; wb_stb <= 1; wb_we <= 1; wb_adr <= addr; wb_dat_w <= data; do @(posedge clk); while (!wb_ack); wb_cyc <= 0; wb_stb <= 0; wb_we <= 0; endtask // Wishbone read task task automatic wb_read(input [7:0] addr, output [31:0] data); @(posedge clk); wb_cyc <= 1; wb_stb <= 1; wb_we <= 0; wb_adr <= addr; do @(posedge clk); while (!wb_ack); data = wb_dat_r; wb_cyc <= 0; wb_stb <= 0; endtask // Test vector data (clean codeword: all-zero message, all LLRs = +31) initial begin logic [31:0] rdata; int errors; errors = 0; rst_n = 0; wb_cyc = 0; wb_stb = 0; wb_we = 0; wb_adr = 0; wb_dat_w = 0; // Reset repeat(10) @(posedge clk); rst_n = 1; repeat(5) @(posedge clk); // ---- Test 1: Read version register ---- $display("[TEST 1] Read VERSION register"); wb_read(8'h54, rdata); if (rdata !== 32'hLD01_0001) begin $display(" FAIL: VERSION = 0x%08X, expected 0xLD010001", rdata); errors++; end else begin $display(" PASS: VERSION = 0x%08X", rdata); end // ---- Test 2: Decode all-zero codeword (clean) ---- $display("[TEST 2] Decode clean all-zero codeword"); // Write LLRs: all +31 (= bit 0 very likely) // Packed: 5 LLRs of +31 = {6'h1F, 6'h1F, 6'h1F, 6'h1F, 6'h1F} // = {5'b11111 x5} in 30 bits = 0x1F7DF7DF for (int i = 0; i < 52; i++) begin wb_write(8'h10 + i*4, 32'h1F7D_F7DF); end // Start decode: early_term=1, max_iter=30, start=1 wb_write(8'h00, 32'h0000_1E03); // [12:8]=30, [1]=1, [0]=1 // Poll status until not busy do begin repeat(10) @(posedge clk); wb_read(8'h04, rdata); end while (rdata[0]); // bit 0 = busy $display(" STATUS = 0x%08X", rdata); $display(" Busy=%0d, Converged=%0d, Iters=%0d, Syndrome=%0d", rdata[0], rdata[1], rdata[12:8], rdata[23:16]); if (!rdata[1]) begin $display(" FAIL: did not converge"); errors++; end if (rdata[23:16] != 0) begin $display(" FAIL: syndrome weight = %0d, expected 0", rdata[23:16]); errors++; end // Read decoded bits wb_read(8'h50, rdata); $display(" Decoded = 0x%08X", rdata); if (rdata !== 32'h0000_0000) begin $display(" FAIL: decoded = 0x%08X, expected 0x00000000", rdata); errors++; end else begin $display(" PASS: decoded bits correct"); end // ---- Summary ---- repeat(10) @(posedge clk); if (errors == 0) $display("\n=== ALL TESTS PASSED ===\n"); else $display("\n=== %0d ERRORS ===\n", errors); $finish; end // Timeout initial begin #(CLK_PERIOD * 100_000); $display("TIMEOUT"); $finish; end // VCD dump initial begin $dumpfile("ldpc_decoder.vcd"); $dumpvars(0, tb_ldpc_decoder); end endmodule ``` **Step 2: Create Makefile** Create `tb/Makefile`: ```makefile RTL_DIR = ../rtl RTL_FILES = $(RTL_DIR)/ldpc_decoder_top.sv \ $(RTL_DIR)/ldpc_decoder_core.sv \ $(RTL_DIR)/wishbone_interface.sv .PHONY: lint sim clean lint: verilator --lint-only -Wall $(RTL_FILES) --top-module ldpc_decoder_top sim: obj_dir/Vtb_ldpc_decoder ./obj_dir/Vtb_ldpc_decoder obj_dir/Vtb_ldpc_decoder: tb_ldpc_decoder.sv $(RTL_FILES) verilator --binary --timing --trace \ -o Vtb_ldpc_decoder \ -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC \ --unroll-count 1024 \ tb_ldpc_decoder.sv $(RTL_FILES) \ --top-module tb_ldpc_decoder clean: rm -rf obj_dir *.vcd ``` **Step 3: Run lint** ```bash cd tb make lint ``` Expected: Clean (0 errors). **Step 4: Run simulation** ```bash cd tb make sim ``` Expected: ``` [TEST 1] Read VERSION register PASS: VERSION = 0xLD010001 [TEST 2] Decode clean all-zero codeword ... PASS: decoded bits correct === ALL TESTS PASSED === ``` **Step 5: Commit** ```bash git add tb/tb_ldpc_decoder.sv tb/Makefile git commit -m "test: add standalone Verilator testbench for LDPC decoder" ``` --- ### Task 4: Extend Verilator testbench with generated test vectors **Files:** - Create: `tb/tb_ldpc_vectors.sv` - Modify: `tb/Makefile` (add target) - Depends on: `data/test_vectors.json` from Task 2 **Step 1: Create a script to convert JSON vectors to $readmemh format** Create `model/gen_verilator_vectors.py`: ```python #!/usr/bin/env python3 """Convert test_vectors.json to hex files for Verilator $readmemh.""" import json import os def main(): project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) json_path = os.path.join(project_root, 'data', 'test_vectors.json') out_dir = os.path.join(project_root, 'tb', 'vectors') os.makedirs(out_dir, exist_ok=True) with open(json_path) as f: vectors = json.load(f) # Write LLR file: one line per LLR, 6-bit hex (2 chars), 256 lines per vector with open(os.path.join(out_dir, 'llr_input.hex'), 'w') as f: for v in vectors: for llr in v['llr_quantized']: f.write(f"{llr & 0x3F:02X}\n") # Write expected results: decoded_word, converged, iterations, syndrome_weight with open(os.path.join(out_dir, 'expected.hex'), 'w') as f: for v in vectors: decoded = sum(b << j for j, b in enumerate(v['info_bits'])) f.write(f"{decoded:08X}\n") f.write(f"{int(v['converged']):02X}\n") f.write(f"{v['iterations']:02X}\n") f.write(f"{v['syndrome_weight']:02X}\n") # Write metadata with open(os.path.join(out_dir, 'num_vectors.txt'), 'w') as f: f.write(f"{len(vectors)}\n") print(f"Generated {len(vectors)} vectors in {out_dir}/") if __name__ == '__main__': main() ``` **Step 2: Run it** ```bash python3 model/gen_verilator_vectors.py ``` Expected: Creates `tb/vectors/llr_input.hex`, `tb/vectors/expected.hex`. **Step 3: Write vector-driven testbench** Create `tb/tb_ldpc_vectors.sv` — a testbench that reads hex files and tests each vector against RTL. Structure matches `tb_ldpc_decoder.sv` but loops over all 20 vectors, compares decoded bits, convergence, and iteration count against the Python model reference. **Step 4: Run vector testbench** ```bash cd tb make sim_vectors ``` Expected: All 20 vectors pass bit-exact match against Python model. **Step 5: Commit** ```bash git add model/gen_verilator_vectors.py tb/tb_ldpc_vectors.sv tb/vectors/ tb/Makefile git commit -m "test: add vector-driven Verilator testbench with Python model cross-check" ``` --- ### Task 5: Write cocotb firmware for LDPC Wishbone test **Files:** - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_basic/ldpc_basic.c` - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_basic/ldpc_basic.py` - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_basic/ldpc_basic.yaml` The cocotb Caravel test pattern requires paired firmware (C) + monitor (Python). **Step 1: Write firmware C code** ```c // ldpc_basic.c - PicoRV32 firmware for LDPC decoder Wishbone test // Writes LLRs, starts decode, reads result, signals pass/fail on GPIO #include #include // LDPC decoder registers (Caravel user project base = 0x30000000) #define LDPC_BASE 0x30000000 #define LDPC_CTRL (*(volatile uint32_t*)(LDPC_BASE + 0x00)) #define LDPC_STATUS (*(volatile uint32_t*)(LDPC_BASE + 0x04)) #define LDPC_LLR(i) (*(volatile uint32_t*)(LDPC_BASE + 0x10 + (i)*4)) #define LDPC_DECODED (*(volatile uint32_t*)(LDPC_BASE + 0x50)) #define LDPC_VERSION (*(volatile uint32_t*)(LDPC_BASE + 0x54)) void main() { // Configure GPIO[7:0] as user output reg_mprj_io_7 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_6 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_5 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_4 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_3 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_2 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_1 = GPIO_MODE_USER_STD_OUTPUT; reg_mprj_io_0 = GPIO_MODE_USER_STD_OUTPUT; // Apply GPIO config reg_mprj_xfer = 1; while (reg_mprj_xfer == 1); // Signal test start via management GPIO reg_mprj_datal = 0x00000000; reg_gpio_out = 1; // mgmt GPIO = 1 -> "firmware ready" // --- Test: Read version register --- uint32_t version = LDPC_VERSION; if (version != 0xLD010001) { reg_mprj_datal = 0x000000FF; // all 1s = FAIL while(1); } // --- Test: Decode all-zero codeword --- // Write LLRs: all +31 (strong confidence in bit=0) // Pack: 5x 6'b011111 = 0x1F7DF7DF for (int i = 0; i < 52; i++) { LDPC_LLR(i) = 0x1F7DF7DF; } // Start decode: early_term=1, max_iter=30, start=1 LDPC_CTRL = 0x00001E03; // Poll until not busy while (LDPC_STATUS & 0x1); uint32_t status = LDPC_STATUS; uint32_t decoded = LDPC_DECODED; // Check: converged, syndrome=0, decoded=0x00000000 int pass = 1; if (!(status & 0x2)) pass = 0; // not converged if ((status >> 16) & 0xFF) pass = 0; // syndrome != 0 if (decoded != 0x00000000) pass = 0; // wrong decoded bits if (pass) { reg_mprj_datal = 0x000000AB; // 0xAB = PASS signature } else { reg_mprj_datal = 0x000000FF; // 0xFF = FAIL signature } } ``` **Step 2: Write cocotb monitor** ```python # ldpc_basic.py - cocotb test monitoring LDPC decode via GPIO from caravel_cocotb.caravel_interfaces import test_configure from caravel_cocotb.caravel_interfaces import report_test import cocotb @cocotb.test() @report_test async def ldpc_basic(dut): caravelEnv = await test_configure(dut, timeout_cycles=100000) await caravelEnv.release_csb() # Wait for firmware to signal ready await caravelEnv.wait_mgmt_gpio(1) cocotb.log.info("[TEST] Firmware started, waiting for result...") # Wait for GPIO output to change (firmware writes result) for _ in range(50000): await cocotb.triggers.ClockCycles(caravelEnv.clk, 10) gpio_val = caravelEnv.monitor_gpio(7, 0).integer if gpio_val != 0: break cocotb.log.info(f"[TEST] GPIO[7:0] = 0x{gpio_val:02X}") if gpio_val == 0xAB: cocotb.log.info("[TEST] LDPC basic decode: PASS") else: cocotb.log.error(f"[TEST] LDPC basic decode: FAIL (GPIO=0x{gpio_val:02X})") ``` **Step 3: Write test YAML** ```yaml # ldpc_basic.yaml Tests: - {name: ldpc_basic, sim: RTL} ``` **Step 4: Register test in cocotb_tests.py** Add to `chip_ignite/verilog/dv/cocotb/cocotb_tests.py`: ```python from ldpc_tests.ldpc_basic.ldpc_basic import ldpc_basic ``` **Step 5: Run cocotb test** ```bash cd chip_ignite cf verify ldpc_basic ``` Expected: PASS with GPIO[7:0] = 0xAB. **Step 6: Commit** ```bash cd chip_ignite git add verilog/dv/cocotb/ldpc_tests/ verilog/dv/cocotb/cocotb_tests.py git commit -m "test: add cocotb LDPC basic decode test with firmware" ``` --- ### Task 6: Write additional cocotb tests **Files:** - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.c` - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.py` - Create: `chip_ignite/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.yaml` Same pattern as Task 5 but uses a noisy test vector from `test_vectors.h`. Firmware loads the first noisy vector, decodes, compares against expected. Reports pass/fail on GPIO. Additional tests following same pattern: - `ldpc_max_iter` — uncorrectable vector, verify decoder hits max iterations and reports non-zero syndrome - `ldpc_back_to_back` — two decodes in sequence, verify no state leakage **Step 1-4: Write firmware + cocotb + YAML for each test (same pattern as Task 5)** **Step 5: Run all tests** ```bash cd chip_ignite cf verify ldpc_basic cf verify ldpc_noisy cf verify ldpc_max_iter cf verify ldpc_back_to_back ``` Expected: All PASS. **Step 6: Commit** ```bash cd chip_ignite git add verilog/dv/cocotb/ldpc_tests/ git commit -m "test: add noisy, max_iter, and back-to-back cocotb tests" ``` --- ## Week 2: OpenLane Hardening ### Task 7: Create OpenLane configuration for LDPC decoder macro **Files:** - Create: `chip_ignite/openlane/ldpc_decoder_top/config.json` - Create: `chip_ignite/openlane/ldpc_decoder_top/pin_order.cfg` - Create: `chip_ignite/openlane/ldpc_decoder_top/base_ldpc.sdc` **Step 1: Write config.json** ```json { "DESIGN_NAME": "ldpc_decoder_top", "VERILOG_FILES": [ "dir::../../verilog/rtl/defines.v", "dir::../../verilog/rtl/ldpc_decoder_top.sv", "dir::../../verilog/rtl/ldpc_decoder_core.sv", "dir::../../verilog/rtl/wishbone_interface.sv" ], "CLOCK_PERIOD": 20, "CLOCK_PORT": "clk", "CLOCK_NET": "u_core.clk", "DIE_AREA": [0, 0, 1400, 1200], "FP_PIN_ORDER_CFG": "dir::pin_order.cfg", "FP_SIZING": "absolute", "FP_PDN_MULTILAYER": false, "VDD_NETS": ["vccd1"], "GND_NETS": ["vssd1"], "FALLBACK_SDC_FILE": "dir::base_ldpc.sdc", "MAX_TRANSITION_CONSTRAINT": 1.5, "MAX_FANOUT_CONSTRAINT": 16, "SYNTH_STRATEGY": "AREA 2", "PL_TARGET_DENSITY_PCT": 40, "DIODE_INSERTION_STRATEGY": "heuristic", "RUN_LINTER": false, "pdk::sky130*": { "SYNTH_MAX_FANOUT": 10, "scl::sky130_fd_sc_hd": { "CLOCK_PERIOD": 20 } } } ``` **Step 2: Write pin_order.cfg** ``` #BUS_SORT #S clk rst_n wb_cyc_i wb_stb_i wb_we_i wb_sel_i\[.*\] wb_adr_i\[.*\] wb_dat_i\[.*\] #N wb_dat_o\[.*\] wb_ack_o irq_o ``` **Step 3: Write SDC constraints** ```sdc # base_ldpc.sdc - Timing constraints for LDPC decoder create_clock -name clk -period 20.0 [get_ports {clk}] set_input_delay -clock clk -max 5.0 [all_inputs] set_input_delay -clock clk -min 1.0 [all_inputs] set_output_delay -clock clk -max 5.0 [all_outputs] set_output_delay -clock clk -min 0.0 [all_outputs] ``` **Step 4: Run hardening** ```bash cd chip_ignite cf harden ldpc_decoder_top ``` Expected: Synthesis + P&R completes. Note WNS (worst negative slack) — should be positive at 50 MHz. **Step 5: Check results** ```bash # Check timing summary in OpenLane logs grep -r "wns" chip_ignite/openlane/ldpc_decoder_top/runs/*/logs/ # Check area grep -r "DIEAREA" chip_ignite/gds/ldpc_decoder_top.gds || true ``` **Step 6: Commit** ```bash cd chip_ignite git add openlane/ldpc_decoder_top/ git commit -m "feat: add OpenLane hardening config for LDPC decoder (50 MHz)" ``` --- ### Task 8: Update wrapper hardening config **Files:** - Modify: `chip_ignite/openlane/user_project_wrapper/config.json` **Step 1: Update MACROS section** Replace `user_proj_example` references with `ldpc_decoder_top`: - Change macro name from `user_proj_example` to `ldpc_decoder_top` - Update GDS/LEF/netlist/SPEF paths - Adjust placement coordinates if needed (keep `(60, 15)` as starting point) **Step 2: Update VERILOG_FILES** Point to LDPC RTL instead of example. **Step 3: Run wrapper hardening** ```bash cd chip_ignite cf harden user_project_wrapper ``` **Step 4: Commit** ```bash cd chip_ignite git add openlane/user_project_wrapper/config.json git commit -m "feat: update wrapper config for LDPC decoder macro" ``` --- ## Week 3: Gate-Level Sim & Precheck ### Task 9: Run gate-level simulation **Step 1: Update GL include file** Edit `chip_ignite/verilog/includes/includes.gl.caravel_user_project`: ``` -v $(USER_PROJECT_VERILOG)/gl/user_project_wrapper.v -v $(USER_PROJECT_VERILOG)/gl/ldpc_decoder_top.v ``` **Step 2: Run GLS for basic test** ```bash cd chip_ignite cf verify ldpc_basic --sim gl ``` Expected: PASS (same as RTL sim but with gate-level netlist). **Step 3: Run all GLS tests** ```bash cf verify ldpc_noisy --sim gl cf verify ldpc_max_iter --sim gl cf verify ldpc_back_to_back --sim gl ``` **Step 4: Commit** ```bash cd chip_ignite git add verilog/includes/ git commit -m "test: gate-level simulation passing for all LDPC tests" ``` --- ### Task 10: Timing push to 75 MHz (stretch goal) **Step 1: Check current timing margin** If WNS > 5 ns at 50 MHz (20 ns period), there's room to push. **Step 2: Tighten clock to 75 MHz** Update `openlane/ldpc_decoder_top/config.json`: ```json "CLOCK_PERIOD": 13.3 ``` **Step 3: Re-run hardening** ```bash cf harden ldpc_decoder_top cf harden user_project_wrapper ``` **Step 4: Check timing** If WNS is negative, either: - Pipeline the critical path in `ldpc_decoder_core.sv` (CN update min-finding) - Or revert to 50 MHz (20 ns) and document the achieved frequency **Step 5: Commit** ```bash git commit -am "perf: tighten clock to 75 MHz (or document 50 MHz)" ``` --- ### Task 11: Run precheck **Step 1: Configure GPIO** ```bash cd chip_ignite cf gpio-config ``` Set GPIO[0:7] as user standard output. Set remaining as management input (default). **Step 2: Run precheck** ```bash cf precheck ``` Expected: All checks pass (DRC, LVS, power, clock, GPIO, wrapper, manifest). **Step 3: Fix any issues and re-run until clean** **Step 4: Commit** ```bash git commit -am "chore: precheck passing" ``` --- ## Week 4: Proposal Submission ### Task 12: Add LICENSE and AI disclosure **Files:** - Create: `chip_ignite/LICENSE` (Apache 2.0) - Create: `chip_ignite/docs/ai-disclosure.md` **Step 1: Add Apache 2.0 license** ```bash cd chip_ignite # Copy standard Apache 2.0 license text cat > LICENSE << 'HEREDOC' Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ ... HEREDOC ``` **Step 2: Write AI disclosure** ```markdown # AI Disclosure This project was developed with assistance from Claude (Anthropic), an AI assistant. ## Usage Claude was used for: - RTL design review and optimization - Testbench generation - Documentation writing - Build system configuration - Design space exploration (density evolution analysis, code rate comparison) ## Session Logs Session transcripts are available upon request. Key AI-assisted contributions: - `rtl/ldpc_decoder_core.sv` — algorithmic implementation reviewed and refined with AI - `model/*.py` — analysis scripts co-developed with AI - `docs/` — documentation drafted with AI assistance - `openlane/` — configuration guided by AI ## Human Contributions All architectural decisions, algorithm selection, code review, and final verification were performed by the human designer. The AI served as a coding assistant and technical reference. ``` **Step 3: Commit** ```bash git add LICENSE docs/ai-disclosure.md git commit -m "docs: add Apache 2.0 license and AI disclosure" ``` --- ### Task 13: Write proposal README **Files:** - Modify: `chip_ignite/README.md` **Step 1: Write comprehensive README** Cover: - Project title and one-line description - Application: photon-starved free-space optical communication - Architecture diagram (ASCII) - Code parameters table - Performance specs (clock frequency, throughput, latency, area) - Verification status (test list, pass/fail) - Timing report summary (WNS/TNS) - Directory structure - How to build and run - Roadmap (Approaches B and C from design doc) - License and AI disclosure link **Step 2: Commit** ```bash git add README.md git commit -m "docs: write proposal README for ChipFoundry contest" ``` --- ### Task 14: Submit proposal **Step 1: Push to GitHub** ```bash cd chip_ignite git push origin main ``` **Step 2: Submit repo URL via contest form** Go to https://chipfoundry.io/challenges/application/#submit_form and submit the GitHub repo URL. --- ## Weeks 5-8: Post-Proposal (Final Submission) ### Task 15: Write PicoRV32 demo firmware **Files:** - Create: `chip_ignite/firmware/ldpc_demo/ldpc_demo.c` - Create: `chip_ignite/firmware/ldpc_demo/Makefile` - Depends on: `chip_ignite/firmware/ldpc_demo/test_vectors.h` from Task 2 Full demo firmware that runs 3 scenarios on boot (clean, noisy, stress test) and prints results over UART. Uses the Caravel BSP for UART and GPIO. ### Task 16: Design PCBA in KiCad **Files:** - Create: `chip_ignite/pcba/ldpc_eval_board.kicad_pro` - Create: `chip_ignite/pcba/ldpc_eval_board.kicad_sch` - Create: `chip_ignite/pcba/ldpc_eval_board.kicad_pcb` - Create: `chip_ignite/pcba/BOM.csv` Schematic sections: 1. Caravel QFN-64 with decoupling 2. Power (LDO 3.3V + 1.8V, USB or barrel jack) 3. 25 MHz crystal oscillator 4. FTDI USB-UART bridge 5. SPI flash (firmware storage) 6. Status LEDs + reset button 7. GMAPD connector (DNP, reference only) 8. TIA + comparator section (DNP, reference only) ### Task 17: Design mechanical enclosure **Files:** - Create: `chip_ignite/mechanical/enclosure.scad` - Output: `chip_ignite/mechanical/enclosure.stl` Parametric OpenSCAD box with PCB standoffs, USB/barrel cutouts. ### Task 18: Record demo video 3-minute screen recording: 1. Show architecture diagram and explain the application (30s) 2. Run cocotb simulation showing decode pass (30s) 3. Show OpenLane GDS layout viewer (30s) 4. Walk through firmware UART output (30s) 5. Show KiCad 3D board render (30s) 6. Summarize specs and roadmap (30s) ### Task 19: Create how-to screenshots Step-by-step PNG screenshots: 1. Clone repo and run `cf setup` 2. Run `cf harden ldpc_decoder_top` 3. Run `cf verify ldpc_basic` 4. Run `cf precheck` 5. Build and flash firmware ### Task 20: Final submission - Verify all deliverables present - Run `cf precheck` one final time - Push to GitHub - Submit via contest form