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