// ============================================================================ // gs_lpddr_wr_probe.sv (Ch322 Brick 3) // // HPS-bridge-driven LPDDR4B WRITE probe — the missing PS2-side LPDDR loader, // cloned from retroDE_ao486/rtl/ao486/lpddr4b_loader.sv (write half). The PS2 // core already has the READ half (gs_lpddr_rd_probe); this is its symmetric // twin so the HPS can STAGE arbitrary words into FPGA-private LPDDR4B from Linux // (e.g. a known texture for Ch322), then read them back / hash via the existing // read-probe before the texture cache fills from them. // // This is HPS -> bridge registers -> FPGA EMIF write. NOT HPS direct memory // access, and NOT the retired f2sdram path. The EMIF write channel is shared // with the GS framebuffer writer through gs_lpddr_wr_arb (FB writer = priority, // this probe writes only when the writer is idle). // // Runs on emif_clk. The bridge pulse/addr/data come from CLOCK2_50 and are // toggle-synchronized internally (same CDC as lpddr4b_loader / gs_lpddr_rd_probe). // // Each wr_pulse triggers ONE single-beat 32-bit write: the 32-bit lane within // the 256-bit EMIF word is selected by addr[4:2] with the matching WSTRB nibble. // ============================================================================ `timescale 1ns/1ps module gs_lpddr_wr_probe ( input logic emif_clk, input logic emif_rst_n, // ---- control (from HPS bridge, CLOCK2_50 domain) ---- input logic wr_pulse, // toggles when the HPS writes a data word input logic [29:0] wr_addr, // EMIF byte address (stable when pulse flips) input logic [31:0] wr_data, // data word (stable when pulse flips) input logic full_beat, // Ch323 diag: write ALL 8 lanes (wstrb=0xFFFFFFFF) — tests // full-width commit through THIS arbiter/profile path // ---- status (emif_clk domain; bridge syncs) ---- output logic busy, output logic done_toggle, // toggles on each completed write output logic [31:0] bresp_errs, // count of non-OKAY write responses // ---- AXI4 write channel to the EMIF user port (emif_clk, 256-bit) ---- output logic [29:0] awaddr, output logic [1:0] awburst, output logic [6:0] awid, output logic [7:0] awlen, output logic [2:0] awsize, output logic awvalid, input logic awready, output logic [255:0] wdata, output logic [31:0] wstrb, output logic wlast, output logic wvalid, input logic wready, input logic [1:0] bresp, input logic bvalid, output logic bready ); assign awburst = 2'b01; // INCR assign awid = 7'd5; // distinct id: fb-writer/probe ids elsewhere; 5 = wr-probe assign awlen = 8'd0; // single beat assign awsize = 3'b101; // 32 bytes (full 256-bit bus) assign bready = 1'b1; // CDC: toggle sync CLOCK2_50 -> emif_clk (same as lpddr4b_loader) reg [2:0] wr_sync; wire wr_edge = (wr_sync[2] != wr_sync[1]); reg [29:0] lat_addr; reg [31:0] lat_wdata; typedef enum logic [1:0] { S_IDLE, S_AW, S_W, S_B } state_t; state_t state; always_ff @(posedge emif_clk or negedge emif_rst_n) begin if (!emif_rst_n) begin wr_sync <= 3'd0; lat_addr <= 30'd0; lat_wdata <= 32'd0; state <= S_IDLE; awaddr <= 30'd0; awvalid <= 1'b0; wdata <= 256'd0; wstrb <= 32'd0; wlast <= 1'b0; wvalid <= 1'b0; busy <= 1'b0; done_toggle <= 1'b0; bresp_errs <= 32'd0; end else begin wr_sync <= {wr_sync[1:0], wr_pulse}; case (state) S_IDLE: begin busy <= 1'b0; if (wr_edge) begin lat_addr <= wr_addr; lat_wdata <= wr_data; busy <= 1'b1; awaddr <= {wr_addr[29:5], 5'd0}; // 32-byte aligned beat awvalid <= 1'b1; state <= S_AW; end end S_AW: begin if (awready) begin awvalid <= 1'b0; wdata <= 256'd0; wstrb <= 32'd0; if (full_beat) begin // diag: replicate the word across all 8 lanes, full WSTRB. wdata <= {8{lat_wdata}}; wstrb <= 32'hFFFF_FFFF; end else case (lat_addr[4:2]) // place the 32-bit lane + its WSTRB nibble 3'd0: begin wdata[ 31: 0] <= lat_wdata; wstrb[ 3: 0] <= 4'hF; end 3'd1: begin wdata[ 63: 32] <= lat_wdata; wstrb[ 7: 4] <= 4'hF; end 3'd2: begin wdata[ 95: 64] <= lat_wdata; wstrb[11: 8] <= 4'hF; end 3'd3: begin wdata[127: 96] <= lat_wdata; wstrb[15:12] <= 4'hF; end 3'd4: begin wdata[159:128] <= lat_wdata; wstrb[19:16] <= 4'hF; end 3'd5: begin wdata[191:160] <= lat_wdata; wstrb[23:20] <= 4'hF; end 3'd6: begin wdata[223:192] <= lat_wdata; wstrb[27:24] <= 4'hF; end 3'd7: begin wdata[255:224] <= lat_wdata; wstrb[31:28] <= 4'hF; end endcase wlast <= 1'b1; wvalid <= 1'b1; state <= S_W; end end S_W: begin if (wready) begin wvalid <= 1'b0; wlast <= 1'b0; state <= S_B; end end S_B: begin if (bvalid) begin if (bresp != 2'b00) bresp_errs <= bresp_errs + 32'd1; busy <= 1'b0; done_toggle <= ~done_toggle; state <= S_IDLE; end end default: state <= S_IDLE; endcase end end endmodule