// retroDE_ps2 — tb_gs_sh3_clut_sprite (Ch347 authentic acceptance, pre-fit)
//
// Renders the AUTHENTIC Silent Hill 3 PSMT8 texture + real CLUT through the proven PSMT8 indexed-texture
// DECAL path and pixel-diffs the framebuffer RGB against the software reference.
//
// Label (Codex): "authentic SH3 palettized texture + real CLUT RGB rendered through chosen sprite geometry"
// — NOT a faithful SH3 draw. DECAL/opaque is the primary render because SH3's real CLUT alpha (~0x04) is
// visibility-hostile under source-over; the CLUT RGB bytes are authentic + untouched, alpha is IGNORED by the
// DECAL render mode (never rewritten). Assets from tools/gs_extract_sh3_clut.py (LOCAL/gitignored).
//
// Datapath: PRIM(SPRITE+TME, ABE=0) + TEX0(PSM=PSMT8, TFX=DECAL, TCC=1) + 1:1 UV over a 128x128 sprite
//   -> gs_texture_unit fetches the 8-bit index, clut_stub maps index->PSMCT32 (Ch296) -> DECAL emit.
// Registered read2 (TEX_RD_REGISTERED=1, board model). CLUT programmed directly into clut_stub (the board
// path loads it via clut_loader_stub on a CLD!=0 TEX0; proven separately).
//
// PASS: every framebuffer pixel RGB == sh3_ref.mem RGB (= CLUT[index]); >100 distinct colors (real palette).

`timescale 1ns/1ps

module tb_gs_sh3_clut_sprite;
    localparam logic [5:0] PSMT8 = 6'h13;
    localparam int W = 128, H = 128;
    localparam int FB_STRIDE = W*4;                 // 512 B/row
    localparam logic [31:0] TBP = 32'd256;          // texture base: byte 256*256=65536 (just past 128x128 FB)
    localparam logic [31:0] TEX_BASE = TBP*32'd256;

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

    logic        gif_reg_wr_en; logic [7:0] gif_reg_num; logic [63:0] gif_reg_data;
    logic [7:0]  bg_r,bg_g,bg_b;
    logic [63:0] pmode_q,dispfb1_q,display1_q,prim_q,rgbaq_q,xyz2_q,xyzf2_q,frame_1_q,zbuf_1_q,tex0_1_q;
    logic [13:0] tex0_1_cbp_q; logic [3:0] tex0_1_cpsm_q; logic tex0_1_csm_q;
    logic [4:0]  tex0_1_csa_q; logic [2:0] tex0_1_cld_q; logic tex0_1_wr_q;
    logic [63:0] bitbltbuf_q,trxpos_q,trxreg_q,trxdir_q; logic trxdir_wr_q;
    logic        prim_complete; logic [31:0] prim_complete_count;
    logic [63:0] prim_v0_q,prim_v1_q,prim_v2_q,prim_color_q,prim_color_v0_q,prim_color_v1_q,prim_color_v2_q;
    trace_pkg::vertex_t prim_v0_decoded_q,prim_v1_decoded_q,prim_v2_decoded_q;
    trace_pkg::color_t  prim_v0_color_decoded_q,prim_v1_color_decoded_q,prim_v2_color_decoded_q;
    logic        pixel_emit; logic [31:0] pixel_emit_count; logic [11:0] pixel_x_q,pixel_y_q;
    logic [63:0] pixel_color_q; logic [8:0] pixel_fbp_q; logic [5:0] pixel_fbw_q,pixel_psm_q; logic [31:0] pixel_fb_addr_q;
    logic        raster_pixel_emit; logic [31:0] raster_pixel_emit_count; logic [11:0] raster_pixel_x_q,raster_pixel_y_q;
    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,raster_overflow,raster_fifo_full,raster_degenerate;
    logic        tex_rd_en; logic [31:0] tex_rd_addr,tex_rd_data;
    logic        fb_rd_en;  logic [31:0] fb_rd_addr,fb_rd_data;
    logic [7:0]  clut_rd_idx; logic [31:0] clut_rd_data;
    logic        gs_ev_valid; trace_pkg::subsys_e gs_ev_subsys; trace_pkg::event_e gs_ev_event;
    logic [63:0] gs_ev_arg0,gs_ev_arg1,gs_ev_arg2,gs_ev_arg3; logic [31:0] gs_ev_flags;

    gs_stub #(.TEX_RD_REGISTERED(1'b1), .FB_RD_REGISTERED(1'b1), .Z_RD_REGISTERED(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_r), .bg_g(bg_g), .bg_b(bg_b),
        .pmode_q(pmode_q), .dispfb1_q(dispfb1_q), .display1_q(display1_q),
        .prim_q(prim_q), .rgbaq_q(rgbaq_q), .xyz2_q(xyz2_q), .xyzf2_q(xyzf2_q),
        .frame_1_q(frame_1_q), .zbuf_1_q(zbuf_1_q), .tex0_1_q(tex0_1_q),
        .tex0_1_cbp_q(tex0_1_cbp_q), .tex0_1_cpsm_q(tex0_1_cpsm_q), .tex0_1_csm_q(tex0_1_csm_q),
        .tex0_1_csa_q(tex0_1_csa_q), .tex0_1_cld_q(tex0_1_cld_q), .tex0_1_wr_q(tex0_1_wr_q),
        .bitbltbuf_q(bitbltbuf_q), .trxpos_q(trxpos_q), .trxreg_q(trxreg_q), .trxdir_q(trxdir_q), .trxdir_wr_q(trxdir_wr_q),
        .prim_complete(prim_complete), .prim_complete_count(prim_complete_count),
        .prim_v0_q(prim_v0_q), .prim_v1_q(prim_v1_q), .prim_v2_q(prim_v2_q), .prim_color_q(prim_color_q),
        .prim_color_v0_q(prim_color_v0_q), .prim_color_v1_q(prim_color_v1_q), .prim_color_v2_q(prim_color_v2_q),
        .prim_v0_decoded_q(prim_v0_decoded_q), .prim_v1_decoded_q(prim_v1_decoded_q), .prim_v2_decoded_q(prim_v2_decoded_q),
        .prim_v0_color_decoded_q(prim_v0_color_decoded_q), .prim_v1_color_decoded_q(prim_v1_color_decoded_q), .prim_v2_color_decoded_q(prim_v2_color_decoded_q),
        .pixel_emit(pixel_emit), .pixel_emit_count(pixel_emit_count), .pixel_x_q(pixel_x_q), .pixel_y_q(pixel_y_q),
        .pixel_color_q(pixel_color_q), .pixel_fbp_q(pixel_fbp_q), .pixel_fbw_q(pixel_fbw_q), .pixel_psm_q(pixel_psm_q), .pixel_fb_addr_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_x_q), .raster_pixel_y_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_overflow), .raster_fifo_full(raster_fifo_full), .raster_degenerate(raster_degenerate),
        .tex_rd_en(tex_rd_en), .tex_rd_addr(tex_rd_addr), .tex_rd_data(tex_rd_data),
        .fb_rd_en(fb_rd_en), .fb_rd_addr(fb_rd_addr), .fb_rd_data(fb_rd_data),
        .clut_rd_idx(clut_rd_idx), .clut_rd_data(clut_rd_data),
        .ev_valid(gs_ev_valid), .ev_subsys(gs_ev_subsys), .ev_event(gs_ev_event),
        .ev_arg0(gs_ev_arg0), .ev_arg1(gs_ev_arg1), .ev_arg2(gs_ev_arg2), .ev_arg3(gs_ev_arg3), .ev_flags(gs_ev_flags)
    );

    // CLUT (programmed directly from sh3_clut.mem)
    logic clut_we; logic [7:0] clut_widx; logic [31:0] clut_wdata;
    clut_stub u_clut (.clk(clk), .rst_n(rst_n),
        .write_en(clut_we), .write_idx(clut_widx), .write_data(clut_wdata),
        .read_idx(8'd0), .read_data(), .tex_read_idx(clut_rd_idx), .tex_read_data(clut_rd_data));

    // VRAM 128 KiB: 128x128 PSMCT32 FB (64 KiB) + 128x128 PSMT8 texture (16 KiB) above it
    logic [31:0] vram_read_addr, vram_read_data, rd2_addr, rd2_data, rd2_data_reg;
    vram_stub #(.BYTES(131072)) u_vram (.clk(clk), .rst_n(rst_n),
        .write_en(raster_pixel_emit), .write_addr(raster_pixel_fb_addr_q), .write_data(raster_pixel_color_q[31:0]),
        .write_be(raster_pixel_be_q), .write_mask(raster_pixel_mask_q),
        .read_addr(vram_read_addr), .read_data(vram_read_data), .read2_addr(rd2_addr), .read2_data(rd2_data));
    assign rd2_addr = fb_rd_en ? fb_rd_addr : tex_rd_addr;
    always_ff @(posedge clk) rd2_data_reg <= rd2_data;
    assign tex_rd_data = rd2_data_reg; assign fb_rd_data = rd2_data_reg;

    // fixtures
    logic [31:0] tex_words [0:(W*H/4)-1];
    logic [31:0] clut_mem  [0:255];
    logic [31:0] ref_mem   [0:(W*H)-1];

    localparam logic [7:0] R_PRIM=8'h00,R_RGBAQ=8'h01,R_UV=8'h03,R_XYZ2=8'h05,R_TEX0_1=8'h06,R_FRAME_1=8'h4C;
    // PRIM: SPRITE(6) + TME(bit4), ABE=0 -> DECAL textured sprite (the proven Ch296 indexed path)
    localparam logic [63:0] PRIM_SPR_TEX = 64'd6 | (64'd1<<4);
    // FRAME_1: FBP=0, FBW=2 (128px), PSM=PSMCT32
    localparam logic [63:0] FRAME_1_FB0 = 64'h0000_0000_0002_0000;
    // TEX0_1: TBP0, TBW=2, PSM=PSMT8, TW=7 TH=7 (128), TCC=1, TFX=1(DECAL)
    localparam logic [63:0] TEX0_VAL = TBP[13:0] | (64'd2<<14) | (64'(PSMT8)<<20)
                                     | (64'd7<<26) | (64'd7<<30) | (64'd1<<34) | (64'd1<<35);
    localparam logic [63:0] WHITE_RGBAQ = 64'h0000_0000_8080_8080;

    task automatic step_drive(input logic wr,input logic [7:0] n,input logic [63:0] dta);
        @(negedge clk); gif_reg_wr_en=wr; gif_reg_num=n; gif_reg_data=dta; @(posedge clk); endtask
    task automatic drive_reg(input logic [7:0] n,input logic [63:0] dta); step_drive(1'b1,n,dta); endtask
    task automatic drive_idle(); step_drive(1'b0,8'd0,64'd0); endtask
    function automatic logic [63:0] xyz2_data(input logic [11:0] xi,input logic [11:0] yi); return {32'd0,yi,4'd0,xi,4'd0}; endfunction
    function automatic logic [63:0] uv_data(input int ui,input int vi); return {36'd0,14'(vi)<<4,14'(ui)<<4}; endfunction
    task automatic vram_word(input logic [31:0] a,output logic [31:0] w); vram_read_addr=a; #1; w=vram_read_data; endtask

    int errors, x, y, i; logic [31:0] got, exp;

    initial begin
        rst_n=1'b0; gif_reg_wr_en=1'b0; gif_reg_num=0; gif_reg_data=0; vram_read_addr=0;
        clut_we=1'b0; clut_widx=0; clut_wdata=0; errors=0;
        tex_words[0]='x;
        $readmemh("../../data/top_psmct32_raster_demo/sh3_tex_idx.mem", tex_words);
        $readmemh("../../data/top_psmct32_raster_demo/sh3_clut.mem", clut_mem);
        $readmemh("../../data/top_psmct32_raster_demo/sh3_ref.mem", ref_mem);
        // LOCAL/gitignored dump-derived assets (run tools/gs_extract_sh3_clut.py). Skip gracefully if absent
        // so an on-demand run without the SH3 dump doesn't masquerade as a failure (NOT in the regression).
        if (tex_words[0] === 32'bx) begin
            $display("[tb_gs_sh3_clut_sprite] SKIP — sh3_*.mem absent (run tools/gs_extract_sh3_clut.py with the local SH3 dump)");
            $finish;
        end

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

        // load the PSMT8 texture into VRAM (linear, 4 indices/word) at TEX_BASE
        for (i=0; i<W*H/4; i++) begin
            u_vram.mem[TEX_BASE + i*4 + 0] = tex_words[i][7:0];
            u_vram.mem[TEX_BASE + i*4 + 1] = tex_words[i][15:8];
            u_vram.mem[TEX_BASE + i*4 + 2] = tex_words[i][23:16];
            u_vram.mem[TEX_BASE + i*4 + 3] = tex_words[i][31:24];
        end
        // program the CLUT directly (board path uses clut_loader_stub on CLD!=0; proven separately)
        for (i=0; i<256; i++) begin
            @(negedge clk); clut_we=1'b1; clut_widx=8'(i); clut_wdata=clut_mem[i];
        end
        @(negedge clk); clut_we=1'b0;

        // draw the 128x128 DECAL textured sprite (UV 1:1 -> u=x, v=y)
        drive_reg(R_PRIM, PRIM_SPR_TEX);
        drive_reg(R_FRAME_1, FRAME_1_FB0);
        drive_reg(R_TEX0_1, TEX0_VAL);
        drive_reg(R_RGBAQ, WHITE_RGBAQ);
        drive_reg(R_UV, uv_data(0,0));
        drive_reg(R_XYZ2, xyz2_data(12'd0,12'd0));
        drive_reg(R_RGBAQ, WHITE_RGBAQ);
        drive_reg(R_UV, uv_data(W-1,H-1));
        drive_reg(R_XYZ2, xyz2_data(12'(W-1),12'(H-1)));
        drive_idle();

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

        if (raster_pixel_emit_count != W*H) begin
            $error("emit count %0d != %0d", raster_pixel_emit_count, W*H); errors++;
        end
        // pixel-diff: FB RGB == CLUT[idx] RGB (authentic art). Compare low 24 bits (DECAL alpha not the claim).
        begin
            logic [23:0] first_rgb; int varied;
            varied=0; first_rgb=24'd0;
            for (y=0; y<H; y++) for (x=0; x<W; x++) begin
                vram_word((y*FB_STRIDE)+(x*4), got);
                exp = ref_mem[y*W+x];
                if (got[23:0] !== exp[23:0]) begin
                    if (errors < 12)
                        $error("FB(%0d,%0d) RGB got=%06x exp=%06x", x, y, got[23:0], exp[23:0]);
                    errors++;
                end
                if (x==0 && y==0) first_rgb = got[23:0];
                else if (got[23:0] !== first_rgb) varied++;
            end
            // variety proxy (iverilog 12 has no assoc-array): a collapsed CLUT would make every pixel identical.
            $display("[tb_gs_sh3_clut_sprite] %0dx%0d emits=%0d varied_px=%0d errors=%0d",
                     W,H, raster_pixel_emit_count, varied, errors);
            if (varied < 1000) begin $error("only %0d varied pixels — CLUT indirection likely collapsed", varied); errors++; end
        end

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

    initial begin #20000000; $error("[tb_gs_sh3_clut_sprite] timeout"); $finish; end
endmodule : tb_gs_sh3_clut_sprite
