// ============================================================================ // 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