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