// retroDE_ps2 — tb_gs_raster_bram_psmt8 (Ch156)
//
// Sibling of `tb_gs_raster_bram_psmct16` for PSMT8. Drives
// gs_stub#(PSMT8_SWIZZLE=1) directly via gif_reg_wr_* and routes
// the registered raster emit signals through
// `vram_normalize_pkg::normalize_write` into `vram_bram_stub` —
// the same wrapper-site plumbing used in
// `top_psmct32_raster_demo_bram.sv` (Ch156).
//
// Why this TB exists: closes the gap that Ch155 unit-test +
// Ch156 CT16 BRAM TB opened — proves the wrapper-site plumbing
// works for the SECOND sub-word PSM (PSMT8 — single byte writes
// shifted to byte_addr[1:0]-keyed lane). PSMT4 needs the
// old-byte read pipeline (Ch157+).
//
// Phase 1 only — write-side lock. The drive splits a 16×8 PSMT8
// image into TWO 8×8 SPRITEs:
//   * Sprite A at (0,0)..(7,7) with byte 0xA5 — under the PSMT8
//     column table (yb=0..7, xb=0..7) these bytes land in lanes
//     {0, 1} of their target words.
//   * Sprite B at (8,0)..(15,7) with byte 0x5A — these land in
//     lanes {2, 3} of the SAME 32-bit words that Sprite A wrote
//     to (PSMT8 swizzle puts xb=0..7 vs xb=8..15 in different
//     byte lanes of shared words at the same yb).
//
// Verification asserts every pixel's byte landed at its swizzled
// byte address AT THE RIGHT LANE — Sprite A in the low lanes,
// Sprite B in the high lanes. With two distinct byte values
// sharing each word, lane-stomp / wrong-lane / over-wide-be
// normalizer bugs all surface (Codex Ch156 audit Medium #2).
//
// Adversarial sentinel preload: `u_vram.mem[]` is forced to a
// non-zero sentinel BEFORE the SPRITE drives so any normalize
// write that lands at the wrong byte address shows up against
// the sentinel rather than against zero.

`timescale 1ns/1ps

module tb_gs_raster_bram_psmt8;

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

    // ------------------------------------------------------------
    // gs_stub I/O — minimal subset (raster + register drive).
    // ------------------------------------------------------------
    logic        gif_reg_wr_en;
    logic [7:0]  gif_reg_num;
    logic [63:0] gif_reg_data;

    logic        raster_pixel_emit;
    logic [31:0] raster_pixel_emit_count;
    logic [63:0] raster_pixel_color_q;
    logic [31:0] raster_pixel_fb_addr_q;
    logic [3:0]  raster_pixel_be_q;
    logic [31:0] raster_pixel_mask_q;
    logic [5:0]  raster_pixel_psm_q;
    logic        raster_active;

    gs_stub #(
        .PSMCT32_SWIZZLE(1'b0),
        .PSMCT16_SWIZZLE(1'b0),
        .PSMT8_SWIZZLE  (1'b1)
    ) u_gs (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en  (1'b0),
        .reg_wr_addr(16'd0),
        .reg_wr_data(64'd0),
        .gif_reg_wr_en(gif_reg_wr_en),
        .gif_reg_num  (gif_reg_num),
        .gif_reg_data (gif_reg_data),
        .bg_r(), .bg_g(), .bg_b(),
        .pmode_q(), .dispfb1_q(), .display1_q(),
        .prim_q(), .rgbaq_q(),
        .xyz2_q(), .xyzf2_q(),
        .frame_1_q(), .zbuf_1_q(),
        .tex0_1_q(), .tex0_1_cbp_q(), .tex0_1_cpsm_q(),
        .tex0_1_csm_q(), .tex0_1_csa_q(), .tex0_1_cld_q(),
        .tex0_1_wr_q(),
        .bitbltbuf_q(), .trxpos_q(), .trxreg_q(), .trxdir_q(),
        .trxdir_wr_q(),
        .prim_complete(), .prim_complete_count(),
        .prim_v0_q(), .prim_v1_q(), .prim_v2_q(),
        .prim_color_q(),
        .prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(),
        .prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(),
        .prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(),
        .pixel_emit(), .pixel_emit_count(),
        .pixel_x_q(), .pixel_y_q(), .pixel_color_q(),
        .pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(), .pixel_fb_addr_q(),
        .raster_pixel_emit(raster_pixel_emit),
        .raster_pixel_emit_count(raster_pixel_emit_count),
        .raster_pixel_x_q(), .raster_pixel_y_q(),
        .raster_pixel_color_q(raster_pixel_color_q),
        .raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
        .raster_pixel_be_q(raster_pixel_be_q),
        .raster_pixel_mask_q(raster_pixel_mask_q),
        .raster_pixel_psm_q(raster_pixel_psm_q),
        .raster_active(raster_active),
        .raster_overflow(), .raster_degenerate(),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------
    // Wrapper-site Ch156 normalize_write plumbing.
    // ------------------------------------------------------------
    vram_normalize_pkg::norm_out_t vram_norm;
    assign vram_norm = vram_normalize_pkg::normalize_write(
        raster_pixel_fb_addr_q,
        raster_pixel_psm_q,
        raster_pixel_color_q[31:0],
        1'b0,
        8'd0
    );

    // ------------------------------------------------------------
    // vram_bram_stub. PSMT8 at FBW=2 places page (0,0) bytes at
    // [0..8191]; 8 KiB is enough.
    //
    // PSMT8 emits never enter the Ch157 PSMT4 RMW pipe — this
    // focused TB wires `raster_pixel_emit` directly to `write_en`
    // and lets `vram_norm` drive the addr/data/be triple. T4 path
    // coverage lives in `tb_gs_raster_bram_psmt4`.
    // ------------------------------------------------------------
    localparam int VRAM_BYTES = 8192;
    localparam int VRAM_WORDS = VRAM_BYTES / 4;
    localparam logic [31:0] SENTINEL = 32'hDEAD_BEEF;

    vram_bram_stub #(.BYTES(VRAM_BYTES)) u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (raster_pixel_emit),
        .write_addr(vram_norm.write_addr),
        .write_data(vram_norm.write_data),
        .write_be  (vram_norm.write_be),
        .read_addr (32'd0),
        .read_data (),
        .read_valid(),
        .read2_addr(32'd0),
        .read2_data(),
        .read2_valid()
    );

    // ------------------------------------------------------------
    // GIF register drive helper.
    // ------------------------------------------------------------
    task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
        @(negedge clk);
        gif_reg_wr_en = 1'b1;
        gif_reg_num   = num;
        gif_reg_data  = data;
        @(posedge clk);
        @(negedge clk);
        gif_reg_wr_en = 1'b0;
        gif_reg_num   = 8'd0;
        gif_reg_data  = 64'd0;
    endtask

    function automatic logic [63:0] xyz2_data(input logic [11:0] x_int,
                                              input logic [11:0] y_int);
        return {32'd0, y_int, 4'd0, x_int, 4'd0};
    endfunction

    // ------------------------------------------------------------
    // PSMT8 swizzle reference (verbatim from Ch134).
    // ------------------------------------------------------------
    function automatic int ref_block_idx8(input int by, input int bx);
        case ({by[1:0], bx[2:0]})
            5'd0:  return  0;  5'd1:  return  1;  5'd2:  return  4;  5'd3:  return  5;
            5'd4:  return 16;  5'd5:  return 17;  5'd6:  return 20;  5'd7:  return 21;
            5'd8:  return  2;  5'd9:  return  3;  5'd10: return  6;  5'd11: return  7;
            5'd12: return 18;  5'd13: return 19;  5'd14: return 22;  5'd15: return 23;
            5'd16: return  8;  5'd17: return  9;  5'd18: return 12;  5'd19: return 13;
            5'd20: return 24;  5'd21: return 25;  5'd22: return 28;  5'd23: return 29;
            5'd24: return 10;  5'd25: return 11;  5'd26: return 14;  5'd27: return 15;
            5'd28: return 26;  5'd29: return 27;  5'd30: return 30;  default: return 31;
        endcase
    endfunction

    function automatic int ref_col_idx8(input int yb, input int xb);
        case ({yb[3:0], xb[3:0]})
            8'd0:   return   0; 8'd1:   return   4; 8'd2:   return  16; 8'd3:   return  20;
            8'd4:   return  32; 8'd5:   return  36; 8'd6:   return  48; 8'd7:   return  52;
            8'd8:   return   2; 8'd9:   return   6; 8'd10:  return  18; 8'd11:  return  22;
            8'd12:  return  34; 8'd13:  return  38; 8'd14:  return  50; 8'd15:  return  54;
            8'd16:  return   8; 8'd17:  return  12; 8'd18:  return  24; 8'd19:  return  28;
            8'd20:  return  40; 8'd21:  return  44; 8'd22:  return  56; 8'd23:  return  60;
            8'd24:  return  10; 8'd25:  return  14; 8'd26:  return  26; 8'd27:  return  30;
            8'd28:  return  42; 8'd29:  return  46; 8'd30:  return  58; 8'd31:  return  62;
            8'd32:  return  33; 8'd33:  return  37; 8'd34:  return  49; 8'd35:  return  53;
            8'd36:  return   1; 8'd37:  return   5; 8'd38:  return  17; 8'd39:  return  21;
            8'd40:  return  35; 8'd41:  return  39; 8'd42:  return  51; 8'd43:  return  55;
            8'd44:  return   3; 8'd45:  return   7; 8'd46:  return  19; 8'd47:  return  23;
            8'd48:  return  41; 8'd49:  return  45; 8'd50:  return  57; 8'd51:  return  61;
            8'd52:  return   9; 8'd53:  return  13; 8'd54:  return  25; 8'd55:  return  29;
            8'd56:  return  43; 8'd57:  return  47; 8'd58:  return  59; 8'd59:  return  63;
            8'd60:  return  11; 8'd61:  return  15; 8'd62:  return  27; 8'd63:  return  31;
            8'd64:  return  96; 8'd65:  return 100; 8'd66:  return 112; 8'd67:  return 116;
            8'd68:  return  64; 8'd69:  return  68; 8'd70:  return  80; 8'd71:  return  84;
            8'd72:  return  98; 8'd73:  return 102; 8'd74:  return 114; 8'd75:  return 118;
            8'd76:  return  66; 8'd77:  return  70; 8'd78:  return  82; 8'd79:  return  86;
            8'd80:  return 104; 8'd81:  return 108; 8'd82:  return 120; 8'd83:  return 124;
            8'd84:  return  72; 8'd85:  return  76; 8'd86:  return  88; 8'd87:  return  92;
            8'd88:  return 106; 8'd89:  return 110; 8'd90:  return 122; 8'd91:  return 126;
            8'd92:  return  74; 8'd93:  return  78; 8'd94:  return  90; 8'd95:  return  94;
            8'd96:  return  65; 8'd97:  return  69; 8'd98:  return  81; 8'd99:  return  85;
            8'd100: return  97; 8'd101: return 101; 8'd102: return 113; 8'd103: return 117;
            8'd104: return  67; 8'd105: return  71; 8'd106: return  83; 8'd107: return  87;
            8'd108: return  99; 8'd109: return 103; 8'd110: return 115; 8'd111: return 119;
            8'd112: return  73; 8'd113: return  77; 8'd114: return  89; 8'd115: return  93;
            8'd116: return 105; 8'd117: return 109; 8'd118: return 121; 8'd119: return 125;
            8'd120: return  75; 8'd121: return  79; 8'd122: return  91; 8'd123: return  95;
            8'd124: return 107; 8'd125: return 111; 8'd126: return 123; 8'd127: return 127;
            default: return 0;
        endcase
    endfunction

    function automatic logic [31:0] ref_addr8(
        input int fbp_v, input int fbw_v, input int x_v, input int y_v);
        int page_x, page_y, bw_pg_v, page_idx, page_base;
        int by, bx, blk_idx, xb, yb, byte_idx;
        int addr_v;
        page_x   = x_v / 128;
        page_y   = y_v / 64;
        bw_pg_v  = fbw_v / 2;
        page_idx = page_y * bw_pg_v + page_x;
        page_base = fbp_v * 2048 + page_idx * 8192;
        by  = (y_v % 64)  / 16;
        bx  = (x_v % 128) / 16;
        blk_idx = ref_block_idx8(by, bx);
        xb = x_v % 16;
        yb = y_v % 16;
        byte_idx = ref_col_idx8(yb, xb);
        addr_v = page_base + blk_idx * 256 + byte_idx;
        return addr_v[31:0];
    endfunction

    // ------------------------------------------------------------
    // Test stimulus.
    //   Two distinct PSMT8 SPRITEs at FBP=0/FBW=2:
    //     A: (0,0)..(7,7),  RGBAQ_A → byte 0xA5.
    //     B: (8,0)..(15,7), RGBAQ_B → byte 0x5A.
    //   xb=0..7 vs xb=8..15 land in different byte lanes of shared
    //   words, so this double-color drive pins all 4 byte lanes.
    // ------------------------------------------------------------
    localparam logic [7:0]  R_PRIM     = 8'h00;
    localparam logic [7:0]  R_RGBAQ    = 8'h01;
    localparam logic [7:0]  R_XYZ2     = 8'h05;
    localparam logic [7:0]  R_FRAME_1  = 8'h4C;

    localparam logic [63:0] PRIM_SPRITE = 64'd6;
    localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_1302_0000;

    // RGBAQ_A: R=0xA5 → byte index 0xA5.
    localparam logic [63:0] RGBAQ_A     = 64'h0000_0000_0000_00A5;
    localparam logic [7:0]  IDX_A       = 8'hA5;

    // RGBAQ_B: R=0x5A → byte index 0x5A.
    localparam logic [63:0] RGBAQ_B     = 64'h0000_0000_0000_005A;
    localparam logic [7:0]  IDX_B       = 8'h5A;

    int errors;

    initial begin
        rst_n          = 1'b0;
        gif_reg_wr_en  = 1'b0;
        gif_reg_num    = 8'd0;
        gif_reg_data   = 64'd0;
        errors         = 0;

        // Sentinel preload — see CT16 sibling for rationale.
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = SENTINEL;
        #1;
        for (int i = 0; i < VRAM_WORDS; i++) u_vram.mem[i] = SENTINEL;

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

        // ---------- Sprite A — left half, byte 0xA5 ----------
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_FRAME_1, FRAME_1_VAL);
        drive_reg(R_RGBAQ,   RGBAQ_A);
        drive_reg(R_XYZ2,    xyz2_data(12'd0, 12'd0));
        drive_reg(R_XYZ2,    xyz2_data(12'd7, 12'd7));

        wait (raster_active == 1'b1);
        wait (raster_active == 1'b0);
        repeat (10) @(posedge clk);

        // ---------- Sprite B — right half, byte 0x5A ----------
        drive_reg(R_PRIM,    PRIM_SPRITE);
        drive_reg(R_RGBAQ,   RGBAQ_B);
        drive_reg(R_XYZ2,    xyz2_data(12'd8,  12'd0));
        drive_reg(R_XYZ2,    xyz2_data(12'd15, 12'd7));

        wait (raster_active == 1'b1);
        wait (raster_active == 1'b0);
        repeat (10) @(posedge clk);

        if (raster_pixel_emit_count != 32'(8 * 8 + 8 * 8)) begin
            $error("expected %0d raster emits, got %0d",
                   8 * 8 + 8 * 8, raster_pixel_emit_count);
            errors = errors + 1;
        end

        // Per-pixel byte verification at swizzled byte addresses.
        // Lane select via byte_addr[1:0].
        for (int yy = 0; yy < 8; yy++) begin
            for (int xx = 0; xx < 16; xx++) begin
                logic [31:0] addr;
                logic [31:0] word;
                logic [7:0]  got, expected;
                addr = ref_addr8(0, 2, xx, yy);
                word = u_vram.mem[addr >> 2];
                case (addr[1:0])
                    2'b00: got = word[ 7: 0];
                    2'b01: got = word[15: 8];
                    2'b10: got = word[23:16];
                    2'b11: got = word[31:24];
                endcase
                expected = (xx < 8) ? IDX_A : IDX_B;
                if (got !== expected) begin
                    $error("[bram-t8] (%0d,%0d) @addr 0x%08x got 0x%02x expected 0x%02x (word=0x%08x)",
                           xx, yy, addr, got, expected, word);
                    errors = errors + 1;
                end
            end
        end

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

    initial begin
        #2000000;
        $error("[tb_gs_raster_bram_psmt8] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_raster_bram_psmt8
