// retroDE_ps2 — tb_top_psmct32_combined_demo (Ch302)
//
// COMBINED textured + alpha + depth probe TB. Proves the multi-beat per-pixel
// memory-op SCHEDULE (per the architect: assert the SEQUENCE of reads/writes,
// not just final pixels), plus the rendered occlusion/blend result.
//
// Fixture (bake.py combined): opaque GREEN Z-writing background (Cbg, Zbg=0x4000)
// then a TME+ABE+ZTE triangle whose interpolated Z runs 0x6000 (top, PASS) ->
// 0x2000 (bottom, FAIL), texture = translucent (A=0x40) red(left)/blue(right).
//
// MEMORY-OP TRACER: while comb_active, per held pixel (ras_cur_x/ras_cur_y) count
//   z_rd_en, tex_rd_en, fb_rd_en, color-writes (emit to FB addr <0x400),
//   z-writes (emit to Z addr >=0x1000).
// Asserts:
//   PROOF A (depth FAIL): >=1 inside-but-hidden pixel with z_rd=1, tex_rd=0,
//     fb_rd=0, color_wr=0, z_wr=0  (no texel read, no dest read, no writes).
//   PROOF B (depth PASS): >=1 visible pixel with z_rd=1, tex_rd=1, fb_rd=1,
//     color_wr=1, z_wr=1.
//   PROOF C (outside triangle): z_rd=0 (FSM beat0 sees !inside, no reads/writes).
//   PROOF D (render): PASS pixel == blend(texel, green) — texel RGB AND green
//     dest both present in the result; FAIL/outside pixel == green (occluded).

`timescale 1ns/1ps

module tb_top_psmct32_combined_demo;

    localparam int H_ACTIVE = 16, V_ACTIVE = 16;
    localparam int ZBG = 'h4000;
    localparam int ZBUF_BASE = 2*2048;          // ZBP=2 -> 0x1000 (FB stride=256B/row -> FB spans 0..0xFFF)
    localparam int TEX_BASE  = 32*256;          // 0x2000 (above FB+Z)

    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(16*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1)
    ) 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)
    );

    // ---- reference: barycentric on the triangle v0(2,1) v1(13,1) v2(7,14) ----
    function automatic real bdet(input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
        bdet=(by-cy)*(ax-cx)+(cx-bx)*(ay-cy); endfunction
    function automatic real bwa(input real px,input real py,input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
        bwa=((by-cy)*(px-cx)+(cx-bx)*(py-cy))/bdet(ax,ay,bx,by,cx,cy); endfunction
    function automatic real bwb(input real px,input real py,input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
        bwb=((cy-ay)*(px-cx)+(ax-cx)*(py-cy))/bdet(ax,ay,bx,by,cx,cy); endfunction

    // ---- memory-op tracer (per held pixel) ----
    int zr  [0:V_ACTIVE-1][0:H_ACTIVE-1];
    int txr [0:V_ACTIVE-1][0:H_ACTIVE-1];
    int fbr [0:V_ACTIVE-1][0:H_ACTIVE-1];
    int cwr [0:V_ACTIVE-1][0:H_ACTIVE-1];   // color writes
    int zwr [0:V_ACTIVE-1][0:H_ACTIVE-1];   // z writes
    initial for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
        zr[y][x]=0; txr[y][x]=0; fbr[y][x]=0; cwr[y][x]=0; zwr[y][x]=0; end

    always_ff @(posedge clk) begin
        if (rst_n && dut.u_gs.comb_active) begin
            int cx, cy;
            // READS: issued while the walker is held at the pixel -> index by ras_cur_x/y.
            cx = int'(dut.u_gs.ras_cur_x); cy = int'(dut.u_gs.ras_cur_y);
            if (cx < H_ACTIVE && cy < V_ACTIVE) begin
                if (dut.u_gs.z_rd_en)   zr [cy][cx] <= zr [cy][cx] + 1;
                if (dut.u_gs.tex_rd_en) txr[cy][cx] <= txr[cy][cx] + 1;
                if (dut.u_gs.fb_rd_en)  fbr[cy][cx] <= fbr[cy][cx] + 1;
            end
            // WRITES: the Z write (last beat) coincides with the walker advance,
            // so attribute writes by DECODING the FB/Z address (robust to timing).
            if (dut.u_gs.raster_pixel_emit) begin
                int wa, wx, wy;
                wa = int'(dut.u_gs.raster_pixel_fb_addr_q);
                if (wa < ZBUF_BASE) begin
                    wx = (wa>>2) % 64; wy = (wa>>2) / 64;
                    if (wx<H_ACTIVE && wy<V_ACTIVE) cwr[wy][wx] <= cwr[wy][wx] + 1;   // FB color
                end else if (wa < TEX_BASE) begin
                    wx = ((wa-ZBUF_BASE)>>2) % 64; wy = ((wa-ZBUF_BASE)>>2) / 64;
                    if (wx<H_ACTIVE && wy<V_ACTIVE) zwr[wy][wx] <= zwr[wy][wx] + 1;    // Z
                end
            end
        end
    end

    // ---- scanout capture ----
    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;
    initial begin
        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
        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) begin
        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
    end

    int errors, n_pass, n_fail, n_out;

    initial begin
        errors=0; n_pass=0; n_fail=0; n_out=0;
        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(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;

        for (int py=0; py<V_ACTIVE; py++) begin
            for (int px=0; px<H_ACTIVE; px++) begin
                real wa, wb, wc, fragz, uu, vv;
                bit  clearly_in, clearly_out, pix_pass, pix_fail;
                logic [7:0] er, eg, eb;
                wa = bwa(px,py, 2,1, 13,1, 7,14);
                wb = bwb(px,py, 2,1, 13,1, 7,14);
                wc = 1.0 - wa - wb;
                clearly_in  = (wa>0.06)&&(wb>0.06)&&(wc>0.06);
                clearly_out = (wa<-0.06)||(wb<-0.06)||(wc<-0.06);
                fragz = (wa+wb)*real'('h6000) + wc*real'('h2000);
                pix_pass = clearly_in && (fragz >= (real'(ZBG) + 1024.0));   // clearly above Zbg
                pix_fail = clearly_in && (fragz <= (real'(ZBG) - 1024.0));   // clearly below Zbg

                // PROOF C — clearly OUTSIDE the triangle: FSM does NO reads/writes.
                if (clearly_out) begin
                    n_out++;
                    if (zr[py][px]!=0 || txr[py][px]!=0 || fbr[py][px]!=0 || cwr[py][px]!=0 || zwr[py][px]!=0) begin
                        if (errors<12) $error("[comb] OUTSIDE (%0d,%0d) had ops z=%0d t=%0d f=%0d cw=%0d zw=%0d",
                            px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
                    end
                    // background green
                    if (cap_de[py][px] && !(cap_r[py][px]==8'h00 && cap_g[py][px]==8'h80 && cap_b[py][px]==8'h00)) begin
                        if (errors<12) $error("[comb] OUTSIDE (%0d,%0d) not green: (%02x,%02x,%02x)",px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px]); errors++;
                    end
                end

                // PROOF A — depth FAIL (hidden): exactly the Z read, nothing else.
                if (pix_fail) begin
                    n_fail++;
                    if (!(zr[py][px]==1 && txr[py][px]==0 && fbr[py][px]==0 && cwr[py][px]==0 && zwr[py][px]==0)) begin
                        if (errors<12) $error("[comb] FAIL (%0d,%0d) wrong ops z=%0d t=%0d f=%0d cw=%0d zw=%0d (want 1,0,0,0,0)",
                            px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
                    end
                    // occluded -> background green
                    if (cap_de[py][px] && !(cap_r[py][px]==8'h00 && cap_g[py][px]==8'h80 && cap_b[py][px]==8'h00)) begin
                        if (errors<12) $error("[comb] FAIL (%0d,%0d) not green: (%02x,%02x,%02x)",px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px]); errors++;
                    end
                end

                // PROOF B + D — depth PASS (visible): full read+write schedule + blend.
                if (pix_pass) begin
                    n_pass++;
                    if (!(zr[py][px]==1 && txr[py][px]==1 && fbr[py][px]==1 && cwr[py][px]==1 && zwr[py][px]==1)) begin
                        if (errors<12) $error("[comb] PASS (%0d,%0d) wrong ops z=%0d t=%0d f=%0d cw=%0d zw=%0d (want 1,1,1,1,1)",
                            px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
                    end
                    // expected blend: texel (red u<4 / blue u>=4, A=0x40) over green
                    uu = wb*7.0 + wc*3.0;  vv = wc*7.0;
                    if (uu < 4.0) begin er=8'd127; eg=8'd64; eb=8'd0;   end  // red  over green
                    else          begin er=8'd0;   eg=8'd64; eb=8'd127; end  // blue over green
                    if (cap_de[py][px] && !(cap_r[py][px]===er && cap_g[py][px]===eg && cap_b[py][px]===eb)) begin
                        if (errors<12) $error("[comb] PASS (%0d,%0d) u=%.1f got(%02x,%02x,%02x) exp(%02x,%02x,%02x)",
                            px,py,uu, cap_r[py][px],cap_g[py][px],cap_b[py][px], er,eg,eb); errors++;
                    end
                end
            end
        end

        if (n_pass < 6)  begin $error("[comb] too few PASS pixels (%0d) — geometry issue", n_pass); errors++; end
        if (n_fail < 6)  begin $error("[comb] too few FAIL pixels (%0d) — geometry issue", n_fail); errors++; end
        if (n_out  < 20) begin $error("[comb] too few OUTSIDE pixels (%0d)", n_out); errors++; end
        if (!core_halt)      begin $error("core_halt low"); errors++; end
        if (raster_overflow) begin $error("raster_overflow"); errors++; end

        $display("[tb_top_psmct32_combined_demo] PASS-pix=%0d FAIL-pix=%0d OUT-pix=%0d errors=%0d", n_pass, n_fail, n_out, errors);
        if (errors==0) $display("[tb_top_psmct32_combined_demo] PASS");
        else           $display("[tb_top_psmct32_combined_demo] FAIL");
        $finish;
    end

    initial begin #120000000; $error("[tb_top_psmct32_combined_demo] TIMEOUT"); $finish; end

endmodule : tb_top_psmct32_combined_demo
