# 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 <defs.h>
#include <stub.c>

// 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
