feat(v2): M6 Dependency Manager, multi-dependency wake-up
Implements M6: dependency_manager.v tracks a table of node
descriptors (node_id/state/required_dependencies/resolved_
dependencies/producer_ids -- §10's exact field list), incrementing a
waiting node's resolved count whenever one of its listed producers
completes, transitioning it to READY once resolved==required, and
dispatching ready nodes to the Neural Director (M5) one at a time via
a backpressure-safe valid/ready interface.
Verified with Verilator on a small hand-built DAG: node0/node1 have no
dependencies (dispatch immediately); node2 depends on BOTH node0 AND
node1 ("dipendenze multiple") and stays WAITING until both complete,
confirmed via an explicit negative check after only one resolves;
node3 depends on node0 ALONE, demonstrating a single producer
("node0") satisfying two different consumers' dependencies
("risultati condivisi... piu' consumer") -- node3 fully, node2
partially. 4/4 tests pass.
Scope for this milestone (decisions.log DEC-0008): dependency
COUNTING/readiness only, no direct producer-to-consumer value
forwarding (§11 frames that as a "quando possibile" optimization, not
a correctness requirement -- deferred until real bandwidth
measurements justify it) and no node-slot reclamation after dispatch
(not exercised by any scenario built so far).
Real synthesis: 0 CHECK problems, 763 LUT4/474 FF/0 DSP/0 CCU2C. Real
place&route (module fits the TRELLIS_IO budget as a bare top-level
this time, no harness needed): Fmax 155.30 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:
@@ -0,0 +1,192 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// M6 testbench (docs/v2-description.md §10/§20): dependency_manager.v
|
||||
// -- a small DAG: node0 and node1 have no dependencies (immediately
|
||||
// READY); node2 depends on BOTH node0 and node1 (required=2, "multiple
|
||||
// dependencies"); node3 depends on node0 ALONE (required=1) --
|
||||
// node0's single completion must satisfy BOTH node2 (partially) and
|
||||
// node3 (fully), proving "risultati condivisi... piu' consumer" (a
|
||||
// shared producer feeding multiple waiting consumers).
|
||||
//
|
||||
// node0 --+--> node2 (needs node0 AND node1)
|
||||
// \-+--> node3 (needs node0 only)
|
||||
// node1 ---+
|
||||
//
|
||||
// Verified with Verilator (decisions.log DEC-0004).
|
||||
// ============================================================
|
||||
|
||||
module tb;
|
||||
|
||||
localparam N_NODES = 8;
|
||||
localparam MAX_DEPS = 4;
|
||||
localparam ADDR_WIDTH = 23;
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
reg clk, rst;
|
||||
initial begin clk = 0; forever #5 clk = ~clk; end
|
||||
|
||||
reg reg_valid;
|
||||
wire reg_ready;
|
||||
reg [NODE_IDW-1:0] reg_node_id;
|
||||
reg [$clog2(MAX_DEPS+1)-1:0] reg_required;
|
||||
reg [MAX_DEPS*NODE_IDW-1:0] reg_producer_ids;
|
||||
reg [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
|
||||
reg [15:0] reg_n_tiles;
|
||||
|
||||
reg producer_done_valid;
|
||||
reg [NODE_IDW-1:0] producer_done_node_id;
|
||||
|
||||
wire ready_valid;
|
||||
reg ready_ready;
|
||||
wire [NODE_IDW-1:0] ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] ready_x_base, ready_w_base, ready_result_addr;
|
||||
wire [15:0] ready_n_tiles;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dm (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(producer_done_valid), .producer_done_node_id(producer_done_node_id),
|
||||
.ready_valid(ready_valid), .ready_ready(ready_ready), .ready_node_id(ready_node_id),
|
||||
.ready_x_base(ready_x_base), .ready_w_base(ready_w_base),
|
||||
.ready_n_tiles(ready_n_tiles), .ready_result_addr(ready_result_addr)
|
||||
);
|
||||
|
||||
integer errors, tests;
|
||||
|
||||
task automatic register_node(
|
||||
input [NODE_IDW-1:0] nid,
|
||||
input [$clog2(MAX_DEPS+1)-1:0] required,
|
||||
input [NODE_IDW-1:0] p0, input [NODE_IDW-1:0] p1,
|
||||
input [15:0] n_tiles_tag // used as a unique tag (via n_tiles field) to identify which node got dispatched
|
||||
);
|
||||
begin
|
||||
@(posedge clk);
|
||||
reg_node_id = nid;
|
||||
reg_required = required;
|
||||
reg_producer_ids = {NODE_IDW*MAX_DEPS{1'b0}};
|
||||
reg_producer_ids[0*NODE_IDW +: NODE_IDW] = p0;
|
||||
reg_producer_ids[1*NODE_IDW +: NODE_IDW] = p1;
|
||||
reg_x_base = {ADDR_WIDTH{1'b0}} + nid;
|
||||
reg_w_base = {ADDR_WIDTH{1'b0}} + nid + 100;
|
||||
reg_n_tiles = n_tiles_tag;
|
||||
reg_result_addr = {ADDR_WIDTH{1'b0}} + nid + 200;
|
||||
reg_valid = 1'b1;
|
||||
while (!reg_ready) @(posedge clk);
|
||||
@(posedge clk);
|
||||
reg_valid = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Collect dispatched node ids (in order) into a small scoreboard.
|
||||
reg [NODE_IDW-1:0] dispatched [0:15];
|
||||
integer n_dispatched;
|
||||
|
||||
task automatic collect_one_dispatch(input integer watchdog_max);
|
||||
integer wd;
|
||||
begin
|
||||
ready_ready = 1'b1;
|
||||
wd = 0;
|
||||
while (!ready_valid && wd < watchdog_max) begin @(posedge clk); wd = wd + 1; end
|
||||
if (!ready_valid) begin
|
||||
$display("FAIL: no ready_valid within watchdog (n_dispatched so far=%0d)", n_dispatched);
|
||||
errors = errors + 1;
|
||||
end else begin
|
||||
dispatched[n_dispatched] = ready_node_id;
|
||||
$display("DISPATCH node_id=%0d (n_tiles tag=%0d)", ready_node_id, ready_n_tiles);
|
||||
n_dispatched = n_dispatched + 1;
|
||||
@(posedge clk);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
integer i;
|
||||
|
||||
initial begin
|
||||
errors = 0; tests = 0; n_dispatched = 0;
|
||||
rst = 1; reg_valid = 0; producer_done_valid = 0; ready_ready = 0;
|
||||
reg_node_id = 0; reg_required = 0; reg_producer_ids = 0;
|
||||
reg_x_base = 0; reg_w_base = 0; reg_n_tiles = 0; reg_result_addr = 0;
|
||||
producer_done_node_id = 0;
|
||||
repeat(4) @(posedge clk);
|
||||
rst = 0;
|
||||
@(posedge clk);
|
||||
|
||||
// node0, node1: no dependencies -> immediately READY
|
||||
register_node(0, 0, 0, 0, 16'd1000);
|
||||
register_node(1, 0, 0, 0, 16'd1001);
|
||||
// node2: depends on BOTH node0 and node1 (multiple dependencies)
|
||||
register_node(2, 2, 0, 1, 16'd1002);
|
||||
// node3: depends on node0 ONLY (shared producer, multiple consumers)
|
||||
register_node(3, 1, 0, 0, 16'd1003);
|
||||
|
||||
// ---- TEST 1: node0 and node1 must both be dispatched first
|
||||
// (they were already READY at registration) -- order between
|
||||
// them is not asserted (both are simultaneously ready,
|
||||
// first-found scan is deterministic but not part of the
|
||||
// contract), just that BOTH appear before node2/node3. ----
|
||||
tests = tests + 1;
|
||||
collect_one_dispatch(50);
|
||||
collect_one_dispatch(50);
|
||||
if (!((dispatched[0] == 0 && dispatched[1] == 1) || (dispatched[0] == 1 && dispatched[1] == 0))) begin
|
||||
$display("FAIL: expected node0+node1 dispatched first (in either order), got %0d,%0d", dispatched[0], dispatched[1]);
|
||||
errors = errors + 1;
|
||||
end else $display("PASS: node0 and node1 (no dependencies) dispatched first, in either order");
|
||||
|
||||
// node2/node3 must NOT be ready yet (still WAITING)
|
||||
tests = tests + 1;
|
||||
ready_ready = 1'b0;
|
||||
repeat(5) @(posedge clk);
|
||||
if (ready_valid) begin
|
||||
$display("FAIL: a node became ready before any producer_done -- got node_id=%0d", ready_node_id);
|
||||
errors = errors + 1;
|
||||
end else $display("PASS: node2/node3 correctly still WAITING (no producer_done yet)");
|
||||
|
||||
// ---- TEST 2: node0 completes -> node3 (needs only node0)
|
||||
// becomes READY; node2 (needs node0 AND node1) does NOT yet. ----
|
||||
tests = tests + 1;
|
||||
producer_done_node_id = 0;
|
||||
producer_done_valid = 1'b1;
|
||||
@(posedge clk);
|
||||
producer_done_valid = 1'b0;
|
||||
collect_one_dispatch(50);
|
||||
if (dispatched[2] !== 3) begin
|
||||
$display("FAIL: expected node3 to become ready right after node0 completed, got node_id=%0d", dispatched[2]);
|
||||
errors = errors + 1;
|
||||
end else $display("PASS: node3 (single dependency on node0) became READY right after node0 completed");
|
||||
|
||||
ready_ready = 1'b0;
|
||||
repeat(5) @(posedge clk);
|
||||
if (ready_valid) begin
|
||||
$display("FAIL: node2 became ready after only ONE of its two dependencies (node0) completed -- got node_id=%0d", ready_node_id);
|
||||
errors = errors + 1;
|
||||
end else $display("PASS: node2 correctly still WAITING (only 1/2 dependencies resolved)");
|
||||
|
||||
// ---- TEST 3: node1 completes -> node2 (needed BOTH) now
|
||||
// becomes READY -- "multiple dependencies" fully resolved. ----
|
||||
tests = tests + 1;
|
||||
producer_done_node_id = 1;
|
||||
producer_done_valid = 1'b1;
|
||||
@(posedge clk);
|
||||
producer_done_valid = 1'b0;
|
||||
collect_one_dispatch(50);
|
||||
if (dispatched[3] !== 2) begin
|
||||
$display("FAIL: expected node2 to become ready after BOTH node0 and node1 completed, got node_id=%0d", dispatched[3]);
|
||||
errors = errors + 1;
|
||||
end else $display("PASS: node2 (two dependencies, node0+node1) became READY only after BOTH completed");
|
||||
|
||||
$display("========================================");
|
||||
if (errors == 0)
|
||||
$display("ALL %0d TESTS PASSED (dependency_manager -- multi-dependency + shared-producer/multi-consumer wake-up)", tests);
|
||||
else
|
||||
$display("FAILED: %0d/%0d test(s) had errors -- see messages above", errors, tests);
|
||||
$display("========================================");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user