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

156 lines
7.7 KiB
Systemverilog

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