// retroDE_ps2 — tb_gs_scanout_bram_psmct16 (Ch158 audit Medium fix)
//
// Direct positive proof of the Ch158 audit Medium fix —
// gs_pcrtc_stub with `VRAM_SYNC_READ=1` correctly selects the
// PSMCT16 halfword lane (`byte_addr[1]==0` low half vs `==1` high
// half) of `vram_bram_stub`'s word-addressable read_data. The
// fix (registered `vram_addr_lane_q` + `data_lane[1]` mux on
// `psm16_pixel`) was needed because before the fix the PCRTC
// extracted `vram_read_data[15:0]` for every CT16 pixel,
// regardless of `byte_addr[1]` — which silently doubled even
// pixels and dropped odd ones whenever the read landed on a
// 32-bit word containing TWO halfwords.
//
// **Adversarial pre-load**: `vram_bram_stub.mem[0]` and
// `mem[1]` are written with FOUR distinct PSMCT16 halfwords
// covering both halfword lanes of two consecutive 32-bit words:
//
//   mem[0] = {HW_B (high half, byte_addr=2), HW_A (low half, byte_addr=0)}
//   mem[1] = {HW_D (high half, byte_addr=6), HW_C (low half, byte_addr=4)}
//
// PCRTC linear PSMCT16 at FBP=0/FBW=1 places pixel (x, 0) at
// `byte_addr = 2x`, so the four scanout pixels read each of the
// four halfwords in order. With the lane fix, each captured
// pixel matches the 5→8 bit-replicate decode of its expected
// halfword. Without the fix, every pixel would emit the LOW
// halfword decode of its containing 32-bit word — pixels (0,0)
// and (1,0) would both look like HW_A, and (2,0) / (3,0) would
// both look like HW_C.

`timescale 1ns/1ps

module tb_gs_scanout_bram_psmct16;

    localparam int H_ACTIVE = 4;
    localparam int V_ACTIVE = 1;
    localparam int H_FRONT  = 1;
    localparam int H_SYNC   = 1;
    localparam int H_BACK   = 1;
    localparam int V_FRONT  = 1;
    localparam int V_SYNC   = 1;
    localparam int V_BACK   = 1;

    logic clk;
    logic rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ------------------------------------------------------------
    // PCRTC outputs.
    // ------------------------------------------------------------
    logic        hsync, vsync, de;
    logic [7:0]  r, g, b;

    // ------------------------------------------------------------
    // PMODE / DISPFB1 / DISPLAY1 set by the TB.
    //
    //   DISPFB1: FBP=0, FBW=1, PSM=PSMCT16 (=2).
    //     FBW=1 → bits[14:9]=0x01 → 0x0200.
    //     PSM=2 → bits[19:15]=0x02 → 0x10000.
    //   DISPLAY1: DX=0, DY=0, DW=H_ACTIVE-1, DH=V_ACTIVE-1, MAGH=MAGV=0.
    //   PMODE.EN1 = 1.
    // ------------------------------------------------------------
    localparam logic [63:0] PMODE_VAL    = 64'h0000_0000_0000_0001;
    localparam logic [63:0] DISPFB1_VAL  = 64'h0000_0000_0001_0200;
    // DISPLAY1: DX[11:0]=0, DY[22:12]=0, MAGH/MAGV=0, DW[43:32]=H-1,
    // DH[54:44]=V-1.
    localparam logic [63:0] DISPLAY1_VAL = (64'(H_ACTIVE - 1) << 32)
                                         | (64'(V_ACTIVE - 1) << 44);

    // ------------------------------------------------------------
    // VRAM read port — wired pcrtc → vram_bram_stub.
    // ------------------------------------------------------------
    logic [31:0] vram_read_addr;
    logic [31:0] vram_read_data;

    // ------------------------------------------------------------
    // gs_pcrtc_stub#(VRAM_SYNC_READ=1) for sync-read alignment.
    // PSMCT16_SWIZZLE=0 — linear PSMCT16 addressing for predictable
    // byte_addr=2x mapping.
    // ------------------------------------------------------------
    gs_pcrtc_stub #(
        .H_ACTIVE(H_ACTIVE), .H_FRONT(H_FRONT), .H_SYNC(H_SYNC), .H_BACK(H_BACK),
        .V_ACTIVE(V_ACTIVE), .V_FRONT(V_FRONT), .V_SYNC(V_SYNC), .V_BACK(V_BACK),
        .PSMCT16_SWIZZLE(1'b0),
        .VRAM_SYNC_READ (1'b1)
    ) u_pcrtc (
        .clk(clk), .rst_n(rst_n),
        .pmode_q       (PMODE_VAL),
        .dispfb1_q     (DISPFB1_VAL),
        .display1_q    (DISPLAY1_VAL),
        .vram_read_addr(vram_read_addr),
        .vram_read_data(vram_read_data),
        .clut_enable   (1'b0),
        .clut_csa      (5'd0),
        .clut_read_idx (),
        .clut_read_data(32'd0),
        .hsync(hsync), .vsync(vsync), .de(de),
        .r(r), .g(g), .b(b),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------
    // vram_bram_stub. 4 KiB is plenty for 4 halfwords.
    // ------------------------------------------------------------
    localparam int VRAM_BYTES = 4096;
    localparam int VRAM_WORDS = VRAM_BYTES / 4;

    vram_bram_stub #(.BYTES(VRAM_BYTES)) u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (1'b0),
        .write_addr(32'd0),
        .write_data(32'd0),
        .write_be  (4'd0),
        .read_addr (vram_read_addr),
        .read_data (vram_read_data),
        .read_valid(),
        .read2_addr(32'd0),
        .read2_data(),
        .read2_valid()
    );

    // ------------------------------------------------------------
    // Adversarial pre-load. Four distinct halfwords; the byte_addr
    // for x=0..3 alternates [1]=0 / [1]=1 within consecutive
    // 32-bit words.
    // ------------------------------------------------------------
    localparam logic [15:0] HW_A = 16'h6155;  // x=0, byte_addr=0, lane[1]=0
    localparam logic [15:0] HW_B = 16'h9F8E;  // x=1, byte_addr=2, lane[1]=1
    localparam logic [15:0] HW_C = 16'h4321;  // x=2, byte_addr=4, lane[1]=0
    localparam logic [15:0] HW_D = 16'hFEDC;  // x=3, byte_addr=6, lane[1]=1

    // 5→8 bit-replicate (matches PCRTC's expand5).
    function automatic logic [7:0] expand5(input logic [4:0] v);
        return {v, v[4:2]};
    endfunction

    function automatic logic [7:0] hw_r(input logic [15:0] hw); return expand5(hw[ 4: 0]); endfunction
    function automatic logic [7:0] hw_g(input logic [15:0] hw); return expand5(hw[ 9: 5]); endfunction
    function automatic logic [7:0] hw_b(input logic [15:0] hw); return expand5(hw[14:10]); endfunction

    function automatic logic [15:0] hw_for_x(input int xx);
        case (xx)
            0:       return HW_A;
            1:       return HW_B;
            2:       return HW_C;
            default: return HW_D;
        endcase
    endfunction

    // ------------------------------------------------------------
    // Frame capture mirrors the top BRAM TB. PCRTC's `de` is the
    // registered (decode-stage) view; r/g/b correspond to the
    // address-stage hcnt at the PRIOR posedge. The TB registers a
    // 1-cycle-delayed copy of `dut.u_pcrtc.hcnt` and uses it as
    // the cap[][] index.
    // ------------------------------------------------------------
    logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit         cap_armed;

    logic [31:0] hcnt_d;
    logic [31:0] vcnt_d;
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            hcnt_d <= 32'd0;
            vcnt_d <= 32'd0;
        end else begin
            hcnt_d <= 32'(u_pcrtc.hcnt);
            vcnt_d <= 32'(u_pcrtc.vcnt);
        end
    end

    always_ff @(posedge clk) begin
        if (rst_n && cap_armed && de
            && (vcnt_d < V_ACTIVE) && (hcnt_d < H_ACTIVE)) begin
            cap_r [vcnt_d][hcnt_d] <= r;
            cap_g [vcnt_d][hcnt_d] <= g;
            cap_b [vcnt_d][hcnt_d] <= b;
            cap_de[vcnt_d][hcnt_d] <= 1'b1;
        end
    end

    int errors;

    initial begin
        for (int y = 0; y < V_ACTIVE; y++)
            for (int x = 0; x < H_ACTIVE; x++) begin
                cap_r [y][x] = 8'd0;
                cap_g [y][x] = 8'd0;
                cap_b [y][x] = 8'd0;
                cap_de[y][x] = 1'b0;
            end
        cap_armed = 1'b0;
        errors    = 0;
        rst_n     = 1'b0;

        // Pre-load mem[]. Two words; HW packing puts the low-half
        // halfword in [15:0] and the high-half halfword in [31:16].
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = 32'd0;
        u_vram.mem[0] = {HW_B, HW_A};   // bytes 0..3
        u_vram.mem[1] = {HW_D, HW_C};   // bytes 4..7
        #1;
        u_vram.mem[0] = {HW_B, HW_A};
        u_vram.mem[1] = {HW_D, HW_C};

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (4) @(posedge clk);

        // Arm capture on the next vsync rising edge so we record
        // exactly one frame.
        @(negedge vsync);
        @(posedge vsync);
        cap_armed = 1'b1;
        repeat ((H_ACTIVE + 4) * (V_ACTIVE + 4) + 50) @(posedge clk);
        cap_armed = 1'b0;

        // Verify captured frame.
        for (int yy = 0; yy < V_ACTIVE; yy++) begin
            for (int xx = 0; xx < H_ACTIVE; xx++) begin
                logic [15:0] expected_hw;
                logic [7:0]  exp_r, exp_g, exp_b;
                expected_hw = hw_for_x(xx);
                exp_r = hw_r(expected_hw);
                exp_g = hw_g(expected_hw);
                exp_b = hw_b(expected_hw);
                if (!cap_de[yy][xx]) begin
                    $error("[ct16] (%0d,%0d) not captured", xx, yy);
                    errors = errors + 1;
                end else if ((cap_r[yy][xx] !== exp_r) ||
                             (cap_g[yy][xx] !== exp_g) ||
                             (cap_b[yy][xx] !== exp_b)) begin
                    $error("[ct16] (%0d,%0d) byte_addr=%0d hw=0x%04x got rgb=(%02x,%02x,%02x) expected (%02x,%02x,%02x)",
                           xx, yy, xx*2, expected_hw,
                           cap_r[yy][xx], cap_g[yy][xx], cap_b[yy][xx],
                           exp_r, exp_g, exp_b);
                    errors = errors + 1;
                end
            end
        end

        $display("[tb_gs_scanout_bram_psmct16] errors=%0d", errors);
        if (errors == 0) $display("[tb_gs_scanout_bram_psmct16] PASS");
        else             $display("[tb_gs_scanout_bram_psmct16] FAIL");
        $finish;
    end

    initial begin
        #500000;
        $error("[tb_gs_scanout_bram_psmct16] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_scanout_bram_psmct16
