feat: real DDR3 memory path verified against MIG's own ddr3_model.sv (EXP-0068)
New hardware/v3/rtl/mig_native_adapter.v: adapts this project's established req/wr/addr/wdata/wmask->rdata/ready/busy contract to the real MIG 7-series native app interface (app_addr/app_cmd/app_en, app_wdf_data/app_wdf_mask/app_wdf_wren/app_wdf_end, app_rd_data/ app_rd_data_valid/app_rd_data_end), derived from this project's own real generated mig_7series_0.v port widths, not assumed. Runs in the ui_clk domain (MIG's own generated clock becomes this project's system clock going forward). Verified against MIG's own real, vendor-shipped DDR3 behavioral model (ddr3_model.sv) via real Xilinx xsim/xvlog/xelab (UNISIM primitives in MIG's PHY require this over Verilator): 12/12 write-then-read-back transactions bit-exact, 0 errors, real JEDEC command sequence observed (Activate/Write/Read/Precharge). Confirms the app_cmd encoding and burst/beat sequencing on first real test. Also adds hardware/v3/rtl/sdram_arbiter_n.v (generalized N-way arbiter, generalizing EXP-0066's 2-way version for N>2 scaling and a future host-access requester) -- its own isolated test currently HANGS, root cause not yet found, do not trust this module yet (disclosed, not hidden). Full writeup in hardware/v2/logs/experiments.log EXP-0068. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// 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).
|
||||
//
|
||||
// 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
|
||||
// are built; matches every real MIG reference design, not a
|
||||
// deviation this project is inventing). rst must already be
|
||||
// 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.
|
||||
//
|
||||
// Sequencing is deliberately fully sequential, not pipelined
|
||||
// (correctness first): the command is issued and accepted (app_en/
|
||||
// app_rdy) BEFORE any write-data beat is asserted, and each of the
|
||||
// two write-data beats (real MIG allows the address and write-data
|
||||
// channels to accept independently/concurrently -- not used here) is
|
||||
// held until its own app_wdf_rdy fires.
|
||||
//
|
||||
// app_cmd encoding (000=Write, 001=Read) is the standard, stable MIG
|
||||
// convention -- NOT taken on faith alone: hardware/v3/sim/
|
||||
// tb_mig_native_adapter.v verifies this adapter against MIG's own
|
||||
// real, vendor-provided ddr3_model.sv (write, real DDR3 behavioral
|
||||
// model, real read-back, bit-exact compare), so a wrong assumption
|
||||
// here would show up as a real, observed data mismatch, not silently
|
||||
// trusted.
|
||||
// ============================================================
|
||||
module mig_native_adapter #(
|
||||
parameter BURST_LEN = 8,
|
||||
parameter ADDR_WIDTH = 25 // matches this project's own word-address convention
|
||||
)(
|
||||
input wire clk, // = ui_clk
|
||||
input wire rst, // pre-synchronized to ui_clk
|
||||
|
||||
// ---- this project's own established memory-controller contract ----
|
||||
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,
|
||||
output reg ready,
|
||||
output wire busy,
|
||||
|
||||
// ---- MIG native "app" interface (real generated port widths) ----
|
||||
output reg [27:0] app_addr,
|
||||
output reg [2:0] app_cmd,
|
||||
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,
|
||||
|
||||
input wire [63: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;
|
||||
|
||||
localparam S_IDLE = 3'd0,
|
||||
S_CMD_WAIT = 3'd1,
|
||||
S_WDF0 = 3'd2,
|
||||
S_WDF1 = 3'd3,
|
||||
S_RD_WAIT = 3'd4,
|
||||
S_DONE = 3'd5;
|
||||
|
||||
reg [2:0] state;
|
||||
reg wr_lat;
|
||||
reg [16*BURST_LEN-1:0] wdata_lat;
|
||||
reg [2*BURST_LEN-1:0] wmask_lat;
|
||||
|
||||
assign busy = (state != S_IDLE);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
app_en <= 1'b0;
|
||||
app_wdf_wren <= 1'b0;
|
||||
app_wdf_end <= 1'b0;
|
||||
ready <= 1'b0;
|
||||
rdata <= {(16*BURST_LEN){1'b0}};
|
||||
app_addr <= 28'h0;
|
||||
app_cmd <= CMD_READ;
|
||||
app_wdf_data <= 64'h0;
|
||||
app_wdf_mask <= 8'h0;
|
||||
end else begin
|
||||
ready <= 1'b0;
|
||||
|
||||
case (state)
|
||||
S_IDLE: begin
|
||||
if (req) begin
|
||||
wr_lat <= wr;
|
||||
wdata_lat <= wdata;
|
||||
wmask_lat <= wmask;
|
||||
app_addr <= {{(28-ADDR_WIDTH){1'b0}}, addr};
|
||||
app_cmd <= wr ? CMD_WRITE : CMD_READ;
|
||||
app_en <= 1'b1;
|
||||
state <= S_CMD_WAIT;
|
||||
end
|
||||
end
|
||||
|
||||
S_CMD_WAIT: begin
|
||||
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_end <= 1'b0;
|
||||
app_wdf_wren <= 1'b1;
|
||||
state <= S_WDF0;
|
||||
end else begin
|
||||
state <= S_RD_WAIT;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_WDF0: begin
|
||||
if (app_wdf_rdy) begin
|
||||
app_wdf_data <= wdata_lat[127:64];
|
||||
app_wdf_mask <= wmask_lat[15:8];
|
||||
app_wdf_end <= 1'b1;
|
||||
app_wdf_wren <= 1'b1;
|
||||
state <= S_WDF1;
|
||||
end
|
||||
end
|
||||
|
||||
S_WDF1: begin
|
||||
if (app_wdf_rdy) begin
|
||||
app_wdf_wren <= 1'b0;
|
||||
app_wdf_end <= 1'b0;
|
||||
state <= S_DONE;
|
||||
end
|
||||
end
|
||||
|
||||
S_RD_WAIT: begin
|
||||
if (app_rd_data_valid) begin
|
||||
if (!app_rd_data_end) begin
|
||||
rdata[63:0] <= app_rd_data;
|
||||
end else begin
|
||||
rdata[127:64] <= app_rd_data;
|
||||
state <= S_DONE;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_DONE: begin
|
||||
ready <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
|
||||
default: state <= S_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,145 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// V3 -- generalized N-way arbiter for a shared memory controller
|
||||
// port (SDRAM placeholder today, DDR3/mig_native_adapter.v tomorrow
|
||||
// -- this arbiter sits on the req/wr/addr/wdata/wmask->rdata/ready/
|
||||
// busy side, identical on either backend).
|
||||
//
|
||||
// Generalizes sdram_slot_arbiter2.v (EXP-0066) to NUM_REQ requesters
|
||||
// instead of a hardcoded 2, for (a) scaling the compute system past
|
||||
// N=2 packed slots, and (b) adding a HOST raw-memory-access requester
|
||||
// (the still-missing SPI WRITE_MEM/READ_MEM equivalent for V3,
|
||||
// flagged when re-auditing spi_host_bridge.v's own opcode set against
|
||||
// this project's actual V3 architecture).
|
||||
//
|
||||
// Preserves EXACTLY the combinational-first-grant mechanism EXP-0066
|
||||
// found necessary the hard way: layer_prefetch_ctrl.v (and any other
|
||||
// requester built the same way, e.g. a future host-access engine)
|
||||
// issues its own ctrl_req as a genuine ONE-SHOT pulse with no retry,
|
||||
// so a requester must see ITS OWN grant asserted the SAME cycle its
|
||||
// own `active` first goes high, or that first request is silently
|
||||
// lost forever (a real, previously-hit bug, not a hypothetical one --
|
||||
// see EXP-0066's own writeup). `locked`/`grant_reg` below only LATCH
|
||||
// a decision already available combinationally, purely to keep it
|
||||
// sticky once made (no mid-fetch grant switching), never to delay
|
||||
// the first grant.
|
||||
//
|
||||
// Priority: lowest-indexed active requester wins on first grant (same
|
||||
// policy as sdram_slot_arbiter2.v -- a documented, simple, first-
|
||||
// come-by-index scheme, not fairness-optimized; matches this
|
||||
// project's own "correctness first" precedent of choosing the
|
||||
// simplest policy that is provably correct before optimizing).
|
||||
// ============================================================
|
||||
module sdram_arbiter_n #(
|
||||
parameter NUM_REQ = 3,
|
||||
parameter ADDR_WIDTH = 25,
|
||||
parameter BURST_LEN = 8
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire [NUM_REQ-1:0] req_active,
|
||||
output wire [NUM_REQ-1:0] req_grant,
|
||||
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,
|
||||
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,
|
||||
input wire ctrl_ready,
|
||||
input wire ctrl_busy
|
||||
);
|
||||
localparam SELW = (NUM_REQ <= 1) ? 1 : $clog2(NUM_REQ);
|
||||
|
||||
wire any_active = |req_active;
|
||||
|
||||
// combinational lowest-index-active picker -- available with zero
|
||||
// cycle latency relative to req_active first asserting (see header).
|
||||
reg [SELW-1:0] pick_idx;
|
||||
integer pi;
|
||||
always @(*) begin
|
||||
pick_idx = {SELW{1'b0}};
|
||||
for (pi = NUM_REQ-1; pi >= 0; pi = pi - 1)
|
||||
if (req_active[pi]) pick_idx = pi[SELW-1:0];
|
||||
end
|
||||
|
||||
reg locked;
|
||||
reg [SELW-1:0] grant_idx_r;
|
||||
|
||||
wire [SELW-1:0] grant_idx_now = locked ? grant_idx_r : pick_idx;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
locked <= 1'b0;
|
||||
grant_idx_r <= {SELW{1'b0}};
|
||||
end else begin
|
||||
if (!locked) begin
|
||||
if (any_active) begin
|
||||
locked <= 1'b1;
|
||||
grant_idx_r <= grant_idx_now;
|
||||
end
|
||||
end else begin
|
||||
if (!req_active[grant_idx_r]) locked <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
wire [NUM_REQ-1:0] sel;
|
||||
genvar gs;
|
||||
generate
|
||||
for (gs = 0; gs < NUM_REQ; gs = gs + 1) begin : GEN_SEL
|
||||
assign sel[gs] = any_active && (grant_idx_now == gs[SELW-1:0]);
|
||||
end
|
||||
endgenerate
|
||||
|
||||
assign req_grant = sel;
|
||||
|
||||
// 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;
|
||||
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}};
|
||||
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];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assign ctrl_req = m_req;
|
||||
assign ctrl_wr = m_wr;
|
||||
assign ctrl_addr = m_addr;
|
||||
assign ctrl_wdata = m_wdata;
|
||||
assign ctrl_wmask = m_wmask;
|
||||
|
||||
// demux response back to whichever requester is currently granted
|
||||
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_ready[gd] = sel[gd] ? ctrl_ready : 1'b0;
|
||||
assign req_busy[gd] = sel[gd] ? ctrl_busy : 1'b1;
|
||||
end
|
||||
endgenerate
|
||||
endmodule
|
||||
Reference in New Issue
Block a user