Files
FPGA-Neural/hardware/v2/logs/decisions.log
T
micheleandClaude Sonnet 5 6cff2c8a7c feat(v2): M8 PSRAM integration - real V1 backend shared across concurrent slots
neural_multiprocessor.v wraps dataflow_core.v (M7, unmodified) around
the real, unmodified V1 PSRAM backend chain (int8_memory_access ->
memory_interface -> psram_controller), funneling N_SLOTS independent
Memory Backend Interface ports through a new generic N-port arbiter
(slot_mem_arbiter.v) inspired by (not copied from) V1's own
mem_arbiter.v.

Real concurrent-slot simulation immediately surfaced a genuine bug
(ERR-0008): memory_manager/prefetch_engine's byte-level backend
protocol is fire-and-forget (a single-cycle mem_req pulse with no
accept handshake) - correct for M4's direct 1:1 connection, but a
naive arbiter silently drops a pulse arriving while the shared bus is
owned by another slot, hanging that slot forever. Fixed with a
per-port pending-request latch, the same "queue, don't drop" idiom
already used by memory_manager's own pf_pending register (ERR-0006).

Verified (Verilator): 4/4 PASS with 2 slots genuinely contending for
one real PSRAM port (444 cycles). No regression on M4's own
testbench. Real synthesis + nextpnr-ecp5 P&R (no harness needed - real
PSRAM pins keep the top-level at 157 pins): 0 problems, Fmax 142.45
MHz, PASS at 80MHz.

Arbitration policy is fixed lowest-index priority, not fairness-
balanced (DEC-0010) - consistent with every other "simplest correct
policy first" scheduling choice in this roadmap, revisited only if
M9's real measurement shows starvation matters.

Logged: simulation/synthesis/timing/benchmark/decisions (DEC-0010)/
experiments (EXP-0009)/errors (ERR-0008)/development.log, ROADMAP.md
updated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-05 15:13:11 +02:00

585 lines
26 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