exp: real P&R of verified weight-reuse memory path + packed core (EXP-0063)

New hardware/v3/rtl/np_packed_weight_reuse_top.v: flat structural
synthesis wrapper around the EXP-0062-verified module chain (real SDR
SDRAM controller -> layer_prefetch_ctrl.v -> layer_weight_buffer.v ->
weight_tile_gather.v -> neural_processor_packed.v).

Real Vivado post-route: 8/240 DSP48E1 (unchanged, memory path uses
zero DSPs), WNS -2.502ns @ 200MHz -> Fmax ~133.3MHz, only -1.2% vs
the isolated single core (134.9MHz, EXP-0059). Real memory-path
control logic adds negligible Fmax cost at this scale.

Full writeup in hardware/v2/logs/experiments.log EXP-0063.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-16 23:44:24 +02:00
co-authored by Claude Sonnet 5
parent 9ca180a787
commit 5afa6a7477
3 changed files with 208 additions and 0 deletions
+45
View File
@@ -3880,3 +3880,48 @@ EXP-0060) for a real system-adjacent Fmax number; (2) neural_director.v
port to dispatch job PAIRS per packed core remains the next real
integration step for a genuine multi-core, multi-layer system, still
not started.
EXP-0063 -- real P&R of the EXP-0062-verified weight-reuse memory path
+ packed core together (2026-09-17)
CONTEXT: EXP-0062's own next_action -- synthesize the just-verified
combined path (sdram_controller.v -> layer_prefetch_ctrl.v ->
layer_weight_buffer.v -> weight_tile_gather.v -> neural_processor_
packed.v), not just the isolated compute core (EXP-0059) or the
zero-interconnect compute array (EXP-0060), for a real Fmax number
that includes actual memory-path control logic, not just DSP/placement
density.
METHOD: new hardware/v3/rtl/np_packed_weight_reuse_top.v, flat
structural wrapper instantiating the real, EXP-0062-verified module
chain with its real internal connections; control ports (pf_start,
tile_req, operand_valid, ...) exposed at the top level rather than
internally sequenced (the closed-loop sequencing is neural_director.v's
still-not-built job, deliberately out of scope here -- this module
exists only to let Vivado see the real combined logic together).
Real Vivado 2026.1 synth + opt_design + place_design + route_design,
same 200MHz constraint and part (xc7a100tcsg324-1) as EXP-0059/0060,
via hardware/v3/synth/synth_np_packed_weight_reuse_top.tcl.
RESULT (real, post-route): 8/240 DSP48E1 (3.33%, unchanged from the
isolated core -- the memory path itself uses zero DSPs, as expected).
WNS -2.502ns @ 200MHz -> real critical path 7.502ns -> Fmax ~133.3MHz.
Vs EXP-0059's isolated single core (134.9MHz): only -1.2%.
DECISION: real memory-path control logic (prefetch controller, double-
buffered weight scratchpad, tile gather adapter, SDRAM controller) adds
negligible Fmax cost on top of the compute core alone, consistent with
EXP-0060's own finding that placement/interconnect density near the
compute core is not the dominant Fmax driver at this scale. The
remaining open question (still not answered by EXP-0059/0060/0063)
is what a REAL multi-core Director-driven system does to Fmax --
none of these three checks include neural_director.v or N>1 packed
cores sharing a single memory path with real arbitration.
next_action: neural_director.v port for job-PAIR dispatch per packed
core remains the largest, still-not-started real integration step
needed before a genuine multi-core system-level P&R number can be
trusted. Until that exists (correctness-verified per this project's
own standard, per EXP-0062's own disclosed lesson about saturating-
output tests hiding real bugs), no further Fmax numbers from larger
configurations should be treated as system-representative.
@@ -0,0 +1,149 @@
`timescale 1ns/1ps
// ============================================================
// V3 -- synthesis top for the EXP-0062 verified weight-reuse memory
// path + packed compute core, flat structural wiring (real modules,
// real internal connections), for a real P&R resource/timing check.
//
// Control ports (pf_start/tile_req/operand_valid/...) are exposed
// directly at the top level rather than internally sequenced -- the
// closed-loop sequencing logic (what EXP-0062's testbench did
// procedurally) is the still-not-built neural_director.v integration,
// deliberately out of scope here. This module exists ONLY to let
// Vivado see the REAL combined logic (SDRAM controller + prefetch +
// weight buffer + tile gather + packed compute core) together for
// utilization/timing purposes, matching EXP-0059's own single-core
// out-of-context methodology.
// ============================================================
module np_packed_weight_reuse_top #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter BURST_LEN = 8,
parameter ROW_BITS = 13,
parameter COL_BITS = 10,
parameter BANK_BITS = 2,
parameter ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS,
parameter LAYER_BYTES = 128,
parameter BUFADDRW = $clog2(LAYER_BYTES)
)(
input wire clk,
input wire rst,
// ---- layer_prefetch_ctrl.v control ----
input wire pf_start,
input wire [ADDR_WIDTH-1:0] pf_layer_base,
output wire pf_busy,
output wire pf_done,
// ---- layer_weight_buffer.v control ----
input wire consume_done,
// ---- weight_tile_gather.v control ----
input wire tile_req,
input wire [BUFADDRW-1:0] tile_base,
output wire tile_valid,
// ---- neural_processor_packed.v job/operand control ----
input wire job_valid,
output wire job_ready,
input wire [15:0] job_node_id_a,
input wire [15:0] job_node_id_b,
input wire signed [DATA_WIDTH-1:0] job_bias,
input wire [1:0] job_activation,
input wire operand_valid,
output wire operand_ready,
input wire signed [DATA_WIDTH*P_IN-1:0] input_data_a,
input wire signed [DATA_WIDTH*P_IN-1:0] input_data_b,
input wire tile_last,
output wire result_valid,
input wire result_ready,
output wire signed [DATA_WIDTH-1:0] result_data_a,
output wire signed [DATA_WIDTH-1:0] result_data_b,
output wire [15:0] result_node_id_a,
output wire [15:0] result_node_id_b,
// ---- real SDRAM pins ----
output wire sdram_cke,
output wire sdram_cs_n,
output wire sdram_ras_n,
output wire sdram_cas_n,
output wire sdram_we_n,
output wire [BANK_BITS-1:0] sdram_ba,
output wire [ROW_BITS-1:0] sdram_a,
inout wire [15:0] sdram_dq,
output wire [1:0] sdram_dqm
);
wire ctrl_req, ctrl_wr, ctrl_ready, ctrl_busy;
wire [ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [2*BURST_LEN-1:0] ctrl_wmask;
sdram_controller #(
.CLK_FREQ_MHZ(64), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
.clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr),
.wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
);
wire pf_fill_we;
wire [BUFADDRW-1:0] pf_fill_addr;
wire [DATA_WIDTH-1:0] pf_fill_data;
layer_prefetch_ctrl #(
.DATA_WIDTH(DATA_WIDTH), .LAYER_BYTES(LAYER_BYTES), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
) u_pf (
.clk(clk), .rst(rst),
.start(pf_start), .layer_base(pf_layer_base), .busy(pf_busy), .done(pf_done),
.fill_we(pf_fill_we), .fill_addr(pf_fill_addr), .fill_data(pf_fill_data),
.ctrl_req(ctrl_req), .ctrl_wr(ctrl_wr), .ctrl_addr(ctrl_addr),
.ctrl_wdata(ctrl_wdata), .ctrl_wmask(ctrl_wmask),
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
wire [BUFADDRW-1:0] lwb_rd_addr;
wire [DATA_WIDTH-1:0] lwb_rd_data;
layer_weight_buffer #(.DATA_WIDTH(DATA_WIDTH), .LAYER_DEPTH(LAYER_BYTES)) u_lwb (
.clk(clk), .rst(rst),
.fill_we(pf_fill_we), .fill_addr(pf_fill_addr), .fill_data(pf_fill_data), .fill_done(pf_done),
.rd_addr(lwb_rd_addr), .rd_data(lwb_rd_data), .consume_done(consume_done),
.active_sel(), .swapped()
);
wire [DATA_WIDTH*P_IN-1:0] tile_data;
weight_tile_gather #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BUFADDRW(BUFADDRW)
) u_gather (
.clk(clk), .rst(rst),
.tile_req(tile_req), .tile_base(tile_base),
.tile_valid(tile_valid), .tile_data(tile_data),
.rd_addr(lwb_rd_addr), .rd_data(lwb_rd_data)
);
wire [3:0] np_state;
wire np_error;
neural_processor_packed #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
) u_np (
.clk(clk), .rst(rst),
.job_valid(job_valid), .job_ready(job_ready),
.job_node_id_a(job_node_id_a), .job_node_id_b(job_node_id_b),
.job_bias(job_bias), .job_activation(job_activation),
.operand_valid(operand_valid), .operand_ready(operand_ready),
.input_data_a(input_data_a), .input_data_b(input_data_b),
.weight_data(tile_data), .tile_last(tile_last),
.result_valid(result_valid), .result_ready(result_ready),
.result_data_a(result_data_a), .result_data_b(result_data_b),
.result_node_id_a(result_node_id_a), .result_node_id_b(result_node_id_b),
.np_state(np_state), .np_error(np_error)
);
endmodule
@@ -0,0 +1,14 @@
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v2/rtl/layer_prefetch_ctrl.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v2/rtl/layer_weight_buffer.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v3/rtl/weight_tile_gather.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v3/rtl/neural_processor_packed.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v2/nms/rtl/sdram_controller.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v3/rtl/np_packed_weight_reuse_top.v
synth_design -top np_packed_weight_reuse_top -part xc7a100tcsg324-1 -mode out_of_context
create_clock -name clk -period 5.000 [get_ports clk]
opt_design
place_design
route_design
report_utilization -file /tmp/util_weight_reuse_top_postroute.rpt
report_timing_summary -file /tmp/timing_weight_reuse_top_postroute.rpt
write_checkpoint -force /tmp/weight_reuse_top_postroute.dcp