exp: N=8 packed-core array real P&R, isolates placement density from interconnect (EXP-0060)

New hardware/v3/rtl/np_packed_array.v: flat array of 8 unmodified
neural_processor_packed.v instances, fully independent I/O, zero
shared arbiter/Director logic -- isolates exactly one variable
(DSP/placement density) from EXP-0059's single-core baseline.

Real Vivado post-route: 64/240 DSP48E1 (26.67%), WNS -2.592ns @
200MHz -> Fmax ~131.7MHz, only -2.4% vs the single-core 134.9MHz.
Placement density alone is NOT the main driver of the ECP5-era gap
between isolated-core and full-system Fmax -- narrows the question
for the still-unbuilt real Director/arbiter/memory integration.

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

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:29:54 +02:00
co-authored by Claude Sonnet 5
parent 34b15cc0a7
commit 94b63705be
3 changed files with 139 additions and 0 deletions
+48
View File
@@ -3713,3 +3713,51 @@ actually targeted (part number confirmed xc7a100tcsg324-1, package/
board pinout not yet chosen) is still needed before any real bring-up,
matching this project's own "no board target skipped" discipline from
V2/STEP19.
EXP-0060 -- N=8 neural_processor_packed.v placement/routing density
check, real Vivado P&R, isolating the congestion variable from any
Director/arbiter/memory RTL (2026-09-17)
CONTEXT: EXP-0059's own next_action flagged that the isolated single-
core Fmax (134.9MHz real post-route) is not the same question as real
N-core system Fmax -- on V2/ECP5 the full board-level system Fmax
(64-97MHz) was measurably lower than the isolated dataflow-core Fmax
(92.63MHz). Before building the (larger, riskier, unverified) real
Director+arbiter+memory integration, this experiment isolates ONE
variable first, per this project's own "one variable at a time" rule:
what does pure DSP/placement DENSITY alone do to Fmax, with zero
shared interconnect logic between cores?
METHOD: new `hardware/v3/rtl/np_packed_array.v`, N_CORES=8 flat array
of unmodified neural_processor_packed.v instances, each core's I/O fully
independent (flattened N*WIDTH buses, sliced per-instance, generate
block) -- deliberately NO arbiter/Director/shared bus, so any Fmax
change vs EXP-0059's single-core number is attributable ONLY to
placement/routing congestion from DSP/LUT/FF density, not to any new
(unverified) integration logic. Real Vivado 2026.1 out-of-context
synth + opt_design + place_design + route_design, same 200MHz
(-period 5.000) constraint and part (xc7a100tcsg324-1) as EXP-0059,
via `hardware/v3/synth/synth_np_packed_array_n8.tcl`.
RESULT (real, post-route, not estimated): 64/240 DSP48E1 (26.67%,
exactly 8 cores x 8 DSP, matches EXP-0059's per-core count). WNS
-2.592ns @ 200MHz -> real critical path 7.592ns -> Fmax ~131.7MHz.
Vs EXP-0059's single-core 134.9MHz: a real but SMALL degradation,
-2.4%, from placement/routing density alone at 8 independent cores /
~27% DSP utilization.
DECISION: the earlier V2/ECP5 gap between isolated-core and full-
system Fmax is NOT mostly explained by raw compute-array placement
density (this experiment's -2.4% is far smaller than ECP5's ~30%+
isolated-vs-system gap) -- the real driver is most likely the shared
Director/arbiter/memory-path interconnect logic itself, not yet built
or tested here. This narrows, not answers, the open question from
EXP-0059 -- still no real system-level number exists.
next_action: build the real Director + memory-feed path for the
packed 2-job-per-core model (neural_director.v needs real changes to
dispatch PAIRS of jobs per core, not a 1:1 port) as a properly
correctness-verified (isolated testbench, bit-exact vs golden model)
integration BEFORE the next real P&R congestion check -- do not
synthesize unverified integration RTL just to get another Fmax number,
per this project's own correctness-first standard.
+80
View File
@@ -0,0 +1,80 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0059 follow-up -- N independent neural_processor_packed.v
// cores, flat array, NO Director/arbiter/memory path yet.
//
// PURPOSE: isolate exactly one variable -- what real P&R placement/
// routing congestion does to Fmax once N copies of the DSP48-packed
// core sit side by side on XC7A100T -- before adding any new
// (unverified) integration RTL (Director, arbiter, memory path).
// Each core keeps its own independent, unshared I/O (flattened to
// N*WIDTH buses, sliced per-instance below); there is deliberately NO
// interconnect logic here to conflate with the placement-density
// question this experiment is asking. Matches this project's own
// "one variable at a time" convention (see decisions.log).
// ============================================================
module np_packed_array #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter N_CORES = 8
)(
input wire clk,
input wire rst,
input wire [N_CORES-1:0] job_valid,
output wire [N_CORES-1:0] job_ready,
input wire [N_CORES*16-1:0] job_node_id_a,
input wire [N_CORES*16-1:0] job_node_id_b,
input wire [N_CORES*DATA_WIDTH-1:0] job_bias,
input wire [N_CORES*2-1:0] job_activation,
input wire [N_CORES-1:0] operand_valid,
output wire [N_CORES-1:0] operand_ready,
input wire [N_CORES*DATA_WIDTH*P_IN-1:0] input_data_a,
input wire [N_CORES*DATA_WIDTH*P_IN-1:0] input_data_b,
input wire [N_CORES*DATA_WIDTH*P_IN-1:0] weight_data,
input wire [N_CORES-1:0] tile_last,
output wire [N_CORES-1:0] result_valid,
input wire [N_CORES-1:0] result_ready,
output wire [N_CORES*DATA_WIDTH-1:0] result_data_a,
output wire [N_CORES*DATA_WIDTH-1:0] result_data_b,
output wire [N_CORES*16-1:0] result_node_id_a,
output wire [N_CORES*16-1:0] result_node_id_b,
output wire [N_CORES*4-1:0] np_state,
output wire [N_CORES-1:0] np_error
);
genvar gc;
generate
for (gc = 0; gc < N_CORES; gc = gc + 1) begin : GEN_CORE
neural_processor_packed #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
) u_core (
.clk(clk), .rst(rst),
.job_valid(job_valid[gc]),
.job_ready(job_ready[gc]),
.job_node_id_a(job_node_id_a[gc*16 +: 16]),
.job_node_id_b(job_node_id_b[gc*16 +: 16]),
.job_bias(job_bias[gc*DATA_WIDTH +: DATA_WIDTH]),
.job_activation(job_activation[gc*2 +: 2]),
.operand_valid(operand_valid[gc]),
.operand_ready(operand_ready[gc]),
.input_data_a(input_data_a[gc*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.input_data_b(input_data_b[gc*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.weight_data(weight_data[gc*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
.tile_last(tile_last[gc]),
.result_valid(result_valid[gc]),
.result_ready(result_ready[gc]),
.result_data_a(result_data_a[gc*DATA_WIDTH +: DATA_WIDTH]),
.result_data_b(result_data_b[gc*DATA_WIDTH +: DATA_WIDTH]),
.result_node_id_a(result_node_id_a[gc*16 +: 16]),
.result_node_id_b(result_node_id_b[gc*16 +: 16]),
.np_state(np_state[gc*4 +: 4]),
.np_error(np_error[gc])
);
end
endgenerate
endmodule
@@ -0,0 +1,11 @@
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v3/rtl/neural_processor_packed.v
read_verilog -sv /home/michele/Develop/FPGA-Neural/hardware/v3/rtl/np_packed_array.v
synth_design -top np_packed_array -part xc7a100tcsg324-1 -mode out_of_context -generic N_CORES=8
create_clock -name clk -period 5.000 [get_ports clk]
opt_design
report_utilization -file /tmp/util_np_array_n8_postsynth.rpt
place_design
route_design
report_utilization -file /tmp/util_np_array_n8_postroute.rpt
report_timing_summary -file /tmp/timing_np_array_n8_postroute.rpt
write_checkpoint -force /tmp/np_array_n8_postroute.dcp