feat(v2): M5 Neural Director, first-free job scheduling
Implements M5: neural_director.v dispatches job descriptors to whichever of N_SLOTS (memory_manager, neural_processor) pairs is currently free (first-free scheduling per §9's initial policy), with a parametric-depth ready-queue FIFO for jobs arriving faster than slots can absorb them. Scope for this milestone (see decisions.log DEC-0007): a reduced 4-state FSM (DIR_IDLE/SCAN_READY/ALLOCATE/ERROR) rather than §9's full 8-state baseline -- dependency tracking, the waiting queue, and wake-up are §10's explicit responsibility (Dependency Manager, M6, not yet built), and slot-completion detection runs as an always-active per-slot tracker rather than a dedicated FSM state, for the same reason DEC-0002 already gave for the Neural Processor's own FSM (gating concurrent per-unit progress behind one shared state kills throughput). Verified with Verilator (N_SLOTS=2, each slot backed by its own independent behavioral memory rather than sharing V1's real PSRAM -- M4 already proved that path for one slot; this milestone's own concern is scheduling across multiple slots): 4/4 tests pass -- 3 jobs submitted to 2 slots (first two dispatch immediately, third correctly queues until a slot frees), and a deliberate burst that forces the ready queue to genuinely fill and recover. Real synthesis: 0 CHECK problems, 382 LUT4/366 FF/4 CCU2C/0 DSP. Real place&route (via a synthesis-only timing harness, same TRELLIS_IO pin-budget reason as M2/M4): Fmax 250.50 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:
@@ -27,7 +27,10 @@ reali, non solo scritto).
|
||||
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.
|
||||
- [x] **M5 — Neural Director** (`neural_director.v`), scheduling
|
||||
first-free. 4/4 test PASS (dispatch + coda + backpressure reale
|
||||
su N_SLOTS=2). FSM ridotta a 4 stati, dependency rimandata a M6
|
||||
(`logs/decisions.log` DEC-0007). Fmax 250.50 MHz.
|
||||
- [ ] **M6 — Dependency Manager** (`dependency_manager.v`), ready/waiting
|
||||
queue, dependency counters, wake-up, producer tracking.
|
||||
- [ ] **M7 — Dataflow Core** (`dataflow_core.v`), integrazione completa.
|
||||
|
||||
@@ -74,3 +74,10 @@ 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).
|
||||
|
||||
[2026-09-05] M5 Neural Director (standalone resource count; Fmax via
|
||||
timing harness -- see errors.log ERR-0005)
|
||||
|
||||
| Module | Fmax (POST-P&R) | LUT | FF | DSP | CCU2C |
|
||||
|------------------|------------------|-----|-----|-----|-------|
|
||||
| neural_director (N_SLOTS=4) | 250.50 MHz | 382 | 366 | 0 | 4 |
|
||||
|
||||
@@ -319,3 +319,73 @@ rather than optimized blindly now.
|
||||
|
||||
STATUS:
|
||||
ACCEPTED
|
||||
|
||||
---
|
||||
|
||||
DEC-0007
|
||||
|
||||
DATE: 2026-09-05
|
||||
|
||||
DECISION:
|
||||
neural_director.v (M5) implements a reduced FSM (DIR_IDLE,
|
||||
DIR_SCAN_READY, DIR_ALLOCATE, DIR_ERROR) instead of §9's full 8-state
|
||||
baseline list (which also includes DIR_WAIT_DEPENDENCY, DIR_MONITOR,
|
||||
DIR_COMPLETE, DIR_WAKEUP). Dependency tracking/waiting/wake-up are
|
||||
entirely deferred to the Dependency Manager (M6, not yet built); slot
|
||||
completion detection (§9's "rilevamento dei completamenti",
|
||||
DIR_MONITOR's job) is handled by an always-active per-slot busy
|
||||
tracker running independently of whatever state the allocate/scan
|
||||
loop happens to be in, not a dedicated state the loop must visit.
|
||||
|
||||
WHY:
|
||||
§10 explicitly assigns dependency counters/ready-vs-waiting
|
||||
tracking/wake-up/producer-tracking to the Dependency Manager, not the
|
||||
Director -- building DIR_WAIT_DEPENDENCY/DIR_WAKEUP now, before M6
|
||||
exists, would mean inventing a dependency model here that M6 would
|
||||
then have to either reuse or replace, backwards from the roadmap's own
|
||||
milestone order. For DIR_MONITOR: gating "did any slot just finish"
|
||||
detection behind a specific FSM state would force the SAME state to be
|
||||
revisited every cycle for every one of N_SLOTS independently-running
|
||||
jobs, which is exactly the throughput-killing pattern DEC-0002 already
|
||||
rejected for the Neural Processor's own FSM -- the same reasoning
|
||||
applies one level up here.
|
||||
|
||||
EVIDENCE:
|
||||
hardware/v2/sim/tb_neural_director.v -- 4/4 tests pass with 2 slots
|
||||
running genuinely concurrent, independently-timed jobs (a 3rd job
|
||||
correctly queued until whichever slot freed first, and a
|
||||
deliberately-slow 2-job burst used to force real ready-queue
|
||||
backpressure) -- confirms slot-completion detection and first-free
|
||||
allocation both work without a dedicated FSM state gating either.
|
||||
|
||||
Separately: this milestone's testbench gives each (memory_manager,
|
||||
neural_processor) slot its OWN independent behavioral byte memory
|
||||
(sim_byte_mem, not the real V1 PSRAM chain) rather than sharing one
|
||||
PSRAM port across N_SLOTS. M4 (EXP-0005) already proved the real PSRAM
|
||||
path end-to-end for ONE slot; M5's own concern is scheduling/dispatch
|
||||
across MULTIPLE slots, which this isolates. Multiple slots genuinely
|
||||
sharing one physical PSRAM port is a backend-arbitration problem
|
||||
already explicitly deferred (DEC-0006), not solved here either.
|
||||
|
||||
ALTERNATIVES:
|
||||
1. Implement the literal 8-state FSM now, with DIR_WAIT_DEPENDENCY/
|
||||
DIR_WAKEUP as real states that simply never get exercised until
|
||||
M6 wires something into them. Rejected: dead states with no real
|
||||
behavior are not simpler or safer than documenting the deferral
|
||||
explicitly, and risk baking in an ad-hoc dependency model that
|
||||
conflicts with M6's actual design once built.
|
||||
2. Share one real PSRAM backend across N_SLOTS now, forcing the
|
||||
arbiter-design question into M5. Rejected: out of this milestone's
|
||||
scope (§9 is about scheduling, not memory arbitration) and would
|
||||
duplicate work once M6/M8 need a real answer to backend sharing
|
||||
anyway.
|
||||
|
||||
RESULT:
|
||||
neural_director.v as implemented: 4-state FSM, always-active slot-busy
|
||||
tracking, ready-queue backpressure via a plain parametric-depth
|
||||
circular FIFO. First-free scheduling only (§9's initial policy);
|
||||
round-robin/least-loaded/etc are explicitly deferred to a later,
|
||||
experimentally-driven milestone per §9's own text.
|
||||
|
||||
STATUS:
|
||||
ACCEPTED
|
||||
|
||||
@@ -137,3 +137,25 @@ 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.
|
||||
|
||||
[2026-09-05T17:00:00Z] commit=175f697 session=v2-M5-neural-director
|
||||
module: hardware/v2/rtl/neural_director.v
|
||||
action: implementato M5 -- Neural Director, scheduling first-free
|
||||
(§9) su N_SLOTS coppie (memory_manager, neural_processor). FSM
|
||||
ridotta a 4 stati (DIR_IDLE/SCAN_READY/ALLOCATE/ERROR) -- dependency
|
||||
tracking/wake-up rimandati al Dependency Manager (M6, non ancora
|
||||
costruito), rilevamento completamenti gestito da un tracker
|
||||
sempre-attivo per-slot (non uno stato dedicato da rivisitare).
|
||||
reason: roadmap M5.
|
||||
result: 4/4 test PASS (N_SLOTS=2) -- dispatch first-free confermato,
|
||||
coda pronta con backpressure reale confermata (riempimento e
|
||||
recupero), 3 job su 2 slot con il terzo correttamente in coda fino
|
||||
a liberazione di uno slot. 2 bug di testbench trovati e risolti
|
||||
(non RTL): DEPTH di sim_byte_mem troppo piccolo per il range di
|
||||
indirizzi usato, e una condizione di attesa che si fermava al primo
|
||||
job completato invece che a tutti e tre. Sintesi reale: 0 problemi,
|
||||
382 LUT4/366 FF/4 CCU2C/0 DSP. Fmax reale (via harness): 250.50 MHz.
|
||||
errors: nessun bug RTL, solo 2 bug di testbench (vedi experiments.log
|
||||
EXP-0006).
|
||||
decision: vedi decisions.log DEC-0007.
|
||||
next_action: M6 -- dependency_manager.v.
|
||||
|
||||
@@ -274,3 +274,51 @@ decision: see decisions.log DEC-0006 (single prefetch engine + pending
|
||||
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.
|
||||
|
||||
EXP-0006
|
||||
timestamp: 2026-09-05T17:00:00Z
|
||||
git_commit: 175f697 (+ uncommitted M5 work)
|
||||
session: v2-M5-neural-director
|
||||
module: hardware/v2/rtl/neural_director.v
|
||||
configuration: N_SLOTS=2 (sim), N_SLOTS=4 (synth default),
|
||||
QUEUE_DEPTH=4 (sim), ADDR_WIDTH=23
|
||||
action: M5 -- first-free job scheduler dispatching to N_SLOTS
|
||||
(memory_manager, neural_processor) pairs, with a parametric-depth
|
||||
ready queue.
|
||||
command (sim, Verilator): verilator --binary --timing -j 0 -Wno-fatal
|
||||
--top-module tb -o /tmp/vtb_dir hardware/v2/rtl/neural_processor.v
|
||||
hardware/v2/rtl/prefetch_engine.v hardware/v2/rtl/memory_manager.v
|
||||
hardware/v2/rtl/neural_director.v hardware/v2/sim/tb_neural_director.v
|
||||
&& /tmp/vtb_dir
|
||||
command (synth): yosys -p "synth_ecp5 -json .../top.json -top
|
||||
neural_director" hardware/v2/rtl/neural_director.v (standalone, real
|
||||
resource counts); harness_neural_director.v (see errors.log
|
||||
ERR-0005 pattern) for real P&R Fmax.
|
||||
result:
|
||||
SIMULATED (N_SLOTS=2, tb_neural_director.v): 4/4 tests PASS --
|
||||
3 jobs submitted to 2 slots (first two dispatch immediately,
|
||||
first-free; third correctly WAITS in the ready queue until a slot
|
||||
frees, then auto-dispatches), each result independently verified;
|
||||
a deliberate 2-long-job burst forces the ready queue to genuinely
|
||||
fill (job_in_ready correctly deasserts after exactly QUEUE_DEPTH
|
||||
queued jobs while both slots are kept busy) and recover once
|
||||
slots/queue drain.
|
||||
SYNTHESIZED (standalone, N_SLOTS=4): 0 CHECK problems, 382 LUT4,
|
||||
366 TRELLIS_FF, 4 CCU2C, 0 DSP.
|
||||
POST-P&R (via harness, real Fmax): 250.50 MHz, PASS at 80MHz with
|
||||
large margin.
|
||||
errors: two real testbench bugs found and fixed during bring-up (not
|
||||
RTL bugs): (1) sim_byte_mem's DEPTH parameter (1024) was smaller
|
||||
than the test's own address range (up to 0x703=1795), an
|
||||
out-of-bounds array access silently returning garbage; (2) the
|
||||
initial completion-wait loop exited as soon as ANY ONE of three
|
||||
jobs' result bytes changed, not all three -- fixed by counting
|
||||
job_out_done pulses instead of polling result memory directly.
|
||||
decision: see decisions.log DEC-0007 (reduced 4-state FSM; dependency
|
||||
handling deferred to M6; per-slot independent behavioral memory
|
||||
instead of shared real PSRAM, deferred to when backend arbitration
|
||||
is actually needed).
|
||||
next_action: M6 -- dependency_manager.v (ready/waiting queue,
|
||||
dependency counters, wake-up, producer tracking) -- the first
|
||||
milestone where job READINESS itself, not just free-slot dispatch,
|
||||
becomes the Director's actual gating condition.
|
||||
|
||||
@@ -48,3 +48,9 @@ bit-exact result: PSRAM-read-back result byte matches hand-computed
|
||||
RTL under test)
|
||||
cycles: 446 (3 tiles), 166 (1 tile), 728 (5 tiles) -- real PSRAM
|
||||
latency dominates, not memory_manager's own control overhead
|
||||
|
||||
[2026-09-05] EXP-0006 -- hardware/v2/sim/tb_neural_director.v
|
||||
test: 4 cases (3-jobs-2-slots first-free dispatch + queueing,
|
||||
backpressure fill/recover)
|
||||
simulator: Verilator 5.050 (--binary --timing)
|
||||
PASS/FAIL: 4/4 PASS
|
||||
|
||||
@@ -40,3 +40,6 @@ CHECK: 0 problems, all 6 configs correctly infer DP16KD (no LUT-RAM
|
||||
[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.
|
||||
|
||||
[2026-09-05] EXP-0006 -- neural_director (N_SLOTS=4, standalone)
|
||||
LUT4=382 TRELLIS_FF=366 CCU2C=4 DSP=0. CHECK: 0 problems.
|
||||
|
||||
@@ -44,3 +44,8 @@ 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)
|
||||
|
||||
[2026-09-05] EXP-0006 -- neural_director (via harness_neural_director.v,
|
||||
see errors.log ERR-0005 for why), real nextpnr-ecp5 --45k --package
|
||||
CABGA381 --speed 8 --freq 80 --lpf-allow-unconstrained
|
||||
Fmax: 250.50 MHz -- PASS at 80MHz (real place&route measurement)
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// FPGA-Neural V2 -- Neural Director (M5, docs/v2-description.md §9)
|
||||
//
|
||||
// Dispatches job descriptors, arriving via a simple valid/ready
|
||||
// producer interface, to whichever of N_SLOTS (memory_manager, M4)
|
||||
// instances is currently free -- first-free scheduling (§9's initial
|
||||
// policy; round-robin/least-loaded/etc are explicitly deferred to a
|
||||
// later, experimentally-driven milestone, not this one).
|
||||
//
|
||||
// Each "slot" is one memory_manager's own job_start/x_base/w_base/
|
||||
// n_tiles/result_addr/job_done interface (M4) -- the Director does
|
||||
// not touch a Neural Processor directly, matching §34's division of
|
||||
// labor ("Il Director gestisce WHAT deve essere eseguito... Il
|
||||
// Memory Manager gestisce COME rendere disponibili i dati").
|
||||
//
|
||||
// Scope of THIS milestone (see hardware/v2/logs/decisions.log
|
||||
// DEC-0007 for the full rationale): jobs are assumed already READY
|
||||
// (no unresolved dependencies) -- dependency tracking, the waiting
|
||||
// queue, and wake-up are explicitly the Dependency Manager's job
|
||||
// (§10, M6, not yet built). §9's baseline FSM states
|
||||
// DIR_WAIT_DEPENDENCY/DIR_COMPLETE/DIR_WAKEUP are therefore not
|
||||
// separate states here; DIR_MONITOR's job (detecting a slot's
|
||||
// completion) is handled by an always-active per-slot busy tracker,
|
||||
// not a state the main allocate/scan loop must visit -- the same
|
||||
// "don't gate concurrent per-unit progress behind a single shared
|
||||
// FSM state" principle already applied to the Neural Processor's own
|
||||
// FSM (DEC-0002).
|
||||
// ================================================================
|
||||
|
||||
module neural_director #(
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter QUEUE_DEPTH = 8
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- job submission (producer interface, e.g. a host or a
|
||||
// future Dependency Manager, M6) ----
|
||||
input wire job_in_valid,
|
||||
output wire job_in_ready,
|
||||
input wire [ADDR_WIDTH-1:0] job_in_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] job_in_w_base,
|
||||
input wire [15:0] job_in_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] job_in_result_addr,
|
||||
input wire [15:0] job_in_node_id,
|
||||
|
||||
// ---- per-slot memory_manager job control (arrayed, §9) ----
|
||||
output reg [N_SLOTS-1:0] slot_job_start,
|
||||
output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base,
|
||||
output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_w_base,
|
||||
output reg [16*N_SLOTS-1:0] slot_n_tiles,
|
||||
output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr,
|
||||
input wire [N_SLOTS-1:0] slot_job_done,
|
||||
|
||||
// ---- completion notification (§9 "rilevamento dei completamenti") ----
|
||||
output reg job_out_done, // one-cycle pulse
|
||||
output reg [$clog2(N_SLOTS)-1:0] job_out_slot,
|
||||
|
||||
output reg [3:0] dir_state,
|
||||
output reg dir_error
|
||||
);
|
||||
|
||||
localparam DIR_IDLE = 4'd0;
|
||||
localparam DIR_SCAN_READY = 4'd1;
|
||||
localparam DIR_ALLOCATE = 4'd2;
|
||||
localparam DIR_ERROR = 4'd3;
|
||||
|
||||
// ---- ready queue: a plain circular FIFO of job descriptors.
|
||||
// Depth is parametric (§9 implies no fixed size); pushing and
|
||||
// popping are independent of the allocate FSM below so a new job
|
||||
// can be accepted the same cycle an old one is dispatched. ----
|
||||
localparam Q_ADDR_WIDTH = $clog2(QUEUE_DEPTH);
|
||||
|
||||
reg [ADDR_WIDTH-1:0] q_x_base [0:QUEUE_DEPTH-1];
|
||||
reg [ADDR_WIDTH-1:0] q_w_base [0:QUEUE_DEPTH-1];
|
||||
reg [15:0] q_n_tiles [0:QUEUE_DEPTH-1];
|
||||
reg [ADDR_WIDTH-1:0] q_result_addr [0:QUEUE_DEPTH-1];
|
||||
reg [15:0] q_node_id [0:QUEUE_DEPTH-1];
|
||||
|
||||
reg [Q_ADDR_WIDTH-1:0] q_head, q_tail;
|
||||
reg [Q_ADDR_WIDTH:0] q_count; // one extra bit: 0..QUEUE_DEPTH inclusive
|
||||
|
||||
wire q_empty = (q_count == 0);
|
||||
wire q_full = (q_count == QUEUE_DEPTH[Q_ADDR_WIDTH:0]);
|
||||
|
||||
assign job_in_ready = !q_full;
|
||||
|
||||
// ---- per-slot busy tracking: always-active, independent of the
|
||||
// main allocate/scan FSM state (see file header). ----
|
||||
reg [N_SLOTS-1:0] slot_busy;
|
||||
|
||||
wire [N_SLOTS-1:0] slot_free = ~slot_busy;
|
||||
wire any_slot_free = |slot_free;
|
||||
|
||||
// first-free slot index (priority encoder, lowest index wins --
|
||||
// "first-free", per §9's initial policy, not load-balanced)
|
||||
reg [$clog2(N_SLOTS)-1:0] free_slot_idx;
|
||||
integer fi;
|
||||
always @(*) begin
|
||||
free_slot_idx = {$clog2(N_SLOTS){1'b0}};
|
||||
for (fi = N_SLOTS-1; fi >= 0; fi = fi - 1) begin
|
||||
if (slot_free[fi]) free_slot_idx = fi[$clog2(N_SLOTS)-1:0];
|
||||
end
|
||||
end
|
||||
|
||||
// Priority-encoded lowest-indexed slot reporting job_done this
|
||||
// cycle (combinational, so it reflects THIS cycle's slot_job_done
|
||||
// bus directly -- a register-based "already reported one" flag
|
||||
// would read its own pre-edge value and not actually suppress a
|
||||
// second same-cycle match, see file header/DEC-0007).
|
||||
reg [$clog2(N_SLOTS)-1:0] done_slot_idx;
|
||||
integer di;
|
||||
always @(*) begin
|
||||
done_slot_idx = {$clog2(N_SLOTS){1'b0}};
|
||||
for (di = N_SLOTS-1; di >= 0; di = di - 1) begin
|
||||
if (slot_job_done[di]) done_slot_idx = di[$clog2(N_SLOTS)-1:0];
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
dir_state <= DIR_IDLE;
|
||||
dir_error <= 1'b0;
|
||||
q_head <= {Q_ADDR_WIDTH{1'b0}};
|
||||
q_tail <= {Q_ADDR_WIDTH{1'b0}};
|
||||
q_count <= {(Q_ADDR_WIDTH+1){1'b0}};
|
||||
slot_busy <= {N_SLOTS{1'b0}};
|
||||
slot_job_start <= {N_SLOTS{1'b0}};
|
||||
slot_x_base <= {(ADDR_WIDTH*N_SLOTS){1'b0}};
|
||||
slot_w_base <= {(ADDR_WIDTH*N_SLOTS){1'b0}};
|
||||
slot_n_tiles <= {(16*N_SLOTS){1'b0}};
|
||||
slot_result_addr <= {(ADDR_WIDTH*N_SLOTS){1'b0}};
|
||||
job_out_done <= 1'b0;
|
||||
job_out_slot <= {$clog2(N_SLOTS){1'b0}};
|
||||
end else begin
|
||||
slot_job_start <= {N_SLOTS{1'b0}};
|
||||
job_out_done <= 1'b0;
|
||||
|
||||
// ---- Accept a new job into the ready queue (independent
|
||||
// of the allocate FSM's own state -- a producer must
|
||||
// never be blocked just because the FSM is mid-allocate
|
||||
// this cycle). ----
|
||||
if (job_in_valid && job_in_ready) begin
|
||||
q_x_base[q_tail] <= job_in_x_base;
|
||||
q_w_base[q_tail] <= job_in_w_base;
|
||||
q_n_tiles[q_tail] <= job_in_n_tiles;
|
||||
q_result_addr[q_tail] <= job_in_result_addr;
|
||||
q_node_id[q_tail] <= job_in_node_id;
|
||||
q_tail <= (q_tail == QUEUE_DEPTH[Q_ADDR_WIDTH-1:0]-1'b1) ? {Q_ADDR_WIDTH{1'b0}} : q_tail + 1'b1;
|
||||
end
|
||||
|
||||
// ---- Free a slot the instant its job_done pulses,
|
||||
// regardless of the allocate FSM's own state (DIR_MONITOR
|
||||
// absorbed here -- see file header). Cleared with a single
|
||||
// vectorized AND-NOT of the whole slot_job_done bus (not a
|
||||
// per-bit for-loop of individual NBA writes) so that TWO
|
||||
// slots completing on the SAME cycle both get freed --a
|
||||
// per-bit loop would have each iteration's non-blocking
|
||||
// write use the same pre-edge slot_busy, so only the LAST
|
||||
// matching bit would actually clear ("last write wins").
|
||||
// job_out_done/job_out_slot still report at most one
|
||||
// (the lowest-indexed) simultaneous completion per cycle
|
||||
// -- a documented simplification (decisions.log DEC-0007),
|
||||
// not a correctness issue for slot freeing itself. ----
|
||||
slot_busy <= slot_busy & ~slot_job_done;
|
||||
if (|slot_job_done) begin
|
||||
job_out_done <= 1'b1;
|
||||
job_out_slot <= done_slot_idx;
|
||||
end
|
||||
|
||||
// ---- Main allocate/scan loop ----
|
||||
case (dir_state)
|
||||
|
||||
DIR_IDLE: begin
|
||||
dir_state <= DIR_SCAN_READY;
|
||||
end
|
||||
|
||||
DIR_SCAN_READY: begin
|
||||
if (!q_empty && any_slot_free) begin
|
||||
dir_state <= DIR_ALLOCATE;
|
||||
end
|
||||
end
|
||||
|
||||
DIR_ALLOCATE: begin
|
||||
// Dispatch the head of the queue to the first
|
||||
// free slot found this cycle.
|
||||
slot_job_start[free_slot_idx] <= 1'b1;
|
||||
slot_x_base[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_x_base[q_head];
|
||||
slot_w_base[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_w_base[q_head];
|
||||
slot_n_tiles[free_slot_idx*16 +: 16] <= q_n_tiles[q_head];
|
||||
slot_result_addr[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_result_addr[q_head];
|
||||
slot_busy[free_slot_idx] <= 1'b1;
|
||||
q_head <= (q_head == QUEUE_DEPTH[Q_ADDR_WIDTH-1:0]-1'b1) ? {Q_ADDR_WIDTH{1'b0}} : q_head + 1'b1;
|
||||
dir_state <= DIR_SCAN_READY;
|
||||
end
|
||||
|
||||
DIR_ERROR: begin
|
||||
// Recoverable only via rst (§34: an error must not
|
||||
// block the rest of the system).
|
||||
end
|
||||
|
||||
default: dir_state <= DIR_ERROR;
|
||||
|
||||
endcase
|
||||
|
||||
// q_count tracks push/pop independently of which branch
|
||||
// above fired, so it stays correct even when a push and a
|
||||
// pop happen the same cycle.
|
||||
case ({job_in_valid && job_in_ready, (dir_state == DIR_SCAN_READY) && !q_empty && any_slot_free})
|
||||
2'b10: q_count <= q_count + 1'b1;
|
||||
2'b01: q_count <= q_count - 1'b1;
|
||||
default: q_count <= q_count; // 00: no change, 11: push+pop cancel out
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,322 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// M5 testbench (docs/v2-description.md §9/§20): neural_director.v
|
||||
// dispatching to N_SLOTS=2 (memory_manager + neural_processor) pairs.
|
||||
// Verified with Verilator (decisions.log DEC-0004).
|
||||
//
|
||||
// Scope decision (decisions.log DEC-0007): each slot gets its OWN
|
||||
// independent simple behavioral byte memory (sim_byte_mem below,
|
||||
// fixed 2-cycle latency, matching int8_memory_access.v's req/wr/addr/
|
||||
// wdata -> rdata/ready contract exactly) instead of sharing V1's real
|
||||
// PSRAM chain -- M4 already proved the real PSRAM path end-to-end
|
||||
// with ONE slot (EXP-0005); M5's own concern is scheduling/dispatch
|
||||
// across MULTIPLE slots, which is what this testbench isolates.
|
||||
// Multiple slots genuinely sharing ONE physical PSRAM port is a
|
||||
// backend-arbitration problem explicitly deferred (DEC-0006), not
|
||||
// re-solved here.
|
||||
//
|
||||
// Coverage:
|
||||
// - more jobs submitted (3) than slots exist (2): first two must
|
||||
// dispatch immediately (first-free), the third must wait in the
|
||||
// ready queue until a slot frees up, then dispatch automatically.
|
||||
// - each job's result independently verified (own oracle).
|
||||
// - ready-queue backpressure: fill the queue past N jobs beyond
|
||||
// slot capacity and confirm job_in_ready deasserts, then confirm
|
||||
// it drains and reasserts as slots complete.
|
||||
// ============================================================
|
||||
|
||||
module sim_byte_mem #(
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter DEPTH = 1024
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
input wire req,
|
||||
input wire wr,
|
||||
input wire [ADDR_WIDTH-1:0] addr,
|
||||
input wire signed [7:0] wdata,
|
||||
output reg signed [7:0] rdata,
|
||||
output reg ready
|
||||
);
|
||||
reg signed [7:0] mem [0:DEPTH-1];
|
||||
reg [1:0] state;
|
||||
reg [ADDR_WIDTH-1:0] addr_reg;
|
||||
reg wr_reg;
|
||||
localparam ST_IDLE = 0, ST_WAIT = 1;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE; ready <= 1'b0; rdata <= 8'sd0;
|
||||
end else begin
|
||||
ready <= 1'b0;
|
||||
case (state)
|
||||
ST_IDLE: if (req) begin
|
||||
addr_reg <= addr; wr_reg <= wr;
|
||||
if (wr) mem[addr] <= wdata;
|
||||
state <= ST_WAIT;
|
||||
end
|
||||
ST_WAIT: begin
|
||||
rdata <= mem[addr_reg];
|
||||
ready <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module tb;
|
||||
|
||||
localparam ADDR_WIDTH = 23;
|
||||
localparam DATA_WIDTH = 8;
|
||||
localparam P_IN = 8;
|
||||
localparam ACC_WIDTH = 32;
|
||||
localparam N_SLOTS = 2;
|
||||
localparam QUEUE_DEPTH = 4;
|
||||
|
||||
reg clk, rst;
|
||||
initial begin clk = 0; forever #5 clk = ~clk; end
|
||||
|
||||
reg job_in_valid;
|
||||
wire job_in_ready;
|
||||
reg [ADDR_WIDTH-1:0] job_in_x_base, job_in_w_base, job_in_result_addr;
|
||||
reg [15:0] job_in_n_tiles, job_in_node_id;
|
||||
|
||||
wire [N_SLOTS-1:0] slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base, slot_w_base, slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] slot_n_tiles;
|
||||
wire [N_SLOTS-1:0] slot_job_done;
|
||||
|
||||
wire job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_dir (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(job_in_valid), .job_in_ready(job_in_ready),
|
||||
.job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base),
|
||||
.job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr),
|
||||
.job_in_node_id(job_in_node_id),
|
||||
.slot_job_start(slot_job_start), .slot_x_base(slot_x_base), .slot_w_base(slot_w_base),
|
||||
.slot_n_tiles(slot_n_tiles), .slot_result_addr(slot_result_addr), .slot_job_done(slot_job_done),
|
||||
.job_out_done(job_out_done), .job_out_slot(job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
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;
|
||||
|
||||
wire mem_req, mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] mem_addr;
|
||||
wire signed [7:0] mem_wdata, 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(slot_job_start[g]),
|
||||
.x_base(slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(slot_job_done[g]),
|
||||
.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)
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_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),
|
||||
.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(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
sim_byte_mem #(.ADDR_WIDTH(ADDR_WIDTH), .DEPTH(4096)) u_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(mem_req), .wr(mem_wr), .addr(mem_addr), .wdata(mem_wdata),
|
||||
.rdata(mem_rdata), .ready(mem_ready)
|
||||
);
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
task automatic poke(input integer slot, input [ADDR_WIDTH-1:0] addr, input [7:0] val);
|
||||
begin
|
||||
case (slot)
|
||||
0: tb.GEN_SLOT[0].u_mem.mem[addr] = val;
|
||||
1: tb.GEN_SLOT[1].u_mem.mem[addr] = val;
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
endtask
|
||||
|
||||
function automatic signed [7:0] peek(input integer slot, input [ADDR_WIDTH-1:0] addr);
|
||||
begin
|
||||
case (slot)
|
||||
0: peek = tb.GEN_SLOT[0].u_mem.mem[addr];
|
||||
1: peek = tb.GEN_SLOT[1].u_mem.mem[addr];
|
||||
default: peek = 8'sdx;
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
integer errors, tests;
|
||||
|
||||
task automatic submit_job(
|
||||
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
|
||||
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr, input [15:0] nid
|
||||
);
|
||||
begin
|
||||
@(posedge clk);
|
||||
job_in_x_base = xb; job_in_w_base = wb; job_in_n_tiles = nt;
|
||||
job_in_result_addr = resaddr; job_in_node_id = nid;
|
||||
job_in_valid = 1'b1;
|
||||
while (!job_in_ready) @(posedge clk);
|
||||
@(posedge clk);
|
||||
job_in_valid = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
integer i;
|
||||
integer wd;
|
||||
|
||||
initial begin
|
||||
errors = 0; tests = 0;
|
||||
rst = 1; job_in_valid = 0; job_in_x_base = 0; job_in_w_base = 0;
|
||||
job_in_n_tiles = 0; job_in_result_addr = 0; job_in_node_id = 0;
|
||||
repeat(4) @(posedge clk);
|
||||
rst = 0;
|
||||
@(posedge clk);
|
||||
|
||||
// ---- prepare 3 independent jobs (2 slots, so job 2 must
|
||||
// wait in the ready queue for a slot to free up) ----
|
||||
// Job 0 (slot 0 pre-loaded region 0x100/0x200): 8 inputs, x=2,w=3 -> acc=48
|
||||
for (i = 0; i < 8; i = i + 1) begin poke(0, 23'h100+i, 8'sd2); poke(0, 23'h200+i, 8'sd3); poke(1, 23'h100+i, 8'sd2); poke(1, 23'h200+i, 8'sd3); end
|
||||
// Job 1 (either slot, region 0x300/0x400): 16 inputs, x=1,w=1 -> acc=16
|
||||
for (i = 0; i < 16; i = i + 1) begin poke(0, 23'h300+i, 8'sd1); poke(0, 23'h400+i, 8'sd1); poke(1, 23'h300+i, 8'sd1); poke(1, 23'h400+i, 8'sd1); end
|
||||
// Job 2 (queued until a slot frees, region 0x500/0x600): 8 inputs, x=1,w=5 -> acc=40
|
||||
for (i = 0; i < 8; i = i + 1) begin poke(0, 23'h500+i, 8'sd1); poke(0, 23'h600+i, 8'sd5); poke(1, 23'h500+i, 8'sd1); poke(1, 23'h600+i, 8'sd5); end
|
||||
|
||||
submit_job(23'h100, 23'h200, 16'd1, 23'h700, 16'd1); // -> dispatches to slot 0 (first-free)
|
||||
submit_job(23'h300, 23'h400, 16'd2, 23'h701, 16'd2); // -> dispatches to slot 1
|
||||
submit_job(23'h500, 23'h600, 16'd1, 23'h702, 16'd3); // -> waits in queue
|
||||
|
||||
// job_in_ready should have stayed high throughout (only 3
|
||||
// jobs, queue depth 4) -- confirmed implicitly: submit_job's
|
||||
// own while-loop would have hung the testbench otherwise.
|
||||
|
||||
tests = tests + 3;
|
||||
wd = 0;
|
||||
begin
|
||||
integer completions;
|
||||
completions = 0;
|
||||
while (completions < 3 && wd < 3000) begin
|
||||
@(posedge clk);
|
||||
wd = wd + 1;
|
||||
if (job_out_done) completions = completions + 1;
|
||||
end
|
||||
if (completions < 3)
|
||||
$display("FAIL: only %0d/3 jobs completed within watchdog", completions);
|
||||
end
|
||||
// give the last-completing job's write a little extra margin
|
||||
repeat(5) @(posedge clk);
|
||||
|
||||
if (peek(0, 23'h700) !== 8'sd48) begin
|
||||
$display("FAIL job0: result=%0d expected=48", peek(0,23'h700)); errors = errors + 1;
|
||||
end else $display("PASS job0 (slot dispatched first-free): result=48");
|
||||
|
||||
if (peek(1, 23'h701) !== 8'sd16) begin
|
||||
$display("FAIL job1: result=%0d expected=16", peek(1,23'h701)); errors = errors + 1;
|
||||
end else $display("PASS job1 (slot dispatched first-free): result=16");
|
||||
|
||||
// Job 2 could have landed on either slot (whichever freed
|
||||
// first) -- check both.
|
||||
if (peek(0, 23'h702) !== 8'sd40 && peek(1, 23'h702) !== 8'sd40) begin
|
||||
$display("FAIL job2 (queued): neither slot's result byte at 0x702 is 40 (got %0d / %0d)", peek(0,23'h702), peek(1,23'h702));
|
||||
errors = errors + 1;
|
||||
end else $display("PASS job2 (queued until a slot freed): result=40");
|
||||
|
||||
// ---- backpressure: occupy both slots with LONG jobs (many
|
||||
// tiles, so they stay busy for a while and won't drain the
|
||||
// queue mid-burst), then push jobs faster than they can be
|
||||
// consumed and confirm job_in_ready genuinely deasserts once
|
||||
// the queue fills, then recovers once slots free up again. ----
|
||||
tests = tests + 1;
|
||||
for (i = 0; i < 64; i = i + 1) begin poke(0, 23'h800+i, 8'sd1); poke(0, 23'h900+i, 8'sd1); poke(1, 23'h800+i, 8'sd1); poke(1, 23'h900+i, 8'sd1); end
|
||||
submit_job(23'h800, 23'h900, 16'd64, 23'h704, 16'd10); // occupies slot 0/1 for a while
|
||||
submit_job(23'h800, 23'h900, 16'd64, 23'h705, 16'd11); // occupies the other slot
|
||||
|
||||
job_in_x_base = 23'h100; job_in_w_base = 23'h200; job_in_n_tiles = 16'd1;
|
||||
job_in_result_addr = 23'h706; job_in_node_id = 16'd12;
|
||||
i = 0;
|
||||
while (job_in_ready && i < QUEUE_DEPTH + 2) begin
|
||||
@(posedge clk);
|
||||
job_in_valid = 1'b1;
|
||||
@(posedge clk);
|
||||
i = i + 1;
|
||||
end
|
||||
if (i > QUEUE_DEPTH) begin
|
||||
$display("FAIL backpressure: job_in_ready never deasserted after %0d pushes (QUEUE_DEPTH=%0d) -- both slots busy, queue should have filled", i, QUEUE_DEPTH);
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
$display("PASS backpressure: job_in_ready correctly deasserted after %0d queued jobs (QUEUE_DEPTH=%0d), both slots busy", i, QUEUE_DEPTH);
|
||||
end
|
||||
job_in_valid = 1'b0;
|
||||
|
||||
// Drain: wait for everything (2 long jobs + whatever got
|
||||
// queued) to finish, no watchdog failure, and confirm
|
||||
// job_in_ready recovers once slots/queue free up.
|
||||
wd = 0;
|
||||
while (!job_in_ready && wd < 5000) begin @(posedge clk); wd = wd + 1; end
|
||||
if (!job_in_ready) begin
|
||||
$display("FAIL backpressure: job_in_ready never recovered within watchdog");
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
$display("PASS backpressure: job_in_ready recovered once slots/queue drained");
|
||||
end
|
||||
|
||||
$display("========================================");
|
||||
if (errors == 0)
|
||||
$display("ALL %0d TESTS PASSED (neural_director, first-free scheduling, N_SLOTS=%0d)", tests, N_SLOTS);
|
||||
else
|
||||
$display("FAILED: %0d/%0d test(s) had errors -- see messages above", errors, tests);
|
||||
$display("========================================");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,66 @@
|
||||
// ================================================================
|
||||
// SYNTHESIS-ONLY TIMING HARNESS -- NOT a functional deliverable.
|
||||
// Same rationale as harness_neural_processor_array.v / harness_
|
||||
// memory_manager.v (see errors.log ERR-0005): neural_director's
|
||||
// per-slot arrayed ports (N_SLOTS=4 * 23-bit addresses x3) exceed the
|
||||
// LFE5U-45F's TRELLIS_IO budget as a bare top-level module.
|
||||
// ================================================================
|
||||
|
||||
module harness_neural_director #(
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter QUEUE_DEPTH = 8
|
||||
)(
|
||||
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_in_valid = lfsr[0];
|
||||
wire [ADDR_WIDTH-1:0] job_in_x_base = lfsr[ADDR_WIDTH-1:0];
|
||||
wire [ADDR_WIDTH-1:0] job_in_w_base = {lfsr[3:0], lfsr[ADDR_WIDTH-5:0]};
|
||||
wire [15:0] job_in_n_tiles = lfsr[15:0];
|
||||
wire [ADDR_WIDTH-1:0] job_in_result_addr = {lfsr[7:0], lfsr[ADDR_WIDTH-9:0]};
|
||||
wire [15:0] job_in_node_id = lfsr[31:16];
|
||||
wire [N_SLOTS-1:0] slot_job_done = lfsr[N_SLOTS-1:0];
|
||||
|
||||
wire job_in_ready;
|
||||
wire [N_SLOTS-1:0] slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base, slot_w_base, slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] slot_n_tiles;
|
||||
wire job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) dut (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(job_in_valid), .job_in_ready(job_in_ready),
|
||||
.job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base),
|
||||
.job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr),
|
||||
.job_in_node_id(job_in_node_id),
|
||||
.slot_job_start(slot_job_start), .slot_x_base(slot_x_base), .slot_w_base(slot_w_base),
|
||||
.slot_n_tiles(slot_n_tiles), .slot_result_addr(slot_result_addr), .slot_job_done(slot_job_done),
|
||||
.job_out_done(job_out_done), .job_out_slot(job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
reg [7:0] chk;
|
||||
always @(posedge clk) begin
|
||||
if (rst) chk <= 8'h0;
|
||||
else chk <= chk ^ {7'h0, job_in_ready} ^ slot_job_start ^ slot_x_base[7:0]
|
||||
^ slot_w_base[7:0] ^ slot_result_addr[7:0] ^ slot_n_tiles[7:0]
|
||||
^ {7'h0, job_out_done} ^ {6'h0, job_out_slot} ^ dir_state ^ {7'h0, dir_error};
|
||||
end
|
||||
assign checksum = chk;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,192 @@
|
||||
|
||||
|
||||
Info: Logic utilisation before packing:
|
||||
Info: Total LUT4s: 163/43848 0%
|
||||
Info: logic LUTs: 107/43848 0%
|
||||
Info: carry LUTs: 8/43848 0%
|
||||
Info: RAM LUTs: 32/ 5481 0%
|
||||
Info: RAMW LUTs: 16/10962 0%
|
||||
|
||||
Info: Total DFFs: 98/43848 0%
|
||||
|
||||
Info: Packing IOs..
|
||||
Info: Packing constants..
|
||||
Info: Packing carries...
|
||||
Info: Packing LUTs...
|
||||
Info: Packing LUT5-7s...
|
||||
Info: Packing FFs...
|
||||
Info: 66 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: 0x77eb46c4
|
||||
|
||||
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: 98/ 43848 0%
|
||||
Info: TRELLIS_COMB: 169/ 43848 0%
|
||||
Info: TRELLIS_RAMW: 8/ 5481 0%
|
||||
|
||||
Info: Placed 0 cells based on constraints.
|
||||
Info: Creating initial analytic placement for 134 cells, random placement wirelen = 12480.
|
||||
Info: at initial placer iter 0, wirelen = 1480
|
||||
Info: at initial placer iter 1, wirelen = 1128
|
||||
Info: at initial placer iter 2, wirelen = 1103
|
||||
Info: at initial placer iter 3, wirelen = 1100
|
||||
Info: Running main analytical placer, max placement attempts per cell = 10804.
|
||||
Info: at iteration #1, type ALL: wirelen solved = 1119, spread = 1315, legal = 1359; time = 0.00s
|
||||
Info: HeAP Placer Time: 0.02s
|
||||
Info: of which solving equations: 0.01s
|
||||
Info: of which spreading cells: 0.00s
|
||||
Info: of which strict legalisation: 0.00s
|
||||
|
||||
Info: Running simulated annealing placer for refinement.
|
||||
Info: at iteration #1: temp = 0.000000, timing cost = 12, wirelen = 1359
|
||||
Info: at iteration #5: temp = 0.000000, timing cost = 33, wirelen = 1072
|
||||
Info: at iteration #10: temp = 0.000000, timing cost = 31, wirelen = 1074
|
||||
Info: at iteration #12: temp = 0.000000, timing cost = 23, wirelen = 1051
|
||||
Info: SA placement time 0.05s
|
||||
|
||||
Info: Max frequency for clock '$glbnet$clk$TRELLIS_IO_IN': 275.10 MHz (PASS at 80.00 MHz)
|
||||
|
||||
Info: Max delay <async> -> posedge $glbnet$clk$TRELLIS_IO_IN: 4.52 ns
|
||||
Info: Max delay posedge $glbnet$clk$TRELLIS_IO_IN -> <async> : 4.86 ns
|
||||
|
||||
Info: Slack histogram:
|
||||
Info: legend: * represents 1 endpoint(s)
|
||||
Info: + represents [1,1) endpoint(s)
|
||||
Info: [ 8865, 9019) |*********************
|
||||
Info: [ 9019, 9173) |*******************
|
||||
Info: [ 9173, 9327) |*****************
|
||||
Info: [ 9327, 9481) |**************
|
||||
Info: [ 9481, 9635) |***
|
||||
Info: [ 9635, 9789) |***
|
||||
Info: [ 9789, 9943) |**
|
||||
Info: [ 9943, 10097) |****
|
||||
Info: [ 10097, 10251) |*****
|
||||
Info: [ 10251, 10405) |*****
|
||||
Info: [ 10405, 10559) |**************
|
||||
Info: [ 10559, 10713) |********************************
|
||||
Info: [ 10713, 10867) |*****************************************
|
||||
Info: [ 10867, 11021) |************************************
|
||||
Info: [ 11021, 11175) |****************************
|
||||
Info: [ 11175, 11329) |******************************
|
||||
Info: [ 11329, 11483) |****
|
||||
Info: [ 11483, 11637) |************
|
||||
Info: [ 11637, 11791) |***************
|
||||
Info: [ 11791, 11945) |***
|
||||
Info: Checksum: 0x23c4f63e
|
||||
Info: Routing globals...
|
||||
Info: routing clock net $glbnet$clk$TRELLIS_IO_IN using global 0
|
||||
|
||||
Info: Routing..
|
||||
Info: Setting up routing queue.
|
||||
Info: Routing 887 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 | 154 829 | 154 829 | 48| 0.08 0.08|
|
||||
Info: 1047 | 154 875 | 0 46 | 0| 0.04 0.11|
|
||||
Info: Routing complete.
|
||||
Info: Router1 time 0.11s
|
||||
Info: Checksum: 0x86bbec10
|
||||
|
||||
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.slot_busy_TRELLIS_FF_Q_2.Q
|
||||
Info: routing 0.68 1.07 Net dut.dir_state_LUT4_D_Z[1] (27,37) -> (28,37)
|
||||
Info: Sink dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0_B0_LUT4_Z.D
|
||||
Info: Defined in:
|
||||
Info: hardware/v2/rtl/neural_director.v:93.23-93.32
|
||||
Info: logic 0.18 1.25 Source dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0_B0_LUT4_Z.F
|
||||
Info: routing 0.85 2.10 Net dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0_B0 (28,37) -> (30,38)
|
||||
Info: Sink dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0$CCU2_COMB0.B
|
||||
Info: logic 0.35 2.46 Source dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0$CCU2_COMB0.FCO
|
||||
Info: routing 0.00 2.46 Net dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0$CCU2_FCI_INT (30,38) -> (30,38)
|
||||
Info: Sink dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0$CCU2_COMB1.FCI
|
||||
Info: logic 0.29 2.75 Source dut.slot_busy_TRELLIS_FF_Q_DI_LUT4_Z_D_LUT4_C_D_CCU2C_S0$CCU2_COMB1.F
|
||||
Info: routing 0.94 3.69 Net dut.dir_state_LUT4_D_Z[3] (30,38) -> (28,35)
|
||||
Info: Sink dut.q_w_base.0.1_DO_LUT4_B_Z_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.87 Source dut.q_w_base.0.1_DO_LUT4_B_Z_LUT4_Z.F
|
||||
Info: routing 0.12 3.99 Net dut.q_w_base.0.1_DO_LUT4_B_Z[2] (28,35) -> (28,35)
|
||||
Info: Sink dut.slot_w_base_TRELLIS_FF_Q_2.DI
|
||||
Info: setup 0.00 3.99 Source dut.slot_w_base_TRELLIS_FF_Q_2.DI
|
||||
Info: 1.40 ns logic, 2.59 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 2.75 2.75 Net rst$TRELLIS_IO_IN (29,0) -> (29,37)
|
||||
Info: Sink dut.slot_job_start_TRELLIS_FF_Q_LSR_LUT4_Z.C
|
||||
Info: Defined in:
|
||||
Info: hardware/v2/synthesis/harness_neural_director.v:15.17-15.20
|
||||
Info: logic 0.18 2.93 Source dut.slot_job_start_TRELLIS_FF_Q_LSR_LUT4_Z.F
|
||||
Info: routing 0.47 3.40 Net dut.slot_job_start_TRELLIS_FF_Q_LSR (29,37) -> (28,36)
|
||||
Info: Sink dut.slot_job_start_TRELLIS_FF_Q_3.LSR
|
||||
Info: setup 0.29 3.68 Source dut.slot_job_start_TRELLIS_FF_Q_3.LSR
|
||||
Info: 0.47 ns logic, 3.22 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_5.Q
|
||||
Info: routing 2.94 3.34 Net checksum[6]$TRELLIS_IO_OUT (32,39) -> (90,38)
|
||||
Info: Sink checksum[6]$tr_io.I
|
||||
Info: Defined in:
|
||||
Info: hardware/v2/synthesis/harness_neural_director.v:57.15-57.18
|
||||
Info: 0.40 ns logic, 2.94 ns routing
|
||||
|
||||
Info: Max frequency for clock '$glbnet$clk$TRELLIS_IO_IN': 250.50 MHz (PASS at 80.00 MHz)
|
||||
|
||||
Info: Max delay <async> -> posedge $glbnet$clk$TRELLIS_IO_IN: 3.68 ns
|
||||
Info: Max delay posedge $glbnet$clk$TRELLIS_IO_IN -> <async> : 3.34 ns
|
||||
|
||||
Info: Slack histogram:
|
||||
Info: legend: * represents 1 endpoint(s)
|
||||
Info: + represents [1,1) endpoint(s)
|
||||
Info: [ 8508, 8673) |**
|
||||
Info: [ 8673, 8838) |********
|
||||
Info: [ 8838, 9003) |***********************
|
||||
Info: [ 9003, 9168) |******
|
||||
Info: [ 9168, 9333) |***********************
|
||||
Info: [ 9333, 9498) |****************
|
||||
Info: [ 9498, 9663) |*
|
||||
Info: [ 9663, 9828) |
|
||||
Info: [ 9828, 9993) |***
|
||||
Info: [ 9993, 10158) |*
|
||||
Info: [ 10158, 10323) |*******
|
||||
Info: [ 10323, 10488) |****
|
||||
Info: [ 10488, 10653) |*********
|
||||
Info: [ 10653, 10818) |*****************************
|
||||
Info: [ 10818, 10983) |***************************
|
||||
Info: [ 10983, 11148) |******************************************
|
||||
Info: [ 11148, 11313) |***************************************
|
||||
Info: [ 11313, 11478) |***********************************
|
||||
Info: [ 11478, 11643) |********************
|
||||
Info: [ 11643, 11808) |*************
|
||||
|
||||
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 it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
|
||||
|
||||
Info: Logic utilisation before packing:
|
||||
Info: Total LUT4s: 522/43848 1%
|
||||
Info: logic LUTs: 382/43848 0%
|
||||
Info: carry LUTs: 8/43848 0%
|
||||
Info: RAM LUTs: 88/ 5481 1%
|
||||
Info: RAMW LUTs: 44/10962 0%
|
||||
|
||||
Info: Total DFFs: 366/43848 0%
|
||||
|
||||
Info: Packing IOs..
|
||||
Info: Packing constants..
|
||||
Info: Packing carries...
|
||||
Info: Packing LUTs...
|
||||
Info: Packing LUT5-7s...
|
||||
Info: Packing FFs...
|
||||
Info: 362 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: 0xb47c9b6c
|
||||
|
||||
Info: Device utilisation:
|
||||
Info: TRELLIS_IO: 461/ 245 188%
|
||||
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: 366/ 43848 0%
|
||||
Info: TRELLIS_COMB: 528/ 43848 1%
|
||||
Info: TRELLIS_RAMW: 22/ 5481 0%
|
||||
|
||||
Info: Placed 0 cells based on constraints.
|
||||
ERROR: Unable to place cell 'job_in_n_tiles[9]$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
Reference in New Issue
Block a user