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

    // ---- Port 4: destination-color read for SH3 alpha ROP ----
    input  logic [29:0] s4_araddr,
    input  logic [1:0]  s4_arburst,
    input  logic [6:0]  s4_arid,
    input  logic [7:0]  s4_arlen,
    input  logic [2:0]  s4_arsize,
    input  logic        s4_arvalid,
    output logic        s4_arready,
    output logic [255:0] s4_rdata,
    output logic [1:0]  s4_rresp,
    output logic        s4_rlast,
    output logic        s4_rvalid,
    input  logic        s4_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 reload/Z, 5=s4 alpha destination.
    // 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
    // Register the terminal R handshake before releasing the grant.  Besides
    // making the ownership boundary explicit, this removes the selected
    // requester's rready mux from the encoded grant register's D cone.  The
    // old direct clear path was the secondary 310 MHz setup family after the
    // wide request-FIFO RAM-enable fanout.
    reg        response_done_q;
    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:(grant==3'd5)?s4_rready:1'b1;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0;
        end else if (grant == 3'd0) begin
            ar_done <= 1'b0; response_done_q <= 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 (s4_arvalid) grant <= 3'd5;   // alpha destination RMW
            else if (s1_arvalid) grant <= 3'd2;   // read probe (debug)
            else if (s2_arvalid) grant <= 3'd3;   // texture fill (lowest)
        end else if (response_done_q) begin
            // The selected requester accepted RLAST on the preceding cycle.
            // Hold ownership through that handshake, then release here.
            grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 1'b0; watchdog <= '0;
        end else begin
            if (m_arvalid && m_arready) ar_done <= 1'b1;            // AR accepted -> COMMITTED
            if (m_rvalid && m_rlast && sel_rready) begin
                response_done_q <= 1'b1; watchdog <= '0;           // response delivered; release next cycle
            end else if (!ar_done) begin                            // still waiting for AR (nothing owed)
                if (wd_expired) begin grant <= 3'd0; ar_done <= 1'b0; response_done_q <= 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'd5)?s4_araddr :(grant==3'd4)?s3_araddr :(grant==3'd3)?s2_araddr :(grant==3'd2)?s1_araddr :s0_araddr;
    assign m_arburst = (grant==3'd5)?s4_arburst:(grant==3'd4)?s3_arburst:(grant==3'd3)?s2_arburst:(grant==3'd2)?s1_arburst:s0_arburst;
    assign m_arid    = (grant==3'd5)?s4_arid   :(grant==3'd4)?s3_arid   :(grant==3'd3)?s2_arid   :(grant==3'd2)?s1_arid   :s0_arid;
    assign m_arlen   = (grant==3'd5)?s4_arlen  :(grant==3'd4)?s3_arlen  :(grant==3'd3)?s2_arlen  :(grant==3'd2)?s1_arlen  :s0_arlen;
    assign m_arsize  = (grant==3'd5)?s4_arsize :(grant==3'd4)?s3_arsize :(grant==3'd3)?s2_arsize :(grant==3'd2)?s1_arsize :s0_arsize;
    // Once an address is accepted, do not expose another address from the
    // selected requester while its response (or registered release) is active.
    // This also makes the deliberate one-cycle release bubble AXI-safe.
    wire ar_open = !ar_done && !response_done_q;
    assign m_arvalid = ar_open && ((grant==3'd1)?s0_arvalid:(grant==3'd2)?s1_arvalid:(grant==3'd3)?s2_arvalid:(grant==3'd4)?s3_arvalid:(grant==3'd5)?s4_arvalid:1'b0);
    assign s0_arready = (ar_open && grant==3'd1)?m_arready:1'b0;
    assign s1_arready = (ar_open && grant==3'd2)?m_arready:1'b0;
    assign s2_arready = (ar_open && grant==3'd3)?m_arready:1'b0;
    assign s3_arready = (ar_open && grant==3'd4)?m_arready:1'b0;
    assign s4_arready = (ar_open && grant==3'd5)?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 s4_rdata=m_rdata;
    assign s0_rresp=m_rresp; assign s1_rresp=m_rresp; assign s2_rresp=m_rresp; assign s3_rresp=m_rresp; assign s4_rresp=m_rresp;
    assign s0_rlast=m_rlast; assign s1_rlast=m_rlast; assign s2_rlast=m_rlast; assign s3_rlast=m_rlast; assign s4_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 s4_rvalid = (grant==3'd5)?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:(grant==3'd5)?s4_rready:1'b1;
endmodule
