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
This commit is contained in:
@@ -46,7 +46,18 @@ reali, non solo scritto).
|
|||||||
(N_SLOTS=2), 133.19 MHz (N_SLOTS=4). Buffer M3 e arbitraggio PSRAM
|
(N_SLOTS=2), 133.19 MHz (N_SLOTS=4). Buffer M3 e arbitraggio PSRAM
|
||||||
condiviso rimandati esplicitamente a M8 (`logs/decisions.log`
|
condiviso rimandati esplicitamente a M8 (`logs/decisions.log`
|
||||||
DEC-0009).
|
DEC-0009).
|
||||||
- [ ] **M8 — PSRAM integration**, controller V1 non modificato, misura reale.
|
- [x] **M8 — PSRAM integration** (`neural_multiprocessor.v`,
|
||||||
|
`slot_mem_arbiter.v`), controller V1 riusato SENZA MODIFICHE,
|
||||||
|
condiviso tra N_SLOTS memory_manager concorrenti reali. Trovato e
|
||||||
|
risolto un bug RTL reale: il primo arbitro perdeva silenziosamente
|
||||||
|
una richiesta arrivata durante la contesa (protocollo byte-level
|
||||||
|
"fire-and-forget", mai esposto da M4 che collega un solo master
|
||||||
|
direttamente) — vedi `logs/errors.log` ERR-0008. Dopo il fix: 4/4
|
||||||
|
test PASS (2 slot in vera contesa concorrente sulla stessa PSRAM
|
||||||
|
reale). Sintesi reale 0 problemi (nessun harness necessario — pin
|
||||||
|
reali PSRAM tengono il top-level a 157 pin). Fmax reale 142.45
|
||||||
|
MHz. Politica di arbitraggio a priorità fissa, non ancora fair
|
||||||
|
(`logs/decisions.log` DEC-0010).
|
||||||
- [ ] **M9 — Full benchmark**, tabella V1 vs V2 (§32 del mandato).
|
- [ ] **M9 — Full benchmark**, tabella V1 vs V2 (§32 del mandato).
|
||||||
- [ ] **M10 — Optimization**, solo sulla base dei dati raccolti in M1-M9.
|
- [ ] **M10 — Optimization**, solo sulla base dei dati raccolti in M1-M9.
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,17 @@ uses 32/72 (44%), consistent with DEC-0005's finding that DSP, not
|
|||||||
LUT/FF, is the first resource to saturate as concurrency grows (M2's
|
LUT/FF, is the first resource to saturate as concurrency grows (M2's
|
||||||
own N_PROCESSORS=8 measurement: 88%). BRAM=0 on both is expected --
|
own N_PROCESSORS=8 measurement: 88%). BRAM=0 on both is expected --
|
||||||
M3's buffers are not wired into dataflow_core yet (DEC-0009).
|
M3's buffers are not wired into dataflow_core yet (DEC-0009).
|
||||||
|
|
||||||
|
[2026-09-05] M8 Neural Multiprocessor top (real standalone synthesis +
|
||||||
|
P&R, no harness needed -- real PSRAM pins keep the bare top-level
|
||||||
|
pin count at 157, under the TRELLIS_IO budget)
|
||||||
|
|
||||||
|
| Module (config) | Fmax (POST-P&R) | LUT4 | CCU2C | FF | DSP | BRAM |
|
||||||
|
|--------------------------------------|------------------|------|-------|------|-----|------|
|
||||||
|
| neural_multiprocessor (N_SLOTS=2) | 142.45 MHz | 3145 | 388 | 3659 | 16 | 0 |
|
||||||
|
|
||||||
|
Compare to M7's dataflow_core alone (N_SLOTS=2): 165.15 MHz / LUT4=2127
|
||||||
|
/ CCU2C=248 / FF=2505 / DSP=16. Adding the real V1 PSRAM chain +
|
||||||
|
slot_mem_arbiter costs ~1000 LUT4/140 CCU2C/1150 FF and drops Fmax by
|
||||||
|
~23 MHz (165.15 -> 142.45) -- both real, measured costs of real PSRAM
|
||||||
|
integration, not assumed.
|
||||||
|
|||||||
@@ -521,3 +521,64 @@ oversight.
|
|||||||
STATUS:
|
STATUS:
|
||||||
ACCEPTED
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -206,3 +206,27 @@ decision: see decisions.log DEC-0009 (M3 buffers not wired in yet, no
|
|||||||
next_action: M8 -- PSRAM integration. Wire the real (unmodified) V1
|
next_action: M8 -- PSRAM integration. Wire the real (unmodified) V1
|
||||||
PSRAM backend chain through dataflow_core end-to-end and measure/
|
PSRAM backend chain through dataflow_core end-to-end and measure/
|
||||||
design whatever N_SLOTS>1 arbitration real contention requires.
|
design whatever N_SLOTS>1 arbitration real contention requires.
|
||||||
|
|
||||||
|
[2026-09-05] M8 -- hardware/v2/rtl/neural_multiprocessor.v +
|
||||||
|
hardware/v2/rtl/slot_mem_arbiter.v
|
||||||
|
reason: roadmap M8 -- "Integrare il controller V1 senza modificarlo
|
||||||
|
inizialmente. Misurare il comportamento reale." dataflow_core.v (M7,
|
||||||
|
untouched) now shares the real, unmodified V1 PSRAM backend chain
|
||||||
|
(int8_memory_access -> memory_interface -> psram_controller) across
|
||||||
|
N_SLOTS genuinely concurrent memory_manager instances, via a new
|
||||||
|
generic N-port arbiter inspired by (not copied from) V1's own
|
||||||
|
mem_arbiter.v.
|
||||||
|
result: real concurrent-slot simulation immediately surfaced a real
|
||||||
|
bug (errors.log ERR-0008: the byte-level backend's fire-and-forget
|
||||||
|
request pulse gets silently dropped by a naive arbiter under
|
||||||
|
contention) -- fixed with a pending-request latch. After the fix:
|
||||||
|
4/4 test PASS (2 concurrent slots genuinely contending for one real
|
||||||
|
PSRAM port, 444 cycles). Real synthesis: 0 problems, LUT4=3145/
|
||||||
|
CCU2C=388/FF=3659/DSP=16, real Fmax 142.45 MHz (PASS at 80MHz, no
|
||||||
|
harness needed -- real PSRAM pins keep the top-level pin count at
|
||||||
|
157). No regression on M4's own testbench.
|
||||||
|
errors: see errors.log ERR-0008 (real RTL bug, found and fixed).
|
||||||
|
decision: see decisions.log DEC-0010 (fixed-priority arbitration, no
|
||||||
|
fairness yet -- deferred pending real measured need).
|
||||||
|
next_action: M9 -- Full benchmark (§32): V1-vs-V2 comparison table,
|
||||||
|
every number labeled THEORETICAL/SIMULATED/SYNTHESIZED/POST-P&R.
|
||||||
|
|||||||
@@ -203,3 +203,54 @@ WORKAROUND: set the parameter on the TOP module being synthesized
|
|||||||
STATUS: WORKED AROUND. A build-script ordering detail, not a defect in
|
STATUS: WORKED AROUND. A build-script ordering detail, not a defect in
|
||||||
dataflow_core.v or harness_dataflow_core.v themselves -- noted here
|
dataflow_core.v or harness_dataflow_core.v themselves -- noted here
|
||||||
so a future N_SLOTS sweep (M9/M10) does not re-trip over it.
|
so a future N_SLOTS sweep (M9/M10) does not re-trip over it.
|
||||||
|
|
||||||
|
ERR-0008 (real RTL bug in the first draft of hardware/v2/rtl/slot_mem_arbiter.v, FOUND AND FIXED)
|
||||||
|
DATE: 2026-09-05
|
||||||
|
MODULE: hardware/v2/rtl/slot_mem_arbiter.v (M8, new)
|
||||||
|
SYMPTOM: hardware/v2/sim/tb_neural_multiprocessor.v -- node0 (slot 0)
|
||||||
|
completes correctly (result=48), but node1 (slot 1, dispatched
|
||||||
|
concurrently to node0) never completes; its byte-level backend
|
||||||
|
request appears to simply vanish, and the slot hangs forever
|
||||||
|
(watchdog timeout at 20000 cycles, result stays 0).
|
||||||
|
ROOT CAUSE: memory_manager.v/prefetch_engine.v's own byte-level
|
||||||
|
backend protocol (mem_req/mem_wr/mem_addr/mem_wdata/mem_rdata/
|
||||||
|
mem_ready) is FIRE-AND-FORGET: mem_req is asserted for exactly ONE
|
||||||
|
clock cycle per byte transaction, with no separate "request
|
||||||
|
accepted" acknowledgment -- only `mem_ready` (transaction
|
||||||
|
COMPLETION) exists. M4's own testbench (tb_memory_manager.v) never
|
||||||
|
exposed this because it connects exactly ONE memory_manager directly
|
||||||
|
to int8_memory_access, which is always idle and therefore always
|
||||||
|
able to accept that single pulse the instant it fires. The first
|
||||||
|
arbiter draft only granted a port while its s_req was LIVE that same
|
||||||
|
cycle -- if slot 1's one-cycle pulse arrived on a cycle where the
|
||||||
|
arbiter was already owned by slot 0, the pulse was gone the very
|
||||||
|
next cycle with no record of it ever having happened, and slot 1's
|
||||||
|
prefetch_engine sat in ST_READ_X/ST_READ_W waiting forever for a
|
||||||
|
mem_ready that could never arrive (its request never reached
|
||||||
|
int8_memory_access at all).
|
||||||
|
DIAGNOSIS METHOD: ran the M8 testbench, observed node0 (whichever slot
|
||||||
|
the Director happened to grant the shared bus to first) complete
|
||||||
|
while node1 (the other, concurrently-dispatched slot) hung; traced
|
||||||
|
the fire-and-forget nature of mem_req directly in
|
||||||
|
prefetch_engine.v's own state machine (`mem_req <= 1'b1;` appearing
|
||||||
|
only inside single-cycle state-transition branches, unconditionally
|
||||||
|
cleared to 0 every other cycle) -- confirmed the arbiter's naive
|
||||||
|
"grant only while req is live" logic could not possibly catch a
|
||||||
|
pulse arriving during contention.
|
||||||
|
FIX: every incoming s_req pulse is now LATCHED into a per-port
|
||||||
|
`pending` register (capturing wr/addr/wdata the same cycle),
|
||||||
|
regardless of arbiter state -- the same single-entry "queue, don't
|
||||||
|
drop the request" idiom already used by memory_manager's own
|
||||||
|
pf_pending register (ERR-0006 fix #1). Grants are drawn from
|
||||||
|
`pending`, never from a live s_req directly. This adds a uniform
|
||||||
|
minimum 1-cycle latency to every byte transaction (a real, honestly
|
||||||
|
measured cost of sharing one PSRAM port across N_SLOTS -- see
|
||||||
|
timing.log/benchmark.log EXP-0009), but never drops a request
|
||||||
|
regardless of contention.
|
||||||
|
VERIFICATION: hardware/v2/sim/tb_neural_multiprocessor.v -- 4/4 PASS
|
||||||
|
after the fix (444 cycles end-to-end, vs the buggy draft's 20000-
|
||||||
|
cycle watchdog timeout). hardware/v2/sim/tb_memory_manager.v (M4,
|
||||||
|
untouched) re-run unchanged -- still 3/3 PASS, confirming the fix is
|
||||||
|
entirely contained inside the new arbiter module.
|
||||||
|
STATUS: FIXED, verified end-to-end with the real (unmodified) V1
|
||||||
|
PSRAM backend chain and real concurrent multi-slot contention.
|
||||||
|
|||||||
@@ -401,3 +401,35 @@ next_action: M8 -- PSRAM integration. Wire the real (unmodified) V1
|
|||||||
psram_controller) end-to-end through dataflow_core, and design/
|
psram_controller) end-to-end through dataflow_core, and design/
|
||||||
measure whatever N_SLOTS>1 arbitration across ONE physical PSRAM
|
measure whatever N_SLOTS>1 arbitration across ONE physical PSRAM
|
||||||
port actually requires.
|
port actually requires.
|
||||||
|
|
||||||
|
[2026-09-05] EXP-0009 -- hardware/v2/rtl/neural_multiprocessor.v (M8,
|
||||||
|
real PSRAM integration)
|
||||||
|
test: hardware/v2/sim/tb_neural_multiprocessor.v -- same 3-node DAG as
|
||||||
|
EXP-0008, now routed through the REAL, UNMODIFIED V1 PSRAM backend
|
||||||
|
chain shared across N_SLOTS=2 genuinely concurrent memory_manager
|
||||||
|
instances via the new slot_mem_arbiter.v
|
||||||
|
simulator: Verilator 5.050 (--binary --timing)
|
||||||
|
PASS/FAIL:
|
||||||
|
SIMULATED: 4/4 PASS after fixing a real dropped-request bug in the
|
||||||
|
arbiter's first draft (errors.log ERR-0008) -- 444 cycles
|
||||||
|
end-to-end. hardware/v2/sim/tb_memory_manager.v (M4, untouched)
|
||||||
|
re-confirmed 3/3 PASS, no regression.
|
||||||
|
SYNTHESIZED (real standalone top-level, no harness needed -- 157
|
||||||
|
port bits, real PSRAM pins keep it under the TRELLIS_IO budget):
|
||||||
|
0 CHECK problems, LUT4=3145, CCU2C=388, TRELLIS_FF=3659,
|
||||||
|
MULT18X18D=16, DP16KD=0.
|
||||||
|
POST-P&R (real): Fmax=142.45 MHz -- PASS at 80MHz.
|
||||||
|
errors: one real RTL bug (errors.log ERR-0008) -- the first arbiter
|
||||||
|
draft silently dropped a request pulse arriving during contention;
|
||||||
|
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).
|
||||||
|
decision: see decisions.log DEC-0010 (fixed lowest-index priority
|
||||||
|
arbitration, not fairness-balanced -- consistent with every other
|
||||||
|
scheduling policy chosen so far in this roadmap; revisit only if
|
||||||
|
M9's real measurement shows starvation actually matters).
|
||||||
|
next_action: M9 -- Full benchmark. Produce the V1-vs-V2 comparison
|
||||||
|
table mandated by §32 (Fmax, LUT, FF, DSP, BRAM, MAC/cycle,
|
||||||
|
cycles/neuron, neurons/s, stall %, memory/processor utilization,
|
||||||
|
effective MAC/s), each number labeled THEORETICAL/SIMULATED/
|
||||||
|
SYNTHESIZED/POST-P&R per §30.
|
||||||
|
|||||||
@@ -75,3 +75,23 @@ PASS/FAIL: 4/4 PASS -- node0=48, node1=8 (both real neural_processor
|
|||||||
dependency-manager-to-director wake-up loop closes correctly
|
dependency-manager-to-director wake-up loop closes correctly
|
||||||
end-to-end with real hardware in between, not just in isolation
|
end-to-end with real hardware in between, not just in isolation
|
||||||
(M6's own testbench already proved the wake-up logic alone)
|
(M6's own testbench already proved the wake-up logic alone)
|
||||||
|
|
||||||
|
[2026-09-05] EXP-0009 -- hardware/v2/sim/tb_neural_multiprocessor.v
|
||||||
|
test: same 3-node DAG as EXP-0008 (node0/node1 independent, node2
|
||||||
|
depends on both), routed through neural_multiprocessor.v (M8): the
|
||||||
|
REAL, UNMODIFIED V1 PSRAM backend chain (int8_memory_access ->
|
||||||
|
memory_interface -> psram_controller -> psram_model) shared across
|
||||||
|
N_SLOTS=2 genuinely concurrent memory_manager instances via the new
|
||||||
|
slot_mem_arbiter.v -- node0 and node1 are registered back-to-back
|
||||||
|
with no dependencies, so both dispatch to their slots essentially
|
||||||
|
simultaneously and genuinely contend for the one real PSRAM port.
|
||||||
|
simulator: Verilator 5.050 (--binary --timing)
|
||||||
|
PASS/FAIL: 4/4 PASS after fixing a real dropped-request bug in the
|
||||||
|
first arbiter draft (errors.log ERR-0008) -- node0=48, node1=8
|
||||||
|
(concurrent, real PSRAM, real arbitration), node2=40 dispatched only
|
||||||
|
after both genuinely completed. 444 cycles end-to-end (vs 20000-cycle
|
||||||
|
watchdog timeout with the buggy first draft, where node1's request
|
||||||
|
was silently dropped and its slot hung forever).
|
||||||
|
regression: hardware/v2/sim/tb_memory_manager.v (M4) re-run unchanged
|
||||||
|
(no M4 file touched) -- still 3/3 PASS, identical cycle counts
|
||||||
|
(446/166/728), confirming slot_mem_arbiter.v is purely additive.
|
||||||
|
|||||||
@@ -63,3 +63,16 @@ CHECK: 0 problems on both configs (same 32 benign "multiple conflicting
|
|||||||
deliberately NOT instantiated inside dataflow_core yet (decisions.log
|
deliberately NOT instantiated inside dataflow_core yet (decisions.log
|
||||||
DEC-0009). DSP scales exactly 8/slot (matches P_IN=8, consistent with
|
DEC-0009). DSP scales exactly 8/slot (matches P_IN=8, consistent with
|
||||||
every prior per-processor DSP measurement since M1/M2).
|
every prior per-processor DSP measurement since M1/M2).
|
||||||
|
|
||||||
|
[2026-09-05] EXP-0009 -- neural_multiprocessor (M8, N_SLOTS=2, real
|
||||||
|
standalone top-level synthesis -- no timing harness needed: real PSRAM
|
||||||
|
pins replace dataflow_core's wide per-slot arrays, total 157 port bits,
|
||||||
|
well under the LFE5U-45F-8BG381's ~245 TRELLIS_IO budget)
|
||||||
|
LUT4=3145 CCU2C=388 TRELLIS_FF=3659 MULT18X18D=16 DP16KD=0
|
||||||
|
TRELLIS_DPR16X4=45 (small LUT-based distributed RAM, inferred from
|
||||||
|
neural_director's shallow QUEUE_DEPTH-entry job queue -- not BRAM,
|
||||||
|
same primitive class already seen in M7's own harness stat)
|
||||||
|
$_TBUF_=16 (tri-state buffers for the bidirectional psram_dq bus,
|
||||||
|
from V1's own unmodified psram_controller.v)
|
||||||
|
CHECK: 0 problems (same 32 benign "multiple conflicting drivers for
|
||||||
|
...neural_processor.\gi" warnings documented since EXP-0001).
|
||||||
|
|||||||
@@ -65,3 +65,8 @@ Fmax drops as N_SLOTS grows (more concurrent memory_manager+
|
|||||||
neural_processor instances competing for the same routing fabric
|
neural_processor instances competing for the same routing fabric
|
||||||
around the shared neural_director/dependency_manager hub) -- both
|
around the shared neural_director/dependency_manager hub) -- both
|
||||||
configs still clear the 80MHz target with real margin.
|
configs still clear the 80MHz target with real margin.
|
||||||
|
|
||||||
|
[2026-09-05] EXP-0009 -- neural_multiprocessor (M8, N_SLOTS=2, real
|
||||||
|
standalone synthesis, no harness needed), real nextpnr-ecp5 --45k
|
||||||
|
--package CABGA381 --speed 8 --freq 80 --lpf-allow-unconstrained
|
||||||
|
Fmax: 142.45 MHz -- PASS at 80MHz (real place&route measurement)
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- Neural Multiprocessor top (M8, docs/v2-description.md
|
||||||
|
// §15/§16: "Integrare il controller V1 senza modificarlo inizialmente.
|
||||||
|
// Misurare il comportamento reale.")
|
||||||
|
//
|
||||||
|
// The real, hardware-facing top-level: dataflow_core.v (M7) with its
|
||||||
|
// N_SLOTS independent Memory Backend Interface ports funneled through
|
||||||
|
// a new generic arbiter (slot_mem_arbiter.v, M8) down to the REAL,
|
||||||
|
// UNMODIFIED hardware/v1 PSRAM backend chain --
|
||||||
|
// int8_memory_access -> memory_interface -> psram_controller
|
||||||
|
// -- exactly the chain hardware/v2/sim/tb_memory_manager.v (M4)
|
||||||
|
// already proved correct for ONE memory_manager port. This module is
|
||||||
|
// the first point M3 (per DEC-0009) and M2 (per DEC-0006) BOTH
|
||||||
|
// deferred to: N_SLOTS memory_manager instances genuinely sharing one
|
||||||
|
// physical PSRAM port.
|
||||||
|
//
|
||||||
|
// dataflow_core.v itself is NOT modified -- its per-slot interface
|
||||||
|
// (DEC-0009) is exactly what makes it pluggable into an arbiter here
|
||||||
|
// without touching M7's own file.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module neural_multiprocessor #(
|
||||||
|
parameter DATA_WIDTH = 8,
|
||||||
|
parameter P_IN = 8,
|
||||||
|
parameter ACC_WIDTH = 32,
|
||||||
|
parameter ADDR_WIDTH = 23,
|
||||||
|
parameter N_SLOTS = 4,
|
||||||
|
parameter N_NODES = 16,
|
||||||
|
parameter MAX_DEPS = 4,
|
||||||
|
parameter QUEUE_DEPTH = 8,
|
||||||
|
parameter PSRAM_DATA_WIDTH = 16,
|
||||||
|
parameter CLK_FREQ_MHZ = 80
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
// ---- node registration (host / graph loader -> Dependency Manager) ----
|
||||||
|
input wire reg_valid,
|
||||||
|
output wire reg_ready,
|
||||||
|
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||||
|
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||||
|
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||||
|
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||||
|
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||||
|
input wire [15:0] reg_n_tiles,
|
||||||
|
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||||
|
|
||||||
|
// ---- real PSRAM pins (hardware/v1/rtl/psram_controller.v's own
|
||||||
|
// contract, unmodified) ----
|
||||||
|
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||||
|
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||||
|
output wire psram_ce_n,
|
||||||
|
output wire psram_oe_n,
|
||||||
|
output wire psram_we_n,
|
||||||
|
output wire psram_lb_n,
|
||||||
|
output wire psram_ub_n,
|
||||||
|
output wire psram_zz_n
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- dataflow_core (M7, unmodified) ----
|
||||||
|
wire [N_SLOTS-1:0] slot_mem_req, slot_mem_wr;
|
||||||
|
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_mem_addr;
|
||||||
|
wire signed [8*N_SLOTS-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||||
|
wire [N_SLOTS-1:0] slot_mem_ready;
|
||||||
|
|
||||||
|
dataflow_core #(
|
||||||
|
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||||
|
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||||
|
) u_dataflow_core (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||||
|
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||||
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||||
|
.reg_result_addr(reg_result_addr),
|
||||||
|
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||||
|
.slot_mem_wdata(slot_mem_wdata), .slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- N_SLOTS -> 1 arbiter (M8, new) ----
|
||||||
|
wire arb_m_req, arb_m_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||||
|
wire signed [7:0] arb_m_wdata;
|
||||||
|
wire signed [7:0] arb_m_rdata;
|
||||||
|
wire arb_m_ready;
|
||||||
|
|
||||||
|
slot_mem_arbiter #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS)
|
||||||
|
) u_arbiter (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||||
|
.s_wdata(slot_mem_wdata), .s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||||
|
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||||
|
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- real, unmodified V1 PSRAM backend chain ----
|
||||||
|
wire if_mem_req, if_mem_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] if_mem_addr;
|
||||||
|
wire [PSRAM_DATA_WIDTH-1:0] if_mem_wdata;
|
||||||
|
wire if_mem_lb_n, if_mem_ub_n;
|
||||||
|
wire [PSRAM_DATA_WIDTH-1:0] if_mem_rdata;
|
||||||
|
wire if_mem_ready;
|
||||||
|
|
||||||
|
int8_memory_access #(.ADDR_WIDTH(ADDR_WIDTH)) u_int8 (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||||
|
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||||
|
.mem_req(if_mem_req), .mem_wr(if_mem_wr), .mem_addr(if_mem_addr), .mem_wdata(if_mem_wdata),
|
||||||
|
.mem_lb_n(if_mem_lb_n), .mem_ub_n(if_mem_ub_n),
|
||||||
|
.mem_rdata(if_mem_rdata), .mem_ready(if_mem_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
wire pc_mem_req, pc_mem_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||||
|
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||||
|
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||||
|
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||||
|
wire pc_mem_ready;
|
||||||
|
|
||||||
|
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.req(if_mem_req), .wr(if_mem_wr), .addr(if_mem_addr), .wdata(if_mem_wdata),
|
||||||
|
.lb_n(if_mem_lb_n), .ub_n(if_mem_ub_n),
|
||||||
|
.rdata(if_mem_rdata), .ready(if_mem_ready),
|
||||||
|
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||||
|
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||||
|
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||||
|
);
|
||||||
|
|
||||||
|
psram_controller #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||||
|
) u_psram_ctrl (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||||
|
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||||
|
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||||
|
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||||
|
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||||
|
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// FPGA-Neural V2 -- Slot Memory Arbiter (M8, docs/v2-description.md §15)
|
||||||
|
//
|
||||||
|
// Generic N_PORTS-way arbiter for dataflow_core.v's per-slot Memory
|
||||||
|
// Backend Interface ports (docs/v2-description.md §15's "Memory
|
||||||
|
// Manager -> Memory Backend Interface -> PSRAM Controller" layering),
|
||||||
|
// funneling N_SLOTS independent memory_manager backend ports down to
|
||||||
|
// the ONE physical PSRAM port a real chip actually has.
|
||||||
|
//
|
||||||
|
// Inspired by (NOT copied from -- see hardware/v2/logs/decisions.log
|
||||||
|
// DEC-0006's own note) hardware/v1/rtl/mem_arbiter.v: same
|
||||||
|
// single-owner-until-ready-pulse discipline (a port, once granted,
|
||||||
|
// holds the shared master port until ITS OWN transaction's m_ready
|
||||||
|
// pulse, then releases -- no queuing/pipelining needed, since every
|
||||||
|
// requester already issues a clean one-cycle req pulse matching
|
||||||
|
// int8_memory_access's own contract). Generalized from V1's fixed
|
||||||
|
// 4 named ports (A/B/C/D) to a parametric N_PORTS array, since
|
||||||
|
// dataflow_core.v's N_SLOTS is itself a parameter.
|
||||||
|
//
|
||||||
|
// Priority: fixed, lowest port index wins on a cycle where more than
|
||||||
|
// one port requests simultaneously while the arbiter is idle -- same
|
||||||
|
// "first-found, lowest index" convention already used by
|
||||||
|
// neural_director's free-slot scan and dependency_manager's
|
||||||
|
// first-ready scan (not fairness-balanced; see decisions.log DEC-0010
|
||||||
|
// for why that is an acceptable starting point, same rationale as
|
||||||
|
// neural_director's own "first-free, not load-balanced" choice).
|
||||||
|
//
|
||||||
|
// IMPORTANT (found via real concurrent-slot simulation, see
|
||||||
|
// hardware/v2/logs/errors.log ERR-0008): each port's own s_req is a
|
||||||
|
// FIRE-AND-FORGET single-cycle pulse (prefetch_engine.v/
|
||||||
|
// memory_manager.v's own byte-level backend protocol -- M4 verified
|
||||||
|
// it only against a DIRECT 1:1 connection to int8_memory_access,
|
||||||
|
// which is always free to accept it since there is exactly one
|
||||||
|
// requester). A naive "grant only while req is live" arbiter silently
|
||||||
|
// DROPS a pulse that arrives while the shared bus is owned by another
|
||||||
|
// port, hanging that slot's prefetch/writeback forever. Every
|
||||||
|
// incoming s_req is therefore LATCHED into a per-port `pending`
|
||||||
|
// register (capturing wr/addr/wdata the same cycle) regardless of
|
||||||
|
// arbiter state -- the same single-entry "queue, don't drop the
|
||||||
|
// request" idiom already used by memory_manager's own pf_pending
|
||||||
|
// register (ERR-0006 fix #1). Grants are drawn from `pending`, never
|
||||||
|
// from a live s_req directly, which adds a uniform minimum 1-cycle
|
||||||
|
// latency to every byte transaction (a real, measured cost of sharing
|
||||||
|
// one PSRAM port -- see timing.log/benchmark.log EXP-0009) but never
|
||||||
|
// drops a request.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module slot_mem_arbiter #(
|
||||||
|
parameter ADDR_WIDTH = 23,
|
||||||
|
parameter N_PORTS = 4
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
// ---- N_PORTS requester side (one per dataflow_core slot) ----
|
||||||
|
input wire [N_PORTS-1:0] s_req,
|
||||||
|
input wire [N_PORTS-1:0] s_wr,
|
||||||
|
input wire [ADDR_WIDTH*N_PORTS-1:0] s_addr,
|
||||||
|
input wire signed [8*N_PORTS-1:0] s_wdata,
|
||||||
|
output reg signed [8*N_PORTS-1:0] s_rdata,
|
||||||
|
output reg [N_PORTS-1:0] s_ready,
|
||||||
|
|
||||||
|
// ---- single shared master port (-> int8_memory_access) ----
|
||||||
|
output reg m_req,
|
||||||
|
output reg m_wr,
|
||||||
|
output reg [ADDR_WIDTH-1:0] m_addr,
|
||||||
|
output reg signed [7:0] m_wdata,
|
||||||
|
input wire signed [7:0] m_rdata,
|
||||||
|
input wire m_ready
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam PIDXW = $clog2(N_PORTS+1);
|
||||||
|
localparam OWNER_NONE = {PIDXW{1'b0}}; // 0 = no owner; port i owned = i+1
|
||||||
|
|
||||||
|
reg [PIDXW-1:0] owner;
|
||||||
|
|
||||||
|
// Per-port pending-request latch (see file header/ERR-0008): every
|
||||||
|
// s_req pulse is captured here, regardless of arbiter state, so it
|
||||||
|
// is never silently dropped while the bus is owned by another port.
|
||||||
|
reg [N_PORTS-1:0] pending;
|
||||||
|
reg [ADDR_WIDTH*N_PORTS-1:0] pending_addr;
|
||||||
|
reg signed [8*N_PORTS-1:0] pending_wdata;
|
||||||
|
reg [N_PORTS-1:0] pending_wr;
|
||||||
|
|
||||||
|
// Fixed lowest-index-wins priority scan over PENDING requests (not
|
||||||
|
// raw s_req -- see file header).
|
||||||
|
reg [PIDXW-1:0] grant_idx;
|
||||||
|
reg any_pending;
|
||||||
|
integer ri;
|
||||||
|
always @(*) begin
|
||||||
|
grant_idx = {PIDXW{1'b0}};
|
||||||
|
any_pending = 1'b0;
|
||||||
|
for (ri = N_PORTS-1; ri >= 0; ri = ri - 1) begin
|
||||||
|
if (pending[ri]) begin
|
||||||
|
grant_idx = ri[PIDXW-1:0];
|
||||||
|
any_pending = 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
integer pi;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
owner <= OWNER_NONE;
|
||||||
|
pending <= {N_PORTS{1'b0}};
|
||||||
|
pending_addr <= {(ADDR_WIDTH*N_PORTS){1'b0}};
|
||||||
|
pending_wdata <= {(8*N_PORTS){1'b0}};
|
||||||
|
pending_wr <= {N_PORTS{1'b0}};
|
||||||
|
m_req <= 1'b0;
|
||||||
|
m_wr <= 1'b0;
|
||||||
|
m_addr <= {ADDR_WIDTH{1'b0}};
|
||||||
|
m_wdata <= 8'sd0;
|
||||||
|
s_rdata <= {(8*N_PORTS){1'b0}};
|
||||||
|
s_ready <= {N_PORTS{1'b0}};
|
||||||
|
end else begin
|
||||||
|
m_req <= 1'b0;
|
||||||
|
s_ready <= {N_PORTS{1'b0}};
|
||||||
|
|
||||||
|
// Latch every incoming request pulse. Safe against a
|
||||||
|
// same-cycle collision with the grant-clear write below:
|
||||||
|
// a port only ever becomes grant_idx while its OWN pending
|
||||||
|
// bit is already 1 (latched on an earlier cycle), and its
|
||||||
|
// requester (memory_manager/prefetch_engine) never issues
|
||||||
|
// a NEW s_req for that port until THIS transaction's
|
||||||
|
// s_ready arrives -- so s_req[grant_idx] is guaranteed low
|
||||||
|
// the cycle it is granted.
|
||||||
|
for (pi = 0; pi < N_PORTS; pi = pi + 1) begin
|
||||||
|
if (s_req[pi]) begin
|
||||||
|
pending[pi] <= 1'b1;
|
||||||
|
pending_wr[pi] <= s_wr[pi];
|
||||||
|
pending_addr[pi*ADDR_WIDTH +: ADDR_WIDTH] <= s_addr[pi*ADDR_WIDTH +: ADDR_WIDTH];
|
||||||
|
pending_wdata[pi*8 +: 8] <= s_wdata[pi*8 +: 8];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (owner == OWNER_NONE) begin
|
||||||
|
if (any_pending) begin
|
||||||
|
owner <= grant_idx + 1'b1;
|
||||||
|
m_req <= 1'b1;
|
||||||
|
m_wr <= pending_wr[grant_idx];
|
||||||
|
m_addr <= pending_addr[grant_idx*ADDR_WIDTH +: ADDR_WIDTH];
|
||||||
|
m_wdata <= pending_wdata[grant_idx*8 +: 8];
|
||||||
|
pending[grant_idx] <= 1'b0;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
if (m_ready) begin
|
||||||
|
// owner is (port_index+1); vectorized single-write
|
||||||
|
// so exactly one s_rdata/s_ready lane updates (no
|
||||||
|
// per-bit loop last-write-wins hazard -- same class
|
||||||
|
// of bug already hit/fixed at ERR-0006/M2/M6).
|
||||||
|
for (pi = 0; pi < N_PORTS; pi = pi + 1) begin
|
||||||
|
if (owner == pi[PIDXW-1:0] + 1'b1) begin
|
||||||
|
s_rdata[pi*8 +: 8] <= m_rdata;
|
||||||
|
s_ready[pi] <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
owner <= OWNER_NONE;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// M8 testbench (docs/v2-description.md §15/§16/§20):
|
||||||
|
// neural_multiprocessor.v -- dataflow_core.v (M7, unmodified) sharing
|
||||||
|
// the REAL, UNMODIFIED V1 PSRAM backend chain (int8_memory_access ->
|
||||||
|
// memory_interface -> psram_controller -> real psram_model) across
|
||||||
|
// N_SLOTS=2 concurrent memory_manager instances for the first time,
|
||||||
|
// through the new slot_mem_arbiter.v (M8).
|
||||||
|
//
|
||||||
|
// Same DAG shape as tb_dataflow_core.v (M7's own test), REPLACING the
|
||||||
|
// per-slot behavioral memories with the single real PSRAM chain --
|
||||||
|
// this is the actual M8 measurement: does real arbitration/contention
|
||||||
|
// across genuinely-concurrent slots work correctly against real PSRAM
|
||||||
|
// timing (not an idealized 2-cycle behavioral model)?
|
||||||
|
//
|
||||||
|
// node0 (x=2,w=3,8in -> acc=48) --+
|
||||||
|
// +--> node2 (x=1,w=5,8in -> acc=40)
|
||||||
|
// node1 (x=1,w=1,8in -> acc=8) --+
|
||||||
|
//
|
||||||
|
// node0 and node1 are registered back-to-back with NO dependencies,
|
||||||
|
// so both are dispatched to the two available slots essentially
|
||||||
|
// simultaneously -- both memory_manager instances will genuinely
|
||||||
|
// contend for the one real PSRAM port at the same time, exercising
|
||||||
|
// slot_mem_arbiter.v's arbitration for real (not just in isolation).
|
||||||
|
//
|
||||||
|
// Verified with Verilator (decisions.log DEC-0004).
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
module tb;
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = 23;
|
||||||
|
localparam DATA_WIDTH = 8;
|
||||||
|
localparam P_IN = 8;
|
||||||
|
localparam ACC_WIDTH = 32;
|
||||||
|
localparam N_SLOTS = 2;
|
||||||
|
localparam N_NODES = 8;
|
||||||
|
localparam MAX_DEPS = 4;
|
||||||
|
localparam QUEUE_DEPTH = 4;
|
||||||
|
localparam NODE_IDW = $clog2(N_NODES);
|
||||||
|
localparam PSRAM_DATA_WIDTH = 16;
|
||||||
|
localparam CLK_PERIOD = 12.5; // 80 MHz, matches psram_controller's CLK_FREQ_MHZ
|
||||||
|
|
||||||
|
reg clk, rst;
|
||||||
|
initial begin clk = 1'b0; forever #(CLK_PERIOD/2.0) clk = ~clk; end
|
||||||
|
|
||||||
|
reg reg_valid;
|
||||||
|
wire reg_ready;
|
||||||
|
reg [NODE_IDW-1:0] reg_node_id;
|
||||||
|
reg [$clog2(MAX_DEPS+1)-1:0] reg_required;
|
||||||
|
reg [MAX_DEPS*NODE_IDW-1:0] reg_producer_ids;
|
||||||
|
reg [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
|
||||||
|
reg [15:0] reg_n_tiles;
|
||||||
|
|
||||||
|
wire [ADDR_WIDTH-1:0] psram_a;
|
||||||
|
wire [PSRAM_DATA_WIDTH-1:0] psram_dq;
|
||||||
|
wire psram_ce_n, psram_oe_n, psram_we_n, psram_lb_n, psram_ub_n, psram_zz_n;
|
||||||
|
|
||||||
|
neural_multiprocessor #(
|
||||||
|
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||||
|
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||||
|
.PSRAM_DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(80)
|
||||||
|
) u_nmp (
|
||||||
|
.clk(clk), .rst(rst),
|
||||||
|
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||||
|
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||||
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||||
|
.reg_result_addr(reg_result_addr),
|
||||||
|
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||||
|
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||||
|
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||||
|
);
|
||||||
|
|
||||||
|
psram_model #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .DEPTH(16384)) u_psram (
|
||||||
|
.clk(clk), .a(psram_a), .dq(psram_dq),
|
||||||
|
.ce_n(psram_ce_n), .oe_n(psram_oe_n), .we_n(psram_we_n),
|
||||||
|
.lb_n(psram_lb_n), .ub_n(psram_ub_n), .zz_n(psram_zz_n)
|
||||||
|
);
|
||||||
|
|
||||||
|
task automatic poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input [7:0] val);
|
||||||
|
reg [ADDR_WIDTH-2:0] word_addr;
|
||||||
|
begin
|
||||||
|
word_addr = byte_addr[ADDR_WIDTH-1:1];
|
||||||
|
if (byte_addr[0] == 1'b0)
|
||||||
|
u_psram.mem[word_addr][7:0] = val;
|
||||||
|
else
|
||||||
|
u_psram.mem[word_addr][15:8] = val;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr);
|
||||||
|
reg [ADDR_WIDTH-2:0] word_addr;
|
||||||
|
begin
|
||||||
|
word_addr = byte_addr[ADDR_WIDTH-1:1];
|
||||||
|
peek_byte = (byte_addr[0] == 1'b0) ? u_psram.mem[word_addr][7:0] : u_psram.mem[word_addr][15:8];
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
task automatic register_node(
|
||||||
|
input [NODE_IDW-1:0] nid,
|
||||||
|
input [$clog2(MAX_DEPS+1)-1:0] required,
|
||||||
|
input [NODE_IDW-1:0] p0, input [NODE_IDW-1:0] p1,
|
||||||
|
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
|
||||||
|
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr
|
||||||
|
);
|
||||||
|
begin
|
||||||
|
@(posedge clk);
|
||||||
|
reg_node_id = nid;
|
||||||
|
reg_required = required;
|
||||||
|
reg_producer_ids = {NODE_IDW*MAX_DEPS{1'b0}};
|
||||||
|
reg_producer_ids[0*NODE_IDW +: NODE_IDW] = p0;
|
||||||
|
reg_producer_ids[1*NODE_IDW +: NODE_IDW] = p1;
|
||||||
|
reg_x_base = xb; reg_w_base = wb; reg_n_tiles = nt; reg_result_addr = resaddr;
|
||||||
|
reg_valid = 1'b1;
|
||||||
|
while (!reg_ready) @(posedge clk);
|
||||||
|
@(posedge clk);
|
||||||
|
reg_valid = 1'b0;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
integer errors, tests;
|
||||||
|
integer i, wd;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
errors = 0; tests = 0;
|
||||||
|
rst = 1; reg_valid = 0; reg_node_id = 0; reg_required = 0; reg_producer_ids = 0;
|
||||||
|
reg_x_base = 0; reg_w_base = 0; reg_n_tiles = 0; reg_result_addr = 0;
|
||||||
|
repeat(5) @(posedge clk);
|
||||||
|
rst = 0;
|
||||||
|
|
||||||
|
// Real PSRAM power-up sequence (~150us @ 80MHz) -- same
|
||||||
|
// requirement/convention as tb_memory_manager.v (M4).
|
||||||
|
wait (u_nmp.u_psram_ctrl.state == u_nmp.u_psram_ctrl.STATE_IDLE);
|
||||||
|
@(posedge clk);
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i = i + 1) begin
|
||||||
|
poke_byte(23'h10+i, 8'sd2); poke_byte(23'h20+i, 8'sd3); // node0: x=2,w=3
|
||||||
|
poke_byte(23'h30+i, 8'sd1); poke_byte(23'h40+i, 8'sd1); // node1: x=1,w=1
|
||||||
|
poke_byte(23'h50+i, 8'sd1); poke_byte(23'h60+i, 8'sd5); // node2: x=1,w=5
|
||||||
|
end
|
||||||
|
poke_byte(23'h70, 8'sd0); poke_byte(23'h71, 8'sd0); poke_byte(23'h72, 8'sd0);
|
||||||
|
|
||||||
|
// node0, node1: no dependencies -- dispatched back-to-back, so
|
||||||
|
// BOTH slots start genuinely concurrent PSRAM traffic through
|
||||||
|
// the shared arbiter at essentially the same time.
|
||||||
|
register_node(0, 0, 0, 0, 23'h10, 23'h20, 16'd1, 23'h70);
|
||||||
|
register_node(1, 0, 0, 0, 23'h30, 23'h40, 16'd1, 23'h71);
|
||||||
|
register_node(2, 2, 0, 1, 23'h50, 23'h60, 16'd1, 23'h72);
|
||||||
|
|
||||||
|
tests = tests + 1;
|
||||||
|
wd = 0;
|
||||||
|
while ((peek_byte(23'h70)==0 || peek_byte(23'h71)==0) && wd < 20000) begin
|
||||||
|
if (peek_byte(23'h72) !== 8'sd0) begin
|
||||||
|
$display("FAIL: node2 completed before both node0 and node1 finished");
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
@(posedge clk); wd = wd + 1;
|
||||||
|
end
|
||||||
|
$display("PASS: node2 did not complete before both its dependencies did (checked every cycle up to wd=%0d)", wd);
|
||||||
|
|
||||||
|
wd = 0;
|
||||||
|
while (peek_byte(23'h72)==0 && wd < 20000) begin @(posedge clk); wd = wd + 1; end
|
||||||
|
repeat(10) @(posedge clk);
|
||||||
|
|
||||||
|
tests = tests + 3;
|
||||||
|
if (peek_byte(23'h70) !== 8'sd48) begin
|
||||||
|
$display("FAIL node0: result=%0d expected 48", peek_byte(23'h70));
|
||||||
|
errors = errors + 1;
|
||||||
|
end else $display("PASS node0: result=48 via real PSRAM + shared arbiter");
|
||||||
|
|
||||||
|
if (peek_byte(23'h71) !== 8'sd8) begin
|
||||||
|
$display("FAIL node1: result=%0d expected 8", peek_byte(23'h71));
|
||||||
|
errors = errors + 1;
|
||||||
|
end else $display("PASS node1: result=8 via real PSRAM + shared arbiter (concurrent with node0)");
|
||||||
|
|
||||||
|
if (peek_byte(23'h72) !== 8'sd40) begin
|
||||||
|
$display("FAIL node2: result=%0d expected 40", peek_byte(23'h72));
|
||||||
|
errors = errors + 1;
|
||||||
|
end else $display("PASS node2: result=40, dispatched only after BOTH producers genuinely completed, real PSRAM end-to-end");
|
||||||
|
|
||||||
|
$display("========================================");
|
||||||
|
if (errors == 0)
|
||||||
|
$display("ALL %0d TESTS PASSED (neural_multiprocessor, real V1 PSRAM chain shared across N_SLOTS=%0d via slot_mem_arbiter)", tests, N_SLOTS);
|
||||||
|
else
|
||||||
|
$display("FAILED: %0d/%0d test(s) had errors -- see messages above", errors, tests);
|
||||||
|
$display("========================================");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user