// retroDE_ps2 — tb_pad_state_via_sif_to_ee (Ch238 + Ch239)
// ============================================================================
// Integration TB for the full HPS → bridge → IOP-side pad reg →
// IOP RAM (packed Sony pad-state buffer) → SIF DMA (IOP→EE direction) →
// EE RAM landing path. Proves Option A from
// `docs/contracts/sio2_pad.md` (Ch237 recon): the existing SIF
// `sif_dma_ee_ram_bridge_stub` carries a 16-byte Sony pad packet from
// the IOP side into EE RAM at `EE_PAD_BUFFER_BASE = 0x0008_0000` as a
// single 128-bit qword write.
//
// **Ch239 update**: every scenario now pulses the new `rewind_i`
// input on the egress bridge BEFORE driving its 4 beats. That resets
// the bridge's internal `wr_offset` to 0, so successive pad packets
// **all land at the same fixed 16-byte slot at EE_PAD_BUFFER_BASE** —
// the libpad-style single-slot buffer contract a libpad/padman
// consumer expects (`padRead(port, &buf)` returns the latest snapshot
// at the same `&buf` address every call).
//
// Stale-byte safety: each scenario writes ALL 16 bytes of the slot
// (a full 4-beat transfer with in_last on beat 3). The byte-enables
// the bridge emits cover all 16 bytes (`bridge_wr_be = 16'hFFFF`),
// so the EE RAM byte-enable-aware write overwrites every byte. The
// "no stale bytes" check is implicit in the per-scenario qword
// equality assertion below — if even one byte from a prior scenario
// leaked through, the 128-bit `check_eq128` would fire.
//
// No new production RTL — the test composes existing stubs end-to-end.
// The "IOP-side sequencer" is the TB itself: it reads PAD_P1_STATE
// through the Ch234 sio2_input_stub, packs the 16-byte struct in the
// libpad-compatible layout, and drives four 32-bit beats into the
// SIF egress bridge.
//
// **Pad packet layout (16 bytes, Sony "digital mode" / type 0x41):**
//   byte 0  : 0x00  success status
//   byte 1  : 0x41  response type (digital)
//   byte 2  : 0x5A  success token
//   byte 3  : Sony pad byte3 (D-pad / start / select / sticks, active-low)
//   byte 4  : Sony pad byte4 (face / shoulder, active-low)
//   bytes 5–8 : 0x80 each (RX, RY, LX, LY analog centers — digital mode)
//   bytes 9–15: 0x00 (pressure / reserved, DualShock 2)
//
// Beats (little-endian byte packing inside the 32-bit beats):
//   beat[0] = {byte3, 0x5A, 0x41, 0x00}    [31:0]
//   beat[1] = {0x80, 0x80, 0x80, byte4}    [63:32]
//   beat[2] = {0x00, 0x00, 0x00, 0x80}     [95:64]
//   beat[3] = 0x00000000                   [127:96]
//
// The bridge's beat-to-qword accumulator maps beat N into
// `bridge_wr_data[32*N +: 32]`, so the landed qword is
// {beat[3], beat[2], beat[1], beat[0]} — i.e. byte 0 of the buffer
// lands at the LSB of the qword, matching little-endian byte order.
//
// **Verified scenarios:**
//   §1. INPUT_P1 = 0 (no buttons)               → expect 0xFF / 0xFF in bytes 3 / 4
//   §2. INPUT_P1 = JOY_RIGHT (bit 0)            → byte3.5 cleared
//   §3. INPUT_P1 = JOY_RIGHT|START|SELECT|TRIANGLE (combo)
//                                               → byte3 + byte4 both have bits cleared
//   §4. INPUT_P1 = 0 again (re-clear)           → buffer rewritten to all-released
//
// Each iteration: AXI write to bridge → settle CDC → IOP read of
// PAD_P1_STATE → pack 4 beats → drive SIF DMA → wait for
// `last_seen_o` → EE-side qword read → byte-level check.
//
// **P2 is deliberately left out of the first slice** per Codex Ch238
// framing ("P1-only first slice"). The next chapter can either add P2
// to the same 16-byte slot (overwrites P1) or move to a multi-port
// layout with EE_PAD_BUFFER_BASE+16 holding P2.
// ============================================================================

`timescale 1ns/1ps

module tb_pad_state_via_sif_to_ee;

    // ------------------------------------------------------------------
    // Two clocks — bridge @ 100 MHz, IOP/EE/SIF @ 33 MHz. Different
    // periods exercise the bclk → iclk CDC inside sio2_input_stub.
    // ------------------------------------------------------------------
    logic bclk;
    logic iclk;
    initial bclk = 1'b0;
    initial iclk = 1'b0;
    always #5  bclk = ~bclk;
    always #15 iclk = ~iclk;

    logic breset_n;
    logic ireset_n;

    // ------------------------------------------------------------------
    // Bridge status inputs (tied LOW).
    // ------------------------------------------------------------------
    logic core_halt        = 1'b0;
    logic dma_done_seen    = 1'b0;
    logic frame_seen       = 1'b0;
    logic hdmi_init_done   = 1'b0;
    logic hdmi_i2c_error   = 1'b0;
    logic raster_overflow  = 1'b0;
    logic frame_toggle     = 1'b0;
    logic dma_done_toggle  = 1'b0;

    // Bridge AXI slave (TB drives master).
    logic [3:0]   s_axi_awid;
    logic [37:0]  s_axi_awaddr;
    logic [7:0]   s_axi_awlen;
    logic [2:0]   s_axi_awsize;
    logic [1:0]   s_axi_awburst;
    logic         s_axi_awlock;
    logic [3:0]   s_axi_awcache;
    logic [2:0]   s_axi_awprot;
    logic         s_axi_awvalid;
    logic         s_axi_awready;
    logic [127:0] s_axi_wdata;
    logic [15:0]  s_axi_wstrb;
    logic         s_axi_wlast;
    logic         s_axi_wvalid;
    logic         s_axi_wready;
    logic [3:0]   s_axi_bid;
    logic [1:0]   s_axi_bresp;
    logic         s_axi_bvalid;
    logic         s_axi_bready;
    logic [3:0]   s_axi_arid;
    logic [37:0]  s_axi_araddr;
    logic [7:0]   s_axi_arlen;
    logic [2:0]   s_axi_arsize;
    logic [1:0]   s_axi_arburst;
    logic         s_axi_arlock;
    logic [3:0]   s_axi_arcache;
    logic [2:0]   s_axi_arprot;
    logic         s_axi_arvalid;
    logic         s_axi_arready;
    logic [3:0]   s_axi_rid;
    logic [127:0] s_axi_rdata;
    logic [1:0]   s_axi_rresp;
    logic         s_axi_rlast;
    logic         s_axi_rvalid;
    logic         s_axi_rready;

    // Bridge other outputs (ignored).
    logic        core_reset_req;
    logic        tile_wr_toggle;
    logic [9:0]  tile_wr_index;
    logic [31:0] tile_wr_data;
    // Ch245 — platform OSD register-surface ports (unused by this
    // integration TB; declared so the `.*` wildcard binds them).
    wire [31:0] osd_ctrl_o;
    wire [31:0] osd_cfg0_o;
    wire [31:0] osd_cfg1_o;
    logic        osd_active_i              = 1'b0;
    logic [4:0]  osd_cursor_row_i          = 5'd0;
    logic        osd_set_trigger_i         = 1'b0;
    logic        osd_back_trigger_i        = 1'b0;
    logic        osd_scroll_down_trigger_i = 1'b0;
    logic        osd_scroll_up_trigger_i   = 1'b0;
    logic        osd_open_trigger_i        = 1'b0;
    logic [4:0]  osd_trigger_row_i         = 5'd0;

    // Ch248 DS2 wired-controller inputs (unused by this TB; tied
    // unplugged so the bridge's DS2_STATUS reads as such).
    logic [31:0] ds2_buttons_i              = 32'd0;
    logic        ds2_connected_i            = 1'b0;
    logic        ds2_error_i                = 1'b0;
    wire [31:0]  bridge_input_p1;
    wire [31:0]  bridge_input_p2;

    // Ch318 — LPDDR test ports (this TB doesn't exercise them; nets exist so the
    // bridge's .* connects, status inputs tied safe).
    logic        lpddr_arm_o, lpddr_canary_o;
    logic        lpddr_ctrl_commit_o;
    logic [31:0] lpddr_fb_base_o;
    logic [31:0] lpddr_bytes_i = 32'd0, lpddr_bursts_i = 32'd0, lpddr_bresp_err_i = 32'd0, lpddr_fifo_ovf_i = 32'd0;
    logic        lpddr_idle_i = 1'b1;
    // Ch319 — read-probe nets (tie inputs safe; .* binds them).
    logic [31:0] lpddr_rd_addr_o; logic lpddr_rd_pulse_o;
    logic [31:0] lpddr_rd_data_i = 32'd0; logic lpddr_rd_done_i = 1'b0;
    logic        lpddr_video_src_o;
    logic        lpddr_scanout_lb_o;
    logic        lpddr_scan_valid_i = 1'b0, lpddr_scan_err_i = 1'b0;
    // Ch322 — write-probe + texture-cache fill nets (matched by the .* below).
    logic [31:0] lpddr_wr_addr_o, lpddr_wr_data_o; logic lpddr_wr_pulse_o;
    logic        lpddr_wr_busy_i = 1'b0, lpddr_wr_done_i = 1'b0; logic [31:0] lpddr_wr_bresp_err_i = 32'd0;
    logic        tex_fill_start_o, tex_fill_done_i = 1'b0;
    logic [31:0] tex_fill_beats_i = 32'd0, tex_fill_bytes_i = 32'd0, tex_rd_errs_i = 32'd0, tex_fill_crc_i = 32'd0;
    logic [31:0] tex_cache_hits_i = 32'd0, tex_bram_hits_i = 32'd0;
    // Ch323 — tile COLOR+Z spill/reload counters (unused here; 0 so the .* connect resolves).
    logic [31:0] spill_color_beats_i = 32'd0, spill_z_beats_i = 32'd0;
    logic [31:0] reload_color_beats_i = 32'd0, reload_z_beats_i = 32'd0;
    logic [31:0] reload_rd_errs_i = 32'd0, spill_color_errs_i = 32'd0, spill_z_errs_i = 32'd0;
    logic        spill_color_ovf_i = 1'b0, spill_z_ovf_i = 1'b0;
    logic [1:0]  diag_ctrl_o;  // Ch323 diag DIAG_CTRL output
    logic [31:0] color_spill_awaddr_i=0, color_spill_wdata0_i=0;
    logic [31:0] dbg_c_first_awaddr_i=0, dbg_c_first_wdata0_i=0, dbg_c_first_wstrb_i=0, dbg_c_last_wstrb_i=0, dbg_c_beat_count_i=0;
    logic [31:0] dbg_c_emit_count_i=0, dbg_c_push_count_i=0, dbg_c_pop_count_i=0; // Ch323 pipeline-split (.* tie-off)
    logic [31:0] dbg_z_beat_count_i=0, dbg_z_emit_count_i=0, dbg_z_push_count_i=0, dbg_z_pop_count_i=0; // Ch324 (.* tie-off)
    logic [31:0] dbg_m_first_awaddr_i=0, dbg_m_first_wdata0_i=0, dbg_m_first_wstrb_i=0;
    logic [31:0] ev_tp_flush_i=0, ev_tp_zflush_i=0, ev_tp_reload_i=0, ev_tp_render_i=0;
    logic [31:0] ev_flush_emit_i=0, ev_zflush_emit_i=0, ev_reload_start_i=0, ev_reload_ready_i=0;
    // Ch330 Brick 4 — feeder control ports (.* tie-off; this TB doesn't exercise the feeder).
    logic        feeder_stg_we_tgl_o, feeder_go_tgl_o;
    logic [11:0] feeder_stg_waddr_o;
    logic [63:0] feeder_stg_wdata_o;
    logic        feeder_ready_i   = 1'b0;
    logic [15:0] feeder_records_i = 16'd0;
    logic [31:0] feeder_waits_i   = 32'd0;
    logic        frame_drained_i  = 1'b0;   // Ch353 — new bridge diagnostic input; tied off (.* matches by name)
    logic        clear_done_i     = 1'b0;   // Ch357 — persistent-Z preclear ack; tied off (.* matches by name)
    logic [31:0] frag_drops_i     = 32'd0;  // Ch357 — persistent-Z drop count; tied off (.* matches by name)
    ps2_hps_bridge u_bridge (
        .clk            (bclk),
        .reset_n        (breset_n),
        .h2f_reset      (1'b0),
        .core_halt      (core_halt),
        .dma_done_seen  (dma_done_seen),
        .frame_seen     (frame_seen),
        .hdmi_init_done (hdmi_init_done),
        .hdmi_i2c_error (hdmi_i2c_error),
        .raster_overflow(raster_overflow),
        .frame_toggle   (frame_toggle),
        .dma_done_toggle(dma_done_toggle),
        .core_reset_req (core_reset_req),
        .tile_wr_toggle (tile_wr_toggle),
        .tile_wr_index  (tile_wr_index),
        .tile_wr_data   (tile_wr_data),
        .input_p1_o     (bridge_input_p1),
        .input_p2_o     (bridge_input_p2),
        .input_p1_raw_o (),
        .*
    );

    // ------------------------------------------------------------------
    // IOP memory map — only the CPU-side read port (for PAD_P1_STATE
    // reads) and the bridge-tied input_p1/p2 are exercised; everything
    // else is tied off.
    // ------------------------------------------------------------------
    logic        iop_rd_en;
    logic [31:0] iop_rd_addr;
    wire  [31:0] iop_rd_data;
    wire         iop_rd_valid;

    wire         iop_tr_valid;
    wire [3:0]   iop_tr_subsys;
    wire [3:0]   iop_tr_event;
    wire [63:0]  iop_tr_arg0, iop_tr_arg1, iop_tr_arg2, iop_tr_arg3;
    wire [31:0]  iop_tr_flags;

    iop_memory_map_stub u_iop_map (
        .clk(iclk), .rst_n(ireset_n),
        .iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
        .iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
        .iop_wr_en(1'b0), .iop_wr_addr(32'd0),
        .iop_wr_data(32'd0), .iop_wr_be(4'd0),
        .master_id(8'd0),
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(32'd0), .bridge_wr_be(4'd0),
        .bridge_master_id(8'd0),
        .dma_rd_en(1'b0), .dma_rd_addr(32'd0),
        .dma_master_id(8'd0),
        .dma_rd_data(), .dma_rd_valid(),
        .ram_rd_en(), .ram_rd_addr(),
        .ram_rd_data(32'd0), .ram_rd_valid(1'b0),
        .ram_wr_en(), .ram_wr_addr(),
        .ram_wr_data(), .ram_wr_be(),
        .ram_master_id(),
        .sif_rd_en(), .sif_rd_addr(),
        .sif_rd_data(32'd0), .sif_rd_valid(1'b0),
        .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
        .iop_dmac_rd_en(), .iop_dmac_rd_addr(),
        .iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
        .iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
        .iop_intc_rd_en(), .iop_intc_rd_addr(),
        .iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
        .iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
        .input_p1(bridge_input_p1),
        .input_p2(bridge_input_p2),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ev_valid(iop_tr_valid), .ev_subsys(iop_tr_subsys),
        .ev_event(iop_tr_event),
        .ev_arg0(iop_tr_arg0), .ev_arg1(iop_tr_arg1),
        .ev_arg2(iop_tr_arg2), .ev_arg3(iop_tr_arg3),
        .ev_flags(iop_tr_flags)
    );

    // ------------------------------------------------------------------
    // SIF egress bridge (IOP → EE direction). TB drives `in_valid` /
    // `in_data` / `in_last` directly; bridge accumulates and writes
    // 128-bit qwords into EE RAM at `EE_PAD_BUFFER_BASE`.
    // ------------------------------------------------------------------
    localparam logic [31:0] EE_PAD_BUFFER_BASE = 32'h0008_0000;

    logic        in_valid;
    logic [31:0] in_data;
    logic        in_last;
    logic        rewind_i;
    wire         in_ready;
    wire         last_seen;

    wire         eebr_wr_en;
    wire [31:0]  eebr_wr_addr;
    wire [127:0] eebr_wr_data;
    wire [15:0]  eebr_wr_be;
    wire [7:0]   eebr_master_id;

    sif_dma_ee_ram_bridge_stub #(
        .DEST_BASE_ADDR(EE_PAD_BUFFER_BASE),
        .MASTER_ID(8'd5)
    ) u_ee_bridge (
        .clk(iclk), .rst_n(ireset_n),
        .in_valid(in_valid), .in_data(in_data), .in_last(in_last),
        .in_ready(in_ready),
        .bridge_wr_en(eebr_wr_en), .bridge_wr_addr(eebr_wr_addr),
        .bridge_wr_data(eebr_wr_data), .bridge_wr_be(eebr_wr_be),
        .bridge_master_id(eebr_master_id),
        .last_seen_o(last_seen),
        // Ch239 — rewind pulse driven between scenarios so every
        // transfer overwrites the same 16-byte slot at
        // EE_PAD_BUFFER_BASE.
        .rewind_i(rewind_i)
    );

    // ------------------------------------------------------------------
    // EE memory map + RAM. TB issues a DMAC-side qword read to verify
    // landing. Other ports tied off.
    // ------------------------------------------------------------------
    localparam int EE_RAM_BYTES = 1024 * 1024;  // 1 MiB — well above buffer base + 16

    logic        ee_dmac_rd_en;
    logic [31:0] ee_dmac_rd_addr;
    wire [127:0] ee_dmac_rd_data;
    wire         ee_dmac_rd_valid;

    wire         ee_map_ram_rd_en;
    wire [24:0]  ee_map_ram_rd_addr;
    wire [127:0] ee_ram_rd_data;
    wire         ee_ram_rd_valid;

    wire         ee_map_ram_wr_en;
    wire [24:0]  ee_map_ram_wr_addr;
    wire [127:0] ee_map_ram_wr_data;
    wire [15:0]  ee_map_ram_wr_be;
    wire [7:0]   ee_map_ram_master_id;

    wire         ee_tr_valid;
    wire [3:0]   ee_tr_subsys;
    wire [3:0]   ee_tr_event;
    wire [63:0]  ee_tr_arg0, ee_tr_arg1, ee_tr_arg2, ee_tr_arg3;
    wire [31:0]  ee_tr_flags;

    ee_memory_map_stub u_ee_map (
        .clk(iclk), .rst_n(ireset_n),
        .ee_rd_en(1'b0), .ee_rd_addr(32'd0),
        .ee_rd_data(), .ee_rd_valid(),
        .ee_wr_en(1'b0), .ee_wr_addr(32'd0),
        .ee_wr_data(32'd0), .ee_wr_be(4'd0),
        .dmac_rd_en(ee_dmac_rd_en), .dmac_rd_addr(ee_dmac_rd_addr),
        .dmac_rd_data(ee_dmac_rd_data), .dmac_rd_valid(ee_dmac_rd_valid),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(ee_map_ram_rd_en), .ram_rd_addr(ee_map_ram_rd_addr),
        .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
        .bridge_wr_en(eebr_wr_en), .bridge_wr_addr(eebr_wr_addr),
        .bridge_wr_data(eebr_wr_data), .bridge_wr_be(eebr_wr_be),
        .bridge_master_id(eebr_master_id),
        .ram_wr_en(ee_map_ram_wr_en), .ram_wr_addr(ee_map_ram_wr_addr),
        .ram_wr_data(ee_map_ram_wr_data), .ram_wr_be(ee_map_ram_wr_be),
        .ram_master_id(ee_map_ram_master_id),
        .ev_valid(ee_tr_valid), .ev_subsys(ee_tr_subsys),
        .ev_event(ee_tr_event),
        .ev_arg0(ee_tr_arg0), .ev_arg1(ee_tr_arg1),
        .ev_arg2(ee_tr_arg2), .ev_arg3(ee_tr_arg3),
        .ev_flags(ee_tr_flags)
    );

    ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram (
        .clk(iclk), .rst_n(ireset_n),
        .rd_en(ee_map_ram_rd_en), .rd_addr(ee_map_ram_rd_addr),
        .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
        .wr_en(ee_map_ram_wr_en), .wr_addr(ee_map_ram_wr_addr),
        .wr_data(ee_map_ram_wr_data), .wr_be(ee_map_ram_wr_be),
        .master_id(ee_map_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(),
        .ev_flags()
    );

    int errors;

    task automatic check_eq128(input string label,
                               input logic [127:0] got,
                               input logic [127:0] expected);
        if (got !== expected) begin
            $error("[%s] got 0x%032x expected 0x%032x", label, got, expected);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // AXI master tasks (mirror tb_bridge_iop_pad_input).
    // ------------------------------------------------------------------
    task automatic axi_write32(input logic [37:0] addr,
                               input logic [31:0] data);
        @(posedge bclk);
        s_axi_awid    <= 4'd0;
        s_axi_awaddr  <= addr;
        s_axi_awlen   <= 8'd0;
        s_axi_awsize  <= 3'd2;
        s_axi_awburst <= 2'b01;
        s_axi_awlock  <= 1'b0;
        s_axi_awcache <= 4'd0;
        s_axi_awprot  <= 3'd0;
        s_axi_awvalid <= 1'b1;
        case (addr[3:2])
            2'b00:   s_axi_wdata <= {96'd0, data};
            2'b01:   s_axi_wdata <= {64'd0, data, 32'd0};
            2'b10:   s_axi_wdata <= {32'd0, data, 64'd0};
            default: s_axi_wdata <= {data, 96'd0};
        endcase
        s_axi_wstrb  <= 16'h000F << ({addr[3:2], 2'b00});
        s_axi_wlast  <= 1'b1;
        s_axi_wvalid <= 1'b1;
        s_axi_bready <= 1'b1;
        wait (s_axi_awready);
        @(posedge bclk);
        s_axi_awvalid <= 1'b0;
        wait (s_axi_wready);
        @(posedge bclk);
        s_axi_wvalid <= 1'b0;
        wait (s_axi_bvalid);
        @(posedge bclk);
        s_axi_bready <= 1'b0;
    endtask

    // ------------------------------------------------------------------
    // IOP read helper (canonical negedge + blocking pattern).
    // ------------------------------------------------------------------
    task automatic iop_read32(input logic [31:0] addr,
                              output logic [31:0] data);
        @(negedge iclk);
        iop_rd_en   = 1'b1;
        iop_rd_addr = addr;
        @(negedge iclk);
        iop_rd_en   = 1'b0;
        iop_rd_addr = 32'd0;
        data = iop_rd_data;
        if (iop_rd_valid !== 1'b1) begin
            $error("[iop_read32] iop_rd_valid not high (addr=0x%08x)", addr);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // SIF beat driver. Drives one beat per call; respects in_ready.
    // ------------------------------------------------------------------
    task automatic sif_send_beat(input logic [31:0] data, input logic last);
        @(negedge iclk);
        in_valid = 1'b1;
        in_data  = data;
        in_last  = last;
        // Block until bridge accepts the beat (in_ready high on the
        // next posedge while in_valid is high).
        while (!in_ready) @(posedge iclk);
        @(posedge iclk);
        @(negedge iclk);
        in_valid = 1'b0;
        in_data  = 32'd0;
        in_last  = 1'b0;
    endtask

    // ------------------------------------------------------------------
    // EE-side qword read (mirror tb_sif_ee_landing_via_dmac pattern).
    // ------------------------------------------------------------------
    task automatic ee_read_qword(input logic [31:0] addr,
                                 output logic [127:0] data);
        @(negedge iclk);
        ee_dmac_rd_en   = 1'b1;
        ee_dmac_rd_addr = addr;
        @(negedge iclk);
        ee_dmac_rd_en   = 1'b0;
        ee_dmac_rd_addr = 32'd0;
        @(posedge iclk);
        data = ee_dmac_rd_data;
    endtask

    // ------------------------------------------------------------------
    // Sony pad-word helper (mirrors sio2_input_stub).
    // ------------------------------------------------------------------
    function automatic logic [7:0] sony_byte3(input logic [31:0] joy);
        sony_byte3 = ~{joy[1], joy[2], joy[0], joy[3],
                       joy[4], joy[15], joy[14], joy[5]};
    endfunction
    function automatic logic [7:0] sony_byte4(input logic [31:0] joy);
        sony_byte4 = ~{joy[8], joy[7], joy[9], joy[6],
                       joy[11], joy[10], joy[13], joy[12]};
    endfunction

    // ------------------------------------------------------------------
    // Pack a 16-byte Sony pad-state buffer into 4 little-endian 32-bit
    // beats. Returns them as a packed 128-bit value where bits [31:0]
    // are beat 0 (bytes 0..3 of the buffer).
    // ------------------------------------------------------------------
    function automatic logic [127:0] pack_sony_buffer(
        input logic [7:0] byte3,
        input logic [7:0] byte4);
        logic [31:0] beat0, beat1, beat2, beat3;
        // beat0 = {byte 3, byte 2, byte 1, byte 0}
        beat0 = {byte3, 8'h5A, 8'h41, 8'h00};
        // beat1 = {byte 7, byte 6, byte 5, byte 4}
        beat1 = {8'h80, 8'h80, 8'h80, byte4};
        // beat2 = {byte11, byte10, byte 9, byte 8}
        beat2 = {8'h00, 8'h00, 8'h00, 8'h80};
        // beat3 = {byte15, byte14, byte13, byte12}
        beat3 = 32'h0000_0000;
        pack_sony_buffer = {beat3, beat2, beat1, beat0};
    endfunction

    task automatic settle_cdc();
        repeat (20) @(posedge iclk);
    endtask

    // ------------------------------------------------------------------
    // Run one scenario: set INPUT_P1, settle CDC, IOP-read
    // PAD_P1_STATE, drive 4 beats into the SIF egress bridge, then
    // verify the qword that landed at EE_PAD_BUFFER_BASE.
    // ------------------------------------------------------------------
    logic [31:0]  pad_word;
    logic [127:0] expected_qw;
    logic [127:0] got_qw;
    logic [7:0]   b3, b4;

    // Ch239 — every scenario rewinds the bridge between transfers,
    // so all four pad packets land at the same 16-byte slot at
    // EE_PAD_BUFFER_BASE. That's the libpad-style single-slot
    // contract: a real libpad consumer reads `padRead(port, &buf)`
    // expecting the same 16-byte struct address every call, with
    // the latest snapshot of the controller's state.

    task automatic pulse_rewind();
        @(negedge iclk);
        rewind_i = 1'b1;
        @(posedge iclk);
        @(negedge iclk);
        rewind_i = 1'b0;
    endtask

    task automatic run_scenario(input string label,
                                input logic [31:0] joy_pattern);
        // Rewind first so the next emit lands at offset 0 of the
        // EE pad buffer. Safe to pulse here because we're idle in
        // S_ACCUM with pos=0 between scenarios.
        pulse_rewind();

        axi_write32(38'h040, joy_pattern);
        settle_cdc();
        iop_read32(32'h1F80_8500, pad_word);
        b3 = pad_word[7:0];
        b4 = pad_word[15:8];
        expected_qw = pack_sony_buffer(b3, b4);

        // Drive the four beats matching the expected packing.
        sif_send_beat(expected_qw[ 31: 0], 1'b0);
        sif_send_beat(expected_qw[ 63:32], 1'b0);
        sif_send_beat(expected_qw[ 95:64], 1'b0);
        sif_send_beat(expected_qw[127:96], 1'b1);

        // Bridge needs one cycle after the 4th beat to emit the qword.
        repeat (4) @(posedge iclk);
        if (last_seen !== 1'b1) begin
            $error("[%s] last_seen never asserted after 4-beat burst", label);
            errors = errors + 1;
        end

        // EVERY scenario reads from the SAME fixed buffer address.
        ee_read_qword(EE_PAD_BUFFER_BASE, got_qw);
        check_eq128(label, got_qw, expected_qw);
    endtask

    initial begin
        errors          = 0;
        breset_n        = 1'b0;
        ireset_n        = 1'b0;

        s_axi_arvalid   = 1'b0;
        s_axi_awvalid   = 1'b0;
        s_axi_wvalid    = 1'b0;
        s_axi_rready    = 1'b0;
        s_axi_bready    = 1'b0;

        iop_rd_en       = 1'b0;
        iop_rd_addr     = 32'd0;
        in_valid        = 1'b0;
        in_data         = 32'd0;
        in_last         = 1'b0;
        rewind_i        = 1'b0;
        ee_dmac_rd_en   = 1'b0;
        ee_dmac_rd_addr = 32'd0;

        repeat (4) @(posedge bclk);
        @(posedge iclk);
        breset_n = 1'b1;
        ireset_n = 1'b1;
        repeat (8) @(posedge bclk);
        repeat (8) @(posedge iclk);

        // ----------------------------------------------------------
        // §1. No buttons. Expect byte3 = byte4 = 0xFF (all released).
        // ----------------------------------------------------------
        run_scenario("no_buttons", 32'h0000_0000);

        // ----------------------------------------------------------
        // §2. Single button — JOY_RIGHT (bit 0). byte3.5 cleared.
        // ----------------------------------------------------------
        run_scenario("right_only", 32'h0000_0001);

        // ----------------------------------------------------------
        // §3. Combo — JOY_RIGHT | JOY_START | JOY_SELECT | JOY_Y
        //     (= TRIANGLE). Both bytes have bits cleared.
        // ----------------------------------------------------------
        run_scenario("combo_right_start_sel_tri",
                     32'h0000_0031 | (32'd1 << 6));

        // ----------------------------------------------------------
        // §4. Clear back to no buttons. Buffer rewrites to all-released.
        // ----------------------------------------------------------
        run_scenario("clear", 32'h0000_0000);

        // ----------------------------------------------------------
        // Done.
        // ----------------------------------------------------------
        $display("[tb_pad_state_via_sif_to_ee] errors=%0d", errors);
        if (errors == 0) $display("[tb_pad_state_via_sif_to_ee] PASS");
        else             $display("[tb_pad_state_via_sif_to_ee] FAIL");
        $finish;
    end

    initial begin
        #20_000_000;
        $error("[tb_pad_state_via_sif_to_ee] TIMEOUT");
        $finish;
    end

endmodule : tb_pad_state_via_sif_to_ee
