// retroDE_ps2 — tb_iop_self_driven
//
// First RAM-backed self-driven IOP transaction. After a single go_i
// pulse, the TB stops orchestrating. The exec stub fetches its control
// program from IOP RAM — same 32-bit map path a real IOP CPU would use
// — and runs it to completion. The TB's only responsibility after
// reset is to lay the script and DMA payload into RAM, then observe.
//
// Chain (all architectural addresses routed by the real IOP map):
//   iop_exec_stub
//     ├── map reads   0x0000_0400..0x0000_04FF  → IOP RAM (ifetch)
//     ├── map write   0x1F80_1074               → IOP INTC I_MASK
//     ├── map writes  0x1F80_1520/4/8           → IOP DMAC ch9 MADR/BCR/CHCR
//     ├── map reads   0x1F80_1070               → IOP INTC I_STAT
//     └── map writes  0x1F80_1070               → IOP INTC I_STAT (W1C)
//
//   iop_dmac_reg_stub (ch9) pulls source words through map.dma_rd_*,
//   drives an endpoint whose ep_ready the TB uses for the stall gate.
//   On S_DONE, irq_completion_o pulses; INTC latches and raises cpu_irq.
//
// RAM layout preloaded by the TB via bridge_wr_*:
//   0x0000_0200..0x0000_020F   DMA payload (4 × 32-bit words)
//   0x0000_0400..0x0000_049F   10-op exec script (16 bytes / op)
//
// Script (one transaction):
//   pc=0  WRITE INTC_MASK = 1
//   pc=1  WRITE DMAC_MADR = 0x0000_0200
//   pc=2  WRITE DMAC_BCR  = 4
//   pc=3  WRITE DMAC_CHCR = 1        (start)
//   pc=4  WAIT_IRQ
//   pc=5  READ  INTC_STAT            (verify pending)
//   pc=6  WRITE INTC_STAT = 1        (W1C ack)
//   pc=7  READ  INTC_STAT            (verify cleared)
//   pc=8  READ  DMAC_CHCR            (verify start cleared)
//   pc=9  HALT
//
// Pre-completion stall window:
//   The TB holds ep_ready=0 for STALL_WINDOW cycles after reset. During
//   that period the DMAC starts, tries to ship beat 0, and stalls. The
//   exec stub must stay in WAIT_IRQ with pc at the wait slot. No
//   cpu_irq must assert. Halt must NOT happen. Once ep_ready rises,
//   the DMAC completes, cpu_irq comes up, the exec stub walks the
//   remaining ops and halts.

`timescale 1ns/1ps

module tb_iop_self_driven;

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

    // Script location (must match exec stub SCRIPT_BASE parameter)
    localparam logic [31:0] SCRIPT_BASE   = 32'h0000_0400;
    localparam logic [31:0] PAYLOAD_BASE  = 32'h0000_0200;

    // Architectural addresses reached by the script
    localparam logic [31:0] DMAC_MADR_ADDR = 32'h1F80_1520;
    localparam logic [31:0] DMAC_BCR_ADDR  = 32'h1F80_1524;
    localparam logic [31:0] DMAC_CHCR_ADDR = 32'h1F80_1528;
    localparam logic [31:0] INTC_STAT_ADDR = 32'h1F80_1070;
    localparam logic [31:0] INTC_MASK_ADDR = 32'h1F80_1074;

    // Opcodes (keep in sync with iop_exec_stub)
    localparam logic [31:0] OP_HALT     = 32'h0000_0000;
    localparam logic [31:0] OP_WRITE    = 32'h0000_0001;
    localparam logic [31:0] OP_READ     = 32'h0000_0002;
    localparam logic [31:0] OP_WAIT_IRQ = 32'h0000_0003;

    localparam logic [7:0]  EXEC_PC_WAIT_IRQ = 8'd4;
    localparam int          STALL_WINDOW     = 30;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // IOP exec stub (drives the IOP map CPU-side port)
    // ------------------------------------------------------------------

    logic        exec_go;
    logic        exec_halt;
    logic [7:0]  exec_pc;

    logic        map_cpu_rd_en;
    logic [31:0] map_cpu_rd_addr;
    logic [31:0] map_cpu_rd_data;
    logic        map_cpu_rd_valid;

    logic        map_cpu_wr_en;
    logic [31:0] map_cpu_wr_addr;
    logic [31:0] map_cpu_wr_data;
    logic [3:0]  map_cpu_wr_be;

    logic        iop_cpu_irq;

    logic                exec_ev_valid;
    trace_pkg::subsys_e  exec_ev_subsys;
    trace_pkg::event_e   exec_ev_event;
    logic [63:0]         exec_ev_arg0, exec_ev_arg1, exec_ev_arg2, exec_ev_arg3;
    logic [31:0]         exec_ev_flags;

    iop_exec_stub #(.SCRIPT_BASE(SCRIPT_BASE)) u_exec (
        .clk(clk), .rst_n(rst_n),
        .go_i(exec_go),
        .map_rd_en(map_cpu_rd_en), .map_rd_addr(map_cpu_rd_addr),
        .map_rd_data(map_cpu_rd_data), .map_rd_valid(map_cpu_rd_valid),
        .map_wr_en(map_cpu_wr_en), .map_wr_addr(map_cpu_wr_addr),
        .map_wr_data(map_cpu_wr_data), .map_wr_be(map_cpu_wr_be),
        .cpu_irq(iop_cpu_irq),
        .halt_o(exec_halt), .pc_o(exec_pc),
        .ev_valid(exec_ev_valid), .ev_subsys(exec_ev_subsys),
        .ev_event(exec_ev_event),
        .ev_arg0(exec_ev_arg0), .ev_arg1(exec_ev_arg1),
        .ev_arg2(exec_ev_arg2), .ev_arg3(exec_ev_arg3),
        .ev_flags(exec_ev_flags)
    );

    // ------------------------------------------------------------------
    // IOP map + IOP RAM. TB drives bridge_wr_* to preload payload + script.
    // ------------------------------------------------------------------

    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;

    logic        map_intc_rd_en;
    logic [7:0]  map_intc_rd_addr;
    logic [31:0] map_intc_rd_data;
    logic        map_intc_rd_valid;
    logic        map_intc_wr_en;
    logic [7:0]  map_intc_wr_addr;
    logic [31:0] map_intc_wr_data;

    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;

    // TB-driven bridge_wr_* for preload
    logic        tb_br_wr_en;
    logic [31:0] tb_br_wr_addr;
    logic [31:0] tb_br_wr_data;
    logic [3:0]  tb_br_wr_be;

    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(map_cpu_rd_en), .iop_rd_addr(map_cpu_rd_addr),
        .iop_rd_data(map_cpu_rd_data), .iop_rd_valid(map_cpu_rd_valid),
        .iop_wr_en(map_cpu_wr_en), .iop_wr_addr(map_cpu_wr_addr),
        .iop_wr_data(map_cpu_wr_data), .iop_wr_be(map_cpu_wr_be),
        .master_id(8'd2),
        .bridge_wr_en(tb_br_wr_en), .bridge_wr_addr(tb_br_wr_addr),
        .bridge_wr_data(tb_br_wr_data), .bridge_wr_be(tb_br_wr_be),
        .bridge_master_id(8'd0),                       // TB direct
        .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_rd_en(map_intc_rd_en), .iop_intc_rd_addr(map_intc_rd_addr),
        .iop_intc_rd_data(map_intc_rd_data), .iop_intc_rd_valid(map_intc_rd_valid),
        .iop_intc_wr_en(map_intc_wr_en), .iop_intc_wr_addr(map_intc_wr_addr),
        .iop_intc_wr_data(map_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 — endpoint drained into the TB (data on floor)
    // ------------------------------------------------------------------

    logic        ep_valid;
    logic [31:0] ep_data;
    logic        ep_last;
    logic        ep_ready;

    logic        tb_stall;
    assign ep_ready = !tb_stall;

    logic        dmac_irq_completion;
    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(dmac_irq_completion),
        .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)
    );

    // ------------------------------------------------------------------
    // IOP INTC
    // ------------------------------------------------------------------

    logic [15:0] iop_irq_src;
    assign iop_irq_src = {15'd0, dmac_irq_completion};

    logic                intc_ev_valid;
    trace_pkg::subsys_e  intc_ev_subsys;
    trace_pkg::event_e   intc_ev_event;
    logic [63:0]         intc_ev_arg0, intc_ev_arg1, intc_ev_arg2, intc_ev_arg3;
    logic [31:0]         intc_ev_flags;

    intc_stub #(
        .INTC_STAT_OFFSET(8'h70),
        .INTC_MASK_OFFSET(8'h74)
    ) u_intc (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en),
        .reg_addr(map_intc_wr_en ? map_intc_wr_addr : map_intc_rd_addr),
        .reg_wr_data(map_intc_wr_data),
        .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid),
        .irq_src(iop_irq_src), .cpu_irq(iop_cpu_irq),
        .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys),
        .ev_event(intc_ev_event),
        .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1),
        .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3),
        .ev_flags(intc_ev_flags)
    );

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

    trace_sink_stub #(.FILENAME("iop_self_driven_exec.trace"), .SINK_LABEL("iop_exec"))
        u_trace_exec (.clk(clk), .rst_n(rst_n),
            .ev_valid(exec_ev_valid), .ev_subsys(exec_ev_subsys),
            .ev_event(exec_ev_event), .ev_arg0(exec_ev_arg0),
            .ev_arg1(exec_ev_arg1), .ev_arg2(exec_ev_arg2),
            .ev_arg3(exec_ev_arg3), .ev_flags(exec_ev_flags));

    trace_sink_stub #(.FILENAME("iop_self_driven_map.trace"), .SINK_LABEL("iop_map"))
        u_trace_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("iop_self_driven_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));

    trace_sink_stub #(.FILENAME("iop_self_driven_intc.trace"), .SINK_LABEL("iop_intc"))
        u_trace_intc (.clk(clk), .rst_n(rst_n),
            .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys),
            .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0),
            .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2),
            .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags));

    // ------------------------------------------------------------------
    // Observers (no orchestration after go)
    // ------------------------------------------------------------------

    int  dmac_done_events;
    int  intc_assert_events;
    int  intc_ack_events;
    int  exec_op_events;
    int  wait_irq_exit_events;
    int  halt_events;

    logic [7:0] stall_pc_sample;
    logic       stall_irq_sample;
    logic       stall_halt_sample;

    int  errors;

    initial begin
        dmac_done_events     = 0;
        intc_assert_events   = 0;
        intc_ack_events      = 0;
        exec_op_events       = 0;
        wait_irq_exit_events = 0;
        halt_events          = 0;
        stall_pc_sample      = 8'd0;
        stall_irq_sample     = 1'b0;
        stall_halt_sample    = 1'b0;
        errors               = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid &&
            dmac_ev_subsys == trace_pkg::SUBSYS_DMAC &&
            dmac_ev_event  == trace_pkg::EV_DMA_DONE)
            dmac_done_events <= dmac_done_events + 1;

        if (rst_n && intc_ev_valid &&
            intc_ev_subsys == trace_pkg::SUBSYS_INTC &&
            intc_ev_event  == trace_pkg::EV_IRQ) begin
            if (intc_ev_arg3 == 64'd0 && (intc_ev_flags & 32'h1) == 32'd0)
                intc_assert_events <= intc_assert_events + 1;
            else if (intc_ev_arg3 == 64'd1)
                intc_ack_events <= intc_ack_events + 1;
        end

        if (rst_n && exec_ev_valid) begin
            exec_op_events <= exec_op_events + 1;
            if ((exec_ev_flags & 32'h2) != 32'd0)
                wait_irq_exit_events <= wait_irq_exit_events + 1;
            if ((exec_ev_flags & 32'h4) != 32'd0)
                halt_events <= halt_events + 1;
        end
    end

    // ------------------------------------------------------------------
    // Preload helpers (bridge_wr_* → iop_ram_stub, 32-bit words)
    // ------------------------------------------------------------------

    task automatic bridge_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data;
        tb_br_wr_be   = 4'b1111;
        @(negedge clk);
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 32'd0;
        tb_br_wr_be   = 4'd0;
    endtask

    task automatic emit_op(input int slot,
                           input logic [31:0] opcode,
                           input logic [31:0] addr_or_tgt,
                           input logic [31:0] data_or_exp);
        logic [31:0] base;
        base = SCRIPT_BASE + (slot << 4);
        bridge_write(base + 32'd0,  opcode);
        bridge_write(base + 32'd4,  addr_or_tgt);
        bridge_write(base + 32'd8,  data_or_exp);
        bridge_write(base + 32'd12, 32'd0);
    endtask

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

    initial begin
        rst_n         = 1'b0;
        exec_go       = 1'b0;
        tb_stall      = 1'b1;
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 32'd0;
        tb_br_wr_be   = 4'd0;

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

        // Preload DMA payload (4 distinctive words into IOP RAM)
        bridge_write(PAYLOAD_BASE + 32'h00, 32'hBEEF_0000);
        bridge_write(PAYLOAD_BASE + 32'h04, 32'hBEEF_0001);
        bridge_write(PAYLOAD_BASE + 32'h08, 32'hBEEF_0002);
        bridge_write(PAYLOAD_BASE + 32'h0C, 32'hBEEF_0003);

        // Preload control program at SCRIPT_BASE
        emit_op( 0, OP_WRITE,    INTC_MASK_ADDR, 32'h0000_0001);
        emit_op( 1, OP_WRITE,    DMAC_MADR_ADDR, PAYLOAD_BASE);
        emit_op( 2, OP_WRITE,    DMAC_BCR_ADDR,  32'h0000_0004);
        emit_op( 3, OP_WRITE,    DMAC_CHCR_ADDR, 32'h0000_0001);
        emit_op( 4, OP_WAIT_IRQ, 32'd0,          32'd0);
        emit_op( 5, OP_READ,     INTC_STAT_ADDR, 32'd0);
        emit_op( 6, OP_WRITE,    INTC_STAT_ADDR, 32'h0000_0001);
        emit_op( 7, OP_READ,     INTC_STAT_ADDR, 32'd0);
        emit_op( 8, OP_READ,     DMAC_CHCR_ADDR, 32'd0);
        emit_op( 9, OP_HALT,     32'd0,          32'd0);

        // Single-shot start
        @(negedge clk);
        exec_go = 1'b1;
        @(negedge clk);
        exec_go = 1'b0;

        // Let ops 0..3 execute and reach WAIT_IRQ. Each op = 3 ifetch
        // reads (6 cycles) + op execution (~2 cycles). Slack is ample.
        repeat (80) @(posedge clk);

        // Stall-window invariants
        stall_pc_sample   = exec_pc;
        stall_irq_sample  = iop_cpu_irq;
        stall_halt_sample = exec_halt;

        if (stall_pc_sample !== EXEC_PC_WAIT_IRQ) begin
            $error("[tb_iop_self_driven] exec pc in stall window: got %0d, expected %0d (WAIT_IRQ)",
                   stall_pc_sample, EXEC_PC_WAIT_IRQ);
            errors = errors + 1;
        end
        if (stall_irq_sample !== 1'b0) begin
            $error("[tb_iop_self_driven] cpu_irq asserted during stall window");
            errors = errors + 1;
        end
        if (stall_halt_sample !== 1'b0) begin
            $error("[tb_iop_self_driven] exec halted during stall window");
            errors = errors + 1;
        end

        repeat (STALL_WINDOW) @(posedge clk);
        if (exec_pc !== EXEC_PC_WAIT_IRQ) begin
            $error("[tb_iop_self_driven] exec advanced past WAIT_IRQ without IRQ: pc=%0d",
                   exec_pc);
            errors = errors + 1;
        end
        if (iop_cpu_irq !== 1'b0) begin
            $error("[tb_iop_self_driven] cpu_irq rose during extended stall");
            errors = errors + 1;
        end

        // Release the stall — chain completes, exec stub walks remaining ops
        @(negedge clk);
        tb_stall = 1'b0;

        while (!exec_halt) @(posedge clk);
        repeat (4) @(posedge clk);

        if (dmac_done_events != 1) begin
            $error("[tb_iop_self_driven] expected 1 DMA_DONE, got %0d", dmac_done_events);
            errors = errors + 1;
        end
        if (intc_assert_events != 1) begin
            $error("[tb_iop_self_driven] expected 1 INTC assert, got %0d", intc_assert_events);
            errors = errors + 1;
        end
        if (intc_ack_events != 1) begin
            $error("[tb_iop_self_driven] expected 1 INTC ack, got %0d", intc_ack_events);
            errors = errors + 1;
        end
        if (wait_irq_exit_events != 1) begin
            $error("[tb_iop_self_driven] expected 1 WAIT_IRQ exit, got %0d",
                   wait_irq_exit_events);
            errors = errors + 1;
        end
        if (halt_events != 1) begin
            $error("[tb_iop_self_driven] expected 1 halt event, got %0d", halt_events);
            errors = errors + 1;
        end

        $display("[tb_iop_self_driven] stall_pc=%0d ops=%0d wait_irq_exit=%0d halt=%0d done=%0d asserts=%0d acks=%0d errors=%0d",
                 stall_pc_sample, exec_op_events, wait_irq_exit_events,
                 halt_events, dmac_done_events,
                 intc_assert_events, intc_ack_events, errors);

        if (errors == 0 &&
            dmac_done_events     == 1 &&
            intc_assert_events   == 1 &&
            intc_ack_events      == 1 &&
            wait_irq_exit_events == 1 &&
            halt_events          == 1)
            $display("[tb_iop_self_driven] PASS");
        else
            $display("[tb_iop_self_driven] FAIL");

        $finish;
    end

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

endmodule : tb_iop_self_driven
