From 9a28e30bed58d63cc48790110f01a83e77803845 Mon Sep 17 00:00:00 2001 From: cah Date: Wed, 25 Feb 2026 18:10:38 -0700 Subject: [PATCH] docs: add detailed implementation plan for ChipFoundry contest 20 tasks across 8 weeks covering RTL integration, verification, OpenLane hardening, firmware, PCBA, and final submission. Co-Authored-By: Claude Opus 4.6 --- .../2026-02-25-chipfoundry-contest-impl.md | 1282 +++++++++++++++++ 1 file changed, 1282 insertions(+) create mode 100644 docs/plans/2026-02-25-chipfoundry-contest-impl.md diff --git a/docs/plans/2026-02-25-chipfoundry-contest-impl.md b/docs/plans/2026-02-25-chipfoundry-contest-impl.md new file mode 100644 index 0000000..cb388d3 --- /dev/null +++ b/docs/plans/2026-02-25-chipfoundry-contest-impl.md @@ -0,0 +1,1282 @@ +# 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