ec82764bef
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>
205 lines
9.2 KiB
Systemverilog
205 lines
9.2 KiB
Systemverilog
// 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
|