Files
retroDE_ps2/rtl/gif_gs/gs_lpddr_rd_probe.sv
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

118 lines
4.8 KiB
Systemverilog

// ============================================================================
// gs_lpddr_rd_probe.sv (Ch319 Brick 3)
//
// HPS-triggered single-word AXI4 READ probe for FPGA-private LPDDR4B.
//
// Lets the HPS read framebuffer bytes back THROUGH THE HPS BRIDGE (never
// /dev/mem) for checksum + screen-dump. Drives the EMIF user port's READ
// channel (AR/R) only — the write channel (AW/W/B) is the GS tile-flush
// writer (gs_lpddr_axi_master); read and write channels are independent, so
// the two masters share the one EMIF port with no arbitration.
//
// Runs on axi_clk (= emif_clk, ~310 MHz). The control input `rd_pulse` is a
// TOGGLE in the bridge (design_clk) domain; it is 2-FF synced + edge-detected
// here. The outputs `rd_done` (toggle) + `rd_data` are produced in axi_clk;
// the bridge syncs `rd_done` and latches `rd_data` on its edge (same return-
// path CDC contract as ao486 ao486_hps_bridge ↔ lpddr4b_loader).
//
// One AXI read = one 32-byte (256-bit) beat; the requested 32-bit word is the
// lane selected by addr[4:2] (8 lanes per beat). araddr is 32-byte aligned.
// ============================================================================
module gs_lpddr_rd_probe #(
parameter ADDR_W = 30
)(
input logic axi_clk, // emif_clk
input logic axi_rst_n, // emif_reset_n (EMIF cal-ready)
// ---- control / status (rd_pulse is a design_clk-domain toggle) ----
input logic rd_pulse, // toggles when the HPS requests a read
input logic [31:0] rd_addr, // byte address (stable when rd_pulse toggles)
output logic rd_done, // toggles (axi_clk) on completion
output logic [31:0] rd_data, // 32-bit word (stable after rd_done edge)
output logic rd_busy,
// ---- AXI4 READ channel to the EMIF user port (axi_clk, 256-bit data) ----
output logic [ADDR_W-1:0] araddr,
output logic [1:0] arburst,
output logic [6:0] arid,
output logic [7:0] arlen,
output logic [2:0] arsize,
output logic arvalid,
input logic arready,
input logic [255:0] rdata,
input logic [1:0] rresp,
input logic rlast,
input logic rvalid,
output logic rready
);
// AXI read-address constants
assign arburst = 2'b01; // INCR
assign arid = 7'd1; // distinct from the writer (awid = 0)
assign arlen = 8'd0; // single beat
assign arsize = 3'b101; // 32 bytes (full 256-bit width)
// CDC: sync rd_pulse (design_clk) into axi_clk + edge-detect.
reg [2:0] pulse_sync;
wire pulse_edge = (pulse_sync[2] != pulse_sync[1]);
reg [2:0] rd_lane; // which 32-bit lane of the 256-bit beat (addr[4:2])
typedef enum logic [1:0] { S_IDLE, S_AR, S_R } st_t;
st_t st;
always_ff @(posedge axi_clk) begin
if (!axi_rst_n) begin
pulse_sync <= 3'd0;
st <= S_IDLE;
araddr <= '0;
arvalid <= 1'b0;
rready <= 1'b0;
rd_done <= 1'b0;
rd_data <= 32'd0;
rd_busy <= 1'b0;
rd_lane <= 3'd0;
end else begin
pulse_sync <= {pulse_sync[1:0], rd_pulse};
case (st)
S_IDLE: begin
if (pulse_edge) begin
rd_lane <= rd_addr[4:2];
araddr <= {rd_addr[ADDR_W-1:5], 5'd0}; // 32-byte aligned
arvalid <= 1'b1;
rd_busy <= 1'b1;
st <= S_AR;
end
end
S_AR: begin
if (arready) begin
arvalid <= 1'b0;
rready <= 1'b1;
st <= S_R;
end
end
S_R: begin
if (rvalid) begin
rready <= 1'b0;
case (rd_lane)
3'd0: rd_data <= rdata[31:0];
3'd1: rd_data <= rdata[63:32];
3'd2: rd_data <= rdata[95:64];
3'd3: rd_data <= rdata[127:96];
3'd4: rd_data <= rdata[159:128];
3'd5: rd_data <= rdata[191:160];
3'd6: rd_data <= rdata[223:192];
default: rd_data <= rdata[255:224];
endcase
rd_busy <= 1'b0;
rd_done <= ~rd_done;
st <= S_IDLE;
end
end
default: st <= S_IDLE;
endcase
end
end
endmodule