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:
@@ -5813,3 +5813,108 @@ now-diminished DDR3-latency lever this experiment just showed has
|
||||
little room left to give at N=2 -- worth real-measuring its own benefit
|
||||
carefully before investing further RTL effort, rather than assuming
|
||||
EXP-0083's original optimistic framing still applies.
|
||||
|
||||
EXP-0088 -- real result-writeback engine: the last hard N-scaling
|
||||
blocker removed (2026-09-20, user's own explicit reprioritization:
|
||||
"riordiniamo le priorita ... BRAM ci pensiamo dopo. Fai la parte
|
||||
realmente mancante prima, il RESULT-WRITEBACK e poi implementa la 4x4
|
||||
sistolica")
|
||||
|
||||
CONTEXT: docs/ARCHITECTURE_ANALYSIS.md S4.6/S5.3 flagged this since
|
||||
packed_slot.v's own original header (unchanged through EXP-0079):
|
||||
result_data_a/b/result_node_id_a/b were literal top-level PACKAGE PINS
|
||||
on n2_system_ddr3_top.v (s0_result_data_a/b, s1_result_data_a/b) --
|
||||
fine at N=2 (4 pins), the exact same class of scaling 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 x 2 lanes x 16 cores = 256 pins, a real, hard blocker.
|
||||
|
||||
DESIGN: 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 -- each slot still contributes exactly one
|
||||
ctrl_req to the shared arbiter, now locally 3-way-muxed instead of 2).
|
||||
On job completion (S_RESULT), packed_slot.v pulses `wb_start`; the new
|
||||
S_WRITEBACK state holds `job_done` back until `wb_done` fires -- job_done
|
||||
now means "the result is durably in DDR3", not "captured in a register
|
||||
only a literal top-level pin could see".
|
||||
|
||||
REAL ADDRESSING (verified against act_tile_fetch.v's/layer_prefetch_
|
||||
ctrl.v's own real address-computation code, not guessed, since getting
|
||||
this wrong would be a silent correctness bug, not just a performance
|
||||
one): 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 (confirmed: `x_base_a_lat[ADDR_WIDTH-2:0]` feeds act_tile_fetch.v's
|
||||
own ctrl_addr computation directly, same truncation). ONE full 32-bit
|
||||
ctrl-word is written per lane: {node_id[15:0], 8'h00, result_data[7:0]}
|
||||
(low 16 bits = zero-extended 8-bit result value, high 16 bits =
|
||||
node_id). The host reads results back via the ALREADY-EXISTING
|
||||
READ_MEM (0x02) SPI opcode -- no new protocol needed. 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 `mem_addr = result_addr[24:0]*2 + 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 (EXP-0084).
|
||||
|
||||
SHARED-BUS DISCIPLINE (mirrors act_tile_fetch.v's own real, proven
|
||||
pattern, not reinvented -- a REAL bug was caught and fixed during
|
||||
design, not just asserted correct): the first draft omitted `mem_grant`
|
||||
entirely and issued ctrl_req unconditionally, exactly the class of bug
|
||||
EXP-0066 already documented (an early/blind request on a shared,
|
||||
arbitrated bus can lose the request permanently) -- caught by re-
|
||||
deriving the design against act_tile_fetch.v's own real S_MEMWAIT
|
||||
sequencing before ever compiling it, not found by simulation. Fixed:
|
||||
real S_MEMWAIT (wait for mem_grant before issuing ctrl_req) and a real
|
||||
S_GAP state (wait 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 -- act_tile_fetch.v's own header already established
|
||||
this). wmask polarity matches host_mem_bridge.v's own real, already-
|
||||
working convention exactly (0 = write this byte, 1 = masked).
|
||||
|
||||
REAL SCALING FIX: 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 IOSTANDARD constraint for them removed too) --
|
||||
packed_slot.v still exposes result_data_a/b/etc. as plain output ports
|
||||
(unchanged, for debug/testbench visibility), but nothing wires them to
|
||||
literal FPGA pins any more, at any N.
|
||||
|
||||
VERIFICATION (two levels, same discipline as every other real change
|
||||
in this project):
|
||||
1. 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 (via burst_mem_model32.v)
|
||||
and confirms BOTH the result value AND node_id match -- not just
|
||||
that job_done eventually pulsed. **9/9 PASS, 0 errors**, real
|
||||
Icarus xsim (iverilog -g2012).
|
||||
2. tb_n2_system_ddr3.v -- real xsim (Vivado, real ddr3_model.sv,
|
||||
real mig_7series_0_mig, real 2-slot sdram_arbiter_n.v contention)
|
||||
confirms the writeback engine behaves correctly under REAL shared-
|
||||
bus arbitration between 2 slots, not just in isolation. **8/8
|
||||
PASS, 0 errors, 8/8 positions completed**, $finish at
|
||||
101204.9335 ns -- consistent with EXP-0087's own real ~100.6-100.7us
|
||||
baseline for this same N=2/8-position workload (writeback adds a
|
||||
small, real, expected overhead, not a regression).
|
||||
Two real setup bugs found and fixed getting this run to compile:
|
||||
result_writeback.v (a brand-new file) needed to be added to the
|
||||
Vivado project's own `sources_1` fileset as a direct reference
|
||||
(not just the xsim-only sim_1 fileset) -- confirmed via TCL query
|
||||
it landed as a direct reference, not an imported copy, avoiding
|
||||
the stale-import class of bug from the start.
|
||||
|
||||
DECISION: keep, real, verified, closes the last hard N-scaling blocker
|
||||
this project's own docs had flagged since EXP-0074/0079. Real P&R
|
||||
re-verification for this specific addition is the next real step
|
||||
(deferred, together with the N=2/4/8/16 scaling tests it directly
|
||||
unblocks, per the user's own next directive).
|
||||
|
||||
next_action: real P&R signoff for this change (confirm it doesn't
|
||||
disturb the closed EXP-0086 timing), then real N=2/4/8/16 core-count
|
||||
scaling tests (each with its own real P&R signoff, per the user's own
|
||||
standing directive), then the 4x4 hybrid systolic architecture
|
||||
(docs/ARCHITECTURE_ANALYSIS.md S5.6, currently exploratory/not built).
|
||||
|
||||
Reference in New Issue
Block a user