`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