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:
2026-09-05 14:46:32 +02:00
co-authored by Claude Sonnet 5
parent 175f697ae1
commit 2e4cedc761
18 changed files with 82810 additions and 1 deletions
+4 -1
View File
@@ -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.
+7
View File
@@ -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 |
+70
View File
@@ -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
+22
View File
@@ -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.
+48
View File
@@ -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.
+6
View File
@@ -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
+3
View File
@@ -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.
+5
View File
@@ -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)
+220
View File
@@ -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
+322
View File
@@ -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