// retroDE_ps2 — tb_top_psmct32_feeder_runtime_demo (Ch330 Brick 3)
//
// RUNTIME PRIMITIVE-LIST SWAP without an RBF rebuild. The feeder plays list A
// (4 combined-TAZ tris in tile t0), the renderer draws it. Then we overwrite the
// staging RAM with list B (4 tris in tile t15) — exactly what a bridge write would
// do at runtime — and pulse the retrigger. The image changes from "blue in t0" to
// "blue in t15", with NO RBF rebuild and NO reset.
//
// The single proof that matters here is Codex's "known-empty" gotcha: list B must
// NOT append onto list A. The retrigger control FSM (C_SETUP->C_RUN->C_DRAIN->C_READY)
// gates a new list on the previous grid having actually run AND drained the FIFO. So:
//   image A  : blue in t0 box,  ZERO blue in t15 box
//   image B  : blue in t15 box,  ZERO blue in t0 box   <- t0 went back to GREEN = no append
// Plus: records_emitted == 4 for each list (the feeder re-ran B's full list), and the
// FSM made a clean C_READY -> C_RUN -> C_READY round-trip on the retrigger.

`timescale 1ns/1ps

module tb_top_psmct32_feeder_runtime_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    localparam real MARG = 0.12;
    // control-FSM state encodings (mirror the bram-top localparams; not visible across the generate)
    localparam logic [1:0] C_RUN = 2'd1, C_READY = 2'd3;

    logic clk, 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, dma_done_seen, frame_seen, raster_overflow;
    logic        frame_toggle, dma_done_toggle;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(32*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(4), .TILE_ROWS(4),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(4), .TILE_FIFO_DEPTH(8),
        .BIN_BUFFER_ENABLE(1'b1), .HEARTBEAT_SPLICE_ENABLE(1'b0),
        .FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(256)
    ) 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), .raster_overflow(raster_overflow),
        .frame_toggle(frame_toggle), .dma_done_toggle(dma_done_toggle),
        .joy_a_pressed_i(1'b0), .joy_b_pressed_i(1'b0),
        // Ch330 Brick 4 feeder ports: staging written via $readmemh backdoor here,
        // so the bridge staging-write port is idle; only feeder_go_i is exercised.
        .feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
        .feeder_go_i(feeder_go_tb),
        .feeder_ready_o(), .feeder_records_o(), .feeder_waits_o()
    );
    logic feeder_go_tb;

    // ---- scanout frame capture (one frame at a time; re-armed between lists) ----
    logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_b[0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit capture_armed;
    task automatic clear_cap;
        for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
            cap_r[y][x]=0; cap_g[y][x]=0; cap_b[y][x]=0; cap_de[y][x]=0;
        end
    endtask
    initial begin clear_cap(); capture_armed=1'b0; end
    logic [31:0] hcnt_d, vcnt_d;
    always_ff @(posedge clk) begin
        if (!rst_n) begin hcnt_d<=0; vcnt_d<=0; end
        else begin hcnt_d<=32'(dut.u_pcrtc.hcnt); vcnt_d<=32'(dut.u_pcrtc.vcnt); end
    end
    always_ff @(posedge clk)
        if (rst_n && capture_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

    // count opaque-blue pixels inside a tile box [bx0,bx0+15] x [by0,by0+15]
    function automatic int blue_in_box(input int bx0, input int by0);
        int n; begin n=0;
            for (int y=by0;y<by0+16;y++) for (int x=bx0;x<bx0+16;x++)
                if (cap_de[y][x] && cap_b[y][x]===8'hFF && cap_r[y][x]===8'h00 && cap_g[y][x]===8'h00) n++;
            blue_in_box = n;
        end
    endfunction

    task automatic capture_frame;   // arm for exactly one frame, then disarm
        clear_cap();
        @(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;
    endtask

    int errors;
    int blueA_t0, blueA_t15, blueB_t0, blueB_t15;
    int recA, recB;

    initial begin
        errors=0;
        // the retrigger pulse is the bram-top feeder_go_i port (bridge owns it in Brick 4)
        feeder_go_tb = 1'b0;

        // ---- list A: loaded into staging at t0; auto-plays after the bootlet upload ----
        $readmemh(`FEEDER_STG_A_FILE, dut.g_feeder.feeder_stg);

        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);

        // list A runs + drains -> control FSM lands in C_READY
        wait (dut.g_feeder.cst == C_READY);
        recA = int'(dut.feeder_records);
        capture_frame();
        blueA_t0 = blue_in_box(0,0);     blueA_t15 = blue_in_box(48,48);

        // ---- runtime swap: overwrite staging with list B (tile t15), then RETRIGGER ----
        $readmemh(`FEEDER_STG_B_FILE, dut.g_feeder.feeder_stg);
        @(negedge clk); feeder_go_tb = 1'b1; @(negedge clk); feeder_go_tb = 1'b0;

        wait (dut.g_feeder.cst == C_RUN);    // FSM left READY -> RUN (B started)
        wait (dut.g_feeder.cst == C_READY);  // B ran + drained
        recB = int'(dut.feeder_records);
        capture_frame();
        blueB_t0 = blue_in_box(0,0);     blueB_t15 = blue_in_box(48,48);

        // ---- proofs ----
        // list A: blue in t0, nothing in t15
        if (blueA_t0 < 20) begin $error("[demo] list A: too few blue px in t0 (%0d) — A not rendered", blueA_t0); errors++; end
        if (blueA_t15 != 0)  begin $error("[demo] list A: blue px in t15 (%0d) — should be empty", blueA_t15); errors++; end
        // list B: blue in t15, t0 went BACK to green => no append
        if (blueB_t15 < 20) begin $error("[demo] list B: too few blue px in t15 (%0d) — B not rendered after retrigger", blueB_t15); errors++; end
        if (blueB_t0 != 0)  begin $error("[demo] list B: blue px STILL in t0 (%0d) — list B APPENDED onto list A!", blueB_t0); errors++; end
        // the image actually changed
        if (blueA_t0 < 20 || blueB_t15 < 20) begin /* covered above */ end
        // both lists fully emitted by the feeder
        if (recA != 4) begin $error("[demo] list A records_emitted=%0d (want 4)", recA); errors++; end
        if (recB != 4) begin $error("[demo] list B records_emitted=%0d (want 4)", recB); errors++; end
        if (!core_halt) begin $error("core_halt low"); errors++; end

        $display("[tb_top_psmct32_feeder_runtime_demo] A:{t0=%0d t15=%0d rec=%0d}  B:{t0=%0d t15=%0d rec=%0d}  errors=%0d",
                 blueA_t0, blueA_t15, recA, blueB_t0, blueB_t15, recB, errors);
        if (errors==0) $display("[tb_top_psmct32_feeder_runtime_demo] PASS");
        else           $display("[tb_top_psmct32_feeder_runtime_demo] FAIL");
        $finish;
    end

    initial begin #900000000; $error("[tb_top_psmct32_feeder_runtime_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_feeder_runtime_demo
