// ============================================================================
// gs_lpddr_wr_arb.sv  (Ch322 Brick 3; Ch323 extended 2:1 -> 3:1)
//
// 3:1 AXI4 WRITE-channel arbiter for the FPGA-private LPDDR4B EMIF user port.
// The write twin of gs_lpddr_rd_arb. Lets the GS framebuffer writer
// (gs_lpddr_axi_master, port 0, PRIORITY), the Ch323 tile Z-flush writer
// (gs_z_flush_writer, port 2) and the Ch322 HPS write-probe
// (gs_lpddr_wr_probe, port 1) share the single EMIF write channel.
//
// EXPLICIT priority (Ch323, Codex): FB-writer > Z-writer > wr-probe — i.e.
// s0 > s2 > s1. The active render's color (FB) and Z spill outrank the debug
// write-probe so a debug write can never starve a render flush. Leave s2_*
// unconnected (awvalid=0) on builds without a Z writer — the arbiter is then
// bit-for-bit the old 2:1 behavior.
//
// Per-transaction grant held AW->W->B (single-beat writes, AWLEN=0, so B
// completes one transaction). Watchdog force-release guards a lost B.
// All single-clock (emif_clk).
// ============================================================================
`timescale 1ns/1ps

module gs_lpddr_wr_arb (
    input  logic        clk,
    input  logic        rst_n,

    // ---- Port 0: GS framebuffer writer (priority) ----
    input  logic [29:0] s0_awaddr,
    input  logic [1:0]  s0_awburst,
    input  logic [6:0]  s0_awid,
    input  logic [7:0]  s0_awlen,
    input  logic [2:0]  s0_awsize,
    input  logic        s0_awvalid,
    output logic        s0_awready,
    input  logic [255:0] s0_wdata,
    input  logic [31:0] s0_wstrb,
    input  logic        s0_wlast,
    input  logic        s0_wvalid,
    output logic        s0_wready,
    output logic [1:0]  s0_bresp,
    output logic        s0_bvalid,
    input  logic        s0_bready,

    // ---- Port 1: HPS write-probe ----
    input  logic [29:0] s1_awaddr,
    input  logic [1:0]  s1_awburst,
    input  logic [6:0]  s1_awid,
    input  logic [7:0]  s1_awlen,
    input  logic [2:0]  s1_awsize,
    input  logic        s1_awvalid,
    output logic        s1_awready,
    input  logic [255:0] s1_wdata,
    input  logic [31:0] s1_wstrb,
    input  logic        s1_wlast,
    input  logic        s1_wvalid,
    output logic        s1_wready,
    output logic [1:0]  s1_bresp,
    output logic        s1_bvalid,
    input  logic        s1_bready,

    // ---- Port 2: tile Z-flush writer (Ch323; priority ABOVE probe, below FB writer) ----
    input  logic [29:0] s2_awaddr,
    input  logic [1:0]  s2_awburst,
    input  logic [6:0]  s2_awid,
    input  logic [7:0]  s2_awlen,
    input  logic [2:0]  s2_awsize,
    input  logic        s2_awvalid,
    output logic        s2_awready,
    input  logic [255:0] s2_wdata,
    input  logic [31:0] s2_wstrb,
    input  logic        s2_wlast,
    input  logic        s2_wvalid,
    output logic        s2_wready,
    output logic [1:0]  s2_bresp,
    output logic        s2_bvalid,
    input  logic        s2_bready,

    // ---- Port 3: HPS write-probe (Ch323 diag; LOWEST priority — debug staging) ----
    input  logic [29:0] s3_awaddr,
    input  logic [1:0]  s3_awburst,
    input  logic [6:0]  s3_awid,
    input  logic [7:0]  s3_awlen,
    input  logic [2:0]  s3_awsize,
    input  logic        s3_awvalid,
    output logic        s3_awready,
    input  logic [255:0] s3_wdata,
    input  logic [31:0] s3_wstrb,
    input  logic        s3_wlast,
    input  logic        s3_wvalid,
    output logic        s3_wready,
    output logic [1:0]  s3_bresp,
    output logic        s3_bvalid,
    input  logic        s3_bready,

    // ---- Master out: EMIF write channel ----
    output logic [29:0] m_awaddr,
    output logic [1:0]  m_awburst,
    output logic [6:0]  m_awid,
    output logic [7:0]  m_awlen,
    output logic [2:0]  m_awsize,
    output logic        m_awvalid,
    input  logic        m_awready,
    output logic [255:0] m_wdata,
    output logic [31:0] m_wstrb,
    output logic        m_wlast,
    output logic        m_wvalid,
    input  logic        m_wready,
    input  logic [1:0]  m_bresp,
    input  logic        m_bvalid,
    output logic        m_bready
);
    // grant: 0=idle, 1=s0 FB writer, 2=s1 color spill, 3=s2 Z spill, 4=s3 HPS write-probe.
    // EXPLICIT priority: FB-writer > Z-spill > color-spill > wr-probe — i.e. s0 > s2 > s1 > s3.
    reg [2:0]  grant;
    // Ch326 — NON-ABORTING ARBITER (Codex), same protocol fix as gs_lpddr_rd_arb. Once
    // m_awvalid && m_awready, the write is COMMITTED (the slave will return B); abandoning it on
    // a watchdog would orphan the B / leave the slave mid-write. So the watchdog gates ONLY the
    // pre-AW wait; after AW acceptance the grant is held until m_bvalid && selected_bready. (The
    // FB/spill writers never tripped the old 2^10 watchdog in practice, but the latent bug is the
    // same — fixed for safety.)
    // "committed" = EITHER the AW or a W beat has handshaked. The current writers send AW-then-W
    // so AW sets it first, but tracking either makes this a GENERAL AXI write arbiter that never
    // abandons a transaction regardless of AW/W ordering (Codex audit note).
    reg        aw_done;        // a write beat/addr accepted for the active grant -> never abort past here
    // Ch368 -- arm EMIF BREADY from the final W handshake, rather than feeding it through the
    // live grant/client-ready mux. All current writers enter their B-wait state on that same
    // handshake (or hold BREADY high), so this preserves the AW->W->B contract while cutting
    // the grant/bready -> EMIF response-FIFO timing cone.
    reg        bready_q;
    reg [21:0] watchdog;       // pre-commit only; ~6.7 ms @ 310 MHz dead-bus backstop
    wire       wd_expired = watchdog[21];

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            grant <= 3'd0; aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0;
        end else if (grant == 3'd0) begin
            aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0;
            if      (s0_awvalid) grant <= 3'd1;   // FB writer (highest)
            else if (s2_awvalid) grant <= 3'd3;   // Z spill (render-flush)
            else if (s1_awvalid) grant <= 3'd2;   // color spill (render-flush)
            else if (s3_awvalid) grant <= 3'd4;   // HPS write-probe (debug, lowest)
        end else begin
            if ((m_awvalid && m_awready) || (m_wvalid && m_wready)) aw_done <= 1'b1;  // AW or W accepted -> COMMITTED
            if (m_wvalid && m_wready && m_wlast) bready_q <= 1'b1;
            if (m_bvalid && bready_q) begin
                grant <= 3'd0; aw_done <= 1'b0; bready_q <= 1'b0; watchdog <= '0;     // B delivered -> release
            end else if (!aw_done) begin                            // still waiting for AW (nothing owed)
                if (wd_expired) begin grant <= 3'd0; aw_done <= 1'b0; watchdog <= '0; end
                else watchdog <= watchdog + 22'd1;
            end
            // aw_done && B not yet seen: HOLD the grant, never abort.
        end
    end

    // AW mux
    assign m_awaddr  = (grant==3'd4)?s3_awaddr :(grant==3'd3)?s2_awaddr :(grant==3'd2)?s1_awaddr :s0_awaddr;
    assign m_awburst = (grant==3'd4)?s3_awburst:(grant==3'd3)?s2_awburst:(grant==3'd2)?s1_awburst:s0_awburst;
    assign m_awid    = (grant==3'd4)?s3_awid   :(grant==3'd3)?s2_awid   :(grant==3'd2)?s1_awid   :s0_awid;
    assign m_awlen   = (grant==3'd4)?s3_awlen  :(grant==3'd3)?s2_awlen  :(grant==3'd2)?s1_awlen  :s0_awlen;
    assign m_awsize  = (grant==3'd4)?s3_awsize :(grant==3'd3)?s2_awsize :(grant==3'd2)?s1_awsize :s0_awsize;
    assign m_awvalid = (grant==3'd1)?s0_awvalid:(grant==3'd2)?s1_awvalid:(grant==3'd3)?s2_awvalid:(grant==3'd4)?s3_awvalid:1'b0;
    assign s0_awready = (grant==3'd1)?m_awready:1'b0;
    assign s1_awready = (grant==3'd2)?m_awready:1'b0;
    assign s2_awready = (grant==3'd3)?m_awready:1'b0;
    assign s3_awready = (grant==3'd4)?m_awready:1'b0;

    // W mux
    assign m_wdata = (grant==3'd4)?s3_wdata:(grant==3'd3)?s2_wdata:(grant==3'd2)?s1_wdata:s0_wdata;
    assign m_wstrb = (grant==3'd4)?s3_wstrb:(grant==3'd3)?s2_wstrb:(grant==3'd2)?s1_wstrb:s0_wstrb;
    assign m_wlast = (grant==3'd4)?s3_wlast:(grant==3'd3)?s2_wlast:(grant==3'd2)?s1_wlast:s0_wlast;
    assign m_wvalid = (grant==3'd1)?s0_wvalid:(grant==3'd2)?s1_wvalid:(grant==3'd3)?s2_wvalid:(grant==3'd4)?s3_wvalid:1'b0;
    assign s0_wready = (grant==3'd1)?m_wready:1'b0;
    assign s1_wready = (grant==3'd2)?m_wready:1'b0;
    assign s2_wready = (grant==3'd3)?m_wready:1'b0;
    assign s3_wready = (grant==3'd4)?m_wready:1'b0;

    // B demux. bready_q arms exactly after the selected final W handshake, one cycle before
    // the earliest response the existing writers can consume. It is held through B acceptance.
    assign s0_bresp = m_bresp; assign s1_bresp = m_bresp; assign s2_bresp = m_bresp; assign s3_bresp = m_bresp;
    assign s0_bvalid = (grant==3'd1)?m_bvalid:1'b0;
    assign s1_bvalid = (grant==3'd2)?m_bvalid:1'b0;
    assign s2_bvalid = (grant==3'd3)?m_bvalid:1'b0;
    assign s3_bvalid = (grant==3'd4)?m_bvalid:1'b0;
    assign m_bready  = bready_q;
endmodule
