// ============================================================================ // 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 reg [21:0] watchdog; // pre-commit only; ~6.7 ms @ 310 MHz dead-bus backstop wire wd_expired = watchdog[21]; wire sel_bready = (grant==3'd1)?s0_bready:(grant==3'd2)?s1_bready: (grant==3'd3)?s2_bready:(grant==3'd4)?s3_bready:1'b1; always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin grant <= 3'd0; aw_done <= 1'b0; watchdog <= '0; end else if (grant == 3'd0) begin aw_done <= 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_bvalid && sel_bready) begin grant <= 3'd0; aw_done <= 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 (idle: bready=1 drains any stale/late response) 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 = (grant==3'd1)?s0_bready:(grant==3'd2)?s1_bready:(grant==3'd3)?s2_bready:(grant==3'd4)?s3_bready:1'b1; endmodule