// retroDE_ps2 — tb_gs_scanout_bram_psmt8 (Ch158 audit Medium fix)
//
// Sibling of `tb_gs_scanout_bram_psmct16` for PSMT8. Direct
// positive proof of the Ch158 audit Medium fix — gs_pcrtc_stub
// with `VRAM_SYNC_READ=1` correctly selects the PSMT8 byte lane
// (`byte_addr[1:0]` ∈ {0,1,2,3}) of `vram_bram_stub`'s
// word-addressable read_data. Before the fix, PSMT8 PCRTC
// extracted `vram_read_data[7:0]` for every pixel and would
// silently miss bytes at lanes 1/2/3 of any 32-bit word.
//
// **Adversarial pre-load**: `vram_bram_stub.mem[0]` is written
// with FOUR distinct PSMT8 byte indices, one per lane:
//
//   mem[0] = {IDX_3, IDX_2, IDX_1, IDX_0}
//
// PCRTC linear PSMT8 at FBP=0/FBW=1 places pixel (x, 0) at
// `byte_addr = x`, so the four scanout pixels read the four
// byte lanes of mem[0] in order. With `clut_enable=0` the PSMT8
// fallback emits the byte index as a grayscale RGB triple
// (r=g=b=byte). With the lane fix, each captured pixel matches
// its byte's grayscale value. Without it, every pixel would
// emit the LOW byte (IDX_0) of mem[0].

`timescale 1ns/1ps

module tb_gs_scanout_bram_psmt8;

    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;

    logic        hsync, vsync, de;
    logic [7:0]  r, g, b;

    // PMODE.EN1=1; DISPFB1.PSM=PSMT8 (=0x13).
    //   FBW=1 → bits[14:9]=0x01 → 0x0200.
    //   PSM=0x13 → bits[19:15]=0x13 → 0x98000.
    localparam logic [63:0] PMODE_VAL    = 64'h0000_0000_0000_0001;
    localparam logic [63:0] DISPFB1_VAL  = 64'h0000_0000_0009_8200;
    localparam logic [63:0] DISPLAY1_VAL = (64'(H_ACTIVE - 1) << 32)
                                         | (64'(V_ACTIVE - 1) << 44);

    logic [31:0] vram_read_addr;
    logic [31:0] vram_read_data;

    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),
        .PSMT8_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),                  // grayscale fallback
        .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()
    );

    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()
    );

    localparam logic [7:0] IDX_0 = 8'hA5;  // x=0, byte_addr=0, lane[1:0]=00
    localparam logic [7:0] IDX_1 = 8'h5A;  // x=1, byte_addr=1, lane[1:0]=01
    localparam logic [7:0] IDX_2 = 8'h33;  // x=2, byte_addr=2, lane[1:0]=10
    localparam logic [7:0] IDX_3 = 8'hCC;  // x=3, byte_addr=3, lane[1:0]=11

    function automatic logic [7:0] idx_for_x(input int xx);
        case (xx)
            0:       return IDX_0;
            1:       return IDX_1;
            2:       return IDX_2;
            default: return IDX_3;
        endcase
    endfunction

    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[]: bytes 0..3 in lanes 0..3 of mem[0].
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = 32'd0;
        u_vram.mem[0] = {IDX_3, IDX_2, IDX_1, IDX_0};
        #1;
        u_vram.mem[0] = {IDX_3, IDX_2, IDX_1, IDX_0};

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

        @(negedge vsync);
        @(posedge vsync);
        cap_armed = 1'b1;
        repeat ((H_ACTIVE + 4) * (V_ACTIVE + 4) + 50) @(posedge clk);
        cap_armed = 1'b0;

        for (int yy = 0; yy < V_ACTIVE; yy++) begin
            for (int xx = 0; xx < H_ACTIVE; xx++) begin
                logic [7:0] expected;
                expected = idx_for_x(xx);
                if (!cap_de[yy][xx]) begin
                    $error("[t8] (%0d,%0d) not captured", xx, yy);
                    errors = errors + 1;
                end else if ((cap_r[yy][xx] !== expected) ||
                             (cap_g[yy][xx] !== expected) ||
                             (cap_b[yy][xx] !== expected)) begin
                    $error("[t8] (%0d,%0d) byte_addr=%0d idx=0x%02x got rgb=(%02x,%02x,%02x) expected (%02x,%02x,%02x)",
                           xx, yy, xx, expected,
                           cap_r[yy][xx], cap_g[yy][xx], cap_b[yy][xx],
                           expected, expected, expected);
                    errors = errors + 1;
                end
            end
        end

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

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

endmodule : tb_gs_scanout_bram_psmt8
