// retroDE_ps2 — tb_iop_core_interrupts
//
// First exception-driven IOP program. `iop_core_stub` now honours
// `cpu_irq` as a real interrupt signal — when enabled through COP0
// Status, a pending unmasked IRQ causes a precise vector to a fixed
// exception address at the next clean instruction boundary.
//
// This TB proves the seam crossing: mainline software no longer polls
// INTC_STAT. Instead it enables interrupts, kicks off DMA transfers,
// and busy-checks DMAC.DONE_COUNT to know when its own work is done.
// The INTC is drained entirely by a small RAM-resident ISR at the
// exception vector, which acks INTC_STAT through the real register
// path and returns via JR + RFE in its delay slot.
//
// RAM layout:
//   0x0000_0080..0x0000_009F   ISR (8 instructions, 32 bytes)
//   0x0000_0100..0x0000_0167   mainline program (26 instructions)
//   0x0000_0200..0x0000_020F   DMA payload (4 qwords' worth of data)
//
// ISR (at EXC_VECTOR = 0x0000_0080):
//   idx  addr      mnemonic                 ; comment
//    0  0x0080   lui   $26, 0x1F80          ; $k0 = 0x1F80_0000
//    1  0x0084   ori   $26, $26, 0x1070     ; $k0 = INTC_STAT addr
//    2  0x0088   ori   $27, $0,  1          ; $k1 = 1
//    3  0x008C   sw    $27, 0($26)          ; INTC_STAT = 1 (W1C ack)
//    4  0x0090   mfc0  $26, $14             ; $k0 = EPC
//    5  0x0094   nop
//    6  0x0098   jr    $26                  ; return to interrupted PC
//    7  0x009C   rfe                         ; delay slot: pop IE stack
//
// Mainline (at 0x0000_0100):
//    0  0x0100   lui   $1, 0x1F80
//    1  0x0104   ori   $1, $1, 0x1520       ; $1 = DMAC_MADR
//    2  0x0108   ori   $2, $0, 0x0200       ; $2 = payload base
//    3  0x010C   ori   $3, $0, 0x0004       ; $3 = BCR value (4 beats)
//    4  0x0110   ori   $4, $0, 0x0001       ; $4 = 1 (mask / start / ack)
//    5  0x0114   lui   $5, 0x1F80
//    6  0x0118   ori   $5, $5, 0x1074       ; $5 = INTC_MASK
//    7  0x011C   addiu $6, $1, 0x000C       ; $6 = DONE_COUNT addr
//    8  0x0120   ori   $7, $0, 0x0002       ; $7 = target done count (2)
//    9  0x0124   sw    $4, 0($5)            ; INTC_MASK = 1
//   10  0x0128   ori   $9, $0, 0x0401       ; Status = IEc | IM2
//   11  0x012C   mtc0  $9, $12              ; Status = 0x401
//   12  0x0130   sw    $2, 0($1)            ; MADR = payload
//   13  0x0134   sw    $3, 4($1)            ; BCR  = 4
//   14  0x0138   sw    $4, 8($1)            ; CHCR = 1 (start first DMA)
//   wait1:
//   15  0x013C   lw    $10, 0($6)           ; $10 = DONE_COUNT
//   16  0x0140   ori   $11, $0, 0x0001
//   17  0x0144   bne   $10, $11, wait1     ; spin until DONE_COUNT == 1
//   18  0x0148   nop                        ; delay slot
//   19  0x014C   sw    $2, 0($1)            ; kick second DMA
//   20  0x0150   sw    $3, 4($1)
//   21  0x0154   sw    $4, 8($1)
//   wait2:
//   22  0x0158   lw    $10, 0($6)
//   23  0x015C   bne   $10, $7, wait2      ; spin until DONE_COUNT == 2
//   24  0x0160   nop                        ; delay slot
//   25  0x0164   syscall                    ; halt
//
// Acceptance:
//   - halt_o asserts exactly once
//   - exactly 2 DMA_DONE events + 2 INTC asserts + 2 INTC acks
//   - exactly 2 exception-entry retires (flag bit 5)
//   - at least 2 RFE retires (flag bit 6)
//   - the SW inside the ISR (W1C at 0x1F80_1070) fires exactly twice
//   - mainline NEVER reads INTC_STAT (sanity check on the map trace:
//     no IOP map-reads to the INTC STAT address except from the ISR)

`timescale 1ns/1ps

module tb_iop_core_interrupts;

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

    localparam logic [31:0] ISR_BASE     = 32'h0000_0080;
    localparam logic [31:0] PROGRAM_BASE = 32'h0000_0100;
    localparam logic [31:0] PAYLOAD_BASE = 32'h0000_0200;

    localparam logic [31:0] INTC_STAT_PHYS = 32'h1F80_1070;

    // MIPS opcodes
    localparam logic [5:0] OP_SPECIAL = 6'h00;
    localparam logic [5:0] OP_J       = 6'h02;
    localparam logic [5:0] OP_BEQ     = 6'h04;
    localparam logic [5:0] OP_BNE     = 6'h05;
    localparam logic [5:0] OP_ADDIU   = 6'h09;
    localparam logic [5:0] OP_ORI     = 6'h0D;
    localparam logic [5:0] OP_LUI     = 6'h0F;
    localparam logic [5:0] OP_COP0    = 6'h10;
    localparam logic [5:0] OP_LW      = 6'h23;
    localparam logic [5:0] OP_SW      = 6'h2B;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // IOP core
    // ------------------------------------------------------------------

    logic        core_go;
    logic        core_halt;
    logic [31:0] core_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                core_ev_valid;
    trace_pkg::subsys_e  core_ev_subsys;
    trace_pkg::event_e   core_ev_event;
    logic [63:0]         core_ev_arg0, core_ev_arg1, core_ev_arg2, core_ev_arg3;
    logic [31:0]         core_ev_flags;

    iop_core_stub #(
        .PC_RESET(PROGRAM_BASE),
        .EXC_VECTOR(ISR_BASE)
    ) u_core (
        .clk(clk), .rst_n(rst_n),
        .go_i(core_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(core_halt), .pc_o(core_pc),
        .trap_o(), .trap_pc_o(), .trap_instr_o(),
        .ev_valid(core_ev_valid), .ev_subsys(core_ev_subsys),
        .ev_event(core_ev_event),
        .ev_arg0(core_ev_arg0), .ev_arg1(core_ev_arg1),
        .ev_arg2(core_ev_arg2), .ev_arg3(core_ev_arg3),
        .ev_flags(core_ev_flags)
    );

    // ------------------------------------------------------------------
    // IOP map + RAM + DMAC + INTC (standard wiring as in tb_iop_core_basic)
    // ------------------------------------------------------------------

    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;

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

    logic        ep_valid;
    logic [31:0] ep_data;
    logic        ep_last;
    logic        ep_ready;
    assign ep_ready = 1'b1;

    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)
    );

    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_core_intr_core.trace"), .SINK_LABEL("iop_core"))
        u_trace_core (.clk(clk), .rst_n(rst_n),
            .ev_valid(core_ev_valid), .ev_subsys(core_ev_subsys),
            .ev_event(core_ev_event), .ev_arg0(core_ev_arg0),
            .ev_arg1(core_ev_arg1), .ev_arg2(core_ev_arg2),
            .ev_arg3(core_ev_arg3), .ev_flags(core_ev_flags));

    trace_sink_stub #(.FILENAME("iop_core_intr_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_core_intr_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_core_intr_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
    // ------------------------------------------------------------------

    int dmac_done_events;
    int intc_assert_events;
    int intc_ack_events;
    int core_retired_events;
    int core_branch_taken_events;
    int core_halt_events;
    int core_delay_slot_events;
    int core_exception_events;
    int core_rfe_events;
    int map_intc_reads_stat;           // reads of INTC_STAT through the map
    int errors;

    initial begin
        dmac_done_events         = 0;
        intc_assert_events       = 0;
        intc_ack_events          = 0;
        core_retired_events      = 0;
        core_branch_taken_events = 0;
        core_halt_events         = 0;
        core_delay_slot_events   = 0;
        core_exception_events    = 0;
        core_rfe_events          = 0;
        map_intc_reads_stat      = 0;
        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 && core_ev_valid) begin
            core_retired_events <= core_retired_events + 1;
            if ((core_ev_flags & 32'h04) != 32'd0)
                core_branch_taken_events <= core_branch_taken_events + 1;
            if ((core_ev_flags & 32'h08) != 32'd0)
                core_halt_events <= core_halt_events + 1;
            if ((core_ev_flags & 32'h10) != 32'd0)
                core_delay_slot_events <= core_delay_slot_events + 1;
            if ((core_ev_flags & 32'h20) != 32'd0)
                core_exception_events <= core_exception_events + 1;
            if ((core_ev_flags & 32'h40) != 32'd0)
                core_rfe_events <= core_rfe_events + 1;
        end

        // Count IOP-map READ events whose address matches INTC_STAT
        // (this detects any rogue mainline poll of INTC_STAT).
        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_arg0[31:0] == INTC_STAT_PHYS)
            map_intc_reads_stat <= map_intc_reads_stat + 1;
    end

    // ------------------------------------------------------------------
    // Preload helpers
    // ------------------------------------------------------------------

    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_instr_at(input logic [31:0] addr, input logic [31:0] word);
        bridge_write(addr, word);
    endtask

    task automatic emit_instr_slot(input logic [31:0] base, input int slot,
                                   input logic [31:0] word);
        bridge_write(base + (slot << 2), word);
    endtask

    function automatic logic [31:0] enc_i(input logic [5:0] op,
                                          input int rs, input int rt,
                                          input logic [15:0] imm);
        return {op, rs[4:0], rt[4:0], imm};
    endfunction

    function automatic logic [15:0] branch_imm(input int branch_slot,
                                               input int target_slot);
        int diff;
        diff = target_slot - branch_slot - 1;
        return diff[15:0];
    endfunction

    task automatic emit_lui(input logic [31:0] base, input int slot,
                            input int rt, input logic [15:0] imm);
        emit_instr_slot(base, slot, enc_i(OP_LUI, 0, rt, imm));
    endtask
    task automatic emit_ori(input logic [31:0] base, input int slot,
                            input int rt, input int rs, input logic [15:0] imm);
        emit_instr_slot(base, slot, enc_i(OP_ORI, rs, rt, imm));
    endtask
    task automatic emit_addiu(input logic [31:0] base, input int slot,
                              input int rt, input int rs, input logic [15:0] imm);
        emit_instr_slot(base, slot, enc_i(OP_ADDIU, rs, rt, imm));
    endtask
    task automatic emit_lw(input logic [31:0] base, input int slot,
                           input int rt, input int rs, input logic [15:0] offs);
        emit_instr_slot(base, slot, enc_i(OP_LW, rs, rt, offs));
    endtask
    task automatic emit_sw(input logic [31:0] base, input int slot,
                           input int rt, input int rs, input logic [15:0] offs);
        emit_instr_slot(base, slot, enc_i(OP_SW, rs, rt, offs));
    endtask
    task automatic emit_beq(input logic [31:0] base, input int slot,
                            input int rs, input int rt, input int target_slot);
        emit_instr_slot(base, slot, enc_i(OP_BEQ, rs, rt,
                                          branch_imm(slot, target_slot)));
    endtask
    task automatic emit_bne(input logic [31:0] base, input int slot,
                            input int rs, input int rt, input int target_slot);
        emit_instr_slot(base, slot, enc_i(OP_BNE, rs, rt,
                                          branch_imm(slot, target_slot)));
    endtask
    task automatic emit_nop(input logic [31:0] base, input int slot);
        emit_instr_slot(base, slot, 32'd0);
    endtask
    task automatic emit_syscall(input logic [31:0] base, input int slot);
        emit_instr_slot(base, slot, 32'h0000_000C);
    endtask

    // COP0 / R-type additions
    task automatic emit_jr(input logic [31:0] base, input int slot,
                           input int rs);
        // SPECIAL opcode, rs set, func=0x08
        emit_instr_slot(base, slot, {6'h00, rs[4:0], 15'd0, 6'h08});
    endtask
    task automatic emit_mfc0(input logic [31:0] base, input int slot,
                             input int rt, input int rd);
        // COP0 opcode, rs=0x00 (MF), rt, rd, pad 0
        emit_instr_slot(base, slot, {6'h10, 5'd0, rt[4:0], rd[4:0], 11'd0});
    endtask
    task automatic emit_mtc0(input logic [31:0] base, input int slot,
                             input int rt, input int rd);
        // COP0 opcode, rs=0x04 (MT)
        emit_instr_slot(base, slot, {6'h10, 5'd4, rt[4:0], rd[4:0], 11'd0});
    endtask
    task automatic emit_rfe(input logic [31:0] base, input int slot);
        // Canonical RFE encoding: 0x42000010
        emit_instr_slot(base, slot, 32'h4200_0010);
    endtask

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

    initial begin
        rst_n         = 1'b0;
        core_go       = 1'b0;
        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);

        // DMA payload
        bridge_write(PAYLOAD_BASE + 32'h00, 32'hA11B_0000);
        bridge_write(PAYLOAD_BASE + 32'h04, 32'hA11B_0001);
        bridge_write(PAYLOAD_BASE + 32'h08, 32'hA11B_0002);
        bridge_write(PAYLOAD_BASE + 32'h0C, 32'hA11B_0003);

        // ISR at ISR_BASE
        emit_lui  (ISR_BASE, 0, 26, 16'h1F80);        // lui  $k0, 0x1F80
        emit_ori  (ISR_BASE, 1, 26, 26, 16'h1070);    // ori  $k0, $k0, 0x1070
        emit_ori  (ISR_BASE, 2, 27,  0, 16'h0001);    // ori  $k1, $0, 1
        emit_sw   (ISR_BASE, 3, 27, 26, 16'h0000);    // sw   $k1, 0($k0)  (W1C)
        emit_mfc0 (ISR_BASE, 4, 26, 14);              // mfc0 $k0, EPC
        emit_nop  (ISR_BASE, 5);                      // nop
        emit_jr   (ISR_BASE, 6, 26);                  // jr   $k0
        emit_rfe  (ISR_BASE, 7);                      // rfe  (delay slot)

        // Mainline at PROGRAM_BASE
        emit_lui  (PROGRAM_BASE,  0,  1, 16'h1F80);   // lui  $1, 0x1F80
        emit_ori  (PROGRAM_BASE,  1,  1,  1, 16'h1520); // ori $1,$1,0x1520
        emit_ori  (PROGRAM_BASE,  2,  2,  0, 16'h0200); // $2 = 0x0200
        emit_ori  (PROGRAM_BASE,  3,  3,  0, 16'h0004); // $3 = 4
        emit_ori  (PROGRAM_BASE,  4,  4,  0, 16'h0001); // $4 = 1
        emit_lui  (PROGRAM_BASE,  5,  5, 16'h1F80);
        emit_ori  (PROGRAM_BASE,  6,  5,  5, 16'h1074); // $5 = INTC_MASK
        emit_addiu(PROGRAM_BASE,  7,  6,  1, 16'h000C); // $6 = DONE_COUNT
        emit_ori  (PROGRAM_BASE,  8,  7,  0, 16'h0002); // $7 = 2
        emit_sw   (PROGRAM_BASE,  9,  4,  5, 16'h0000); // INTC_MASK = 1
        emit_ori  (PROGRAM_BASE, 10,  9,  0, 16'h0401); // $9 = 0x0401
        emit_mtc0 (PROGRAM_BASE, 11,  9, 12);           // Status = $9
        emit_sw   (PROGRAM_BASE, 12,  2,  1, 16'h0000); // MADR = $2
        emit_sw   (PROGRAM_BASE, 13,  3,  1, 16'h0004); // BCR  = $3
        emit_sw   (PROGRAM_BASE, 14,  4,  1, 16'h0008); // CHCR = $4
        // wait1 (slot 15)
        emit_lw   (PROGRAM_BASE, 15, 10,  6, 16'h0000); // $10 = DONE_COUNT
        emit_ori  (PROGRAM_BASE, 16, 11,  0, 16'h0001); // $11 = 1
        emit_bne  (PROGRAM_BASE, 17, 10, 11, 15);       // bne -> slot 15
        emit_nop  (PROGRAM_BASE, 18);
        emit_sw   (PROGRAM_BASE, 19,  2,  1, 16'h0000); // second DMA
        emit_sw   (PROGRAM_BASE, 20,  3,  1, 16'h0004);
        emit_sw   (PROGRAM_BASE, 21,  4,  1, 16'h0008);
        // wait2 (slot 22)
        emit_lw   (PROGRAM_BASE, 22, 10,  6, 16'h0000);
        emit_bne  (PROGRAM_BASE, 23, 10,  7, 22);        // bne -> slot 22
        emit_nop  (PROGRAM_BASE, 24);
        emit_syscall(PROGRAM_BASE, 25);

        @(negedge clk);
        core_go = 1'b1;
        @(negedge clk);
        core_go = 1'b0;

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

        // --------------------------------------------------------------
        // Post-halt checks
        // --------------------------------------------------------------
        if (dmac_done_events != 2) begin
            $error("[tb_iop_core_interrupts] expected 2 DMA_DONE, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        if (intc_assert_events != 2) begin
            $error("[tb_iop_core_interrupts] expected 2 INTC asserts, got %0d",
                   intc_assert_events);
            errors = errors + 1;
        end
        if (intc_ack_events != 2) begin
            $error("[tb_iop_core_interrupts] expected 2 INTC acks, got %0d",
                   intc_ack_events);
            errors = errors + 1;
        end
        if (core_exception_events != 2) begin
            $error("[tb_iop_core_interrupts] expected 2 exception-entry retires, got %0d",
                   core_exception_events);
            errors = errors + 1;
        end
        if (core_rfe_events < 2) begin
            $error("[tb_iop_core_interrupts] expected >=2 RFE retires, got %0d",
                   core_rfe_events);
            errors = errors + 1;
        end
        if (core_halt_events != 1) begin
            $error("[tb_iop_core_interrupts] expected 1 halt event, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (map_intc_reads_stat != 0) begin
            $error("[tb_iop_core_interrupts] mainline polled INTC_STAT (%0d reads seen)",
                   map_intc_reads_stat);
            errors = errors + 1;
        end
        if (core_delay_slot_events != core_branch_taken_events) begin
            $error("[tb_iop_core_interrupts] delay-slot invariant: %0d delays vs %0d taken",
                   core_delay_slot_events, core_branch_taken_events);
            errors = errors + 1;
        end
        // Final pc should park at the syscall address
        if (core_pc !== PROGRAM_BASE + (32'd25 << 2)) begin
            $error("[tb_iop_core_interrupts] final pc mismatch: got 0x%08h expected 0x%08h",
                   core_pc, PROGRAM_BASE + (32'd25 << 2));
            errors = errors + 1;
        end

        $display("[tb_iop_core_interrupts] retired=%0d branches_taken=%0d delay_slots=%0d except=%0d rfe=%0d halt=%0d done=%0d asserts=%0d acks=%0d intc_stat_polls=%0d errors=%0d",
                 core_retired_events, core_branch_taken_events,
                 core_delay_slot_events, core_exception_events,
                 core_rfe_events, core_halt_events,
                 dmac_done_events, intc_assert_events, intc_ack_events,
                 map_intc_reads_stat, errors);

        if (errors == 0 &&
            dmac_done_events      == 2 &&
            intc_assert_events    == 2 &&
            intc_ack_events       == 2 &&
            core_exception_events == 2 &&
            core_halt_events      == 1 &&
            map_intc_reads_stat   == 0)
            $display("[tb_iop_core_interrupts] PASS");
        else
            $display("[tb_iop_core_interrupts] FAIL");

        $finish;
    end

    initial begin
        #10000000;
        $error("[tb_iop_core_interrupts] timeout");
        $finish;
    end

endmodule : tb_iop_core_interrupts
