Files
FPGA-Neural/hardware/v3/rtl/host_mem_bridge.v
T
micheleandClaude Sonnet 5 9dead54ebf 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
2026-09-20 16:28:44 +02:00

153 lines
7.0 KiB
Verilog

`timescale 1ns/1ps
// ============================================================
// V3 -- host raw-memory-access bridge: the missing piece flagged
// re-auditing spi_host_bridge.v against V3's real architecture.
// spi_host_bridge.v's WRITE_MEM/READ_MEM opcodes drive a single-
// 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, 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 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.
//
// 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,
parameter ADDR_WIDTH = 25 // word address, matches sdram_arbiter_n.v's own convention
)(
input wire clk,
input wire rst,
// ---- 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 -- UNCHANGED by EXP-0084,
// see header) ----
input wire mem_req,
input wire mem_wr,
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,
output reg [15:0] mem_rdata,
output reg mem_ready,
// ---- arbiter-facing requester port (matches sdram_arbiter_n.v's
// own per-slot req_active/req_grant/req_req/req_wr/req_addr/
// req_wdata/req_wmask -> req_rdata/req_ready/req_busy naming) ----
output wire req_active,
input wire req_grant,
output reg req_req,
output reg req_wr,
output reg [ADDR_WIDTH-1:0] req_addr,
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); // 3: which of the BURST_LEN 32-bit words in the burst
localparam S_IDLE = 2'd0,
S_MEMWAIT = 2'd1,
S_XFER = 2'd2,
S_DONE = 2'd3;
reg [1:0] state;
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;
req_req <= 1'b0;
mem_ready <= 1'b0;
end else begin
req_req <= 1'b0;
mem_ready <= 1'b0;
case (state)
S_IDLE: begin
if (mem_req) begin
// 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 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).
// 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
end
S_MEMWAIT: begin
if (req_grant) begin
req_req <= 1'b1;
state <= S_XFER;
end
end
S_XFER: begin
if (req_ready) begin
if (!req_wr)
mem_rdata <= req_rdata[byte_offset*8 +: 16];
state <= S_DONE;
end
end
S_DONE: begin
mem_ready <= 1'b1;
state <= S_IDLE;
end
default: state <= S_IDLE;
endcase
end
end
endmodule