chore: remove root-level V1 duplicates, superseded by hardware/v1/ freeze

hardware/v1/ was created (dc0b331) as a frozen snapshot of the V1
project that then lived at the repo root (rtl/, sim/, synth/, tools/,
docs/). Root received zero further commits to those files after the
freeze -- confirmed byte-identical to the hardware/v1/ copy for every
file removed here. Root was the "before", hardware/v1/ is the
curated, canonical "after".

Removed (all verified exact-hash duplicates of hardware/v1/ content):
  - rtl/ (20 files, 100% covered by hardware/v1/rtl/)
  - tools/{netasm,pinout,run_regression.py,flash_catalog,validation,
    fpga_benchmark.py} (19 files, 100% covered by hardware/v1/tools/;
    tools/neural_sim/ kept -- unique, post-freeze, no counterpart)
  - sim/*.v (47 testbenches, 100% covered by hardware/v1/sim/; the
    ~38 remaining sim/ entries are compiled binaries and .vcd
    waveform dumps, left as a separate cleanup decision)
  - synth/ecp5/{p2,p4,p8,post_fix_verify} (25 files, exact duplicates
    of hardware/v1/synthesis/; the other ~84 synth/ecp5/* experiment
    build directories are historical artifacts never carried into the
    freeze, left as a separate decision)
  - WORKLOG.md (duplicate of hardware/v1/docs/WORKLOG.md)
  - docs/{FPGA-Neural-Datapatch-Benchmark,FPGA-Neural-Hardware-Design,
    FPGA-NeuralNetwork-Engine}.md, docs/validation/*.md (18 files),
    docs/FPGA-Neural-Datasheet-{EN,IT}.pdf -- all exact duplicates of
    hardware/v1/docs/ content
  - hardware/v1/docs/DatasheetLatex/ (24 files) -- exact duplicate of
    hardware/v2/docs/datasheet/files/docs/datasheet/en/ (discovered
    during this audit; not the same DatasheetLatex already removed
    from hardware/v2/docs/ in an earlier commit)

Moved (genuine, unique, post-freeze V2 content -- not duplicated
anywhere, just living in the wrong/legacy root docs/ location):
  - docs/architecture/*.md -> hardware/v2/docs/architecture/
  - docs/pinouts.md, docs/FPGA_NEURAL_V2_DATASHEET.md,
    docs/FPGA_NEURAL_V2_SCHEMATIC.md,
    docs/FPGA-Neural-V2-Datasheet-EN.pdf -> hardware/v2/docs/

Left untouched (separate decisions, not part of this cleanup):
  - docs/FPGA-Neural-Flash-Subsystem-Verification.md, docs/
    v2-description.md -- orphaned root-only content, no duplicate
    found anywhere, but also not part of the reviewed plan
  - synth/ecp5/* experiment dirs and sim/*_sim + sim/*.vcd build
    artifacts -- not literal duplicates, flagged as candidates for a
    future, separate cleanup pass

Verified no functional breakage: grepped all remaining scripts/docs
for references to every removed path -- only prose/comment mentions
found, no executable imports or build-script paths broken.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-07 05:30:17 +02:00
co-authored by Claude Sonnet 5
parent e1cefd13db
commit 81a9619214
184 changed files with 0 additions and 1023781 deletions
@@ -0,0 +1,92 @@
# NMS Activation Fill Controller Timing (STEP14 Part B)
Status: fixed, real post-P&R verified, bit-exact, adopted. Full data:
`hardware/v2/reports/step14_activation_timing.csv`. Full narrative:
`hardware/v2/logs/experiments.log` (EXP-0029, 0030, 0031),
`decisions.log` (DEC-0026, DEC-0027).
## B1 — Exact critical path (not assumed)
Mined directly from the real nextpnr-ecp5 P&R report for
`nms_neural_multiprocessor_stream.v` at N_SLOTS=4
(Fmax=55.22 MHz, FAIL @ 80 MHz). Full path, 18.11 ns total (6.25 ns
logic + 11.85 ns routing):
```
SOURCE: u_act_fill.resident_tag[11] (register Q)
-> COMBINATIONAL, chained, NO register in between:
(1) max_n_tiles computation, nms_activation_fill_ctrl.v:92
(N_SLOTS-wide running-max fold, each iteration gated by a
23-bit tag-equality check) -- long CCU2C carry chain
(2) resident_count < max_n_tiles comparison, line 165
(the ST_IDLE refill/continue decision) -- ANOTHER 16-bit
magnitude-comparison carry chain, feeding directly off (1)
in the SAME cycle
(3) into pf_start's own next-state logic
DESTINATION: u_act_fill.pf_addr's clock-enable (CE) pin
```
Two full 16-bit magnitude comparisons sit in **one** combinational
cone across **one** clock edge. This confirms, at the exact RTL-line
level, the failure class DEC-0016/EXP-0022 predicted analytically
("O(N_SLOTS) unpipelined combinational scan feeding directly into a
control decision") — but precisely localizes it to the comparison
logic (lines 92 and 165), *not* the priority-encoder
(`desired_valid`/`desired_x_base`, lines 77-86), which does not appear
in this critical path at all.
## B2 — Scaling behavior
The bottleneck is the `max_n_tiles` running-max fold: an imperative
`for` loop creates a data dependency between iterations (`max_n_tiles`
after iteration *i* depends on iteration *i-1*), which Yosys
synthesizes as a sequentially-chained carry structure — inherently
O(N_SLOTS) deep, not O(log N_SLOTS). At N_SLOTS=4 the chain reached
6.25 ns logic + 11.85 ns routing; at N_SLOTS=8 it doubles again (see
below).
## B3 — Minimum fix (two iterations, evidence-driven)
**v2** (one pipeline stage: register `max_n_tiles` before its use in
the `resident_count` comparison): Fmax 55.22 → 72.78 MHz (+31.8%) —
real improvement, still fails 80 MHz. Re-tracing showed the *remaining*
critical path was entirely inside `max_n_tiles`'s own computation
(now feeding its own register), confirming the fix needed to go one
level deeper.
**v3** (second stage: register each slot's tag-equality/masking result
first — independent per-slot work, no N_SLOTS-dependent chain — *then*
fold the already-registered, already-masked values): Fmax 55.22 →
**106.81 MHz** (+93.4%). **PASSES** 80 MHz with real margin. Resource
cost: LUT4 -5.5%, FF +1.4% (2 added pipeline registers), CCU2C
unchanged.
## B4 — No serialization reintroduced
Verified directly: N_SLOTS=2 bit-exact regression test (D-Stress, real
V1 PSRAM chain) gives **numerically identical** cycle count and
sustained MAC/cycle before and after the fix (185270/185270 cycles,
0.1769/0.1769 MAC/cycle). The 3 total cycles of added latency apply
only to the rare, tile-refill-boundary-only decision — never to the
real-time per-tile consumption path (already fully decoupled by
STEP13's own streaming manager). Higher Fmax, zero throughput cost —
satisfying B4's explicit requirement.
## N=8 (exploratory)
`nms_activation_fill_ctrl_v3.v` at N_SLOTS=8: DSP=64/72 (89%, FEASIBLE),
LUT4=4653, FF=10855 (both comfortably FEASIBLE). **Fmax=52.25 MHz,
FAILS 80 MHz** — the v3 fix's second stage (the max-fold itself) is
still O(N_SLOTS)-deep; at N=8 it is twice as deep as at N=4 and becomes
dominant again. This is expected: v3 shifted the crossover point, it
did not eliminate the underlying dependency. A genuine balanced-tree
reduction (or a pipeline scaling with log₂(N_SLOTS) rather than a flat
2-stage split) would be required for N=8 — not undertaken this round
(N=8 is explicitly exploratory; the limiting resource (Fmax, not
DSP/LUT/FF/BRAM) is precisely identified and quantified, per spec).
## Adoption
`nms_activation_fill_ctrl_v3.v` is adopted as the reference activation
fill controller for N_SLOTS≥4 configurations (DEC-0027). The original
and the insufficient v2 are preserved for reference.
@@ -0,0 +1,122 @@
# NMS Continuous Tile Stream — Memory Manager Redesign (STEP13)
Status: implemented, bit-exact verified, synthesized. **Adopted** as
the new reference NMS memory-manager configuration (DEC-0025). Full
data: `hardware/v2/nms/reports/batch_processor_{sweep.csv,summary.md}`.
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0025 through
EXP-0028), `decisions.log` (DEC-0024, DEC-0025).
## Why this file is not `neural_processor_batch.v`
The governing brief for this STEP asked for a "batch/continuous
neuron execution model" — multiple neurons processed per dispatch, or
a continuous neuron stream — to amortize the ~68.5-cycles/neuron
non-memory floor found in EXP-0024. Before writing any RTL, Step 1
required tracing the actual RTL to find exactly where those cycles
go, rather than assuming.
That trace (EXP-0025, an isolated testbench with `neural_processor.v`
+ `nms_memory_manager_pf.v` driven with zero real memory latency
anywhere) found: **93.4% of the floor is explained by a 4-cycles/tile
serialization bug inside the memory manager's own `ST_RUN` state**,
not by per-job dispatch overhead (only 6.6%). `ST_RUN` implements
operand delivery as a strictly sequential chain —
`read_issued → read_ready → present → consumed` — with zero overlap
between consecutive tiles, even though:
- the local activation/weight SRAMs (`nms_activation_replicated.v`,
`nms_weight_packed.v`) have only a 1-cycle `rd_en`-to-data latency;
- `neural_processor.v`'s own `operand_ready` is held continuously high
through the whole tile-loading phase — its datapath is explicitly
designed (per its own header comment) to accept a new tile every
cycle while previous tiles drain through the adder tree/accumulator.
Neither side of this interface requires 4 cycles/tile. It is purely
an artifact of the memory manager's own un-pipelined FSM. **The fix is
therefore a continuous per-tile streaming redesign of the memory
manager, not a neuron-batching scheme — hence
`nms_memory_manager_stream.v`, not `neural_processor_batch.v`.**
`neural_processor.v` itself required no modification.
## Design: `nms_memory_manager_stream.v`
Drop-in replacement for `nms_memory_manager_pf.v` (identical external
interface, same `weight_prefetch_engine.v` instance, same outer job
FSM `ST_IDLE`/`ST_WAIT_RESULT`/`ST_WRITE_RES`/`ST_DONE`). Only
`ST_RUN`'s internal operand-delivery logic differs:
- `rd_ptr` — the read-**issue** pointer (which tile's SRAM read has
been, or is about to be, issued), independent of and normally one
tile ahead of `tile_idx` (the **consumption** pointer, i.e. how many
tiles `neural_processor.v` has actually accepted).
- A 1-deep skid buffer (`buf_valid`/`buf_input`/`buf_weight`/
`buf_last`) holds one tile's fully-read SRAM data, presented to NP
as `operand_valid`/`input_data`/`weight_data`/`tile_last`.
- Every cycle: if a read issued last cycle is landing now (1-cycle
SRAM latency), it's captured into the skid buffer; independently, a
new read is issued for `rd_ptr` whenever legal (in bounds, weight +
activation ready) **and** the buffer will not overflow (empty, or
being drained this same cycle).
Since `operand_ready` stays high throughout the tile-loading phase,
the skid buffer drains every cycle it's full, so a new read can be
issued every cycle too — sustained ~1 cycle/tile, down from 4.
`tile_idx` (the consumption pointer) is still what feeds
`weight_prefetch_engine.v`'s own `consumed_count` port — its external
contract is unchanged; only the local SRAM read-issue pointer
(`rd_ptr`) is new, and it can run up to one tile ahead of `tile_idx`
(the skid buffer's own depth).
## Verification chain (all real, none assumed)
1. **EXP-0025**: isolated zero-latency trace of the *old* design —
established the 4-cycles/tile floor and its 93.4% share of
EXP-0024's real measured floor.
2. **EXP-0026**: same isolated trace against the *new* design — the
fix works exactly as designed (confirmed cycle-by-cycle), but
total cycles barely move (81→80), because it immediately hits a
*second*, previously-masked bottleneck: `weight_prefetch_engine.v`'s
own word-fetch rate is *also* exactly 4 cycles/tile (P_IN=8 bytes ÷
16-bit bus = 4 word-transactions, 1 cycle/word minimum even at
zero real latency) — a bus-**width** ceiling, structurally
different from an FSM-serialization ceiling, that happens to
coincide numerically today.
3. **EXP-0027**: a direct control experiment — a scratch variant with
weight-fetch bypassed (always-ready) isolates the new design's
*own* ceiling: a clean 1 cycle/tile (100% of `neural_processor.v`'s
theoretical per-tile rate), vs. the old design's hard 4-cycles/tile
cap under the identical bypass. This is the direct proof that the
fix removes a real, 4× architectural ceiling — it was just masked
by a coincidentally-equal second bottleneck.
4. **EXP-0028**: full real-system integration
(`nms_dataflow_core_stream.v``nms_neural_multiprocessor_stream.v`,
real V1 PSRAM chain) — bit-exact PASS, 256/256 neurons, D-Stress
workload identical to EXP-0022/0024. Real cycle count: 185270 vs.
185398 (`_pf` baseline), -0.07% — confirms the "masked, zero net
benefit today" prediction exactly. Real synthesis + P&R: N=1
Fmax=142.92 MHz (+3.7% vs. baseline), N=2 Fmax=92.57 MHz (-2.8%,
still comfortably above 80 MHz), resource cost within ±6%. N=4:
55.22 MHz, FAILS 80 MHz — but for the *pre-existing*,
already-documented `nms_activation_fill_ctrl.v` priority-scan
regression (EXP-0022), unrelated to and unaffected by this fix.
## Outcome and adoption
**Outcome B** (helps, but another bottleneck appears — see
DEC-0025 and `batch_processor_summary.md` for the full nine-question
final decision). `nms_memory_manager_stream.v` is adopted as the new
reference configuration: it is a strict improvement (bit-exact,
resource-neutral, no measured downside) and is **required groundwork**
for any future PSRAM bandwidth increase to actually translate into a
throughput gain — without it, a wider/faster memory would immediately
hit the old FSM's 4-cycles/tile ceiling and realize only 25% of its
potential benefit. The original `nms_memory_manager.v` and
`nms_memory_manager_pf.v` remain preserved, unmodified, for A/B/C
reference. Neuron-batching (the brief's original Model B/C) was not
pursued — evidence showed it addresses only 6.6% of the real floor and
would deliver no measurable benefit today for the identical reason
(weight-fetch-rate-bound). N=4/N=8 viability remains blocked by two
independent issues neither addressed by this STEP: external PSRAM
bandwidth, and the activation fill controller's own Fmax regression —
both flagged as future work.
@@ -0,0 +1,145 @@
# NMS Real Weight Prefetch Engine (STEP11)
Status: implemented, bit-exact verified, benchmarked against the real
V1 PSRAM chain, synthesized. **Not adopted as the default NMS
configuration** — see Outcome/Recommendation below. Full data:
`hardware/v2/nms/reports/nms_prefetch_sweep.csv`,
`nms_prefetch_summary.md`; full narrative:
`hardware/v2/logs/experiments.log` (EXP-0023, EXP-0024),
`decisions.log` (DEC-0023), `errors.log` (ERR-0015).
## Problem
The "Current NMS" baseline (`nms_memory_manager.v`, backed by
`prefetch_engine.v`) measured `prefetch_effectiveness≈0%` and
`weight_stall≈92.5%` at N_SLOTS=2 (EXP-0022). Tracing the actual RTL
(not assuming from filenames) showed the real gap: `prefetch_engine.v`
is a single-shot FSM (`ST_IDLE`/`ST_READ_W`/`ST_DONE`) that can only
have **one fetch in flight at a time**, and `nms_memory_manager.v`'s
own restart logic only re-triggers the next tile's fetch once the
*previous* tile's fetch has fully completed and the FSM has returned
to idle — paying a real per-tile control-plane restart cost on every
tile boundary. The gap was never insufficient lookahead *distance*
(the old design already tried to fetch as far ahead as `n_tiles`
allowed); it was zero *outstanding-request depth*.
## Real backend constraint
`memory_interface.v``psram_controller.v` (V1, reused verbatim,
never modified) is a fire-and-forget, **one-transaction-in-flight**
protocol: a single `mem_req` pulse, wait for `mem_ready`, and that IS
the whole transaction. No wire-level pipelining is physically possible
against a real single PSRAM port. So "multiple outstanding requests"
cannot mean multiple simultaneous word transactions — it means
eliminating the *control-plane* overhead paid at every tile boundary
and letting the fetch stream run continuously across tiles, queueing
up to `PREFETCH_DISTANCE` tiles of lookahead ahead of consumption.
## Design: `weight_prefetch_engine.v`
Two monotonic counters fully describe the engine (tiles are always
fetched in strict sequential order, never reordered or re-fetched, so
no per-tile state array is needed):
- `fetch_tile`/`fetch_word` — the next word to request (or the word
currently in flight).
- `ready_count` — tiles 0..`ready_count`-1 are fully resident in the
weight SRAM.
`consumed_count` (the consumer's own tile index, `nms_memory_manager_pf.v`'s
`tile_idx`) bounds a configurable lookahead window:
`window_limit = consumed_count + PREFETCH_DISTANCE`; the engine may
fetch tile K only if `K < n_tiles` **and** `K < window_limit`.
The core mechanism: on `mem_ready && req_outstanding`, the just-completed
word is committed **and**, in the same cycle, the very next request is
issued — either the same tile's next word, or (at a tile boundary) the
next tile's first word — giving zero-gap streaming across tile
boundaries against a backend that only ever has one word in flight.
(An earlier draft used mutually-exclusive `if/else-if` branches for
"commit" vs. "issue next", which reintroduced a 1-cycle gap between
*every* word, not just tile boundaries; fixed by merging both into one
branch — see `weight_prefetch_engine.v`'s own header comment.)
## Integration: the "_pf" A/B variants
Per the explicit "preserve the current NMS baseline" constraint, the
new engine was integrated into parallel `_pf`-suffixed files, leaving
the originals untouched:
- `nms_memory_manager_pf.v` — drop-in replacement for
`nms_memory_manager.v`'s external interface; internally swaps the
private `prefetch_engine.v` instance for `weight_prefetch_engine.v`,
and changes `can_present`'s weight-ready check from
`tile_idx < wgt_fetched` to `tile_idx < wgt_ready_count`.
- `nms_dataflow_core_pf.v` — mirrors `nms_dataflow_core.v`, adds a
`PREFETCH_DISTANCE` parameter, instantiates `nms_memory_manager_pf`.
- `nms_neural_multiprocessor_pf.v` — mirrors
`nms_neural_multiprocessor.v`, instantiates `nms_dataflow_core_pf`.
Both the baseline (`nms_neural_multiprocessor.v`) and the prefetch
variant (`nms_neural_multiprocessor_pf.v`) remain in the repository
side by side; neither supersedes the other.
## Verification
`hardware/v2/nms/sim/tb_weight_prefetch.v` — isolated correctness
testbench: real `sim_word_mem` (configurable extra latency), real
`nms_weight_packed.v` production SRAM, bit-exact fill-pattern checking.
Covers `n_tiles ∈ {0,1,2,PFD,PFD+1,MAX_TILES-1,MAX_TILES}`, back-to-back
jobs with no explicit reset, a dedicated windowing-cap test (frozen
consumer, confirms `ready_count` stops exactly at
`min(PFD,MAX_TILES)`), and (post-ERR-0015) a large-PFD regression case.
10/10 (9/9 at PFD≥MAX_TILES) tests pass bit-exact across
PFD∈{1,2,4,8,32} and under injected extra memory latency.
`hardware/v2/nms/sim/tb_nms_dstress_pf.v` — full real-integration
benchmark: identical D-Stress workload/golden-model/correctness
criteria as `tb_nms_dstress.v` (EXP-0022), instantiating
`nms_neural_multiprocessor_pf` with a `PFD_CFG` parameter, plus new
testbench-only instrumentation for `weight_stall_cycles` and
`prefetch_effectiveness` (tiles consumed with zero weight-blocking
cycles beforehand / total tiles consumed — the exact STEP11
definition). All runs pass 256/256 neurons bit-exact vs. the golden
model.
## ERR-0015: a real bug found and fixed
The initial `window_limit` computation truncated the
`PREFETCH_DISTANCE` *parameter itself* to `CNTW` bits
(`PREFETCH_DISTANCE[CNTW-1:0]`) before adding it to `consumed_count`.
At `MAX_TILES=16` (`CNTW=5` bits), `PFD=32` truncates to 0, making
`window_limit == consumed_count` forever and deadlocking the engine
completely (0/256 neurons ever completed, 0% PSRAM utilization).
Fixed by computing `window_limit` and its comparisons in a fixed
32-bit width, using the untruncated parameter value. Regression-tested
in `tb_weight_prefetch.v`. Full writeup: `errors.log` ERR-0015.
## Results and outcome
See `nms_prefetch_summary.md` for the full comparison table and the
nine explicitly-answered final-report questions. In short:
- **N_SLOTS=1** (no port contention): a real, reproducible **-10.3%**
cycle-count improvement (PFD=1 → PFD≥2), then a complete plateau —
deeper buffering gives zero further benefit. Sustained MAC/cycle
reaches only 2.8% of the theoretical target.
- **N_SLOTS=2** (this project's own primary reference configuration,
real shared-port contention via `slot_mem_arbiter`): **zero
measurable benefit** at any PREFETCH_DISTANCE from 1 to 16 — all
runs are statistically indistinguishable from each other and from
the pre-STEP11 baseline. The single physical PSRAM port is already
saturated (90.5% busy, unchanged from baseline) by natural two-slot
contention before any lookahead scheme can act.
**Final decision: Outcome B (N_SLOTS=1, partial) / Outcome C
(N_SLOTS=2, failure against the 90% criterion).** The mechanism is
correct and does measurably hide latency when the port has spare
capacity; it cannot manufacture bandwidth out of an already-saturated
single physical port. Reaching the STEP11 target would require ~36×
(N=1) to ~82× (N=2) more real PSRAM bandwidth — a hardware-level
constraint, not an RTL-scheduling one. Per DEC-0023, the new engine is
**not** recommended as the default NMS configuration; both variants
are preserved for reference. The evidence-backed next step (real PSRAM
bandwidth — wider bus, multiple independent banks, or a faster backing
technology) is flagged as future work, not undertaken this round.
@@ -0,0 +1,91 @@
# NMS Weight Datapath Scaling (STEP14 Part A)
Status: architectural requirement established and proven (simulation),
**not realizable on real hardware today** (fixed 16-bit physical
PSRAM). Full data: `hardware/v2/reports/step14_weight_scaling.csv`.
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0032, EXP-0033),
`decisions.log` (DEC-0028).
## Question answered
*At what weight-path width does the processor stop being fundamentally
starved by weight delivery?* **64 bits** — exactly `P_IN × DATA_WIDTH`
(8 × 8). Proven by direct cycle-exact simulation, not assumed.
## What was built
`weight_prefetch_engine_wide.v` — a parameterized (`MEM_DATA_WIDTH`)
generalization of the real `weight_prefetch_engine.v`'s continuous
cross-tile-boundary streaming design, simulation-only/exploratory
(same status as `ideal_memory_model.v`). `WORDS_PER_TILE =
ceil(P_IN*DATA_WIDTH / MEM_DATA_WIDTH)`, clamped to a minimum of 1.
`nms_memory_manager_stream_wide.v` pairs it with STEP13's own streaming
memory manager unchanged (A2's requirement), on a *separate* logical
wide port from the real 16-bit result-write-back port.
A real bug was found and fixed during development: address stepping
initially used `WORDS_PER_TILE × BYTES_PER_WORD` as the inter-tile
byte stride, which over-counts whenever the bus is wider than one full
tile (the 128-bit case, `WORDS_PER_TILE=1` but `BYTES_PER_WORD=16`
while the tile itself is only 8 bytes) — this skips over the next
tile's actual data in the packed backing store. Fixed by defining
`TILE_BYTES = TILE_BITS/8` as the canonical, width-independent stride.
## Results (bit-exact + ideal-memory cycle count)
| Width | Words/tile | Steady-state cycles/tile | 16-tile job total |
|---|---|---|---|
| 16-bit | 4 | 4 | 80 |
| 32-bit | 2 | 2 | 48 |
| **64-bit** | **1** | **1** | **32** |
| 128-bit | 1 | 1 | 32 |
All four widths pass bit-exact correctness (9/9 tests each, including
under injected extra memory latency). 64-bit achieves a clean,
cycle-exact **1 cycle/tile** — 100% of `neural_processor.v`'s own
theoretical per-tile acceptance rate, exactly matching the streaming
memory manager's own ceiling (EXP-0027, STEP13). 128-bit gives **zero**
further benefit: a bus wider than one full tile still delivers exactly
one tile per transaction in this single-tile-per-request design (no
multi-tile bursting was attempted).
## The critical distinction: logical vs. physical bandwidth (A5)
STEP14 explicitly warned against assuming a wider logical interface
means the real memory can deliver it. It cannot, here: **the real V1
PSRAM chain is fixed at 16 bits** — a real chip
(ISSI IS66WVE4M16EBLL-70BLI, x16), not an RTL parameter. The
already-existing, already-verified `weight_prefetch_engine.v` (real,
used throughout STEP11-13) *is* exactly what a "64-bit logical / 16-bit
physical" packing adapter would produce: it assembles one 64-bit
logical tile from 4 real sequential 16-bit word transactions. Its real,
repeatedly-measured result is 4 cycles/tile — identical to the ideal
16-bit row above, because the real transaction count is unchanged
regardless of what the logical interface upstream claims. **A logical
wide interface backed by a physically-narrow bus delivers exactly the
narrow bus's own throughput.** No new "packing adapter" module was
built for this reason — the real engine already demonstrates the
answer, conclusively, without further RTL.
## Answer to the primary research questions
- **Is 16-bit weight delivery fundamentally insufficient for P_IN=8?**
Yes — it costs 4 cycles/tile, 4× the achievable minimum.
- **Is 32-bit enough?** No — still 2× the achievable minimum (2
cycles/tile).
- **Is 64-bit the natural architectural point?** Yes, exactly — proven
cycle-exact, not approximate.
- **Does wider logical delivery actually improve real throughput?**
**Not on this hardware.** Realizing the 64-bit ideal requires a
matching *physical* bandwidth increase (a real 64-bit-wide external
bus, or multiple parallel 16-bit PSRAM chips banked together) — a
board/silicon-level change, outside this project's own RTL scope.
## Recommendation
The 64-bit requirement is now precisely quantified and should inform
any future hardware revision (wider PSRAM, multiple banks). No RTL
change is warranted on the current board: `weight_prefetch_engine.v`
(real, 16-bit) remains the correct, already-optimal implementation
given the fixed physical bus width — STEP13's streaming-manager fix
already extracts everything available from the real interface.