feat: real first step of 4x4 hybrid systolic architecture (EXP-0089)
Adds packed_pe.v (packed_slot.v's compute+activation-fetch+writeback subsystem, reusing ddr_prefetch_mgr.v/neural_processor_packed.v/ result_writeback.v completely unmodified, with its own private weight-fetch removed) and systolic_group.v (one real layer_prefetch_ ctrl.v+layer_weight_buffer.v+weight_tile_gather.v shared by 4x packed_pe.v via a real, barrier-synchronized broadcast bus). Real design choice confirmed with the user before writing any RTL (AskUserQuestion, concrete topology preview): shared-weight broadcast, not a literal PE-to-PE systolic shift register -- achieves the real, quantified rationale (4x reduction in redundant weight-fetch DDR3 traffic per group of 4 PEs) with much lower real risk than genuine inter-PE pipeline fill/drain. The real new design is the barrier: each PE's own tcnt is the join key against the group's broadcast tcnt, self-synchronizing regardless of which PE is momentarily ahead/behind (e.g. a real DDR3 row-switch stall on one PE's own activation fetch). Found and fixed a real bug during verification (not by inspection): the first full test run reported every result as undefined despite every control-flow signal tracing correctly -- root-caused via real signal tracing down to a 5-way test arbiter bus mis-sliced at the wrong slot offset (single-bit handshake buses happened to use a correct range and masked it from the control-flow trace; only the wide, byte-offset buses were wrong). Verified in isolation (tb_systolic_group.v, real Icarus xsim, real sdram_arbiter_n.v generalized to NUM_REQ=5 with zero changes): 8/8 PASS across 2 consecutive group jobs (exercising the barrier's own per-job reset path, not just cold start). Deliberately scoped to the isolated mechanism only, per this project's "one variable at a time" discipline -- Director/SPI job dispatch for group jobs, a real N=16 top-level, and real P&R are real, disclosed next steps, not done here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -5948,3 +5948,134 @@ design confirmed by the user -- NOT a literal PE-to-PE systolic shift
|
||||
register -- verified in isolation before any Director/SPI-protocol
|
||||
integration, matching this project's own "one variable at a time"
|
||||
discipline).
|
||||
|
||||
EXP-0089 -- real, isolated first step of the 4x4 hybrid systolic
|
||||
architecture: shared-weight-broadcast group, built and verified
|
||||
(2026-09-20, user's own explicit reprioritization: "Fai la parte
|
||||
realmente mancante prima, il RESULT-WRITEBACK e poi implementa la 4x4
|
||||
sistolica"; real A/B design decision confirmed by the user before any
|
||||
RTL was written: shared-weight BROADCAST, not a literal PE-to-PE
|
||||
systolic shift register)
|
||||
|
||||
CONTEXT: docs/ARCHITECTURE_ANALYSIS.md S5.6 captured this direction as
|
||||
purely exploratory ("nothing in this subsection is implemented"),
|
||||
explicitly flagging "intra-chain dataflow RTL... is a real, new design,
|
||||
not a trivial extension" as an open question. Before writing any RTL,
|
||||
the user was asked (AskUserQuestion, with a concrete side-by-side
|
||||
preview of both real candidate topologies) to resolve that open
|
||||
question directly: (A) one shared weight fetch per group of 4 PEs,
|
||||
broadcast to all 4, each PE computing its own independent activation
|
||||
positions in parallel -- achieves the doc's own quantified rationale
|
||||
(4x reduction in redundant weight-fetch DDR3 traffic per group) with
|
||||
much lower real risk; or (B) a literal PE-to-PE systolic shift
|
||||
register (weight physically translating PE by PE, real pipeline
|
||||
fill/drain at chain boundaries). User confirmed (A), explicitly noting
|
||||
(B) can be revisited later if real data justifies it.
|
||||
|
||||
DESIGN: two new modules.
|
||||
- `packed_pe.v`: packed_slot.v's own compute+activation-fetch+
|
||||
writeback subsystem (ddr_prefetch_mgr.v, neural_processor_packed.v,
|
||||
result_writeback.v -- all THREE reused completely unmodified),
|
||||
with packed_slot.v's own private layer_prefetch_ctrl.v/layer_
|
||||
weight_buffer.v/weight_tile_gather.v REMOVED -- weight tile data
|
||||
arrives via a real, group-broadcast interface instead
|
||||
(group_tcnt/group_tile_data/group_tile_valid).
|
||||
- `systolic_group.v`: ONE real layer_prefetch_ctrl.v + layer_weight_
|
||||
buffer.v + weight_tile_gather.v (group-level, shared by reference,
|
||||
not duplicated -- identical instances to what packed_slot.v already
|
||||
owned per-slot), driving 4x packed_pe.v.
|
||||
|
||||
REAL SYNCHRONIZATION (the actual new design, not a trivial extension --
|
||||
confirmed by building and testing it, not asserted): each packed_pe.v's
|
||||
own `tcnt` IS the join key -- a PE in S_TILEWAIT waits for
|
||||
`group_tile_valid && (group_tcnt == tcnt)` before consuming, a real,
|
||||
self-synchronizing comparison immune to which PE happens to be
|
||||
momentarily ahead or behind (e.g. one PE's own activation fetch hit a
|
||||
real DDR3 row switch the others didn't). systolic_group.v's own barrier
|
||||
extends this project's established 2-source "S_TILEWAIT join"
|
||||
discipline to 4 independent sources: it only advances to the next
|
||||
tile's weight fetch once ALL 4 PEs have ack'd the current one
|
||||
(`pe_acked`, individually latched, using a `pe_acked_next` combinational
|
||||
fold-in to detect same-cycle acks without an extra latency cycle). A
|
||||
similar accumulator (`pe_done_latch`) tracks all 4 PEs' own job_done
|
||||
pulses, deliberately made UNCONDITIONAL (not state-gated) to correctly
|
||||
catch a real race: a fast PE's own job_done can pulse the SAME cycle
|
||||
the group's own barrier clears the LAST tile (i.e. the same cycle the
|
||||
group transitions toward waiting for completion) -- a state-gated
|
||||
accumulator would have silently dropped that pulse.
|
||||
|
||||
REAL BUGS FOUND AND FIXED DURING DESIGN (before compiling, via re-
|
||||
deriving against already-proven code, not guessed) AND DURING
|
||||
VERIFICATION (via real signal tracing, not by inspection):
|
||||
1. First draft of packed_pe.v omitted the neural_processor_packed.v
|
||||
job_valid/job_ready handshake's own real gating (packed_slot.v's
|
||||
own proven S_JOBSTART state) -- would have raced operand_valid
|
||||
against job acceptance. Caught by re-deriving against packed_
|
||||
slot.v's own real sequencing before ever compiling, not by
|
||||
simulation.
|
||||
2. systolic_group.v's first draft indexed an expression directly
|
||||
(`(tcnt + 16'd1)[BUFADDRW-1:0]`) -- not valid plain Verilog syntax
|
||||
(only nets/regs, not arbitrary expressions, can be part-selected).
|
||||
Fixed with an intermediate `tcnt_next` wire.
|
||||
3. A duplicate `pe_acked_pulse` wire declaration (real copy-paste
|
||||
leftover from restructuring the generate block).
|
||||
4. THE real bug, found via real hierarchical signal tracing after the
|
||||
first full test run reported EVERY result as `x` (undefined) even
|
||||
though `job_done` fired and every control-flow signal traced
|
||||
correctly (barrier, tcnt, state transitions all real and correct)
|
||||
-- proving the bug was in DATA, not control flow. Traced down
|
||||
through ddr_prefetch_mgr.v's own real, already-proven internals
|
||||
(confirmed innocent) to the actual root cause: the ISOLATED
|
||||
testbench's own 5-way arbiter wiring (1 group weight-fetch slot +
|
||||
4 PE slots) sliced the flattened `pe_ctrl_addr/wdata/wmask/rdata`
|
||||
buses starting at the WRONG offset (`4*WIDTH`, i.e. slot 4, instead
|
||||
of `1*WIDTH`, slot 1 -- slot 0 is the group's own weight-fetch).
|
||||
The single-bit handshake buses (req/grant/ready/busy) happened to
|
||||
use a correct `[4:1]` bit range and so masked the bug from the
|
||||
control-flow trace entirely -- only the wide, byte-offset-computed
|
||||
buses were wrong, which is exactly why data silently corrupted
|
||||
while every handshake still looked correct. A real, honest example
|
||||
of why "the control flow works" is not suffient evidence that "the
|
||||
data path works" -- this project's own standing discipline (root-
|
||||
cause via real signal tracing, verify don't assume) is what caught
|
||||
it, not luck.
|
||||
|
||||
VERIFICATION: new `tb_systolic_group.v`, real Icarus xsim, real
|
||||
`burst_mem_model32.v` backend shared via a real `sdram_arbiter_n.v`
|
||||
(NUM_REQ=5, confirmed its own NUM_REQ parameter already generalizes to
|
||||
this shape with zero changes, per this project's own prior note).
|
||||
Preloads 2 real layers' worth of weights + 16 real activation
|
||||
positions, runs TWO consecutive group jobs (deliberately, to exercise
|
||||
the barrier's own per-job reset path, not just a single cold-start).
|
||||
**8/8 PASS (2 jobs x 4 PEs), 0 errors**, real, independently-reproduced
|
||||
golden model (same formula convention as every other v3 testbench).
|
||||
This verifies the shared-weight-broadcast mechanism itself is
|
||||
functionally correct -- it does NOT re-verify result_writeback.v's own
|
||||
real DDR3 read-after-write correctness, since packed_pe.v reuses that
|
||||
module completely unmodified and it already has its own real
|
||||
read-after-write verification (EXP-0088, tb_packed_slot.v).
|
||||
|
||||
DECISION: real, verified first step. This is deliberately SCOPED to the
|
||||
isolated group mechanism only, per this project's own "one variable at
|
||||
a time" discipline (same precedent as act_tile_fetch.v EXP-0079 or
|
||||
ddr_prefetch_mgr.v EXP-0083, both built and verified standalone well
|
||||
before system-level integration). NOT yet integrated: Director-level
|
||||
job dispatch (neural_director_packed.v only knows how to dispatch
|
||||
2-position jobs to flat slots, not 4-position jobs to a group),
|
||||
SPI/WRITE_JOB protocol support for group-level job submission, a real
|
||||
N=16 (4 groups x 4 PEs) top-level module, and real P&R signoff for any
|
||||
of this. These are real, disclosed, deliberately deferred next steps,
|
||||
not overlooked.
|
||||
|
||||
next_action: (1) real P&R for systolic_group.v in isolation (out-of-
|
||||
context first, matching this project's own precedent for brand-new
|
||||
modules before real system integration) to get a first real sense of
|
||||
its area/timing cost. (2) Design the real Director/SPI protocol
|
||||
extension needed for group-level job dispatch (a real, disclosed,
|
||||
non-trivial addition -- WRITE_JOB's current payload only carries one
|
||||
job's worth of x_base/w_base/result_addr/node_id, not 4). (3) Build a
|
||||
real N=16 top-level (4x systolic_group.v) once (1) and (2) are real and
|
||||
verified, with its own real P&R signoff. (4) Revisit the flat N=2/4/8/16
|
||||
core-count scaling tests (deferred by the user's own explicit
|
||||
reprioritization this session) once there's a real basis for comparing
|
||||
flat vs. grouped scaling with real numbers from both.
|
||||
|
||||
Reference in New Issue
Block a user