// retroDE_ps2 — tb_top_psmct32_raster_demo (Ch146)
//
// Focused TB for the first hardware-targeted top wrapper. Drives the
// top with the static .mem fixtures generated by sim/data/
// top_psmct32_raster_demo/bake.py and verifies the same 16×8 PCRTC
// frame Ch123 produces. The point of this TB is NOT to re-verify
// every Ch123 invariant — that's already covered by
// tb_gs_demo_psmct32_swizzle_e2e — but to prove:
//   (1) The top wrapper compiles standalone with no hierarchical
//       refs into TB scopes.
//   (2) `$readmemh` of the static fixtures produces identical
//       behavior to the procedural Ch123 preload.
//   (3) The status bundle (core_halt + dma_done_seen + frame_seen)
//       fires in the expected order.
//   (4) The active PCRTC region matches Ch123's per-quadrant
//       grayscale exactly — so a board-level bring-up that wires
//       r/g/b/hsync/vsync/de to a video PHY would display the same
//       image the sim produces.

`timescale 1ns/1ps

module tb_top_psmct32_raster_demo;

    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;

    // BIOS / payload image paths are passed to the top wrapper via
    // pre-defined macros (the Ch146 build rule sets
    // TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE +
    // TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE via -D, then the
    // wrapper expands them at the bios_rom_stub / ee_ram_stub
    // instances).
    top_psmct32_raster_demo #(
        .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 RGB (matches Ch123 / 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

    // ----------------------------------------------------------------
    // Frame capture.
    // ----------------------------------------------------------------
    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         capture_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
        capture_armed = 1'b0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && capture_armed && de
            && (dut.u_pcrtc.vcnt < V_ACTIVE)
            && (dut.u_pcrtc.hcnt < H_ACTIVE)) begin
            cap_r [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= r;
            cap_g [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= g;
            cap_b [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= b;
            cap_de[dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= 1'b1;
        end
    end

    int errors;
    initial errors = 0;

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

        // Pulse core_go for one cycle. On hardware a board reset-
        // release sequencer can do the same.
        @(negedge clk);
        core_go = 1'b1;
        @(negedge clk);
        core_go = 1'b0;

        // Wait for the EE to halt (SYSCALL halt_o), then for DMAC
        // DONE event to be sticky-latched.
        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 (10) @(posedge clk);

        @(posedge dut.u_pcrtc.end_of_frame);
        @(posedge clk);
        capture_armed = 1'b1;
        @(posedge dut.u_pcrtc.end_of_frame);
        @(posedge clk);
        capture_armed = 1'b0;

        // Phase 1 — scanout assertion. Per-pixel per-quadrant RGB.
        for (int y = 0; y < V_ACTIVE; y++) begin
            for (int x = 0; x < H_ACTIVE; x++) begin
                int          q;
                logic [7:0]  er, eg, eb;
                q  = quadrant_idx(x, y);
                er = expected_r(q);
                eg = expected_g(q);
                eb = expected_b(q);
                if (!cap_de[y][x]) begin
                    $error("(%0d,%0d) DE never asserted", x, y);
                    errors = errors + 1;
                end
                if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
                    $error("[scanout] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x) sprite=%0d",
                           x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb, q);
                    errors = errors + 1;
                end
            end
        end

        // Phase 2 — status bundle. core_halt, dma_done_seen, frame_seen
        // all 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");
            errors = errors + 1;
        end

        $display("[tb_top_psmct32_raster_demo] core_halt=%0b dma_done_seen=%0b frame_seen=%0b raster_emits=%0d errors=%0d",
                 core_halt, dma_done_seen, frame_seen,
                 dut.u_gs.raster_pixel_emit_count, errors);
        if (errors == 0)
            $display("[tb_top_psmct32_raster_demo] PASS");
        else
            $display("[tb_top_psmct32_raster_demo] FAIL");
        $finish;
    end

    // Watchdog.
    initial begin
        #20000000;
        $error("[tb_top_psmct32_raster_demo] TIMEOUT");
        $finish;
    end

endmodule : tb_top_psmct32_raster_demo
