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
+204
View File
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025-2026 retroDE contributors
// ============================================================================
// sio2_input_stub — Ch234 retroDE-local IOP-readable pad input stub
// ============================================================================
// **Not real SIO2.** A deliberately minimal MMIO surface that translates
// the Ch222 HPS-written `INPUT_P1`/`INPUT_P2` controller bitmaps into a
// Sony-format 16-bit digital pad word, exposed as IOP-readable
// registers in the retroDE-local I/O window
// `0x1F80_8500..0x1F80_85FF`. Real SIO2 emulation (`0x1F80_8200..0x1F80_82FF`,
// FIFO, command/response, IOP DMAC channel 11) is intentionally deferred
// — see `docs/contracts/sio2_pad.md` for the reconnaissance + scoping.
//
// **Register surface** (offsets relative to PAD_IO_BASE = 0x1F80_8500):
//
// 0x500 PAD_P1_STATE (RO) [15:0] = Sony 16-bit pad word for P1
// [31:16] = 0
// 0x504 PAD_P2_STATE (RO) Same shape, sourced from `input_p2`.
// 0x508 PAD_STATUS (RO) [0] = pad path present/valid = 1
// [31:1] = 0
// other reserved reads return 32'd0; writes accepted-and-ignored.
//
// **Sony pad word format (Sony "digital mode" / type 0x41 response,
// bytes 3 and 4 of the libpad/padman struct):**
//
// pad_byte3 (D-pad / start / select / sticks; active-low, 0 = pressed):
// bit 7 LEFT bit 6 DOWN bit 5 RIGHT bit 4 UP
// bit 3 START bit 2 R3 bit 1 L3 bit 0 SELECT
//
// pad_byte4 (face / shoulder buttons; active-low):
// bit 7 □ square bit 6 × cross bit 5 ○ circle bit 4 △ triangle
// bit 3 R1 bit 2 L1 bit 1 R2 bit 0 L2
//
// PAD_P1_STATE[7:0] = pad_byte3
// PAD_P1_STATE[15:8] = pad_byte4
//
// **INPUT_P1 → Sony mapping** (per `docs/contracts/sio2_pad.md`,
// SNES-style 32-bit retroDE bitmap folded onto Sony names by spatial
// face-button layout — matches the convention coco2 / a2600 already use):
//
// INPUT_P1[ 0] JOY_RIGHT → Sony RIGHT (byte3.5)
// INPUT_P1[ 1] JOY_LEFT → Sony LEFT (byte3.7)
// INPUT_P1[ 2] JOY_DOWN → Sony DOWN (byte3.6)
// INPUT_P1[ 3] JOY_UP → Sony UP (byte3.4)
// INPUT_P1[ 4] JOY_START → Sony START (byte3.3)
// INPUT_P1[ 5] JOY_SELECT → Sony SELECT (byte3.0)
// INPUT_P1[ 6] JOY_Y → Sony △ triangle (byte4.4)
// INPUT_P1[ 7] JOY_B → Sony × cross (byte4.6)
// INPUT_P1[ 8] JOY_X → Sony □ square (byte4.7)
// INPUT_P1[ 9] JOY_A → Sony ○ circle (byte4.5)
// INPUT_P1[10] JOY_L → Sony L1 (byte4.2)
// INPUT_P1[11] JOY_R → Sony R1 (byte4.3)
// INPUT_P1[12] JOY_L2 → Sony L2 (byte4.0)
// INPUT_P1[13] JOY_R2 → Sony R2 (byte4.1)
// INPUT_P1[14] JOY_L3 → Sony L3 (byte3.1)
// INPUT_P1[15] JOY_R3 → Sony R3 (byte3.2)
// INPUT_P1[16] JOY_OSD → not forwarded (retrodesd consumes it)
//
// retroDE bitmap is **active-high** (1 = pressed); Sony word is
// **active-low** (0 = pressed). The two `pad_byteN` assigns invert
// per-bit and reorder.
//
// **CDC contract.** `input_p1`/`input_p2` are bridge-clock-domain
// signals (CLOCK2_50). This module runs on the IOP/design clock.
// The 2-FF synchronizer chain inside is the standard retroDE
// single-bit sync; tearing between bits during a partial-write
// settling window is theoretically possible but practically
// vanishingly rare (retrodesd writes the whole 32-bit latch at
// one bridge edge ≤ 1 kHz; the IOP-side read is a small window
// against millions of bridge cycles). A future chapter can promote
// this to "snapshot CDC" (latch + 2-sample coherency) if tearing
// ever becomes observable.
//
// In the focused TB and single-clock sim setups, the 2-FF sync is
// a no-op functionally and adds 2 cycles of read latency from
// input change to readable register update.
// ============================================================================
`timescale 1ns/1ps
module sio2_input_stub (
input logic clk, // IOP / design clock
input logic rst_n,
// Bridge-clock-domain inputs (sync'd internally).
input logic [31:0] input_p1,
input logic [31:0] input_p2,
// IOP map read port. `rd_addr` is the 4-bit word offset within
// the PAD I/O region (so 0x500 → addr 0x0, 0x504 → 0x1, etc.).
input logic rd_en,
input logic [3:0] rd_addr,
output logic [31:0] rd_data,
output logic rd_valid,
// IOP map write port. Writes are accepted-and-ignored.
input logic wr_en,
input logic [3:0] wr_addr,
input logic [31:0] wr_data,
// Ch250 — surface the post-translation Sony 16-bit pad words for
// fabric consumers that don't go through the IOP read memory map.
// The synth top uses `p1_sony_word_o` bits to drive status LEDs as
// a hardware proof that `bridge_input_p1_raw` actually reaches a
// live fabric consumer. (Ch241 noted those wires terminated at
// unconnected nets that Quartus elided; Ch250 ends that.) Bits
// are still active-LOW per Sony's wire-format convention. Both
// outputs are parallel taps of the same internal logic that feeds
// the 0x500/0x504 read responses — no functional change to the
// existing IOP-side path.
output logic [15:0] p1_sony_word_o,
output logic [15:0] p2_sony_word_o
);
// -----------------------------------------------------------------
// 2-FF sync of each P1/P2 bit into the IOP clock domain.
// -----------------------------------------------------------------
logic [31:0] p1_sync_0, p1_sync_1;
logic [31:0] p2_sync_0, p2_sync_1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
p1_sync_0 <= 32'd0;
p1_sync_1 <= 32'd0;
p2_sync_0 <= 32'd0;
p2_sync_1 <= 32'd0;
end else begin
p1_sync_0 <= input_p1;
p1_sync_1 <= p1_sync_0;
p2_sync_0 <= input_p2;
p2_sync_1 <= p2_sync_0;
end
end
wire [31:0] p1_q = p1_sync_1;
wire [31:0] p2_q = p2_sync_1;
// -----------------------------------------------------------------
// Sony pad-word translation. Each `pad_byteN` is the *active-low*
// Sony byte; inversion folds the active-high retroDE bitmap.
// Bit positions per `docs/contracts/sio2_pad.md`:
// byte3 = {LEFT, DOWN, RIGHT, UP, START, R3, L3, SELECT} (MSB→LSB)
// byte4 = {□, ×, ○, △, R1, L1, R2, L2}
// -----------------------------------------------------------------
function automatic logic [15:0] sony_word(input logic [31:0] joy);
logic [7:0] byte3;
logic [7:0] byte4;
// byte3 MSB→LSB: LEFT[1], DOWN[2], RIGHT[0], UP[3], START[4], R3[15], L3[14], SELECT[5]
byte3 = ~{joy[1], joy[2], joy[0], joy[3], joy[4], joy[15], joy[14], joy[5]};
// byte4 MSB→LSB: SQUARE[8], CROSS[7], CIRCLE[9], TRIANGLE[6], R1[11], L1[10], R2[13], L2[12]
byte4 = ~{joy[8], joy[7], joy[9], joy[6], joy[11], joy[10], joy[13], joy[12]};
sony_word = {byte4, byte3};
endfunction
wire [15:0] p1_word = sony_word(p1_q);
wire [15:0] p2_word = sony_word(p2_q);
// Ch250 — surface the post-translation Sony words to fabric.
assign p1_sony_word_o = p1_word;
assign p2_sony_word_o = p2_word;
// -----------------------------------------------------------------
// Register address constants (word-aligned within the PAD I/O
// region; address bits [3:2] passed in as `rd_addr[1:0]`).
// 0x500 → rd_addr = 4'h0 PAD_P1_STATE
// 0x504 → rd_addr = 4'h1 PAD_P2_STATE
// 0x508 → rd_addr = 4'h2 PAD_STATUS
// -----------------------------------------------------------------
localparam logic [3:0] OFF_P1_STATE = 4'h0;
localparam logic [3:0] OFF_P2_STATE = 4'h1;
localparam logic [3:0] OFF_STATUS = 4'h2;
// -----------------------------------------------------------------
// Read response. Combinational lookup + 1-cycle valid pipeline
// (matches the rest of the IOP map peripherals).
// -----------------------------------------------------------------
logic [31:0] rd_data_c;
always_comb begin
unique case (rd_addr)
OFF_P1_STATE: rd_data_c = {16'd0, p1_word};
OFF_P2_STATE: rd_data_c = {16'd0, p2_word};
OFF_STATUS: rd_data_c = {31'd0, 1'b1};
default: rd_data_c = 32'd0;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_data <= 32'd0;
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (rd_en)
rd_data <= rd_data_c;
end
end
// -----------------------------------------------------------------
// Writes are accepted-and-ignored. We tie `wr_*` to a placeholder
// wire so lint tools don't flag them as unused.
// -----------------------------------------------------------------
// verilator lint_off UNUSED
wire _wr_unused = &{1'b0, wr_en, wr_addr, wr_data, 1'b0};
// verilator lint_on UNUSED
endmodule : sio2_input_stub