Files
FPGA-Neural/CLAUDE.md
T
micheleandClaude Sonnet 5 9dead54ebf feat: real 32-bit DDR3 channel widening - functionally complete, timing NOT yet closed (EXP-0084)
Real 32-bit DDR3 widening (2x MT41J128M16JT-125:K chips ganged in
parallel, user's own MIG wizard session). Full RTL adaptation across
the shared ctrl bus (16-bit word -> 32-bit word, BURST_LEN=8 unchanged,
burst payload 128->256 bits):

- mig_native_adapter.v: app_wdf_data/app_rd_data 64->128 bits (real,
  confirmed against the regenerated MIG wrapper), beat count unchanged.
- act_tile_fetch.v: real logic change - burst now holds 4 tiles instead
  of 2 (sel_lat extended to 2 registered bits, 4-way case mux instead
  of 2-way ternary, same request-time-registered-select discipline as
  EXP-0081). Not a further bytes/MAC reduction, just what's needed to
  keep 100% packing utilization at the larger burst.
- host_mem_bridge.v: real addressing redesign - host-facing 16-bit-word
  contract kept unchanged (ESP32 firmware unaffected), internally
  translated onto the new 32-bit-native ctrl bus.
- sdram_arbiter_n.v, layer_prefetch_ctrl.v, packed_slot.v,
  ddr_prefetch_mgr.v, n2_system_ddr3_top.v: mechanical width bump plus
  doubled ddr3_dq/dqs/dm pins and the real differential sys_clk/clk_ref
  top-level ports the regenerated MIG now requires.

New burst_mem_model32.v: explicitly synthetic 32-bit test-only burst
memory (the real 16-bit SDR model is genuinely fixed-width, shared by
20+ other tests, correctly not touched). Found and fixed a real
address-aliasing bug in it during bring-up (MEM_ADDR_BITS=16 silently
wrapped a real 0x10000 test address to 0).

Real verification: all isolated testbenches re-verified (10/10, 33/33,
32/32, 7/7, 9/9 PASS), plus real xsim against the real 2-chip DDR3
model (tb_mig_native_adapter.v 12/12 PASS, tb_n2_system_ddr3.v 8/8
PASS, both chips visibly returning different real data).

Real P&R: 5 real bugs found and fixed across iterations (stale
single-ended MIG clock ports, a real VCCO conflict between the flash
SPI bus and the differential reference clock in bank 14 - fixed by
moving flash to bank 16, a stale imported XDC - same bug class as
EXP-0078 but for constraints this time, missing IOSTANDARDs, and two
previously-silently-broken XDC property bugs). Route completes 100%,
but real timing does NOT close: WNS -0.618ns, 213 failing endpoints.

Honest root cause: the violation is inside neural_processor_packed.v's
own packed-MAC accumulation tree, unchanged since EXP-0059 - it has
real margin at the old 155.039MHz ui_clk but not at the new 172.414MHz
the paired clock-period change produced. This is NOT caused by the
32-bit width change itself. Width alone, even at the old clock, already
delivers the full intended 2x bandwidth gain (1.24 -> ~2.48 GB/s) -
width and clock rate are separable levers. Current trustworthy timing
signoff remains EXP-0083 (16-bit, +0.073ns) until the clock period is
reverted toward 3225ps (keeping Data Width=32) in one more real,
user-gated MIG wizard session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-20 16:28:44 +02:00

6.9 KiB

FPGA-Neural — project instructions for Claude Code

Hardware neural accelerator for a custom PCB: bare Xilinx XC7A100T-CSG324-2 (Artix-7) chip + real DDR3, designed and assembled by the user themselves — never a Digilent/dev-board purchase. An ESP32 is the host/central processor, talking to the FPGA over a dedicated SPI bus (FPGA is slave there) and, via the FPGA, through to a separate config flash used only for FPGA bootstrapping (FPGA is master on that second, physically distinct SPI bus).

Active branch: v3-artix7. hardware/v3/ is the current, real target. hardware/v2/ is the archived ECP5 baseline (frozen, DSP-count-limited, superseded — do not build on it, some of its RTL is still reused unmodified by v3, e.g. layer_prefetch_ctrl.v/layer_weight_buffer.v). hardware/v1/ is older still, reference only.

Read first

  • docs/PHYSICAL_REALIZATION.md — every real pin assignment, part number, timing number, and memory-layout convention needed for the physical board and for host (ESP32) firmware. Keep it in sync with reality — if a pin assignment or timing number changes, update this file in the same commit.
  • docs/ARCHITECTURE_ANALYSIS.md — real, measured bottleneck analysis (DDR3 bandwidth is the real ceiling, not DSP count — see it before proposing to scale core count) and ranked recommended interventions. Update it whenever a recommendation from it gets built or a new real bottleneck is found.
  • hardware/v2/logs/experiments.log — the real project history, one EXP-NNNN entry per real experiment/change (context/method/result/ decision/next_action). Read the tail before starting new work; append a new entry for anything non-trivial you do. This log — not memory, not chat history — is the authoritative record of what's been tried and why.

Toolchains (real paths, already working — don't re-diagnose from scratch)

  • Vivado 2026.1: source /home/michele/tools_cache/Xilinx/2026.1/Vivado/settings64.sh, then export LD_LIBRARY_PATH="/home/michele/tools_cache/Xilinx/2026.1/Vivado/lib/lnx64.o/Ubuntu/24:$LD_LIBRARY_PATH" (this machine's Ubuntu is too new for Vivado's own OS detection; the LD_LIBRARY_PATH points at Vivado's own bundled compat libs — not a real distro package, must be set every session).
  • OSS CAD Suite (iverilog/vvp for fast plain-Verilog sims, no Xilinx primitives): source /home/michele/tools_cache/oss-cad-suite/environment.
  • Real Vivado project: Vivado/NeuralProcessor/NeuralProcessor.xpr — the MIG DDR3 IP lives there, real, already generated for the exact part.

Hard-won lessons (do not re-derive these the slow way)

  • Vivado's imported source copies go stale silently — RTL and constraint (.xdc) files alike. If a project file under NeuralProcessor.srcs/sources_1/imports/... (RTL) or NeuralProcessor.srcs/constrs_1/imports/... (XDC) was ever edited on disk after being added to the project, diff it against the live hardware/v3/... source before trusting any P&R result — add_files/ update_compile_order do NOT auto-refresh it, and a stale copy produces no error, just silently wrong (old) synthesis/implementation results (EXP-0078 for RTL; EXP-0084 for the XDC — the stale constraints file was old enough to still have PRE-EXP-0077 pin assignments, predating the flash bridge entirely, and its own real bug fixes silently didn't take effect across multiple P&R re-runs until the staleness itself was diagnosed and fixed). Prefer adding new files so they stay a direct reference (check IS_GLOBAL_INCLUDE/the file's own path isn't under imports/) rather than get copied — for BOTH the sources_1 and constrs_1 filesets.
  • Testbench stimulus must use nonblocking assignment (<=), not blocking (=), when driving a DUT's inputs from a separate always/initial block. Blocking assignment races the DUT's own posedge-triggered always block under Icarus and can silently corrupt data OR miss a one-shot pulse entirely (causing a real hang) — hit and fixed repeatedly (EXP-0073, 0075, 0077) before this became standing practice. If a new Icarus testbench shows shuffled/duplicated fields or an inexplicable hang, suspect this class of bug before assuming the RTL is wrong.
  • Never use a runtime-indexed part-select (data[idx*W +: W] where idx is a signal, not a constant) on a wide bus in anything synthesizable — a known real Fmax killer (weight_tile_gather.v's own header, EXP-0061). Use fixed shift-concat, or (as act_tile_fetch.v does, EXP-0079) design the memory layout so only a fixed slice is ever needed. The current real P&R timing margin is thin (WNS +0.013ns, EXP-0078) — there is no slack to absorb a new critical path.
  • A one-shot-pulse requester on a shared/arbitrated bus must see its own grant the SAME cycle its own active signal first asserts — a registered/one-cycle-late grant silently loses the request forever (EXP-0066's own real bug, now a standing design rule for every arbiter/ requester pair in this project).
  • xvlog/iverilog need -sv/-g2012 respectively to accept SystemVerilog-only syntax (e.g. '0) even in a plain .v file — prefer just not using SV-only syntax in synthesizable RTL (Vivado's synth_design has no such escape hatch at all).
  • Verify real component availability (LCSC) before committing to a part — the user has asked for this explicitly more than once. Don't guess availability or specs from training data; search when it matters.
  • Real, measured numbers only — never estimate/guess a timing or performance figure and present it as fact. Out-of-context synthesis is not a real signoff; only a real in-context place_design/route_design run on the actual top-level module counts. If a number is a projection (not measured), say so explicitly and show the real numbers it's built from.

Working discipline

  • Fork before promote: don't edit an already-verified, in-use RTL file in place for a new experiment — copy/fork it, verify the fork, then decide whether to promote it. (Established V2-era convention, still followed in V3.)
  • One variable at a time: verify a new module in isolation before wiring it into a larger system; verify the larger system before trusting a P&R number built on top of it.
  • Root-cause every anomaly via hierarchical signal tracing — never guess or paper over an unexplained result. Several real bugs in this project were found exactly this way, not by inspection.
  • After ANY RTL change to logic that's part of the real synthesis target (hardware/v3/rtl/n2_system_ddr3_top.v and its dependents), re-run a real P&R before claiming it's still timing-clean — the margin is thin enough that this is not optional caution, it's load-bearing.
  • Commit messages end with the attribution lines already configured for this session (Co-Authored-By + Claude-Session) — keep using them.