// retroDE_ps2 — tb_gs_scanout_psm16 (Ch94)
//
// Locks the contract for DISPFB1.PSM=PSMCT16 (RGB5A1) scanout.
// gs_pcrtc_stub now selects byte-offset shift and color-decode
// path based on the PSM field in DISPFB1: PSMCT32 (=0) reads 4
// bytes/pixel and uses {A, B, G, R} byte order; PSMCT16 (=2)
// reads 2 bytes/pixel and unpacks RGB5A1 with 5→8 bit-replicate
// expansion.
//
// gs_stub's raster channel still emits PSMCT32 only, so this TB
// bypasses gs_stub entirely — it instantiates only `vram_stub`
// (so we have a real RAM) and `gs_pcrtc_stub` (so we exercise
// the scanout PSM path), and drives privileged-register-shaped
// inputs from TB-side regs. That isolates the test to the
// scanout PSM decode.
//
// Setup:
//   VRAM populated with a 4×4 RGB5A1 sprite, color
//   (R5, G5, B5) = (0x10, 0x18, 0x08) → 16-bit pixel 0x2310,
//   row stride 128 bytes (FBW=1 → 64 pixels/row × 2 bytes/pixel).
//   PSM = PSMCT16 (DISPFB1[19:15] = 0x02).
//   DISPFB1.FBP=0, FBW=1. DBX=DBY=0.
//   DISPLAY1 covers full 16×8 active area, MAGH=MAGV=0 (1×).
//
// Expected:
//   R8 = {0x10, 0x10[4:2]} = {0x10, 3'b100}        = 8'h84
//   G8 = {0x18, 0x18[4:2]} = {0x18, 3'b110}        = 8'hC6
//   B8 = {0x08, 0x08[4:2]} = {0x08, 3'b010}        = 8'h42
//   At displayed (0..3, 0..3): (R, G, B) = (0x84, 0xC6, 0x42).
//   At every other displayed pixel: (0, 0, 0).

`timescale 1ns/1ps

module tb_gs_scanout_psm16;

    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;

    // Privileged-register-shaped TB regs feeding pcrtc directly.
    logic [63:0] pmode_q;
    logic [63:0] dispfb1_q;
    logic [63:0] display1_q;

    // VRAM write side: TB-driven (no gs_stub raster source).
    logic        vram_we;
    logic [31:0] vram_waddr;
    logic [31:0] vram_wdata;

    // VRAM read side: pcrtc drives the address.
    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  (4'b1111),
        .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);
        @(negedge clk);
        vram_we    = 1'b1;
        vram_waddr = addr;
        vram_wdata = data;
        @(posedge clk);
        @(negedge clk);
        vram_we    = 1'b0;
        vram_waddr = 32'd0;
        vram_wdata = 32'd0;
    endtask

    // Build a packed RGB5A1 16-bit value: A[15] B[14:10] G[9:5] R[4:0].
    function automatic logic [15:0] psm16_pack(input logic [4:0] r5,
                                               input logic [4:0] g5,
                                               input logic [4:0] b5,
                                               input logic       a1);
        return {a1, b5, g5, r5};
    endfunction

    // Sprite color.
    localparam logic [4:0] R5 = 5'h10;
    localparam logic [4:0] G5 = 5'h18;
    localparam logic [4:0] B5 = 5'h08;
    // Expected expanded 8-bit values (5→8 bit-replicate).
    localparam logic [7:0] EXP_R = {R5, R5[4:2]};
    localparam logic [7:0] EXP_G = {G5, G5[4:2]};
    localparam logic [7:0] EXP_B = {B5, B5[4:2]};

    localparam int SPRITE_W = 4;
    localparam int SPRITE_H = 4;

    // DISPFB1 PSM = PSMCT16 (5'h02), FBP=0, FBW=1.
    //   FBP[8:0]   = 0
    //   FBW[14:9]  = 1   → 0x200
    //   PSM[19:15] = 2   → 0x10000
    localparam logic [63:0] DISPFB1_VAL = 64'h0000_0000_0001_0200;

    // DISPLAY1 covers full active area, MAGH=MAGV=0.
    localparam logic [63:0] DISPLAY1_VAL =
          (64'(PCRTC_H_ACTIVE - 1) << 32)   // DW
        | (64'(PCRTC_V_ACTIVE - 1) << 44);  // DH

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

    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;

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

        // Populate VRAM with the 4×4 PSMCT16 sprite. FBW=1
        // means 64 pixels/row × 2 bytes = 128 bytes/row stride.
        // Row Y starts at byte Y*128. Each row stores 4 PSMCT16
        // pixels = 8 bytes = 2 32-bit writes.
        for (int y = 0; y < SPRITE_H; y++) begin
            logic [15:0] pix;
            logic [31:0] pair;
            int          row_base;

            pix      = psm16_pack(R5, G5, B5, 1'b0);
            pair     = {pix, pix};      // two PSMCT16 pixels packed
            row_base = y * 128;

            // Pixels (0,1) at byte row_base..row_base+3.
            vram_write32(row_base,           pair);
            // Pixels (2,3) at byte row_base+4..row_base+7.
            vram_write32(row_base + 32'd4,   pair);
        end

        // Configure scanout. Reset has dispfb1_q=display1_q=0, so
        // we must explicitly write both before enabling EN1.
        dispfb1_q  = DISPFB1_VAL;
        display1_q = DISPLAY1_VAL;
        @(posedge clk);

        // Cross-check the PSM field landed at [19:15]=2.
        if (dispfb1_q[19:15] !== 5'h02) begin
            $error("DISPFB1.PSM=%0d (expected 2 = PSMCT16)", 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;
                if (x < SPRITE_W && y < SPRITE_H) begin
                    er = EXP_R; eg = EXP_G; eb = EXP_B;
                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)",
                           x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb);
                    errors = errors + 1;
                end
            end
        end

        $display("[tb_gs_scanout_psm16] sprite=%0dx%0d RGB5A1=(%02x,%02x,%02x) → RGB8=(%02x,%02x,%02x)",
                 SPRITE_W, SPRITE_H, R5, G5, B5, EXP_R, EXP_G, EXP_B);

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

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

endmodule : tb_gs_scanout_psm16
