docs: sync physical/architecture docs with EXP-0081/0082 reality, add 32-bit vs dual-channel analysis

- PHYSICAL_REALIZATION.md: replace stale "1 tile = 1 burst" layout
  description with the real EXP-0081/0082 "2 tiles = 1 burst" convention;
  add EXP-0082 signoff row and history table.
- ARCHITECTURE_ANALYSIS.md: mark §5.1 (denser activation packing) DONE with
  real re-measured numbers (bandwidth ceiling fraction 25%->50%, WNS
  +0.030->+0.068ns); add §5.4, the real device-data-backed comparison of
  32-bit single-channel widening vs a second independent DDR3 channel
  (decided: 32-bit widening, per real DQS/bank pin-conflict analysis);
  update scaling-path recommendation to reflect the user's final directive
  (widen channel -> build DDRManager -> N=2/4/8/16 tests, N=8 target, N=16
  documentary).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-20 10:07:16 +02:00
co-authored by Claude Sonnet 5
parent e6768623d6
commit cbd16dd727
2 changed files with 237 additions and 98 deletions
+43 -15
View File
@@ -118,7 +118,7 @@ these from an ESP32 doing real JTAG bit-banging (TAP state machine, IR/DR
shifting) rather than a bench programmer — that firmware is separate,
software-side work, not covered here.
## 3. Real timing signoff (EXP-0078, the current, trustworthy number)
## 3. Real timing signoff (EXP-0082, the current, trustworthy number)
Real in-context Vivado place-and-route (not out-of-context, not estimated):
@@ -126,14 +126,23 @@ Real in-context Vivado place-and-route (not out-of-context, not estimated):
|---|---|
| DDR3 PHY clock (sys_clk_i) | **310.078 MHz** (3.225ns period) |
| Compute domain clock (ui_clk, PLL-derived 2:1 from sys_clk_i) | **155.039 MHz** |
| WNS (setup slack) | **+0.013 ns** — real, but very thin. Re-verify with a fresh P&R after ANY further logic addition. |
| WHS (hold slack) | +0.032 ns |
| Failing endpoints | 0 / 17473 (setup), 0 / 17470 (hold) |
| LUTs used | 5213 / 63400 (8.22%) |
| DSP48E1 used | 16 / 240 (6.67%) — 8 per compute core × 2 cores |
| WNS (setup slack) | **+0.068 ns** — real, still thin but improved vs EXP-0078/0079. Re-verify with a fresh P&R after ANY further logic addition. |
| Failing endpoints | 0 (setup), 0 (hold) |
| LUTs used | 5437 / 63400 (8.58%) |
| DSP48E1 used | 16 / 240 (6.67%) — 8 per compute core × 2 cores, unchanged since EXP-0059 |
| Block RAM used | 0 |
| STARTUPE2 used | 1 / 1 (100%) — the config-flash bridge |
Signoff history (every real change, same target, in-context P&R):
| EXP | WNS (ns) | LUTs | DSP48E1 |
|---|---|---|---|
| 0074 (first real DDR3 + pins) | +0.040 | 5140 | 16 |
| 0076 (+regfile, +pins, +SPI fix) | +0.056 | 5173 | 16 |
| 0078 (+flash bridge, STARTUPE2) | +0.013 | 5213 | 16 |
| 0079 (+real activation engine) | +0.030 | 5379 | 16 |
| 0082 (+denser activation packing) | **+0.068** | 5437 | 16 |
## 4. Real DDR3 memory layout convention
Both weight data and activation data share the same DDR3 address space
@@ -143,17 +152,36 @@ Both weight data and activation data share the same DDR3 address space
WORDS_PER_LAYER` (`WORDS_PER_LAYER = LAYER_BYTES/2`). Densely packed —
`layer_prefetch_ctrl.v` reads full bursts sequentially into the on-chip
weight buffer once per job.
- **Activations** (real engine since EXP-0079, `act_tile_fetch.v`): **each
tile (P_IN=8 INT8 values) occupies its own full `BURST_LEN=8`-word
(128-bit) burst slot** — the 8 useful bytes sit in the low 64 bits, the
upper 64 bits are unused padding. This is deliberately 2× wasteful of DDR3
capacity, in exchange for needing zero runtime-indexed bit-selects in the
fetch logic (a real Fmax risk this project's thin P&R margin, §3, can't
currently afford). Tile `t`'s word address is `base + t*BURST_LEN`, always
burst-aligned by construction.
- **Activations** (real engine, `act_tile_fetch.v`; current layout is the
**v2 convention, EXP-0081/0082**): **two consecutive tiles (P_IN=8 INT8
values each) share ONE full `BURST_LEN=8`-word (128-bit) burst** — the
even-indexed tile occupies the low 64 bits, the odd-indexed tile occupies
the high 64 bits. Tile `t`'s burst address is `base + (t>>1)*BURST_LEN`
(integer division — two tiles per burst), always burst-aligned by
construction. This **halves** real DDR3 bytes-moved-per-useful-byte versus
the original EXP-0079 "1 tile = 1 burst" layout, which wasted the upper 64
bits of every burst as padding.
- **Why this is timing-safe despite selecting a sub-burst half at read
time**: the tile index's LSB (which half of the burst a given tile
lives in) is known at *request* time, not at response time. It's
latched into a register (`sel_lat`) the same cycle the request is
accepted — many `ui_clk` cycles before the real DDR3 round-trip
completes and `ctrl_rdata` becomes valid. The eventual data-select mux
therefore always selects on an already-long-stable registered bit,
never a bit racing live read data — this is NOT the runtime-indexed-
part-select-on-the-critical-path pattern flagged as a real Fmax risk in
EXP-0061 (that pattern is about a select signal arriving *late*,
simultaneously with the data it gates). Confirmed timing-safe by real
P&R (EXP-0082): margin *improved* from +0.030ns to +0.068ns, not
degraded.
- Real measured effect: back-to-back same-row DDR3 throughput is a fixed
1.24 GB/s (measured, EXP-0080) regardless of packing — this convention
doesn't change that ceiling, it changes how much of it is *wasted* on
padding, doubling the real achievable useful fraction (see
`docs/ARCHITECTURE_ANALYSIS.md` §3 and §5.1).
- `base` (a job's own `x_base_a`/`x_base_b`) is chosen freely by whoever
submits jobs (the SPI host) — just keep each position's own activation
array in its own non-overlapping `N_TILES * BURST_LEN`-word region.
array in its own non-overlapping `(N_TILES/2) * BURST_LEN`-word region.
## 5. FPGA configuration (boot) procedure