feat(v2): M4 Memory Manager + Prefetch Engine, real V1 PSRAM backend

Implements M4: memory_manager.v (arbitration/buffering/forwarding/
latency hiding/double buffering, §12) + prefetch_engine.v
(double-buffered tile fetch, §13), sitting on the REAL, UNMODIFIED V1
PSRAM backend chain (int8_memory_access.v -> memory_interface.v ->
psram_controller.v, per §15's explicit mandate not to touch the
controller).

Verified fully end-to-end with Verilator: real neural_processor (M1)
fed entirely by memory_manager, computing against PSRAM-resident X/W
tiles (double-buffered prefetch across up to 5 tiles) and writing its
result back to PSRAM -- checked via an independent PSRAM read-back,
with poison bytes around the operand regions to catch addressing
errors. 3/3 jobs pass (1/3/5-tile configurations).

Three real RTL bugs found and fixed during integration (full
diagnostic trail in errors.log ERR-0006): prefetch_engine had no
single-in-flight-request discipline, letting a queued request corrupt
the bank bookkeeping of a fetch already running; the fix's own
!pf_busy guard had a one-cycle blind spot (pf_busy lags pf_start by a
clock) that needed an explicit !pf_start term; and a state-based mux
for the shared backend port was off by one cycle, silently dropping
the PSRAM result write entirely.

Real synthesis: 0 CHECK problems, 851 LUT4/789 FF/108 CCU2C/0 DSP
(expected, no multiplication in this module). Real place&route (via a
synthesis-only timing harness, needed for the same TRELLIS_IO pin-
budget reason as M2's array): Fmax 165.86 MHz, PASS at 80MHz.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-05 14:39:29 +02:00
co-authored by Claude Sonnet 5
parent 5f0d7f101c
commit 175f697ae1
20 changed files with 137935 additions and 2 deletions
+5 -2
View File
@@ -22,8 +22,11 @@ reali, non solo scritto).
`result_buffer.v`). Tutti inferiscono DP16KD reale (10/10 test, `result_buffer.v`). Tutti inferiscono DP16KD reale (10/10 test,
6/6 config sintetizzate 0 problemi). Scoperta: il costo BRAM di 6/6 config sintetizzate 0 problemi). Scoperta: il costo BRAM di
weight_buffer e' guidato da P_IN (larghezza), non da DEPTH. weight_buffer e' guidato da P_IN (larghezza), non da DEPTH.
- [ ] **M4 — Memory Manager** (`memory_manager.v`, `prefetch_engine.v`), - [x] **M4 — Memory Manager** (`memory_manager.v`, `prefetch_engine.v`),
backend PSRAM V1 riusato senza modifiche. backend PSRAM V1 riusato SENZA MODIFICHE. End-to-end reale (3/3
job PASS) con vero neural_processor + vera catena PSRAM V1.
3 bug RTL trovati/risolti (`logs/errors.log` ERR-0006). Fmax
165.86 MHz.
- [ ] **M5 — Neural Director** (`neural_director.v`), scheduling first-free. - [ ] **M5 — Neural Director** (`neural_director.v`), scheduling first-free.
- [ ] **M6 — Dependency Manager** (`dependency_manager.v`), ready/waiting - [ ] **M6 — Dependency Manager** (`dependency_manager.v`), ready/waiting
queue, dependency counters, wake-up, producer tracking. queue, dependency counters, wake-up, producer tracking.
+15
View File
@@ -59,3 +59,18 @@ wide) scale as expected with depth. Confirms §14's warning literally:
"non assumere che buffer piu' grandi siano automaticamente migliori" "non assumere che buffer piu' grandi siano automaticamente migliori"
-- here, smaller was not cheaper either, because depth was the wrong -- here, smaller was not cheaper either, because depth was the wrong
lever for this specific buffer's cost. lever for this specific buffer's cost.
[2026-09-05] M4 Memory Manager + Prefetch Engine (standalone resource
count; Fmax via timing harness -- see errors.log ERR-0005)
| Module | Fmax (POST-P&R) | LUT | FF | DSP | CCU2C |
|----------------------------------|------------------|-----|-----|-----|-------|
| memory_manager + prefetch_engine | 165.86 MHz | 851 | 789 | 0 | 108 |
End-to-end (real PSRAM + real neural_processor, SIMULATED only, no
system-level P&R yet -- deferred to M7/M9): 3-tile job = 446 cycles,
1-tile job = 166 cycles, 5-tile job = 728 cycles. ~140-150 cycles/tile,
dominated by psram_model.v's real ~70ns TAA access latency, not by
memory_manager's own control overhead (its bank-swap turnaround is
documented as a fixed +1 cycle/tile in decisions.log DEC-0006, a small
fraction of the ~140-cycle PSRAM-dominated total).
+71
View File
@@ -248,3 +248,74 @@ not merely a stylistic choice.
STATUS: STATUS:
ACCEPTED 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
+27
View File
@@ -110,3 +110,30 @@ decision: vedi benchmark.log -- il dimensionamento di weight_buffer
andra' guidato da P_IN, non solo da DEPTH, quando si arrivera' a andra' guidato da P_IN, non solo da DEPTH, quando si arrivera' a
M4/M9. M4/M9.
next_action: M4 -- memory_manager.v + prefetch_engine.v. next_action: M4 -- memory_manager.v + prefetch_engine.v.
[2026-09-05T16:00:00Z] commit=5f0d7f1 session=v2-M4-memory-manager
module: hardware/v2/rtl/memory_manager.v, prefetch_engine.v
action: implementato M4 -- Memory Manager (arbitraggio/buffering/
forwarding/gestione latenza/double buffering, §12) + Prefetch Engine
(fetch a doppio buffer con retargeting di una singola istanza per
banco, §13). Backend PSRAM V1 riusato SENZA MODIFICHE
(int8_memory_access.v -> memory_interface.v -> psram_controller.v,
§15), integrato end-to-end con un vero hardware/v2/rtl/
neural_processor.v (M1).
reason: roadmap M4.
result: 3/3 job PASS end-to-end (1/3/5 tile), risultato verificato con
RILETTURA INDIPENDENTE da PSRAM (non solo ispezione di segnali
interni), con byte "poison" attorno alle regioni operando per
catturare eventuali errori di indirizzamento off-by-one (nessuno
trovato). 3 bug RTL reali trovati e risolti durante l'integrazione
(vedi errors.log ERR-0006): mancava una disciplina "una sola
richiesta di prefetch in volo", un buco di un ciclo nel check
!pf_busy, un mux di stato disallineato di un ciclo che faceva
silenziosamente perdere la scrittura del risultato su PSRAM.
Sintesi reale: 0 problemi, 851 LUT4/789 FF/108 CCU2C/0 DSP (atteso).
Fmax reale (via harness, stesso motivo pin-count di ERR-0005):
165.86 MHz, PASS a 80MHz.
errors: vedi errors.log ERR-0005 (ricorrenza), ERR-0006 (3 bug nuovi).
decision: vedi decisions.log DEC-0006 (motore di prefetch singolo +
registro pendente, nessun arbitro backend ancora necessario).
next_action: M5 -- neural_director.v, scheduling first-free.
+52
View File
@@ -124,3 +124,55 @@ WORKAROUND: hardware/v2/synthesis/harness_neural_processor_array.v --
STATUS: WORKED AROUND. Will become moot once M4/M5 exist and the array STATUS: WORKED AROUND. Will become moot once M4/M5 exist and the array
is synthesized as part of a larger design with on-chip ports instead is synthesized as part of a larger design with on-chip ports instead
of a bare top-level module. of a bare top-level module.
ERR-0006 (real RTL bugs in hardware/v2/rtl/memory_manager.v, FOUND AND FIXED)
DATE: 2026-09-05
MODULE: hardware/v2/rtl/memory_manager.v
SYMPTOM: end-to-end M4 testbench (real V1 PSRAM backend + real M1
neural_processor) hung permanently partway through the first
multi-tile job -- bank_ready for the "current" bank never became 1,
even though the byte-level backend clearly kept completing read
transactions (observed via cycle-by-cycle hierarchical tracing of
memory_manager/prefetch_engine internal state).
ROOT CAUSES (three, found together during the same debugging session):
1. The tile-N+2 prefetch request, queued on tile-N's handoff, could
be issued (pf_start asserted) while prefetch_engine was STILL
mid-fetch for tile-N+1 -- there was no single-in-flight-request
discipline at all in the first draft. Fixed by adding a
single-entry pf_pending register: requests are queued, not
issued directly, and a dedicated rule launches the queued
request only once the engine reports free.
2. Even with that queue, pf_busy does not read 1 until the cycle
AFTER pf_start was first observed by prefetch_engine (its own
fetch_busy<=1 lags its own fetch_start sampling by one clock) --
checking only `!pf_busy` left a genuine one-cycle window where a
second queued request would fire on top of the one just
launched, silently overwriting pf_target_bank (and pf_x_addr/
pf_w_addr) for the fetch already in flight. The address corruption
was harmless (prefetch_engine had already latched the correct
address into its own state that same edge), but pf_target_bank
corruption meant the eventually-completed fetch's real data got
filed into the WRONG bank's bank_ready/bank_x/bank_w, permanently
starving the bank actually needed next. Fixed by gating the issue
rule on `!pf_busy && !pf_start` (the extra term closes exactly
this one-cycle window).
3. A combinational mux selecting between prefetch_engine's own
backend wires and the result-write-back FSM's wires was gated on
`state == MM_WRITE_RESULT`, but wr_mem_req (asserted while
state==MM_WRITE_RESULT) only becomes valid the FOLLOWING cycle,
i.e. while state==MM_DONE -- the mux therefore selected the wrong
source for the one cycle the write request pulse was actually
high, silently dropping the PSRAM write entirely. Fixed by
widening the mux's select condition to cover both states.
DIAGNOSIS METHOD: cycle-by-cycle hierarchical signal dumps (mm.state,
tile_idx, bank_ready, pf_busy, pf_pending, pf_target_bank,
prefetch_engine's own state) under Verilator, printed only on
signal-change to keep the trace readable, comparing against the
hand-derived expected sequence of events for a 3-tile job.
VERIFICATION: hardware/v2/sim/tb_memory_manager.v -- 3/3 tests PASS
after all three fixes, including a 5-tile job (steady-state
double-buffer swap across more than 2 tiles) and independent
PSRAM read-back of the written-back result byte (not just internal
signal inspection).
STATUS: FIXED, verified end-to-end with the real (unmodified) V1
PSRAM backend chain and a real M1 neural_processor.
+56
View File
@@ -218,3 +218,59 @@ decision: keep DEPTH parametric as specified, but document (this
next_action: M4 -- memory_manager.v + prefetch_engine.v (wires these next_action: M4 -- memory_manager.v + prefetch_engine.v (wires these
three buffers + the array together, PSRAM backend reused unmodified three buffers + the array together, PSRAM backend reused unmodified
from V1 per §15). from V1 per §15).
EXP-0005
timestamp: 2026-09-05T16:00:00Z
git_commit: 5f0d7f1 (+ uncommitted M4 work)
session: v2-M4-memory-manager
module: hardware/v2/rtl/memory_manager.v, prefetch_engine.v
configuration: P_IN=8, ADDR_WIDTH=23, real V1 backend chain
(int8_memory_access -> memory_interface -> psram_controller ->
psram_model, ALL unmodified), real M1 neural_processor
action: M4 -- end-to-end integration: memory_manager double-buffers
prefetch of X/W tiles from PSRAM, feeds a real neural_processor,
writes the computed result back to PSRAM.
command (sim, Verilator): verilator --binary --timing -j 0 -Wno-fatal
--top-module tb -o /tmp/vtb_mm hardware/v1/rtl/int8_memory_access.v
hardware/v1/rtl/memory_interface.v hardware/v1/rtl/psram_controller.v
hardware/v1/sim/psram_model.v hardware/v2/rtl/neural_processor.v
hardware/v2/rtl/prefetch_engine.v hardware/v2/rtl/memory_manager.v
hardware/v2/sim/tb_memory_manager.v && /tmp/vtb_mm
command (synth): yosys -p "synth_ecp5 -json .../top.json -top
memory_manager" hardware/v2/rtl/memory_manager.v
hardware/v2/rtl/prefetch_engine.v (standalone, for real resource
counts); harness_memory_manager.v (see errors.log ERR-0005 pattern)
for real P&R Fmax, since the bare module exceeds the device's
TRELLIS_IO budget as a top-level (same class of artifact as the
Processor Array, not a logic limit).
result:
SIMULATED: 3/3 jobs PASS end-to-end -- 3-tile (bias=0, ACT_RELU,
saturates to 127), 1-tile (no saturation, y=32), and 5-tile
(steady-state double-buffer swap across more than 2 tiles, y=40)
-- each verified by an INDEPENDENT PSRAM read-back of the
written result byte (not just internal signal inspection), with
"poison" bytes surrounding the real operand regions to catch any
off-by-one addressing (none found).
Cycle counts (real, PSRAM power-up already excluded): 3-tile job =
446 cycles, 1-tile job = 166 cycles, 5-tile job = 728 cycles.
Roughly ~140-150 cycles/tile at this PSRAM timing (dominated by
the real ~70ns TAA access latency modeled in psram_model.v, not
by memory_manager's own control overhead) -- a real, measured
number, not estimated.
SYNTHESIZED (standalone, real resource count): 0 CHECK problems,
851 LUT4, 789 TRELLIS_FF, 108 CCU2C, 0 DSP (expected -- no
multiplication in this module).
POST-P&R (via harness, real Fmax): 165.86 MHz, PASS at 80MHz with
large margin.
errors: ERR-0006 (three real RTL bugs found and fixed during bring-up
-- see errors.log for full detail: a missing single-in-flight-
request discipline, a one-cycle pf_busy blind spot, and an
off-by-one state mux for the write-back path). ERR-0005's pin-count
artifact recurred for this module too (worked around the same way).
decision: see decisions.log DEC-0006 (single prefetch engine + pending
register is sufficient for this milestone's scope; a real backend
arbiter is deferred until multiple processors/jobs actually need to
share one memory_manager).
next_action: M5 -- neural_director.v (first-free scheduling), wiring
job dispatch to potentially multiple (memory_manager, neural_
processor) pairs instead of the single hardcoded pair tested here.
+12
View File
@@ -36,3 +36,15 @@ vectors: extreme INT8 (-128, 127, -100, 100), regular values, full
writes undisturbed writes undisturbed
simulator: Verilator 5.050 (--binary --timing) simulator: Verilator 5.050 (--binary --timing)
PASS/FAIL: 10/10 PASS PASS/FAIL: 10/10 PASS
[2026-09-05] EXP-0005 -- hardware/v2/sim/tb_memory_manager.v
test: 3 end-to-end jobs (3-tile/saturating, 1-tile/non-saturating,
5-tile/steady-state-swap), real V1 PSRAM backend chain (unmodified),
real M1 neural_processor
simulator: Verilator 5.050 (--binary --timing)
PASS/FAIL: 3/3 PASS
bit-exact result: PSRAM-read-back result byte matches hand-computed
expectation in every case (independent oracle, not derived from the
RTL under test)
cycles: 446 (3 tiles), 166 (1 tile), 728 (5 tiles) -- real PSRAM
latency dominates, not memory_manager's own control overhead
+4
View File
@@ -36,3 +36,7 @@ result_buffer D=4096: LUT=37 FF=30 DP16KD=2 (D=256: LUT=21 FF=26 DP16KD=1)
CHECK: 0 problems, all 6 configs correctly infer DP16KD (no LUT-RAM CHECK: 0 problems, all 6 configs correctly infer DP16KD (no LUT-RAM
fallback). weight_buffer's DP16KD count does NOT drop with depth fallback). weight_buffer's DP16KD count does NOT drop with depth
(width-bound, not depth-bound -- see decisions.log / benchmark.log). (width-bound, not depth-bound -- see decisions.log / benchmark.log).
[2026-09-05] EXP-0005 -- memory_manager + prefetch_engine (standalone)
LUT4=851 TRELLIS_FF=789 CCU2C=108 MULT18X18D=0 (expected, no
multiplication in this module). CHECK: 0 problems.
+6
View File
@@ -38,3 +38,9 @@ weight_buffer (D=512): Fmax=339.67 MHz PASS
result_buffer (D=4096): Fmax=325.20 MHz PASS result_buffer (D=4096): Fmax=325.20 MHz PASS
All far above the 80MHz target -- buffers are not a timing concern in All far above the 80MHz target -- buffers are not a timing concern in
isolation at these depths. isolation at these depths.
[2026-09-05] EXP-0005 -- memory_manager + prefetch_engine (via
harness_memory_manager.v, see errors.log ERR-0005 for why a harness
was needed), real nextpnr-ecp5 --45k --package CABGA381 --speed 8
--freq 80 --lpf-allow-unconstrained
Fmax: 165.86 MHz -- PASS at 80MHz (real place&route measurement)
+313
View File
@@ -0,0 +1,313 @@
`timescale 1ns/1ps
// ================================================================
// FPGA-Neural V2 -- Memory Manager (M4, docs/v2-description.md §12/§15)
//
// Sits between a single Neural Processor (M1) and the byte-level
// Memory Backend Interface (hardware/v1/rtl/int8_memory_access.v,
// reused UNMODIFIED, per §15 -- "NON iniziare modificando il
// controller PSRAM. Mantenere inizialmente il backend esistente").
// The processor sees only "data available" (operand_valid/ready,
// tile_last) -- never PSRAM request/wait cycles directly (§12).
//
// Double-buffered prefetch (§13): while the processor consumes tile
// N from bank "current", this module retargets the single
// prefetch_engine instance (M4) at bank "next" to fetch tile N+1
// concurrently. On tile handoff, banks swap; if a bank isn't ready in
// time (prefetch slower than compute for this run), operand_valid
// simply stays low until it is -- a real stall, not hidden, so its
// frequency is genuinely measurable (§22, deferred to M9). NOTE
// (measured characteristic, not yet optimized -- see
// hardware/v2/logs/decisions.log DEC-0006): the bank-swap-and-check
// control path itself 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 -- a real,
// deliberately-not-hidden overhead of this first Memory Manager
// implementation, left for M10 (Optimization) to revisit with real
// stall-percentage data (§22) rather than optimized blindly now.
//
// One job = one neuron's worth of tiles (n_tiles), read from x_base/
// w_base (PSRAM byte addresses), followed by writing the single
// INT8 result back to result_addr. The result write only happens
// after the last tile has been handed off and prefetch_engine is
// idle (temporally disjoint from prefetching by construction), so no
// separate backend arbiter is needed at this milestone -- see
// decisions.log DEC-0006 for why, and what changes once multiple
// concurrent jobs/processors need to share one backend port
// (deferred, not yet needed).
// ================================================================
module memory_manager #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ADDR_WIDTH = 23
)(
input wire clk,
input wire rst,
// ---- job control (from a future Neural Director, M5; driven
// directly by a testbench at M4) ----
input wire job_start,
input wire [ADDR_WIDTH-1:0] x_base,
input wire [ADDR_WIDTH-1:0] w_base,
input wire [15:0] n_tiles,
input wire [ADDR_WIDTH-1:0] result_addr,
output reg job_done, // one-cycle pulse
// ---- Neural Processor-facing operand stream (mirrors
// neural_processor.v's own operand port exactly) ----
output reg operand_valid,
input wire operand_ready,
output reg signed [DATA_WIDTH*P_IN-1:0] input_data,
output reg signed [DATA_WIDTH*P_IN-1:0] weight_data,
output reg tile_last,
// ---- Neural Processor-facing result consumption ----
input wire result_valid,
output reg result_ready,
input wire signed [DATA_WIDTH-1:0] result_data,
// ---- Memory Backend Interface (matches int8_memory_access.v) ----
output wire mem_req,
output wire mem_wr,
output wire [ADDR_WIDTH-1:0] mem_addr,
output wire signed [7:0] mem_wdata,
input wire signed [7:0] mem_rdata,
input wire mem_ready
);
localparam MM_IDLE = 3'd0;
localparam MM_PREFETCH_FIRST = 3'd1;
localparam MM_STREAM = 3'd2;
localparam MM_WAIT_RESULT = 3'd3;
localparam MM_WRITE_RESULT = 3'd4;
localparam MM_DONE = 3'd5;
reg [2:0] state;
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
reg [15:0] n_tiles_reg;
reg [15:0] tile_idx; // tile currently presented (bank `current`)
reg current_bank; // 0 or 1
reg [1:0] bank_ready; // bank_ready[b] = bank b holds valid, unconsumed prefetched data
// ---- double-buffer storage (owned here, filled by prefetch_engine) ----
reg signed [DATA_WIDTH*P_IN-1:0] bank_x [0:1];
reg signed [DATA_WIDTH*P_IN-1:0] bank_w [0:1];
// ---- single prefetch_engine instance, retargeted per bank ----
reg pf_start;
reg [ADDR_WIDTH-1:0] pf_x_addr, pf_w_addr;
wire pf_busy, pf_done;
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile_x, pf_tile_w;
reg pf_target_bank; // which bank the CURRENTLY-running (or just-launched) prefetch fills
// Single-entry pending-request register: prefetch_engine is one
// instance, so a NEW fetch can only be launched once it has
// genuinely returned to idle (pf_busy low) -- issuing pf_start
// while it is still mid-fetch would silently corrupt
// pf_target_bank for the fetch ALREADY in flight (a real bug
// found and fixed here -- see hardware/v2/logs/errors.log
// ERR-0006). Every "kick a prefetch" site below sets this
// descriptor instead of touching pf_start directly; a single
// always-active rule issues pf_start once the engine is free.
reg pf_pending;
reg [ADDR_WIDTH-1:0] pf_pending_x, pf_pending_w;
reg pf_pending_bank;
// prefetch_engine drives its OWN internal backend wires; the
// result-write FSM below drives its own. A combinational mux
// (never both at once, by construction -- see file header)
// selects which one actually reaches the real output port,
// avoiding a two-driver conflict on mem_req/mem_wr/mem_addr/
// mem_wdata.
wire pf_mem_req, pf_mem_wr;
wire [ADDR_WIDTH-1:0] pf_mem_addr;
wire signed [7:0] pf_mem_wdata;
prefetch_engine #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
) u_prefetch (
.clk(clk), .rst(rst),
.fetch_start(pf_start), .x_addr(pf_x_addr), .w_addr(pf_w_addr),
.fetch_busy(pf_busy), .fetch_done(pf_done),
.tile_x(pf_tile_x), .tile_w(pf_tile_w),
.mem_req(pf_mem_req), .mem_wr(pf_mem_wr), .mem_addr(pf_mem_addr), .mem_wdata(pf_mem_wdata),
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
);
reg wr_mem_req;
reg [ADDR_WIDTH-1:0] wr_mem_addr;
reg signed [7:0] wr_mem_wdata;
// wr_mem_req is SET while state==MM_WRITE_RESULT but only becomes
// valid (via NBA) the FOLLOWING cycle, i.e. while state==MM_DONE --
// the mux must select the write-back source across BOTH states,
// not just the one that issues it (an off-by-one here silently
// dropped the write request entirely -- found and fixed here, see
// hardware/v2/logs/errors.log ERR-0006).
wire wr_active = (state == MM_WRITE_RESULT) || (state == MM_DONE);
assign mem_req = wr_active ? wr_mem_req : pf_mem_req;
assign mem_wr = wr_active ? 1'b1 : pf_mem_wr;
assign mem_addr = wr_active ? wr_mem_addr : pf_mem_addr;
assign mem_wdata = wr_active ? wr_mem_wdata : pf_mem_wdata;
always @(posedge clk) begin
if (rst) begin
state <= MM_IDLE;
job_done <= 1'b0;
operand_valid <= 1'b0;
tile_last <= 1'b0;
input_data <= {DATA_WIDTH*P_IN{1'b0}};
weight_data <= {DATA_WIDTH*P_IN{1'b0}};
result_ready <= 1'b0;
pf_start <= 1'b0;
current_bank <= 1'b0;
bank_ready <= 2'b00;
tile_idx <= 16'h0;
wr_mem_req <= 1'b0;
wr_mem_addr <= {ADDR_WIDTH{1'b0}};
wr_mem_wdata <= 8'sd0;
pf_pending <= 1'b0;
end else begin
job_done <= 1'b0;
pf_start <= 1'b0;
result_ready <= 1'b0;
// Latch a completed prefetch into its target bank.
if (pf_done) begin
bank_x[pf_target_bank] <= pf_tile_x;
bank_w[pf_target_bank] <= pf_tile_w;
bank_ready[pf_target_bank] <= 1'b1;
end
// Issue a pending fetch request as soon as the (single)
// prefetch engine is genuinely free. The `!pf_start` guard
// is required, not cosmetic: pf_busy does not read 1 until
// the cycle AFTER pf_start was first observed (prefetch_
// engine's own fetch_busy<=1 is one clock behind its own
// fetch_start sampling), so checking !pf_busy alone leaves
// a genuine one-cycle window where a second pending
// request would fire on top of the one just launched,
// silently corrupting pf_target_bank for the fetch already
// in flight (found and fixed here -- see
// hardware/v2/logs/errors.log ERR-0006).
if (pf_pending && !pf_busy && !pf_start) begin
pf_start <= 1'b1;
pf_x_addr <= pf_pending_x;
pf_w_addr <= pf_pending_w;
pf_target_bank <= pf_pending_bank;
pf_pending <= 1'b0;
end
case (state)
MM_IDLE: begin
if (job_start) begin
x_base_reg <= x_base;
w_base_reg <= w_base;
n_tiles_reg <= n_tiles;
result_addr_reg <= result_addr;
tile_idx <= 16'h0;
current_bank <= 1'b0;
bank_ready <= 2'b00;
operand_valid <= 1'b0;
// kick off the very first fetch (tile 0 into bank 0)
pf_pending <= 1'b1;
pf_pending_x <= x_base;
pf_pending_w <= w_base;
pf_pending_bank <= 1'b0;
state <= MM_PREFETCH_FIRST;
end
end
MM_PREFETCH_FIRST: begin
if (bank_ready[0] || (pf_done && pf_target_bank == 1'b0)) begin
// Present tile 0; concurrently start prefetching
// tile 1 into bank 1, if there is one.
operand_valid <= 1'b1;
input_data <= pf_done ? pf_tile_x : bank_x[0];
weight_data <= pf_done ? pf_tile_w : bank_w[0];
tile_last <= (n_tiles_reg == 16'h1);
if (n_tiles_reg > 16'h1) begin
pf_pending <= 1'b1;
pf_pending_x <= x_base_reg + P_IN[ADDR_WIDTH-1:0];
pf_pending_w <= w_base_reg + P_IN[ADDR_WIDTH-1:0];
pf_pending_bank <= 1'b1;
end
state <= MM_STREAM;
end
end
MM_STREAM: begin
if (operand_valid && operand_ready) begin
// This tile consumed; free its bank, swap.
bank_ready[current_bank] <= 1'b0;
current_bank <= ~current_bank;
tile_idx <= tile_idx + 16'h1;
operand_valid <= 1'b0; // re-asserted below once the new bank is ready
if (tile_idx + 16'h1 == n_tiles_reg) begin
// That was the last tile -- nothing more to present.
state <= MM_WAIT_RESULT;
end else if (tile_idx + 16'h2 < n_tiles_reg) begin
// Queue a prefetch for the tile AFTER next into
// the bank we just freed (current_bank, pre-swap)
// -- it will actually launch once the (single)
// prefetch engine is free (see the pf_pending
// issue rule above); it is very likely still
// busy with the tile-N+1 fetch kicked off on the
// PREVIOUS handoff, so this almost always queues
// rather than launching immediately.
pf_pending <= 1'b1;
pf_pending_x <= x_base_reg + (tile_idx + 16'h2) * P_IN[ADDR_WIDTH-1:0];
pf_pending_w <= w_base_reg + (tile_idx + 16'h2) * P_IN[ADDR_WIDTH-1:0];
pf_pending_bank <= current_bank; // the one just freed
end
end else if (!operand_valid) begin
// Waiting for the new current bank to become ready
// (either just swapped, or a stall still in
// progress).
if (bank_ready[current_bank] && tile_idx < n_tiles_reg) begin
operand_valid <= 1'b1;
input_data <= bank_x[current_bank];
weight_data <= bank_w[current_bank];
tile_last <= (tile_idx == n_tiles_reg - 16'h1);
end
end
end
MM_WAIT_RESULT: begin
result_ready <= 1'b1;
if (result_valid && result_ready) begin
wr_mem_wdata <= result_data;
state <= MM_WRITE_RESULT;
end
end
MM_WRITE_RESULT: begin
// prefetch_engine is guaranteed idle here (no more
// tiles to fetch for this job), so driving the shared
// backend port directly is safe -- see file header.
wr_mem_req <= 1'b1;
wr_mem_addr <= result_addr_reg;
state <= MM_DONE;
end
MM_DONE: begin
wr_mem_req <= 1'b0;
if (mem_ready) begin
job_done <= 1'b1;
state <= MM_IDLE;
end
end
default: state <= MM_IDLE;
endcase
end
end
endmodule
+129
View File
@@ -0,0 +1,129 @@
`timescale 1ns/1ps
// ================================================================
// FPGA-Neural V2 -- Prefetch Engine (M4, docs/v2-description.md §13)
//
// Fetches ONE tile (P_IN activation bytes + P_IN weight bytes) from
// the byte-level Memory Backend Interface into a pair of output
// registers, sequentially (2*P_IN single-byte transactions -- the
// same byte-at-a-time convention hardware/v1/rtl/neuron_memory.v
// already uses against the same backend, reused unmodified here).
//
// This module fetches exactly one tile per fetch_start pulse; the
// double-buffering strategy itself (§13: compute tile N while
// prefetching tile N+1, swap, repeat) is memory_manager.v's
// responsibility -- it retargets this single engine at whichever
// bank currently needs refilling, so no internal arbitration between
// multiple fetch engines sharing the backend port is ever needed.
//
// The backend port (mem_req/mem_wr/mem_addr/mem_wdata/mem_rdata/
// mem_ready) matches hardware/v1/rtl/int8_memory_access.v's contract
// exactly -- this engine can sit directly on top of that unmodified
// V1 module (which itself sits on memory_interface.v ->
// psram_controller.v, also unmodified, per §15).
// ================================================================
module prefetch_engine #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ADDR_WIDTH = 23
)(
input wire clk,
input wire rst,
input wire fetch_start,
input wire [ADDR_WIDTH-1:0] x_addr, // base addr of this tile's P_IN X bytes
input wire [ADDR_WIDTH-1:0] w_addr, // base addr of this tile's P_IN W bytes
output reg fetch_busy,
output reg fetch_done, // one-cycle pulse
output reg signed [DATA_WIDTH*P_IN-1:0] tile_x,
output reg signed [DATA_WIDTH*P_IN-1:0] tile_w,
output reg mem_req,
output reg mem_wr,
output reg [ADDR_WIDTH-1:0] mem_addr,
output reg signed [7:0] mem_wdata,
input wire signed [7:0] mem_rdata,
input wire mem_ready
);
localparam ST_IDLE = 2'd0;
localparam ST_READ_X = 2'd1;
localparam ST_READ_W = 2'd2;
localparam ST_DONE = 2'd3;
reg [1:0] state;
reg [$clog2(P_IN+1)-1:0] byte_idx;
always @(posedge clk) begin
if (rst) begin
state <= ST_IDLE;
byte_idx <= 0;
fetch_busy <= 1'b0;
fetch_done <= 1'b0;
mem_req <= 1'b0;
mem_wr <= 1'b0;
mem_addr <= {ADDR_WIDTH{1'b0}};
mem_wdata <= 8'sd0;
end else begin
mem_req <= 1'b0;
fetch_done <= 1'b0;
case (state)
ST_IDLE: begin
if (fetch_start) begin
fetch_busy <= 1'b1;
byte_idx <= 0;
mem_req <= 1'b1;
mem_wr <= 1'b0;
mem_addr <= x_addr;
state <= ST_READ_X;
end
end
ST_READ_X: begin
if (mem_ready) begin
tile_x[byte_idx*DATA_WIDTH +: DATA_WIDTH] <= mem_rdata;
if (byte_idx == P_IN[$clog2(P_IN+1)-1:0] - 1'b1) begin
byte_idx <= 0;
mem_req <= 1'b1;
mem_wr <= 1'b0;
mem_addr <= w_addr;
state <= ST_READ_W;
end else begin
byte_idx <= byte_idx + 1'b1;
mem_req <= 1'b1;
mem_wr <= 1'b0;
mem_addr <= x_addr + byte_idx + 1'b1;
end
end
end
ST_READ_W: begin
if (mem_ready) begin
tile_w[byte_idx*DATA_WIDTH +: DATA_WIDTH] <= mem_rdata;
if (byte_idx == P_IN[$clog2(P_IN+1)-1:0] - 1'b1) begin
state <= ST_DONE;
end else begin
byte_idx <= byte_idx + 1'b1;
mem_req <= 1'b1;
mem_wr <= 1'b0;
mem_addr <= w_addr + byte_idx + 1'b1;
end
end
end
ST_DONE: begin
fetch_busy <= 1'b0;
fetch_done <= 1'b1;
state <= ST_IDLE;
end
default: state <= ST_IDLE;
endcase
end
end
endmodule
+287
View File
@@ -0,0 +1,287 @@
`timescale 1ns/1ps
// ============================================================
// M4 testbench (docs/v2-description.md §12/§13/§15/§20): full
// end-to-end stack -- memory_manager.v + prefetch_engine.v (V2, M4)
// driving a REAL hardware/v2/rtl/neural_processor.v (M1) on one side,
// and the REAL, UNMODIFIED hardware/v1 PSRAM backend chain
// (int8_memory_access -> memory_interface -> psram_controller ->
// psram_model) on the other -- exactly the layering §15 mandates
// ("Memory Manager -> Memory Backend Interface -> PSRAM Controller"),
// with the backend reused byte-for-byte from the frozen V1 tree.
//
// Verified with Verilator (see decisions.log DEC-0004).
//
// Coverage:
// - end-to-end job: PSRAM pre-loaded with real X/W bytes at known
// addresses, memory_manager fetches them (double-buffered
// prefetch across multiple tiles), feeds neural_processor, and
// writes the computed result back to PSRAM -- read back
// independently afterward and checked against a hand-computed
// expectation (an oracle independent of the RTL under test).
// - multi-tile job (prefetch actually has to overlap tile N+1's
// fetch with tile N's compute, not just single-tile).
// - "poison" bytes surrounding the real operand region, to catch
// any off-by-one addressing error in prefetch_engine.
// ============================================================
module tb;
localparam ADDR_WIDTH = 23;
localparam DATA_WIDTH = 8;
localparam P_IN = 8;
localparam ACC_WIDTH = 32;
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
// ---- memory_manager <-> neural_processor ----
reg job_start;
reg [ADDR_WIDTH-1:0] x_base, w_base, result_addr;
reg [15:0] n_tiles;
wire job_done;
wire mm_operand_valid, mm_operand_ready;
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
wire mm_tile_last;
wire mm_result_valid, mm_result_ready;
wire signed [DATA_WIDTH-1:0] mm_result_data;
// ---- memory_manager <-> int8_memory_access (Memory Backend Interface) ----
wire mem_req, mem_wr;
wire [ADDR_WIDTH-1:0] mem_addr;
wire signed [7:0] mem_wdata;
wire signed [7:0] mem_rdata;
wire mem_ready;
memory_manager #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
) u_mm (
.clk(clk), .rst(rst),
.job_start(job_start), .x_base(x_base), .w_base(w_base),
.n_tiles(n_tiles), .result_addr(result_addr), .job_done(job_done),
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
);
// ---- real Neural Processor (M1), driven entirely by memory_manager ----
reg job_valid_np;
wire job_ready_np;
wire result_valid_np;
wire signed [DATA_WIDTH-1:0] result_data_np;
wire [15:0] result_node_id_np;
wire [3:0] np_state;
wire np_error;
neural_processor #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
) u_np (
.clk(clk), .rst(rst),
.job_valid(job_valid_np), .job_ready(job_ready_np),
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1), // ACT_RELU
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
.result_valid(result_valid_np), .result_ready(mm_result_ready),
.result_data(result_data_np), .result_node_id(result_node_id_np),
.np_state(np_state), .np_error(np_error)
);
assign mm_result_valid = result_valid_np;
assign mm_result_data = result_data_np;
// job_valid_np must pulse once per memory_manager job, synchronized
// to job_start (both start a "job" at the same moment: memory_manager
// begins prefetching tile 0 while neural_processor waits in NP_IDLE
// until tile 0 actually arrives, exactly like any other operand
// producer feeding it).
always @(posedge clk) begin
if (rst) job_valid_np <= 1'b0;
else if (job_start) job_valid_np <= 1'b1;
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
end
// ---- REAL, unmodified V1 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(mem_req), .wr(mem_wr), .addr(mem_addr), .wdata(mem_wdata),
.rdata(mem_rdata), .ready(mem_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)
);
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;
psram_controller #(
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(80)
) 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)
);
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)
);
// ---- helper: poke one byte directly into psram_model's backing
// array (test setup only, bypasses the real write path -- same
// convention as hardware/v1/sim's own testbenches that pre-load
// psram_model for read-side tests). ----
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
task automatic peek_byte(input [ADDR_WIDTH-1:0] byte_addr, output [7:0] val);
reg [ADDR_WIDTH-2:0] word_addr;
begin
word_addr = byte_addr[ADDR_WIDTH-1:1];
val = (byte_addr[0] == 1'b0) ? u_psram.mem[word_addr][7:0] : u_psram.mem[word_addr][15:8];
end
endtask
integer errors, tests;
integer i;
function automatic signed [7:0] expect_relu(input integer acc);
begin
if (acc <= 0) expect_relu = 0;
else if (acc > 127) expect_relu = 127;
else expect_relu = acc[7:0];
end
endfunction
task automatic run_job(
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr,
input signed [7:0] exp_y
);
integer wd;
reg [7:0] rb;
begin
@(posedge clk);
tests = tests + 1;
x_base = xb; w_base = wb; n_tiles = nt; result_addr = resaddr;
job_start = 1'b1;
@(posedge clk);
job_start = 1'b0;
wd = 0;
while (!job_done && wd < 2000) begin
@(posedge clk);
wd = wd + 1;
end
if (!job_done) begin
$display("FAIL job xb=%0d: no job_done within watchdog (%0d cycles)", xb, wd);
errors = errors + 1;
end else begin
peek_byte(resaddr, rb);
if (rb !== exp_y[7:0]) begin
$display("FAIL job xb=%0d: PSRAM result byte=%0d expected=%0d (%0d cycles)", xb, $signed(rb), exp_y, wd);
errors = errors + 1;
end else begin
$display("PASS job xb=%0d: PSRAM result byte=%0d correct, %0d cycles, n_tiles=%0d", xb, $signed(rb), wd, nt);
end
end
end
endtask
initial begin
errors = 0; tests = 0;
rst = 1; job_start = 0; x_base = 0; w_base = 0; n_tiles = 0; result_addr = 0;
repeat(5) @(posedge clk);
rst = 0;
// Wait for the real PSRAM controller's power-up sequence
// (~150us @ 80MHz) before issuing any request -- same
// requirement/convention documented in
// hardware/v1/docs/FPGA-NeuralNetwork-Engine.md's
// WRITE_RAM/READ_RAM backpressure warning.
wait (u_psram_ctrl.state == u_psram_ctrl.STATE_IDLE);
@(posedge clk);
// ---- pre-load PSRAM: X at 0x1000, W at 0x2000, 3 tiles
// (24 inputs), with "poison" bytes immediately before/after
// the real region to catch any off-by-one in prefetch_engine's
// addressing. ----
for (i = -4; i < 24+4; i = i + 1) begin
poke_byte(23'h1000 + i, 8'sd99); // poison
poke_byte(23'h2000 + i, 8'sd99); // poison
end
for (i = 0; i < 24; i = i + 1) begin
poke_byte(23'h1000 + i, 8'sd2); // X = 2
poke_byte(23'h2000 + i, 8'sd3); // W = 3
end
// acc = 24 * 2 * 3 = 144 -> ACT_RELU saturates to 127
run_job(23'h1000, 23'h2000, 16'd3, 23'h3000, expect_relu(144));
// ---- second job: 1 tile (8 inputs), smaller, no saturation ----
for (i = 0; i < 8; i = i + 1) begin
poke_byte(23'h4000 + i, 8'sd1); // X = 1
poke_byte(23'h5000 + i, 8'sd4); // W = 4
end
// acc = 8*1*4 = 32
run_job(23'h4000, 23'h5000, 16'd1, 23'h3001, expect_relu(32));
// ---- third job: 5 tiles (40 inputs), exercises the
// steady-state double-buffer swap across more than 2 tiles. ----
for (i = 0; i < 40; i = i + 1) begin
poke_byte(23'h6000 + i, 8'sd1); // X = 1
poke_byte(23'h7000 + i, 8'sd1); // W = 1
end
// acc = 40*1*1 = 40
run_job(23'h6000, 23'h7000, 16'd5, 23'h3002, expect_relu(40));
$display("========================================");
if (errors == 0)
$display("ALL %0d TESTS PASSED (memory_manager + prefetch_engine, real V1 PSRAM backend, real neural_processor)", tests);
else
$display("FAILED: %0d/%0d test(s) had errors -- see messages above", errors, tests);
$display("========================================");
$finish;
end
endmodule
@@ -0,0 +1,71 @@
// ================================================================
// SYNTHESIS-ONLY TIMING HARNESS -- NOT a functional deliverable.
// Same rationale/pattern as harness_neural_processor_array.v (see its
// header and hardware/v2/logs/errors.log ERR-0005): memory_manager's
// wide ports (input_data/weight_data alone, 64 bits) exceed the
// LFE5U-45F's TRELLIS_IO budget as a bare top-level module. Drives
// them from an internal LFSR and reduces outputs to a small
// checksum, keeping only clk/rst/seed/checksum as real pins, to get
// a representative Fmax for memory_manager's own logic/routing.
// ================================================================
module harness_memory_manager #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ADDR_WIDTH = 23
)(
input wire clk,
input wire rst,
input wire [7:0] seed,
output wire [7:0] checksum
);
reg [31:0] lfsr;
always @(posedge clk) begin
if (rst) lfsr <= {24'h0, seed} | 32'h1;
else lfsr <= {lfsr[30:0], lfsr[31] ^ lfsr[21] ^ lfsr[1] ^ lfsr[0]};
end
wire job_start = lfsr[0];
wire [ADDR_WIDTH-1:0] x_base = lfsr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0] w_base = {lfsr[7:0], lfsr[ADDR_WIDTH-9:0]};
wire [15:0] n_tiles = lfsr[15:0];
wire [ADDR_WIDTH-1:0] result_addr = {lfsr[3:0], lfsr[ADDR_WIDTH-5:0]};
wire operand_ready = lfsr[2];
wire result_valid = lfsr[3];
wire signed [DATA_WIDTH-1:0] result_data = lfsr[7:0];
wire signed [7:0] mem_rdata = lfsr[15:8];
wire mem_ready = lfsr[4];
wire job_done;
wire operand_valid;
wire signed [DATA_WIDTH*P_IN-1:0] input_data, weight_data;
wire tile_last;
wire result_ready;
wire mem_req, mem_wr;
wire [ADDR_WIDTH-1:0] mem_addr;
wire signed [7:0] mem_wdata;
memory_manager #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
) dut (
.clk(clk), .rst(rst),
.job_start(job_start), .x_base(x_base), .w_base(w_base),
.n_tiles(n_tiles), .result_addr(result_addr), .job_done(job_done),
.operand_valid(operand_valid), .operand_ready(operand_ready),
.input_data(input_data), .weight_data(weight_data), .tile_last(tile_last),
.result_valid(result_valid), .result_ready(result_ready), .result_data(result_data),
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
);
reg [7:0] chk;
always @(posedge clk) begin
if (rst) chk <= 8'h0;
else chk <= chk ^ input_data[7:0] ^ weight_data[7:0] ^ {7'h0, job_done}
^ {6'h0, tile_last, result_ready} ^ mem_addr[7:0]
^ {7'h0, mem_req} ^ {7'h0, mem_wr} ^ mem_wdata;
end
assign checksum = chk;
endmodule
@@ -0,0 +1,302 @@
Info: Logic utilisation before packing:
Info: Total LUT4s: 349/43848 0%
Info: logic LUTs: 257/43848 0%
Info: carry LUTs: 92/43848 0%
Info: RAM LUTs: 0/ 5481 0%
Info: RAMW LUTs: 0/10962 0%
Info: Total DFFs: 203/43848 0%
Info: Packing IOs..
Info: Packing constants..
Info: Packing carries...
Info: Packing LUTs...
Info: Packing LUT5-7s...
Info: Packing FFs...
Info: 72 FFs paired with LUTs.
Info: Generating derived timing constraints...
Info: Promoting globals...
Info: promoting clock net clk$TRELLIS_IO_IN to global network
Info: Checksum: 0x6e129573
Info: Device utilisation:
Info: TRELLIS_IO: 18/ 245 7%
Info: DCCA: 1/ 56 1%
Info: DP16KD: 0/ 108 0%
Info: MULT18X18D: 0/ 72 0%
Info: ALU54B: 0/ 36 0%
Info: EHXPLLL: 0/ 4 0%
Info: EXTREFB: 0/ 2 0%
Info: DCUA: 0/ 2 0%
Info: PCSCLKDIV: 0/ 2 0%
Info: IOLOGIC: 0/ 160 0%
Info: SIOLOGIC: 0/ 85 0%
Info: GSR: 0/ 1 0%
Info: JTAGG: 0/ 1 0%
Info: OSCG: 0/ 1 0%
Info: SEDGA: 0/ 1 0%
Info: DTR: 0/ 1 0%
Info: USRMCLK: 0/ 1 0%
Info: CLKDIVF: 0/ 4 0%
Info: ECLKSYNCB: 0/ 10 0%
Info: DLLDELD: 0/ 8 0%
Info: DDRDLL: 0/ 4 0%
Info: DQSBUFM: 0/ 10 0%
Info: TRELLIS_ECLKBUF: 0/ 8 0%
Info: ECLKBRIDGECS: 0/ 2 0%
Info: DCSC: 0/ 2 0%
Info: TRELLIS_FF: 203/ 43848 0%
Info: TRELLIS_COMB: 383/ 43848 0%
Info: TRELLIS_RAMW: 0/ 5481 0%
Info: Placed 0 cells based on constraints.
Info: Creating initial analytic placement for 333 cells, random placement wirelen = 28028.
Info: at initial placer iter 0, wirelen = 1283
Info: at initial placer iter 1, wirelen = 1162
Info: at initial placer iter 2, wirelen = 1104
Info: at initial placer iter 3, wirelen = 1098
Info: Running main analytical placer, max placement attempts per cell = 45753.
Info: at iteration #1, type ALL: wirelen solved = 1068, spread = 2127, legal = 2276; time = 0.01s
Info: at iteration #2, type ALL: wirelen solved = 1116, spread = 2137, legal = 2271; time = 0.01s
Info: at iteration #3, type ALL: wirelen solved = 1148, spread = 1979, legal = 2159; time = 0.00s
Info: at iteration #4, type ALL: wirelen solved = 1209, spread = 1880, legal = 2056; time = 0.00s
Info: at iteration #5, type ALL: wirelen solved = 1233, spread = 1999, legal = 2152; time = 0.00s
Info: at iteration #6, type ALL: wirelen solved = 1282, spread = 1933, legal = 2171; time = 0.00s
Info: at iteration #7, type ALL: wirelen solved = 1355, spread = 1921, legal = 2143; time = 0.00s
Info: at iteration #8, type ALL: wirelen solved = 1411, spread = 1875, legal = 2071; time = 0.00s
Info: at iteration #9, type ALL: wirelen solved = 1399, spread = 1911, legal = 2120; time = 0.00s
Info: HeAP Placer Time: 0.07s
Info: of which solving equations: 0.04s
Info: of which spreading cells: 0.01s
Info: of which strict legalisation: 0.00s
Info: Running simulated annealing placer for refinement.
Info: at iteration #1: temp = 0.000000, timing cost = 91, wirelen = 2056
Info: at iteration #5: temp = 0.000000, timing cost = 72, wirelen = 1619
Info: at iteration #10: temp = 0.000000, timing cost = 93, wirelen = 1531
Info: at iteration #15: temp = 0.000000, timing cost = 90, wirelen = 1514
Info: at iteration #15: temp = 0.000000, timing cost = 89, wirelen = 1514
Info: SA placement time 0.09s
Info: Max frequency for clock '$glbnet$clk$TRELLIS_IO_IN': 164.28 MHz (PASS at 80.00 MHz)
Info: Max delay <async> -> posedge $glbnet$clk$TRELLIS_IO_IN: 4.99 ns
Info: Max delay posedge $glbnet$clk$TRELLIS_IO_IN -> <async> : 4.67 ns
Info: Slack histogram:
Info: legend: * represents 1 endpoint(s)
Info: + represents [1,1) endpoint(s)
Info: [ 6413, 6680) |*********
Info: [ 6680, 6947) |*****
Info: [ 6947, 7214) |****
Info: [ 7214, 7481) |********
Info: [ 7481, 7748) |***
Info: [ 7748, 8015) |
Info: [ 8015, 8282) |**
Info: [ 8282, 8549) |********
Info: [ 8549, 8816) |*******
Info: [ 8816, 9083) |*************
Info: [ 9083, 9350) |***************************************
Info: [ 9350, 9617) |****************************
Info: [ 9617, 9884) |*********************
Info: [ 9884, 10151) |*******************************
Info: [ 10151, 10418) |**************************
Info: [ 10418, 10685) |********************************
Info: [ 10685, 10952) |**********
Info: [ 10952, 11219) |*******************
Info: [ 11219, 11486) |*********************************************
Info: [ 11486, 11753) |***************************************
Info: Checksum: 0x691562ea
Info: Routing globals...
Info: routing clock net $glbnet$clk$TRELLIS_IO_IN using global 0
Info: Routing..
Info: Setting up routing queue.
Info: Routing 1629 arcs.
Info: | (re-)routed arcs | delta | remaining| time spent |
Info: IterCnt | w/ripup wo/ripup | w/r wo/r | arcs| batch(sec) total(sec)|
Info: 1000 | 209 742 | 209 742 | 884| 0.08 0.08|
Info: 2000 | 361 1528 | 152 786 | 65| 0.13 0.21|
Info: 2066 | 363 1587 | 2 59 | 0| 0.01 0.22|
Info: Routing complete.
Info: Router1 time 0.22s
Info: Checksum: 0x5e42e5ef
Info: Critical path report for clock '$glbnet$clk$TRELLIS_IO_IN' (posedge -> posedge):
Info: type curr total name
Info: clk-to-q 0.40 0.40 Source dut.tile_idx_TRELLIS_FF_Q_2.Q
Info: routing 0.93 1.32 Net dut.tile_idx[3] (31,5) -> (32,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.B
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:90.26-90.34
Info: logic 0.35 1.68 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 1.68 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_FCI_INT (32,2) -> (32,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 1.68 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 1.68 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN (32,2) -> (32,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.54
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.06 1.73 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 1.73 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_FCI_INT (32,2) -> (32,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 1.73 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 1.73 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN (32,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.54
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.06 1.79 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 1.79 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_FCI_INT (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 1.79 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 1.79 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT_CIN (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.54
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.06 1.84 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 1.84 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_FCI_INT (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 1.84 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 1.84 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT_CIN (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.54
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.06 1.90 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 1.90 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT$CCU2_FCI_INT (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 1.90 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 1.90 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_CIN (33,2) -> (33,2)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.54
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.33 2.23 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1$CCU2_COMB0.F
Info: routing 1.01 3.24 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_A0_CCU2C_S1_S0 (33,2) -> (34,3)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.A
Info: logic 0.35 3.60 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_CIN_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 3.60 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT_CIN (34,3) -> (35,3)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT$CCU2_COMB0.FCI
Info: Defined in:
Info: hardware/v2/rtl/memory_manager.v:255.38-255.68
Info: /opt/homebrew/bin/../share/yosys/lattice/arith_map_ccu2c.v:74.7-80.4
Info: /opt/homebrew/bin/../share/yosys/lattice/ccu2c_sim.vh:9.9-9.13
Info: logic 0.06 3.65 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT$CCU2_COMB0.FCO
Info: routing 0.00 3.65 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT$CCU2_FCI_INT (35,3) -> (35,3)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT$CCU2_COMB1.FCI
Info: logic 0.00 3.65 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z_CCU2C_COUT$CCU2_COMB1.FCO
Info: routing 0.00 3.65 Net $nextpnr_CCU2C_5$CIN (35,3) -> (35,3)
Info: Sink $nextpnr_CCU2C_5$CCU2_COMB0.FCI
Info: logic 0.33 3.98 Source $nextpnr_CCU2C_5$CCU2_COMB0.F
Info: routing 0.71 4.69 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z[2] (35,3) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.C
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:108.23-108.24
Info: logic 0.18 4.87 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.F
Info: routing 0.00 4.87 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_ALUT (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_BLUT_LUT4_Z.F1
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.30-157.32
Info: logic 0.13 5.00 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_BLUT_LUT4_Z.OFX
Info: routing 0.00 5.00 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1 (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.FXB
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.54-157.56
Info: logic 0.18 5.18 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.OFX
Info: routing 0.00 5.18 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D0 (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D0_PFUMX_Z_ALUT_LUT4_Z.FXA
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.66-157.68
Info: logic 0.18 5.36 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D0_PFUMX_Z_ALUT_LUT4_Z.OFX
Info: routing 0.67 6.03 Net dut.pf_pending_x_TRELLIS_FF_Q_CE (35,4) -> (35,7)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_1.CE
Info: setup 0.00 6.03 Source dut.pf_pending_x_TRELLIS_FF_Q_1.CE
Info: 2.71 ns logic, 3.32 ns routing
Info: Critical path report for cross-domain path '<async>' -> 'posedge $glbnet$clk$TRELLIS_IO_IN':
Info: type curr total name
Info: source 0.00 0.00 Source rst$tr_io.O
Info: routing 1.39 1.39 Net rst$TRELLIS_IO_IN (38,0) -> (35,7)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_C_LUT4_Z.B
Info: Defined in:
Info: hardware/v2/synthesis/harness_memory_manager.v:18.17-18.20
Info: logic 0.18 1.57 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_C_LUT4_Z.F
Info: routing 0.92 2.49 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C[2] (35,7) -> (36,4)
Info: Sink dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A.C
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:108.23-108.24
Info: logic 0.18 2.67 Source dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A.F
Info: routing 0.49 3.16 Net dut.operand_valid_TRELLIS_FF_Q_CE_LUT4_Z_C_LUT4_A_Z[3] (36,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.D
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:108.23-108.24
Info: logic 0.18 3.34 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.F
Info: routing 0.00 3.34 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_ALUT (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_BLUT_LUT4_Z.F1
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.46-157.48
Info: logic 0.13 3.47 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_BLUT_LUT4_Z.OFX
Info: routing 0.00 3.47 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1 (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.FXB
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.62-157.64
Info: logic 0.18 3.65 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D1_PFUMX_Z_ALUT_LUT4_Z.OFX
Info: routing 0.00 3.65 Net dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1 (35,4) -> (35,4)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D0_PFUMX_Z_ALUT_LUT4_Z.FXB
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:157.70-157.72
Info: logic 0.18 3.83 Source dut.pf_pending_x_TRELLIS_FF_Q_CE_L6MUX21_Z_D1_L6MUX21_Z_D0_PFUMX_Z_ALUT_LUT4_Z.OFX
Info: routing 0.67 4.50 Net dut.pf_pending_x_TRELLIS_FF_Q_CE (35,4) -> (35,7)
Info: Sink dut.pf_pending_x_TRELLIS_FF_Q_1.CE
Info: setup 0.00 4.50 Source dut.pf_pending_x_TRELLIS_FF_Q_1.CE
Info: 1.03 ns logic, 3.47 ns routing
Info: Critical path report for cross-domain path 'posedge $glbnet$clk$TRELLIS_IO_IN' -> '<async>':
Info: type curr total name
Info: clk-to-q 0.40 0.40 Source chk_TRELLIS_FF_Q_2.Q
Info: routing 2.91 3.30 Net checksum[2]$TRELLIS_IO_OUT (38,8) -> (90,11)
Info: Sink checksum[2]$tr_io.I
Info: Defined in:
Info: /opt/homebrew/bin/../share/yosys/lattice/cells_map_trellis.v:108.23-108.24
Info: 0.40 ns logic, 2.91 ns routing
Info: Max frequency for clock '$glbnet$clk$TRELLIS_IO_IN': 165.86 MHz (PASS at 80.00 MHz)
Info: Max delay <async> -> posedge $glbnet$clk$TRELLIS_IO_IN: 4.50 ns
Info: Max delay posedge $glbnet$clk$TRELLIS_IO_IN -> <async> : 3.30 ns
Info: Slack histogram:
Info: legend: * represents 1 endpoint(s)
Info: + represents [1,1) endpoint(s)
Info: [ 6471, 6738) |*******
Info: [ 6738, 7005) |**
Info: [ 7005, 7272) |**
Info: [ 7272, 7539) |*
Info: [ 7539, 7806) |*
Info: [ 7806, 8073) |***
Info: [ 8073, 8340) |*************
Info: [ 8340, 8607) |***
Info: [ 8607, 8874) |****
Info: [ 8874, 9141) |*************
Info: [ 9141, 9408) |**********************************************
Info: [ 9408, 9675) |***********************************
Info: [ 9675, 9942) |**************************
Info: [ 9942, 10209) |******************************
Info: [ 10209, 10476) |****************************
Info: [ 10476, 10743) |****************
Info: [ 10743, 11010) |**************
Info: [ 11010, 11277) |*********************
Info: [ 11277, 11544) |**********************************************
Info: [ 11544, 11811) |**************************************
Info: Program finished normally.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
Info: Logic utilisation before packing:
Info: Total LUT4s: 1067/43848 2%
Info: logic LUTs: 851/43848 1%
Info: carry LUTs: 216/43848 0%
Info: RAM LUTs: 0/ 5481 0%
Info: RAMW LUTs: 0/10962 0%
Info: Total DFFs: 789/43848 1%
Info: Packing IOs..
Info: Packing constants..
Info: Packing carries...
Info: Packing LUTs...
Info: Packing LUT5-7s...
Info: Packing FFs...
Info: 167 FFs paired with LUTs.
Info: Generating derived timing constraints...
Info: Promoting globals...
Info: promoting clock net clk$TRELLIS_IO_IN to global network
Info: Checksum: 0x6e816bc9
Info: Device utilisation:
Info: TRELLIS_IO: 272/ 245 111%
Info: DCCA: 1/ 56 1%
Info: DP16KD: 0/ 108 0%
Info: MULT18X18D: 0/ 72 0%
Info: ALU54B: 0/ 36 0%
Info: EHXPLLL: 0/ 4 0%
Info: EXTREFB: 0/ 2 0%
Info: DCUA: 0/ 2 0%
Info: PCSCLKDIV: 0/ 2 0%
Info: IOLOGIC: 0/ 160 0%
Info: SIOLOGIC: 0/ 85 0%
Info: GSR: 0/ 1 0%
Info: JTAGG: 0/ 1 0%
Info: OSCG: 0/ 1 0%
Info: SEDGA: 0/ 1 0%
Info: DTR: 0/ 1 0%
Info: USRMCLK: 0/ 1 0%
Info: CLKDIVF: 0/ 4 0%
Info: ECLKSYNCB: 0/ 10 0%
Info: DLLDELD: 0/ 8 0%
Info: DDRDLL: 0/ 4 0%
Info: DQSBUFM: 0/ 10 0%
Info: TRELLIS_ECLKBUF: 0/ 8 0%
Info: ECLKBRIDGECS: 0/ 2 0%
Info: DCSC: 0/ 2 0%
Info: TRELLIS_FF: 789/ 43848 1%
Info: TRELLIS_COMB: 1117/ 43848 2%
Info: TRELLIS_RAMW: 0/ 5481 0%
Info: Placed 0 cells based on constraints.
ERROR: Unable to place cell 'input_data[54]$tr_io', no BELs remaining to implement cell type 'TRELLIS_IO'
0 warnings, 1 error
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff