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:
@@ -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
|
||||
Reference in New Issue
Block a user