// retroDE_ps2 — tb_gs_scanout_swizzle_psmct32 (Ch120)
//
// Read-side wiring of the Ch119 swizzle math primitive into PCRTC
// scanout. PSMCT32 framebuffer reads now go through
// gs_swizzle_psmct32_stub when the new `PSMCT32_SWIZZLE=1`
// parameter is set on gs_pcrtc_stub. This TB proves that the
// swizzled read path lands on the expected VRAM byte for every
// in-window pixel.
//
// Topology (no gs_stub — keeps the read-side proof isolated from
// the write-side, which still uses linear addressing):
//
//   TB.preload  →  vram_stub  →  gs_pcrtc_stub#(PSMCT32_SWIZZLE=1) → r/g/b
//
// VRAM is preloaded at SWIZZLED PSMCT32 addresses for a 16×4
// image at FBP=0, FBW=1. The image spans block (0,0) (x∈[0..7])
// AND block (1,0) (x∈[8..15]) so the visible scanout exercises
// at least one block-x boundary in the swizzle table — block
// (0,0) → swizzle index 0 (bytes 0..255), block (1,0) → swizzle
// index 1 (bytes 256..511). Each pixel gets a unique color
// `pixel(x,y) = {0xFF, y[3:0]<<4, x[3:0]<<4, 0x10 | (y*16+x)}`
// so any address mistake or block-swap surfaces immediately.
//
// Swizzle math (mirrored from gs_swizzle_psmct32_stub):
//   page_x = x>>6; page_y = y>>5
//   page_index = page_y*FBW + page_x
//   page_base  = FBP*2048 + page_index*8192
//   block_idx  = SWIZZLE[(y%32)/8][(x%64)/8]
//   addr       = page_base + block_idx*256 + (y%8)*32 + (x%8)*4
//
// PCRTC config: H_ACTIVE=16, V_ACTIVE=4, DISPLAY1 window covers
// the full active area at MAGH=MAGV=0. DISPFB1 = FBP=0, FBW=1,
// PSM=PSMCT32. Capture the entire 16×4 active frame and assert
// each pixel against its preloaded color.
//
// Two strict linear-vs-swizzled separators in the captured set:
//   (1) y=1, x=0 — linear byte = 64, swizzled byte = 32 (row-
//       stride within block (0,0)).
//   (2) y=0, x=8 — linear byte = 32, swizzled byte = 256 (block
//       boundary crossing into block (1,0)).
// Both are sampled in this TB, so the integration is verified
// to carry both the in-block row-stride math AND the block-
// index lookup through PCRTC's effective_x[3]+ bits.

`timescale 1ns/1ps

module tb_gs_scanout_swizzle_psmct32;

    localparam int PCRTC_H_ACTIVE = 16;
    localparam int PCRTC_V_ACTIVE = 4;

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

    // -----------------------------------------------------------
    // Privileged-register-shaped feeds (no gs_stub — TB drives
    // pmode/dispfb1/display1 directly).
    // -----------------------------------------------------------
    logic [63:0] pmode_q;
    logic [63:0] dispfb1_q;
    logic [63:0] display1_q;

    // -----------------------------------------------------------
    // VRAM (TB drives the write port directly).
    // -----------------------------------------------------------
    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(16384)) 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;

    // -----------------------------------------------------------
    // PCRTC instantiated with the Ch120 PSMCT32_SWIZZLE parameter
    // set to 1 — switches its PSMCT32 read addresses to the page/
    // block swizzled formula.
    // -----------------------------------------------------------
    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),
        .PSMCT32_SWIZZLE(1'b1)
    ) 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)
    );

    // -----------------------------------------------------------
    // Frame capture (mirrors tb_gs_scanout_psmt8).
    // -----------------------------------------------------------
    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

    // -----------------------------------------------------------
    // VRAM write task (32-bit aligned PSMCT32 word).
    // -----------------------------------------------------------
    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;
        vram_wbe   = 4'b1111;
        @(posedge clk);
        @(negedge clk);
        vram_we    = 1'b0;
        vram_waddr = 32'd0;
        vram_wdata = 32'd0;
        vram_wbe   = 4'b0000;
    endtask

    // -----------------------------------------------------------
    // Reference swizzle (matches gs_swizzle_psmct32_stub +
    // tb_gs_swizzle_psmct32). Used to compute the SAME byte
    // address the DUT will compute, so we preload at the right
    // place. Phase 3 of Ch119's TB has already proven this matches
    // the canonical PCSX2 table.
    // -----------------------------------------------------------
    function automatic int ref_block_idx(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 logic [31:0] ref_addr(
        input int fbp_v, input int fbw_v, input int x_v, input int y_v);
        int page_x, page_y, page_idx, page_base;
        int by, bx, blk_idx, xb, yb;
        int addr_v;
        page_x   = x_v / 64;
        page_y   = y_v / 32;
        page_idx = page_y * fbw_v + page_x;
        page_base = fbp_v * 2048 + page_idx * 8192;
        by  = (y_v % 32) / 8;
        bx  = (x_v % 64) / 8;
        blk_idx = ref_block_idx(by, bx);
        xb = x_v % 8;
        yb = y_v % 8;
        addr_v = page_base + blk_idx * 256 + yb * 32 + xb * 4;
        return addr_v[31:0];
    endfunction

    // -----------------------------------------------------------
    // Test image: 8×4 PSMCT32 at FBP=0, FBW=1.
    // Pixel color: {A=0xFF, B=y[3:0]<<4, G=x[3:0]<<4, R=0x10 | (y*8+x)}
    //   so every pixel has a unique (R, G, B) triple.
    //
    // 8×4 = pixels (0,0)..(7,3). x ∈ [0..7] all stay in block
    // (0,0)..(0,0)... wait, x ∈ [0..7] is block_x_in_page=0.
    // We want to cross block boundaries to actually exercise the
    // swizzle. Let's do 16×4 instead (blocks (0,0) and (1,0))
    // — 16 pixels horizontally, 4 vertically. block (0,0) →
    // swizzle 0 → bytes 0..255; block (1,0) → swizzle 1 → bytes
    // 256..511. Linear vs. swizzled differ at x=8 (linear byte
    // 32, swizzled byte 256), so a wrong-path scan would surface.
    // -----------------------------------------------------------
    localparam int IMG_W  = 16;
    localparam int IMG_H  = 4;

    // pixel(x,y) = {A=0xFF, B=y[3:0]<<4, G=x[3:0]<<4, R=0x10|(y*16+x)}
    // — every pixel in 16×4 has a unique (R,G,B) triple.
    function automatic logic [31:0] expected_color(input int x, input int y);
        logic [7:0] r;
        logic [7:0] g;
        logic [7:0] b;
        r = 8'h10 | 8'(y*16 + x);
        g = 8'((x[3:0] << 4) & 8'hFF);
        b = 8'((y[3:0] << 4) & 8'hFF);
        return {8'hFF, b, g, r};
    endfunction

    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 (4) @(posedge clk);

        // ---------------------------------------------------
        // Preload VRAM at the SWIZZLED address for each pixel
        // of the 16×4 image. FBP=0, FBW=1.
        // ---------------------------------------------------
        for (int yy = 0; yy < IMG_H; yy++) begin
            for (int xx = 0; xx < IMG_W; xx++) begin
                logic [31:0] addr;
                logic [31:0] color;
                addr  = ref_addr(0, 1, xx, yy);
                color = expected_color(xx, yy);
                vram_write32(addr, color);
            end
        end

        // ---------------------------------------------------
        // DISPFB1: FBP=0, FBW=1, PSM=PSMCT32(=0).
        //   FBW [14:9]   = 1   → 0x0200
        //   PSM [19:15]  = 0   → 0x00000
        // ---------------------------------------------------
        dispfb1_q = 64'h0000_0000_0000_0200;

        // DISPLAY1: DX=0, DY=0, DW=IMG_W-1=15, DH=IMG_H-1=3,
        // MAGH=0, MAGV=0. Full 16×4 active area visible — the
        // window spans both block (0,0) (x∈[0..7]) and block
        // (1,0) (x∈[8..15]) so the swizzle's block-index lookup
        // is exercised.
        //   DX  [11:0]   = 0
        //   DY  [22:12]  = 0
        //   MAGH[26:23]  = 0
        //   MAGV[28:27]  = 0
        //   DW  [43:32]  = 15  → 0x0000_000F_0000_0000
        //   DH  [54:44]  = 3   → 0x0030_0000_0000_0000
        display1_q = 64'h0030_000F_0000_0000;

        // PMODE: EN1=1
        pmode_q = 64'h0000_0000_0000_0001;

        // Wait for the next frame edge before arming the capture
        // (let the privileged regs settle).
        repeat (32) @(posedge clk);
        capture_armed = 1'b1;

        // Run for one full frame.
        // Frame ≈ (1 + 8 + 1 + 1) × (1 + 4 + 1 + 1) = 11 × 7 = 77
        // pixel clocks. Wait substantially longer to ensure full
        // capture.
        repeat (300) @(posedge clk);

        // ---------------------------------------------------
        // Verify each captured pixel matches expected color.
        // ---------------------------------------------------
        for (int yy = 0; yy < PCRTC_V_ACTIVE; yy++) begin
            for (int xx = 0; xx < PCRTC_H_ACTIVE; xx++) begin
                logic [31:0] expected;
                logic [7:0]  exp_r, exp_g, exp_b;
                if (!cap_de[yy][xx]) begin
                    $error("(%0d,%0d) DE never asserted", xx, yy);
                    errors = errors + 1;
                end else begin
                    expected = expected_color(xx, yy);
                    exp_r = expected[7:0];
                    exp_g = expected[15:8];
                    exp_b = expected[23:16];
                    if (cap_r[yy][xx] !== exp_r
                        || cap_g[yy][xx] !== exp_g
                        || cap_b[yy][xx] !== exp_b) begin
                        $error("(%0d,%0d) got R=%02x G=%02x B=%02x expected R=%02x G=%02x B=%02x",
                               xx, yy,
                               cap_r[yy][xx], cap_g[yy][xx], cap_b[yy][xx],
                               exp_r, exp_g, exp_b);
                        errors = errors + 1;
                    end
                end
            end
        end

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

    initial begin
        #500000;
        $error("[tb_gs_scanout_swizzle_psmct32] timeout");
        $finish;
    end

endmodule : tb_gs_scanout_swizzle_psmct32
