feat: real DDRManager re-measurement at 32-bit channel + BOM/pinout docs (EXP-0087)

Re-measured ddr_prefetch_mgr.v's (EXP-0083) real benefit against the
now-closed 32-bit DDR3 channel (EXP-0086), per this project's own
standing plan. Real result: the 2.86% benefit measured at the old
16-bit channel is GONE at 32-bit (WITH: 100663.1335ns vs WITHOUT:
100656.6835ns -- a 0.0064% regression, statistically a wash). The
wider channel's lower per-tile latency already absorbs the gap the
look-ahead prefetch used to hide. Kept wired in for correctness/
timing-neutrality (real P&R already signs off with it included), but
it's no longer a real performance win. Updated docs/ARCHITECTURE_
ANALYSIS.md and docs/PHYSICAL_REALIZATION.md accordingly.

Found and fixed 3 real testbench/simulation-setup bugs along the way:
- tb_n2_system_ddr3.v and tb_mig_native_adapter.v still had a stale
  CLKIN_PERIOD=2900 (the FAILED EXP-0084 clock period) instead of the
  current real, closed 3225ps (EXP-0086).
- tb_n2_system_ddr3.v used SystemVerilog-only $signed(8'(...)) cast
  syntax, invalid for xvlog's default plain-Verilog mode -- fixed via
  an intermediate 8-bit reg.
- Building a fresh sim_1 fileset needs the real MIG simulation
  dependency set added explicitly (mig_7series_0_mig.v is marked
  USED_IN_SIMULATION=0 in the project since testbenches bypass the
  public wrapper); verilog_define is a fileset-level property, not
  per-file, in this Vivado version.

New measurement-only fork (not part of the real synthesis target, per
fork-before-promote discipline): packed_slot_noprefetch.v +
tb_n2_system_ddr3_noprefetch.v, reproducing the pre-EXP-0083 direct
per-tile activation-fetch sequencing for a fair A/B baseline.

Also adds docs/BOM.md and docs/PINOUT.md: a real component list (DDR3
x2, flash, FPGA already verified; clk_ref oscillator and an ESP32-S3-
WROOM-1 module newly verified in-stock on LCSC; sys_clk oscillator
flagged as needing a custom-programmed order, no off-the-shelf SKU at
the required 310.077MHz) and a consolidated, board-layout-ready pinout
extract of PHYSICAL_REALIZATION.md's own pin tables.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-20 18:37:41 +02:00
co-authored by Claude Sonnet 5
parent 678caa9bce
commit 344e798ad5
9 changed files with 1276 additions and 48 deletions
+106
View File
@@ -5707,3 +5707,109 @@ closes"). (3) Build the result-writeback engine (ARCHITECTURE_ANALYSIS
core-count scaling tests, each with its own real P&R signoff, per the
user's own standing directive ("senza illusioni ma analizzando la
situazione piu' performante").
EXP-0087 -- real re-measurement of DDRManager (EXP-0083) benefit
against the now-closed 32-bit channel: real result is that the
benefit VANISHES (2026-09-20, user's own directive: "misuriamo il
beneficio come consigli" -- re-measure once the wider channel's
timing actually closes, per EXP-0086's own next_action)
CONTEXT: EXP-0083's own real 2.86% stall-reduction figure for
ddr_prefetch_mgr.v (single-slot look-ahead activation prefetch) was
measured ONLY against the OLD 16-bit/155.039MHz DDR3 channel -- never
re-verified at the real, now-closed 32-bit/155.039MHz config (EXP-0086).
This experiment redoes that A/B measurement fairly, both variants now
run against the SAME real 32-bit channel.
METHOD: real xsim (Vivado's own project-integrated `launch_simulation`,
not raw xvlog/xelab/xsim by hand) of `tb_n2_system_ddr3.v` against a
freshly-built `sim_1` fileset, real `ddr3_model.sv` (2 real chip
instances) + real `mig_7series_0_mig` (not the public wrapper, matching
this project's own established SIM_BYPASS_INIT_CAL="FAST" override
pattern). Real A/B pair:
- WITH prefetch: the CURRENT, real, committed `packed_slot.v` (wires
`ddr_prefetch_mgr.v`, unmodified).
- WITHOUT prefetch: a new, measurement-only fork,
`hardware/v3/sim/packed_slot_noprefetch.v`, reproducing the
pre-EXP-0083 baseline sequencing -- direct `act_tile_fetch.v`,
one req/wait/consume cycle per tile, no look-ahead overlap. Per
this project's own fork-before-promote discipline: NOT part of the
real synthesis target, sim-only, alongside its own driver testbench
`hardware/v3/sim/tb_n2_system_ddr3_noprefetch.v` (identical to
tb_n2_system_ddr3.v except the one module instantiation swapped).
REAL SETUP BUGS FOUND AND FIXED BEFORE A TRUSTWORTHY MEASUREMENT WAS
POSSIBLE (none of these were about the DDRManager itself -- all were
real, pre-existing or fresh-fileset gaps in the test infrastructure):
1. `tb_n2_system_ddr3.v` and `tb_mig_native_adapter.v` both still had
`CLKIN_PERIOD = 2900` (the FAILED EXP-0084 clock period) hardcoded
-- stale since EXP-0086 reverted the REAL config to 3225ps. Fixed
both to 3225, so this and all future xsim runs against these
testbenches reflect the real, current, closed-timing hardware
config, not a superseded one.
2. `tb_n2_system_ddr3.v` used SystemVerilog-only `$signed(8'((expr) &
8'hFF))` sized-cast syntax in two golden-data helper functions --
silently invalid for `xvlog` in its default (non `-sv`) mode for a
plain `.v` file, exactly the class of bug CLAUDE.md's own
"no SV-only syntax in a plain .v file" lesson already warned about
(until now only checked for synthesizable RTL, this is the first
real hit in a TESTBENCH). Fixed with an intermediate 8-bit `reg`
doing the same width-truncation-before-`$signed()` job portably.
3. Building a FRESH `sim_1` fileset from scratch (rather than reusing
a pre-populated one) does not auto-pull in `mig_7series_0_mig.v`'s
own real simulation dependency set -- that file is marked
`USED_IN_SIMULATION=0` in the project (Vivado expects the PUBLIC
`mig_7series_0.v` wrapper to be the sim entry point; this project's
own testbenches deliberately bypass it to override
`SIM_BYPASS_INIT_CAL`). Fixed by explicitly adding the real 68-file
`user_design/rtl` tree, `ddr3_model.sv` (`x2Gb`/`sg125`/`x16`
defines -- a real, second gotcha: `verilog_define` is a
FILESET-level property in this Vivado version, not a per-file one,
`set_property verilog_define ... [get_files ...]` errors outright),
`wiredly.v`, and `glbl.v` to the fileset by hand, mirroring the
real vendor-shipped `xsim_files.prj` file list.
REAL RESULT (both real xsim runs, 8/8 PASS, 0 errors, identical golden
results, both against the SAME real 32-bit/3225ps closed-timing config):
WITH ddr_prefetch_mgr.v: $finish at 100663.1335 ns
WITHOUT ddr_prefetch_mgr.v: $finish at 100656.6835 ns
-> WITH is 6.45 ns SLOWER than WITHOUT -- a 0.0064% real REGRESSION,
not a benefit. Statistically indistinguishable from zero (well
within normal run-to-run scheduling noise), but definitively NOT
the 2.86% improvement EXP-0083 measured at the old 16-bit width.
REAL, HONEST INTERPRETATION (not asserted without the measurement above
to back it): the 32-bit channel's real widening (EXP-0084/0086) already
halves the real per-tile DDR3 round-trip latency (same burst count, ~2x
the bits/cycle). EXP-0083's own real finding was that the look-ahead
prefetch's benefit was ALREADY capped by `neural_processor_packed.v`'s
own fixed one-operand-per-cycle consumption rate, not by DDR3 latency
itself, even at 16-bit -- widening the channel further shrinks the real
per-tile DDR3 wait below whatever gap the look-ahead could hide, so
there is now essentially nothing left for `ddr_prefetch_mgr.v` to
usefully overlap. This is a real, coherent explanation consistent with
EXP-0083's own already-disclosed caveat ("this hypothesis overstated the
achievable benefit... the pipeline accepts one operand per cycle"), not
a new assumption.
DECISION: `ddr_prefetch_mgr.v` stays wired into the real, committed
`packed_slot.v` (no reason to rip it out -- real P&R signoff, EXP-0086,
already shows the 32-bit config closes timing WITH it included, and it
causes zero real harm). But its own real justification for existing is
now "real, verified, functionally correct, timing-neutral" rather than
"real, measured performance win" -- the performance case this project
built it for (EXP-0083's own 2.86%) does not survive the wider channel.
Building the larger multi-slot/whole-Director-queue scheduler version
(the ORIGINAL, not-yet-built EXP-0083 stretch goal) is NOT justified by
this real result -- the real bottleneck this experiment reveals is
`neural_processor_packed.v`'s own one-operand-per-cycle consumption
rate, not DDR3 latency, at the current core count.
next_action: with DDR3 latency no longer the real constraint at N=2,
core-count scaling (N=4/8/16, already directed by the user) is now the
more promising real lever -- proceed there. The opportunistic BRAM
cache idea (`docs/ARCHITECTURE_ANALYSIS.md` S5.6.1) targets the SAME
now-diminished DDR3-latency lever this experiment just showed has
little room left to give at N=2 -- worth real-measuring its own benefit
carefully before investing further RTL effort, rather than assuming
EXP-0083's original optimistic framing still applies.
+336
View File
@@ -0,0 +1,336 @@
`timescale 1ns/1ps
// ============================================================
// MEASUREMENT-ONLY FORK of hardware/v3/rtl/packed_slot.v, NOT part of
// the real synthesis target and NOT maintained going forward -- exists
// ONLY to reproduce the pre-EXP-0083 per-tile req/wait/consume
// activation-fetch sequencing (direct act_tile_fetch.v, no look-ahead
// ping-pong buffer), so it can be A/B-measured against the current,
// real, committed packed_slot.v (which wires ddr_prefetch_mgr.v) at
// the NOW-CLOSED 32-bit/3225ps DDR3 config (EXP-0086) -- the ORIGINAL
// EXP-0083 measurement was only ever taken at the OLD 16-bit/155MHz
// config, so it's not a fair before/after comparison for today's real
// hardware. Per this project's own fork-before-promote discipline:
// this file is the throwaway "before" fork, `packed_slot.v` itself is
// never edited for this measurement.
//
// Everything below is copied from the current real packed_slot.v
// EXCEPT: ddr_prefetch_mgr.v is replaced with a direct
// act_tile_fetch.v instance, and S_TILEREQ/S_TILEWAIT revert to the
// simple one-shot-per-tile sequencing act_tile_fetch.v's own interface
// expects (issue req, wait for its own valid pulse, consume, move on)
// -- no job-level look-ahead, no depth-2 ping-pong overlap.
// ============================================================
module packed_slot_noprefetch #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter BURST_LEN = 8,
parameter ADDR_WIDTH = 26,
parameter LAYER_BYTES = 128,
parameter BUFADDRW = $clog2(LAYER_BYTES)
)(
input wire clk,
input wire rst,
input wire job_start,
input wire [ADDR_WIDTH-1:0] x_base_a,
input wire [ADDR_WIDTH-1:0] x_base_b,
input wire [ADDR_WIDTH-1:0] w_base,
input wire [15:0] n_tiles,
input wire [ADDR_WIDTH-1:0] result_addr_a,
input wire [ADDR_WIDTH-1:0] result_addr_b,
input wire [15:0] node_id_a,
input wire [15:0] node_id_b,
output reg job_done,
output reg signed [DATA_WIDTH-1:0] result_data_a,
output reg signed [DATA_WIDTH-1:0] result_data_b,
output reg [15:0] result_node_id_a,
output reg [15:0] result_node_id_b,
output reg [ADDR_WIDTH-1:0] result_addr_a_out,
output reg [ADDR_WIDTH-1:0] result_addr_b_out,
output wire mem_active,
input wire mem_grant,
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-2:0] ctrl_addr,
output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready,
input wire ctrl_busy
);
localparam S_IDLE = 4'd0,
S_MEMWAIT = 4'd1,
S_PREFETCH = 4'd2,
S_SWAP = 4'd3,
S_JOBSTART = 4'd4,
S_TILEREQ = 4'd5,
S_TILEWAIT = 4'd6,
S_OPERAND = 4'd7,
S_RESULT = 4'd8,
S_DONE = 4'd9;
reg [3:0] state;
reg [ADDR_WIDTH-1:0] w_base_lat, x_base_a_lat, x_base_b_lat;
reg [15:0] n_tiles_lat;
reg [ADDR_WIDTH-1:0] result_addr_a_lat, result_addr_b_lat;
reg [15:0] node_id_a_lat, node_id_b_lat;
reg [15:0] tcnt;
reg pf_start;
wire pf_busy, pf_done;
wire pf_fill_we;
wire [BUFADDRW-1:0] pf_fill_addr;
wire [DATA_WIDTH-1:0] pf_fill_data;
wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-2:0] pf_ctrl_addr;
wire [32*BURST_LEN-1:0] pf_ctrl_wdata;
wire [4*BURST_LEN-1:0] pf_ctrl_wmask;
layer_prefetch_ctrl #(
.DATA_WIDTH(DATA_WIDTH), .LAYER_BYTES(LAYER_BYTES), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
) u_pf (
.clk(clk), .rst(rst),
.start(pf_start), .layer_base(w_base_lat[ADDR_WIDTH-2:0]), .busy(pf_busy), .done(pf_done),
.fill_we(pf_fill_we), .fill_addr(pf_fill_addr), .fill_data(pf_fill_data),
.ctrl_req(pf_ctrl_req), .ctrl_wr(pf_ctrl_wr), .ctrl_addr(pf_ctrl_addr),
.ctrl_wdata(pf_ctrl_wdata), .ctrl_wmask(pf_ctrl_wmask),
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
// ---- pre-EXP-0083 baseline: direct act_tile_fetch.v, one req per
// tile, no look-ahead ----
reg act_req;
wire act_valid;
wire signed [DATA_WIDTH*P_IN-1:0] act_data_a_w, act_data_b_w;
wire act_mem_active;
wire act_ctrl_req, act_ctrl_wr;
wire [ADDR_WIDTH-2:0] act_ctrl_addr;
wire [32*BURST_LEN-1:0] act_ctrl_wdata;
wire [4*BURST_LEN-1:0] act_ctrl_wmask;
act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
) u_act (
.clk(clk), .rst(rst),
.req(act_req), .base_a(x_base_a_lat[ADDR_WIDTH-2:0]), .base_b(x_base_b_lat[ADDR_WIDTH-2:0]),
.tcnt(tcnt),
.valid(act_valid), .data_a(act_data_a_w), .data_b(act_data_b_w),
.mem_active(act_mem_active), .mem_grant(mem_grant),
.ctrl_req(act_ctrl_req), .ctrl_wr(act_ctrl_wr), .ctrl_addr(act_ctrl_addr),
.ctrl_wdata(act_ctrl_wdata), .ctrl_wmask(act_ctrl_wmask),
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
assign ctrl_req = act_mem_active ? act_ctrl_req : pf_ctrl_req;
assign ctrl_wr = act_mem_active ? act_ctrl_wr : pf_ctrl_wr;
assign ctrl_addr = act_mem_active ? act_ctrl_addr : pf_ctrl_addr;
assign ctrl_wdata = act_mem_active ? act_ctrl_wdata : pf_ctrl_wdata;
assign ctrl_wmask = act_mem_active ? act_ctrl_wmask : pf_ctrl_wmask;
assign mem_active = (state == S_MEMWAIT) || (state == S_PREFETCH) || act_mem_active;
wire [BUFADDRW-1:0] lwb_rd_addr;
wire [DATA_WIDTH-1:0] lwb_rd_data;
reg consume_done;
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()
);
reg tile_req;
reg [BUFADDRW-1:0] tile_base;
reg tile_seen;
reg act_seen;
wire tile_valid;
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)
);
reg job_valid_np;
wire job_ready_np;
reg [1:0] job_activation;
reg signed [DATA_WIDTH-1:0] job_bias;
reg operand_valid;
wire operand_ready;
reg signed [DATA_WIDTH*P_IN-1:0] input_data_a_r, input_data_b_r;
reg [DATA_WIDTH*P_IN-1:0] weight_data_r;
reg tile_last;
wire result_valid_np;
reg result_ready;
wire signed [DATA_WIDTH-1:0] result_data_a_np, result_data_b_np;
wire [15:0] result_node_id_a_np, result_node_id_b_np;
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_np), .job_ready(job_ready_np),
.job_node_id_a(node_id_a_lat), .job_node_id_b(node_id_b_lat),
.job_bias(job_bias), .job_activation(job_activation),
.operand_valid(operand_valid), .operand_ready(operand_ready),
.input_data_a(input_data_a_r), .input_data_b(input_data_b_r),
.weight_data(weight_data_r), .tile_last(tile_last),
.result_valid(result_valid_np), .result_ready(result_ready),
.result_data_a(result_data_a_np), .result_data_b(result_data_b_np),
.result_node_id_a(result_node_id_a_np), .result_node_id_b(result_node_id_b_np),
.np_state(np_state), .np_error(np_error)
);
localparam ACT_RELU = 2'd1;
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
job_done <= 1'b0;
pf_start <= 1'b0;
consume_done <= 1'b0;
tile_req <= 1'b0;
act_req <= 1'b0;
tile_seen <= 1'b0;
act_seen <= 1'b0;
job_valid_np <= 1'b0;
operand_valid<= 1'b0;
tile_last <= 1'b0;
result_ready <= 1'b0;
job_bias <= {DATA_WIDTH{1'b0}};
job_activation <= ACT_RELU;
tcnt <= 16'd0;
end else begin
job_done <= 1'b0;
pf_start <= 1'b0;
consume_done <= 1'b0;
tile_req <= 1'b0;
act_req <= 1'b0;
case (state)
S_IDLE: begin
if (job_start) begin
w_base_lat <= w_base;
x_base_a_lat <= x_base_a;
x_base_b_lat <= x_base_b;
n_tiles_lat <= n_tiles;
result_addr_a_lat <= result_addr_a;
result_addr_b_lat <= result_addr_b;
node_id_a_lat <= node_id_a;
node_id_b_lat <= node_id_b;
job_bias <= {DATA_WIDTH{1'b0}};
job_activation <= ACT_RELU;
state <= S_MEMWAIT;
end
end
S_MEMWAIT: begin
if (mem_grant) begin
pf_start <= 1'b1;
state <= S_PREFETCH;
end
end
S_PREFETCH: begin
if (pf_done) begin
consume_done <= 1'b1;
state <= S_SWAP;
end
end
S_SWAP: begin
job_valid_np <= 1'b1;
state <= S_JOBSTART;
end
S_JOBSTART: begin
if (job_valid_np && job_ready_np) begin
job_valid_np <= 1'b0;
tcnt <= 16'd0;
state <= S_TILEREQ;
end
end
// pre-EXP-0083 baseline: issue ONE tile request per
// iteration, wait for its own valid pulse -- no
// look-ahead overlap with the previous tile's consume.
S_TILEREQ: begin
tile_req <= 1'b1;
tile_base <= tcnt[BUFADDRW-1:0]*P_IN[BUFADDRW-1:0];
tile_seen <= 1'b0;
act_seen <= 1'b0;
act_req <= 1'b1;
state <= S_TILEWAIT;
end
// pre-EXP-0083 real join: weight_tile_gather.v's tile_valid
// and act_tile_fetch.v's act_valid are BOTH one-cycle pulses
// (act_tile_fetch.v has no level-hold the way ddr_prefetch_
// mgr.v's ddrpf_tile_valid does) -- each needs its own seen
// latch since they don't arrive on the same cycle in general.
S_TILEWAIT: begin
if (tile_valid) begin
weight_data_r <= tile_data;
tile_seen <= 1'b1;
end
if (act_valid) begin
input_data_a_r <= act_data_a_w;
input_data_b_r <= act_data_b_w;
act_seen <= 1'b1;
end
if ((tile_valid || tile_seen) && (act_valid || act_seen)) begin
tile_last <= (tcnt == n_tiles_lat - 16'd1);
operand_valid <= 1'b1;
state <= S_OPERAND;
end
end
S_OPERAND: begin
if (operand_valid && operand_ready) begin
operand_valid <= 1'b0;
tile_last <= 1'b0;
if (tcnt == n_tiles_lat - 16'd1) begin
result_ready <= 1'b1;
state <= S_RESULT;
end else begin
tcnt <= tcnt + 16'd1;
state <= S_TILEREQ;
end
end
end
S_RESULT: begin
if (result_valid_np) begin
result_data_a <= result_data_a_np;
result_data_b <= result_data_b_np;
result_node_id_a <= result_node_id_a_np;
result_node_id_b <= result_node_id_b_np;
result_addr_a_out <= result_addr_a_lat;
result_addr_b_out <= result_addr_b_lat;
result_ready <= 1'b0;
job_done <= 1'b1;
state <= S_IDLE;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule
+5 -1
View File
@@ -30,9 +30,13 @@
// 16-bit dq / 2-bit dqs / 2-bit dm slice -- confirmed by reading the
// real regenerated sim_tb_top.v (not assumed), replicated exactly
// below for DQ_WIDTH=32 (NUM_COMP=2).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns) -- 2900ps was
// the FAILED intermediate attempt (WNS=-0.618ns, EXP-0084).
// ============================================================
module tb;
localparam CLKIN_PERIOD = 2900; // ps, matches this project's real MIG config (EXP-0084)
localparam CLKIN_PERIOD = 3225; // ps, matches this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
+31 -12
View File
@@ -27,18 +27,24 @@
// variant), real ddr3_model.sv, real WireDelay pass-through -- same
// proven instantiation pattern as tb_mig_native_adapter.v.
//
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- CLKIN_PERIOD
// 2900ps, dq/dqs/dm pin widths doubled (two MT41J128M16 chips ganged
// in parallel), TWO real ddr3_model.sv components instantiated (one
// per chip, exact real pattern confirmed against the real regenerated
// sim_tb_top.v), both sys_clk and clk_ref are now real differential
// pairs on the inner mig_7series_0_mig module (the user's own wizard
// choice), and the weight/activation preload tasks rewritten for the
// new BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts
// (same rewrite already verified in tb_packed_slot.v).
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- dq/dqs/dm pin
// widths doubled (two MT41J128M16 chips ganged in parallel), TWO real
// ddr3_model.sv components instantiated (one per chip, exact real
// pattern confirmed against the real regenerated sim_tb_top.v), both
// sys_clk and clk_ref are now real differential pairs on the inner
// mig_7series_0_mig module (the user's own wizard choice), and the
// weight/activation preload tasks rewritten for the new
// BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts (same
// rewrite already verified in tb_packed_slot.v).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns, EXP-0086) --
// the 2900ps value was the FAILED intermediate attempt (WNS=-0.618ns,
// EXP-0084) and must not be simulated as if it were the real, current
// hardware.
// ============================================================
module tb;
localparam CLKIN_PERIOD = 2900; // ps, this project's real MIG config (EXP-0084)
localparam CLKIN_PERIOD = 3225; // ps, this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
@@ -228,11 +234,24 @@ module tb;
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid)
);
// EXP-0086 fix: the original `8'(expr)` SystemVerilog sized-cast
// syntax was silently never valid plain Verilog (CLAUDE.md's own
// "no SV-only syntax in a plain .v file" lesson) -- xvlog in default
// (non `-sv`) mode rejects it outright. An intermediate 8-bit reg
// does the same width-truncation-before-$signed() job portably.
function automatic signed [7:0] weight_byte(input integer li, input integer t);
weight_byte = $signed(8'((li*17 + t*29 + 13) & 8'hFF));
reg [7:0] tmp;
begin
tmp = (li*17 + t*29 + 13) & 8'hFF;
weight_byte = $signed(tmp);
end
endfunction
function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t);
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF));
reg [7:0] tmp;
begin
tmp = (li*11 + pos*41 + t*7 + 3) & 8'hFF;
input_byte = $signed(tmp);
end
endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
@@ -0,0 +1,521 @@
`timescale 1ps/100fs
// ============================================================
// MILESTONE: the full N=2 multi-core system (EXP-0066/0067, real
// neural_director_packed.v + 2 real packed_slot.v instances + real
// sdram_arbiter_n.v) running against REAL DDR3 (mig_native_adapter.v,
// EXP-0068, verified against MIG's own ddr3_model.sv) instead of the
// SDR SDRAM placeholder used everywhere until now.
//
// Runs entirely in the ui_clk domain (MIG's own generated clock is
// now this whole system's clock, per mig_native_adapter.v's own
// documented convention). Everything downstream of the memory
// backend (Director, packed_slot, weight-reuse path, packed core) is
// UNCHANGED, byte-for-byte, from EXP-0066/0067 -- only the physical
// memory backend is swapped, isolating that as the one variable
// under test.
//
// EXP-0079 UPDATE: activations are now fetched via a REAL act_tile_
// fetch.v inside each packed_slot.v instance (real DDR3 reads, same
// physical bus each slot already uses for weights) -- no more stand-
// in. This test now preloads real activation data into the SAME real
// DDR3 model too (preload_ddr3_activations), on top of the weight
// preload that was already here.
//
// Uses mig_7series_0_mig_sim (SIM_BYPASS_INIT_CAL="FAST" default,
// EXP-0068's own real vendor-shipped fast-calibration simulation
// variant), real ddr3_model.sv, real WireDelay pass-through -- same
// proven instantiation pattern as tb_mig_native_adapter.v.
//
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- dq/dqs/dm pin
// widths doubled (two MT41J128M16 chips ganged in parallel), TWO real
// ddr3_model.sv components instantiated (one per chip, exact real
// pattern confirmed against the real regenerated sim_tb_top.v), both
// sys_clk and clk_ref are now real differential pairs on the inner
// mig_7series_0_mig module (the user's own wizard choice), and the
// weight/activation preload tasks rewritten for the new
// BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts (same
// rewrite already verified in tb_packed_slot.v).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns, EXP-0086) --
// the 2900ps value was the FAILED intermediate attempt (WNS=-0.618ns,
// EXP-0084) and must not be simulated as if it were the real, current
// hardware.
// ============================================================
module tb;
localparam CLKIN_PERIOD = 3225; // ps, this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
localparam DATA_WIDTH = 8;
localparam P_IN = 8;
localparam ACC_WIDTH = 32;
localparam ADDR_WIDTH = 26; // this project's byte-address convention (Director/packed_slot)
localparam MIG_ADDR_WIDTH = 25; // word-address convention (BURST_LEN=8) at the arbiter/adapter
localparam BURST_LEN = 8;
localparam N_INPUTS = 128;
localparam N_TILES = N_INPUTS/P_IN;
localparam LAYER_BYTES = N_INPUTS;
localparam WORDS_PER_LAYER = LAYER_BYTES/2;
localparam N_SLOTS = 2;
localparam QUEUE_DEPTH = 8;
localparam L = 2; // layers (kept small -- real DDR3 calibration + JEDEC timing already
localparam M = 4; // costs real simulated time; this is an integration check, not a
// repeat of EXP-0066's own fuller correctness sweep)
// ---- clock/reset (mirrors tb_mig_native_adapter.v's own proven pattern) ----
reg sys_rst_n;
wire sys_rst = sys_rst_n;
reg sys_clk_i = 1'b0;
always #(CLKIN_PERIOD/2.0) sys_clk_i = ~sys_clk_i;
wire sys_clk_p = sys_clk_i;
wire sys_clk_n = ~sys_clk_i;
reg clk_ref_i = 1'b0;
always #REFCLK_PERIOD clk_ref_i = ~clk_ref_i;
wire clk_ref_p = clk_ref_i;
wire clk_ref_n = ~clk_ref_i;
initial begin
sys_rst_n = 1'b0;
#RESET_PERIOD sys_rst_n = 1'b1;
end
// ---- real DDR3 pins + model (identical to tb_mig_native_adapter.v) ----
wire ddr3_reset_n;
wire [31:0] ddr3_dq_fpga;
wire [3:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
wire [13:0] ddr3_addr_fpga;
wire [2:0] ddr3_ba_fpga;
wire ddr3_ras_n_fpga, ddr3_cas_n_fpga, ddr3_we_n_fpga;
wire [0:0] ddr3_cke_fpga, ddr3_ck_p_fpga, ddr3_ck_n_fpga, ddr3_cs_n_fpga;
wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga;
wire [31:0] ddr3_dq_sdram;
reg [13:0] ddr3_addr_sdram;
reg [2:0] ddr3_ba_sdram;
reg ddr3_ras_n_sdram, ddr3_cas_n_sdram, ddr3_we_n_sdram;
wire [0:0] ddr3_cs_n_sdram;
wire [0:0] ddr3_odt_sdram;
reg [0:0] ddr3_cke_sdram;
wire [3:0] ddr3_dm_sdram;
wire [3:0] ddr3_dqs_p_sdram, ddr3_dqs_n_sdram;
reg [0:0] ddr3_ck_p_sdram, ddr3_ck_n_sdram;
reg [0:0] ddr3_cs_n_sdram_tmp;
reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin
ddr3_ck_p_sdram <= ddr3_ck_p_fpga;
ddr3_ck_n_sdram <= ddr3_ck_n_fpga;
ddr3_addr_sdram <= ddr3_addr_fpga;
ddr3_ba_sdram <= ddr3_ba_fpga;
ddr3_ras_n_sdram <= ddr3_ras_n_fpga;
ddr3_cas_n_sdram <= ddr3_cas_n_fpga;
ddr3_we_n_sdram <= ddr3_we_n_fpga;
ddr3_cke_sdram <= ddr3_cke_fpga;
end
always @(*) ddr3_cs_n_sdram_tmp <= ddr3_cs_n_fpga;
assign ddr3_cs_n_sdram = ddr3_cs_n_sdram_tmp;
always @(*) ddr3_dm_sdram_tmp <= ddr3_dm_fpga;
assign ddr3_dm_sdram = ddr3_dm_sdram_tmp;
always @(*) ddr3_odt_sdram_tmp <= ddr3_odt_fpga;
assign ddr3_odt_sdram = ddr3_odt_sdram_tmp;
genvar dqwd;
generate
for (dqwd = 0; dqwd < 32; dqwd = dqwd + 1) begin : dq_delay
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dq (
.A(ddr3_dq_fpga[dqwd]), .B(ddr3_dq_sdram[dqwd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
end
endgenerate
genvar dqswd;
generate
for (dqswd = 0; dqswd < 4; dqswd = dqswd + 1) begin : dqs_delay
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_p (
.A(ddr3_dqs_p_fpga[dqswd]), .B(ddr3_dqs_p_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_n (
.A(ddr3_dqs_n_fpga[dqswd]), .B(ddr3_dqs_n_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
end
endgenerate
genvar ci;
generate
for (ci = 0; ci < 2; ci = ci + 1) begin : gen_mem
ddr3_model u_comp_ddr3 (
.rst_n(ddr3_reset_n), .ck(ddr3_ck_p_sdram), .ck_n(ddr3_ck_n_sdram),
.cke(ddr3_cke_sdram[0]), .cs_n(ddr3_cs_n_sdram[0]),
.ras_n(ddr3_ras_n_sdram), .cas_n(ddr3_cas_n_sdram), .we_n(ddr3_we_n_sdram),
.dm_tdqs(ddr3_dm_sdram[2*ci +: 2]), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram[16*ci +: 16]),
.dqs(ddr3_dqs_p_sdram[2*ci +: 2]), .dqs_n(ddr3_dqs_n_sdram[2*ci +: 2]),
.tdqs_n(), .odt(ddr3_odt_sdram[0])
);
end
endgenerate
wire [27:0] app_addr;
wire [2:0] app_cmd;
wire app_en, app_rdy;
wire [127:0] app_wdf_data;
wire app_wdf_end;
wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete;
mig_7series_0_mig #(
.SIM_BYPASS_INIT_CAL("FAST")
) u_mig (
.ddr3_dq(ddr3_dq_fpga), .ddr3_dqs_n(ddr3_dqs_n_fpga), .ddr3_dqs_p(ddr3_dqs_p_fpga),
.ddr3_addr(ddr3_addr_fpga), .ddr3_ba(ddr3_ba_fpga),
.ddr3_ras_n(ddr3_ras_n_fpga), .ddr3_cas_n(ddr3_cas_n_fpga), .ddr3_we_n(ddr3_we_n_fpga),
.ddr3_reset_n(ddr3_reset_n),
.ddr3_ck_p(ddr3_ck_p_fpga), .ddr3_ck_n(ddr3_ck_n_fpga),
.ddr3_cke(ddr3_cke_fpga), .ddr3_cs_n(ddr3_cs_n_fpga),
.ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga),
.sys_clk_p(sys_clk_p), .sys_clk_n(sys_clk_n), .clk_ref_p(clk_ref_p), .clk_ref_n(clk_ref_n),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end),
.app_rd_data_valid(app_rd_data_valid), .app_rdy(app_rdy), .app_wdf_rdy(app_wdf_rdy),
.app_sr_req(1'b0), .app_ref_req(1'b0), .app_zq_req(1'b0),
.app_sr_active(), .app_ref_ack(), .app_zq_ack(),
.ui_clk(ui_clk), .ui_clk_sync_rst(ui_clk_sync_rst),
.init_calib_complete(init_calib_complete),
.device_temp(),
.sys_rst(sys_rst)
);
// ---- preload path: direct access to mig_native_adapter.v,
// bypassing the arbiter, exactly like every prior testbench's own
// "pre_active" mux (EXP-0057 onward) -- used only before job
// submission begins. ----
reg pre_active;
reg pre_req, pre_wr;
reg [MIG_ADDR_WIDTH-1:0] pre_addr;
reg [32*BURST_LEN-1:0] pre_wdata;
wire adp_req, adp_wr;
wire [MIG_ADDR_WIDTH-1:0] adp_addr;
wire [32*BURST_LEN-1:0] adp_wdata;
wire [4*BURST_LEN-1:0] adp_wmask;
wire [32*BURST_LEN-1:0] adp_rdata;
wire adp_ready, adp_busy;
wire arb_ctrl_req_o, arb_ctrl_wr_o;
wire [MIG_ADDR_WIDTH-1:0] arb_ctrl_addr_o;
wire [32*BURST_LEN-1:0] arb_ctrl_wdata_o;
wire [4*BURST_LEN-1:0] arb_ctrl_wmask_o;
assign adp_req = pre_active ? pre_req : arb_ctrl_req_o;
assign adp_wr = pre_active ? pre_wr : arb_ctrl_wr_o;
assign adp_addr = pre_active ? pre_addr : arb_ctrl_addr_o;
assign adp_wdata = pre_active ? pre_wdata : arb_ctrl_wdata_o;
assign adp_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : arb_ctrl_wmask_o;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MIG_ADDR_WIDTH)) u_adapter (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req(adp_req), .wr(adp_wr), .addr(adp_addr), .wdata(adp_wdata), .wmask(adp_wmask),
.rdata(adp_rdata), .ready(adp_ready), .busy(adp_busy),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en), .app_rdy(app_rdy),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end), .app_wdf_mask(app_wdf_mask),
.app_wdf_wren(app_wdf_wren), .app_wdf_rdy(app_wdf_rdy),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid)
);
// EXP-0086 fix: the original `8'(expr)` SystemVerilog sized-cast
// syntax was silently never valid plain Verilog (CLAUDE.md's own
// "no SV-only syntax in a plain .v file" lesson) -- xvlog in default
// (non `-sv`) mode rejects it outright. An intermediate 8-bit reg
// does the same width-truncation-before-$signed() job portably.
function automatic signed [7:0] weight_byte(input integer li, input integer t);
reg [7:0] tmp;
begin
tmp = (li*17 + t*29 + 13) & 8'hFF;
weight_byte = $signed(tmp);
end
endfunction
function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t);
reg [7:0] tmp;
begin
tmp = (li*11 + pos*41 + t*7 + 3) & 8'hFF;
input_byte = $signed(tmp);
end
endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin
@(posedge ui_clk); while (adp_busy) @(posedge ui_clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@(posedge ui_clk); pre_req = 1'b0;
while (!adp_ready) @(posedge ui_clk);
end
endtask
// EXP-0084: BYTES_PER_BURST = 4*BURST_LEN (32 bytes/burst, up from
// 16) -- 4 consecutive weight bytes pack into each 32-bit word now.
task automatic preload_sdram_layers;
integer li, bi, wb, tt;
reg [32*BURST_LEN-1:0] burst_data;
begin
for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(4*BURST_LEN)); bi = bi + 1) begin
for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin
tt = bi*(4*BURST_LEN) + wb*4;
burst_data[wb*32 +: 32] = {weight_byte(li, tt+3), weight_byte(li, tt+2),
weight_byte(li, tt+1), weight_byte(li, tt)};
end
sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
end
end
end
endtask
// ---- real activation preload (EXP-0084 layout: FOUR consecutive
// tiles share one BURST_LEN=8-word (256-bit) burst -- tile parity
// 0/1/2/3 -> quarters [63:0]/[127:64]/[191:128]/[255:192], see
// act_tile_fetch.v's own header). ----
localparam [MIG_ADDR_WIDTH-1:0] ACT_MEM_BASE = 25'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * ((N_TILES/4)*BURST_LEN);
endfunction
task automatic preload_ddr3_activations;
integer li, pos, tq, qi;
reg [32*BURST_LEN-1:0] burst_data;
reg [ADDR_WIDTH-1:0] base;
begin
for (li = 0; li < L; li = li + 1) begin
for (pos = 0; pos < M; pos = pos + 1) begin
base = act_x_base(li, pos);
for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin
burst_data = {(32*BURST_LEN){1'b0}};
for (qi = 0; qi < 4; qi = qi + 1)
burst_data[qi*64 +: 64] = {input_byte(li, pos, (4*tq+qi)*P_IN + 7), input_byte(li, pos, (4*tq+qi)*P_IN + 6),
input_byte(li, pos, (4*tq+qi)*P_IN + 5), input_byte(li, pos, (4*tq+qi)*P_IN + 4),
input_byte(li, pos, (4*tq+qi)*P_IN + 3), input_byte(li, pos, (4*tq+qi)*P_IN + 2),
input_byte(li, pos, (4*tq+qi)*P_IN + 1), input_byte(li, pos, (4*tq+qi)*P_IN + 0)};
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tq*BURST_LEN, burst_data);
end
end
end
end
endtask
// ---- neural_director_packed.v ----
reg job_in_valid;
wire job_in_ready;
reg [ADDR_WIDTH-1:0] job_in_x_base, job_in_w_base, job_in_result_addr;
reg [15:0] job_in_n_tiles, job_in_node_id;
wire [N_SLOTS-1:0] slot_job_start;
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base_a, slot_x_base_b, slot_w_base;
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr_a, slot_result_addr_b;
wire [16*N_SLOTS-1:0] slot_n_tiles, slot_node_id_a, slot_node_id_b;
wire [N_SLOTS-1:0] slot_job_done;
wire job_out_done;
wire [$clog2(N_SLOTS)-1:0] job_out_slot;
wire [3:0] dir_state;
wire dir_error;
neural_director_packed #(
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
) u_dir (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_in_valid(job_in_valid), .job_in_ready(job_in_ready),
.job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base),
.job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr),
.job_in_node_id(job_in_node_id),
.slot_job_start(slot_job_start),
.slot_x_base_a(slot_x_base_a), .slot_x_base_b(slot_x_base_b),
.slot_w_base(slot_w_base), .slot_n_tiles(slot_n_tiles),
.slot_result_addr_a(slot_result_addr_a), .slot_result_addr_b(slot_result_addr_b),
.slot_node_id_a(slot_node_id_a), .slot_node_id_b(slot_node_id_b),
.slot_job_done(slot_job_done),
.job_out_done(job_out_done), .job_out_slot(job_out_slot),
.dir_state(dir_state), .dir_error(dir_error)
);
// ---- 2 real packed_slot.v instances + real N-way arbiter (NUM_REQ=2) ----
wire [1:0] mem_active, mem_grant;
wire [1:0] s_ctrl_req, s_ctrl_wr;
wire [1:0] s_ctrl_ready, s_ctrl_busy;
wire [MIG_ADDR_WIDTH*2-1:0] s_ctrl_addr_flat;
wire [32*BURST_LEN*2-1:0] s_ctrl_wdata_flat, s_ctrl_rdata_flat;
wire [4*BURST_LEN*2-1:0] s_ctrl_wmask_flat;
sdram_arbiter_n #(.NUM_REQ(2), .ADDR_WIDTH(MIG_ADDR_WIDTH), .BURST_LEN(BURST_LEN)) u_arb (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req_active(mem_active), .req_grant(mem_grant),
.req_req(s_ctrl_req), .req_wr(s_ctrl_wr), .req_addr(s_ctrl_addr_flat),
.req_wdata(s_ctrl_wdata_flat), .req_wmask(s_ctrl_wmask_flat),
.req_rdata(s_ctrl_rdata_flat), .req_ready(s_ctrl_ready), .req_busy(s_ctrl_busy),
.ctrl_req(arb_ctrl_req_o), .ctrl_wr(arb_ctrl_wr_o), .ctrl_addr(arb_ctrl_addr_o),
.ctrl_wdata(arb_ctrl_wdata_o), .ctrl_wmask(arb_ctrl_wmask_o),
.ctrl_rdata(adp_rdata), .ctrl_ready(adp_ready), .ctrl_busy(adp_busy)
);
genvar gi;
generate
for (gi = 0; gi < N_SLOTS; gi = gi + 1) begin : GEN_SLOT
wire signed [DATA_WIDTH-1:0] res_a, res_b;
wire [15:0] res_nid_a, res_nid_b;
wire [ADDR_WIDTH-1:0] res_addr_a_out, res_addr_b_out;
packed_slot_noprefetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES)
) u_slot (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_start(slot_job_start[gi]),
.x_base_a(slot_x_base_a[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.x_base_b(slot_x_base_b[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.w_base(slot_w_base[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.n_tiles(slot_n_tiles[gi*16 +: 16]),
.result_addr_a(slot_result_addr_a[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.result_addr_b(slot_result_addr_b[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.node_id_a(slot_node_id_a[gi*16 +: 16]), .node_id_b(slot_node_id_b[gi*16 +: 16]),
.job_done(slot_job_done[gi]),
.result_data_a(res_a), .result_data_b(res_b),
.result_node_id_a(res_nid_a), .result_node_id_b(res_nid_b),
.result_addr_a_out(res_addr_a_out), .result_addr_b_out(res_addr_b_out),
.mem_active(mem_active[gi]), .mem_grant(mem_grant[gi]),
.ctrl_req(s_ctrl_req[gi]), .ctrl_wr(s_ctrl_wr[gi]),
.ctrl_addr(s_ctrl_addr_flat[gi*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH]),
.ctrl_wdata(s_ctrl_wdata_flat[gi*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(s_ctrl_wmask_flat[gi*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(s_ctrl_rdata_flat[gi*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(s_ctrl_ready[gi]), .ctrl_busy(s_ctrl_busy[gi])
);
end
endgenerate
integer errors, tests, completions, n_expected, si;
reg [15:0] expect_node [0:31];
reg signed [7:0] expect_val [0:31];
function automatic signed [7:0] golden_result(input integer li, input integer pos);
integer t, acc;
reg signed [7:0] r;
begin
acc = 0;
for (t = 0; t < N_INPUTS; t = t + 1)
acc = acc + (input_byte(li, pos, t) * weight_byte(li, t));
if (acc <= 0) r = 0; else if (acc > 127) r = 8'sd127; else r = acc[7:0];
golden_result = r;
end
endfunction
task automatic check_completion(input integer slot, input [15:0] nid, input signed [7:0] val);
integer idx, found;
begin
found = 0;
for (idx = 0; idx < n_expected; idx = idx + 1) begin
if (expect_node[idx] === nid && !found) begin
found = 1;
tests = tests + 1;
if (expect_val[idx] !== val) begin
$display("FAIL slot=%0d node_id=%0d: got=%0d expected=%0d", slot, nid, $signed(val), $signed(expect_val[idx]));
errors = errors + 1;
end else begin
$display("PASS slot=%0d node_id=%0d: result=%0d", slot, nid, $signed(val));
end
end
end
end
endtask
always @(posedge ui_clk) begin
if (!ui_clk_sync_rst) begin
for (si = 0; si < N_SLOTS; si = si + 1) begin
if (slot_job_done[si]) begin
completions = completions + 2;
case (si)
0: begin
check_completion(0, GEN_SLOT[0].u_slot.result_node_id_a, GEN_SLOT[0].u_slot.result_data_a);
check_completion(0, GEN_SLOT[0].u_slot.result_node_id_b, GEN_SLOT[0].u_slot.result_data_b);
end
1: begin
check_completion(1, GEN_SLOT[1].u_slot.result_node_id_a, GEN_SLOT[1].u_slot.result_data_a);
check_completion(1, GEN_SLOT[1].u_slot.result_node_id_b, GEN_SLOT[1].u_slot.result_data_b);
end
endcase
end
end
end
end
task automatic submit_job(
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr, input [15:0] nid
);
begin
@(posedge ui_clk);
job_in_x_base = xb; job_in_w_base = wb; job_in_n_tiles = nt;
job_in_result_addr = resaddr; job_in_node_id = nid;
job_in_valid = 1'b1;
while (!job_in_ready) @(posedge ui_clk);
@(posedge ui_clk);
job_in_valid = 1'b0;
end
endtask
integer li_i, pp_i, wd;
initial begin
errors = 0; tests = 0; completions = 0; n_expected = 0;
pre_active = 1'b1; pre_req = 0; pre_wr = 0; pre_addr = 0; pre_wdata = 0;
job_in_valid = 0; job_in_x_base = 0; job_in_w_base = 0;
job_in_n_tiles = 0; job_in_result_addr = 0; job_in_node_id = 0;
$display("=== waiting for real DDR3 init_calib_complete ===");
wait (init_calib_complete);
$display("=== calibration done at time %0t ===", $time);
repeat (10) @(posedge ui_clk);
$display("=== preload SDRAM with %0d resident-filter weight sets ===", L);
preload_sdram_layers;
$display("=== preload SDRAM with real activation data (EXP-0079) ===");
preload_ddr3_activations;
@(posedge ui_clk);
pre_active = 1'b0;
repeat (5) @(posedge ui_clk);
$display("=== N=2 system on REAL DDR3: submitting %0d layers x %0d positions ===", L, M);
for (li_i = 0; li_i < L; li_i = li_i + 1) begin
for (pp_i = 0; pp_i < M; pp_i = pp_i + 1) begin
submit_job(act_x_base(li_i, pp_i), li_i*WORDS_PER_LAYER, N_TILES[15:0],
26'h9000 + li_i*10 + pp_i, (li_i*M + pp_i));
expect_node[n_expected] = (li_i*M + pp_i);
expect_val[n_expected] = golden_result(li_i, pp_i);
n_expected = n_expected + 1;
end
end
wd = 0;
while (completions < n_expected && wd < 200000) begin
@(posedge ui_clk);
wd = wd + 1;
end
if (completions < n_expected) begin
$display("FAIL: only %0d/%0d position-results completed within watchdog", completions, n_expected);
errors = errors + 1;
end
$display("=== %0d/%0d tests, %0d errors, %0d/%0d positions completed ===", tests-errors, tests, errors, completions, n_expected);
if (errors == 0 && completions == n_expected) $display("ALL TESTS PASSED (tb_n2_system_ddr3, REAL DDR3)");
$finish;
end
endmodule