// retroDE_ps2 — tb_ee_core_gif_primitive_strip (Ch77)
//
// Verifies STRIP/FAN observer cadence in gs_stub:
//   LINE_STRIP  (PRIM=2, anchor=2): first 2 vertices form a line; each
//                                   additional vertex closes another
//                                   line (continuous strip).
//   TRI_STRIP   (PRIM=4, anchor=3): first 3 vertices form a triangle;
//                                   each additional vertex closes
//                                   another triangle (rolling).
//   TRI_FAN     (PRIM=5, anchor=3): same draw cadence as TRI_STRIP,
//                                   but with v0 of every saturated
//                                   draw PINNED to the pivot vertex
//                                   (Ch78 / Ch80 / Ch81: raw, color,
//                                   decoded vertex, and decoded color
//                                   all carry pivot identity). The
//                                   identity model differs from
//                                   TRI_STRIP — see the load-bearing
//                                   pd_v0[7] / pd_cv0[7] / pd_dv0[7]
//                                   assertions below for proof.
//
// Also confirms two negative cases the Ch76 TB exercised in passing:
//   - TRI_STRIP with only 2 vertices → 0 draws (anchor unmet).
//   - LINE_STRIP with only 1 vertex  → 0 draws (anchor unmet).
//
// And a sanity check that PRIM=7 (reserved) still emits no draws and
// no count increment regardless of vertex count.
//
// Stream (6 GIFtags, 32 qwords, single DMA burst):
//
//   Tag1 — LINE_STRIP, 4 vertices → 3 draws (anchor + 2 extensions):
//     q0     : GIFtag NLOOP=1 NREG=5 EOP=0 PACKED, REGS=[A+D × 5]
//     q1     : A+D PRIM    data=0x0...0002
//     q2..q5 : A+D XYZ2 ×4  vertex data 0x10, 0x20, 0x30, 0x40
//
//   Tag2 — TRI_STRIP, 5 vertices → 3 draws (anchor + 2 rolling tris):
//     q6     : GIFtag NLOOP=1 NREG=6 EOP=0 PACKED, REGS=[A+D × 6]
//     q7     : A+D PRIM    data=0x0...0004
//     q8..q12: vertex data 0x110..0x150. v1/v2/v3/v5 use XYZ2;
//              **v4 uses XYZF2** so the strip cadence is exercised
//              across both vertex-commit reg#s. v4 is a saturated
//              extension → it closes a primitive and EV_PRIM_DRAW
//              still fires (gif_is_vertex covers both XYZ2 and
//              XYZF2). The latch update goes to xyzf2_q on that beat.
//
//   Tag3 — TRI_FAN, 5 vertices → 3 draws (same cadence as TRI_STRIP):
//     q13    : GIFtag NLOOP=1 NREG=6 EOP=0 PACKED, REGS=[A+D × 6]
//     q14    : A+D PRIM    data=0x0...0005
//     q15..q19: A+D XYZ2 ×5 vertex data 0x210..0x250
//
//   Tag4 — TRI_STRIP partial, 2 vertices → 0 draws (anchor unmet):
//     q20    : GIFtag NLOOP=1 NREG=3 EOP=0 PACKED, REGS=[A+D × 3]
//     q21    : A+D PRIM    data=0x0...0004
//     q22..q23: A+D XYZ2 ×2 vertex data 0x310, 0x320
//
//   Tag5 — LINE_STRIP partial, 1 vertex → 0 draws (anchor unmet):
//     q24    : GIFtag NLOOP=1 NREG=2 EOP=0 PACKED, REGS=[A+D × 2]
//     q25    : A+D PRIM    data=0x0...0002
//     q26    : A+D XYZ2     vertex data 0x410
//
//   Tag6 — reserved (PRIM=7), 3 vertices → 0 draws (vert_threshold=0):
//     q27    : GIFtag NLOOP=1 NREG=4 EOP=1 PACKED, REGS=[A+D × 4]
//     q28    : A+D PRIM    data=0x0...0007
//     q29..q31: A+D XYZ2 ×3 vertex data 0x510, 0x520, 0x530
//
// Pass criteria (32-qword stream):
//   - DMAC: 1 START / 32 BEAT / 1 DONE
//   - GIF:  6 GIFTAG, 26 EV_WRITE (5+6+6+3+2+4 A+D entries), 0 EV_MODE
//   - GS:
//       - 6 PRIM EV_WRITE
//       - Tag1 LINE_STRIP: v1 EV_WRITE, v2/v3/v4 EV_PRIM_DRAW (3 PD)
//       - Tag2 TRI_STRIP : v1/v2 EV_WRITE, v3/v4/v5 EV_PRIM_DRAW (3 PD)
//       - Tag3 TRI_FAN   : v1/v2 EV_WRITE, v3/v4/v5 EV_PRIM_DRAW (3 PD)
//       - Tag4 strip2v   : v1/v2 EV_WRITE, 0 PD
//       - Tag5 strip1v   : v1 EV_WRITE,    0 PD
//       - Tag6 reserved  : v1/v2/v3 EV_WRITE (XYZ2 known), 0 PD
//     Total: 0 BGCOLOR / 17 EV_WRITE (6 PRIM + 11 vertices) /
//            9 EV_PRIM_DRAW / 0 EV_MODE
//   - prim_complete_count == 9
//   - prim_q final  == 7   (last PRIM written, reserved)
//   - xyz2_q final  == 0x530 (last vertex, in tag6)
//
// EV_PRIM_DRAW arg checks (captured in firing order, 9 entries):
//   [0] LINE_STRIP v2  : prim=2 thr=2 count=1 vertex=0x020
//   [1] LINE_STRIP v3  : prim=2 thr=2 count=2 vertex=0x030
//   [2] LINE_STRIP v4  : prim=2 thr=2 count=3 vertex=0x040
//   [3] TRI_STRIP  v3  : prim=4 thr=3 count=4 vertex=0x130
//   [4] TRI_STRIP  v4  : prim=4 thr=3 count=5 vertex=0x140
//   [5] TRI_STRIP  v5  : prim=4 thr=3 count=6 vertex=0x150
//   [6] TRI_FAN    v3  : prim=5 thr=3 count=7 vertex=0x230
//   [7] TRI_FAN    v4  : prim=5 thr=3 count=8 vertex=0x240
//   [8] TRI_FAN    v5  : prim=5 thr=3 count=9 vertex=0x250
//
// Ch78 vertex-tuple checks (prim_v0_q / prim_v1_q / prim_v2_q
// captured at the same time as the args). LINE_STRIP uses 2 slots,
// triangles use 3. The TRI_STRIP-vs-TRI_FAN distinction shows up at
// the first SATURATED draw of each (events [4] vs [7..8]):
//   [0] LINE_STRIP v2 (anchor)        : v0=0x10  v1=0x20
//   [1] LINE_STRIP v3 (extension)     : v0=0x20  v1=0x30
//   [2] LINE_STRIP v4 (extension)     : v0=0x30  v1=0x40
//   [3] TRI_STRIP  v3 (anchor)        : v0=0x110 v1=0x120 v2=0x130
//   [4] TRI_STRIP  v4 (sat. extension): v0=0x120 v1=0x130 v2=0x140
//                                        ^^^ rolling — v0 advances;
//                                        v2=XYZF2 vertex
//   [5] TRI_STRIP  v5 (sat. extension): v0=0x130 v1=0x140 v2=0x150
//                                        ^^^ XYZF2 vertex still in window
//   [6] TRI_FAN    v3 (anchor)        : v0=0x210 v1=0x220 v2=0x230
//                                        (v_pivot ≡ v_prev on anchor)
//   [7] TRI_FAN    v4 (sat. extension): v0=0x210 v1=0x230 v2=0x240
//                                        ^^^ pivot — v0 PINNED; v1 rolled
//   [8] TRI_FAN    v5 (sat. extension): v0=0x210 v1=0x240 v2=0x250
//                                        ^^^ pivot still pinned

`timescale 1ns/1ps

module tb_ee_core_gif_primitive_strip;

    localparam int RAM_SIZE_BYTES = 4 * 1024;
    localparam int RAM_ADDR_W     = $clog2(RAM_SIZE_BYTES);

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

    // ee_ram_stub
    logic                  ram_rd_en;
    logic [RAM_ADDR_W-1:0] ram_rd_addr;
    logic [127:0]          ram_rd_data;
    logic                  ram_rd_valid;
    logic                  ram_wr_en;
    logic [RAM_ADDR_W-1:0] ram_wr_addr;
    logic [127:0]          ram_wr_data;
    logic [15:0]           ram_wr_be;
    logic [7:0]            ram_master_id;

    ee_ram_stub #(.SIZE_BYTES(RAM_SIZE_BYTES)) u_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ram_rd_en), .rd_addr(ram_rd_addr),
        .rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
        .wr_en(ram_wr_en), .wr_addr(ram_wr_addr),
        .wr_data(ram_wr_data), .wr_be(ram_wr_be),
        .master_id(ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );
    assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;

    // DMAC + map
    logic        dmac_reg_wr_en;
    logic [7:0]  dmac_reg_offset;
    logic [31:0] dmac_reg_wr_data;
    logic        dmac_mem_rd_en;
    logic [31:0] dmac_mem_rd_addr;
    logic         dmac_gif_valid;
    logic [127:0] dmac_gif_data;
    logic         dmac_gif_last;
    logic         dmac_gif_ready;

    logic                dmac_ev_valid;
    trace_pkg::subsys_e  dmac_ev_subsys;
    trace_pkg::event_e   dmac_ev_event;
    logic [63:0]         dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3;
    logic [31:0]         dmac_ev_flags;

    logic [127:0] map_to_dmac_rd_data;
    logic         map_to_dmac_rd_valid;

    dmac_reg_stub u_dmac (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset),
        .reg_wr_data(dmac_reg_wr_data),
        .reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(),
        .mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr),
        .mem_rd_data(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid),
        .ep_valid(dmac_gif_valid), .ep_data(dmac_gif_data),
        .ep_last(dmac_gif_last), .ep_ready(dmac_gif_ready),
        .irq_completion_o(),
        .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
        .ev_event(dmac_ev_event),
        .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
        .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
        .ev_flags(dmac_ev_flags)
    );

    logic        map_ram_rd_en;
    logic [24:0] map_ram_rd_addr;
    ee_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        .ee_rd_en(1'b0), .ee_rd_addr(32'd0),
        .ee_rd_data(), .ee_rd_valid(),
        .ee_wr_en(1'b0), .ee_wr_addr(32'd0),
        .ee_wr_data(32'd0), .ee_wr_be(4'd0),
        .dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr),
        .dmac_rd_data(map_to_dmac_rd_data),
        .dmac_rd_valid(map_to_dmac_rd_valid),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr),
        .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(128'd0), .bridge_wr_be(16'd0),
        .bridge_master_id(8'd0),
        .ram_wr_en(), .ram_wr_addr(), .ram_wr_data(),
        .ram_wr_be(), .ram_master_id(),
        .ee_dmac_ch2_wr_en(), .ee_dmac_ch2_wr_addr(), .ee_dmac_ch2_wr_data(),
        .ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(),
        .ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0),
        .ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(),
        .ee_intc_rd_en(), .ee_intc_rd_addr(),
        .ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0),
        .ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(),
        .ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(),
        .ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0),
        .ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(),
        .ee_biu_rd_en(), .ee_biu_rd_addr(),
        .ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    assign ram_rd_en   = map_ram_rd_en;
    assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0];

    // gif_packed_stub (REAL_AD_REG_MAP=1)
    logic        gif_gs_wr_en;
    logic [15:0] gif_gs_wr_addr;
    logic [63:0] gif_gs_wr_data;
    logic        gif_gif_reg_wr_en;
    logic [7:0]  gif_gif_reg_num;
    logic [63:0] gif_gif_reg_data;

    logic                gif_ev_valid;
    trace_pkg::subsys_e  gif_ev_subsys;
    trace_pkg::event_e   gif_ev_event;
    logic [63:0]         gif_ev_arg0, gif_ev_arg1, gif_ev_arg2, gif_ev_arg3;
    logic [31:0]         gif_ev_flags;

    gif_packed_stub #(.REAL_AD_REG_MAP(1'b1)) u_gif (
        .clk(clk), .rst_n(rst_n),
        .in_valid(dmac_gif_valid), .in_data(dmac_gif_data),
        .in_last(dmac_gif_last), .in_ready(dmac_gif_ready),
        .image_data_ready(1'b1),
        .raster_fifo_full(1'b0),
        .gs_wr_en(gif_gs_wr_en), .gs_wr_addr(gif_gs_wr_addr),
        .gs_wr_data(gif_gs_wr_data),
        .gif_reg_wr_en(gif_gif_reg_wr_en),
        .gif_reg_num(gif_gif_reg_num),
        .gif_reg_data(gif_gif_reg_data),
        .ev_valid(gif_ev_valid), .ev_subsys(gif_ev_subsys),
        .ev_event(gif_ev_event),
        .ev_arg0(gif_ev_arg0), .ev_arg1(gif_ev_arg1),
        .ev_arg2(gif_ev_arg2), .ev_arg3(gif_ev_arg3),
        .ev_flags(gif_ev_flags)
    );

    // gs_stub
    logic [7:0]          bg_r, bg_g, bg_b;
    logic [63:0]         prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_q;
    logic                prim_complete;
    logic [31:0]         prim_complete_count;
    logic [63:0]         prim_v0_q, prim_v1_q, prim_v2_q;
    logic [63:0]         prim_color_q;
    logic [63:0]         prim_color_v0_q, prim_color_v1_q, prim_color_v2_q;
    trace_pkg::vertex_t  prim_v0_decoded_q, prim_v1_decoded_q, prim_v2_decoded_q;
    trace_pkg::color_t   prim_v0_color_decoded_q, prim_v1_color_decoded_q, prim_v2_color_decoded_q;
    logic                gs_ev_valid;
    trace_pkg::subsys_e  gs_ev_subsys;
    trace_pkg::event_e   gs_ev_event;
    logic [63:0]         gs_ev_arg0, gs_ev_arg1, gs_ev_arg2, gs_ev_arg3;
    logic [31:0]         gs_ev_flags;

    gs_stub u_gs (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(gif_gs_wr_en), .reg_wr_addr(gif_gs_wr_addr),
        .reg_wr_data(gif_gs_wr_data),
        .gif_reg_wr_en(gif_gif_reg_wr_en),
        .gif_reg_num(gif_gif_reg_num),
        .gif_reg_data(gif_gif_reg_data),
        .bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
        .prim_q(prim_q), .rgbaq_q(rgbaq_q),
        .xyz2_q(xyz2_q), .xyzf2_q(xyzf2_q),
        .frame_1_q(frame_1_q), .zbuf_1_q(zbuf_1_q),
        .prim_complete(prim_complete),
        .prim_complete_count(prim_complete_count),
        .prim_v0_q(prim_v0_q), .prim_v1_q(prim_v1_q), .prim_v2_q(prim_v2_q),
        .prim_color_q(prim_color_q),
        .prim_color_v0_q(prim_color_v0_q),
        .prim_color_v1_q(prim_color_v1_q),
        .prim_color_v2_q(prim_color_v2_q),
        .prim_v0_decoded_q(prim_v0_decoded_q),
        .prim_v1_decoded_q(prim_v1_decoded_q),
        .prim_v2_decoded_q(prim_v2_decoded_q),
        .prim_v0_color_decoded_q(prim_v0_color_decoded_q),
        .prim_v1_color_decoded_q(prim_v1_color_decoded_q),
        .prim_v2_color_decoded_q(prim_v2_color_decoded_q),
        .ev_valid(gs_ev_valid), .ev_subsys(gs_ev_subsys),
        .ev_event(gs_ev_event),
        .ev_arg0(gs_ev_arg0), .ev_arg1(gs_ev_arg1),
        .ev_arg2(gs_ev_arg2), .ev_arg3(gs_ev_arg3),
        .ev_flags(gs_ev_flags)
    );

    trace_sink_stub #(.FILENAME("gif_primitive_strip_dmac.trace"), .SINK_LABEL("dmac"))
        u_trace_dmac (.clk(clk), .rst_n(rst_n),
            .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
            .ev_event(dmac_ev_event),
            .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
            .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
            .ev_flags(dmac_ev_flags));
    trace_sink_stub #(.FILENAME("gif_primitive_strip_gif.trace"), .SINK_LABEL("gif"))
        u_trace_gif (.clk(clk), .rst_n(rst_n),
            .ev_valid(gif_ev_valid), .ev_subsys(gif_ev_subsys),
            .ev_event(gif_ev_event),
            .ev_arg0(gif_ev_arg0), .ev_arg1(gif_ev_arg1),
            .ev_arg2(gif_ev_arg2), .ev_arg3(gif_ev_arg3),
            .ev_flags(gif_ev_flags));
    trace_sink_stub #(.FILENAME("gif_primitive_strip_gs.trace"), .SINK_LABEL("gs"))
        u_trace_gs (.clk(clk), .rst_n(rst_n),
            .ev_valid(gs_ev_valid), .ev_subsys(gs_ev_subsys),
            .ev_event(gs_ev_event),
            .ev_arg0(gs_ev_arg0), .ev_arg1(gs_ev_arg1),
            .ev_arg2(gs_ev_arg2), .ev_arg3(gs_ev_arg3),
            .ev_flags(gs_ev_flags));

    // Counters
    int dma_start_count, dma_beat_count, dma_done_count;
    int gif_giftag_count, gif_write_count, gif_mode_count;
    int gs_bgcolor_count, gs_write_count, gs_prim_draw_count, gs_mode_count;
    int prim_complete_pulse_count;
    int errors;
    logic dma_done_seen;

    initial begin
        dma_start_count = 0; dma_beat_count = 0; dma_done_count = 0;
        gif_giftag_count = 0; gif_write_count = 0; gif_mode_count = 0;
        gs_bgcolor_count = 0; gs_write_count = 0;
        gs_prim_draw_count = 0; gs_mode_count = 0;
        prim_complete_pulse_count = 0;
        errors = 0; dma_done_seen = 1'b0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid) begin
            unique case (dmac_ev_event)
                trace_pkg::EV_DMA_START: dma_start_count <= dma_start_count + 1;
                trace_pkg::EV_DMA_BEAT:  dma_beat_count  <= dma_beat_count  + 1;
                trace_pkg::EV_DMA_DONE: begin
                    dma_done_count <= dma_done_count + 1;
                    dma_done_seen  <= 1'b1;
                end
                default: ;
            endcase
        end
        if (rst_n && gif_ev_valid) begin
            if (gif_ev_event == trace_pkg::EV_GIFTAG) gif_giftag_count <= gif_giftag_count + 1;
            if (gif_ev_event == trace_pkg::EV_WRITE)  gif_write_count  <= gif_write_count  + 1;
            if (gif_ev_event == trace_pkg::EV_MODE)   gif_mode_count   <= gif_mode_count   + 1;
        end
        if (rst_n && gs_ev_valid) begin
            if (gs_ev_event == trace_pkg::EV_BGCOLOR)   gs_bgcolor_count   <= gs_bgcolor_count   + 1;
            if (gs_ev_event == trace_pkg::EV_WRITE)     gs_write_count     <= gs_write_count     + 1;
            if (gs_ev_event == trace_pkg::EV_PRIM_DRAW) gs_prim_draw_count <= gs_prim_draw_count + 1;
            if (gs_ev_event == trace_pkg::EV_MODE)      gs_mode_count      <= gs_mode_count      + 1;
        end
        if (rst_n && prim_complete) begin
            prim_complete_pulse_count <= prim_complete_pulse_count + 1;
        end
    end

    // Stimulus
    localparam logic [7:0]  CHCR_OFF     = 8'h00;
    localparam logic [7:0]  MADR_OFF     = 8'h10;
    localparam logic [7:0]  QWC_OFF      = 8'h20;
    localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0100;
    localparam int          NUM_QWORDS   = 32;

    task automatic write_dmac(input logic [7:0] offset, input logic [31:0] data);
        @(negedge clk); dmac_reg_wr_en = 1'b1; dmac_reg_offset = offset; dmac_reg_wr_data = data;
        @(negedge clk); dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0;
    endtask

    task automatic preload_qword(input logic [RAM_ADDR_W-1:0] addr,
                                 input logic [127:0] data);
        @(negedge clk); ram_wr_en = 1'b1; ram_wr_addr = addr; ram_wr_data = data; ram_wr_be = 16'hFFFF;
        @(negedge clk); ram_wr_en = 1'b0; ram_wr_addr = '0; ram_wr_data = 128'd0; ram_wr_be = 16'd0;
    endtask

    function automatic logic [127:0] giftag(
        input logic [14:0] nloop, input logic eop,
        input logic [1:0] flg, input logic [3:0] nreg, input logic [63:0] regs);
        logic [63:0] lower;
        lower = {nreg, flg, 42'd0, eop, nloop};
        return {regs, lower};
    endfunction

    function automatic logic [127:0] aplusd_real(input logic [7:0]  reg_num,
                                                 input logic [63:0] data64);
        return {56'd0, reg_num, data64};
    endfunction

    localparam logic [7:0] R_PRIM  = 8'h00;
    localparam logic [7:0] R_XYZF2 = 8'h04;
    localparam logic [7:0] R_XYZ2  = 8'h05;

    localparam logic [63:0] PRIM_LINE_STRIP = 64'd2;
    localparam logic [63:0] PRIM_TRI_STRIP  = 64'd4;
    localparam logic [63:0] PRIM_TRI_FAN    = 64'd5;
    localparam logic [63:0] PRIM_RESERVED   = 64'd7;

    // EV_PRIM_DRAW capture array (9 entries) — args + Ch78 vertex tuple
    logic [63:0] pd_arg0 [0:8];
    logic [63:0] pd_arg1 [0:8];
    logic [63:0] pd_arg2 [0:8];
    logic [63:0] pd_arg3 [0:8];
    logic [63:0] pd_v0    [0:8];
    logic [63:0] pd_v1    [0:8];
    logic [63:0] pd_v2    [0:8];
    logic [63:0] pd_color [0:8];
    logic [63:0] pd_cv0   [0:8];
    logic [63:0] pd_cv1   [0:8];
    logic [63:0] pd_cv2   [0:8];
    trace_pkg::vertex_t pd_dv0 [0:8];
    trace_pkg::vertex_t pd_dv1 [0:8];
    trace_pkg::vertex_t pd_dv2 [0:8];
    int          pd_idx;

    initial begin
        pd_idx = 0;
        for (int i = 0; i < 9; i = i + 1) begin
            pd_arg0[i]  = 64'd0; pd_arg1[i] = 64'd0;
            pd_arg2[i]  = 64'd0; pd_arg3[i] = 64'd0;
            pd_v0[i]    = 64'd0; pd_v1[i]   = 64'd0; pd_v2[i] = 64'd0;
            pd_color[i] = 64'd0;
            pd_cv0[i]   = 64'd0; pd_cv1[i]  = 64'd0; pd_cv2[i] = 64'd0;
        end
    end

    always_ff @(posedge clk) begin
        if (rst_n && gs_ev_valid && gs_ev_event == trace_pkg::EV_PRIM_DRAW
            && pd_idx < 9) begin
            pd_arg0[pd_idx]  <= gs_ev_arg0;
            pd_arg1[pd_idx]  <= gs_ev_arg1;
            pd_arg2[pd_idx]  <= gs_ev_arg2;
            pd_arg3[pd_idx]  <= gs_ev_arg3;
            pd_v0[pd_idx]    <= prim_v0_q;
            pd_v1[pd_idx]    <= prim_v1_q;
            pd_v2[pd_idx]    <= prim_v2_q;
            pd_color[pd_idx] <= prim_color_q;
            pd_cv0[pd_idx]   <= prim_color_v0_q;
            pd_cv1[pd_idx]   <= prim_color_v1_q;
            pd_cv2[pd_idx]   <= prim_color_v2_q;
            pd_dv0[pd_idx]   <= prim_v0_decoded_q;
            pd_dv1[pd_idx]   <= prim_v1_decoded_q;
            pd_dv2[pd_idx]   <= prim_v2_decoded_q;
            pd_idx           <= pd_idx + 1;
        end
    end

    initial begin
        rst_n = 1'b0;
        dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0;
        ram_wr_en = 1'b0; ram_wr_addr = '0; ram_wr_data = 128'd0; ram_wr_be = 16'd0;

        repeat (4) @(posedge clk); rst_n = 1'b1; repeat (8) @(posedge clk);

        begin : preload_block
            logic [RAM_ADDR_W-1:0] addr_q;

            // ---------- Tag1: LINE_STRIP, 4 vertices (NREG=5) ----------
            addr_q = PAYLOAD_MADR + 32'd0;
            preload_qword(addr_q, giftag(15'd1, 1'b0, 2'd0, 4'd5,
                                         64'h0000_0000_000E_EEEE));
            addr_q = PAYLOAD_MADR + 32'd16;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_LINE_STRIP));
            addr_q = PAYLOAD_MADR + 32'd32;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h10));
            addr_q = PAYLOAD_MADR + 32'd48;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h20));
            addr_q = PAYLOAD_MADR + 32'd64;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h30));
            addr_q = PAYLOAD_MADR + 32'd80;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h40));

            // ---------- Tag2: TRI_STRIP, 5 vertices (NREG=6) ----------
            addr_q = PAYLOAD_MADR + 32'd96;
            preload_qword(addr_q, giftag(15'd1, 1'b0, 2'd0, 4'd6,
                                         64'h0000_0000_00EE_EEEE));
            addr_q = PAYLOAD_MADR + 32'd112;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_TRI_STRIP));
            addr_q = PAYLOAD_MADR + 32'd128;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h110));
            addr_q = PAYLOAD_MADR + 32'd144;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h120));
            addr_q = PAYLOAD_MADR + 32'd160;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h130));
            // v4 uses XYZF2 to lock "XYZF2 also closes strip primitives".
            // This vertex saturates a TRI_STRIP → EV_PRIM_DRAW fires.
            addr_q = PAYLOAD_MADR + 32'd176;
            preload_qword(addr_q, aplusd_real(R_XYZF2, 64'h140));
            addr_q = PAYLOAD_MADR + 32'd192;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h150));

            // ---------- Tag3: TRI_FAN, 5 vertices (NREG=6) ----------
            addr_q = PAYLOAD_MADR + 32'd208;
            preload_qword(addr_q, giftag(15'd1, 1'b0, 2'd0, 4'd6,
                                         64'h0000_0000_00EE_EEEE));
            addr_q = PAYLOAD_MADR + 32'd224;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_TRI_FAN));
            addr_q = PAYLOAD_MADR + 32'd240;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h210));
            addr_q = PAYLOAD_MADR + 32'd256;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h220));
            addr_q = PAYLOAD_MADR + 32'd272;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h230));
            addr_q = PAYLOAD_MADR + 32'd288;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h240));
            addr_q = PAYLOAD_MADR + 32'd304;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h250));

            // ---------- Tag4: TRI_STRIP, 2 vertices — anchor unmet (NREG=3) ----------
            addr_q = PAYLOAD_MADR + 32'd320;
            preload_qword(addr_q, giftag(15'd1, 1'b0, 2'd0, 4'd3,
                                         64'h0000_0000_0000_0EEE));
            addr_q = PAYLOAD_MADR + 32'd336;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_TRI_STRIP));
            addr_q = PAYLOAD_MADR + 32'd352;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h310));
            addr_q = PAYLOAD_MADR + 32'd368;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h320));

            // ---------- Tag5: LINE_STRIP, 1 vertex — anchor unmet (NREG=2) ----------
            addr_q = PAYLOAD_MADR + 32'd384;
            preload_qword(addr_q, giftag(15'd1, 1'b0, 2'd0, 4'd2,
                                         64'h0000_0000_0000_00EE));
            addr_q = PAYLOAD_MADR + 32'd400;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_LINE_STRIP));
            addr_q = PAYLOAD_MADR + 32'd416;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h410));

            // ---------- Tag6: reserved PRIM=7, 3 vertices — no draws (NREG=4) ----------
            addr_q = PAYLOAD_MADR + 32'd432;
            preload_qword(addr_q, giftag(15'd1, 1'b1, 2'd0, 4'd4,
                                         64'h0000_0000_0000_EEEE));
            addr_q = PAYLOAD_MADR + 32'd448;
            preload_qword(addr_q, aplusd_real(R_PRIM, PRIM_RESERVED));
            addr_q = PAYLOAD_MADR + 32'd464;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h510));
            addr_q = PAYLOAD_MADR + 32'd480;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h520));
            addr_q = PAYLOAD_MADR + 32'd496;
            preload_qword(addr_q, aplusd_real(R_XYZ2, 64'h530));
        end

        write_dmac(MADR_OFF, PAYLOAD_MADR);
        write_dmac(QWC_OFF,  NUM_QWORDS);
        write_dmac(CHCR_OFF, 32'h0000_0001);

        wait (dma_done_seen == 1'b1);
        repeat (40) @(posedge clk);

        $display("[tb_ee_core_gif_primitive_strip] dma=(%0d,%0d,%0d)  gif=(tag=%0d,wr=%0d,mode=%0d)  gs=(bg=%0d,wr=%0d,prim_draw=%0d,mode=%0d)  prim_complete_count=%0d  prim_pulses=%0d",
                 dma_start_count, dma_beat_count, dma_done_count,
                 gif_giftag_count, gif_write_count, gif_mode_count,
                 gs_bgcolor_count, gs_write_count, gs_prim_draw_count, gs_mode_count,
                 prim_complete_count, prim_complete_pulse_count);
        $display("[tb_ee_core_gif_primitive_strip] final latches: prim=0x%016h xyz2=0x%016h",
                 prim_q, xyz2_q);

        if (dma_start_count != 1)         begin $error("dma_start"); errors = errors + 1; end
        if (dma_beat_count  != NUM_QWORDS) begin $error("dma_beat=%0d (expected %0d)", dma_beat_count, NUM_QWORDS); errors = errors + 1; end
        if (dma_done_count  != 1)         begin $error("dma_done"); errors = errors + 1; end
        if (gif_giftag_count != 6)        begin $error("gif_giftag=%0d (expected 6)", gif_giftag_count); errors = errors + 1; end
        if (gif_write_count != 26)        begin $error("gif_write=%0d (expected 26: 5+6+6+3+2+4)", gif_write_count); errors = errors + 1; end
        if (gif_mode_count != 0)          begin $error("gif_mode=%0d (expected 0)", gif_mode_count); errors = errors + 1; end
        if (gs_bgcolor_count != 0)        begin $error("gs_bgcolor=%0d", gs_bgcolor_count); errors = errors + 1; end
        if (gs_write_count != 17)         begin $error("gs_write=%0d (expected 17: 6 PRIM + 11 non-closing vertices)", gs_write_count); errors = errors + 1; end
        if (gs_prim_draw_count != 9)      begin $error("gs_prim_draw=%0d (expected 9: STRIP 3 + STRIP 3 + FAN 3)", gs_prim_draw_count); errors = errors + 1; end
        if (gs_mode_count != 0)           begin $error("gs_mode=%0d (expected 0)", gs_mode_count); errors = errors + 1; end
        if (prim_complete_count != 32'd9) begin $error("prim_complete_count=%0d (expected 9)", prim_complete_count); errors = errors + 1; end
        if (prim_complete_pulse_count != 9) begin $error("prim_complete pulses=%0d (expected 9)", prim_complete_pulse_count); errors = errors + 1; end
        if (prim_q !== PRIM_RESERVED)     begin $error("prim_q=0x%016h (expected 0x7)", prim_q); errors = errors + 1; end
        if (xyz2_q !== 64'h530)           begin $error("xyz2_q=0x%016h (expected 0x530)", xyz2_q); errors = errors + 1; end
        // xyzf2_q final = 0x140 — the only XYZF2 vertex in the stream
        // (v4 of TRI_STRIP). Locks that XYZF2 commits land on xyzf2_q
        // and that strip cadence works through the XYZF2 path.
        if (xyzf2_q !== 64'h140)          begin $error("xyzf2_q=0x%016h (expected 0x140)", xyzf2_q); errors = errors + 1; end

        // EV_PRIM_DRAW arg checks. Each row: (prim_type, threshold, count, vertex)
        // Tag1 LINE_STRIP draws (anchor + 2 extensions)
        if (pd_arg0[0] !== 64'd2)   begin $error("pd[0].arg0=%0d (expected 2 LINE_STRIP)", pd_arg0[0]); errors = errors + 1; end
        if (pd_arg1[0] !== 64'd2)   begin $error("pd[0].arg1=%0d (expected 2)",            pd_arg1[0]); errors = errors + 1; end
        if (pd_arg2[0] !== 64'd1)   begin $error("pd[0].arg2=%0d (expected 1)",            pd_arg2[0]); errors = errors + 1; end
        if (pd_arg3[0] !== 64'h20)  begin $error("pd[0].arg3=0x%h (expected 0x20)",        pd_arg3[0]); errors = errors + 1; end

        if (pd_arg0[1] !== 64'd2)   begin $error("pd[1].arg0=%0d (expected 2)",            pd_arg0[1]); errors = errors + 1; end
        if (pd_arg2[1] !== 64'd2)   begin $error("pd[1].arg2=%0d (expected 2)",            pd_arg2[1]); errors = errors + 1; end
        if (pd_arg3[1] !== 64'h30)  begin $error("pd[1].arg3=0x%h (expected 0x30 — strip extension)", pd_arg3[1]); errors = errors + 1; end

        if (pd_arg0[2] !== 64'd2)   begin $error("pd[2].arg0=%0d (expected 2)",            pd_arg0[2]); errors = errors + 1; end
        if (pd_arg2[2] !== 64'd3)   begin $error("pd[2].arg2=%0d (expected 3)",            pd_arg2[2]); errors = errors + 1; end
        if (pd_arg3[2] !== 64'h40)  begin $error("pd[2].arg3=0x%h (expected 0x40)",        pd_arg3[2]); errors = errors + 1; end

        // Tag2 TRI_STRIP draws (anchor at v3 + 2 rolling tris at v4, v5)
        if (pd_arg0[3] !== 64'd4)   begin $error("pd[3].arg0=%0d (expected 4 TRI_STRIP)",  pd_arg0[3]); errors = errors + 1; end
        if (pd_arg1[3] !== 64'd3)   begin $error("pd[3].arg1=%0d (expected 3)",            pd_arg1[3]); errors = errors + 1; end
        if (pd_arg2[3] !== 64'd4)   begin $error("pd[3].arg2=%0d (expected 4)",            pd_arg2[3]); errors = errors + 1; end
        if (pd_arg3[3] !== 64'h130) begin $error("pd[3].arg3=0x%h (expected 0x130)",       pd_arg3[3]); errors = errors + 1; end

        if (pd_arg0[5] !== 64'd4)   begin $error("pd[5].arg0=%0d (expected 4)",            pd_arg0[5]); errors = errors + 1; end
        if (pd_arg2[5] !== 64'd6)   begin $error("pd[5].arg2=%0d (expected 6)",            pd_arg2[5]); errors = errors + 1; end
        if (pd_arg3[5] !== 64'h150) begin $error("pd[5].arg3=0x%h (expected 0x150)",       pd_arg3[5]); errors = errors + 1; end

        // Tag3 TRI_FAN draws (anchor at v3 + 2 fan tris)
        if (pd_arg0[6] !== 64'd5)   begin $error("pd[6].arg0=%0d (expected 5 TRI_FAN)",    pd_arg0[6]); errors = errors + 1; end
        if (pd_arg1[6] !== 64'd3)   begin $error("pd[6].arg1=%0d (expected 3)",            pd_arg1[6]); errors = errors + 1; end
        if (pd_arg2[6] !== 64'd7)   begin $error("pd[6].arg2=%0d (expected 7)",            pd_arg2[6]); errors = errors + 1; end
        if (pd_arg3[6] !== 64'h230) begin $error("pd[6].arg3=0x%h (expected 0x230)",       pd_arg3[6]); errors = errors + 1; end

        if (pd_arg0[8] !== 64'd5)   begin $error("pd[8].arg0=%0d (expected 5)",            pd_arg0[8]); errors = errors + 1; end
        if (pd_arg2[8] !== 64'd9)   begin $error("pd[8].arg2=%0d (expected 9)",            pd_arg2[8]); errors = errors + 1; end
        if (pd_arg3[8] !== 64'h250) begin $error("pd[8].arg3=0x%h (expected 0x250)",       pd_arg3[8]); errors = errors + 1; end

        // ---------- Ch78: vertex-tuple identity checks ----------
        // LINE_STRIP draws (2 slots; v2 unused → 0)
        if (pd_v0[0] !== 64'h10)   begin $error("pd_v0[0]=0x%h (expected 0x10 — LINE_STRIP anchor v1)", pd_v0[0]); errors = errors + 1; end
        if (pd_v1[0] !== 64'h20)   begin $error("pd_v1[0]=0x%h (expected 0x20)",            pd_v1[0]); errors = errors + 1; end
        if (pd_v2[0] !== 64'd0)    begin $error("pd_v2[0]=0x%h (expected 0; LINE_STRIP has 2 slots)", pd_v2[0]); errors = errors + 1; end

        if (pd_v0[1] !== 64'h20)   begin $error("pd_v0[1]=0x%h (expected 0x20 — LINE_STRIP roll v2)", pd_v0[1]); errors = errors + 1; end
        if (pd_v1[1] !== 64'h30)   begin $error("pd_v1[1]=0x%h (expected 0x30)",            pd_v1[1]); errors = errors + 1; end
        if (pd_v2[1] !== 64'd0)    begin $error("pd_v2[1]=0x%h (expected 0; LINE_STRIP has 2 slots)", pd_v2[1]); errors = errors + 1; end

        if (pd_v0[2] !== 64'h30)   begin $error("pd_v0[2]=0x%h (expected 0x30 — LINE_STRIP roll v3)", pd_v0[2]); errors = errors + 1; end
        if (pd_v1[2] !== 64'h40)   begin $error("pd_v1[2]=0x%h (expected 0x40)",            pd_v1[2]); errors = errors + 1; end
        if (pd_v2[2] !== 64'd0)    begin $error("pd_v2[2]=0x%h (expected 0; LINE_STRIP has 2 slots)", pd_v2[2]); errors = errors + 1; end

        // TRI_STRIP draws — rolling window {v_n-2, v_n-1, v_n}
        // [3] anchor: {v1=0x110, v2=0x120, v3=0x130}
        if (pd_v0[3] !== 64'h110)  begin $error("pd_v0[3]=0x%h (expected 0x110 — TRI_STRIP anchor v1)", pd_v0[3]); errors = errors + 1; end
        if (pd_v1[3] !== 64'h120)  begin $error("pd_v1[3]=0x%h (expected 0x120)",           pd_v1[3]); errors = errors + 1; end
        if (pd_v2[3] !== 64'h130)  begin $error("pd_v2[3]=0x%h (expected 0x130)",           pd_v2[3]); errors = errors + 1; end
        // [4] saturated extension: {v2=0x120, v3=0x130, v4=0x140 (XYZF2!)}
        if (pd_v0[4] !== 64'h120)  begin $error("pd_v0[4]=0x%h (expected 0x120 — TRI_STRIP rolled v_n-2)", pd_v0[4]); errors = errors + 1; end
        if (pd_v1[4] !== 64'h130)  begin $error("pd_v1[4]=0x%h (expected 0x130)",           pd_v1[4]); errors = errors + 1; end
        if (pd_v2[4] !== 64'h140)  begin $error("pd_v2[4]=0x%h (expected 0x140 — XYZF2 closing vertex)", pd_v2[4]); errors = errors + 1; end
        // [5] saturated extension: {v3=0x130, v4=0x140, v5=0x150}
        // — proves the prior XYZF2 vertex (v4=0x140) carries forward
        //   in the rolling window after a strip extension.
        if (pd_v0[5] !== 64'h130)  begin $error("pd_v0[5]=0x%h (expected 0x130)",           pd_v0[5]); errors = errors + 1; end
        if (pd_v1[5] !== 64'h140)  begin $error("pd_v1[5]=0x%h (expected 0x140 — XYZF2 vertex stays in window)", pd_v1[5]); errors = errors + 1; end
        if (pd_v2[5] !== 64'h150)  begin $error("pd_v2[5]=0x%h (expected 0x150)",           pd_v2[5]); errors = errors + 1; end

        // TRI_FAN draws — pivot+prev+curr
        // [6] anchor: {v1=0x210, v2=0x220, v3=0x230}
        if (pd_v0[6] !== 64'h210)  begin $error("pd_v0[6]=0x%h (expected 0x210 — TRI_FAN anchor v1)", pd_v0[6]); errors = errors + 1; end
        if (pd_v1[6] !== 64'h220)  begin $error("pd_v1[6]=0x%h (expected 0x220)",           pd_v1[6]); errors = errors + 1; end
        if (pd_v2[6] !== 64'h230)  begin $error("pd_v2[6]=0x%h (expected 0x230)",           pd_v2[6]); errors = errors + 1; end
        // [7] saturated extension: {pivot=0x210, v_n-1=0x230, closing=0x240}
        // — load-bearing distinction from TRI_STRIP. A TRI_STRIP at
        //   the same vertex stream would emit v0=0x220 here.
        if (pd_v0[7] !== 64'h210)  begin $error("pd_v0[7]=0x%h (expected 0x210 — TRI_FAN pivot PINNED)", pd_v0[7]); errors = errors + 1; end
        if (pd_v1[7] !== 64'h230)  begin $error("pd_v1[7]=0x%h (expected 0x230 — v_n-1 rolled)", pd_v1[7]); errors = errors + 1; end
        if (pd_v2[7] !== 64'h240)  begin $error("pd_v2[7]=0x%h (expected 0x240)",           pd_v2[7]); errors = errors + 1; end
        // [8] saturated extension: {pivot=0x210, v_n-1=0x240, closing=0x250}
        if (pd_v0[8] !== 64'h210)  begin $error("pd_v0[8]=0x%h (expected 0x210 — pivot still pinned)", pd_v0[8]); errors = errors + 1; end
        if (pd_v1[8] !== 64'h240)  begin $error("pd_v1[8]=0x%h (expected 0x240)",           pd_v1[8]); errors = errors + 1; end
        if (pd_v2[8] !== 64'h250)  begin $error("pd_v2[8]=0x%h (expected 0x250)",           pd_v2[8]); errors = errors + 1; end

        // Ch79 color-snapshot checks. This stream writes NO RGBAQ
        // entries anywhere — rgbaq_q stays at its reset value (0)
        // for the entire run, so every closed primitive should
        // snapshot prim_color_q == 0. Ch80 per-vertex color slots
        // also stay at 0 for the same reason.
        for (int i = 0; i < 9; i = i + 1) begin
            if (pd_color[i] !== 64'd0) begin
                $error("pd_color[%0d]=0x%h (expected 0; no RGBAQ written in stream)", i, pd_color[i]);
                errors = errors + 1;
            end
            if (pd_cv0[i] !== 64'd0) begin
                $error("pd_cv0[%0d]=0x%h (expected 0; no RGBAQ written)", i, pd_cv0[i]);
                errors = errors + 1;
            end
            if (pd_cv1[i] !== 64'd0) begin
                $error("pd_cv1[%0d]=0x%h (expected 0; no RGBAQ written)", i, pd_cv1[i]);
                errors = errors + 1;
            end
            if (pd_cv2[i] !== 64'd0) begin
                $error("pd_cv2[%0d]=0x%h (expected 0; no RGBAQ written)", i, pd_cv2[i]);
                errors = errors + 1;
            end
        end

        // Ch81 format-flag rolling check. The strip TB uses XYZ2 for
        // every vertex EXCEPT TRI_STRIP v4 (= 0x140), which is XYZF2.
        // That flag must follow the vertex through the rolling
        // window: it's the closing vertex at event [4], then rolls
        // into v_prev (v1 slot) at event [5], then drops out of the
        // 3-deep window by event [6] (where v_n-2/v_n-1/v_n =
        // {STRIP partial v1, STRIP partial v2, FAN reset...}, but
        // by then the FAN tag has issued its own PRIM write which
        // clears the flag window anyway).
        for (int i = 0; i < 9; i = i + 1) begin
            trace_pkg::vertex_t dv0, dv1, dv2;
            dv0 = pd_dv0[i];
            dv1 = pd_dv1[i];
            dv2 = pd_dv2[i];
            case (i)
            4: begin // TRI_STRIP saturated v4: closing slot is XYZF2
                if (dv2.is_xyzf2 !== 1'b1) begin $error("pd_dv2[4].is_xyzf2=%b (expected 1; v4 closes via XYZF2)", dv2.is_xyzf2); errors = errors + 1; end
                if (dv2.x        !== 16'h140) begin $error("pd_dv2[4].x=0x%h (expected 0x140)", dv2.x); errors = errors + 1; end
                if (dv1.is_xyzf2 !== 1'b0) begin $error("pd_dv1[4].is_xyzf2=%b (expected 0; v3 was XYZ2)", dv1.is_xyzf2); errors = errors + 1; end
            end
            5: begin // TRI_STRIP saturated v5: v_n-1 slot now XYZF2 (rolled from v4)
                if (dv1.is_xyzf2 !== 1'b1) begin $error("pd_dv1[5].is_xyzf2=%b (expected 1; XYZF2 vertex rolled into v_prev)", dv1.is_xyzf2); errors = errors + 1; end
                if (dv1.x        !== 16'h140) begin $error("pd_dv1[5].x=0x%h (expected 0x140 — XYZF2 vertex carried in window)", dv1.x); errors = errors + 1; end
                if (dv2.is_xyzf2 !== 1'b0) begin $error("pd_dv2[5].is_xyzf2=%b (expected 0; v5 was XYZ2)", dv2.is_xyzf2); errors = errors + 1; end
            end
            default: begin
                // All other events should have is_xyzf2=0 throughout
                // (no XYZF2 vertices in their windows).
                if (dv0.is_xyzf2 !== 1'b0) begin $error("pd_dv0[%0d].is_xyzf2=%b (expected 0)", i, dv0.is_xyzf2); errors = errors + 1; end
                if (dv1.is_xyzf2 !== 1'b0) begin $error("pd_dv1[%0d].is_xyzf2=%b (expected 0)", i, dv1.is_xyzf2); errors = errors + 1; end
                if (dv2.is_xyzf2 !== 1'b0) begin $error("pd_dv2[%0d].is_xyzf2=%b (expected 0)", i, dv2.is_xyzf2); errors = errors + 1; end
            end
            endcase
        end

        if (errors == 0) $display("[tb_ee_core_gif_primitive_strip] PASS");
        else             $display("[tb_ee_core_gif_primitive_strip] FAIL");
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_ee_core_gif_primitive_strip] timeout");
        $finish;
    end

endmodule : tb_ee_core_gif_primitive_strip
