feat(v2): M7 Dataflow Core - full M1-M6 integration, wake-up loop closed end-to-end

dataflow_core.v integrates dependency_manager (M6) -> neural_director
(M5) -> N_SLOTS x (memory_manager (M4) + neural_processor (M1)) for
the first time. A slot's completion (via neural_director's new
slot_node_id tracking, an additive port) feeds back as a
producer_done event to dependency_manager, waking up any node that
depended on it - closing the dataflow loop without external glue.

Verified end-to-end (Verilator) on a 3-node DAG: two independent
nodes plus a third depending on both, confirmed to dispatch only
after both genuinely complete via real neural_processor computation.
4/4 PASS.

Real synthesis + nextpnr-ecp5 P&R via a synthesis-only timing harness
(bare per-slot backend ports exceed the LFE5U-45F's TRELLIS_IO
budget, same pattern as ERR-0005): N_SLOTS=2 -> 165.15 MHz,
N_SLOTS=4 -> 133.19 MHz, both PASS at 80MHz, 0 synthesis problems.

Scope explicitly deferred to M8 (DEC-0009): M3's BRAM buffers not
wired in yet, per-slot Memory Backend Interface ports not arbitrated
to one shared PSRAM master yet - both need real measured data before
committing to a design, not guessed at here.

Logged: simulation/synthesis/timing/benchmark/decisions (DEC-0009)/
experiments (EXP-0008)/errors (ERR-0007, a Yosys chparam-ordering
build quirk, not an RTL bug)/development.log, ROADMAP.md updated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-05 15:05:28 +02:00
co-authored by Claude Sonnet 5
parent 8af16d3a12
commit 77baa8fc16
13 changed files with 798 additions and 1 deletions
@@ -0,0 +1,117 @@
// ================================================================
// SYNTHESIS-ONLY TIMING HARNESS -- NOT a functional deliverable.
// Same rationale/pattern as harness_neural_processor_array.v and
// harness_memory_manager.v (see their headers, and
// hardware/v2/logs/errors.log ERR-0005): dataflow_core's own ports
// (per-slot mem_addr/wdata/rdata buses, node registration fields)
// exceed the LFE5U-45F's ~245 TRELLIS_IO budget as a bare top-level
// module well before N_SLOTS=2 (measured: N_SLOTS=4 alone needs 280
// bits just for the per-slot Memory Backend Interface arrays).
//
// dataflow_core.v additionally instantiates N_SLOTS REAL copies of
// (memory_manager + neural_processor) via `generate` -- exactly the
// same CSE risk already hit and fixed once in
// harness_neural_processor_array.v (giving every instance IDENTICAL
// LFSR data lets Yosys collapse all N_SLOTS copies down to 1). This
// harness reuses that fix: each slot's mem_rdata/mem_ready input gets
// its own distinct bit-rotated LFSR slice, and the checksum folds in
// a real bit from EVERY slot's own outputs, not just slot 0's.
//
// Only clk/rst/seed/checksum are exposed as real top-level pins.
// ================================================================
module harness_dataflow_core #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter ADDR_WIDTH = 23,
parameter N_SLOTS = 4,
parameter N_NODES = 16,
parameter MAX_DEPS = 4,
parameter QUEUE_DEPTH = 8
)(
input wire clk,
input wire rst,
input wire [7:0] seed,
output wire [7:0] checksum
);
localparam NODE_IDW = $clog2(N_NODES);
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
// ---- node-registration side: a single port, no per-instance
// CSE risk -- plain LFSR slices are enough. ----
wire reg_valid = lfsr[0];
wire [NODE_IDW-1:0] reg_node_id = lfsr[NODE_IDW-1:0];
wire [$clog2(MAX_DEPS+1)-1:0] reg_required = lfsr[$clog2(MAX_DEPS+1)-1:0];
wire [MAX_DEPS*NODE_IDW-1:0] reg_producer_ids;
wire [ADDR_WIDTH-1:0] reg_x_base = lfsr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0] reg_w_base = {lfsr[3:0], lfsr[ADDR_WIDTH-5:0]};
wire [15:0] reg_n_tiles = lfsr[15:0];
wire [ADDR_WIDTH-1:0] reg_result_addr = {lfsr[6:0], lfsr[ADDR_WIDTH-8:0]};
genvar pgi;
generate
for (pgi = 0; pgi < MAX_DEPS; pgi = pgi + 1) begin : GEN_PID
wire [31:0] prot = {lfsr[pgi:0], lfsr[31:pgi+1]};
assign reg_producer_ids[pgi*NODE_IDW +: NODE_IDW] = prot[NODE_IDW-1:0];
end
endgenerate
// ---- per-slot Memory Backend Interface inputs: EACH slot needs
// a DISTINCT rotated slice (see file header) so the N_SLOTS
// memory_manager+neural_processor pairs stay N_SLOTS real,
// distinguishable instances instead of collapsing to 1. ----
wire signed [8*N_SLOTS-1:0] slot_mem_rdata;
wire [N_SLOTS-1:0] slot_mem_ready;
genvar sgi;
generate
for (sgi = 0; sgi < N_SLOTS; sgi = sgi + 1) begin : GEN_SLOT_DRIVE
wire [31:0] srot = {lfsr[sgi:0], lfsr[31:sgi+1]};
assign slot_mem_rdata[sgi*8 +: 8] = srot[7:0];
assign slot_mem_ready[sgi] = srot[8];
end
endgenerate
wire reg_ready;
wire [N_SLOTS-1:0] slot_mem_req, slot_mem_wr;
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_mem_addr;
wire signed [8*N_SLOTS-1:0] slot_mem_wdata;
dataflow_core #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH)
) dut (
.clk(clk), .rst(rst),
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
.slot_mem_wdata(slot_mem_wdata), .slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
);
// Fold in a real bit from EVERY slot's own outputs (not just slot
// 0's) -- otherwise all slots but one have no observable output
// path and Yosys correctly strips them as dead logic.
wire [N_SLOTS-1:0] addr_lsb, wdata_lsb;
generate
for (sgi = 0; sgi < N_SLOTS; sgi = sgi + 1) begin : GEN_CHK_LANE
assign addr_lsb[sgi] = slot_mem_addr[sgi*ADDR_WIDTH];
assign wdata_lsb[sgi] = slot_mem_wdata[sgi*8];
end
endgenerate
reg [7:0] chk;
always @(posedge clk) begin
if (rst) chk <= 8'h0;
else chk <= chk ^ {7'h0, reg_ready} ^ slot_mem_req ^ slot_mem_wr
^ addr_lsb ^ wdata_lsb;
end
assign checksum = chk;
endmodule