Commit Graph
17 Commits
Author SHA1 Message Date
micheleandClaude Sonnet 5 fa327b75ca feat: DDRManager phase 1 - single-slot look-ahead activation prefetch (EXP-0083)
New ddr_prefetch_mgr.v wraps act_tile_fetch.v with a depth-2 ping-pong
buffer, issuing the next tile's DDR3 fetch as soon as the fetch engine
is free instead of waiting for packed_slot.v to finish consuming the
current tile. Wired into packed_slot.v's tile loop (job-level start
instead of per-tile req), simplifying the S_TILEWAIT join in the process
(ddrpf_tile_valid is level-held, no separate act_seen latch needed).

Verification: new tb_ddr_prefetch_mgr.v (25/25 PASS after fixing a real
testbench polling race found via iteration-tagged tracing, not an RTL
bug), tb_packed_slot.v re-run unmodified (9/9 PASS, bit-identical
results), tb_n2_system_ddr3.v re-run via real xsim against real
ddr3_model.sv (8/8 PASS). Real P&R: WNS +0.073ns (up from EXP-0082's
+0.068ns), LUTs 5644, DSP48E1 16 unchanged, 0 failing endpoints.

Honest result: real A/B on the actual DDR3 backend (same testbench,
before/after) shows a real but modest 2.86% reduction in total
simulated time - smaller than the original hypothesis suggested, because
neural_processor_packed.v already accepts one operand per cycle, so the
per-tile dead time being removed was already small relative to real DDR3
fetch latency. Docs updated to report this honestly rather than oversell
it; the larger multi-slot DDRManager is deferred pending re-measurement
against the (still pending, user-gated) 32-bit channel widening.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-20 10:59:45 +02:00
micheleandClaude Sonnet 5 8ad04987de feat: denser activation packing, real bandwidth ceiling doubled (EXP-0081)
Implements the highest-leverage fix from EXP-0080's bottleneck
analysis: act_tile_fetch.v now packs 2 consecutive tiles per DDR3
burst (even tile low 64 bits, odd tile high 64 bits) instead of 1
tile per burst, halving real DDR3 bytes-per-useful-byte. Timing-safe
by construction: the tile-index select bit is registered at request
time, long before the real DDR3 round-trip completes, never racing
the arriving read data (unlike the runtime part-select pattern
EXP-0079 deliberately avoided).

Re-verified at all 3 levels (isolated engine 8/8, packed_slot.v 9/9
with bit-identical results to EXP-0079, full N=2 system on real DDR3
8/8) -- the real JEDEC trace now shows no half-burst padding, direct
confirmation the fix works in practice, not just in theory.

Also: real device data gathered on this package's I/O bank layout
(only 5 banks total, 14/15/16/34/35) informing the next bandwidth step
(32-bit-wide single controller recommended over a second independent
channel, given the pin/logic cost comparison).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-20 09:55:16 +02:00
micheleandClaude Sonnet 5 43a12379a5 feat: MILESTONE - real activation-fetch engine, full N=2 system verified on real DDR3 (EXP-0079)
Closes the last major disclosed functional gap: packed_slot.v's
activation data was read through a combinational stand-in since
EXP-0062. New act_tile_fetch.v reads activation tiles directly from
DDR3 (no on-chip buffering needed, unlike weights -- activation data
has no reuse), sharing each slot's existing ctrl port with its own
weight-prefetch engine. Real memory layout: one full BURST_LEN=8-word
burst per tile, deliberately avoiding any runtime-indexed part-select
given this project's thin P&R timing margin (EXP-0078).

Verified at three levels: act_tile_fetch.v alone (6/6), packed_slot.v
with real preloaded activation data (9/9), and the full N=2 system
against real DDR3 via xsim (8/8, 0 errors) -- the first time this
project's compute path has been verified end-to-end with real DDR3
for both weights and activations.

Retired hardware/v3/rtl/n2_system_top.v and its testbench (pre-DDR3
SDR-placeholder era, fully superseded by n2_system_ddr3_top.v).

Also: docs/PHYSICAL_REALIZATION.md (real pinout/parts/timing/protocol
reference for the physical board) and CLAUDE.md (persistent project
instructions for future Claude Code sessions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-20 08:13:15 +02:00
micheleandClaude Sonnet 5 a4c080da83 feat: config-flash passthrough bridge via STARTUPE2, real board-exclusive flash access (EXP-0077)
Implements the user's board architecture: config flash wired
exclusively to the FPGA, host (ESP32) reaches it only through the
FPGA. flash_spi_master.v is a plain byte-wide SPI master using
STARTUPE2 to reclaim CCLK after configuration (the real, Xilinx-
documented "indirect SPI flash programming" technique, UG470 p94-96).
New opcode 0x40 FLASH_XFER in spi_host_bridge_v3.v relays bytes
byte-for-byte between host and the physical flash bus -- the host
decides the exact SPI NOR command sequence (verified against the real
W25Q32JV datasheet), this RTL knows nothing about flash semantics.

Found and fixed two real bugs during verification: a byte-assembly
off-by-one in flash_spi_master.v, and a genuine protocol-latency bug
in the FLASH_XFER opcode's response timing (needed 2 trailing margin
bytes, not 1 -- the internal flash transfer doesn't start until the
triggering byte finishes, so 1 byte of margin isn't enough). 39/39
tests pass end to end (host SPI -> bridge -> flash_spi_master ->
behavioral flash model).

Wired into n2_system_ddr3_top.v with real pin constraints (flash_mosi
=K17/flash_miso=K18/flash_cs_n=L13, the same pins reserved-but-unused
in EXP-0075) and BITSTREAM.CONFIG.PERSIST=FALSE made explicit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-19 21:45:17 +02:00
micheleandClaude Sonnet 5 ca17765fe3 fix: real SPI MISO bit-corruption bug found+fixed; add register file + pin plan (EXP-0075)
Found a real, previously-masked bug in spi_host_bridge_v3.v's physical
layer (inherited unchanged from V1/V2): the bit_count==0 MISO bypass
corrupts the last bit of any multi-byte response whose value happens
to end in a 1 -- every prior test's response data coincidentally
ended in 0, hiding it until the new DEVICE_ID register (0x...01)
exposed it via a real bit-exact mismatch. Fixed by removing the
bypass (verified unnecessary for this protocol's actual usage).

Added REG_WRITE/REG_READ opcodes (0x30/0x31) and a register file
(DEVICE_ID/CONTROL/STATUS/N_SLOTS) for general device control beyond
job submission, per explicit user request. Full regression: 38/38
PASS, including new cases specifically targeting the bit-corruption
bug for both REG_READ and READ_MEM.

New hardware/v3/constraints/n2_system_ddr3_top.xdc: reserves the
FPGA's dedicated Master-SPI config-flash pins (found colliding with
auto-placed design ports in the real routed checkpoint) and assigns
the neural-processor management SPI to real, verified-free, edge-
adjacent pins on xc7a100tcsg324-2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-19 18:31:51 +02:00
micheleandClaude Sonnet 5 6caea54b8e fix: neural_director_packed.v SV literal for synth; exonerate it from a testbench race (EXP-0073)
Replaced three uses of the SystemVerilog '0 self-sizing literal with
explicit-width zero-fill so the file synthesizes under Vivado's
synth_design (which has no -sv equivalent in this flow), needed while
adding this module to the real in-context P&R project.

Re-running its isolated regression after that edit surfaced 3/8
failures. Root-caused via git stash (reproduces on the untouched
committed file, not caused by this edit) and a DUT-internal $display:
tb_neural_director_packed.v's own submit_job task drove DUT inputs
with blocking assignment across two separate @(posedge clk) waits,
racing the DUT's own always block under Icarus and causing a spurious
duplicate enqueue. Fixed by switching to nonblocking assignment
(race-free by construction). neural_director_packed.v itself was
correct all along - 8/8 tests pass after the testbench fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-19 13:37:25 +02:00
micheleandClaude Sonnet 5 562cf91f1e feat: spi_host_bridge_v3.v, SPI opcode re-audit against real V3 RTL (EXP-0072)
Forked from V2's spi_host_bridge.v after finding two real protocol
mismatches: WRITE_JOB carried dependency-manager fields (required/
producer_ids) that neural_director_packed.v's job_in_* port doesn't
have (no dependency manager exists in V3 -- dropped, disclosed, not
silently ignored), and WRITE_MEM/READ_MEM assumed a word-granularity
host-arb port V3 never had (now wired through host_mem_bridge.v,
EXP-0071). Physical SPI layer carried over unchanged.

Verified standalone: 18/18 tests, 0 errors, including a case
exercising the narrower 25-bit MEM_ADDR_WIDTH's own top bit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:56:15 +02:00
micheleandClaude Sonnet 5 786464ee21 feat: host_mem_bridge.v, word<->burst translator for host DDR3 access (EXP-0071)
Closes part of the gap found re-auditing spi_host_bridge.v against V3:
V3 had no host raw-memory-access path into DDR3 at all. This module
translates single-16-bit-word req/wr/addr/wdata/lb_n/ub_n transactions
(spi_host_bridge.v's own WRITE_MEM/READ_MEM shape) into BURST_LEN=8
transactions on the shared arbiter, using the project's existing
DQM-style partial-burst masking technique.

Verified standalone against the SDR SDRAM placeholder: 16/16 tests,
0 errors, including cross-word-corruption checks on every burst
offset. Not yet wired into the N=2 system or driven by real SPI
opcode decode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:53:23 +02:00
micheleandClaude Sonnet 5 598feb975b feat: first genuine N=2 multi-core system verified against real DDR3 (EXP-0070)
Swapped mig_native_adapter.v + sdram_arbiter_n.v into the full N=2
system (neural_director_packed.v + 2x packed_slot.v), replacing the
SDR SDRAM placeholder used since EXP-0057. Verified against the real
Vivado-generated ddr3_model.sv end-to-end: 8/8 positions bit-exact
against golden model, 0 errors, real JEDEC command traffic observed.

This is the first fully real V3 system-level correctness result:
real packed DSP cores + real weight-reuse scheduling + real N-way
arbitration + real DDR3 timing, all verified together.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:51:43 +02:00
micheleandClaude Sonnet 5 afff0c4f02 test: fix tb_sdram_arbiter_n.v hang, root-caused as testbench bug (EXP-0069)
TEST2 fired all 3 simulated requesters' one-shot req pulse
unconditionally, not waiting for each one's own grant -- an
unrealistic stimulus that doesn't match packed_slot.v's real
S_MEMWAIT usage (wait for grant, then fire). Rewrote with parallel
fork branches, each waiting for its own req_grant first, still
exercising the real simultaneous-activation contention case.

7/7 PASS, 0 errors. sdram_arbiter_n.v is now genuinely verified, not
just written.

Full writeup in hardware/v2/logs/experiments.log EXP-0069.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:44:46 +02:00
micheleandClaude Sonnet 5 e25e4a1506 feat: real DDR3 memory path verified against MIG's own ddr3_model.sv (EXP-0068)
New hardware/v3/rtl/mig_native_adapter.v: adapts this project's
established req/wr/addr/wdata/wmask->rdata/ready/busy contract to the
real MIG 7-series native app interface (app_addr/app_cmd/app_en,
app_wdf_data/app_wdf_mask/app_wdf_wren/app_wdf_end, app_rd_data/
app_rd_data_valid/app_rd_data_end), derived from this project's own
real generated mig_7series_0.v port widths, not assumed. Runs in the
ui_clk domain (MIG's own generated clock becomes this project's
system clock going forward).

Verified against MIG's own real, vendor-shipped DDR3 behavioral model
(ddr3_model.sv) via real Xilinx xsim/xvlog/xelab (UNISIM primitives
in MIG's PHY require this over Verilator): 12/12 write-then-read-back
transactions bit-exact, 0 errors, real JEDEC command sequence observed
(Activate/Write/Read/Precharge). Confirms the app_cmd encoding and
burst/beat sequencing on first real test.

Also adds hardware/v3/rtl/sdram_arbiter_n.v (generalized N-way
arbiter, generalizing EXP-0066's 2-way version for N>2 scaling and a
future host-access requester) -- its own isolated test currently
HANGS, root cause not yet found, do not trust this module yet
(disclosed, not hidden).

Full writeup in hardware/v2/logs/experiments.log EXP-0068.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:43:22 +02:00
micheleandClaude Sonnet 5 09fbf01ea5 feat: first genuine N=2 multi-core system, two real bugs found+fixed (EXP-0066)
New sdram_slot_arbiter2.v + tb_np_director_n2_system.v: real
neural_director_packed.v dispatching to 2 real packed_slot.v
instances sharing one real SDRAM controller. Jobs submitted one at a
time through the Director's own producer interface -- the Director's
own scheduling decisions determine slot assignment here, unlike every
prior V3 test.

Bug 1 (real, structural): the arbiter's first design registered its
grant one cycle late; layer_prefetch_ctrl.v's ctrl_req is a one-shot
pulse with no retry (every prior use wired it directly to a
controller, never behind arbitration), so a slot's first request
could be silently lost, hanging it forever. Fixed with a new
S_MEMWAIT state in packed_slot.v (wait for a combinational mem_grant
before ever pulsing layer_prefetch_ctrl's start) and a combinational-
first grant in the arbiter.

Bug 2 (testbench): node_id used a stray bit-slice (li[15:8]) instead
of a real multiply, making every layer produce the same node_ids and
silently checking results against the wrong layer's golden value.
Fixed.

Result: 12/12 PASS, 0 errors, real concurrent execution across both
slots (slot 0: positions {0,1,4,5,8,9}, slot 1: {2,3,6,7,10,11}).

Also noted (user correction): the SDR SDRAM controller used
throughout this memory path is a declared placeholder -- the real
target is DDR3 on a custom XC7A100T board, not yet built.

Full writeup in hardware/v2/logs/experiments.log EXP-0066.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 00:16:47 +02:00
micheleandClaude Sonnet 5 124a0dbca0 feat: packed_slot.v, real per-slot sequencer FSM (EXP-0065)
Promotes EXP-0062's own procedural testbench sequence (prefetch ->
buffer swap -> per-tile gather -> operand streaming -> result
capture) into real synthesizable RTL, wrapping layer_prefetch_ctrl.v
-> layer_weight_buffer.v -> weight_tile_gather.v ->
neural_processor_packed.v behind a 9-state FSM matching
neural_director_packed.v's own per-slot contract.

First run: 4/9 failed, deterministic. Root-caused (not a sequencer
bug): the testbench's own w_base computation wrongly treated it as a
byte address needing *2 conversion; layer_prefetch_ctrl.v expects a
word address directly, and packed_slot.v already passes it through
unconverted to match. Fixed the testbench.

Re-verified: 9/9 PASS, 0 errors, bit-exact results and correct
node_id/result_addr passthrough, entirely self-sequenced (no
testbench-side procedural driving of the sub-modules).

Full writeup in hardware/v2/logs/experiments.log EXP-0065.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 00:02:35 +02:00
micheleandClaude Sonnet 5 71600096f9 feat: neural_director_packed.v, job-pairing scheduler for packed cores (EXP-0064)
Forked from neural_director.v (M5): dispatches PAIRS of queued jobs
(sharing w_base+n_tiles) to packed-core slots instead of one job per
slot, matching neural_processor_packed.v's A/B job structure. If the
two oldest queue entries don't share w_base/n_tiles, the Director
stalls (never mis-pairs) -- a disclosed scope limitation, not hidden.

Isolated testbench with behavioral per-slot stubs (same DEC-0007 scope
decision as tb_neural_director.v). First run: 3/7 tests failed --
investigated each, root-caused as testbench timing bugs (checking
dispatch state before the Director's own FSM had caught up, and a
held-too-long job_in_valid making push counts ambiguous), not Director
bugs. Fixed the testbench, re-verified: 8/8 PASS, 0 errors.

Full writeup in hardware/v2/logs/experiments.log EXP-0064.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-16 23:52:19 +02:00
micheleandClaude Sonnet 5 9ca180a787 test: first real end-to-end weight-reuse -> packed-core integration, bug found+fixed (EXP-0062)
New hardware/v3/sim/tb_np_packed_layer_reuse.v: real SDR SDRAM ->
layer_prefetch_ctrl.v -> layer_weight_buffer.v -> weight_tile_gather.v
-> neural_processor_packed.v, ALL real synthesizable RTL (unlike
EXP-0058, which still had a testbench-only gather step).

First run: 15/16 PASS, 1 FAIL. Root-caused (not re-run away): a
testbench handshake bug, not a DUT bug -- operand_valid was held one
extra clock edge after each accepted tile, double-consuming stale
data every tile on every pair. 15 of 16 "passed" only because this
test's saturating outputs happened to clamp to the same value whether
or not the accumulator was inflated -- disclosed as a real methodology
risk, not swept under the rug. Fixed by dropping operand_valid the
same delta the handshake is observed.

Re-verified after the fix: 16/16 PASS, 0 errors, bit-exact against an
independent golden model, through the complete real RTL path.

Full writeup in hardware/v2/logs/experiments.log EXP-0062.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-16 23:41:35 +02:00
micheleandClaude Sonnet 5 5c127fb069 feat: weight_tile_gather.v, real synthesizable byte-to-tile adapter (EXP-0061)
Closes the gap EXP-0058 left testbench-only: assembling P_IN
sequential layer_weight_buffer.v byte reads into one weight_data
tile bus, as real RTL instead of a testbench driver task. Avoids the
runtime-indexed-part-select anti-pattern already found and fixed once
in neural_director.v (ERR-0027-class Fmax collapse) by using a fixed
shift-concat instead.

Verified in isolation against a real, unmodified layer_weight_buffer.v:
37/37 tests, 0 errors, bit-exact across sequential, back-to-back, and
non-sequential/repeated (real reuse-position-style) access patterns.

Full writeup in hardware/v2/logs/experiments.log EXP-0061.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-16 23:34:09 +02:00
micheleandClaude Sonnet 5 1cbe7b85d5 v3: real Artix-7 compute core -- DSP48 packing verified, 2 real bugs found and fixed
New hardware/v3/ (Artix-7 port, branch v3-artix7): the compute engine
that makes the 100x-vs-ESP32 target theoretically reachable on
XC7A100T's 240 DSP48E1 budget.

mac2_dsp_packed.v: packs 2 INT8 MACs sharing one resident weight into
a single DSP48-shaped 25x18 multiply, exploiting this project's own
weight-stationary reuse pattern (layer_weight_buffer.v, EXP-0057/0058)
where one weight is genuinely multiplied against many different
activations. Verified exhaustively: 16,777,216/16,777,216
(weight,x0,x1) combinations, 0 errors.

Two real bugs found and fixed during that verification (both purely
arithmetic/RTL, not toolchain-related):
1. An off-by-one in a declared wire width caused Verilog's part-select
   unsigned-by-default rule to corrupt sign extension on the upper
   product field -- ~50% of vectors failed.
2. After fixing (1), still ~50% failed: concatenating two independently
   sign-extended fields ({sext(x1,9), sext(x0,16)}) is NOT equivalent
   to the real arithmetic sum x1*2^16+x0 whenever the lower field is
   negative (its own two's-complement encoding "bleeds" an extra 2^16
   into the concatenated value). Fixed by building the packed operand
   with an explicit arithmetic shift-and-add instead of concatenation.

neural_processor_packed.v: full port of hardware/v2/rtl/
neural_processor.v's pipeline (same stage count/structure), doubled on
the accumulator/bias/activation/saturation side to process two
weight-reuse positions per weight-tile stream. Verified against TWO
real hardware/v2/rtl/neural_processor.v instances (job A / job B, same
shared weight, independent activations) -- 18/18 PASS, 0 errors,
covering the functional sweep, INT8 extremes, and back-to-back jobs.

A third real bug found in the process (in the new testbench, not the
RTL): clearing operand_valid/tile_last in the same simulation delta as
the handshake edge that should register tile_last=1 races against the
DUTs' own FSM evaluation of that same edge -- the same pulse-clearing
race class found three times already today in hardware/v2/sim (EXP-0058
and its follow-up commits). Fixed the same way: hold the pulse past the
edge with a real time delay (#1) before clearing.

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