// retroDE_ps2 — ee_gs_priv_bridge_stub (Ch111)
//
// Bridges 32-bit EE-MMIO writes targeting the GS privileged-
// register window at 0x1200_0000 into the 64-bit gs_stub.reg_wr_*
// port. Real PS2 driver code reaches PMODE / DISPFB1 / DISPLAY1
// (etc.) via 64-bit MIPS `sd` instructions; the EE microarch
// breaks each `sd` into a pair of 32-bit `sw` operations to the
// low+high halves of the 8-byte register slot. This bridge does
// the inverse — it watches the 32-bit EE write stream, latches a
// 64-bit shadow per 8-byte slot, and fires a gs_stub.reg_wr_*
// pulse on EVERY half-write with the running 64-bit shadow value.
//
// Scope:
//   - One shared 64-bit shadow + an offset[15:3] tag identifying
//     the currently-tracked 8-byte slot. Sequential writes to the
//     SAME slot accumulate (low first, then high → final shadow
//     has both halves correct on the second fire). Switching
//     slots resets the shadow to zero so partial-half writes to
//     a fresh slot don't carry stale data from a different reg.
//   - Each EE half-write fires a gs_stub.reg_wr_* pulse with the
//     8-byte-aligned offset (`{ee_wr_addr[15:3], 3'b000}`) and
//     the FULL 64-bit shadow. Single-half writes (e.g. PMODE
//     where only the low byte matters) work because the high
//     half stays zero and gs_stub's latch sees the right value.
//   - 32-bit EE write width (matches ee_memory_map_stub's
//     ee_wr_*-port surface). **Full-word writes only**:
//     `ee_wr_be` MUST be 4'b1111 on every accepted write. Byte-
//     lane merging into the 64-bit shadow is intentionally NOT
//     modelled here — control-plane GS registers (PMODE/
//     DISPFB1/DISPLAY1/etc.) are always written as full 32-bit
//     halves of an `sd`, and constraining the contract keeps the
//     shadow + commit logic small. A simulation-time `$error`
//     fires if a non-full be is presented; a future chapter can
//     widen the bridge to per-byte merge if/when a real driver
//     pattern needs sub-word writes here.
//
// Wiring contract (TB-level for Ch111):
//   ee_wr_en   ← TB EE-MMIO write strobe at 0x12000000+offset
//   ee_wr_addr ← 16-bit offset within the GS priv window (= EE
//                phys addr [15:0]; the upper EE-window decode
//                lives in the test bench / memory map)
//   ee_wr_data ← 32-bit EE data (one of two halves of a 64-bit
//                GS register)
//   ee_wr_be   ← 4-bit per-byte enable (typically 4'b1111)
//
// The bridge does NOT participate in EE reads. The gs_stub
// privileged-register port is write-only at this scope, matching
// the limited read coverage of the GS priv block in the rest of
// the design.

`timescale 1ns/1ps

module ee_gs_priv_bridge_stub
(
    input  logic         clk,
    input  logic         rst_n,

    // EE-MMIO write port (32-bit data, 16-bit offset within
    // 0x1200_0000 window).
    input  logic         ee_wr_en,
    input  logic [15:0]  ee_wr_addr,
    input  logic [31:0]  ee_wr_data,
    input  logic [3:0]   ee_wr_be,

    // gs_stub privileged-register port (16-bit offset, 64-bit data).
    output logic         gs_reg_wr_en,
    output logic [15:0]  gs_reg_wr_addr,
    output logic [63:0]  gs_reg_wr_data
);

    // Shared 64-bit shadow + the 13-bit offset[15:3] tag of the
    // currently-tracked slot. Resets to zero on rst_n or on a
    // switch to a different slot.
    logic [63:0] shadow;
    logic [12:0] shadow_tag;
    logic        shadow_valid;

    logic [12:0] cur_tag;
    logic        cur_is_high;
    logic [63:0] new_shadow;

    assign cur_tag     = ee_wr_addr[15:3];
    assign cur_is_high = ee_wr_addr[2];

    always_comb begin
        logic [63:0] base_shadow;
        // If the EE write hits the same 8-byte slot we're already
        // tracking, merge into the existing shadow. Otherwise start
        // a fresh shadow at zero (the un-touched half stays 0 — that's
        // safe for the demo where we always write the half that
        // matters first; high-only writes are not used in this TB
        // family).
        base_shadow = (shadow_valid && shadow_tag == cur_tag) ? shadow
                                                              : 64'd0;
        if (cur_is_high)
            new_shadow = {ee_wr_data, base_shadow[31:0]};
        else
            new_shadow = {base_shadow[63:32], ee_wr_data};
    end

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            shadow         <= 64'd0;
            shadow_tag     <= 13'd0;
            shadow_valid   <= 1'b0;
            gs_reg_wr_en   <= 1'b0;
            gs_reg_wr_addr <= 16'd0;
            gs_reg_wr_data <= 64'd0;
        end else begin
            gs_reg_wr_en <= 1'b0;
            if (ee_wr_en) begin
                // Contract: full-word writes only. Sub-word
                // (per-byte) merging into the 64-bit shadow is
                // out of scope at Ch111. Catch contract violations
                // loudly so a future driver pattern that needs
                // byte-lane writes is forced to widen the bridge.
                if (ee_wr_be !== 4'b1111) begin
                    $error("ee_gs_priv_bridge_stub: ee_wr_be=%b — only 4'b1111 supported (full-word writes); offset=0x%04x data=0x%08h",
                           ee_wr_be, ee_wr_addr, ee_wr_data);
                end
                shadow         <= new_shadow;
                shadow_tag     <= cur_tag;
                shadow_valid   <= 1'b1;
                gs_reg_wr_en   <= 1'b1;
                gs_reg_wr_addr <= {cur_tag, 3'b000};
                gs_reg_wr_data <= new_shadow;
            end
        end
    end

endmodule : ee_gs_priv_bridge_stub
