// retroDE_ps2 — tb_gs_scanout_psmt8 (Ch96)
//
// Locks the contract for DISPFB1.PSM=PSMT8 (=0x13) scanout.
// PSMT8 stores 1 byte per pixel in VRAM. Without a CLUT (Ch97
// candidate), gs_pcrtc_stub surfaces the 8-bit index as
// grayscale R=G=B=index so the storage lane is visually
// verifiable.
//
// This TB exercises the PSMT8 path WITH a non-trivial display
// window AND non-trivial horizontal magnification, proving:
//   - byte stride = 1 byte/pixel (FBW=1 → 64 bytes/row)
//   - DISPLAY1.DX/DY/DW/DH still gate the window
//   - DISPLAY1.MAGH/MAGV still scale the VRAM coord lookup
//
// gs_stub raster channel doesn't emit PSMT8 yet (Ch97 will
// extend the write side), so this TB bypasses gs_stub and
// drives vram_stub.write_* directly.
//
// Setup:
//   VRAM populated with a 4×4 PSMT8 sprite at indices
//     row 0: 0x10 0x11 0x12 0x13 at bytes 0..3
//     row 1: 0x14 0x15 0x16 0x17 at bytes 64..67
//     row 2: 0x18 0x19 0x1A 0x1B at bytes 128..131
//     row 3: 0x1C 0x1D 0x1E 0x1F at bytes 192..195
//   Each row's 4 bytes are written as one 32-bit word with
//   write_be=4'b1111. Adjacent bytes don't conflict because the
//   sprite starts at a 4-byte-aligned address per row.
//
//   DISPFB1: FBP=0, FBW=1, PSM=PSMT8 (0x13), DBX=DBY=0.
//   DISPLAY1: DX=4, DY=2, DW=7 (8 wide), DH=3 (4 tall),
//             MAGH=1 (2× horizontal), MAGV=0 (1× vertical).
//   Window covers displayed (4..11, 2..5) = 8×4 pixels.
//   With MAGH=1, each VRAM column shows for 2 displayed pixels.
//   So a 4-VRAM-wide sprite fills the 8-pixel-wide window.
//
// Expected per displayed (x, y):
//   if x ∈ [4,11] AND y ∈ [2,5]:
//     vram_x = (x - 4) / 2          (range 0..3)
//     vram_y = y - 2                (range 0..3)
//     idx    = 0x10 + (vram_y * 4 + vram_x)
//     RGB    = (idx, idx, idx)      (grayscale)
//   else: RGB = (0, 0, 0)

`timescale 1ns/1ps

module tb_gs_scanout_psmt8;

    localparam int PCRTC_H_ACTIVE = 16;
    localparam int PCRTC_V_ACTIVE = 8;

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

    // TB-driven privileged-register-shaped feeds (no gs_stub).
    logic [63:0] pmode_q;
    logic [63:0] dispfb1_q;
    logic [63:0] display1_q;

    logic        vram_we;
    logic [31:0] vram_waddr;
    logic [31:0] vram_wdata;
    logic [3:0]  vram_wbe;
    logic [31:0] vram_raddr;
    logic [31:0] vram_rdata;

    vram_stub #(.BYTES(4096)) u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (vram_we),
        .write_addr(vram_waddr),
        .write_data(vram_wdata),
        .write_be  (vram_wbe),
        .write_mask(32'hFFFF_FFFF),
        .read_addr (vram_raddr),
        .read_data (vram_rdata),
        .read2_addr(32'd0),
        .read2_data()
    );

    logic       hsync_o, vsync_o, de_o;
    logic [7:0] r_o, g_o, b_o;
    logic                pcrtc_ev_valid;
    trace_pkg::subsys_e  pcrtc_ev_subsys;
    trace_pkg::event_e   pcrtc_ev_event;
    logic [63:0]         pcrtc_ev_arg0, pcrtc_ev_arg1;
    logic [63:0]         pcrtc_ev_arg2, pcrtc_ev_arg3;
    logic [31:0]         pcrtc_ev_flags;

    gs_pcrtc_stub #(
        .H_ACTIVE(PCRTC_H_ACTIVE), .H_FRONT(1), .H_SYNC(1), .H_BACK(1),
        .V_ACTIVE(PCRTC_V_ACTIVE), .V_FRONT(1), .V_SYNC(1), .V_BACK(1)
    ) u_pcrtc (
        .clk(clk), .rst_n(rst_n),
        .pmode_q       (pmode_q),
        .dispfb1_q     (dispfb1_q),
        .display1_q    (display1_q),
        .vram_read_addr(vram_raddr),
        .vram_read_data(vram_rdata),
        .clut_enable   (1'b0),
        .clut_csa      (5'd0),
        .clut_read_idx (),
        .clut_read_data(32'd0),
        .hsync(hsync_o), .vsync(vsync_o), .de(de_o),
        .r(r_o), .g(g_o), .b(b_o),
        .ev_valid(pcrtc_ev_valid),
        .ev_subsys(pcrtc_ev_subsys),
        .ev_event(pcrtc_ev_event),
        .ev_arg0(pcrtc_ev_arg0), .ev_arg1(pcrtc_ev_arg1),
        .ev_arg2(pcrtc_ev_arg2), .ev_arg3(pcrtc_ev_arg3),
        .ev_flags(pcrtc_ev_flags)
    );

    logic [7:0] cap_r [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
    logic [7:0] cap_g [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
    logic [7:0] cap_b [0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];
    logic       cap_de[0:PCRTC_V_ACTIVE-1][0:PCRTC_H_ACTIVE-1];

    int errors;
    bit capture_armed;

    initial begin
        for (int y = 0; y < PCRTC_V_ACTIVE; y++)
            for (int x = 0; x < PCRTC_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
        errors        = 0;
        capture_armed = 1'b0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && capture_armed && de_o
            && (u_pcrtc.vcnt < PCRTC_V_ACTIVE)
            && (u_pcrtc.hcnt < PCRTC_H_ACTIVE)) begin
            cap_r [u_pcrtc.vcnt][u_pcrtc.hcnt] <= r_o;
            cap_g [u_pcrtc.vcnt][u_pcrtc.hcnt] <= g_o;
            cap_b [u_pcrtc.vcnt][u_pcrtc.hcnt] <= b_o;
            cap_de[u_pcrtc.vcnt][u_pcrtc.hcnt] <= 1'b1;
        end
    end

    task automatic vram_write32(input logic [31:0] addr,
                                input logic [31:0] data,
                                input logic [3:0]  be);
        @(negedge clk);
        vram_we    = 1'b1;
        vram_waddr = addr;
        vram_wdata = data;
        vram_wbe   = be;
        @(posedge clk);
        @(negedge clk);
        vram_we    = 1'b0;
        vram_waddr = 32'd0;
        vram_wdata = 32'd0;
        vram_wbe   = 4'b0000;
    endtask

    localparam int SPRITE_W   = 4;
    localparam int SPRITE_H   = 4;
    localparam logic [7:0] BASE_IDX = 8'h10;
    // FBW=1 → 64 bytes/row stride for PSMT8 (1 byte/pixel × 64 px/FBW).
    localparam int ROW_STRIDE = 64;

    // DISPFB1: FBP=0, FBW=1, PSM=PSMT8 (= 0x13).
    //   FBW [14:9]   = 1   → 0x200
    //   PSM [19:15]  = 0x13 → bits 18,17,16,15 = 1,0,0,1; total bit pattern at 19:15 = 5'b10011 → 0x9_8000
    //     5'b10011 << 15 = bit15..bit19 → 0x0009_8000 / wait let me recompute
    //     PSM is bits [19:15], 5-bit. Value 0x13 = 5'b10011.
    //     bit15=1, bit16=1, bit17=0, bit18=0, bit19=1
    //     0x13 << 15 = 0x13 * 0x8000 = 0x9_8000
    localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0009_8200;

    // DISPLAY1: DX=4, DY=2, DW=7, DH=3, MAGH=1, MAGV=0.
    //   DX   [11:0]   = 4
    //   DY   [22:12]  = 2  → bit 12
    //   MAGH [26:23]  = 1  → bit 23
    //   MAGV [28:27]  = 0
    //   DW   [43:32]  = 7  → bit 32
    //   DH   [54:44]  = 3  → bit 44
    localparam logic [63:0] DISPLAY1_VAL =
          64'(12'd4)
        | (64'(11'd2) << 12)
        | (64'(4'd1)  << 23)
        | (64'(12'd7) << 32)
        | (64'(11'd3) << 44);

    localparam logic [63:0] PMODE_EN1 = 64'h0000_0000_0000_0001;

    localparam int WIN_DX   = 4;
    localparam int WIN_DY   = 2;
    localparam int WIN_W    = 8;   // DW + 1
    localparam int WIN_H    = 4;   // DH + 1
    localparam int MAGH_FAC = 2;   // MAGH + 1
    localparam int MAGV_FAC = 1;   // MAGV + 1

    initial begin
        rst_n      = 1'b0;
        pmode_q    = 64'd0;
        dispfb1_q  = 64'd0;
        display1_q = 64'd0;
        vram_we    = 1'b0;
        vram_waddr = 32'd0;
        vram_wdata = 32'd0;
        vram_wbe   = 4'b0000;

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

        // Populate VRAM with the 4×4 PSMT8 sprite. Each row's 4
        // indices pack into one 32-bit word: data[7:0]=col0,
        // [15:8]=col1, [23:16]=col2, [31:24]=col3 (little-endian
        // byte order matches vram_stub's mem layout).
        for (int y = 0; y < SPRITE_H; y++) begin
            logic [7:0] i0, i1, i2, i3;
            logic [31:0] data;
            int row_base;
            i0 = BASE_IDX + 8'(y * SPRITE_W + 0);
            i1 = BASE_IDX + 8'(y * SPRITE_W + 1);
            i2 = BASE_IDX + 8'(y * SPRITE_W + 2);
            i3 = BASE_IDX + 8'(y * SPRITE_W + 3);
            data = {i3, i2, i1, i0};
            row_base = y * ROW_STRIDE;
            vram_write32(row_base, data, 4'b1111);
        end

        // Configure scanout. dispfb1_q + display1_q first; then
        // PMODE.EN1 last, mirroring the canonical driver order.
        dispfb1_q  = DISPFB1_VAL;
        display1_q = DISPLAY1_VAL;
        @(posedge clk);

        if (dispfb1_q[19:15] !== 5'h13) begin
            $error("DISPFB1.PSM=0x%02x (expected 0x13 PSMT8)", dispfb1_q[19:15]);
            errors = errors + 1;
        end

        pmode_q = PMODE_EN1;
        @(posedge clk);

        @(posedge u_pcrtc.end_of_frame);
        @(posedge clk);
        capture_armed = 1'b1;
        @(posedge u_pcrtc.end_of_frame);
        @(posedge clk);
        capture_armed = 1'b0;

        // Per-pixel verification.
        for (int y = 0; y < PCRTC_V_ACTIVE; y++) begin
            for (int x = 0; x < PCRTC_H_ACTIVE; x++) begin
                logic [7:0] er, eg, eb;
                bit         in_window;
                int         hwin_rel, vwin_rel;
                int         vram_x, vram_y;
                logic [7:0] idx;
                bit         in_sprite;

                in_window = (x >= WIN_DX) && (x < WIN_DX + WIN_W)
                         && (y >= WIN_DY) && (y < WIN_DY + WIN_H);

                if (in_window) begin
                    hwin_rel  = x - WIN_DX;
                    vwin_rel  = y - WIN_DY;
                    vram_x    = hwin_rel / MAGH_FAC;
                    vram_y    = vwin_rel / MAGV_FAC;
                    in_sprite = (vram_x < SPRITE_W) && (vram_y < SPRITE_H);
                    idx       = BASE_IDX + 8'(vram_y * SPRITE_W + vram_x);
                end else begin
                    in_sprite = 1'b0;
                    idx       = 8'd0;
                end

                if (in_sprite) begin
                    er = idx; eg = idx; eb = idx;
                end else begin
                    er = 8'd0; eg = 8'd0; eb = 8'd0;
                end

                if (!cap_de[y][x]) begin
                    $error("(%0d,%0d) DE never asserted", x, y);
                    errors = errors + 1;
                end
                if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
                    $error("(%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x) in_window=%0d in_sprite=%0d",
                           x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x],
                           er, eg, eb, in_window, in_sprite);
                    errors = errors + 1;
                end
            end
        end

        $display("[tb_gs_scanout_psmt8] sprite=%0dx%0d window=(%0d,%0d %0dx%0d) MAGH=%0d MAGV=%0d (factors %0dx %0dx)",
                 SPRITE_W, SPRITE_H, WIN_DX, WIN_DY, WIN_W, WIN_H,
                 1, 0, MAGH_FAC, MAGV_FAC);

        if (errors == 0) $display("[tb_gs_scanout_psmt8] PASS");
        else             $display("[tb_gs_scanout_psmt8] FAIL");
        $finish;
    end

    initial begin
        #5000000;
        $error("[tb_gs_scanout_psmt8] timeout");
        $finish;
    end

endmodule : tb_gs_scanout_psmt8
