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>
This commit is contained in:
2026-06-29 20:10:50 -04:00
commit ec82764bef
2462 changed files with 2174303 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025-2026 retroDE contributors
// ============================================================================
// tile_ram_cdc — Ch229 bridge-clock → design-clock tile-RAM shadow
// ============================================================================
// Implements the design-domain side of the Ch229 tile-RAM CDC. Owns a
// 1024 × 32-bit shadow memory in the design clock domain. Bridge-side
// writes arrive as a toggle-based "event" signal plus latched index +
// data; a 2-FF synchronizer + XOR edge detector turns each toggle edge
// into a 1-cycle write pulse against the shadow RAM. Read port is
// purely combinational (the consumer is the Ch245 platform-OSD
// char-BRAM read adapter in the top, which selects high/low 16-bit
// cells from each 32-bit shadow word and feeds them to the platform
// `osd_overlay`. Pre-Ch245 the consumer was the now-retired
// PS2-local `osd_overlay_stub`). No back-pressure — the bridge is assumed
// to space tile writes far enough apart for the sync chain to keep up.
//
// **CDC contract (read carefully before refactoring):**
// - The bridge updates `bclk_wr_toggle`, `bclk_wr_index`, `bclk_wr_data`
// at the same `bclk` edge (one bridge clock cycle).
// - The receiver sees the toggle through a 2-FF synchronizer; the
// edge-detection wire `wr_pulse` fires on the dclk cycle where the
// synchronized toggle has FULLY settled. That guarantees ≥ 2 dclk
// periods of stability on `bclk_wr_index/data` before they're
// sampled into the shadow memory.
// - Multiple bridge writes faster than ~3 dclk periods apart will
// race and may drop or merge events. For the Ch229 use case
// (retrodesd OSD updates at ≤ 1 kHz, design_clk at 2550 MHz),
// this is many orders of magnitude of slack. **Do not** wire a
// fast-cycling source (e.g. a counter) into the bridge's tile
// write path without first replacing this CDC with an async FIFO.
//
// **Reset behavior:**
// - On `breset_n` deasserted: bridge clears `bclk_wr_toggle` to 0
// (matching the receiver's post-reset state). When both domains
// reset together (the normal case on FPGA configure), no spurious
// edge fires after release.
// - On `dreset_n` deasserted: synchronizer chain clears to 0;
// shadow memory contents are NOT cleared (matches Ch227 retention
// semantics — sim `initial` block zeroes for determinism, hardware
// power-up is undefined). The Ch229 contract is "tile RAM survives
// warm reset"; rebooting both sides is a power-cycle scenario and
// the bridge will re-broadcast any written tiles via the next set
// of AXI writes from HPS.
// ============================================================================
`timescale 1ns/1ps
module tile_ram_cdc (
// ---- Bridge clock domain (write port) ----
input logic bclk,
input logic breset_n,
input logic bclk_wr_toggle,
input logic [9:0] bclk_wr_index,
input logic [31:0] bclk_wr_data,
// ---- Design clock domain (read port) ----
input logic dclk,
input logic dreset_n,
input logic [9:0] dclk_rd_index,
output logic [31:0] dclk_rd_data,
// ---- Ch230 design-domain diagnostic counter ----
// Saturating count of "tile writes too close" events — successive
// wr_pulse events fewer than MIN_DCLK_GAP dclk cycles apart.
// Exposed as an output so the top can route it to a reverse-CDC +
// bridge-readable diagnostic register in a future chapter (Ch231+).
// For Ch230 the top leaves it unconnected; the counter still exists
// in the design domain as a synthesis artifact ready for hookup.
output logic [15:0] tile_wr_too_close_count
);
// Shadow RAM lives in the design clock domain. Matched-size with
// the bridge-side `ps2_hps_bridge.tile_mem` (1024 × 32-bit). The
// `ramstyle = "M20K"` attribute (added in the Ch232 hardware
// bring-up hotfix) forces Quartus to use a single M20K block
// instead of distributing the storage across LABs.
(* ramstyle = "M20K" *) logic [31:0] shadow_mem [0:1023];
initial begin
for (int i = 0; i < 1024; i++)
shadow_mem[i] = 32'd0;
end
// 2-FF synchronizer on the bridge toggle into the design clock.
// Three stages let us compute an edge detector against the
// already-resampled bits ([2] ^ [1]), giving the wr_pulse a full
// dclk cycle of bclk_wr_index/data stability before we sample.
logic [2:0] toggle_sync;
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n)
toggle_sync <= 3'b000;
else
toggle_sync <= {toggle_sync[1:0], bclk_wr_toggle};
end
wire wr_pulse = toggle_sync[2] ^ toggle_sync[1];
// Shadow write port. At the dclk edge where wr_pulse fires,
// sample bclk_wr_index + bclk_wr_data. Both have been stable for
// ≥ 2 dclk cycles by construction of the CDC contract above.
always_ff @(posedge dclk) begin
if (wr_pulse)
shadow_mem[bclk_wr_index] <= bclk_wr_data;
end
// Read port: combinational lookup. The consumer pulls index from
// its pixel position and uses the data to decide overlay vs
// transparent for each pixel.
assign dclk_rd_data = shadow_mem[dclk_rd_index];
// ---- Ch229 / Ch230 tile-write rate watchdog ----
// The CDC contract requires writes to be spaced far enough apart
// that each toggle edge passes through the sync chain cleanly.
// Two consecutive bridge writes that both flip toggle within one
// dclk of each other can be merged into a single transition at
// sync[0] — the first write's bclk_wr_index/bclk_wr_data are
// overwritten before the receiver samples them, and the write is
// silently lost.
//
// The actual minimum gap is ≥ 3 dclk between successive
// wr_pulse events at the receiver:
// - 1 dclk for the synchronizer to fully settle (so the
// second edge is visible as a distinct transition)
// - 1 dclk for the receiver to fire wr_pulse for write 1
// - 1 dclk of margin for jitter / setup time
//
// Production rate enforcer is software-side (retrodesd OSD
// updates at ≤ 1 kHz ≫ 3 dclk @ 25 MHz = 120 ns); the bridge
// does not back-pressure AXI on this constraint. Ch229 added a
// sim-only `$display` warning; Ch230 promotes the gap-tracker to
// a real **saturating counter** (16-bit) exposed as
// `tile_wr_too_close_count` so a future chapter can route it
// through a reverse CDC into a bridge-readable register
// (HDMI_DIAG upper bits or a new diagnostic offset). The
// `$display` aid remains in `\`ifndef SYNTHESIS` for pre-silicon
// log visibility.
localparam int unsigned MIN_DCLK_GAP = 3;
logic [31:0] dclk_since_last_pulse;
wire too_close = wr_pulse && (dclk_since_last_pulse < MIN_DCLK_GAP);
always_ff @(posedge dclk or negedge dreset_n) begin
if (!dreset_n) begin
dclk_since_last_pulse <= 32'hFFFF_FFFF;
tile_wr_too_close_count <= 16'd0;
end else begin
if (wr_pulse)
dclk_since_last_pulse <= 32'd0;
else if (dclk_since_last_pulse != 32'hFFFF_FFFF)
dclk_since_last_pulse <= dclk_since_last_pulse + 32'd1;
if (too_close && (tile_wr_too_close_count != 16'hFFFF))
tile_wr_too_close_count <= tile_wr_too_close_count + 16'd1;
end
end
`ifndef SYNTHESIS
always_ff @(posedge dclk) begin
if (dreset_n && too_close) begin
$display(
"[tile_ram_cdc] WARN time=%0t: tile writes too close - %0d dclk cycles between toggle edges (CDC needs >= %0d for safe sample).",
$time, dclk_since_last_pulse, MIN_DCLK_GAP);
end
end
`endif
// ---- Lint: bclk + breset_n are intentionally referenced ONLY
// via the bclk_wr_toggle path. Tie a placeholder reference
// to silence "unused" warnings on tools that don't trace
// through the upstream toggle source.
// verilator lint_off UNUSED
wire _unused_ok = &{1'b0, bclk, breset_n, 1'b0};
// verilator lint_on UNUSED
endmodule : tile_ram_cdc