Files
FPGA-Neural/hardware/v3/rtl/host_mem_bridge.v
T
micheleandClaude Sonnet 5 786464ee21 feat: host_mem_bridge.v, word<->burst translator for host DDR3 access (EXP-0071)
Closes part of the gap found re-auditing spi_host_bridge.v against V3:
V3 had no host raw-memory-access path into DDR3 at all. This module
translates single-16-bit-word req/wr/addr/wdata/lb_n/ub_n transactions
(spi_host_bridge.v's own WRITE_MEM/READ_MEM shape) into BURST_LEN=8
transactions on the shared arbiter, using the project's existing
DQM-style partial-burst masking technique.

Verified standalone against the SDR SDRAM placeholder: 16/16 tests,
0 errors, including cross-word-corruption checks on every burst
offset. Not yet wired into the N=2 system or driven by real SPI
opcode decode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-17 08:53:23 +02:00

124 lines
5.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 (128-bit)
// chunks. 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.
//
// 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).
// ============================================================
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) ----
input wire mem_req,
input wire mem_wr,
input wire [ADDR_WIDTH-1:0] mem_addr, // 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 [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,
input wire req_ready,
input wire req_busy
);
localparam ALIGN_BITS = $clog2(BURST_LEN);
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;
assign req_active = (state == S_MEMWAIT) || (state == S_XFER);
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
req_addr <= {mem_addr[ADDR_WIDTH-1:ALIGN_BITS], {ALIGN_BITS{1'b0}}};
word_in_block <= mem_addr[ALIGN_BITS-1: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
// 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));
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[word_in_block*16 +: 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