// ============================================================================ // gs_lpddr_rd_arb.sv (Ch320 Brick 2; Ch322 extended 2:1 -> 3:1) // // 3:1 AXI4 READ-channel arbiter for the FPGA-private LPDDR4B EMIF user port. // Lets the Ch320 scanout reader (port 0, priority), the Ch319 HPS read-probe // (port 1), and the Ch322 texture-cache fill (port 2, lowest priority) share the // single EMIF read channel. The write channel is arbitrated separately // (gs_lpddr_wr_arb). Adapted from ao486 axi_fb_arbiter (read half): grant held // for a whole transaction, watchdog force-release, idle-drain rready so a late // response can't wedge the bus. All single-clock (emif_clk). // // Port 2 (texture fill) is a ONE-SHOT prefill before raster; scanout (port 0) // keeps priority. Leave s2_* unconnected (arvalid=0) on builds without a texture // cache — the arbiter is then bit-for-bit the old 2:1 behavior. // // Single-beat transactions (ARLEN=0), so a response completes on rvalid&rlast. // ============================================================================ `timescale 1ns/1ps module gs_lpddr_rd_arb ( input logic clk, input logic rst_n, // ---- Port 0: scanout reader (priority) ---- input logic [29:0] s0_araddr, input logic [1:0] s0_arburst, input logic [6:0] s0_arid, input logic [7:0] s0_arlen, input logic [2:0] s0_arsize, input logic s0_arvalid, output logic s0_arready, output logic [255:0] s0_rdata, output logic [1:0] s0_rresp, output logic s0_rlast, output logic s0_rvalid, input logic s0_rready, // ---- Port 1: HPS read-probe ---- input logic [29:0] s1_araddr, input logic [1:0] s1_arburst, input logic [6:0] s1_arid, input logic [7:0] s1_arlen, input logic [2:0] s1_arsize, input logic s1_arvalid, output logic s1_arready, output logic [255:0] s1_rdata, output logic [1:0] s1_rresp, output logic s1_rlast, output logic s1_rvalid, input logic s1_rready, // ---- Port 2: texture-cache fill (lowest priority; Ch322) ---- input logic [29:0] s2_araddr, input logic [1:0] s2_arburst, input logic [6:0] s2_arid, input logic [7:0] s2_arlen, input logic [2:0] s2_arsize, input logic s2_arvalid, output logic s2_arready, output logic [255:0] s2_rdata, output logic [1:0] s2_rresp, output logic s2_rlast, output logic s2_rvalid, input logic s2_rready, // ---- Port 3: tile-reload fill (Ch323; priority ABOVE probe/texfill, below scanout) ---- input logic [29:0] s3_araddr, input logic [1:0] s3_arburst, input logic [6:0] s3_arid, input logic [7:0] s3_arlen, input logic [2:0] s3_arsize, input logic s3_arvalid, output logic s3_arready, output logic [255:0] s3_rdata, output logic [1:0] s3_rresp, output logic s3_rlast, output logic s3_rvalid, input logic s3_rready, // ---- Master out: EMIF read channel ---- output logic [29:0] m_araddr, output logic [1:0] m_arburst, output logic [6:0] m_arid, output logic [7:0] m_arlen, output logic [2:0] m_arsize, output logic m_arvalid, input logic m_arready, input logic [255:0] m_rdata, input logic [1:0] m_rresp, input logic m_rlast, input logic m_rvalid, output logic m_rready ); // grant: 0=idle, 1=s0 scanout, 2=s1 probe, 3=s2 texfill, 4=s3 tile-reload. // EXPLICIT priority (Ch323, Codex): scanout > tile_reload > probe > texture_fill — i.e. // s0 > s3 > s1 > s2. Render-display (scanout) highest; the render-prep tile reload above // the debug read-probe so a debug read can never starve a render's Z/color reload. reg [2:0] grant; // Ch326 — NON-ABORTING ARBITER (Codex). The OLD design force-released the grant on a // watchdog (was 2^10 ~3.3us) at ANY point in the transaction; when it fired AFTER the AR had // handshaked, the idle state's m_rready=1 drained the now-orphaned response and the requester // hung forever (blank HDMI + stuck HPS probe under the always-on-scanout traffic). Once // m_arvalid && m_arready, the read is COMMITTED and its response BELONGS to that requester — // there is no AXI-legal way to abandon it. So: the watchdog gates ONLY the pre-AR wait (no // transaction committed yet — safe to drop); after AR acceptance the grant is held until // m_rvalid && m_rlast && selected_rready, regardless of how long the read takes. reg ar_done; // AR handshake captured for the active grant -> never abort past here reg [21:0] watchdog; // pre-AR only (waiting for m_arready); ~6.7 ms @ 310 MHz dead-bus backstop wire wd_expired = watchdog[21]; wire sel_rready = (grant==3'd1)?s0_rready:(grant==3'd2)?s1_rready: (grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:1'b1; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0; end else if (grant == 3'd0) begin ar_done <= 1'b0; watchdog <= '0; if (s0_arvalid) grant <= 3'd1; // scanout (highest) else if (s3_arvalid) grant <= 3'd4; // tile reload (render-prep) else if (s1_arvalid) grant <= 3'd2; // read probe (debug) else if (s2_arvalid) grant <= 3'd3; // texture fill (lowest) end else begin if (m_arvalid && m_arready) ar_done <= 1'b1; // AR accepted -> COMMITTED if (m_rvalid && m_rlast && sel_rready) begin grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0; // response delivered -> release end else if (!ar_done) begin // still waiting for AR (nothing owed) if (wd_expired) begin grant <= 3'd0; ar_done <= 1'b0; watchdog <= '0; end else watchdog <= watchdog + 22'd1; end // ar_done && response not yet complete: HOLD the grant, never abort. end end // AR mux assign m_araddr = (grant==3'd4)?s3_araddr :(grant==3'd3)?s2_araddr :(grant==3'd2)?s1_araddr :s0_araddr; assign m_arburst = (grant==3'd4)?s3_arburst:(grant==3'd3)?s2_arburst:(grant==3'd2)?s1_arburst:s0_arburst; assign m_arid = (grant==3'd4)?s3_arid :(grant==3'd3)?s2_arid :(grant==3'd2)?s1_arid :s0_arid; assign m_arlen = (grant==3'd4)?s3_arlen :(grant==3'd3)?s2_arlen :(grant==3'd2)?s1_arlen :s0_arlen; assign m_arsize = (grant==3'd4)?s3_arsize :(grant==3'd3)?s2_arsize :(grant==3'd2)?s1_arsize :s0_arsize; assign m_arvalid = (grant==3'd1)?s0_arvalid:(grant==3'd2)?s1_arvalid:(grant==3'd3)?s2_arvalid:(grant==3'd4)?s3_arvalid:1'b0; assign s0_arready = (grant==3'd1)?m_arready:1'b0; assign s1_arready = (grant==3'd2)?m_arready:1'b0; assign s2_arready = (grant==3'd3)?m_arready:1'b0; assign s3_arready = (grant==3'd4)?m_arready:1'b0; // R demux (idle: rready=1 drains any stale/late response) assign s0_rdata=m_rdata; assign s1_rdata=m_rdata; assign s2_rdata=m_rdata; assign s3_rdata=m_rdata; assign s0_rresp=m_rresp; assign s1_rresp=m_rresp; assign s2_rresp=m_rresp; assign s3_rresp=m_rresp; assign s0_rlast=m_rlast; assign s1_rlast=m_rlast; assign s2_rlast=m_rlast; assign s3_rlast=m_rlast; assign s0_rvalid = (grant==3'd1)?m_rvalid:1'b0; assign s1_rvalid = (grant==3'd2)?m_rvalid:1'b0; assign s2_rvalid = (grant==3'd3)?m_rvalid:1'b0; assign s3_rvalid = (grant==3'd4)?m_rvalid:1'b0; assign m_rready = (grant==3'd1)?s0_rready:(grant==3'd2)?s1_rready:(grant==3'd3)?s2_rready:(grant==3'd4)?s3_rready:1'b1; endmodule