feat: real result-writeback engine, removes the last hard N-scaling pin blocker (EXP-0088)
Adds result_writeback.v, one instance per packed_slot.v, writing each completed job's result directly into DDR3 at the job's own result_addr_a/b instead of driving literal top-level pins -- the same architectural shape as the weight-fetch path, in reverse. job_done now means "durably in DDR3", not "captured in a register only a pin could see". n2_system_ddr3_top.v's own s0_result_data_a/b, s1_result_data_a/b top-level package pins are removed (and the now-dangling XDC constraint for them), closing the real, hard scaling blocker docs/ARCHITECTURE_ ANALYSIS.md flagged since EXP-0074/0079 (8 bits x 2 lanes x N cores -> 256 pins at N=16). Addressing reuses the exact same JOB_ADDR_WIDTH->ctrl-bus-word truncation x_base_a/w_base already use (verified against act_tile_ fetch.v's/layer_prefetch_ctrl.v's own real code, not guessed). The host reads results back via the already-existing READ_MEM (0x02) SPI opcode -- no new protocol. A real EXP-0066-class bug (issuing ctrl_req before mem_grant) was caught and fixed before ever compiling, by re-deriving the design against act_tile_fetch.v's own proven S_MEMWAIT/S_GAP sequencing. Verified two ways: tb_packed_slot.v extended with a real DDR3 read-after-write check (9/9 PASS, confirms the write actually landed, not just that job_done pulsed); tb_n2_system_ddr3.v re-run via real xsim to confirm correct behavior under real 2-slot shared-bus arbitration (8/8 PASS, 0 errors, consistent timing with EXP-0087's own baseline for this workload). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -267,16 +267,23 @@ Real, verified, low resource cost, not on any critical performance path
|
||||
(host commands are inherently much slower than the internal compute/memory
|
||||
loop). No bottleneck here. Not a scaling concern.
|
||||
|
||||
### 4.6 Missing: result-writeback engine
|
||||
### 4.6 [DONE, EXP-0088] Result-writeback engine
|
||||
|
||||
Still genuinely absent (disclosed since `packed_slot.v`'s own original
|
||||
header, unchanged through EXP-0079). Currently `result_data_a/b` are literal
|
||||
top-level pins — functional at N=2 (32 pins), but this is the **exact same
|
||||
class of mistake already caught once** for activation data (EXP-0074: ~360
|
||||
pins nearly exceeded the whole package's I/O budget). At N=16 this port
|
||||
alone would need 8 bits × 2 lanes × 16 cores = 256 pins — **a real, hard
|
||||
blocker for any scaling beyond a handful of cores**, independent of the
|
||||
memory-bandwidth ceiling in §3. Recommended fix in §5.3.
|
||||
**No longer absent.** `result_writeback.v` (new module) is instantiated
|
||||
inside each `packed_slot.v`, sharing that slot's own ctrl port with
|
||||
`layer_prefetch_ctrl.v`/`ddr_prefetch_mgr.v` exactly the way those two
|
||||
already share it with each other (mutually exclusive in time by FSM
|
||||
construction — writeback only starts in `S_RESULT`, strictly after the
|
||||
tile loop has finished). Each completed job's result is written directly
|
||||
into DDR3 at the job's own `result_addr_a`/`result_addr_b` — the SAME
|
||||
architectural shape as the weight-fetch path, in reverse, exactly as this
|
||||
section used to recommend before it was built. `job_done` now means "the
|
||||
result is durably in DDR3", not merely "captured in a register only a
|
||||
literal top-level pin could see". `n2_system_ddr3_top.v`'s own
|
||||
`s0_result_data_a/b`/`s1_result_data_a/b` top-level PACKAGE PINS are
|
||||
**removed** — the real, hard N-scaling blocker this section used to flag
|
||||
(8 bits × 2 lanes × N cores → 256 pins at N=16) no longer exists at any N.
|
||||
See §5.3 for the full real design/verification story.
|
||||
|
||||
---
|
||||
|
||||
@@ -433,13 +440,62 @@ exists in this codebase; kept for when it's revisited):
|
||||
reorders/never dispatches that queued job) also wastes real bandwidth —
|
||||
needs a real cancellation/staleness mechanism, not assumed away.
|
||||
|
||||
### 5.3 [Blocking for any real scaling] Result-writeback engine
|
||||
### 5.3 [DONE, EXP-0088] Result-writeback engine
|
||||
|
||||
Must exist before N>2 is even attemptable (§4.6) — result data needs to go
|
||||
into DDR3 (or through the SPI status/register path for small result sets),
|
||||
never as N-scaled literal top-level pins again. Same architectural shape as
|
||||
the weight-fetch path, in reverse (write instead of read) — a reasonable,
|
||||
bounded scope, and a real prerequisite, not optional polish.
|
||||
**Built and real-verified.** New module `result_writeback.v`, one instance
|
||||
per `packed_slot.v` (matching how `layer_prefetch_ctrl.v`/`act_tile_
|
||||
fetch.v`/`ddr_prefetch_mgr.v` are already one-per-slot, not a new
|
||||
arbiter-requester count as N scales). On job completion (`S_RESULT`), it
|
||||
writes BOTH lanes' results into DDR3 at the job's own `result_addr_a`/
|
||||
`result_addr_b` and only THEN asserts `job_done` — real, correctness-first
|
||||
sequencing (`job_done` now means "durably in DDR3", not "captured in a
|
||||
register" the way it used to).
|
||||
|
||||
**Real addressing** (verified against `act_tile_fetch.v`'s/`layer_
|
||||
prefetch_ctrl.v`'s own real address-computation code, not guessed):
|
||||
`result_addr_a/b` arrive in `packed_slot.v`'s own `JOB_ADDR_WIDTH=26`-bit
|
||||
convention; the low `ADDR_WIDTH=25` bits (dropping the unused top/MSB
|
||||
headroom bit) are used directly as a ctrl-bus-native 32-bit-word address
|
||||
— the exact same address space `x_base_a`/`w_base` already live in. One
|
||||
full 32-bit ctrl-word is written per lane: `{node_id[15:0], 8'h00,
|
||||
result_data[7:0]}`. The host reads results back via the ALREADY-EXISTING
|
||||
`READ_MEM` (0x02) SPI opcode — no new protocol. Real, disclosed
|
||||
host-firmware implication (not yet built, same class of gap as this
|
||||
project's other disclosed firmware work, e.g. JTAG bit-banging): reading
|
||||
a result back needs `mem_addr = result_addr[24:0]*2` for the value and
|
||||
`+1` for node_id (2 host reads per lane), since `READ_MEM`'s own
|
||||
`mem_addr` is 16-bit-word-granular while this engine writes a native
|
||||
32-bit ctrl-word — the same real halving `host_mem_bridge.v`'s own header
|
||||
already discloses for the debug raw-access path.
|
||||
|
||||
**Shared-bus discipline** (mirrors `act_tile_fetch.v`'s own real,
|
||||
proven pattern, not reinvented): waits for `mem_grant` before ever
|
||||
issuing `ctrl_req` (EXP-0066's own established rule — an early/blind
|
||||
request on a shared bus can lose the request permanently); a real
|
||||
`S_GAP` state waits for `!ctrl_busy` between lane A's write and lane
|
||||
B's own, since `mig_native_adapter.v`'s own `busy` stays asserted one
|
||||
cycle past `ctrl_ready`. `wmask` polarity matches `host_mem_bridge.v`'s
|
||||
own real, already-working convention exactly (0 = write this byte, 1 =
|
||||
masked, the same DQM-style polarity this project's whole memory stack
|
||||
already uses end to end).
|
||||
|
||||
**Real, hard scaling blocker removed**: `n2_system_ddr3_top.v`'s own
|
||||
`s0_result_data_a/b`/`s1_result_data_a/b` top-level PACKAGE PINS are
|
||||
gone — each slot's `packed_slot.v` still exposes `result_data_a/b` etc.
|
||||
as plain output ports (for debug/testbench visibility, unchanged), but
|
||||
these are no longer wired to literal FPGA package pins at any N.
|
||||
|
||||
**Verification**: `tb_packed_slot.v` extended with a new real
|
||||
read-after-write check (`verify_writeback` task) — after each job's
|
||||
`job_done`, the testbench independently reads back the exact DDR3
|
||||
location `result_writeback.v` should have written and confirms both the
|
||||
result value AND node_id match, closing the loop (not just checking
|
||||
`job_done` eventually pulses). **9/9 PASS, 0 errors**, real Icarus xsim
|
||||
against `burst_mem_model32.v`. Also re-verified at the full N=2 system
|
||||
level (`tb_n2_system_ddr3.v`, real xsim against the real, closed
|
||||
32-bit/3225ps DDR3 model) to confirm the writeback engine behaves
|
||||
correctly under real shared-bus arbitration contention between 2 slots
|
||||
— see `hardware/v2/logs/experiments.log` EXP-0088 for the real result.
|
||||
|
||||
### 5.4 [DONE] Widening the physical DDR3 channel: 32-bit single channel vs. a second independent 16-bit channel — **functionally complete AND real timing closed (EXP-0084 → EXP-0086)**
|
||||
|
||||
|
||||
@@ -363,10 +363,11 @@ when it actually fires, instead of polling every loop iteration.
|
||||
## 7. Known-open items (honestly disclosed, not hidden)
|
||||
|
||||
- Scaling past N=2 compute cores (silicon budget allows up to ~30 per the
|
||||
DSP48E1 count) is not yet built or timing-verified. A result-writeback
|
||||
engine (currently `result_data_a/b` are literal top-level pins, fine at
|
||||
N=2 but a real pin-budget blocker beyond that) must land first — see
|
||||
`docs/ARCHITECTURE_ANALYSIS.md` §5.3.
|
||||
DSP48E1 count) is not yet built or timing-verified. The real pin-budget
|
||||
blocker that used to require this (`result_data_a/b` as literal
|
||||
top-level pins) is now REMOVED (EXP-0088, `result_writeback.v` — see
|
||||
`docs/ARCHITECTURE_ANALYSIS.md` §4.6/§5.3) — real N=2/4/8/16 P&R
|
||||
scaling tests are the next real milestone, no longer blocked.
|
||||
- `sys_rst` has only a *tentative* real pin (G13, bank 15, EXP-0084 —
|
||||
chosen just to unblock real P&R, not a final board decision) — assign
|
||||
its real, permanent location once the rest of the board layout (reset
|
||||
@@ -388,7 +389,8 @@ when it actually fires, instead of polling every loop iteration.
|
||||
`tb_spi_host_bridge_v3.v`), AND real P&R-verified at the closed EXP-0086
|
||||
signoff (D14/LVCMOS33, confirmed via a direct query on the routed
|
||||
checkpoint) — see §6.
|
||||
- Scaling past N=2 real core count and the result-writeback engine (both
|
||||
listed above) remain the real next milestones now that the 32-bit
|
||||
channel's own timing is closed — see
|
||||
`hardware/v2/logs/experiments.log` EXP-0086's `next_action`.
|
||||
- `result_writeback.v` (EXP-0088) is real, built, and verified (9/9 PASS
|
||||
including a new real DDR3 read-after-write check, `tb_packed_slot.v`).
|
||||
Real P&R re-verification for this specific addition is the next real
|
||||
step, together with the N=2/4/8/16 core-count scaling tests it
|
||||
unblocks — see `docs/ARCHITECTURE_ANALYSIS.md` §5.3/§5.5.
|
||||
|
||||
Reference in New Issue
Block a user