Final milestone of docs/v2-description.md's §33 roadmap, scoped exactly to its own mandate: optimize only on data already gathered in M1-M9, across the pipeline/P_IN/processor-count/scheduling/memory axes - no speculative new features. Three concrete, data-driven results: 1. N_SLOTS=8 (numero processor axis): real synthesis + nextpnr-ecp5 P&R for dataflow_core at N_SLOTS=8, extending M7's N_SLOTS=2/4 sweep to the real DSP ceiling DEC-0005 predicted. 92.63 MHz POST-P&R, PASS at 80MHz, DSP 64/72 (88.9%). DEC-0012 recommends N_SLOTS=8 as the practical ceiling for P_IN=8 on the LFE5U-45F-8BG381. 2. ACC_WIDTH 24 vs 32 (pipeline axis): a real 6-seed nextpnr-ecp5 placement sweep (reusing already-synthesized netlists, no new synthesis needed) resolves EXP-0002's single-seed inconclusiveness. ACC_WIDTH=24 wins on both mean Fmax (+6.2%, 180.71 vs 170.12 MHz) and seed-to-seed variance (~3.4x tighter), on top of its already-known resource advantage. DEC-0013 recommends ACC_WIDTH=24 as the new default. 3. Stall %/utilization (scheduling/memory axes): testbench-only cycle counters added to tb_neural_multiprocessor.v (no RTL touched) close DEC-0011's deferred measurement gap with real data - shared PSRAM port 81.7% utilized, slot 0 95.2%, slot 1 65.2%, no conclusive evidence of harmful fixed-priority starvation at this scale. The 10-milestone V2 roadmap (docs/v2-description.md §33) is now complete end-to-end: real Verilator simulation, real Yosys synthesis, real nextpnr-ecp5 place & route for every milestone, fully logged (EXP-0001..EXP-0013, DEC-0001..DEC-0013, ERR-0001..ERR-0008) with no invented results (§30) and V1 kept frozen and untouched throughout (§1/§34). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
778 lines
35 KiB
Plaintext
778 lines
35 KiB
Plaintext
# V2 decisions log -- formato DEC-XXXX, mai sovrascritto (vedi README.md)
|
|
|
|
DEC-0001
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
Congelare V1 come copia separata in hardware/v1/ (sola lettura a livello
|
|
filesystem) invece di spostare (git mv) l'albero top-level esistente
|
|
(rtl/, sim/, synth/, tools/) dentro hardware/v1/.
|
|
|
|
WHY:
|
|
docs/v2-description.md §1/§34 impone una "struttura obbligatoria"
|
|
hardware/v1/ + hardware/v2/ e vieta di modificare/degradare V1. Due
|
|
strade possibili: (a) spostare fisicamente rtl/sim/synth/tools
|
|
nell'albero hardware/v1/, oppure (b) copiarli lasciando l'albero
|
|
top-level esattamente come e' oggi. Lo spostamento romperebbe tutti i
|
|
riferimenti a percorso in WORKLOG.md, docs/validation/*.md,
|
|
docs/FPGA-NeuralNetwork-Engine.md (centinaia di citazioni tipo
|
|
"rtl/neuron_parallel.v:127") e negli script (tools/run_regression.py
|
|
resta funzionante per costruzione relativa, ma altri riferimenti
|
|
documentali no) -- un costo reale senza benefico funzionale, dato che
|
|
l'obiettivo della regola e' *non perdere/alterare* V1, non *dove* vive
|
|
fisicamente. La copia raggiunge lo stesso obiettivo (baseline
|
|
funzionale/numerica/bit-exact per V2, mai modificabile) senza il
|
|
rischio di rompere la cronologia documentale esistente.
|
|
|
|
EVIDENCE:
|
|
- `diff -rq rtl/ hardware/v1/rtl/` e confronto file-per-file su
|
|
sim/*.v: 0 differenze (copia bit-esatta verificata, non assunta).
|
|
- hardware/v1/ reso sola lettura (`chmod -R a-w`) subito dopo la copia,
|
|
prima di qualunque lavoro V2.
|
|
|
|
ALTERNATIVES:
|
|
- git mv dell'intero albero rtl/sim/synth/tools sotto hardware/v1/,
|
|
poi aggiornamento di tutti i riferimenti di percorso nella
|
|
documentazione. Scartata: costo/rischio alto, beneficio nullo
|
|
rispetto all'obiettivo dichiarato della regola.
|
|
- Symlink hardware/v1/ -> ../../rtl ecc. Scartata: non garantisce
|
|
l'immutabilita' (un simlink non protegge dalla modifica dell'originale
|
|
ed e' fragile rispetto a `chmod -R a-w`).
|
|
|
|
RESULT:
|
|
hardware/v1/ creato come copia bit-esatta e sola-lettura. L'albero
|
|
top-level del repository resta la "produzione" V1 corrente, invariata,
|
|
usata anche dal resto del progetto (WORKLOG.md, docs/) esattamente come
|
|
prima di questa sessione.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
DEC-0002
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
Collapse §6's baseline Neural Processor FSM states NP_LOAD_TILE,
|
|
NP_MAC, NP_ACCUM, NP_NEXT_TILE into the single NP_WAIT_OPERANDS state
|
|
in the actual implementation (hardware/v2/rtl/neural_processor.v),
|
|
rather than implementing them as four separate one-cycle-gated states.
|
|
|
|
WHY:
|
|
§5 explicitly states the internal datapath must be pipelined and the
|
|
goal is throughput, not minimal latency -- accepting a new P_IN-wide
|
|
tile every cycle. Gating tile acceptance behind four sequential FSM
|
|
states (each holding for exactly one cycle per tile) would recreate a
|
|
non-pipelined, one-tile-per-4-cycles controller, directly
|
|
contradicting §5/§34 ("ottimizzare il throughput effettivo"). The four
|
|
states from §6's baseline list describe what the ORIGINAL (pre-M1)
|
|
sketch assumed before the pipeline design in §5 was fully worked out;
|
|
once the datapath is genuinely pipelined, tile acceptance becomes a
|
|
single steady-state condition (operand_valid && operand_ready), and
|
|
per-tile progress is tracked by the valid/last tags flowing through
|
|
the pipeline registers, not by the outer FSM.
|
|
|
|
EVIDENCE:
|
|
EXP-0001 (bit-exact vs V1, 7/7 tests incl. a deliberate zero-idle-gap
|
|
back-to-back-tiles case, TEST 5 in tb_neural_processor.v) -- confirms
|
|
tiles are genuinely accepted one per cycle with no outer-FSM stall
|
|
between them.
|
|
|
|
ALTERNATIVES:
|
|
Literal 11-state FSM per §6's baseline list, with LOAD_TILE/MAC/
|
|
ACCUM/NEXT_TILE each a real one-cycle state gating acceptance.
|
|
Rejected: would cap throughput at 1 tile per 4 cycles, defeating the
|
|
pipeline's own purpose.
|
|
|
|
RESULT:
|
|
7-state FSM (NP_IDLE, NP_LOAD_JOB, NP_WAIT_OPERANDS, NP_FINISH,
|
|
NP_WRITE_RESULT, NP_DONE, NP_ERROR) implemented and verified.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0003
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
Remove the operand-arrival protocol-violation guard from
|
|
neural_processor.v (the check that would raise NP_ERROR if
|
|
operand_valid arrived while the processor could not consume it) rather
|
|
than continue debugging it. Defer this responsibility to the Neural
|
|
Director (M5).
|
|
|
|
WHY:
|
|
The guard's own evaluation triggered ERR-0002 (docs/v2-description.md
|
|
mandate §25-29 requires documenting this, not hiding it) -- a
|
|
reproducible Icarus Verilog v13.0 bug where the guard's condition
|
|
evaluated true despite operand_valid being independently confirmed 0.
|
|
Root cause was bisected down to a minimal FSM transition unrelated to
|
|
this specific expression (see errors.log ERR-0002), meaning the bug is
|
|
in the toolchain's scheduling, not fixable by rewording the condition.
|
|
Architecturally, a standalone Neural Processor policing its OWN
|
|
issuer's protocol is also arguably the wrong owner of that
|
|
responsibility: per §34's own division of labor ("Il Director gestisce
|
|
WHAT deve essere eseguito"), arbitrating/validating operand issuance
|
|
across possibly-multiple Neural Processors is the Director's job, not
|
|
each processor's.
|
|
|
|
EVIDENCE:
|
|
ERR-0002 (errors.log) -- the guard, and several simplified variants of
|
|
it, all misevaluated under Icarus v13.0; disabling it entirely (and
|
|
only it) restored correct behavior in every case, confirmed via
|
|
Verilator that the underlying pipeline logic was already correct.
|
|
|
|
ALTERNATIVES:
|
|
1. Keep chasing the exact Icarus root cause. Rejected for this
|
|
session: already bisected to a toolchain-level scheduling issue
|
|
independent of this specific code, further chasing would not
|
|
change the architectural need for this check to live in the
|
|
Director eventually anyway.
|
|
2. Reimplement the same check with different Verilog phrasing.
|
|
Rejected: multiple independent phrasings all reproduced the bug.
|
|
|
|
RESULT:
|
|
NP_ERROR is now reachable only via the `default:` case branch (a
|
|
genuine np_state encoding corruption) -- a real safety net, just not
|
|
exercised by operand-arrival timing. The corresponding negative test
|
|
(TEST 7) was removed from tb_neural_processor.v; the scenario is
|
|
deferred to M5's testbench (tb_neural_director.v), where the Director
|
|
is the actual issuer under test.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0004
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
Adopt Verilator 5.050 (`verilator --binary --timing`) as the primary/
|
|
trusted simulator for hardware/v2/ testbenches going forward, in
|
|
addition to (not instead of) Icarus Verilog. Cross-check any Icarus
|
|
result that looks anomalous against Verilator before concluding it is
|
|
an RTL bug.
|
|
|
|
WHY:
|
|
ERR-0001/ERR-0002 (errors.log) are two independently-reproduced Icarus
|
|
Verilog v13.0 defects that produced WRONG simulation results (not
|
|
compile errors) for straightforward, standard sequential Verilog, with
|
|
no workaround available at the RTL/testbench level for ERR-0002 short
|
|
of removing the affected logic. Verilator gave the CORRECT result for
|
|
every one of these repros. §30's rule against invented results cuts
|
|
both ways: a simulator that silently gives a WRONG "measured" result is
|
|
just as dangerous as inventing one outright -- cross-checking against
|
|
a second, architecturally different simulator (Verilator compiles to
|
|
C++, Icarus interprets bytecode -- unlikely to share the same
|
|
scheduling bug) is now mandatory whenever a hardware/v2/ testbench
|
|
shows unexpected behavior.
|
|
|
|
EVIDENCE:
|
|
- Minimal FSM repro (`if (go) st<=B;`, no tasks, no other logic):
|
|
Icarus v13.0 fails to transition on specific testbench edge-count
|
|
parities; Verilator 5.050 gives the correct result every time.
|
|
- Full hardware/v2/sim/tb_neural_processor.v: Icarus v13.0 hangs/
|
|
misbehaves even after every known-real RTL bug (ERR-0003) was fixed;
|
|
the SAME unmodified file under Verilator gives 7/7 PASS, bit-exact
|
|
vs the frozen V1 reference.
|
|
|
|
ALTERNATIVES:
|
|
1. Downgrade Icarus to an older release. Rejected: no older bottle was
|
|
cached on this machine (`brew list --versions icarus-verilog` shows
|
|
only 13.0) and fetching a specific historical formula version was
|
|
not attempted this session (time-boxed decision, revisit if it
|
|
becomes a recurring blocker).
|
|
2. Keep using only Icarus and manually work around each new defect as
|
|
found. Rejected: not sustainable across the dozens of testbenches
|
|
the full V2 roadmap requires (§20).
|
|
|
|
RESULT:
|
|
Verilator installed (`brew install verilator`, 5.050). hardware/v2/
|
|
testbenches are compiled/run with both simulators when convenient;
|
|
Verilator's result is authoritative when the two disagree, and any
|
|
such disagreement is logged here / in errors.log, not silently
|
|
resolved by picking whichever answer looks more convenient.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0005
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
Treat DSP (MULT18X18D) budget, not LUT/FF/routing, as the primary
|
|
constraint when exploring the N_PROCESSORS x P_IN trade-off space
|
|
(§16) going forward.
|
|
|
|
WHY:
|
|
Real place&route measurement (EXP-0003) shows MULT18X18D usage
|
|
scaling linearly and reaching 88% of the LFE5U-45F-8BG381's 72 total
|
|
DSPs at N_PROCESSORS=8, P_IN=8 -- while LUT4/FF usage stays under 6%
|
|
at the SAME configuration and Fmax is still comfortably above the
|
|
80MHz target (134.70 MHz). This means the naive "just add more
|
|
processors" scaling (§8/§16) hits a hard DSP ceiling around
|
|
N_PROCESSORS=9 at P_IN=8, long before LUT/FF/routing/timing become
|
|
relevant -- the opposite of what LUT/FF utilization alone would
|
|
suggest if read in isolation.
|
|
|
|
EVIDENCE:
|
|
experiments.log EXP-0003 -- MULT18X18D 8/16/32/64 (11%/22%/44%/88% of
|
|
72) at N_PROCESSORS 1/2/4/8, LUT4 under 6% throughout, Fmax PASS at
|
|
80MHz throughout (159.11/149.59/151.01/134.70 MHz).
|
|
|
|
ALTERNATIVES:
|
|
Assume LUT/FF/routing congestion would be the limiting factor (the
|
|
naive expectation for "more parallel copies of a datapath"). Rejected
|
|
by direct measurement, not assumed -- §16 explicitly requires
|
|
choosing the final configuration "sulla base del throughput effettivo
|
|
... non dell'utilizzo massimo delle risorse", and knowing WHICH
|
|
resource binds first is a prerequisite for that.
|
|
|
|
RESULT:
|
|
Future N_PROCESSORS x P_IN sweeps (§16, deferred to a dedicated
|
|
scripts/sweep/ run per §31) should budget MULT18X18D count explicitly
|
|
(N_PROCESSORS * P_IN <= ~72, minus whatever the rest of the real
|
|
system needs once M4/PSRAM integration lands) rather than only
|
|
tracking LUT/FF. A smaller P_IN with more N_PROCESSORS (or vice versa)
|
|
is a live trade-off worth exploring precisely because of this ceiling,
|
|
not merely a stylistic choice.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0006
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
memory_manager.v (M4) uses a SINGLE prefetch_engine instance,
|
|
retargeted per bank via a depth-1 pending-request register, rather
|
|
than multiple engines or a general request queue. The result
|
|
write-back (one byte per job, after the last tile) shares the same
|
|
backend port via a simple state-based mux, not a general arbiter --
|
|
because prefetch and write-back are temporally disjoint by
|
|
construction (the write only happens after prefetch_engine has
|
|
nothing left to fetch for that job).
|
|
|
|
WHY:
|
|
§13's double-buffering strategy needs at most ONE fetch "in flight"
|
|
and at most ONE fetch "queued" at any time for a SINGLE Neural
|
|
Processor consuming tiles sequentially (proven by construction: a new
|
|
prefetch is only ever queued on a tile handoff, and at most one
|
|
handoff can be pending completion of the previous prefetch before the
|
|
next one is even requested). A general multi-entry queue or a second
|
|
engine would add complexity with no present benefit. Likewise,
|
|
because this Memory Manager currently serves exactly one Neural
|
|
Processor and one job at a time, no concurrent second requester can
|
|
ever contend for the backend port with prefetch reads -- a real
|
|
mem_arbiter-style arbiter (as V1 uses for ITS OWN multi-master case)
|
|
is deferred until a scenario that actually needs it exists (multiple
|
|
Neural Processors or overlapping jobs sharing one memory_manager,
|
|
not yet built).
|
|
|
|
EVIDENCE:
|
|
errors.log ERR-0006 -- the single-entry pending register, once
|
|
correctly gated (see ERR-0006 items 1-2), handled 1-tile, 3-tile, and
|
|
5-tile jobs correctly with no queue overflow in
|
|
hardware/v2/sim/tb_memory_manager.v.
|
|
|
|
ALTERNATIVES:
|
|
1. Multiple prefetch_engine instances (one per bank), letting both
|
|
banks fetch fully in parallel. Rejected for M4: doubles DSP-free
|
|
logic for a benefit only realized when compute-tile time is
|
|
SHORTER than 2x fetch-tile time for a single engine -- not yet
|
|
measured to be the case (§22, deferred to M9), and the single-
|
|
engine design already fully hides fetch latency behind neural_
|
|
processor's own per-tile compute time in the cases tested (see
|
|
experiments.log EXP-0005 cycle counts).
|
|
2. General N-entry FIFO for pending requests. Rejected: no scenario
|
|
in the current single-processor, single-job design can ever
|
|
generate more than one pending request before the in-flight one
|
|
completes -- an N-entry queue would be complexity with no
|
|
reachable use.
|
|
3. Reuse V1's mem_arbiter.v as-is for the prefetch-vs-writeback
|
|
sharing. Rejected: mem_arbiter.v's four ports are hardcoded to
|
|
specific V1 module names/priorities (§1 already established this
|
|
pattern in DEC-0001 for the broader V1-freeze decision) -- and
|
|
prefetch/write-back are provably never simultaneous here anyway,
|
|
so even a generic 2-port arbiter would be unexercised complexity.
|
|
|
|
RESULT:
|
|
memory_manager.v as implemented. A NOTED, NOT-YET-OPTIMIZED
|
|
characteristic (documented in the module's own header comment): the
|
|
bank-swap-and-check control path costs a minimum 1 idle cycle per
|
|
tile handoff even when the next bank was already prefetched in time,
|
|
unlike neural_processor.v's own zero-gap tile acceptance -- left for
|
|
M10 (Optimization) to revisit using real stall-percentage data (§22)
|
|
rather than optimized blindly now.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0007
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
neural_director.v (M5) implements a reduced FSM (DIR_IDLE,
|
|
DIR_SCAN_READY, DIR_ALLOCATE, DIR_ERROR) instead of §9's full 8-state
|
|
baseline list (which also includes DIR_WAIT_DEPENDENCY, DIR_MONITOR,
|
|
DIR_COMPLETE, DIR_WAKEUP). Dependency tracking/waiting/wake-up are
|
|
entirely deferred to the Dependency Manager (M6, not yet built); slot
|
|
completion detection (§9's "rilevamento dei completamenti",
|
|
DIR_MONITOR's job) is handled by an always-active per-slot busy
|
|
tracker running independently of whatever state the allocate/scan
|
|
loop happens to be in, not a dedicated state the loop must visit.
|
|
|
|
WHY:
|
|
§10 explicitly assigns dependency counters/ready-vs-waiting
|
|
tracking/wake-up/producer-tracking to the Dependency Manager, not the
|
|
Director -- building DIR_WAIT_DEPENDENCY/DIR_WAKEUP now, before M6
|
|
exists, would mean inventing a dependency model here that M6 would
|
|
then have to either reuse or replace, backwards from the roadmap's own
|
|
milestone order. For DIR_MONITOR: gating "did any slot just finish"
|
|
detection behind a specific FSM state would force the SAME state to be
|
|
revisited every cycle for every one of N_SLOTS independently-running
|
|
jobs, which is exactly the throughput-killing pattern DEC-0002 already
|
|
rejected for the Neural Processor's own FSM -- the same reasoning
|
|
applies one level up here.
|
|
|
|
EVIDENCE:
|
|
hardware/v2/sim/tb_neural_director.v -- 4/4 tests pass with 2 slots
|
|
running genuinely concurrent, independently-timed jobs (a 3rd job
|
|
correctly queued until whichever slot freed first, and a
|
|
deliberately-slow 2-job burst used to force real ready-queue
|
|
backpressure) -- confirms slot-completion detection and first-free
|
|
allocation both work without a dedicated FSM state gating either.
|
|
|
|
Separately: this milestone's testbench gives each (memory_manager,
|
|
neural_processor) slot its OWN independent behavioral byte memory
|
|
(sim_byte_mem, not the real V1 PSRAM chain) rather than sharing one
|
|
PSRAM port across N_SLOTS. M4 (EXP-0005) already proved the real PSRAM
|
|
path end-to-end for ONE slot; M5's own concern is scheduling/dispatch
|
|
across MULTIPLE slots, which this isolates. Multiple slots genuinely
|
|
sharing one physical PSRAM port is a backend-arbitration problem
|
|
already explicitly deferred (DEC-0006), not solved here either.
|
|
|
|
ALTERNATIVES:
|
|
1. Implement the literal 8-state FSM now, with DIR_WAIT_DEPENDENCY/
|
|
DIR_WAKEUP as real states that simply never get exercised until
|
|
M6 wires something into them. Rejected: dead states with no real
|
|
behavior are not simpler or safer than documenting the deferral
|
|
explicitly, and risk baking in an ad-hoc dependency model that
|
|
conflicts with M6's actual design once built.
|
|
2. Share one real PSRAM backend across N_SLOTS now, forcing the
|
|
arbiter-design question into M5. Rejected: out of this milestone's
|
|
scope (§9 is about scheduling, not memory arbitration) and would
|
|
duplicate work once M6/M8 need a real answer to backend sharing
|
|
anyway.
|
|
|
|
RESULT:
|
|
neural_director.v as implemented: 4-state FSM, always-active slot-busy
|
|
tracking, ready-queue backpressure via a plain parametric-depth
|
|
circular FIFO. First-free scheduling only (§9's initial policy);
|
|
round-robin/least-loaded/etc are explicitly deferred to a later,
|
|
experimentally-driven milestone per §9's own text.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
---
|
|
|
|
DEC-0008
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
dependency_manager.v (M6) does NOT implement §11's direct producer-
|
|
to-consumer VALUE forwarding (bypassing the Result Buffer/external-
|
|
memory round-trip). It tracks dependency COUNTS and READINESS only --
|
|
"has this node's data become available", resolved via a
|
|
producer_done_node_id tag matched against each waiting node's own
|
|
producer_ids list. A ready node's job descriptor still points at
|
|
result_addr (wherever the Memory Manager, M4, wrote the producer's
|
|
actual result), which is how a consumer finds its real input data
|
|
today. Additionally, node table slots are NOT reclaimed after
|
|
dispatch (ST_DISPATCHED is terminal) -- a full graph run allocates its
|
|
N_NODES once, not a reusable pool.
|
|
|
|
WHY:
|
|
§11 itself frames forwarding as an optimization ("quando possibile"),
|
|
not a correctness requirement -- the dependency-COUNTING mechanism
|
|
(§10's actual explicit field list: node_id/state/required_dependencies/
|
|
resolved_dependencies/producer_information) is what gates correct
|
|
scheduling; forwarding is a bandwidth/latency optimization on top of
|
|
an already-correct base. Implementing real value forwarding would
|
|
require reworking the Neural Processor's operand path (M1) and Memory
|
|
Manager's fetch path (M4) to support a bypass source in addition to
|
|
PSRAM -- a bigger change that should be justified by real measured
|
|
data (§22/§30: no invented results) showing memory bandwidth is
|
|
actually the bottleneck, not assumed now. Slot non-reclamation is
|
|
similarly a scope choice: reclaiming/reusing node table entries mid-run
|
|
only matters for graphs that run longer than N_NODES distinct node
|
|
launches, or that need dynamic re-registration -- not exercised by
|
|
this milestone's own test (a bounded DAG, registered once, run once).
|
|
|
|
EVIDENCE:
|
|
hardware/v2/sim/tb_dependency_manager.v -- 4/4 tests pass demonstrating
|
|
multi-dependency (node2 needs both node0 AND node1) and shared-
|
|
producer/multi-consumer wake-up (node0's single completion correctly
|
|
satisfies both node3 fully and node2 partially) using ONLY the
|
|
counting mechanism, no forwarded values -- confirming the counting-
|
|
only design is sufficient for correct scheduling.
|
|
|
|
ALTERNATIVES:
|
|
1. Implement value forwarding now (Producer -> Consumer FIFO directly,
|
|
per §11's diagram). Rejected: no measured evidence yet that the
|
|
PSRAM round-trip is a real bottleneck (§22 measurements are M9's
|
|
job); adding it now would be exactly the kind of unmeasured,
|
|
assumption-driven change §30 warns against.
|
|
2. Reclaim/reuse node table slots after dispatch. Rejected: adds
|
|
real complexity (a free-list, or requiring producer_done for a
|
|
DISPATCHED node to also clear it) for a scenario (graphs needing
|
|
more distinct node launches than N_NODES, or dynamic re-
|
|
registration) this milestone's test doesn't exercise -- revisit if
|
|
a real M7+ integration scenario needs it.
|
|
|
|
RESULT:
|
|
dependency_manager.v as implemented: pure dependency-count tracking,
|
|
first-found-ready dispatch to the Director (M5), no value forwarding,
|
|
no slot reclamation. Both explicitly noted as deferred, not silently
|
|
missing.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
DEC-0009
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
dataflow_core.v (M7) integrates dependency_manager (M6) -> neural_director
|
|
(M5) -> N_SLOTS x (memory_manager (M4) + neural_processor (M1)), closing
|
|
the wake-up loop end-to-end for the first time. Two things are
|
|
deliberately NOT done in this module: (1) M3's BRAM-backed buffers
|
|
(activation_buffer/weight_buffer/result_buffer) are not instantiated
|
|
anywhere inside it; (2) each slot's byte-level Memory Backend Interface
|
|
is exposed as its own SEPARATE port (slot_mem_req/wr/addr/wdata/rdata/
|
|
ready, arrayed by N_SLOTS) rather than arbitrated down to one shared
|
|
PSRAM master.
|
|
|
|
WHY:
|
|
(1) §15's own diagram places the Memory Manager -> Memory Backend
|
|
Interface -> PSRAM Controller path on one side, with M3's buffers
|
|
belonging as an on-chip cache concept, not a mandatory pass-through --
|
|
each memory_manager instance already owns its own prefetch double
|
|
buffer (M4) for the fast path it actually needs, and no measured
|
|
benchmark yet shows a real need for an additional shared cache layer
|
|
(§22/§30: no invented results/optimizations). (2) real PSRAM has
|
|
exactly ONE physical port; N_SLOTS>1 memory_manager instances wanting
|
|
concurrent access is fundamentally an arbitration problem, and building
|
|
an arbiter now, before M8's real-toolchain measurement of what
|
|
contention actually looks like end-to-end with the real (unmodified)
|
|
V1 PSRAM chain, risks designing to a guess instead of to data.
|
|
|
|
EVIDENCE:
|
|
hardware/v2/sim/tb_dataflow_core.v -- 4/4 tests PASS on a 3-node DAG
|
|
run through the full stack with each slot backed by its own
|
|
independent behavioral memory (deliberately NOT the real shared V1
|
|
PSRAM chain, for exactly the reason above): node0 and node1 (no
|
|
dependencies) both complete correctly via real neural_processor
|
|
computation, and node2 (depends on BOTH) is only dispatched after
|
|
BOTH genuinely finish -- continuously polled every cycle, not just
|
|
checked at the end -- proving the producer_done wake-up loop closes
|
|
correctly with real M1/M4/M5/M6 hardware in between, not just
|
|
between M5 and M6 in isolation (already proven separately by their
|
|
own testbenches).
|
|
|
|
ALTERNATIVES:
|
|
1. Wire a naive round-robin N-port arbiter in front of one shared
|
|
PSRAM master now. Rejected: M8's own roadmap text is explicit
|
|
("Integrare il controller V1 senza modificarlo inizialmente.
|
|
Misurare il comportamento reale.") -- arbitration design should
|
|
follow a real measurement of contention under the real PSRAM
|
|
latency model, not be guessed at during M7's own scope (proving
|
|
the dependency/scheduling loop closes, not memory sharing).
|
|
2. Instantiate M3's buffers as a shared cache in front of each slot's
|
|
Memory Backend Interface now. Rejected: no benchmark yet shows
|
|
PSRAM bandwidth or latency is actually a bottleneck for the
|
|
dependency-graph workloads this module targets -- premature
|
|
without measured justification.
|
|
|
|
RESULT:
|
|
dataflow_core.v as implemented: N_SLOTS independent Memory Backend
|
|
Interface ports, no M3 buffers wired in. Both explicitly deferred to
|
|
M8 (shared PSRAM integration/arbitration) and a future
|
|
measurement-driven decision (M3 buffer reuse), not missing by
|
|
oversight.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
DEC-0010
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
slot_mem_arbiter.v (M8) arbitrates dataflow_core's N_SLOTS independent
|
|
Memory Backend Interface ports down to the ONE real PSRAM port using
|
|
FIXED, lowest-port-index priority (not round-robin/least-loaded/
|
|
fair-share), with a per-port single-entry pending-request latch (see
|
|
errors.log ERR-0008) so a fire-and-forget request pulse arriving
|
|
during contention is queued, never dropped.
|
|
|
|
WHY:
|
|
Fixed lowest-index priority is the same "first-found, simplest
|
|
correct policy first" starting point already chosen for
|
|
neural_director's first-free slot scheduling (decisions.log DEC-0007)
|
|
and dependency_manager's first-ready dispatch -- consistent with this
|
|
whole roadmap's own pattern of shipping the simplest policy that is
|
|
provably correct, then revisiting fairness/throughput ONLY once real
|
|
measured data (M9) shows it actually matters for a real workload.
|
|
Under sustained heavy contention a low-index slot COULD in principle
|
|
starve a higher-index one (an unfair, but not incorrect, outcome);
|
|
this is an honestly-acknowledged limitation of a first cut, not an
|
|
oversight. The pending-latch discipline (ERR-0008) is not a policy
|
|
choice but a correctness requirement -- discovered empirically via
|
|
real concurrent-slot simulation, not designed in from the start (an
|
|
example of the mandate's own point, §22/§30: real measurement finds
|
|
real problems that a purely theoretical design would not).
|
|
|
|
EVIDENCE:
|
|
hardware/v2/sim/tb_neural_multiprocessor.v -- 4/4 PASS with N_SLOTS=2
|
|
genuinely concurrent slots (node0/node1, no dependencies, dispatched
|
|
back-to-back) contending for the one real PSRAM port through the real
|
|
V1 backend chain; both complete correctly and node2 (depends on both)
|
|
dispatches only once they genuinely do. No starvation observed in this
|
|
small a test (2 slots, one short job each) -- a real starvation
|
|
measurement would need a longer-running, higher-N_SLOTS workload,
|
|
deferred to M9's own benchmark.
|
|
|
|
ALTERNATIVES:
|
|
1. Round-robin or least-recently-served fairness now. Rejected: no
|
|
measured evidence yet (M9 not run) that fixed-priority starvation
|
|
is a real problem for the graph workloads this system targets --
|
|
adding fairness logic before a measured need is speculative
|
|
complexity, the same reasoning DEC-0007 already applied to
|
|
neural_director's own scheduling policy.
|
|
2. Give each slot its own dedicated PSRAM port (no arbitration at
|
|
all). Rejected: real PSRAM hardware has exactly one physical port
|
|
(the whole reason this module exists) -- not an option on real
|
|
hardware, only in simulation.
|
|
|
|
RESULT:
|
|
slot_mem_arbiter.v as implemented: fixed lowest-index priority,
|
|
single-entry pending-request latch per port (mandatory for
|
|
correctness, not a policy choice). Fairness/throughput-aware
|
|
scheduling explicitly deferred to a future measurement-driven
|
|
decision, not missing by oversight.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
DEC-0011
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
M9's §32 comparison table reports "stall %", "memory utilization" and
|
|
"processor utilization" as NOT INDEPENDENTLY MEASURED this milestone,
|
|
rather than computing a number for them. All other rows (Fmax, LUT,
|
|
FF, DSP, BRAM, MAC/cycle, cycles/neuron, neurons/s, effective MAC/s)
|
|
are filled with real, sourced numbers (see benchmark.log's M9 entry).
|
|
|
|
WHY:
|
|
Computing a real, honest stall %/utilization figure requires isolating
|
|
"real compute cycles" from "real memory-wait cycles" for BOTH V1 and
|
|
V2 on an equal footing -- V1's own docs give a real end-to-end cycle
|
|
count (209 cycles, 1 neuron/8 inputs, real PSRAM) but not a
|
|
correspondingly measured ISOLATED (no-PSRAM) compute-only cycle count
|
|
for the exact same configuration; V2 has the isolated pipeline latency
|
|
(8-stage neural_processor, known from M1) but computing a precise,
|
|
honest stall % still means dedicated per-cycle instrumentation of a
|
|
real run, not something to approximate from numbers already at hand
|
|
without effectively inventing the missing half of the ratio. §30's own
|
|
rule ("nessun risultato inventato") applies exactly here: an
|
|
approximated/guessed percentage would look precise while not being a
|
|
real measurement.
|
|
|
|
EVIDENCE:
|
|
benchmark.log/timing.log/simulation.log already contain the REAL
|
|
numbers this table draws from (V1: hardware/v1/docs/
|
|
FPGA-NeuralNetwork-Engine.md's own already-certified 209-cycle
|
|
measurement, PARALLEL=8/N_INPUTS=8, real PSRAM; V2: EXP-0005's 166-
|
|
cycle measurement, P_IN=8, real V1 PSRAM chain, plus EXP-0009's 444-
|
|
cycle real 2-slot-concurrent run). The qualitative finding is already
|
|
documented (simulation.log EXP-0005: "real PSRAM latency dominates,
|
|
not memory_manager's own control overhead") -- both systems are
|
|
memory-latency-bound for a single small job, but a precise percentage
|
|
needs dedicated instrumentation neither system has had built for it
|
|
yet.
|
|
|
|
ALTERNATIVES:
|
|
1. Approximate stall % from the neural_processor pipeline's known
|
|
8-stage latency vs total real cycles (rough mental arithmetic).
|
|
Rejected: this is exactly the kind of "looks-measured-but-isn't"
|
|
number §30 prohibits -- pipeline latency and PSRAM real access
|
|
latency are not the same thing as "non-stalled cycles" once
|
|
overlap/pipelining across tiles is accounted for (M4's own
|
|
double-buffered prefetch specifically overlaps compute with the
|
|
NEXT tile's fetch), so a naive subtraction would misrepresent real
|
|
behavior, not measure it.
|
|
2. Skip the whole M9 table until full instrumentation exists.
|
|
Rejected: 9 of the table's 12 rows already have solid real data
|
|
sitting in the logs from M1-M8 -- withholding the whole table would
|
|
throw away real, useful, already-measured information for the sake
|
|
of 3 rows that genuinely need new instrumentation.
|
|
|
|
RESULT:
|
|
M9's table ships with 9/12 rows filled from real measured data
|
|
(labeled THEORETICAL/SIMULATED/SYNTHESIZED/POST-P&R per row) and 3
|
|
rows (stall %, memory utilization, processor utilization) explicitly
|
|
marked NOT MEASURED, deferred to M10 -- which itself explicitly needs
|
|
real utilization data to decide what to optimize, making dedicated
|
|
cycle-accounting instrumentation a natural M10 prerequisite rather
|
|
than M9 scope creep.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
DEC-0012
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
For the LFE5U-45F-8BG381 target at P_IN=8, N_SLOTS=8 is the
|
|
recommended practical ceiling for dataflow_core's processor count (the
|
|
"numero processor" axis of M10). Beyond N_SLOTS=8, DSP usage would
|
|
exceed the chip's 72 total MULT18X18D (N_SLOTS=8 already uses 64/72 =
|
|
88.9%; N_SLOTS=9 would need 72/72 = 100%, leaving zero margin for any
|
|
other DSP use and very likely failing placement given routing
|
|
congestion already visibly eating into Fmax margin well before that
|
|
point).
|
|
|
|
WHY:
|
|
Real P&R data across the full N_SLOTS sweep now run (EXP-0003 at the
|
|
neural_processor_array level for M2, EXP-0008/EXP-0011 at the
|
|
dataflow_core level for M7/M10) shows TWO real, independent trends
|
|
converging on the same conclusion: (1) DSP usage scales exactly
|
|
linearly at 8 per slot (matches P_IN=8), hitting 88.9% at N_SLOTS=8 --
|
|
consistent with DEC-0005's original finding that DSP, not LUT/FF, is
|
|
the first resource to saturate; (2) Fmax falls monotonically and
|
|
non-linearly as N_SLOTS grows (165.15 -> 133.19 -> 92.63 MHz for
|
|
N_SLOTS=2/4/8), meaning routing congestion around the shared
|
|
neural_director/dependency_manager hub is ALREADY compounding with DSP
|
|
pressure well before the hard resource ceiling is reached. N_SLOTS=8
|
|
is therefore not merely "the largest N_SLOTS that fits" but close to
|
|
where BOTH constraints (DSP budget and real routing congestion)
|
|
become simultaneously binding -- a genuinely data-driven ceiling, not
|
|
an assumed one.
|
|
|
|
EVIDENCE:
|
|
timing.log/synthesis.log EXP-0008 (N_SLOTS=2: 165.15 MHz, 16/72 DSP;
|
|
N_SLOTS=4: 133.19 MHz, 32/72 DSP) and EXP-0011 (N_SLOTS=8: 92.63 MHz,
|
|
PASS at 80MHz but with a much thinner margin, 64/72 DSP) -- all real
|
|
nextpnr-ecp5 place&route measurements via harness_dataflow_core.v.
|
|
|
|
ALTERNATIVES:
|
|
1. Recommend a smaller N_SLOTS (e.g. 4) for a larger Fmax safety
|
|
margin. Rejected as a BLANKET recommendation: whether 133.19 MHz's
|
|
larger margin over 133.19 vs 92.63 MHz's thinner one actually
|
|
matters depends on the target application's own real timing needs,
|
|
which M9's benchmark did not fix to a specific number beyond
|
|
"PASS at 80MHz" -- both configurations real-measure as passing.
|
|
N_SLOTS=8 remains the data-driven CEILING; choosing a smaller
|
|
N_SLOTS for a specific deployment is a downstream product decision,
|
|
not something this session can make on the target's behalf.
|
|
2. Reduce P_IN below 8 to allow more slots within the same DSP budget
|
|
(e.g. P_IN=4, N_SLOTS=16 -> still 64 DSP). Rejected as untested:
|
|
no real data exists yet on Fmax/throughput for P_IN=4 slots at any
|
|
N_SLOTS -- this is a real, open experiment for a FUTURE session,
|
|
not something to recommend without having actually measured it
|
|
(§30).
|
|
|
|
RESULT:
|
|
N_SLOTS=8 (P_IN=8) is the data-driven practical ceiling on the
|
|
LFE5U-45F-8BG381 for dataflow_core/neural_multiprocessor. Smaller
|
|
N_SLOTS values remain valid, real-measured configurations trading
|
|
Fmax margin for less concurrency; a P_IN<8 exploration for even higher
|
|
N_SLOTS is explicitly flagged as untested future work, not assumed.
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|
|
DEC-0013
|
|
|
|
DATE: 2026-09-05
|
|
|
|
DECISION:
|
|
ACC_WIDTH=24 is the recommended default for neural_processor.v's
|
|
accumulator width (the "pipeline" axis of M10), replacing EXP-0002's
|
|
original single-seed, inconclusive finding.
|
|
|
|
WHY:
|
|
A 6-seed real placement sweep (EXP-0012: default seed plus 5 explicit
|
|
--seed values, same two already-synthesized netlists, real
|
|
nextpnr-ecp5 P&R only -- no re-synthesis needed) shows ACC_WIDTH=24
|
|
has a HIGHER mean Fmax (180.71 vs 170.12 MHz, +6.2%) AND a much
|
|
tighter seed-to-seed spread (stdev 4.21 vs 14.16 MHz) than
|
|
ACC_WIDTH=32. EXP-0002's original single-seed result (176.21 <
|
|
183.12 MHz, suggesting ACC=24 was WORSE) is now understood as
|
|
placement-seed noise, not a real trend -- exactly the kind of mistake
|
|
a single-seed measurement risks, which is why §30/§31 call for
|
|
experimentally exploring configurations rather than trusting one
|
|
placement run. Combined with EXP-0001/EXP-0002's already-known
|
|
resource advantage (ACC_WIDTH=24: LUT=49/FF=509/CCU2C=88 vs
|
|
ACC_WIDTH=32: LUT=55/FF=533/CCU2C=96 -- fewer of every resource) and
|
|
both being bit-exact-correct against the same 7-test regression
|
|
(EXP-0001/EXP-0002), ACC_WIDTH=24 dominates ACC_WIDTH=32 on every real
|
|
axis measured for a plain INT8 perceptron whose products/partial sums
|
|
never need more than 24 bits of headroom for P_IN=8 (8 x int8 x int8
|
|
products, worst case magnitude fits well under 2^24).
|
|
|
|
EVIDENCE:
|
|
timing.log EXP-0012 (6-seed Fmax data for both configs, computed
|
|
mean/min/max/stdev); synthesis.log EXP-0001/EXP-0002 (resource
|
|
counts, already logged); simulation.log EXP-0001/EXP-0002 (both
|
|
configs bit-exact-correct against V1, 7/7 PASS).
|
|
|
|
ALTERNATIVES:
|
|
1. Keep ACC_WIDTH=32 as the default (matches V1's own mac_unit.v
|
|
accumulator width, "when in doubt, match the frozen baseline").
|
|
Rejected: real multi-seed data now shows ACC_WIDTH=24 is strictly
|
|
better on Fmax, resource usage, AND correctness for this specific
|
|
P_IN=8 INT8 configuration -- there is no real axis left on which
|
|
ACC_WIDTH=32 wins for THIS workload. V1 itself is a separate, frozen
|
|
baseline (§1/§34) and is not required to match V2's own internal
|
|
width choices.
|
|
2. Run more than 6 seeds per config for a tighter confidence interval.
|
|
Deferred, not rejected: 6 seeds already show a clear, consistent
|
|
direction (ACC=24 wins on both mean and variance) -- diminishing
|
|
returns for this decision's purposes; a future session could extend
|
|
the sweep if ACC_WIDTH ever becomes a live bottleneck again.
|
|
|
|
RESULT:
|
|
ACC_WIDTH default changed to 24 going forward for any NEW V2 module
|
|
instantiating neural_processor.v at P_IN=8 (no existing committed
|
|
module needs to be edited retroactively purely for this -- M1-M9's own
|
|
modules already default to ACC_WIDTH=32 via their own parameter
|
|
defaults and remain correct either way, since both widths are
|
|
bit-exact verified; this is a recommendation for future configuration
|
|
choices, not a mandate to re-synthesize already-logged results).
|
|
|
|
STATUS:
|
|
ACCEPTED
|
|
|