// retroDE_ps2 — tb_top_psmct32_raster_demo_bram (Ch155 origin /
// Ch158 frame-capture enabled)
//
// Integration TB for `top_psmct32_raster_demo_bram` — the same
// Ch146 dep tree, but with `vram_bram_stub` (Ch154) replacing
// `vram_stub` and (since Ch158) `gs_pcrtc_stub` instantiated with
// `VRAM_SYNC_READ=1` so its data-decode + sync-output stages line
// up with the BRAM's registered read. Drives the same Ch146 .mem
// fixtures (4 SPRITE PACKED PSMCT32 packets), waits for the EE
// bootlet to halt + DMAC done + raster drained, then runs two
// verification phases:
//
//   Phase 1 (Ch155): per-pixel VRAM probe via hierarchical ref to
//   `dut.u_vram.mem[byte_addr >> 2]` for each of the 16×8 pixels
//   at canonical PSMCT32 swizzled word indices.
//
//   Phase 2 (Ch158, NEW): wait for the next end-of-frame, capture
//   one full PCRTC frame's worth of (de, r, g, b) into a 2D
//   `cap_*[v][h]` array, then assert every active-region pixel
//   matches the ABGR-decoded expected color for its quadrant.
//   PCRTC's `VRAM_SYNC_READ=1` pipeline means r/g/b are no longer
//   1-column shifted — captured pixel @(x,y) holds the value at
//   the swizzled mem location for that (x,y).
//
// Codex Ch155 framing: "vram_bram_stub integration TB for the
// Ch123 PSMCT32 path first, because CT32 already matches the
// contract." Ch158 closes the loop by capturing the scanout too.

`timescale 1ns/1ps

module tb_top_psmct32_raster_demo_bram;

    localparam int H_ACTIVE = 16;
    localparam int V_ACTIVE = 8;

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

    logic        core_go;
    logic [7:0]  r, g, b;
    logic        hsync, vsync, de;
    logic        core_halt;
    logic        dma_done_seen;
    logic        frame_seen;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE),
        .V_ACTIVE(V_ACTIVE)
    ) dut (
        .clk(clk), .rst_n(rst_n),
        .core_go(core_go),
        .r(r), .g(g), .b(b),
        .hsync(hsync), .vsync(vsync), .de(de),
        .core_halt(core_halt),
        .dma_done_seen(dma_done_seen),
        .frame_seen(frame_seen)
    );

    // Per-quadrant expected ABGR (matches Ch146 bake.py).
    function automatic int quadrant_idx(input int x, input int y);
        int qx, qy;
        qx = (x < 8) ? 0 : 1;
        qy = (y < 4) ? 0 : 1;
        return qy * 2 + qx;
    endfunction
    function automatic logic [7:0] expected_r(input int q);
        case (q)
            0: return 8'h55; 1: return 8'h66; 2: return 8'h77; default: return 8'h88;
        endcase
    endfunction
    function automatic logic [7:0] expected_g(input int q);
        case (q)
            0: return 8'hAA; 1: return 8'hBB; 2: return 8'h33; default: return 8'h44;
        endcase
    endfunction
    function automatic logic [7:0] expected_b(input int q);
        case (q)
            0: return 8'hCC; 1: return 8'hDD; 2: return 8'h99; default: return 8'h22;
        endcase
    endfunction
    // ABGR ordering: write_data[31:24]=A, [23:16]=B, [15:8]=G, [7:0]=R.
    function automatic logic [31:0] expected_word(input int q);
        return {8'hFF, expected_b(q), expected_g(q), expected_r(q)};
    endfunction

    // PSMCT32 swizzle reference — same shape as Ch123's TB.
    function automatic int ref_block_idx_ct32(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_addr_psmct32(
        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;
        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_ct32(by, bx);
        xb = x_v % 8;
        yb = y_v % 8;
        return page_base + blk_idx * 256 + yb * 32 + xb * 4;
    endfunction

    int errors;
    initial errors = 0;

    // ------------------------------------------------------------
    // Ch158 — frame capture. Latches r/g/b for every pixel in the
    // PCRTC active region while `cap_armed` is high.
    // ------------------------------------------------------------
    logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit         cap_armed;

    initial begin
        for (int y = 0; y < V_ACTIVE; y++)
            for (int x = 0; x < 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
        cap_armed = 1'b0;
    end

    // Ch158: PCRTC's `de` is the registered (decode-stage) view
    // when VRAM_SYNC_READ=1, so when it pulses high the r/g/b on
    // output corresponds to PCRTC's `hcnt`/`vcnt` at the PRIOR
    // posedge (the pixel that was at the address stage one cycle
    // ago). The TB therefore registers a 1-cycle-late copy of the
    // address-stage counters and uses them as the cap[][] indices.
    logic [31:0] hcnt_d;
    logic [31:0] vcnt_d;
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            hcnt_d <= 32'd0;
            vcnt_d <= 32'd0;
        end else begin
            hcnt_d <= 32'(dut.u_pcrtc.hcnt);
            vcnt_d <= 32'(dut.u_pcrtc.vcnt);
        end
    end

    always_ff @(posedge clk) begin
        if (rst_n && cap_armed && de
            && (vcnt_d < V_ACTIVE) && (hcnt_d < H_ACTIVE)) begin
            cap_r [vcnt_d][hcnt_d] <= r;
            cap_g [vcnt_d][hcnt_d] <= g;
            cap_b [vcnt_d][hcnt_d] <= b;
            cap_de[vcnt_d][hcnt_d] <= 1'b1;
        end
    end

    initial begin
        rst_n   = 1'b0;
        core_go = 1'b0;
        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (8) @(posedge clk);

        @(negedge clk); core_go = 1'b1;
        @(negedge clk); core_go = 1'b0;

        wait (core_halt == 1'b1);
        repeat (4) @(posedge clk);
        wait (dma_done_seen == 1'b1);
        repeat (10) @(posedge clk);
        if (dut.xfer_busy == 1'b1)        wait (dut.xfer_busy == 1'b0);
        if (dut.u_gs.raster_active == 1'b1) wait (dut.u_gs.raster_active == 1'b0);
        repeat (20) @(posedge clk);

        // Per-pixel VRAM probe via hierarchical ref. vram_bram_stub
        // is word-indexed (`mem [0:WORDS-1]`); convert byte_addr →
        // word_idx by `>> 2`.
        for (int y = 0; y < V_ACTIVE; y++) begin
            for (int x = 0; x < H_ACTIVE; x++) begin
                int          q;
                int          byte_addr;
                logic [31:0] expected;
                logic [31:0] got;
                q         = quadrant_idx(x, y);
                byte_addr = ref_addr_psmct32(0, 1, x, y);
                expected  = expected_word(q);
                got       = dut.u_vram.mem[byte_addr >> 2];
                if (got !== expected) begin
                    $error("[vram] (%0d,%0d) word @byte 0x%04x got 0x%08x expected 0x%08x sprite=%0d",
                           x, y, byte_addr, got, expected, q);
                    errors = errors + 1;
                end
            end
        end

        // -----------------------------------------------------------
        // Phase 2 (Ch158) — frame capture. Arm the capture latch on
        // the next vsync rising edge so we record exactly one full
        // frame of active-region pixels, then assert each captured
        // pixel matches the expected ABGR for its quadrant after
        // PCRTC's PSMCT32 → r/g/b decode (which drops alpha).
        // -----------------------------------------------------------
        @(negedge dut.vsync);  // currently in vsync? wait for it to clear
        @(posedge dut.vsync);  // next vsync rising
        cap_armed = 1'b1;
        // Capture for one full frame plus a margin.
        repeat ((H_ACTIVE + 4) * (V_ACTIVE + 4) + 50) @(posedge clk);
        cap_armed = 1'b0;

        for (int y = 0; y < V_ACTIVE; y++) begin
            for (int x = 0; x < H_ACTIVE; x++) begin
                int          q;
                logic [7:0]  exp_r_q, exp_g_q, exp_b_q;
                q       = quadrant_idx(x, y);
                exp_r_q = expected_r(q);
                exp_g_q = expected_g(q);
                exp_b_q = expected_b(q);
                if (!cap_de[y][x]) begin
                    $error("[scanout] (%0d,%0d) not captured (cap_de=0)", x, y);
                    errors = errors + 1;
                end else if ((cap_r[y][x] !== exp_r_q) ||
                             (cap_g[y][x] !== exp_g_q) ||
                             (cap_b[y][x] !== exp_b_q)) begin
                    $error("[scanout] (%0d,%0d) got rgb=(0x%02x,0x%02x,0x%02x) expected (0x%02x,0x%02x,0x%02x) sprite=%0d",
                           x, y,
                           cap_r[y][x], cap_g[y][x], cap_b[y][x],
                           exp_r_q,     exp_g_q,     exp_b_q, q);
                    errors = errors + 1;
                end
            end
        end

        // Status bundle: core_halt + dma_done_seen latched.
        if (!core_halt) begin
            $error("core_halt low at end of test");
            errors = errors + 1;
        end
        if (!dma_done_seen) begin
            $error("dma_done_seen never latched");
            errors = errors + 1;
        end
        if (!frame_seen) begin
            $error("frame_seen never latched (PCRTC end-of-frame not observed)");
            errors = errors + 1;
        end

        $display("[tb_top_psmct32_raster_demo_bram] core_halt=%0b dma_done_seen=%0b frame_seen=%0b errors=%0d",
                 core_halt, dma_done_seen, frame_seen, errors);
        if (errors == 0) $display("[tb_top_psmct32_raster_demo_bram] PASS");
        else             $display("[tb_top_psmct32_raster_demo_bram] FAIL");
        $finish;
    end

    initial begin
        #20000000;
        $error("[tb_top_psmct32_raster_demo_bram] TIMEOUT");
        $finish;
    end

endmodule : tb_top_psmct32_raster_demo_bram
