// retroDE_ps2 — tb_bgcolor_via_dma
//
// Wave 2.7 integration testbench: DMAC routed through ee_memory_map_stub.
// Same two-beat red-final outcome as Wave 2.6, but DMAC no longer talks
// directly to ee_ram_stub — it goes through the memory map, which decodes
// the RAM window and routes the request to the RAM stub. Adds trace-visible
// proof of the map-layer hop.
//
// Plan refs:
//   docs/wave26_multi_beat_dma_plan.md    (multi-beat transfers — preserved)
//   docs/wave25_memory_backed_dma_plan.md (memory-backed payload — preserved)
//   docs/wave2_dma_gif_plan.md            (Wave 2 original arc)
//
// Scenario: preload two qwords at MADR and MADR+16, each a project-local
// GIF-register-write packet targeting BGCOLOR. Beat 0 sets blue, beat 1
// sets red (the final visible color). DMAC fetches both in sequence,
// GIF accepts two writes, GS observes both BGCOLOR updates, platform
// settles to the second color.
//
// DUT chain (Wave 2.7):
//   TB writes ──────────────────────► ee_ram_stub (write port, preload)
//
//   dmac_reg_stub (channel 2)
//         │ mem_rd_*                   (physical address, 128-bit data)
//         ▼
//   ee_memory_map_stub                 (decodes EE_RAM window → RAM)
//         │ ram_rd_*
//         ▼
//   ee_ram_stub (read port)            (delivery)
//         │ rd_data back up through map_dmac_rd_data
//         ▼ (DMAC holds qword, forwards on ep_* to...)
//   gif_path_stub                      (project-local packet decode)
//         │ reg_wr_*
//         ▼
//   gs_stub                            (BGCOLOR latch)
//         │ bg_{r,g,b}
//         ▼
//   platform_video_stub                (flood-fill raster)
//
// Pass criteria (Wave 2.7):
//   - exactly 2 MEM READs from ee_ram_stub (delivery events, master=DMAC)
//   - exactly 2 MEM READs from ee_memory_map_stub for DMAC
//     (request-routing events; master=DMAC=1, region=EE_RAM=1)
//   - DMA_CFG x≥3 / DMA_START x1 / DMA_BEAT x2 / DMA_DONE x1
//   - DMA_START.arg2 == programmed MADR
//   - DMA_BEAT.arg2 for beat 0 == MADR, for beat 1 == MADR + 16
//   - first GIFTAG flags[0] == 0, second GIFTAG flags[0] == 1 (in_last)
//   - GIFTAG x2, BGCOLOR x2
//   - DMA_DONE is observed only after the second GIFTAG (causality)
//   - final bg_{r,g,b} == second-beat color (red)
//   - platform continues to render active pixels after transfer

`timescale 1ns/1ps

module tb_bgcolor_via_dma;

    // Tiny platform video timing.
    localparam int H_ACTIVE = 8;
    localparam int H_FRONT  = 2;
    localparam int H_SYNC   = 2;
    localparam int H_BACK   = 2;
    localparam int V_ACTIVE = 4;
    localparam int V_FRONT  = 1;
    localparam int V_SYNC   = 2;
    localparam int V_BACK   = 1;

    localparam int RAM_SIZE_BYTES = 4 * 1024;   // 4 KiB — plenty
    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;

    logic                    ram_ev_valid;
    trace_pkg::subsys_e      ram_ev_subsys;
    trace_pkg::event_e       ram_ev_event;
    logic [63:0]             ram_ev_arg0, ram_ev_arg1, ram_ev_arg2, ram_ev_arg3;
    logic [31:0]             ram_ev_flags;

    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(ram_ev_valid), .ev_subsys(ram_ev_subsys),
        .ev_event(ram_ev_event),
        .ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1),
        .ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3),
        .ev_flags(ram_ev_flags)
    );

    // master_id selector: when DMAC is driving the read, tag as DMAC (1);
    // when the TB is preloading via wr_en, tag as TB_DIRECT (0).
    assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;

    // ------------------------------------------------------------------
    // DMAC (now memory-backed)
    // ------------------------------------------------------------------

    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;

    // DMAC ↔ memory-map wires (Wave 2.7). DMAC's memory-master port connects
    // into the map, not directly into RAM.
    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)
    );

    // ------------------------------------------------------------------
    // ee_memory_map_stub (Wave 2.7 — sits between DMAC and RAM)
    // ------------------------------------------------------------------

    logic         map_ram_rd_en;
    logic [24:0]  map_ram_rd_addr;

    logic                map_ev_valid;
    trace_pkg::subsys_e  map_ev_subsys;
    trace_pkg::event_e   map_ev_event;
    logic [63:0]         map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3;
    logic [31:0]         map_ev_flags;

    ee_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        // EE fetch port — unused in this TB
        .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 read port — active
        .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 downstream — unused
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        // RAM downstream — wired into ee_ram_stub
        .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 write port + RAM write downstream — unused by this TB
        .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),
        // Trace
        .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
        .ev_event(map_ev_event),
        .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
        .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
        .ev_flags(map_ev_flags)
    );

    // Connect the map's RAM-side output into ee_ram_stub. The map exposes a
    // 25-bit offset; ee_ram_stub takes its own narrower width.
    assign ram_rd_en   = map_ram_rd_en;
    assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0];

    // ------------------------------------------------------------------
    // GIF
    // ------------------------------------------------------------------

    logic        gif_gs_wr_en;
    logic [15:0] gif_gs_wr_addr;
    logic [63:0] gif_gs_wr_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_path_stub 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),
        .gs_wr_en(gif_gs_wr_en), .gs_wr_addr(gif_gs_wr_addr),
        .gs_wr_data(gif_gs_wr_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
    // ------------------------------------------------------------------

    logic [7:0]          bg_r, bg_g, bg_b;
    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(1'b0), .gif_reg_num(8'd0), .gif_reg_data(64'd0),
        .prim_q(), .rgbaq_q(), .xyz2_q(), .xyzf2_q(), .frame_1_q(), .zbuf_1_q(),
        .prim_complete(), .prim_complete_count(),
        .prim_v0_q(), .prim_v1_q(), .prim_v2_q(),
        .prim_color_q(),
        .prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(),
        .prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(),
        .prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(),
        .pixel_emit(), .pixel_emit_count(),
        .pixel_x_q(), .pixel_y_q(),
        .pixel_color_q(),
        .pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(),
        .pixel_fb_addr_q(),
        .raster_pixel_emit(), .raster_pixel_emit_count(),
        .raster_pixel_x_q(), .raster_pixel_y_q(),
        .raster_pixel_color_q(),
        .raster_pixel_fb_addr_q(),
        .raster_active(), .raster_overflow(),
        .raster_degenerate(),
        .bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
        .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)
    );

    // ------------------------------------------------------------------
    // Platform video
    // ------------------------------------------------------------------

    logic       hsync, vsync, de;
    logic [7:0] r, g, b;

    logic                plat_ev_valid;
    trace_pkg::subsys_e  plat_ev_subsys;
    trace_pkg::event_e   plat_ev_event;
    logic [63:0]         plat_ev_arg0, plat_ev_arg1, plat_ev_arg2, plat_ev_arg3;
    logic [31:0]         plat_ev_flags;

    platform_video_stub #(
        .H_ACTIVE(H_ACTIVE), .H_FRONT(H_FRONT), .H_SYNC(H_SYNC), .H_BACK(H_BACK),
        .V_ACTIVE(V_ACTIVE), .V_FRONT(V_FRONT), .V_SYNC(V_SYNC), .V_BACK(V_BACK)
    ) u_plat (
        .clk(clk), .rst_n(rst_n),
        .bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
        .hsync(hsync), .vsync(vsync), .de(de),
        .r(r), .g(g), .b(b),
        .ev_valid(plat_ev_valid), .ev_subsys(plat_ev_subsys),
        .ev_event(plat_ev_event),
        .ev_arg0(plat_ev_arg0), .ev_arg1(plat_ev_arg1),
        .ev_arg2(plat_ev_arg2), .ev_arg3(plat_ev_arg3),
        .ev_flags(plat_ev_flags)
    );

    // ------------------------------------------------------------------
    // Trace sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("bgcolor_via_dma_ram.trace"), .SINK_LABEL("ee_ram"))
        u_trace_ram (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
            .ev_event(ram_ev_event),
            .ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1),
            .ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3),
            .ev_flags(ram_ev_flags)
        );

    trace_sink_stub #(.FILENAME("bgcolor_via_dma_map.trace"), .SINK_LABEL("map"))
        u_trace_map (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
            .ev_event(map_ev_event),
            .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
            .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
            .ev_flags(map_ev_flags)
        );

    trace_sink_stub #(.FILENAME("bgcolor_via_dma_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("bgcolor_via_dma_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("bgcolor_via_dma_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)
        );

    trace_sink_stub #(.FILENAME("bgcolor_via_dma_plat.trace"), .SINK_LABEL("plat"))
        u_trace_plat (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(plat_ev_valid), .ev_subsys(plat_ev_subsys),
            .ev_event(plat_ev_event),
            .ev_arg0(plat_ev_arg0), .ev_arg1(plat_ev_arg1),
            .ev_arg2(plat_ev_arg2), .ev_arg3(plat_ev_arg3),
            .ev_flags(plat_ev_flags)
        );

    // ------------------------------------------------------------------
    // Counters / checkers
    // ------------------------------------------------------------------

    localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0100;   // arbitrary aligned

    int mem_reads_dmac;
    int mem_writes;
    int map_reads_dmac;         // map-layer request-routing events (DMAC, EE_RAM)
    int dma_cfg_count;
    int dma_start_count;
    int dma_beat_count;
    int dma_done_count;
    int giftag_count;
    int gs_bgcolor_count;
    int plat_frame_count;
    int post_transfer_frames;
    int errors;
    logic dma_done_seen;
    logic [63:0] dma_start_madr_observed;
    logic [63:0] dma_beat_src_beat0;
    logic [63:0] dma_beat_src_beat1;
    logic        giftag_last_beat0;
    logic        giftag_last_beat1;
    logic        dma_done_after_second_giftag;

    initial begin
        mem_reads_dmac               = 0;
        mem_writes                   = 0;
        map_reads_dmac               = 0;
        dma_cfg_count                = 0;
        dma_start_count              = 0;
        dma_beat_count               = 0;
        dma_done_count               = 0;
        giftag_count                 = 0;
        gs_bgcolor_count             = 0;
        plat_frame_count             = 0;
        post_transfer_frames         = 0;
        errors                       = 0;
        dma_done_seen                = 1'b0;
        dma_start_madr_observed      = 64'd0;
        dma_beat_src_beat0           = 64'd0;
        dma_beat_src_beat1           = 64'd0;
        giftag_last_beat0            = 1'b0;
        giftag_last_beat1            = 1'b0;
        dma_done_after_second_giftag = 1'b0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && ram_ev_valid) begin
            if (ram_ev_event == trace_pkg::EV_READ &&
                ram_ev_arg2[7:0] == 8'd1)                  // master_id == DMAC
                mem_reads_dmac <= mem_reads_dmac + 1;
            if (ram_ev_event == trace_pkg::EV_WRITE)
                mem_writes <= mem_writes + 1;
        end

        // Map-layer request-routing events for DMAC → EE_RAM. master=1, region=1.
        if (rst_n && map_ev_valid && map_ev_event == trace_pkg::EV_READ &&
            map_ev_arg2[7:0] == 8'd1 && map_ev_arg3[7:0] == 8'd1)
            map_reads_dmac <= map_reads_dmac + 1;

        if (rst_n && dmac_ev_valid) begin
            unique case (dmac_ev_event)
                trace_pkg::EV_DMA_CFG:   dma_cfg_count   <= dma_cfg_count   + 1;
                trace_pkg::EV_DMA_START: begin
                    dma_start_count        <= dma_start_count + 1;
                    dma_start_madr_observed <= dmac_ev_arg2;
                end
                trace_pkg::EV_DMA_BEAT: begin
                    dma_beat_count <= dma_beat_count + 1;
                    // arg1 is beat index; arg2 is this beat's source addr.
                    if (dmac_ev_arg1 == 64'd0) dma_beat_src_beat0 <= dmac_ev_arg2;
                    if (dmac_ev_arg1 == 64'd1) dma_beat_src_beat1 <= dmac_ev_arg2;
                end
                trace_pkg::EV_DMA_DONE: begin
                    dma_done_count               <= dma_done_count + 1;
                    dma_done_seen                <= 1'b1;
                    // Causality guard: DMA_DONE must not precede the second
                    // GIFTAG. Latch true only if both GIFTAGs already logged.
                    if (giftag_count >= 2) dma_done_after_second_giftag <= 1'b1;
                end
                default: ;
            endcase
        end

        if (rst_n && gif_ev_valid && gif_ev_event == trace_pkg::EV_GIFTAG) begin
            giftag_count <= giftag_count + 1;
            // gif_path_stub puts in_last in flags[0].
            if (giftag_count == 0) giftag_last_beat0 <= gif_ev_flags[0];
            if (giftag_count == 1) giftag_last_beat1 <= gif_ev_flags[0];
        end
        if (rst_n && gs_ev_valid && gs_ev_event == trace_pkg::EV_BGCOLOR)
            gs_bgcolor_count <= gs_bgcolor_count + 1;

        if (rst_n && plat_ev_valid && plat_ev_event == trace_pkg::EV_MODE) begin
            plat_frame_count <= plat_frame_count + 1;
            if (dma_done_seen) post_transfer_frames <= post_transfer_frames + 1;
        end

        if (rst_n && de && dma_done_seen) begin
            if (r !== bg_r || g !== bg_g || b !== bg_b) begin
                $error("[tb_bgcolor_via_dma] pixel != bg_*");
                errors <= errors + 1;
            end
        end
    end

    // ------------------------------------------------------------------
    // Stimulus helpers
    // ------------------------------------------------------------------

    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 [15:0] BGCOLOR_OFF = 16'h00E0;

    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_ram_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

    // ------------------------------------------------------------------
    // Stimulus
    // ------------------------------------------------------------------

    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;

        // Let a mid-grey frame tick.
        repeat (130) @(posedge clk);

        // Preload two qwords at MADR and MADR + 16. Packet payload format
        // (gif_path_stub Wave 2 project-local):
        //   [ 15: 0] register offset (BGCOLOR = 0x00E0)
        //   [ 79:16] register value (low 24 bits = 24'h{R,G,B})
        //   [127:80] reserved zero
        //
        // Beat 0 = blue (0x0000FF). Transient color; not the final visible.
        // Beat 1 = red  (0xFF0000). Final visible color after transfer.
        begin : do_preload
            logic [31:0] second_addr;
            second_addr = PAYLOAD_MADR + 32'd16;
            preload_ram_qword(
                PAYLOAD_MADR[RAM_ADDR_W-1:0],
                {48'd0, 64'h0000_0000_0000_00FF, BGCOLOR_OFF}
            );
            preload_ram_qword(
                second_addr[RAM_ADDR_W-1:0],
                {48'd0, 64'h0000_0000_00FF_0000, BGCOLOR_OFF}
            );
        end

        // Program DMAC for a 2-qword transfer.
        write_dmac(MADR_OFF, PAYLOAD_MADR);
        write_dmac(QWC_OFF,  32'd2);
        write_dmac(CHCR_OFF, 32'h0000_0001);

        // Wait for transfer completion.
        wait (dma_done_seen == 1'b1);

        // Let frames tick so we observe the pixel change.
        repeat (250) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_bgcolor_via_dma] mem_writes=%0d mem_reads_dmac=%0d map_reads_dmac=%0d dma_cfg=%0d dma_start=%0d dma_beat=%0d dma_done=%0d giftag=%0d gs_bgcolor=%0d frames=%0d post_frames=%0d last0=%0b last1=%0b bg=(%02h,%02h,%02h) errors=%0d",
                 mem_writes, mem_reads_dmac, map_reads_dmac,
                 dma_cfg_count, dma_start_count, dma_beat_count, dma_done_count,
                 giftag_count, gs_bgcolor_count, plat_frame_count,
                 post_transfer_frames, giftag_last_beat0, giftag_last_beat1,
                 bg_r, bg_g, bg_b, errors);

        if (mem_writes       != 2) $error("expected exactly 2 RAM writes (two preload qwords), got %0d",
                                           mem_writes);
        if (mem_reads_dmac   != 2) $error("expected exactly 2 DMAC-sourced RAM reads, got %0d",
                                           mem_reads_dmac);
        if (map_reads_dmac   != 2) $error("expected exactly 2 map-layer DMAC→EE_RAM routing events, got %0d",
                                           map_reads_dmac);
        if (dma_cfg_count     < 3) $error("expected >= 3 DMA_CFG (MADR+QWC+CHCR)");
        if (dma_start_count  != 1) $error("expected exactly 1 DMA_START, got %0d", dma_start_count);
        if (dma_beat_count   != 2) $error("expected exactly 2 DMA_BEAT, got %0d", dma_beat_count);
        if (dma_done_count   != 1) $error("expected exactly 1 DMA_DONE, got %0d", dma_done_count);
        if (giftag_count     != 2) $error("expected exactly 2 GIFTAG, got %0d", giftag_count);
        if (gs_bgcolor_count != 2) $error("expected exactly 2 GS BGCOLOR, got %0d",
                                           gs_bgcolor_count);
        if (post_transfer_frames < 1)
            $error("expected >= 1 frame after DMA_DONE");
        if (dma_start_madr_observed !== {32'd0, PAYLOAD_MADR})
            $error("DMA_START.arg2 expected 0x%0h got 0x%0h",
                   {32'd0, PAYLOAD_MADR}, dma_start_madr_observed);
        if (dma_beat_src_beat0 !== {32'd0, PAYLOAD_MADR})
            $error("DMA_BEAT[0].arg2 expected 0x%0h got 0x%0h",
                   {32'd0, PAYLOAD_MADR}, dma_beat_src_beat0);
        if (dma_beat_src_beat1 !== {32'd0, PAYLOAD_MADR + 32'd16})
            $error("DMA_BEAT[1].arg2 expected 0x%0h got 0x%0h",
                   {32'd0, PAYLOAD_MADR + 32'd16}, dma_beat_src_beat1);
        if (giftag_last_beat0 !== 1'b0)
            $error("GIFTAG[0] flags[0] expected 0 (not last), got %0b", giftag_last_beat0);
        if (giftag_last_beat1 !== 1'b1)
            $error("GIFTAG[1] flags[0] expected 1 (last), got %0b", giftag_last_beat1);
        if (!dma_done_after_second_giftag)
            $error("DMA_DONE observed before the second GIFTAG (causality violation)");
        if (bg_r !== 8'hFF || bg_g !== 8'h00 || bg_b !== 8'h00)
            $error("expected final bg=(FF,00,00) [red], got (%02h,%02h,%02h)",
                   bg_r, bg_g, bg_b);

        if (errors == 0 &&
            mem_writes == 2 && mem_reads_dmac == 2 && map_reads_dmac == 2 &&
            dma_cfg_count >= 3 && dma_start_count == 1 &&
            dma_beat_count == 2 && dma_done_count == 1 &&
            giftag_count == 2 && gs_bgcolor_count == 2 &&
            post_transfer_frames >= 1 &&
            dma_start_madr_observed === {32'd0, PAYLOAD_MADR} &&
            dma_beat_src_beat0 === {32'd0, PAYLOAD_MADR} &&
            dma_beat_src_beat1 === {32'd0, PAYLOAD_MADR + 32'd16} &&
            giftag_last_beat0 === 1'b0 && giftag_last_beat1 === 1'b1 &&
            dma_done_after_second_giftag &&
            bg_r === 8'hFF && bg_g === 8'h00 && bg_b === 8'h00)
            $display("[tb_bgcolor_via_dma] PASS");
        else
            $display("[tb_bgcolor_via_dma] FAIL");

        $finish;
    end

    initial begin
        #200000;
        $error("[tb_bgcolor_via_dma] timeout");
        $finish;
    end

endmodule : tb_bgcolor_via_dma
