feat: real 32-bit DDR3 channel widening - functionally complete, timing NOT yet closed (EXP-0084)

Real 32-bit DDR3 widening (2x MT41J128M16JT-125:K chips ganged in
parallel, user's own MIG wizard session). Full RTL adaptation across
the shared ctrl bus (16-bit word -> 32-bit word, BURST_LEN=8 unchanged,
burst payload 128->256 bits):

- mig_native_adapter.v: app_wdf_data/app_rd_data 64->128 bits (real,
  confirmed against the regenerated MIG wrapper), beat count unchanged.
- act_tile_fetch.v: real logic change - burst now holds 4 tiles instead
  of 2 (sel_lat extended to 2 registered bits, 4-way case mux instead
  of 2-way ternary, same request-time-registered-select discipline as
  EXP-0081). Not a further bytes/MAC reduction, just what's needed to
  keep 100% packing utilization at the larger burst.
- host_mem_bridge.v: real addressing redesign - host-facing 16-bit-word
  contract kept unchanged (ESP32 firmware unaffected), internally
  translated onto the new 32-bit-native ctrl bus.
- sdram_arbiter_n.v, layer_prefetch_ctrl.v, packed_slot.v,
  ddr_prefetch_mgr.v, n2_system_ddr3_top.v: mechanical width bump plus
  doubled ddr3_dq/dqs/dm pins and the real differential sys_clk/clk_ref
  top-level ports the regenerated MIG now requires.

New burst_mem_model32.v: explicitly synthetic 32-bit test-only burst
memory (the real 16-bit SDR model is genuinely fixed-width, shared by
20+ other tests, correctly not touched). Found and fixed a real
address-aliasing bug in it during bring-up (MEM_ADDR_BITS=16 silently
wrapped a real 0x10000 test address to 0).

Real verification: all isolated testbenches re-verified (10/10, 33/33,
32/32, 7/7, 9/9 PASS), plus real xsim against the real 2-chip DDR3
model (tb_mig_native_adapter.v 12/12 PASS, tb_n2_system_ddr3.v 8/8
PASS, both chips visibly returning different real data).

Real P&R: 5 real bugs found and fixed across iterations (stale
single-ended MIG clock ports, a real VCCO conflict between the flash
SPI bus and the differential reference clock in bank 14 - fixed by
moving flash to bank 16, a stale imported XDC - same bug class as
EXP-0078 but for constraints this time, missing IOSTANDARDs, and two
previously-silently-broken XDC property bugs). Route completes 100%,
but real timing does NOT close: WNS -0.618ns, 213 failing endpoints.

Honest root cause: the violation is inside neural_processor_packed.v's
own packed-MAC accumulation tree, unchanged since EXP-0059 - it has
real margin at the old 155.039MHz ui_clk but not at the new 172.414MHz
the paired clock-period change produced. This is NOT caused by the
32-bit width change itself. Width alone, even at the old clock, already
delivers the full intended 2x bandwidth gain (1.24 -> ~2.48 GB/s) -
width and clock rate are separable levers. Current trustworthy timing
signoff remains EXP-0083 (16-bit, +0.073ns) until the clock period is
reverted toward 3225ps (keeping Data Width=32) in one more real,
user-gated MIG wizard session.

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 16:28:44 +02:00
co-authored by Claude Sonnet 5
parent 376ccb6ee2
commit 9dead54ebf
21 changed files with 1302 additions and 754 deletions
+51 -19
View File
@@ -15,20 +15,30 @@
# when PERSIST is FALSE (the Vivado default -- set explicitly here so
# this dependency is self-documenting in the constraints, not just a
# silent default someone could flip later without realizing why).
set_property BITSTREAM.CONFIG.PERSIST FALSE [current_design]
set_property BITSTREAM.CONFIG.PERSIST NO [current_design]
# ---- config-flash passthrough (-> flash_spi_master.v, EXP-0077):
# D00_MOSI/D01_DIN/FCS_B are the SAME physical pins the FPGA's own
# dedicated Master-SPI config hardware uses AT BOOT to self-load its
# bitstream -- post-configuration they become ordinary fabric I/O
# (real Xilinx behavior, PERSIST=FALSE, the Vivado default) and this
# design deliberately reclaims them for the flash_spi_master.v bridge
# (the board wires the config flash EXCLUSIVELY to the FPGA -- see
# that module's own header). CCLK is NOT constrained here -- it's
# driven via STARTUPE2 internally, never a plain top-level port.
set_property PACKAGE_PIN K17 [get_ports flash_mosi]
set_property PACKAGE_PIN K18 [get_ports flash_miso]
set_property PACKAGE_PIN L13 [get_ports flash_cs_n]
# EXP-0084 REAL, RE-CONFIRMED CONFLICT (not hypothetical any more):
# D00_MOSI/D01_DIN/FCS_B's ORIGINAL pins (K17/K18/L13, bank 14) share
# bank 14 with the real differential reference clock (clk_ref_p/n,
# T14/T15) chosen during the EXP-0084 MIG wizard session -- a single
# I/O bank can only have ONE VCCO, and LVCMOS33 (3.3V, what the flash
# needs) is incompatible with LVDS_25 (2.5V, what clk_ref needs). This
# was flagged as a real *risk* when T14/T15 was chosen (real device
# data showed bank 14 already hosted the flash bus); EXP-0084's real
# place_design run turned that risk into a real, observed placement
# failure ("IO placement is infeasible" -- flash_mosi/flash_cs_n
# couldn't be placed at their old LOCs at all). FIX: moved the flash
# bus to bank 16 (D9/D10/C9) -- completely unconstrained, no VCCO
# commitment, so it can freely be 3.3V with zero conflict. Real,
# verified-available pins (queried from the actual part database, not
# guessed) -- D9/D10/C9 are ordinary I/O in bank 16, none of them are
# the config-mode-reserved EMCCLK/RDWR_B/CSI_B pins (still PROHIBITed
# below regardless). CCLK is NOT constrained here -- it's driven via
# STARTUPE2 internally, never a plain top-level port.
set_property PACKAGE_PIN D9 [get_ports flash_mosi]
set_property PACKAGE_PIN D10 [get_ports flash_miso]
set_property PACKAGE_PIN C9 [get_ports flash_cs_n]
set_property IOSTANDARD LVCMOS33 [get_ports flash_mosi]
set_property IOSTANDARD LVCMOS33 [get_ports flash_miso]
set_property IOSTANDARD LVCMOS33 [get_ports flash_cs_n]
@@ -39,7 +49,21 @@ set_property IOSTANDARD LVCMOS33 [get_ports flash_cs_n]
# extra data lines) -- PROHIBITed so Vivado's auto-placement never
# lands an unrelated port there by accident (it already had once,
# before this constraint existed, on a result-data bit).
set_property PROHIBIT true [get_package_pins {L16 R16 V15}]
set_property PROHIBIT true [get_sites -of_objects [get_package_pins {L16 R16 V15}]]
# ---- EXP-0084: remaining top-level ports with no fixed board LOC yet
# (result-data debug pins, status signals) default to LVCMOS18 with no
# explicit IOSTANDARD set -- real place_design found this real,
# concrete: banks 14/15/34/35 are ALL already committed to other real
# voltages (2.5V/3.3V/1.5V/1.5V), leaving only bank 16's spare pins as
# LVCMOS18-compatible, and there aren't enough of them (40 ports vs 10
# pins). Assign these explicitly to LVCMOS33 so they place in bank
# 15's own real spare capacity (46 free pins) instead -- a real,
# necessary fix, not a workaround; a permanent board LOC for each
# should still be assigned once the rest of the board layout is
# decided (S7 of docs/PHYSICAL_REALIZATION.md).
set_property IOSTANDARD LVCMOS33 [get_ports {s0_result_data_a[*] s0_result_data_b[*] s1_result_data_a[*] s1_result_data_b[*]}]
set_property IOSTANDARD LVCMOS33 [get_ports {job_out_slot[*] job_out_done init_calib_complete ui_clk_o}]
# ---- neural-processor management SPI (-> spi_host_bridge_v3.v):
# job submission + register file. Bank 15, column A/B (package edge,
@@ -57,9 +81,17 @@ set_property IOSTANDARD LVCMOS33 [get_ports mosi]
set_property IOSTANDARD LVCMOS33 [get_ports miso]
set_property IOSTANDARD LVCMOS33 [get_ports cs_n]
# ---- sys_rst: not part of the DDR3 MIG's own pin set (that's
# sys_rst too, but MIG's XDC only constrains the DDR3-facing timing,
# not necessarily IOSTANDARD for every board variant) -- pin left to
# auto-placement for now (low pin-count, no real board decision yet
# on where the reset source sits); explicitly constrain once the PCB
# layout for the reset circuit (button/supervisor IC) is decided.
# ---- sys_rst: EXP-0084 found this real -- with the 32-bit DDR3
# interface now committing banks 14/34/35 to 2.5V/1.5V/1.5V and bank
# 15 to 3.3V, there is genuinely NO bank left at the 1.8V sys_rst was
# silently defaulting to (no explicit IOSTANDARD was ever set) -- real
# place_design failure ("IO placement is infeasible... needs 1.8V,
# has 0 sites"), not hit before only because the smaller 16-bit I/O
# footprint happened to leave enough slack somewhere. Placed here
# temporarily in bank 15 alongside the management SPI bus (same real,
# already-committed 3.3V) -- NOT a final board decision, still pending
# the real PCB layout for the reset circuit (button/supervisor IC),
# per S7 of docs/PHYSICAL_REALIZATION.md. Real, verified-free pin
# (queried from the actual part database).
set_property PACKAGE_PIN G13 [get_ports sys_rst]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst]
+53 -33
View File
@@ -15,30 +15,38 @@
// so buffering it on-chip would only add complexity for zero benefit.
// This engine reads DIRECTLY from DDR3 per tile instead.
//
// MEMORY LAYOUT CONVENTION v2 (EXP-0081, real, disclosed, and REQUIRED
// MEMORY LAYOUT CONVENTION v3 (EXP-0084, real, disclosed, and REQUIRED
// of whoever prepares activation data in DDR3 -- documented in the
// physical realization doc too): TWO consecutive tiles (P_IN=8 INT8
// values each) share ONE full BURST_LEN=8-word (128-bit) burst -- even
// tile index in the LOW 64 bits, odd tile index in the HIGH 64 bits.
// Tile t's burst address is `base + (t>>1)*BURST_LEN`. This HALVES
// real DDR3 bytes-moved-per-useful-byte versus the original EXP-0079
// "1 tile = 1 burst" layout (real measured 1.24GB/s ceiling could only
// sustain ~25% of one core's peak DSP throughput under that layout --
// see docs/ARCHITECTURE_ANALYSIS.md S3.2 -- this doubles the real
// achievable fraction).
// physical realization doc too): FOUR consecutive tiles (P_IN=8 INT8
// values each, 64 bits each) share ONE full BURST_LEN=8-word burst --
// since EXP-0084's real 32-bit DDR3 channel widening, one burst is now
// 8*32=256 bits (up from 128 bits at the old 16-bit width), and 4
// tiles of 64 bits exactly fill it (100% utilization, same packing
// EFFICIENCY as EXP-0081's "2 tiles fill a 128-bit burst" -- this is
// NOT a further bytes-per-MAC reduction beyond EXP-0081's already-
// optimal 1 byte/MAC, it is what's REQUIRED to keep that same 100%
// utilization at the new, larger burst size instead of leaving half
// of it newly wasted). Tile index within the burst selects a quarter:
// tile parity 0/1/2/3 (tcnt[1:0]) -> bits [63:0]/[127:64]/[191:128]/
// [255:192] of the burst response. Tile t's burst address is
// `base + (t>>2)*BURST_LEN`.
//
// WHY THIS IS TIMING-SAFE (the thing EXP-0079 deliberately avoided):
// the tile index's own LSB (which half of the burst to use) is known
// at REQUEST time, not at response time -- it's registered into
// the tile index's own low 2 bits (which quarter of the burst to use)
// are known at REQUEST time, not at response time -- registered into
// `sel_lat` the SAME cycle `tcnt` is latched, many ui_clk cycles
// BEFORE the real DDR3 round-trip completes and `ctrl_rdata` becomes
// valid. The eventual data-select mux therefore selects using an
// already-long-stable registered bit, never a bit racing the read
// data itself -- this is NOT the runtime-indexed-part-select-on-the-
// critical-path pattern weight_tile_gather.v's own header (EXP-0061)
// warned about; that pattern is about a select signal arriving
// LATE/simultaneously with the data it gates. Confirmed via a real
// P&R re-check after this change (see the log), not just asserted.
// valid. The eventual data-select mux (a real `case` on the registered
// 2-bit `sel_lat`, not a runtime-indexed part-select expression --
// deliberately written as explicit constant-offset case arms, see
// below) therefore selects using an already-long-stable registered
// value, never bits racing the read data itself -- this is NOT the
// runtime-indexed-part-select-on-the-critical-path pattern weight_
// tile_gather.v's own header (EXP-0061) warned about; that pattern is
// about a select signal arriving LATE/simultaneously with the data it
// gates. Same real discipline EXP-0081 already established for the
// 1-bit case, now extended to 2 bits -- confirmed via a real P&R
// re-check after this change (see the log), not just asserted.
//
// PROTOCOL: one request (`req` pulse + base_a/base_b/tcnt) triggers
// TWO SEQUENTIAL burst reads (lane A then lane B) over the SAME
@@ -74,14 +82,14 @@ module act_tile_fetch #(
output reg ctrl_req,
output reg ctrl_wr,
output reg [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata,
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
);
assign ctrl_wdata = {(16*BURST_LEN){1'b0}};
assign ctrl_wmask = {(2*BURST_LEN){1'b0}}; // read-only engine, mask unused
assign ctrl_wdata = {(32*BURST_LEN){1'b0}};
assign ctrl_wmask = {(4*BURST_LEN){1'b0}}; // read-only engine, mask unused
localparam S_IDLE = 3'd0,
S_MEMWAIT = 3'd1,
@@ -92,12 +100,12 @@ module act_tile_fetch #(
reg [2:0] state;
reg [ADDR_WIDTH-1:0] base_a_lat, base_b_lat;
reg [15:0] tcnt_lat;
reg sel_lat; // registered at request time -- see header
reg [1:0] sel_lat; // registered at request time -- see header
assign mem_active = (state != S_IDLE);
// burst index = tcnt/2 (integer division -- two tiles share one burst)
wire [ADDR_WIDTH-1:0] tile_offset = {{(ADDR_WIDTH-15){1'b0}}, tcnt_lat[15:1]} * BURST_LEN[ADDR_WIDTH-1:0];
// burst index = tcnt/4 (integer division -- four tiles share one burst)
wire [ADDR_WIDTH-1:0] tile_offset = {{(ADDR_WIDTH-14){1'b0}}, tcnt_lat[15:2]} * BURST_LEN[ADDR_WIDTH-1:0];
always @(posedge clk) begin
if (rst) begin
@@ -105,7 +113,7 @@ module act_tile_fetch #(
ctrl_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= {ADDR_WIDTH{1'b0}};
valid <= 1'b0; data_a <= {(DATA_WIDTH*P_IN){1'b0}}; data_b <= {(DATA_WIDTH*P_IN){1'b0}};
base_a_lat <= {ADDR_WIDTH{1'b0}}; base_b_lat <= {ADDR_WIDTH{1'b0}}; tcnt_lat <= 16'd0;
sel_lat <= 1'b0;
sel_lat <= 2'd0;
end else begin
ctrl_req <= 1'b0;
valid <= 1'b0;
@@ -116,7 +124,7 @@ module act_tile_fetch #(
base_a_lat <= base_a;
base_b_lat <= base_b;
tcnt_lat <= tcnt;
sel_lat <= tcnt[0];
sel_lat <= tcnt[1:0];
state <= S_MEMWAIT;
end
end
@@ -132,8 +140,16 @@ module act_tile_fetch #(
S_REQ_A: begin
if (ctrl_ready) begin
data_a <= sel_lat ? ctrl_rdata[DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]
: ctrl_rdata[0 +: DATA_WIDTH*P_IN];
// explicit constant-offset case arms, not a
// runtime-indexed part-select expression -- see
// header (EXP-0084, extends EXP-0081's same
// discipline from 1 to 2 select bits).
case (sel_lat)
2'd0: data_a <= ctrl_rdata[0 +: DATA_WIDTH*P_IN];
2'd1: data_a <= ctrl_rdata[64 +: DATA_WIDTH*P_IN];
2'd2: data_a <= ctrl_rdata[128 +: DATA_WIDTH*P_IN];
2'd3: data_a <= ctrl_rdata[192 +: DATA_WIDTH*P_IN];
endcase
ctrl_addr <= base_b_lat + tile_offset;
ctrl_wr <= 1'b0;
state <= S_GAP;
@@ -156,8 +172,12 @@ module act_tile_fetch #(
S_REQ_B: begin
if (ctrl_ready) begin
data_b <= sel_lat ? ctrl_rdata[DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]
: ctrl_rdata[0 +: DATA_WIDTH*P_IN];
case (sel_lat)
2'd0: data_b <= ctrl_rdata[0 +: DATA_WIDTH*P_IN];
2'd1: data_b <= ctrl_rdata[64 +: DATA_WIDTH*P_IN];
2'd2: data_b <= ctrl_rdata[128 +: DATA_WIDTH*P_IN];
2'd3: data_b <= ctrl_rdata[192 +: DATA_WIDTH*P_IN];
endcase
valid <= 1'b1;
state <= S_IDLE;
end
+3 -3
View File
@@ -88,9 +88,9 @@ module ddr_prefetch_mgr #(
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata,
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
);
+58 -29
View File
@@ -7,20 +7,30 @@
// 16-bit-WORD req/wr/addr/wdata/lb_n/ub_n -> rdata/ready port (the
// SAME shape as V2's real psram_controller.v / sdram_unified_
// backend.v AR port), but V3's shared memory path (sdram_arbiter_n.v
// -> mig_native_adapter.v) only understands BURST_LEN=8 (128-bit)
// chunks. This module is the translator, matching sdram_unified_
// -> mig_native_adapter.v) only understands BURST_LEN=8, 32-bit-word
// (256-bit) chunks (EXP-0084's real DDR3 widening -- was 128-bit
// before). This module is the translator, matching sdram_unified_
// backend.v's own AR-port technique exactly (not reinvented): a
// write masks out every word in the burst except the target one
// (DQM-style byte masking, already how this project's whole memory
// stack works); a read fetches the whole burst and extracts the
// target word combinationally.
// write masks out every byte in the burst except the target 16-bit
// half-word (DQM-style byte masking, already how this project's whole
// memory stack works); a read fetches the whole burst and extracts
// the target half-word combinationally.
//
// Sits as one requester on sdram_arbiter_n.v (alongside N packed_
// slot.v instances) -- `active` is asserted for the WHOLE single-word
// transaction (word-granularity, no multi-burst sequencing needed),
// so mem_grant only needs to be observed once before the one-shot
// ctrl_req fires, same discipline as packed_slot.v's own S_MEMWAIT
// (EXP-0066's real, hard-won lesson).
// EXP-0084 ADDRESSING NOTE (real, deliberate design choice, not just a
// mechanical width bump): the HOST-facing contract (mem_addr as a
// 16-bit-word address, mem_wdata/mem_rdata as 16-bit values,
// mem_lb_n/mem_ub_n as byte enables) is kept COMPLETELY UNCHANGED --
// this module stays the shock absorber between the host's own fixed
// 16-bit-word view (spi_host_bridge_v3.v's WRITE_MEM/READ_MEM opcode
// payload size, and by extension the ESP32 firmware contract, is
// NOT touched by the DDR3 widening) and the system's now-32-bit-word-
// native shared ctrl bus. mem_addr's LSB now additionally selects
// WHICH 16-bit half of the addressed 32-bit ctrl-bus word to target;
// this halves the host's own reachable byte range for a given
// ADDR_WIDTH (a real, honestly-disclosed, non-blocking limitation of
// this debug/raw-access path only -- not the compute path, and not
// currently a practical constraint at this project's real usage
// scale).
// ============================================================
module host_mem_bridge #(
parameter BURST_LEN = 8,
@@ -31,10 +41,11 @@ module host_mem_bridge #(
// ---- host-facing port (matches spi_host_bridge.v's own
// mem_req/mem_wr/mem_addr/mem_wdata/mem_lb_n/mem_ub_n ->
// mem_rdata/mem_ready convention exactly) ----
// mem_rdata/mem_ready convention exactly -- UNCHANGED by EXP-0084,
// see header) ----
input wire mem_req,
input wire mem_wr,
input wire [ADDR_WIDTH-1:0] mem_addr, // WORD address (not burst-aligned)
input wire [ADDR_WIDTH-1:0] mem_addr, // 16-bit-word address (not burst-aligned)
input wire [15:0] mem_wdata,
input wire mem_lb_n,
input wire mem_ub_n,
@@ -49,13 +60,13 @@ module host_mem_bridge #(
output reg req_req,
output reg req_wr,
output reg [ADDR_WIDTH-1:0] req_addr,
output reg [16*BURST_LEN-1:0] req_wdata,
output reg [2*BURST_LEN-1:0] req_wmask,
input wire [16*BURST_LEN-1:0] req_rdata,
output reg [32*BURST_LEN-1:0] req_wdata,
output reg [4*BURST_LEN-1:0] req_wmask,
input wire [32*BURST_LEN-1:0] req_rdata,
input wire req_ready,
input wire req_busy
);
localparam ALIGN_BITS = $clog2(BURST_LEN);
localparam ALIGN_BITS = $clog2(BURST_LEN); // 3: which of the BURST_LEN 32-bit words in the burst
localparam S_IDLE = 2'd0,
S_MEMWAIT = 2'd1,
@@ -63,10 +74,16 @@ module host_mem_bridge #(
S_DONE = 2'd3;
reg [1:0] state;
reg [ALIGN_BITS-1:0] word_in_block;
reg [ALIGN_BITS-1:0] word_in_block; // which 32-bit word within the burst
reg half_sel; // which 16-bit half of that 32-bit word
assign req_active = (state == S_MEMWAIT) || (state == S_XFER);
// byte offset (0..4*BURST_LEN-2, even) of the target half-word
// within the burst's own byte layout -- word_in_block*4 bytes/word
// + half_sel*2 bytes/half.
wire [ALIGN_BITS+1:0] byte_offset = {word_in_block, half_sel, 1'b0};
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
@@ -79,18 +96,30 @@ module host_mem_bridge #(
case (state)
S_IDLE: begin
if (mem_req) begin
req_addr <= {mem_addr[ADDR_WIDTH-1:ALIGN_BITS], {ALIGN_BITS{1'b0}}};
word_in_block <= mem_addr[ALIGN_BITS-1:0];
// mem_addr[0] = which 16-bit half of the 32-bit
// ctrl-bus word; mem_addr[ALIGN_BITS:1] = which
// of the BURST_LEN 32-bit words in the burst;
// the remaining upper bits, re-aligned to
// 32-bit-word (ctrl bus) units, form the
// burst-aligned req_addr.
req_addr <= {1'b0, mem_addr[ADDR_WIDTH-1:ALIGN_BITS+1], {ALIGN_BITS{1'b0}}};
word_in_block <= mem_addr[ALIGN_BITS:1];
half_sel <= mem_addr[0];
req_wr <= mem_wr;
if (mem_wr) begin
// replicate the target word across the whole
// burst; only its own mask bits matter (see
// header -- same DQM-style technique as
// replicate the target half-word across the
// whole burst; only its own mask bits matter
// (see header -- same DQM-style technique as
// sdram_unified_backend.v's own AR port).
req_wdata <= {BURST_LEN{mem_wdata}};
req_wmask <= {(2*BURST_LEN){1'b1}} &
~(({{(2*BURST_LEN-2){1'b0}}, 2'b11}) << (mem_addr[ALIGN_BITS-1:0]*2)) |
(({{(2*BURST_LEN-2){1'b0}}, mem_ub_n, mem_lb_n}) << (mem_addr[ALIGN_BITS-1:0]*2));
// byte offset within the burst is computed
// directly from the LIVE mem_addr here (not
// from word_in_block/half_sel, which are
// nonblocking-assigned THIS same cycle and
// not yet valid until the next one).
req_wdata <= {(4*BURST_LEN/2){mem_wdata}};
req_wmask <= ({(4*BURST_LEN){1'b1}} &
~(({{(4*BURST_LEN-2){1'b0}}, 2'b11}) << {mem_addr[ALIGN_BITS:0], 1'b0})) |
(({{(4*BURST_LEN-2){1'b0}}, mem_ub_n, mem_lb_n}) << {mem_addr[ALIGN_BITS:0], 1'b0});
end
state <= S_MEMWAIT;
end
@@ -106,7 +135,7 @@ module host_mem_bridge #(
S_XFER: begin
if (req_ready) begin
if (!req_wr)
mem_rdata <= req_rdata[word_in_block*16 +: 16];
mem_rdata <= req_rdata[byte_offset*8 +: 16];
state <= S_DONE;
end
end
+43 -37
View File
@@ -3,12 +3,12 @@
// ============================================================
// V3 -- adapter between this project's own established memory-
// controller contract (req/wr/addr/wdata/wmask -> rdata/ready/busy,
// BURST_LEN=8 16-bit words = 128 bits/transaction, the SAME shape
// sdram_controller.v has presented everywhere in this project since
// STEP16) and the REAL Xilinx MIG 7-series native "app" user
// interface (PG063), generated for this project's actual DDR3 target
// (mig_7series_0, XC7A100T, MT41J128M16JT-125:K, PHY:Controller
// ratio 2:1).
// BURST_LEN=8 32-bit words = 256 bits/transaction since EXP-0084's
// real 32-bit DDR3 channel widening -- was 16-bit/128 bits before)
// and the REAL Xilinx MIG 7-series native "app" user interface
// (PG063), generated for this project's actual DDR3 target
// (mig_7series_0, XC7A100T, 2x MT41J128M16JT-125:K ganged to 32-bit,
// PHY:Controller ratio 2:1, 2900ps/344.827MHz, EXP-0084).
//
// Runs entirely in the ui_clk domain -- MIG's own generated clock is
// this design's new system clock (the standard way MIG-based designs
@@ -17,14 +17,20 @@
// synchronized to ui_clk by the caller.
//
// ADDRESSING (real, derived from THIS project's actual generated MIG
// config, not assumed): Data Width=16, Phy:Controller ratio 2:1 =>
// nCK_PER_CLK=2 => app data width = 16*8/2 = 64 bits, matching the
// real generated mig_7series_0.v port widths exactly (app_wdf_data
// [63:0], app_rd_data[63:0]). One app_addr/app_cmd issuance moves a
// FULL BURST_LEN=8 (128-bit) DDR3 burst, delivered as TWO 64-bit
// beats on the app data bus -- so app_addr increments in the SAME
// unit as this project's own existing ctrl_addr (one BURST_LEN=8
// chunk per increment), no address scaling needed at this boundary.
// config, not assumed, re-verified after EXP-0084's 32-bit
// regeneration): Data Width=32, Phy:Controller ratio 2:1 =>
// nCK_PER_CLK=2 => app data width = 32*8/2 = 128 bits, matching the
// real regenerated mig_7series_0.v port widths exactly (app_wdf_data
// [127:0], app_rd_data[127:0], app_wdf_mask[15:0] -- confirmed by
// directly reading the real generated wrapper, not assumed from the
// old 16-bit numbers). app_addr itself stayed 28 bits (confirmed same
// real generated width as before the widening) -- one app_addr/
// app_cmd issuance still moves a FULL BURST_LEN=8 chunk per increment,
// same as before, just each chunk is now 256 bits (32-bit words) not
// 128 bits (16-bit words), delivered as TWO 128-bit beats instead of
// two 64-bit beats -- so app_addr increments in the SAME unit as this
// project's own existing ctrl_addr, no address scaling needed at this
// boundary, same as before.
//
// Sequencing is deliberately fully sequential, not pipelined
// (correctness first): the command is issued and accepted (app_en/
@@ -52,9 +58,9 @@ module mig_native_adapter #(
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [16*BURST_LEN-1:0] wdata,
input wire [2*BURST_LEN-1:0] wmask,
output reg [16*BURST_LEN-1:0] rdata,
input wire [32*BURST_LEN-1:0] wdata,
input wire [4*BURST_LEN-1:0] wmask,
output reg [32*BURST_LEN-1:0] rdata,
output reg ready,
output wire busy,
@@ -64,15 +70,15 @@ module mig_native_adapter #(
output reg app_en,
input wire app_rdy,
output reg [63:0] app_wdf_data,
output reg app_wdf_end,
output reg [7:0] app_wdf_mask,
output reg app_wdf_wren,
input wire app_wdf_rdy,
output reg [127:0] app_wdf_data,
output reg app_wdf_end,
output reg [15:0] app_wdf_mask,
output reg app_wdf_wren,
input wire app_wdf_rdy,
input wire [63:0] app_rd_data,
input wire app_rd_data_end,
input wire app_rd_data_valid
input wire [127:0] app_rd_data,
input wire app_rd_data_end,
input wire app_rd_data_valid
);
localparam CMD_WRITE = 3'b000;
localparam CMD_READ = 3'b001;
@@ -86,8 +92,8 @@ module mig_native_adapter #(
reg [2:0] state;
reg wr_lat;
reg [16*BURST_LEN-1:0] wdata_lat;
reg [2*BURST_LEN-1:0] wmask_lat;
reg [32*BURST_LEN-1:0] wdata_lat;
reg [4*BURST_LEN-1:0] wmask_lat;
assign busy = (state != S_IDLE);
@@ -98,11 +104,11 @@ module mig_native_adapter #(
app_wdf_wren <= 1'b0;
app_wdf_end <= 1'b0;
ready <= 1'b0;
rdata <= {(16*BURST_LEN){1'b0}};
rdata <= {(32*BURST_LEN){1'b0}};
app_addr <= 28'h0;
app_cmd <= CMD_READ;
app_wdf_data <= 64'h0;
app_wdf_mask <= 8'h0;
app_wdf_data <= 128'h0;
app_wdf_mask <= 16'h0;
end else begin
ready <= 1'b0;
@@ -123,8 +129,8 @@ module mig_native_adapter #(
if (app_rdy) begin
app_en <= 1'b0;
if (wr_lat) begin
app_wdf_data <= wdata_lat[63:0];
app_wdf_mask <= wmask_lat[7:0];
app_wdf_data <= wdata_lat[127:0];
app_wdf_mask <= wmask_lat[15:0];
app_wdf_end <= 1'b0;
app_wdf_wren <= 1'b1;
state <= S_WDF0;
@@ -136,8 +142,8 @@ module mig_native_adapter #(
S_WDF0: begin
if (app_wdf_rdy) begin
app_wdf_data <= wdata_lat[127:64];
app_wdf_mask <= wmask_lat[15:8];
app_wdf_data <= wdata_lat[255:128];
app_wdf_mask <= wmask_lat[31:16];
app_wdf_end <= 1'b1;
app_wdf_wren <= 1'b1;
state <= S_WDF1;
@@ -155,10 +161,10 @@ module mig_native_adapter #(
S_RD_WAIT: begin
if (app_rd_data_valid) begin
if (!app_rd_data_end) begin
rdata[63:0] <= app_rd_data;
rdata[127:0] <= app_rd_data;
end else begin
rdata[127:64] <= app_rd_data;
state <= S_DONE;
rdata[255:128] <= app_rd_data;
state <= S_DONE;
end
end
end
+51 -34
View File
@@ -14,9 +14,16 @@
// packed_slot instances, and the SPI bridge itself) runs in the
// ui_clk domain, per this project's own standing convention
// (mig_native_adapter.v's header) -- ui_clk is generated BY the MIG
// from sys_clk_i, so this module only takes sys_clk_i/clk_ref_i/
// from sys_clk_p/n, so this module only takes sys_clk_p/n/clk_ref_p/n/
// sys_rst as clock/reset inputs, not a separate system clock.
//
// EXP-0084: sys_clk and clk_ref are now real DIFFERENTIAL pairs (the
// user's own wizard choice, confirmed against the real regenerated
// public mig_7series_0.v wrapper, which expects sys_clk_p/n and
// clk_ref_p/n directly as raw top-level differential pins -- not a
// single-ended sys_clk_i/clk_ref_i pair any more). Real board
// implication: a differential oscillator, not a single-ended one.
//
// This is the first REAL (in-context, not out-of-context) P&R target
// for V3: previous P&R runs (EXP-0059/63/67) were all out-of-context
// synthesis of a sub-block, without the real MIG-generated pin/timing
@@ -42,15 +49,25 @@ module n2_system_ddr3_top #(
parameter N_SLOTS = 2,
parameter QUEUE_DEPTH = 8
)(
// ---- MIG clock/reset ----
input wire sys_clk_i,
// ---- MIG clock/reset (EXP-0084: real differential pairs, both
// the user's own wizard choice -- confirmed against the real
// regenerated public mig_7series_0.v wrapper, which now expects
// sys_clk_p/n and clk_ref_p/n directly as raw top-level
// differential pins, not a single-ended sys_clk_i/clk_ref_i pair
// any more) ----
input wire sys_clk_p,
input wire sys_clk_n,
input wire sys_rst,
input wire clk_ref_i,
input wire clk_ref_p,
input wire clk_ref_n,
// ---- real DDR3 pins (matches mig_7series_0.xdc's own port names) ----
inout wire [15:0] ddr3_dq,
inout wire [1:0] ddr3_dqs_n,
inout wire [1:0] ddr3_dqs_p,
// ---- real DDR3 pins (matches mig_7series_0.xdc's own port names;
// dq/dqs/dm widths doubled since EXP-0084's real 32-bit widening --
// two MT41J128M16 chips ganged in parallel, confirmed against the
// real regenerated mig_7series_0.v wrapper, not assumed) ----
inout wire [31:0] ddr3_dq,
inout wire [3:0] ddr3_dqs_n,
inout wire [3:0] ddr3_dqs_p,
output wire [13:0] ddr3_addr,
output wire [2:0] ddr3_ba,
output wire ddr3_ras_n,
@@ -61,7 +78,7 @@ module n2_system_ddr3_top #(
output wire [0:0] ddr3_ck_n,
output wire [0:0] ddr3_cke,
output wire [0:0] ddr3_cs_n,
output wire [1:0] ddr3_dm,
output wire [3:0] ddr3_dm,
output wire [0:0] ddr3_odt,
// ---- physical SPI host interface (-> spi_host_bridge_v3.v) ----
@@ -95,15 +112,15 @@ module n2_system_ddr3_top #(
output wire job_out_done,
output wire [$clog2(N_SLOTS)-1:0] job_out_slot
);
wire [27:0] app_addr;
wire [2:0] app_cmd;
wire app_en, app_rdy;
wire [63:0] app_wdf_data;
wire app_wdf_end;
wire [7:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
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;
assign ui_clk_o = ui_clk;
@@ -120,7 +137,7 @@ module n2_system_ddr3_top #(
.ddr3_ck_p(ddr3_ck_p), .ddr3_ck_n(ddr3_ck_n),
.ddr3_cke(ddr3_cke), .ddr3_cs_n(ddr3_cs_n),
.ddr3_dm(ddr3_dm), .ddr3_odt(ddr3_odt),
.sys_clk_i(sys_clk_i), .clk_ref_i(clk_ref_i),
.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),
@@ -136,9 +153,9 @@ module n2_system_ddr3_top #(
wire adp_req, adp_wr;
wire [MEM_ADDR_WIDTH-1:0] adp_addr;
wire [16*BURST_LEN-1:0] adp_wdata;
wire [2*BURST_LEN-1:0] adp_wmask;
wire [16*BURST_LEN-1:0] adp_rdata;
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;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MEM_ADDR_WIDTH)) u_adapter (
@@ -156,9 +173,9 @@ module n2_system_ddr3_top #(
wire [NUM_REQ-1:0] req_active, req_grant, req_req, req_wr;
wire [NUM_REQ-1:0] req_ready, req_busy;
wire [NUM_REQ*MEM_ADDR_WIDTH-1:0] req_addr;
wire [NUM_REQ*16*BURST_LEN-1:0] req_wdata;
wire [NUM_REQ*2*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata;
wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata;
wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata;
sdram_arbiter_n #(
.NUM_REQ(NUM_REQ), .ADDR_WIDTH(MEM_ADDR_WIDTH), .BURST_LEN(BURST_LEN)
@@ -251,9 +268,9 @@ module n2_system_ddr3_top #(
.req_active(req_active[2]), .req_grant(req_grant[2]),
.req_req(req_req[2]), .req_wr(req_wr[2]),
.req_addr(req_addr[2*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.req_wdata(req_wdata[2*16*BURST_LEN +: 16*BURST_LEN]),
.req_wmask(req_wmask[2*2*BURST_LEN +: 2*BURST_LEN]),
.req_rdata(req_rdata[2*16*BURST_LEN +: 16*BURST_LEN]),
.req_wdata(req_wdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_wmask(req_wmask[2*4*BURST_LEN +: 4*BURST_LEN]),
.req_rdata(req_rdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_ready(req_ready[2]), .req_busy(req_busy[2])
);
@@ -288,9 +305,9 @@ module n2_system_ddr3_top #(
.mem_active(req_active[0]), .mem_grant(req_grant[0]),
.ctrl_req(req_req[0]), .ctrl_wr(req_wr[0]),
.ctrl_addr(req_addr[0*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[0*16*BURST_LEN +: 16*BURST_LEN]),
.ctrl_wmask(req_wmask[0*2*BURST_LEN +: 2*BURST_LEN]),
.ctrl_rdata(req_rdata[0*16*BURST_LEN +: 16*BURST_LEN]),
.ctrl_wdata(req_wdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[0*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[0]), .ctrl_busy(req_busy[0])
);
@@ -314,9 +331,9 @@ module n2_system_ddr3_top #(
.mem_active(req_active[1]), .mem_grant(req_grant[1]),
.ctrl_req(req_req[1]), .ctrl_wr(req_wr[1]),
.ctrl_addr(req_addr[1*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[1*16*BURST_LEN +: 16*BURST_LEN]),
.ctrl_wmask(req_wmask[1*2*BURST_LEN +: 2*BURST_LEN]),
.ctrl_rdata(req_rdata[1*16*BURST_LEN +: 16*BURST_LEN]),
.ctrl_wdata(req_wdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[1*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[1]), .ctrl_busy(req_busy[1])
);
endmodule
+7 -7
View File
@@ -107,9 +107,9 @@ module packed_slot #(
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-2:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata,
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
);
@@ -140,8 +140,8 @@ module packed_slot #(
wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-2:0] pf_ctrl_addr;
wire [16*BURST_LEN-1:0] pf_ctrl_wdata;
wire [2*BURST_LEN-1:0] pf_ctrl_wmask;
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)
@@ -167,8 +167,8 @@ module packed_slot #(
wire act_ctrl_req, act_ctrl_wr;
wire [ADDR_WIDTH-2:0] act_ctrl_addr;
wire [16*BURST_LEN-1:0] act_ctrl_wdata;
wire [2*BURST_LEN-1:0] act_ctrl_wmask;
wire [32*BURST_LEN-1:0] act_ctrl_wdata;
wire [4*BURST_LEN-1:0] act_ctrl_wmask;
ddr_prefetch_mgr #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
+13 -13
View File
@@ -44,18 +44,18 @@ module sdram_arbiter_n #(
input wire [NUM_REQ-1:0] req_req,
input wire [NUM_REQ-1:0] req_wr,
input wire [NUM_REQ*ADDR_WIDTH-1:0] req_addr,
input wire [NUM_REQ*16*BURST_LEN-1:0] req_wdata,
input wire [NUM_REQ*2*BURST_LEN-1:0] req_wmask,
output wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata,
input wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata,
input wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask,
output wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata,
output wire [NUM_REQ-1:0] req_ready,
output wire [NUM_REQ-1:0] req_busy,
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata,
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
);
@@ -107,22 +107,22 @@ module sdram_arbiter_n #(
// mux request-side signals from the granted requester -> shared ctrl
reg m_req, m_wr;
reg [ADDR_WIDTH-1:0] m_addr;
reg [16*BURST_LEN-1:0] m_wdata;
reg [2*BURST_LEN-1:0] m_wmask;
reg [32*BURST_LEN-1:0] m_wdata;
reg [4*BURST_LEN-1:0] m_wmask;
integer mi;
always @(*) begin
m_req = 1'b0;
m_wr = 1'b0;
m_addr = {ADDR_WIDTH{1'b0}};
m_wdata = {(16*BURST_LEN){1'b0}};
m_wmask = {(2*BURST_LEN){1'b0}};
m_wdata = {(32*BURST_LEN){1'b0}};
m_wmask = {(4*BURST_LEN){1'b0}};
for (mi = 0; mi < NUM_REQ; mi = mi + 1) begin
if (sel[mi]) begin
m_req = req_req[mi];
m_wr = req_wr[mi];
m_addr = req_addr[mi*ADDR_WIDTH +: ADDR_WIDTH];
m_wdata = req_wdata[mi*16*BURST_LEN +: 16*BURST_LEN];
m_wmask = req_wmask[mi*2*BURST_LEN +: 2*BURST_LEN];
m_wdata = req_wdata[mi*32*BURST_LEN +: 32*BURST_LEN];
m_wmask = req_wmask[mi*4*BURST_LEN +: 4*BURST_LEN];
end
end
end
@@ -137,7 +137,7 @@ module sdram_arbiter_n #(
genvar gd;
generate
for (gd = 0; gd < NUM_REQ; gd = gd + 1) begin : GEN_DEMUX
assign req_rdata[gd*16*BURST_LEN +: 16*BURST_LEN] = ctrl_rdata;
assign req_rdata[gd*32*BURST_LEN +: 32*BURST_LEN] = ctrl_rdata;
assign req_ready[gd] = sel[gd] ? ctrl_ready : 1'b0;
assign req_busy[gd] = sel[gd] ? ctrl_busy : 1'b1;
end
+109
View File
@@ -0,0 +1,109 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0084 -- minimal, EXPLICITLY SYNTHETIC 32-bit-wide burst-memory
// test model. NOT a real chip model (unlike sdram_controller.v/
// sdram_model.v, which genuinely represent the real AS4C32M16SA x16
// SDR part this project also uses) -- that real model is inherently
// fixed at 16-bit words (a real hardware fact, not a parameter choice)
// and is shared by 20+ other testbenches across v2 and v3, so it is
// deliberately NOT modified here. This file exists purely so the
// isolated, fast (iverilog) testbenches for modules that now speak
// this project's real 32-bit ctrl bus convention (EXP-0084's DDR3
// widening) have a same-shape, functionally-correct backend to run
// against WITHOUT needing the full real MIG IP + ddr3_model.sv (real
// xsim, much slower) for every isolated glue-logic check -- matching
// this project's own established "verify new glue logic against a
// fast backend first" precedent (tb_act_tile_fetch.v's own header),
// just re-pointed at a backend that actually matches the current real
// bus width. The REAL, trustworthy, board-accurate verification still
// comes from tb_n2_system_ddr3.v against the real ddr3_model.sv, same
// as always -- this model's own fixed latency is a plausible, but NOT
// claimed-real, stand-in.
//
// Small DENSE backing store (2^MEM_ADDR_BITS entries), not a full
// 2^ADDR_WIDTH array -- ADDR_WIDTH=25 would need ~1GB densely
// allocated for no reason; every real test in this project only ever
// touches small, low addresses. MEM_ADDR_BITS=20 (~1M entries, ~32MB
// of simulation memory) comfortably covers any realistic test address
// -- including tb_packed_slot.v's own ACT_MEM_BASE=0x10000 region,
// which a first version of this model sized at 16 bits (65536
// entries) silently WRAPPED to address 0, aliasing weight and
// activation data and producing real, confusing wrong-answer failures
// (found via real simulation, not by inspection -- see EXP-0084's
// log for the full root-cause trace). Staying portable (Icarus's
// associative-array support for a packed-vector key type turned out
// not to work for this purpose -- found via a real elaboration
// error, not assumed).
// ============================================================
module burst_mem_model32 #(
parameter BURST_LEN = 8,
parameter ADDR_WIDTH = 25,
parameter MEM_ADDR_BITS = 20,
parameter LATENCY = 6 // fixed req->ready cycles, a plausible stand-in, not claimed real
)(
input wire clk,
input wire rst,
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [32*BURST_LEN-1:0] wdata,
input wire [4*BURST_LEN-1:0] wmask,
output reg [32*BURST_LEN-1:0] rdata,
output reg ready,
output wire busy
);
reg [32*BURST_LEN-1:0] mem [0:(1<<MEM_ADDR_BITS)-1];
localparam S_IDLE = 2'd0, S_BUSY = 2'd1, S_DONE = 2'd2;
reg [1:0] state;
reg [7:0] cnt;
reg [ADDR_WIDTH-1:0] addr_lat;
reg wr_lat;
reg [32*BURST_LEN-1:0] wdata_lat;
reg [4*BURST_LEN-1:0] wmask_lat;
integer bi;
assign busy = (state != S_IDLE);
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
ready <= 1'b0;
cnt <= 8'd0;
end else begin
ready <= 1'b0;
case (state)
S_IDLE: begin
if (req) begin
addr_lat <= addr;
wr_lat <= wr;
wdata_lat <= wdata;
wmask_lat <= wmask;
cnt <= LATENCY[7:0];
state <= S_BUSY;
end
end
S_BUSY: begin
if (cnt == 8'd1) state <= S_DONE;
else cnt <= cnt - 8'd1;
end
S_DONE: begin
if (wr_lat) begin
// real DQM polarity (matches sdram_controller.v's
// own convention): 0=write that byte, 1=masked.
for (bi = 0; bi < 4*BURST_LEN; bi = bi + 1)
if (!wmask_lat[bi])
mem[addr_lat[MEM_ADDR_BITS-1:0]][bi*8 +: 8] <= wdata_lat[bi*8 +: 8];
end else begin
rdata <= mem[addr_lat[MEM_ADDR_BITS-1:0]];
end
ready <= 1'b1;
state <= S_IDLE;
end
default: state <= S_IDLE;
endcase
end
end
endmodule
+62 -72
View File
@@ -1,21 +1,23 @@
`timescale 1ns/1ps
// ============================================================
// Isolated correctness test for act_tile_fetch.v -- real SDR SDRAM
// placeholder backend (same precedent as tb_host_mem_bridge.v/
// tb_sdram_arbiter_n.v: verify new glue logic against the fast
// backend first). Checks: (1) both lanes read back bit-exact from
// their own burst-aligned tile slot; (2) different tile indices
// correctly compute different burst addresses (tile_offset =
// tcnt*BURST_LEN); (3) back-to-back requests (multiple tiles in a
// row) all stay correct, exercising the S_GAP busy-wait logic.
// Isolated correctness test for act_tile_fetch.v -- EXP-0084: re-run
// against the new 32-bit-wide ctrl bus / 4-tiles-per-burst layout
// (real 32-bit DDR3 channel widening). Backend switched from the real
// AS4C32M16SA x16 SDR model (sdram_controller.v/sdram_model.v -- a
// REAL chip, genuinely fixed at 16-bit, not reusable here) to
// burst_mem_model32.v, an explicitly synthetic 32-bit test-only
// burst memory built for exactly this purpose (see its own header).
// Checks: (1) all four lanes of a burst read back bit-exact from
// their own quarter-slot; (2) different tile indices correctly
// compute different burst addresses (tile_offset = (tcnt>>2)*
// BURST_LEN); (3) back-to-back requests (multiple tiles in a row,
// including crossing a burst boundary) all stay correct, exercising
// the S_GAP busy-wait logic.
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam ADDR_WIDTH = 25;
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam DATA_WIDTH = 8;
@@ -27,30 +29,16 @@ module tb;
wire ctrl_req, ctrl_wr;
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;
wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
burst_mem_model32 #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
) u_mem (
.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(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
);
// single requester -> tie grant = active, same precedent as
@@ -66,8 +54,8 @@ module tb;
wire dut_ctrl_req, dut_ctrl_wr;
wire [ADDR_WIDTH-1:0] dut_ctrl_addr;
wire [16*BURST_LEN-1:0] dut_ctrl_wdata;
wire [2*BURST_LEN-1:0] dut_ctrl_wmask;
wire [32*BURST_LEN-1:0] dut_ctrl_wdata;
wire [4*BURST_LEN-1:0] dut_ctrl_wmask;
act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -81,27 +69,21 @@ module tb;
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
// ---- preload path: direct access to the SDRAM controller,
// bypassing act_tile_fetch.v entirely, same "pre_active" mux
// pattern as every other testbench in this project ----
// ---- preload path: direct access to the memory model, bypassing
// act_tile_fetch.v entirely, same "pre_active" mux pattern as
// every other testbench in this project ----
reg pre_active;
reg pre_req, pre_wr;
reg [ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata;
reg [32*BURST_LEN-1:0] pre_wdata;
// reroute: real DUT ctrl_* wires go through a mux so the testbench
// can preload memory directly before act_tile_fetch.v ever runs.
// (Re-declare the connection: DUT was wired directly above for
// simplicity of the DUT instantiation; use force-free approach by
// instead having the DUT's own ctrl_req/wr/addr/wdata feed the mux
// inputs below and the mux feed the real controller.)
assign ctrl_req = pre_active ? pre_req : dut_ctrl_req;
assign ctrl_wr = pre_active ? pre_wr : dut_ctrl_wr;
assign ctrl_addr = pre_active ? pre_addr : dut_ctrl_addr;
assign ctrl_wdata = pre_active ? pre_wdata : dut_ctrl_wdata;
assign ctrl_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : dut_ctrl_wmask;
assign ctrl_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : dut_ctrl_wmask;
task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data);
task automatic mem_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin
@(posedge clk); while (ctrl_busy) @(posedge clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@@ -137,7 +119,7 @@ module tb;
reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b;
integer k, wi;
reg [16*BURST_LEN-1:0] burst;
reg [32*BURST_LEN-1:0] burst;
initial begin
errors = 0; tests = 0;
@@ -147,54 +129,62 @@ module tb;
rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload 2 bursts/lane, 2 tiles packed per burst (EXP-0081 layout) ===");
$display("=== preload 2 bursts/lane, 4 tiles packed per burst (EXP-0084 layout) ===");
// lane A base = 0, lane B base = 100 (arbitrary, word-address units).
// burst pair p holds tile 2p (low 64 bits) and tile 2p+1 (high 64 bits).
for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst-pair index (0 -> tiles 0/1, 1 -> tiles 2/3)
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)};
sdram_write_burst(0 + wi*BURST_LEN, burst);
// burst pair p holds tiles 4p, 4p+1, 4p+2, 4p+3 in quarters 0..3.
for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst index (0 -> tiles 0-3, 1 -> tiles 4-7)
for (k = 0; k < 4; k = k + 1)
burst[k*64 +: 64] = {act_byte(0, 4*wi+k, 7), act_byte(0, 4*wi+k, 6), act_byte(0, 4*wi+k, 5), act_byte(0, 4*wi+k, 4),
act_byte(0, 4*wi+k, 3), act_byte(0, 4*wi+k, 2), act_byte(0, 4*wi+k, 1), act_byte(0, 4*wi+k, 0)};
mem_write_burst(0 + wi*BURST_LEN, burst);
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(100, 2*wi, 2*k+1), act_byte(100, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(100, 2*wi+1, 2*k+1), act_byte(100, 2*wi+1, 2*k)};
sdram_write_burst(100 + wi*BURST_LEN, burst);
for (k = 0; k < 4; k = k + 1)
burst[k*64 +: 64] = {act_byte(100, 4*wi+k, 7), act_byte(100, 4*wi+k, 6), act_byte(100, 4*wi+k, 5), act_byte(100, 4*wi+k, 4),
act_byte(100, 4*wi+k, 3), act_byte(100, 4*wi+k, 2), act_byte(100, 4*wi+k, 1), act_byte(100, 4*wi+k, 0)};
mem_write_burst(100 + wi*BURST_LEN, burst);
end
@(posedge clk);
pre_active = 1'b0;
$display("=== TEST 1: fetch tile 0 (even -> low half), both lanes ===");
$display("=== TEST 1: fetch tile 0 (quarter 0), both lanes ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 0, k);
check(data_a === exp_a, "T1: lane A tile 0 bit-exact");
check(data_b === exp_b, "T1: lane B tile 0 bit-exact");
$display("=== TEST 2: fetch tile 1 (odd -> high half, SAME burst address as tile 0) ===");
$display("=== TEST 2: fetch tile 1 (quarter 1, SAME burst address as tile 0) ===");
do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 1, k);
check(data_a === exp_a, "T2: lane A tile 1 bit-exact");
check(data_b === exp_b, "T2: lane B tile 1 bit-exact");
$display("=== TEST 3: fetch tile 2 (even -> low half, NEW burst address) ===");
$display("=== TEST 2b: fetch tile 2 (quarter 2, SAME burst) ===");
do_fetch(25'd0, 25'd100, 16'd2);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k);
check(data_a === exp_a, "T3: lane A tile 2 bit-exact (new burst)");
check(data_a === exp_a, "T2b: lane A tile 2 bit-exact");
$display("=== TEST 4: back-to-back fetches, alternating even/odd tiles ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, even), lane A correct");
do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 1, odd), lane A correct");
$display("=== TEST 2c: fetch tile 3 (quarter 3, SAME burst) ===");
do_fetch(25'd0, 25'd100, 16'd3);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 3, k);
check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 3, odd, new burst), lane A correct");
check(data_a === exp_a, "T2c: lane A tile 3 bit-exact");
$display("=== TEST 3: fetch tile 4 (quarter 0, NEW burst address) ===");
do_fetch(25'd0, 25'd100, 16'd4);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 4, k);
check(data_a === exp_a, "T3: lane A tile 4 bit-exact (new burst)");
$display("=== TEST 4: back-to-back fetches, cycling through all 4 quarters ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, quarter 0), lane A correct");
do_fetch(25'd0, 25'd100, 16'd2);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k);
check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 2, quarter 2), lane A correct");
do_fetch(25'd0, 25'd100, 16'd7);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 7, k);
check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 7, quarter 3, new burst), lane A correct");
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_act_tile_fetch)");
+51 -125
View File
@@ -1,40 +1,33 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0083: (1) isolated correctness test for ddr_prefetch_mgr.v --
// same real SDR SDRAM placeholder backend precedent as
// tb_act_tile_fetch.v -- and (2) a real, measured, honest A/B cycle-
// count comparison against the OLD per-tile req/wait/consume loop
// packed_slot.v used before EXP-0083, to get a REAL number for the
// look-ahead prefetch's benefit instead of asserting one.
// EXP-0084: re-run of the EXP-0083 ddr_prefetch_mgr.v isolated
// correctness + A/B cycle-count test against the new 32-bit ctrl bus
// (real DDR3 channel widening). Backend switched to burst_mem_
// model32.v (see its own header -- explicitly synthetic, fixed
// latency, not claiming real DDR3 row/bank AC timing) since the real
// AS4C32M16SA x16 SDR model this test used before is genuinely fixed
// at 16-bit and can't represent the new bus width.
//
// Both the "baseline" (direct act_tile_fetch.v, one requester per
// tile, old packed_slot.v sequencing) and the "prefetch" (ddr_
// prefetch_mgr.v, EXP-0083) loops are run against the SAME shared
// backend and the SAME preloaded data, back to back, muxed the same
// way tb_act_tile_fetch.v's own pre_active mux works -- so the
// comparison is apples to apples, not two different simulated
// environments.
//
// Both loops apply the SAME 2-cycle "simulated compute overhead" per
// tile (matching packed_slot.v's own real S_TILEREQ + S_OPERAND
// single-cycle costs) between a tile becoming available and the next
// step being taken -- the honest question this answers is: does
// removing the OLD design's serialization of that overhead with the
// NEXT tile's DDR3 fetch produce a real, measurable improvement, and
// how much.
// EXP-0083's own PART 3 (same-row vs row-switch-heavy A/B, exploiting
// the old SDR model's own real row/bank timing distinction) is
// DROPPED here -- burst_mem_model32.v has a single fixed latency
// regardless of address pattern, so that comparison would no longer
// carry real meaning on this backend. The trustworthy, real number
// for that effect already comes from EXP-0083's own real-DDR3-backend
// measurement (tb_n2_system_ddr3.v, 2.86% real reduction) -- this
// isolated test's own job is correctness + a basic sanity check that
// prefetch is still not slower than the old per-tile loop, not to
// re-derive that headline number.
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam ADDR_WIDTH = 25;
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam DATA_WIDTH = 8;
localparam P_IN = 8;
localparam N_TILES = 6; // 3 burst-pairs/lane -- enough to see steady-state behavior
localparam N_TILES = 8; // 2 bursts/lane at 4 tiles/burst -- enough to see steady-state behavior
reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk;
@@ -42,30 +35,16 @@ module tb;
wire ctrl_req, ctrl_wr;
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;
wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
burst_mem_model32 #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
) u_mem (
.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(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
);
// ---- 3-way mux on the shared backend: preload / baseline DUT / prefetch DUT ----
@@ -74,7 +53,7 @@ module tb;
reg pre_req, pre_wr;
reg [ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata;
reg [32*BURST_LEN-1:0] pre_wdata;
// ---- baseline DUT: plain act_tile_fetch.v, driven by a per-tile
// req/wait/consume loop replicating OLD packed_slot.v sequencing ----
@@ -86,8 +65,8 @@ module tb;
wire base_mem_active;
wire base_ctrl_req, base_ctrl_wr;
wire [ADDR_WIDTH-1:0] base_ctrl_addr;
wire [16*BURST_LEN-1:0] base_ctrl_wdata;
wire [2*BURST_LEN-1:0] base_ctrl_wmask;
wire [32*BURST_LEN-1:0] base_ctrl_wdata;
wire [4*BURST_LEN-1:0] base_ctrl_wmask;
act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -111,8 +90,8 @@ module tb;
wire pf_mem_active;
wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-1:0] pf_ctrl_addr;
wire [16*BURST_LEN-1:0] pf_ctrl_wdata;
wire [2*BURST_LEN-1:0] pf_ctrl_wmask;
wire [32*BURST_LEN-1:0] pf_ctrl_wdata;
wire [4*BURST_LEN-1:0] pf_ctrl_wmask;
ddr_prefetch_mgr #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -130,9 +109,9 @@ module tb;
assign ctrl_wr = (sel==SEL_PRELOAD) ? pre_wr : (sel==SEL_BASE) ? base_ctrl_wr : pf_ctrl_wr;
assign ctrl_addr = (sel==SEL_PRELOAD) ? pre_addr : (sel==SEL_BASE) ? base_ctrl_addr : pf_ctrl_addr;
assign ctrl_wdata = (sel==SEL_PRELOAD) ? pre_wdata : (sel==SEL_BASE) ? base_ctrl_wdata : pf_ctrl_wdata;
assign ctrl_wmask = (sel==SEL_BASE) ? base_ctrl_wmask : (sel==SEL_PF) ? pf_ctrl_wmask : {(2*BURST_LEN){1'b0}};
assign ctrl_wmask = (sel==SEL_BASE) ? base_ctrl_wmask : (sel==SEL_PF) ? pf_ctrl_wmask : {(4*BURST_LEN){1'b0}};
task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data);
task automatic mem_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin
@(posedge clk); while (ctrl_busy) @(posedge clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@@ -155,8 +134,8 @@ module tb;
endtask
reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b;
integer k, wi, cyc;
reg [16*BURST_LEN-1:0] burst;
integer k, wi, qi;
reg [32*BURST_LEN-1:0] burst;
integer t_start, t_end, base_cycles, pf_cycles;
initial begin
@@ -169,24 +148,22 @@ module tb;
rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload lane A base=0, lane B base=200, %0d tiles (EXP-0081 2-tiles/burst layout) ===", N_TILES);
for (wi = 0; wi < N_TILES/2; wi = wi + 1) begin
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)};
sdram_write_burst(0 + wi*BURST_LEN, burst);
$display("=== preload lane A base=0, lane B base=200, %0d tiles (EXP-0084 4-tiles/burst layout) ===", N_TILES);
for (wi = 0; wi < N_TILES/4; wi = wi + 1) begin
for (qi = 0; qi < 4; qi = qi + 1)
burst[qi*64 +: 64] = {act_byte(0, 4*wi+qi, 7), act_byte(0, 4*wi+qi, 6), act_byte(0, 4*wi+qi, 5), act_byte(0, 4*wi+qi, 4),
act_byte(0, 4*wi+qi, 3), act_byte(0, 4*wi+qi, 2), act_byte(0, 4*wi+qi, 1), act_byte(0, 4*wi+qi, 0)};
mem_write_burst(0 + wi*BURST_LEN, burst);
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(200, 2*wi, 2*k+1), act_byte(200, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(200, 2*wi+1, 2*k+1), act_byte(200, 2*wi+1, 2*k)};
sdram_write_burst(200 + wi*BURST_LEN, burst);
for (qi = 0; qi < 4; qi = qi + 1)
burst[qi*64 +: 64] = {act_byte(200, 4*wi+qi, 7), act_byte(200, 4*wi+qi, 6), act_byte(200, 4*wi+qi, 5), act_byte(200, 4*wi+qi, 4),
act_byte(200, 4*wi+qi, 3), act_byte(200, 4*wi+qi, 2), act_byte(200, 4*wi+qi, 1), act_byte(200, 4*wi+qi, 0)};
mem_write_burst(200 + wi*BURST_LEN, burst);
end
@(posedge clk);
// ============================================================
// PART 1: baseline correctness + real cycle count, OLD-style
// PART 1: baseline correctness + cycle count, OLD-style
// per-tile req -> wait valid -> 2-cycle simulated compute -> next req
// ============================================================
$display("=== PART 1: baseline (direct act_tile_fetch.v, old packed_slot.v loop) ===");
@@ -213,10 +190,10 @@ module tb;
$display("baseline: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, base_cycles, base_cycles*1.0/N_TILES);
// ============================================================
// PART 2: prefetch correctness + real cycle count, EXP-0083
// job-level start, poll tile_valid, 2-cycle simulated compute, consume
// PART 2: prefetch correctness + cycle count, EXP-0083 job-level
// start, poll tile_valid, 2-cycle simulated compute, consume
// ============================================================
$display("=== PART 2: prefetch (ddr_prefetch_mgr.v, EXP-0083) ===");
$display("=== PART 2: prefetch (ddr_prefetch_mgr.v, EXP-0083/0084) ===");
sel = SEL_PF;
@(posedge clk);
pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= N_TILES[15:0];
@@ -230,11 +207,7 @@ module tb;
// pulse being sampled by the DUT (both triggered off the same
// @(posedge clk)), reading pf_tile_valid/bank_valid BEFORE the
// DUT's own nonblocking update for that consume has been
// applied -- a real testbench race, not an RTL bug (found via
// an iteration-tagged trace: k=1 was reading k=0's still-
// unconsumed bank). #1 (real time, 1ns << the 15.625ns clock
// period) forces this poll to always run strictly after that
// update has settled.
// applied -- a real testbench race, not an RTL bug (EXP-0083).
#1;
while (!pf_tile_valid) @(posedge clk);
for (wi = 0; wi < P_IN; wi = wi + 1) begin
@@ -252,56 +225,9 @@ module tb;
pf_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("prefetch: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, pf_cycles, pf_cycles*1.0/N_TILES);
$display("=== REAL MEASURED COMPARISON (row-switch-heavy, 3 different burst pairs): baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% ===",
base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles);
check(pf_cycles < base_cycles, "prefetch is real, measurably faster than baseline (row-switch-heavy)");
// ============================================================
// PART 3 (EXP-0083 addendum): best-case, SAME-ROW comparison --
// only tiles 0/1 (both already resident in the FIRST preloaded
// burst pair, no row activation needed for either), isolating
// the look-ahead benefit from row-switch cost entirely. Answers
// honestly whether the small PART-1/2 result is because the
// benefit is inherently small, or because row-switch cost
// dominates and masks it in that scenario.
// ============================================================
$display("=== PART 3: same-row best case (2 tiles, single burst pair, no row switch) ===");
sel = SEL_BASE;
@(posedge clk);
t_start = $time;
for (k = 0; k < 2; k = k + 1) begin
@(posedge clk);
base_base_a <= 0; base_base_b <= 200; base_tcnt <= k[15:0];
base_req <= 1'b1;
@(posedge clk);
base_req <= 1'b0;
while (!base_valid) @(posedge clk);
repeat(2) @(posedge clk);
end
t_end = $time;
base_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("same-row baseline: 2 tiles in %0d cycles", base_cycles);
sel = SEL_PF;
@(posedge clk);
pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= 16'd2;
pf_job_start <= 1'b1;
t_start = $time;
@(posedge clk);
pf_job_start <= 1'b0;
for (k = 0; k < 2; k = k + 1) begin
#1;
while (!pf_tile_valid) @(posedge clk);
repeat(2) @(posedge clk);
pf_tile_consume <= 1'b1;
@(posedge clk);
pf_tile_consume <= 1'b0;
end
t_end = $time;
pf_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("same-row prefetch: 2 tiles in %0d cycles", pf_cycles);
$display("=== REAL MEASURED COMPARISON (same-row, best case): baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% ===",
$display("=== cycle comparison on this fixed-latency synthetic backend: baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% (sanity check only -- the real, trustworthy number is EXP-0083's real-DDR3-backend measurement, 2.86%%) ===",
base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles);
check(pf_cycles <= base_cycles, "prefetch is not slower than baseline (sanity check)");
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_ddr_prefetch_mgr)");
+31 -40
View File
@@ -4,26 +4,31 @@
// Isolated correctness test for host_mem_bridge.v: the word<->burst
// translator that closes the "no host raw-memory-access path" gap
// found re-auditing spi_host_bridge.v against V3 (EXP-0068's audit).
// Uses the cheap SDR SDRAM placeholder backend (sdram_controller.v +
// sdram_model.v), same precedent as tb_sdram_arbiter_n.v: verify new
// glue logic against the fast backend first, real DDR3 integration
// is a separate, later step once this is trusted standalone.
//
// Checks: (a) single-word write only touches its OWN word inside the
// burst (byte masking correctness, lb_n/ub_n both individually and
// together) without corrupting neighboring words in the same burst;
// (b) single-word read extracts the correct word regardless of its
// offset within the burst (all BURST_LEN=8 offsets exercised);
// (c) mem_ready pulses exactly once per transaction.
// EXP-0084: re-run against the new 32-bit ctrl bus / burst_mem_
// model32.v backend (real DDR3 channel widening -- was 16-bit-word-
// native before, now 32-bit-word-native, see host_mem_bridge.v's own
// header for the real addressing redesign this required: mem_addr
// stays a 16-bit-word address for the host's own unchanged protocol,
// but now mem_addr[0] additionally selects which half of the
// underlying 32-bit ctrl-bus word to target). One burst now spans
// 2*BURST_LEN=16 host-side 16-bit-word offsets (was BURST_LEN=8) --
// this test now exercises all 16, not just 8, to cover the new
// half-select logic across the WHOLE burst, not half of it.
//
// Checks: (a) single-word write only touches its OWN half-word inside
// the burst (byte masking correctness, lb_n/ub_n both individually and
// together) without corrupting neighboring half-words in the same
// burst; (b) single-word read extracts the correct half-word
// regardless of its offset within the burst (all 16 offsets
// exercised); (c) mem_ready pulses exactly once per transaction.
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS;
localparam ADDR_WIDTH = 25;
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam BURST_SPAN = 2*BURST_LEN; // 16-bit-word positions per burst, host-side addressing
reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk;
@@ -31,30 +36,16 @@ module tb;
wire ctrl_req, ctrl_wr;
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;
wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
burst_mem_model32 #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
) u_mem (
.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(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
);
// single requester -> arbiter isn't even needed for an isolated
@@ -112,7 +103,7 @@ module tb;
reg [15:0] got;
integer i;
localparam [ADDR_WIDTH-1:0] BASE = 25'd200; // burst-aligned base (200 % 8 == 0)
localparam [ADDR_WIDTH-1:0] BASE = 25'd256; // burst-aligned base (256 % 16 == 0)
initial begin
errors = 0; tests = 0;
@@ -121,11 +112,11 @@ module tb;
rst = 0;
@(posedge clk);
$display("=== TEST 1: write+read every word offset within one burst, verify no cross-word corruption ===");
for (i = 0; i < BURST_LEN; i = i + 1) begin
$display("=== TEST 1: write+read every half-word offset within one burst (0..%0d), verify no cross-word corruption ===", BURST_SPAN-1);
for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_write(BASE + i[ADDR_WIDTH-1:0], 16'hA000 + i[15:0], 1'b0, 1'b0);
end
for (i = 0; i < BURST_LEN; i = i + 1) begin
for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_read(BASE + i[ADDR_WIDTH-1:0], got);
tests = tests + 1;
if (got !== (16'hA000 + i[15:0])) begin
@@ -136,9 +127,9 @@ module tb;
end
end
$display("=== TEST 2: re-write word 3 only, confirm neighbors (0,1,2,4..7) untouched ===");
$display("=== TEST 2: re-write offset 3 only, confirm neighbors (0..%0d except 3) untouched ===", BURST_SPAN-1);
host_write(BASE + 25'd3, 16'hBEEF, 1'b0, 1'b0);
for (i = 0; i < BURST_LEN; i = i + 1) begin
for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_read(BASE + i[ADDR_WIDTH-1:0], got);
tests = tests + 1;
if (i == 3) begin
+73 -44
View File
@@ -16,17 +16,23 @@
// real calibration, impractically slow for simulation) and does not
// expose this parameter; mig_7series_0_mig.v does. All other
// parameters are left at their defaults, which already ARE this
// project's real generated configuration (DQ_WIDTH=16, MEM_DENSITY=
// 2Gb, MEM_SPEEDGRADE=125, MEM_ADDR_ORDER=BANK_ROW_COLUMN, etc.) --
// project's real generated configuration (EXP-0084: DQ_WIDTH=32 now,
// two MT41J128M16 components ganged in parallel -- MEM_DENSITY=2Gb,
// MEM_SPEEDGRADE=125, MEM_ADDR_ORDER=BANK_ROW_COLUMN unchanged) --
// not generic MIG defaults.
//
// Clock/reset generation and DDR3 pin wiring (WireDelay pass-through,
// zero propagation delay) mirror this project's own vendor-shipped
// example_design/sim/sim_tb_top.v exactly, per its own real, proven
// pattern -- not re-derived from scratch.
// pattern -- not re-derived from scratch. EXP-0084: the real vendor
// pattern instantiates NUM_COMP=DQ_WIDTH/16 real ddr3_model.sv
// components for a >16-bit aggregate width, each wired to its own
// 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).
// ============================================================
module tb;
localparam CLKIN_PERIOD = 3225; // ps, matches this project's real MIG config
localparam CLKIN_PERIOD = 2900; // ps, matches this project's real MIG config (EXP-0084)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
@@ -37,41 +43,56 @@ module tb;
reg sys_rst_n;
wire sys_rst = sys_rst_n; // Active Low, matches mig_7series_0_mig's own default polarity
// EXP-0084: both system clock AND reference clock are now real
// DIFFERENTIAL pairs on the inner mig_7series_0_mig module (the
// user's own wizard choice, "Differential" for both) -- confirmed
// against the real regenerated mig_7series_0_mig.v port list
// (sys_clk_p/n, clk_ref_p/n, no single-ended sys_clk_i/clk_ref_i
// ports exist any more). Same real vendor pattern as sim_tb_top.v:
// generate a single-ended internal clock, drive the P/N pair as
// true/complement of it.
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 ----
// ---- real DDR3 pins (dq/dqs/dm widths doubled since EXP-0084's
// real 32-bit widening -- two MT41J128M16 chips ganged in
// parallel, confirmed against the real regenerated
// mig_7series_0.v wrapper) ----
wire ddr3_reset_n;
wire [15:0] ddr3_dq_fpga;
wire [1:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
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 [1:0] ddr3_dm_fpga;
wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga;
wire [15:0] ddr3_dq_sdram;
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 [1:0] ddr3_dm_sdram;
wire [1:0] ddr3_dqs_p_sdram, ddr3_dqs_n_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 [1:0] ddr3_dm_sdram_tmp;
reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin
@@ -93,7 +114,7 @@ module tb;
genvar dqwd;
generate
for (dqwd = 0; dqwd < 16; dqwd = dqwd + 1) begin : dq_delay
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)
@@ -102,7 +123,7 @@ module tb;
endgenerate
genvar dqswd;
generate
for (dqswd = 0; dqswd < 2; dqswd = dqswd + 1) begin : dqs_delay
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)
@@ -114,29 +135,37 @@ module tb;
end
endgenerate
// ---- real DDR3 behavioral model (single component, DQ_WIDTH=16
// matches MEMORY_WIDTH=16 exactly, no splitting needed) ----
ddr3_model u_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), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram), .dqs(ddr3_dqs_p_sdram), .dqs_n(ddr3_dqs_n_sdram),
.tdqs_n(), .odt(ddr3_odt_sdram[0])
);
// ---- real DDR3 behavioral model (EXP-0084: TWO components now,
// DQ_WIDTH=32 / 16 per component -- exact real pattern confirmed
// against the real regenerated sim_tb_top.v's own generate block,
// not assumed) ----
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
// ---- real MIG controller (inner module, SIM_BYPASS_INIT_CAL
// overridden for a real but fast simulation calibration) ----
wire [27:0] app_addr;
wire [2:0] app_cmd;
wire app_en, app_rdy;
wire [63:0] app_wdf_data;
wire app_wdf_end;
wire [7:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete;
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")
@@ -148,7 +177,7 @@ module tb;
.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_i(sys_clk_i), .clk_ref_i(clk_ref_i),
.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),
@@ -165,9 +194,9 @@ module tb;
// ---- adapter under test ----
reg req, wr;
reg [ADDR_WIDTH-1:0] addr;
reg [16*BURST_LEN-1:0] wdata;
reg [2*BURST_LEN-1:0] wmask;
wire [16*BURST_LEN-1:0] rdata;
reg [32*BURST_LEN-1:0] wdata;
reg [4*BURST_LEN-1:0] wmask;
wire [32*BURST_LEN-1:0] rdata;
wire ready, busy;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)) u_adapter (
@@ -183,13 +212,13 @@ module tb;
task automatic do_txn(
input t_wr,
input [ADDR_WIDTH-1:0] t_addr,
input [16*BURST_LEN-1:0] t_wdata,
output [16*BURST_LEN-1:0] t_rdata
input [32*BURST_LEN-1:0] t_wdata,
output [32*BURST_LEN-1:0] t_rdata
);
begin
@(posedge ui_clk);
while (busy) @(posedge ui_clk);
req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; wmask = {(2*BURST_LEN){1'b0}};
req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; wmask = {(4*BURST_LEN){1'b0}};
@(posedge ui_clk);
req = 1'b0;
while (!ready) @(posedge ui_clk);
@@ -198,15 +227,15 @@ module tb;
endtask
integer errors, tests;
reg [16*BURST_LEN-1:0] got, wpat;
reg [32*BURST_LEN-1:0] got, wpat;
integer k, i;
task automatic check_addr(input [ADDR_WIDTH-1:0] a, input [15:0] pattern);
begin
for (k = 0; k < BURST_LEN; k = k + 1)
wpat[k*16 +: 16] = pattern + k[15:0];
wpat[k*32 +: 32] = {pattern, pattern + k[15:0]};
do_txn(1'b1, a, wpat, got);
do_txn(1'b0, a, {(16*BURST_LEN){1'b0}}, got);
do_txn(1'b0, a, {(32*BURST_LEN){1'b0}}, got);
tests = tests + 1;
if (got !== wpat) begin
$display("FAIL addr=%0d: got=%h expected=%h", a, got, wpat);
+84 -60
View File
@@ -26,9 +26,19 @@
// 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 -- 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).
// ============================================================
module tb;
localparam CLKIN_PERIOD = 3225; // ps, this project's real MIG config
localparam CLKIN_PERIOD = 2900; // ps, this project's real MIG config (EXP-0084)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
@@ -55,8 +65,12 @@ module tb;
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;
@@ -64,27 +78,27 @@ module tb;
// ---- real DDR3 pins + model (identical to tb_mig_native_adapter.v) ----
wire ddr3_reset_n;
wire [15:0] ddr3_dq_fpga;
wire [1:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
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 [1:0] ddr3_dm_fpga;
wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga;
wire [15:0] ddr3_dq_sdram;
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 [1:0] ddr3_dm_sdram;
wire [1:0] ddr3_dqs_p_sdram, ddr3_dqs_n_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 [1:0] ddr3_dm_sdram_tmp;
reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin
@@ -106,7 +120,7 @@ module tb;
genvar dqwd;
generate
for (dqwd = 0; dqwd < 16; dqwd = dqwd + 1) begin : dq_delay
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)
@@ -115,7 +129,7 @@ module tb;
endgenerate
genvar dqswd;
generate
for (dqswd = 0; dqswd < 2; dqswd = dqswd + 1) begin : dqs_delay
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)
@@ -127,25 +141,31 @@ module tb;
end
endgenerate
ddr3_model u_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), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram), .dqs(ddr3_dqs_p_sdram), .dqs_n(ddr3_dqs_n_sdram),
.tdqs_n(), .odt(ddr3_odt_sdram[0])
);
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 [63:0] app_wdf_data;
wire app_wdf_end;
wire [7:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete;
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")
@@ -157,7 +177,7 @@ module tb;
.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_i(sys_clk_i), .clk_ref_i(clk_ref_i),
.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),
@@ -178,25 +198,25 @@ module tb;
reg pre_active;
reg pre_req, pre_wr;
reg [MIG_ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata;
reg [32*BURST_LEN-1:0] pre_wdata;
wire adp_req, adp_wr;
wire [MIG_ADDR_WIDTH-1:0] adp_addr;
wire [16*BURST_LEN-1:0] adp_wdata;
wire [2*BURST_LEN-1:0] adp_wmask;
wire [16*BURST_LEN-1:0] adp_rdata;
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 [16*BURST_LEN-1:0] arb_ctrl_wdata_o;
wire [2*BURST_LEN-1:0] arb_ctrl_wmask_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 ? {(2*BURST_LEN){1'b0}} : arb_ctrl_wmask_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),
@@ -215,7 +235,7 @@ module tb;
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF));
endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data);
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;
@@ -224,15 +244,18 @@ module tb;
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 [16*BURST_LEN-1:0] burst_data;
reg [32*BURST_LEN-1:0] burst_data;
begin
for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(2*BURST_LEN)); bi = bi + 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*(2*BURST_LEN) + wb*2;
burst_data[wb*16 +: 16] = {weight_byte(li, tt+1), weight_byte(li, tt)};
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
@@ -240,30 +263,31 @@ module tb;
end
endtask
// ---- real activation preload (EXP-0081 layout: TWO consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's
// own header). ----
// ---- 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/2)*BURST_LEN);
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, tp, k;
reg [16*BURST_LEN-1:0] burst_data;
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 (tp = 0; tp < N_TILES/2; tp = tp + 1) begin
burst_data = {(16*BURST_LEN){1'b0}};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)};
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data);
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
@@ -309,8 +333,8 @@ module tb;
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 [16*BURST_LEN*2-1:0] s_ctrl_wdata_flat, s_ctrl_rdata_flat;
wire [2*BURST_LEN*2-1:0] s_ctrl_wmask_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),
@@ -350,9 +374,9 @@ module tb;
.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*16*BURST_LEN +: 16*BURST_LEN]),
.ctrl_wmask(s_ctrl_wmask_flat[gi*2*BURST_LEN +: 2*BURST_LEN]),
.ctrl_rdata(s_ctrl_rdata_flat[gi*16*BURST_LEN +: 16*BURST_LEN]),
.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
+48 -54
View File
@@ -11,17 +11,20 @@
//
// EXP-0079 UPDATE: packed_slot.v now wraps a REAL act_tile_fetch.v
// (real DDR3 reads, no stand-in port left) -- this test now preloads
// activation data into the SAME real SDR SDRAM placeholder backend
// already used for weights (preload_sdram_activations, matching
// act_tile_fetch.v's own real memory layout: one full BURST_LEN=8-word
// burst per tile), instead of a combinational behavioral lookup.
// activation data into a real burst-memory backend, matching
// act_tile_fetch.v's own real memory layout.
//
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- backend
// switched to burst_mem_model32.v (the real AS4C32M16SA x16 SDR model
// this test used before is genuinely fixed at 16-bit and can't
// represent the new bus width, see that model's own header), and both
// preload tasks rewritten for the new BYTES_PER_BURST=4*BURST_LEN
// (weights, layer_prefetch_ctrl.v) and 4-tiles-per-burst (activations,
// act_tile_fetch.v) real layouts.
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam SDRAM_ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam SDRAM_ADDR_WIDTH = 25;
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
@@ -42,50 +45,36 @@ module tb;
integer cyc;
always @(posedge clk) if (!rst) cyc <= cyc + 1;
// ---- real SDRAM controller + model ----
// ---- real burst-memory backend ----
wire ctrl_req, ctrl_wr;
wire [SDRAM_ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata;
wire [2*BURST_LEN-1:0] ctrl_wmask;
wire [16*BURST_LEN-1:0] ctrl_rdata;
wire [32*BURST_LEN-1:0] ctrl_wdata;
wire [4*BURST_LEN-1:0] ctrl_wmask;
wire [32*BURST_LEN-1:0] ctrl_rdata;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
reg wpre_req, wpre_wr;
reg [SDRAM_ADDR_WIDTH-1:0] wpre_addr;
reg [16*BURST_LEN-1:0] wpre_wdata;
reg [32*BURST_LEN-1:0] wpre_wdata;
reg pre_active;
wire slot_ctrl_req, slot_ctrl_wr;
wire [SDRAM_ADDR_WIDTH-1:0] slot_ctrl_addr;
wire [16*BURST_LEN-1:0] slot_ctrl_wdata;
wire [2*BURST_LEN-1:0] slot_ctrl_wmask;
wire [32*BURST_LEN-1:0] slot_ctrl_wdata;
wire [4*BURST_LEN-1:0] slot_ctrl_wmask;
assign ctrl_req = pre_active ? wpre_req : slot_ctrl_req;
assign ctrl_wr = pre_active ? wpre_wr : slot_ctrl_wr;
assign ctrl_addr = pre_active ? wpre_addr : slot_ctrl_addr;
assign ctrl_wdata = pre_active ? wpre_wdata : slot_ctrl_wdata;
assign ctrl_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : slot_ctrl_wmask;
assign ctrl_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : slot_ctrl_wmask;
sdram_controller #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
burst_mem_model32 #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(SDRAM_ADDR_WIDTH)
) u_mem (
.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(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
);
function automatic signed [7:0] weight_byte(input integer li, input integer t);
@@ -95,7 +84,7 @@ module tb;
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF));
endfunction
task automatic sdram_write_burst(input [SDRAM_ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data);
task automatic sdram_write_burst(input [SDRAM_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin
@(posedge clk); while (ctrl_busy) @(posedge clk);
wpre_req = 1'b1; wpre_wr = 1'b1; wpre_addr = word_addr; wpre_wdata = data;
@@ -104,15 +93,18 @@ module tb;
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 [16*BURST_LEN-1:0] burst_data;
reg [32*BURST_LEN-1:0] burst_data;
begin
for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(2*BURST_LEN)); bi = bi + 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*(2*BURST_LEN) + wb*2;
burst_data[wb*16 +: 16] = {weight_byte(li, tt+1), weight_byte(li, tt)};
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
@@ -120,31 +112,33 @@ module tb;
end
endtask
// ---- real activation preload (EXP-0081 layout: TWO consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's
// own header). x_base(li,pos) = ACT_MEM_BASE + (li*M+pos)*
// (N_TILES/2*BURST_LEN), well clear of the weight region. ----
// ---- 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). x_base(li,pos) = ACT_MEM_BASE +
// (li*M+pos)*(N_TILES/4*BURST_LEN), well clear of the weight
// region. ----
localparam [ADDR_WIDTH-1:0] ACT_MEM_BASE = 26'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = ACT_MEM_BASE + (li*M + pos) * ((N_TILES/2)*BURST_LEN);
act_x_base = ACT_MEM_BASE + (li*M + pos) * ((N_TILES/4)*BURST_LEN);
endfunction
task automatic preload_sdram_activations;
integer li, pos, tp, k;
reg [16*BURST_LEN-1:0] burst_data;
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 (tp = 0; tp < N_TILES/2; tp = tp + 1) begin // tp = burst-pair index
burst_data = {(16*BURST_LEN){1'b0}};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)};
sdram_write_burst(base[SDRAM_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data);
for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin // tq = burst-quad index
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[SDRAM_ADDR_WIDTH-1:0] + tq*BURST_LEN, burst_data);
end
end
end
+24 -41
View File
@@ -12,10 +12,7 @@
// ============================================================
module tb;
localparam BURST_LEN = 8;
localparam ROW_BITS = 13;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS;
localparam ADDR_WIDTH = 25;
localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam NUM_REQ = 3;
@@ -26,38 +23,24 @@ module tb;
wire ctrl_req, ctrl_wr;
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;
wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN),
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_ctrl (
burst_mem_model32 #(
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
) u_mem (
.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(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
);
reg [NUM_REQ-1:0] req_active, req_req, req_wr;
wire [NUM_REQ-1:0] req_grant, req_ready, req_busy;
reg [NUM_REQ*ADDR_WIDTH-1:0] req_addr;
reg [NUM_REQ*16*BURST_LEN-1:0] req_wdata;
reg [NUM_REQ*2*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata;
reg [NUM_REQ*32*BURST_LEN-1:0] req_wdata;
reg [NUM_REQ*4*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata;
sdram_arbiter_n #(
.NUM_REQ(NUM_REQ), .ADDR_WIDTH(ADDR_WIDTH), .BURST_LEN(BURST_LEN)
@@ -80,7 +63,7 @@ module tb;
// no retry if lost.
task automatic one_shot_txn(
input integer slot, input t_wr, input [ADDR_WIDTH-1:0] t_addr,
input [16*BURST_LEN-1:0] t_wdata, output [16*BURST_LEN-1:0] t_rdata
input [32*BURST_LEN-1:0] t_wdata, output [32*BURST_LEN-1:0] t_rdata
);
begin
@(posedge clk);
@@ -88,26 +71,26 @@ module tb;
req_req[slot] = 1'b1;
req_wr[slot] = t_wr;
req_addr[slot*ADDR_WIDTH +: ADDR_WIDTH] = t_addr;
req_wdata[slot*16*BURST_LEN +: 16*BURST_LEN] = t_wdata;
req_wmask[slot*2*BURST_LEN +: 2*BURST_LEN] = {(2*BURST_LEN){1'b0}};
req_wdata[slot*32*BURST_LEN +: 32*BURST_LEN] = t_wdata;
req_wmask[slot*4*BURST_LEN +: 4*BURST_LEN] = {(4*BURST_LEN){1'b0}};
@(posedge clk);
req_req[slot] = 1'b0;
while (!req_ready[slot]) @(posedge clk);
t_rdata = req_rdata[slot*16*BURST_LEN +: 16*BURST_LEN];
t_rdata = req_rdata[slot*32*BURST_LEN +: 32*BURST_LEN];
req_active[slot] = 1'b0;
end
endtask
reg [16*BURST_LEN-1:0] got, wpat;
reg [32*BURST_LEN-1:0] got, wpat;
integer k;
task automatic check_slot(input integer slot, input [ADDR_WIDTH-1:0] a, input [15:0] pattern);
integer i;
begin
for (i = 0; i < BURST_LEN; i = i + 1)
wpat[i*16 +: 16] = pattern + i[15:0];
wpat[i*32 +: 32] = {pattern, pattern + i[15:0]};
one_shot_txn(slot, 1'b1, a, wpat, got);
one_shot_txn(slot, 1'b0, a, {(16*BURST_LEN){1'b0}}, got);
one_shot_txn(slot, 1'b0, a, {(32*BURST_LEN){1'b0}}, got);
tests = tests + 1;
if (got !== wpat) begin
$display("FAIL slot=%0d addr=%0d: got=%h expected=%h", slot, a, got, wpat);
@@ -133,19 +116,19 @@ module tb;
$display("=== TEST 2: simultaneous multi-requester ACTIVATION (the real EXP-0066 risk case) -- each requester fires its OWN one-shot req only once IT sees its OWN grant, exactly matching packed_slot.v's real S_MEMWAIT usage, not a blind simultaneous fire ===");
begin : test2
reg [16*BURST_LEN-1:0] w0, w1, w2;
reg [32*BURST_LEN-1:0] w0, w1, w2;
integer kk;
for (kk = 0; kk < BURST_LEN; kk = kk + 1) begin
w0[kk*16 +: 16] = 16'hD000 + kk[15:0];
w1[kk*16 +: 16] = 16'hE000 + kk[15:0];
w2[kk*16 +: 16] = 16'hF000 + kk[15:0];
w0[kk*32 +: 32] = {16'hD000, 16'hD000 + kk[15:0]};
w1[kk*32 +: 32] = {16'hE000, 16'hE000 + kk[15:0]};
w2[kk*32 +: 32] = {16'hF000, 16'hF000 + kk[15:0]};
end
req_addr[0*ADDR_WIDTH +: ADDR_WIDTH] = 25'd100;
req_addr[1*ADDR_WIDTH +: ADDR_WIDTH] = 25'd108;
req_addr[2*ADDR_WIDTH +: ADDR_WIDTH] = 25'd116;
req_wdata[0*16*BURST_LEN +: 16*BURST_LEN] = w0;
req_wdata[1*16*BURST_LEN +: 16*BURST_LEN] = w1;
req_wdata[2*16*BURST_LEN +: 16*BURST_LEN] = w2;
req_wdata[0*32*BURST_LEN +: 32*BURST_LEN] = w0;
req_wdata[1*32*BURST_LEN +: 32*BURST_LEN] = w1;
req_wdata[2*32*BURST_LEN +: 32*BURST_LEN] = w2;
req_wr[0] = 1'b1; req_wr[1] = 1'b1; req_wr[2] = 1'b1;
// all three raise `active` on the SAME cycle (the real