// retroDE_ps2 — tb_sif_ee_landing_via_dmac
//
// First reverse-direction SIF DMA: IOP → EE payload movement with every
// hop going through the real architectural infrastructure.
//
// Chain:
//   iop_ram_stub (source)
//     └─► iop_memory_map_stub  (dma_rd_* master port, 32-bit)
//           └─► iop_dmac_reg_stub  (ch9 / SIF0 IOP→EE)
//                 └─► sif_dma_ee_ram_bridge_stub  (4× 32-bit → 128-bit)
//                       └─► ee_memory_map_stub  (bridge_wr_* port)
//                             └─► ee_ram_stub  (destination)
//
// TB verification path:
//   EE-side readback goes through the EE map's dmac_rd_* master port,
//   so the landed bytes are observed the same way a real EE DMAC would
//   see them — via the address decoder, not by poking ee_ram_stub
//   directly.
//
// Scenarios (single run):
//   1. Preload 16 distinctive words into IOP RAM at SRC_BASE.
//   2. Program DMAC ch9 via 0x1F80_152x: MADR=SRC_BASE, BCR=16.
//   3. Start (CHCR bit 0). Transfer runs.
//   4. Mid-stream stall: after STALL_AT_BEAT beats have been accepted at
//      the endpoint, the TB drops a stall_override that holds the
//      bridge's in_ready path low for STALL_CYCLES cycles. Busy must
//      stay high; beat count must freeze; no DONE must fire.
//   5. Release the stall; the remaining beats finish and DMA_DONE fires.
//   6. EE-side readback: for each of the 4 landed qwords, read via the
//      EE map dmac_rd_* port and compare against the expected pattern
//      (4 consecutive source words, little-endian packed).
//
// Key trace-visible assertions:
//   - DMA_START × 1, DMA_BEAT × 16, DMA_DONE × 1, all on SUBSYS_DMAC.
//   - EE map emits 4 EV_WRITE events with region=EE_RAM (1) and
//     master_id=5 (SIF EE-side bridge).
//   - IOP map emits ≥ 16 EV_READ events with region=IOP_RAM and
//     master_id=4 (DMA read master).
//   - At least one stall window (stall_cycles > 0) observed while busy
//     and no DONE fires during it.

`timescale 1ns/1ps

module tb_sif_ee_landing_via_dmac;

    localparam int IOP_RAM_BYTES  = 4 * 1024;
    localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES);
    localparam int EE_RAM_BYTES   = 4 * 1024;
    localparam int EE_RAM_ADDR_W  = $clog2(EE_RAM_BYTES);

    localparam logic [31:0] DMAC_CH9_BASE  = 32'h1F80_1520;
    localparam logic [31:0] DMAC_MADR_ADDR = DMAC_CH9_BASE | 32'h00;
    localparam logic [31:0] DMAC_BCR_ADDR  = DMAC_CH9_BASE | 32'h04;
    localparam logic [31:0] DMAC_CHCR_ADDR = DMAC_CH9_BASE | 32'h08;

    localparam logic [31:0] SRC_BASE     = 32'h0000_0200;
    localparam logic [31:0] EE_DEST_BASE = 32'h0000_0100;
    localparam int          BEATS        = 16;
    localparam int          QWORDS       = BEATS / 4;

    localparam int          STALL_AT_BEAT = 6;   // mid-stream
    localparam int          STALL_CYCLES  = 20;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // IOP map + IOP RAM
    // ------------------------------------------------------------------

    logic        iop_rd_en;
    logic [31:0] iop_rd_addr;
    logic [31:0] iop_rd_data;
    logic        iop_rd_valid;

    logic        iop_wr_en;
    logic [31:0] iop_wr_addr;
    logic [31:0] iop_wr_data;
    logic [3:0]  iop_wr_be;

    logic        iop_ram_rd_en;
    logic [20:0] iop_ram_rd_addr;
    logic [31:0] iop_ram_rd_data;
    logic        iop_ram_rd_valid;
    logic        iop_ram_wr_en;
    logic [20:0] iop_ram_wr_addr;
    logic [31:0] iop_ram_wr_data;
    logic [3:0]  iop_ram_wr_be;
    logic [7:0]  iop_ram_master_id;

    logic        map_dmac_rd_en;
    logic [3:0]  map_dmac_rd_addr;
    logic [31:0] map_dmac_rd_data;
    logic        map_dmac_rd_valid;
    logic        map_dmac_wr_en;
    logic [3:0]  map_dmac_wr_addr;
    logic [31:0] map_dmac_wr_data;

    // IOP DMAC → IOP map dma_rd_*
    logic        dma_rd_en;
    logic [31:0] dma_rd_addr;
    logic [7:0]  dma_master_id;
    logic [31:0] dma_rd_data;
    logic        dma_rd_valid;

    logic                iop_map_ev_valid;
    trace_pkg::subsys_e  iop_map_ev_subsys;
    trace_pkg::event_e   iop_map_ev_event;
    logic [63:0]         iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3;
    logic [31:0]         iop_map_ev_flags;

    iop_memory_map_stub u_iop_map (
        .clk(clk), .rst_n(rst_n),
        .iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
        .iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
        .iop_wr_en(iop_wr_en), .iop_wr_addr(iop_wr_addr),
        .iop_wr_data(iop_wr_data), .iop_wr_be(iop_wr_be),
        .master_id(8'd2),                             // IOP_CPU
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(32'd0), .bridge_wr_be(4'd0),
        .bridge_master_id(8'd0),
        .dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr),
        .dma_master_id(dma_master_id),
        .dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid),
        .sif_rd_en(), .sif_rd_addr(),
        .sif_rd_data(32'd0), .sif_rd_valid(1'b0),
        .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
        .iop_dmac_rd_en(map_dmac_rd_en), .iop_dmac_rd_addr(map_dmac_rd_addr),
        .iop_dmac_rd_data(map_dmac_rd_data), .iop_dmac_rd_valid(map_dmac_rd_valid),
        .iop_dmac_wr_en(map_dmac_wr_en), .iop_dmac_wr_addr(map_dmac_wr_addr),
        .iop_dmac_wr_data(map_dmac_wr_data),
        // IOP INTC port — unused by this TB
        .iop_intc_rd_en(), .iop_intc_rd_addr(),
        .iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
        .iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
        .input_p1(32'd0), .input_p2(32'd0),
        // BIOS ROM port — unused by this TB
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(iop_ram_rd_en), .ram_rd_addr(iop_ram_rd_addr),
        .ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid),
        .ram_wr_en(iop_ram_wr_en), .ram_wr_addr(iop_ram_wr_addr),
        .ram_wr_data(iop_ram_wr_data), .ram_wr_be(iop_ram_wr_be),
        .ram_master_id(iop_ram_master_id),
        .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
        .ev_event(iop_map_ev_event),
        .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1),
        .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3),
        .ev_flags(iop_map_ev_flags)
    );

    iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(iop_ram_rd_en), .rd_addr(iop_ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
        .rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid),
        .wr_en(iop_ram_wr_en), .wr_addr(iop_ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
        .wr_data(iop_ram_wr_data), .wr_be(iop_ram_wr_be),
        .master_id(iop_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // IOP DMAC ch9
    // ------------------------------------------------------------------

    logic        ep_valid;
    logic [31:0] ep_data;
    logic        ep_last;
    logic        ep_ready;                    // gated: bridge-in-ready && !stall_override

    logic        dmac_busy;
    logic [31:0] dmac_done_count;

    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;

    iop_dmac_reg_stub u_dmac (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_dmac_wr_en), .reg_rd_en(map_dmac_rd_en),
        .reg_offset(map_dmac_wr_en ? map_dmac_wr_addr : map_dmac_rd_addr),
        .reg_wr_data(map_dmac_wr_data),
        .reg_rd_data(map_dmac_rd_data), .reg_rd_valid(map_dmac_rd_valid),
        .mem_rd_en(dma_rd_en), .mem_rd_addr(dma_rd_addr),
        .mem_master_id(dma_master_id),
        .mem_rd_data(dma_rd_data), .mem_rd_valid(dma_rd_valid),
        .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last),
        .ep_ready(ep_ready),
        .irq_completion_o(),
        .busy_o(dmac_busy), .done_count_o(dmac_done_count),
        .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)
    );

    // ------------------------------------------------------------------
    // SIF egress bridge
    // ------------------------------------------------------------------

    logic        bridge_in_ready;
    logic        stall_override;
    logic        gated_valid;

    // Stall injection gates BOTH sides of the handshake so the bridge and
    // DMAC agree on when a beat is accepted. ep_ready drops at the DMAC,
    // and in_valid drops at the bridge, during stall_override windows.
    assign gated_valid = ep_valid && !stall_override;
    assign ep_ready    = bridge_in_ready && !stall_override;

    logic        eebr_wr_en;
    logic [31:0] eebr_wr_addr;
    logic [127:0] eebr_wr_data;
    logic [15:0] eebr_wr_be;
    logic [7:0]  eebr_master_id;

    sif_dma_ee_ram_bridge_stub #(
        .DEST_BASE_ADDR(EE_DEST_BASE),
        .MASTER_ID(8'd5)
    ) u_ee_bridge (
        .clk(clk), .rst_n(rst_n),
        .in_valid(gated_valid), .in_data(ep_data), .in_last(ep_last),
        .in_ready(bridge_in_ready),
        .bridge_wr_en(eebr_wr_en), .bridge_wr_addr(eebr_wr_addr),
        .bridge_wr_data(eebr_wr_data), .bridge_wr_be(eebr_wr_be),
        .bridge_master_id(eebr_master_id),
        .last_seen_o()
    );

    // ------------------------------------------------------------------
    // EE map + EE RAM
    // ------------------------------------------------------------------

    logic        ee_dmac_rd_en;
    logic [31:0] ee_dmac_rd_addr;
    logic [127:0] ee_dmac_rd_data;
    logic        ee_dmac_rd_valid;

    logic        ee_map_ram_rd_en;
    logic [24:0] ee_map_ram_rd_addr;
    logic [127:0] ee_ram_rd_data;
    logic        ee_ram_rd_valid;

    logic        ee_map_ram_wr_en;
    logic [24:0] ee_map_ram_wr_addr;
    logic [127:0] ee_map_ram_wr_data;
    logic [15:0] ee_map_ram_wr_be;
    logic [7:0]  ee_map_ram_master_id;

    logic                ee_map_ev_valid;
    trace_pkg::subsys_e  ee_map_ev_subsys;
    trace_pkg::event_e   ee_map_ev_event;
    logic [63:0]         ee_map_ev_arg0, ee_map_ev_arg1, ee_map_ev_arg2, ee_map_ev_arg3;
    logic [31:0]         ee_map_ev_flags;

    ee_memory_map_stub u_ee_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(ee_dmac_rd_en), .dmac_rd_addr(ee_dmac_rd_addr),
        .dmac_rd_data(ee_dmac_rd_data), .dmac_rd_valid(ee_dmac_rd_valid),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(ee_map_ram_rd_en), .ram_rd_addr(ee_map_ram_rd_addr),
        .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
        // Bridge write port — wired to the SIF egress bridge
        .bridge_wr_en(eebr_wr_en), .bridge_wr_addr(eebr_wr_addr),
        .bridge_wr_data(eebr_wr_data), .bridge_wr_be(eebr_wr_be),
        .bridge_master_id(eebr_master_id),
        // RAM write downstream — wired to ee_ram_stub
        .ram_wr_en(ee_map_ram_wr_en), .ram_wr_addr(ee_map_ram_wr_addr),
        .ram_wr_data(ee_map_ram_wr_data), .ram_wr_be(ee_map_ram_wr_be),
        .ram_master_id(ee_map_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(ee_map_ev_valid), .ev_subsys(ee_map_ev_subsys),
        .ev_event(ee_map_ev_event),
        .ev_arg0(ee_map_ev_arg0), .ev_arg1(ee_map_ev_arg1),
        .ev_arg2(ee_map_ev_arg2), .ev_arg3(ee_map_ev_arg3),
        .ev_flags(ee_map_ev_flags)
    );

    ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ee_map_ram_rd_en), .rd_addr(ee_map_ram_rd_addr[EE_RAM_ADDR_W-1:0]),
        .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
        .wr_en(ee_map_ram_wr_en), .wr_addr(ee_map_ram_wr_addr[EE_RAM_ADDR_W-1:0]),
        .wr_data(ee_map_ram_wr_data), .wr_be(ee_map_ram_wr_be),
        .master_id(ee_map_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

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

    trace_sink_stub #(.FILENAME("ee_landing_iop_map.trace"), .SINK_LABEL("iop_map"))
        u_trace_iop_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
            .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0),
            .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2),
            .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags));

    trace_sink_stub #(.FILENAME("ee_landing_ee_map.trace"), .SINK_LABEL("ee_map"))
        u_trace_ee_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(ee_map_ev_valid), .ev_subsys(ee_map_ev_subsys),
            .ev_event(ee_map_ev_event), .ev_arg0(ee_map_ev_arg0),
            .ev_arg1(ee_map_ev_arg1), .ev_arg2(ee_map_ev_arg2),
            .ev_arg3(ee_map_ev_arg3), .ev_flags(ee_map_ev_flags));

    trace_sink_stub #(.FILENAME("ee_landing_dmac.trace"), .SINK_LABEL("iop_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));

    // ------------------------------------------------------------------
    // Counters + bookkeeping
    // ------------------------------------------------------------------

    int dmac_start_events;
    int dmac_beat_events;
    int dmac_done_events;
    int ee_map_ram_writes;
    int iop_map_dma_reads;
    int stall_cycles;
    int stall_beats_seen;         // dmac_beat_events observed just before stall release
    int dmac_done_during_stall;
    int errors;

    initial begin
        dmac_start_events      = 0;
        dmac_beat_events       = 0;
        dmac_done_events       = 0;
        ee_map_ram_writes      = 0;
        iop_map_dma_reads      = 0;
        stall_cycles           = 0;
        stall_beats_seen       = 0;
        dmac_done_during_stall = 0;
        errors                 = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid &&
            dmac_ev_subsys == trace_pkg::SUBSYS_DMAC) begin
            case (dmac_ev_event)
                trace_pkg::EV_DMA_START: dmac_start_events <= dmac_start_events + 1;
                trace_pkg::EV_DMA_BEAT:  dmac_beat_events  <= dmac_beat_events  + 1;
                trace_pkg::EV_DMA_DONE:  dmac_done_events  <= dmac_done_events  + 1;
                default: ;
            endcase
        end
        if (rst_n && ee_map_ev_valid &&
            ee_map_ev_subsys == trace_pkg::SUBSYS_MEM &&
            ee_map_ev_event  == trace_pkg::EV_WRITE &&
            ee_map_ev_arg3[7:0] == 8'd1)                      // EE_RAM region
            ee_map_ram_writes <= ee_map_ram_writes + 1;

        if (rst_n && iop_map_ev_valid &&
            iop_map_ev_subsys == trace_pkg::SUBSYS_IOP &&
            iop_map_ev_event  == trace_pkg::EV_READ &&
            iop_map_ev_arg3[7:0] == 8'd2 &&                   // IOP_RAM region
            iop_map_ev_arg2[7:0] == 8'd4)                     // DMA master
            iop_map_dma_reads <= iop_map_dma_reads + 1;

        if (rst_n && stall_override) begin
            stall_cycles <= stall_cycles + 1;
            if (dmac_ev_valid && dmac_ev_subsys == trace_pkg::SUBSYS_DMAC &&
                dmac_ev_event == trace_pkg::EV_DMA_DONE)
                dmac_done_during_stall <= dmac_done_during_stall + 1;
        end
    end

    // ------------------------------------------------------------------
    // Stall injection: once dmac_beat_events >= STALL_AT_BEAT, hold
    // stall_override high for STALL_CYCLES cycles.
    // ------------------------------------------------------------------

    int stall_window_remaining;
    logic stall_fired;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            stall_override         <= 1'b0;
            stall_window_remaining <= 0;
            stall_fired            <= 1'b0;
            stall_beats_seen       <= 0;
        end else begin
            if (!stall_fired && dmac_beat_events >= STALL_AT_BEAT) begin
                stall_override         <= 1'b1;
                stall_window_remaining <= STALL_CYCLES;
                stall_fired            <= 1'b1;
                stall_beats_seen       <= dmac_beat_events;
            end else if (stall_window_remaining > 0) begin
                stall_window_remaining <= stall_window_remaining - 1;
                if (stall_window_remaining == 1) stall_override <= 1'b0;
            end
        end
    end

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------

    task automatic iop_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        iop_wr_en   = 1'b1;
        iop_wr_addr = addr;
        iop_wr_data = data;
        iop_wr_be   = 4'b1111;
        @(negedge clk);
        iop_wr_en   = 1'b0;
        iop_wr_addr = 32'd0;
        iop_wr_data = 32'd0;
        iop_wr_be   = 4'd0;
    endtask

    task automatic ee_dmac_read_qword(input logic [31:0] addr,
                                      output logic [127:0] data);
        @(negedge clk);
        ee_dmac_rd_en   = 1'b1;
        ee_dmac_rd_addr = addr;
        @(negedge clk);
        ee_dmac_rd_en   = 1'b0;
        ee_dmac_rd_addr = 32'd0;
        @(posedge clk);                     // wait one cycle for valid
        data = ee_dmac_rd_data;
    endtask

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

    logic [31:0] payload [0:BEATS-1];
    logic [127:0] expected_qw;
    logic [127:0] got_qw;

    initial begin
        rst_n           = 1'b0;
        iop_rd_en       = 1'b0;
        iop_rd_addr     = 32'd0;
        iop_wr_en       = 1'b0;
        iop_wr_addr     = 32'd0;
        iop_wr_data     = 32'd0;
        iop_wr_be       = 4'd0;
        ee_dmac_rd_en   = 1'b0;
        ee_dmac_rd_addr = 32'd0;

        // Build payload: 16 distinctive words.
        for (int i = 0; i < BEATS; i++) payload[i] = 32'hCAFE_0000 | i[31:0];

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

        // Preload IOP RAM via CPU-side writes through the map.
        for (int i = 0; i < BEATS; i++) begin
            iop_write(SRC_BASE + (i << 2), payload[i]);
        end

        // Program DMAC ch9
        iop_write(DMAC_MADR_ADDR, SRC_BASE);
        iop_write(DMAC_BCR_ADDR,  32'(BEATS));

        // Kick off the transfer
        iop_write(DMAC_CHCR_ADDR, 32'h0000_0001);

        // Let the chain run. Each beat takes a few cycles (fetch + send +
        // bridge accumulate + every 4th beat one emit cycle). A generous
        // bound that also absorbs the STALL window.
        repeat (10 * BEATS + STALL_CYCLES) @(posedge clk);

        if (dmac_done_count != 1) begin
            $error("[tb_sif_ee_landing_via_dmac] done_count expected 1, got %0d",
                   dmac_done_count);
            errors = errors + 1;
        end
        if (dmac_done_during_stall != 0) begin
            $error("[tb_sif_ee_landing_via_dmac] DMA_DONE fired during stall (!)");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // EE-side readback through the EE map dmac_rd_* port. Four qwords
        // expected starting at EE_DEST_BASE.
        // --------------------------------------------------------------
        for (int q = 0; q < QWORDS; q++) begin
            expected_qw = { payload[q*4 + 3], payload[q*4 + 2],
                            payload[q*4 + 1], payload[q*4 + 0] };
            ee_dmac_read_qword(EE_DEST_BASE + (q << 4), got_qw);
            if (got_qw !== expected_qw) begin
                $error("[tb_sif_ee_landing_via_dmac] qw[%0d] got 0x%032h expected 0x%032h",
                       q, got_qw, expected_qw);
                errors = errors + 1;
            end
        end

        repeat (4) @(posedge clk);

        $display("[tb_sif_ee_landing_via_dmac] start=%0d beat=%0d done=%0d ee_map_writes=%0d iop_map_dma_reads=%0d stall_cy=%0d stall_beats=%0d errors=%0d",
                 dmac_start_events, dmac_beat_events, dmac_done_events,
                 ee_map_ram_writes, iop_map_dma_reads,
                 stall_cycles, stall_beats_seen, errors);

        if (dmac_start_events != 1)
            $error("expected 1 DMA_START, got %0d", dmac_start_events);
        if (dmac_beat_events != BEATS)
            $error("expected %0d DMA_BEAT, got %0d", BEATS, dmac_beat_events);
        if (dmac_done_events != 1)
            $error("expected 1 DMA_DONE, got %0d", dmac_done_events);
        if (ee_map_ram_writes != QWORDS)
            $error("expected %0d EE map writes, got %0d",
                   QWORDS, ee_map_ram_writes);
        if (iop_map_dma_reads < BEATS)
            $error("expected >=%0d IOP map DMA reads, got %0d",
                   BEATS, iop_map_dma_reads);
        if (stall_cycles < STALL_CYCLES - 2)          // allow small edge slack
            $error("expected >=%0d stall cycles, got %0d",
                   STALL_CYCLES - 2, stall_cycles);

        if (errors == 0 &&
            dmac_start_events == 1 &&
            dmac_beat_events  == BEATS &&
            dmac_done_events  == 1 &&
            ee_map_ram_writes == QWORDS &&
            iop_map_dma_reads >= BEATS &&
            stall_cycles      >= STALL_CYCLES - 2)
            $display("[tb_sif_ee_landing_via_dmac] PASS");
        else
            $display("[tb_sif_ee_landing_via_dmac] FAIL");

        $finish;
    end

    initial begin
        #500000;
        $error("[tb_sif_ee_landing_via_dmac] timeout");
        $finish;
    end

endmodule : tb_sif_ee_landing_via_dmac
