Files
retroDE_ps2/rtl/iop/sio2_input_stub.sv
T
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

205 lines
9.2 KiB
Systemverilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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