Files
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

180 lines
8.6 KiB
Systemverilog

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