// retroDE_ps2 — tb_gs_image_xfer_psmt8 (Ch117)
//
// Locks the contract for `gif_image_xfer_stub`'s PSMT8 path —
// the third supported PSM (PSMCT32 was Ch110, PSMCT16 was Ch116).
// PSMT8 packs 1 byte/pixel (an 8-bit CLUT index), so each 128-bit
// IMAGE qword carries 16 pixels. The engine emits one 8-bit
// pixel per cycle to vram_stub with `write_be = 4'b0001` and the
// pixel value in the LOW byte of `write_data`; vram_stub commits
// `mem[write_addr] <= write_data[7:0]` at the exact byte address
// (no alignment requirement).
//
// Setup:
//   No GS pipeline, no gif_packed_stub. Just gs_stub (for its
//   TRX-register latches + trxdir_wr_q pulse) + gif_image_xfer_stub
//   + vram_stub. TB drives BITBLTBUF/TRXPOS/TRXREG/TRXDIR via
//   gs_stub.gif_reg_wr_*, then hand-pumps 2 IMAGE qwords (32
//   PSMT8 pixels = 16 px/qword × 2) into xfer.data_*.
//
//   BITBLTBUF: DBP=4 (dest base = 4*256 = 1024 = 0x400 bytes),
//              DBW=1 (64 px/row → row stride = 1*64*1 = 64 bytes),
//              DPSM=PSMT8 (=0x13).
//   TRXPOS:    DSAX=DSAY=0.
//   TRXREG:    RRW=8, RRH=4 → 8×4 = 32 PSMT8 pixels = 2 qwords.
//   TRXDIR:    XDIR=0 (host→local).
//
//   Pixel pattern: pixel(x, y) = 8'h(y*16 + x) — distinct per-
//   pixel values (0x00..0x37 across the 8×4 rect) so a wrong-
//   byte-lane commit shows up unambiguously.
//
// PASS criteria:
//   - 1 trxdir_wr_q pulse arms the engine.
//   - 32 vram writes from gif_image_xfer_stub.
//   - Every emit has be == 4'b0001, mask == 0xFFFFFFFF.
//   - Every pixel reads back at byte address
//       addr(x, y) = DEST_BASE + y*ROW_STRIDE + x
//     with the right 8-bit value.
//   - Bytes immediately RIGHT of the rect on each row stay 0x00
//     (no leak past RRW).
//   - Bytes BEFORE / AFTER the destination region stay 0x00.

`timescale 1ns/1ps

module tb_gs_image_xfer_psmt8;

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

    // ------------------------------------------------------------
    // gs_stub — only used for TRX-register latches + trxdir_wr_q.
    // ------------------------------------------------------------
    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;
    logic [63:0]         prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_q;
    logic [63:0]         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;
    logic [63:0]         prim_color_q;
    logic [63:0]         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;
    logic                raster_overflow;
    logic                raster_degenerate;
    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 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_degenerate(raster_degenerate),
        .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)
    );

    // ------------------------------------------------------------
    // gif_image_xfer_stub
    // ------------------------------------------------------------
    logic         xfer_data_valid;
    logic [127:0] xfer_data_qword;
    logic         xfer_data_last;
    logic         xfer_data_ready;
    logic         xfer_we;
    logic [31:0]  xfer_waddr;
    logic [31:0]  xfer_wdata;
    logic [3:0]   xfer_wbe;
    logic [31:0]  xfer_wmask;
    logic         xfer_busy;

    gif_image_xfer_stub u_xfer (
        .clk(clk), .rst_n(rst_n),
        .trxdir_wr_pulse(trxdir_wr_q),
        .trxdir(trxdir_q),
        .bitbltbuf(bitbltbuf_q),
        .trxpos(trxpos_q),
        .trxreg(trxreg_q),
        .data_valid(xfer_data_valid),
        .data_qword(xfer_data_qword),
        .data_last (xfer_data_last),
        .data_ready(xfer_data_ready),
        .vram_we   (xfer_we),
        .vram_waddr(xfer_waddr),
        .vram_wdata(xfer_wdata),
        .vram_wbe  (xfer_wbe),
        .vram_wmask(xfer_wmask),
        .busy      (xfer_busy)
    );

    // ------------------------------------------------------------
    // vram_stub
    // ------------------------------------------------------------
    logic [31:0] vram_raddr;
    logic [31:0] vram_rdata;

    vram_stub #(.BYTES(4096)) u_vram (
        .clk(clk), .rst_n(rst_n),
        .write_en  (xfer_we),
        .write_addr(xfer_waddr),
        .write_data(xfer_wdata),
        .write_be  (xfer_wbe),
        .write_mask(xfer_wmask),
        .read_addr (vram_raddr),
        .read_data (vram_rdata),
        .read2_addr(32'd0),
        .read2_data()
    );

    // ------------------------------------------------------------
    // Counters + invariants observers.
    // ------------------------------------------------------------
    int  errors;
    int  xfer_writes_seen;
    int  trxdir_arms_seen;

    initial begin
        errors           = 0;
        xfer_writes_seen = 0;
        trxdir_arms_seen = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && trxdir_wr_q)
            trxdir_arms_seen <= trxdir_arms_seen + 1;
        if (rst_n && xfer_we) begin
            xfer_writes_seen <= xfer_writes_seen + 1;
            if (xfer_wbe !== 4'b0001) begin
                $error("xfer write %0d: be=%b (expected 4'b0001 for PSMT8)",
                       xfer_writes_seen, xfer_wbe);
                errors <= errors + 1;
            end
            if (xfer_wmask !== 32'hFFFF_FFFF) begin
                $error("xfer write %0d: mask=0x%08x (expected 0xFFFFFFFF)",
                       xfer_writes_seen, xfer_wmask);
                errors <= errors + 1;
            end
        end
    end

    // ------------------------------------------------------------
    // GIF / TRX register helpers.
    // ------------------------------------------------------------
    task automatic step_drive(input logic        wr_en,
                              input logic [7:0]  num,
                              input logic [63:0] data);
        @(negedge clk);
        gif_reg_wr_en = wr_en;
        gif_reg_num   = num;
        gif_reg_data  = data;
        @(posedge clk);
    endtask

    task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
        step_drive(1'b1, num, data);
    endtask

    task automatic drive_idle();
        step_drive(1'b0, 8'd0, 64'd0);
    endtask

    function automatic logic [63:0] bitbltbuf_pack(
        input logic [13:0] dbp_v,
        input logic [5:0]  dbw_v,
        input logic [5:0]  dpsm_v);
        logic [63:0] v;
        v = 64'd0;
        v[45:32] = dbp_v;
        v[53:48] = dbw_v;
        v[61:56] = dpsm_v;
        return v;
    endfunction
    function automatic logic [63:0] trxpos_pack(
        input logic [10:0] dsax_v,
        input logic [10:0] dsay_v);
        logic [63:0] v;
        v = 64'd0;
        v[42:32] = dsax_v;
        v[58:48] = dsay_v;
        return v;
    endfunction
    function automatic logic [63:0] trxreg_pack(
        input logic [11:0] rrw_v,
        input logic [11:0] rrh_v);
        logic [63:0] v;
        v = 64'd0;
        v[11:0]  = rrw_v;
        v[43:32] = rrh_v;
        return v;
    endfunction
    function automatic logic [63:0] trxdir_pack(input logic [1:0] xdir_v);
        return {62'd0, xdir_v};
    endfunction

    // Push one IMAGE qword. Honors data_ready flow control.
    task automatic push_image_qword(input logic [127:0] qword,
                                    input logic         last);
        @(negedge clk);
        wait (xfer_data_ready == 1'b1);
        @(negedge clk);
        xfer_data_valid = 1'b1;
        xfer_data_qword = qword;
        xfer_data_last  = last;
        @(posedge clk);
        @(negedge clk);
        xfer_data_valid = 1'b0;
        xfer_data_qword = 128'd0;
        xfer_data_last  = 1'b0;
    endtask

    // PSMT8 pattern: pixel(x, y) = 8-bit (y*16 + x). 32 distinct
    // values over the 8×4 rect — easy to spot a wrong-lane commit.
    function automatic logic [7:0] pixel_at(input int x, input int y);
        return 8'(y * 16 + x);
    endfunction

    // Build an IMAGE qword: 16 PSMT8 pixels packed low → high.
    // qword[ 7:0]=lane0, qword[15:8]=lane1, ..., qword[127:120]=lane15.
    // The 16 pixels cover TWO rows of 8: lanes 0..7 are row y_base,
    // lanes 8..15 are row y_base+1.
    function automatic logic [127:0] qword_for_pixels(
        input int x_base, input int y_base);
        logic [127:0] q;
        q = 128'd0;
        for (int i = 0; i < 8; i++) begin
            q[i*8 +: 8]       = pixel_at(x_base + i, y_base + 0);
            q[(8 + i)*8 +: 8] = pixel_at(x_base + i, y_base + 1);
        end
        return q;
    endfunction

    // ------------------------------------------------------------
    // Constants.
    // ------------------------------------------------------------
    localparam logic [7:0] R_BITBLTBUF = 8'h50;
    localparam logic [7:0] R_TRXPOS    = 8'h51;
    localparam logic [7:0] R_TRXREG    = 8'h52;
    localparam logic [7:0] R_TRXDIR    = 8'h53;

    localparam int          DBP    = 4;
    localparam int          DBW    = 1;
    localparam int          RRW    = 8;
    localparam int          RRH    = 4;
    localparam int          ROW_STRIDE_BYTES = DBW * 64 * 1;       // = 64
    localparam int          DEST_BASE_BYTES  = DBP * 256;          // = 1024 = 0x400
    localparam int          NUM_PIXELS = RRW * RRH;                // 32
    localparam int          NUM_QWORDS = NUM_PIXELS / 16;          // 2

    // ------------------------------------------------------------
    // Read helpers.
    // ------------------------------------------------------------
    task automatic check_byte(input logic [31:0] byte_addr,
                              input logic [7:0]  expected,
                              input string       tag);
        logic [7:0] got;
        vram_raddr = byte_addr & ~32'd3;
        #1;
        unique case (byte_addr[1:0])
            2'd0: got = vram_rdata[7:0];
            2'd1: got = vram_rdata[15:8];
            2'd2: got = vram_rdata[23:16];
            2'd3: got = vram_rdata[31:24];
        endcase
        if (got !== expected) begin
            $error("[%s] @byte 0x%08x got 0x%02x expected 0x%02x",
                   tag, byte_addr, got, expected);
            errors = errors + 1;
        end
    endtask

    initial begin
        rst_n            = 1'b0;
        gif_reg_wr_en    = 1'b0;
        gif_reg_num      = 8'd0;
        gif_reg_data     = 64'd0;
        xfer_data_valid  = 1'b0;
        xfer_data_qword  = 128'd0;
        xfer_data_last   = 1'b0;
        vram_raddr       = 32'd0;

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

        // ------------------------------------------------------------
        // TRX setup via GIF A+D writes. DPSM=PSMT8 (0x13).
        // ------------------------------------------------------------
        drive_reg(R_BITBLTBUF, bitbltbuf_pack(14'(DBP), 6'(DBW), 6'h13));
        drive_reg(R_TRXPOS,    trxpos_pack(11'd0, 11'd0));
        drive_reg(R_TRXREG,    trxreg_pack(12'(RRW), 12'(RRH)));
        drive_reg(R_TRXDIR,    trxdir_pack(2'd0));
        drive_idle();

        @(posedge clk);
        @(posedge clk);

        // ------------------------------------------------------------
        // Push 2 IMAGE qwords (16 pixels each, 2 rows per qword).
        //   qword 0: y=0..1, x=0..7 → 16 pixels
        //   qword 1: y=2..3, x=0..7 → 16 pixels
        // ------------------------------------------------------------
        for (int yy = 0; yy < RRH; yy += 2) begin
            logic [127:0] q;
            q = qword_for_pixels(0, yy);
            push_image_qword(q, (yy + 2 >= RRH));
        end

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

        // ------------------------------------------------------------
        // Aggregate counts.
        // ------------------------------------------------------------
        if (trxdir_arms_seen != 1) begin
            $error("trxdir_wr_q pulses=%0d (expected 1)", trxdir_arms_seen);
            errors = errors + 1;
        end
        if (xfer_writes_seen != NUM_PIXELS) begin
            $error("xfer VRAM writes=%0d (expected %0d)",
                   xfer_writes_seen, NUM_PIXELS);
            errors = errors + 1;
        end

        // ------------------------------------------------------------
        // Per-pixel readback. addr(x, y) = DEST_BASE + y*ROW_STRIDE + x.
        // ------------------------------------------------------------
        for (int yy = 0; yy < RRH; yy++) begin
            for (int xx = 0; xx < RRW; xx++) begin
                logic [31:0] addr;
                logic [7:0]  expected;
                addr     = 32'(DEST_BASE_BYTES + yy * ROW_STRIDE_BYTES + xx);
                expected = pixel_at(xx, yy);
                check_byte(addr, expected, "psmt8-pixel");
            end
        end

        // ------------------------------------------------------------
        // Boundary preservation: bytes immediately RIGHT of the
        // rect (x=RRW) on each row should still read 0x00.
        // ------------------------------------------------------------
        for (int yy = 0; yy < RRH; yy++) begin
            logic [31:0] right_addr;
            right_addr = 32'(DEST_BASE_BYTES + yy * ROW_STRIDE_BYTES + RRW);
            check_byte(right_addr, 8'h00, "right-of-rect");
        end

        check_byte(32'(DEST_BASE_BYTES - 16), 8'h00, "before-rect");
        check_byte(32'(DEST_BASE_BYTES + RRH * ROW_STRIDE_BYTES + 16),
                   8'h00, "after-rect");

        $display("[tb_gs_image_xfer_psmt8] arms=%0d writes=%0d pixels=%0dx%0d",
                 trxdir_arms_seen, xfer_writes_seen, RRW, RRH);

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

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

endmodule : tb_gs_image_xfer_psmt8
