// retroDE_ps2 — tb_iop_core_bootstrap_intc
//
// Phase 2 of the BIOS bootstrap seam: combine BIOS-sourced mainline
// with RAM-resident exception handling. The core resets at
// 0xBFC0_0000 (kseg1 → phys 0x1FC0_0000 → bios_rom_stub). The BIOS
// bootstrap enables interrupts via MTC0, kicks two DMAC ch9 transfers,
// busy-checks DMAC.DONE_COUNT — and NEVER touches INTC_STAT. A small
// ISR preloaded in IOP RAM at the exception vector (0x0000_0080)
// drains the INTC via W1C and returns via JR + RFE.
//
// This TB layers Phase 1's BIOS seam on top of Phase 2's asynchronous
// exception-entry proof from tb_iop_core_interrupts. Nothing about
// the interrupt plumbing changes — only the reset source does.
//
// RAM layout (TB preloads via bridge_wr_*):
//   0x0000_0080..0x0000_009F   ISR (8 instructions)
//   0x0000_0200..0x0000_020F   DMA payload
//
// BIOS layout (TB preloads via hierarchical reference):
//   0xBFC0_0000   j  0xBFC0_0100    ; reset → bootstrap main
//   0xBFC0_0004   nop                ; delay slot
//   0xBFC0_0100+  bootstrap (setup, enable IRQs, kick 2 DMAs, syscall)
//
// Acceptance (load-bearing):
//   - halt_o asserts exactly once
//   - exactly 2 DMA_DONE events + 2 INTC asserts + 2 INTC acks
//   - exactly 2 exception-entry retires (core flag bit 5)
//   - ≥2 RFE retires (core flag bit 6)
//   - 0 mainline reads of INTC_STAT through the map
//   - at least one BIOS fetch at 0xBFC0_0000 (reset source truly
//     changed); overall BIOS fetch count dominates mainline ifetches

`timescale 1ns/1ps

module tb_iop_core_bootstrap_intc;

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

    localparam logic [31:0] RESET_VECTOR = 32'hBFC0_0000;
    localparam logic [31:0] BOOTSTRAP_VA = 32'hBFC0_0100;
    localparam logic [31:0] ISR_BASE     = 32'h0000_0080;
    localparam logic [31:0] PAYLOAD_BASE = 32'h0000_0200;
    localparam logic [31:0] INTC_STAT_PHYS = 32'h1F80_1070;

    localparam int RESET_SLOT      = 0;
    localparam int BOOTSTRAP_SLOT0 = 32'h100 >> 2;       // 0x40

    // 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 (default PC_RESET = 0xBFC0_0000, not overridden)
    // ------------------------------------------------------------------

    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 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 + BIOS + DMAC + INTC
    // ------------------------------------------------------------------

    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        bios_rd_en;
    logic [21:0] bios_rd_addr;
    logic [31:0] bios_rd_data;
    logic        bios_rd_valid;

    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_rd_en(bios_rd_en), .bios_rd_addr(bios_rd_addr),
        .bios_rd_data(bios_rd_data), .bios_rd_valid(bios_rd_valid),
        .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()
    );

    bios_rom_stub #(.SIZE_BYTES(BIOS_BYTES)) u_bios (
        .clk(clk), .rst_n(rst_n),
        .rd_en(bios_rd_en), .rd_addr(bios_rd_addr),
        .rd_data(bios_rd_data), .rd_valid(bios_rd_valid),
        .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_boot_intc_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_boot_intc_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_boot_intc_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_boot_intc_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_bios_reads;
    int map_bios_reset_reads;
    int map_intc_reads_stat;
    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_bios_reads           = 0;
        map_bios_reset_reads     = 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

        if (rst_n && iop_map_ev_valid &&
            iop_map_ev_subsys == trace_pkg::SUBSYS_IOP &&
            iop_map_ev_event  == trace_pkg::EV_READ) begin
            if (iop_map_ev_arg3[7:0] == 8'd6) begin
                map_bios_reads <= map_bios_reads + 1;
                if (iop_map_ev_arg0[31:0] == RESET_VECTOR)
                    map_bios_reset_reads <= map_bios_reset_reads + 1;
            end
            if (iop_map_ev_arg0[31:0] == INTC_STAT_PHYS)
                map_intc_reads_stat <= map_intc_reads_stat + 1;
        end
    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 bios_word(input int slot, input logic [31:0] word);
        u_bios.mem[slot] = word;
    endtask

    // Place a MIPS instruction into IOP RAM at byte offset (slot<<2)+base.
    // Used by the TB to preload the ISR at ISR_BASE in RAM.
    task automatic ram_word_at(input logic [31:0] addr, input logic [31:0] word);
        bridge_write(addr, 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

    function automatic logic [31:0] enc_j(input logic [31:0] target_addr);
        return {OP_J, target_addr[27:2]};
    endfunction

    // BIOS emission (word-indexed into u_bios.mem[])
    task automatic bios_j(input int slot, input logic [31:0] target_va);
        bios_word(slot, enc_j(target_va));
    endtask
    task automatic bios_lui(input int slot, input int rt, input logic [15:0] imm);
        bios_word(slot, enc_i(OP_LUI, 0, rt, imm));
    endtask
    task automatic bios_ori(input int slot, input int rt, input int rs,
                            input logic [15:0] imm);
        bios_word(slot, enc_i(OP_ORI, rs, rt, imm));
    endtask
    task automatic bios_addiu(input int slot, input int rt, input int rs,
                              input logic [15:0] imm);
        bios_word(slot, enc_i(OP_ADDIU, rs, rt, imm));
    endtask
    task automatic bios_lw(input int slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(slot, enc_i(OP_LW, rs, rt, offs));
    endtask
    task automatic bios_sw(input int slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(slot, enc_i(OP_SW, rs, rt, offs));
    endtask
    task automatic bios_bne(input int slot, input int rs, input int rt,
                            input int target_slot);
        bios_word(slot, enc_i(OP_BNE, rs, rt, branch_imm(slot, target_slot)));
    endtask
    task automatic bios_nop(input int slot);
        bios_word(slot, 32'd0);
    endtask
    task automatic bios_syscall(input int slot);
        bios_word(slot, 32'h0000_000C);
    endtask
    task automatic bios_mtc0(input int slot, input int rt, input int rd);
        bios_word(slot, {6'h10, 5'd4, rt[4:0], rd[4:0], 11'd0});
    endtask

    // ISR in IOP RAM. Index is slot within ISR region (base = ISR_BASE).
    task automatic isr_instr(input int slot, input logic [31:0] word);
        bridge_write(ISR_BASE + (slot << 2), word);
    endtask
    task automatic isr_lui(input int slot, input int rt, input logic [15:0] imm);
        isr_instr(slot, enc_i(OP_LUI, 0, rt, imm));
    endtask
    task automatic isr_ori(input int slot, input int rt, input int rs,
                           input logic [15:0] imm);
        isr_instr(slot, enc_i(OP_ORI, rs, rt, imm));
    endtask
    task automatic isr_sw(input int slot, input int rt, input int rs,
                          input logic [15:0] offs);
        isr_instr(slot, enc_i(OP_SW, rs, rt, offs));
    endtask
    task automatic isr_nop(input int slot);
        isr_instr(slot, 32'd0);
    endtask
    task automatic isr_mfc0(input int slot, input int rt, input int rd);
        isr_instr(slot, {6'h10, 5'd0, rt[4:0], rd[4:0], 11'd0});
    endtask
    task automatic isr_jr(input int slot, input int rs);
        isr_instr(slot, {6'h00, rs[4:0], 15'd0, 6'h08});
    endtask
    task automatic isr_rfe(input int slot);
        isr_instr(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);

        // ISR in RAM at ISR_BASE
        isr_lui (0, 26, 16'h1F80);          // $k0 = 0x1F800000
        isr_ori (1, 26, 26, 16'h1070);      // $k0 = INTC_STAT
        isr_ori (2, 27,  0, 16'h0001);      // $k1 = 1
        isr_sw  (3, 27, 26, 16'h0000);      // W1C ack
        isr_mfc0(4, 26, 14);                // $k0 = EPC
        isr_nop (5);
        isr_jr  (6, 26);                    // return
        isr_rfe (7);                        // delay slot: pop IE stack

        // DMA payload in RAM
        bridge_write(PAYLOAD_BASE + 32'h00, 32'hC01B_0000);
        bridge_write(PAYLOAD_BASE + 32'h04, 32'hC01B_0001);
        bridge_write(PAYLOAD_BASE + 32'h08, 32'hC01B_0002);
        bridge_write(PAYLOAD_BASE + 32'h0C, 32'hC01B_0003);

        // BIOS reset + bootstrap
        bios_j  (RESET_SLOT,     BOOTSTRAP_VA);
        bios_nop(RESET_SLOT + 1);

        bios_lui  (BOOTSTRAP_SLOT0 +  0,  1, 16'h1F80);
        bios_ori  (BOOTSTRAP_SLOT0 +  1,  1,  1, 16'h1520);  // DMAC_MADR
        bios_ori  (BOOTSTRAP_SLOT0 +  2,  2,  0, 16'h0200);  // payload
        bios_ori  (BOOTSTRAP_SLOT0 +  3,  3,  0, 16'h0004);
        bios_ori  (BOOTSTRAP_SLOT0 +  4,  4,  0, 16'h0001);
        bios_lui  (BOOTSTRAP_SLOT0 +  5,  5, 16'h1F80);
        bios_ori  (BOOTSTRAP_SLOT0 +  6,  5,  5, 16'h1074);  // INTC_MASK
        bios_addiu(BOOTSTRAP_SLOT0 +  7,  6,  1, 16'h000C);  // DONE_COUNT
        bios_ori  (BOOTSTRAP_SLOT0 +  8,  7,  0, 16'h0002);  // target=2
        bios_sw   (BOOTSTRAP_SLOT0 +  9,  4,  5, 16'h0000);  // INTC_MASK=1
        bios_ori  (BOOTSTRAP_SLOT0 + 10,  9,  0, 16'h0401);  // Status = IEc|IM2
        bios_mtc0 (BOOTSTRAP_SLOT0 + 11,  9, 12);            // enable IRQs
        bios_sw   (BOOTSTRAP_SLOT0 + 12,  2,  1, 16'h0000);  // start DMA 1
        bios_sw   (BOOTSTRAP_SLOT0 + 13,  3,  1, 16'h0004);
        bios_sw   (BOOTSTRAP_SLOT0 + 14,  4,  1, 16'h0008);
        // wait1 (slot +15)
        bios_lw   (BOOTSTRAP_SLOT0 + 15, 10,  6, 16'h0000);
        bios_ori  (BOOTSTRAP_SLOT0 + 16, 11,  0, 16'h0001);
        bios_bne  (BOOTSTRAP_SLOT0 + 17, 10, 11, BOOTSTRAP_SLOT0 + 15);
        bios_nop  (BOOTSTRAP_SLOT0 + 18);
        bios_sw   (BOOTSTRAP_SLOT0 + 19,  2,  1, 16'h0000);  // start DMA 2
        bios_sw   (BOOTSTRAP_SLOT0 + 20,  3,  1, 16'h0004);
        bios_sw   (BOOTSTRAP_SLOT0 + 21,  4,  1, 16'h0008);
        // wait2 (slot +22)
        bios_lw   (BOOTSTRAP_SLOT0 + 22, 10,  6, 16'h0000);
        bios_bne  (BOOTSTRAP_SLOT0 + 23, 10,  7, BOOTSTRAP_SLOT0 + 22);
        bios_nop  (BOOTSTRAP_SLOT0 + 24);
        bios_syscall(BOOTSTRAP_SLOT0 + 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_bootstrap_intc] expected 2 DMA_DONE, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        if (intc_assert_events != 2) begin
            $error("[tb_iop_core_bootstrap_intc] expected 2 INTC asserts, got %0d",
                   intc_assert_events);
            errors = errors + 1;
        end
        if (intc_ack_events != 2) begin
            $error("[tb_iop_core_bootstrap_intc] expected 2 INTC acks, got %0d",
                   intc_ack_events);
            errors = errors + 1;
        end
        if (core_exception_events != 2) begin
            $error("[tb_iop_core_bootstrap_intc] expected 2 exception-entry retires, got %0d",
                   core_exception_events);
            errors = errors + 1;
        end
        if (core_rfe_events < 2) begin
            $error("[tb_iop_core_bootstrap_intc] expected >=2 RFE retires, got %0d",
                   core_rfe_events);
            errors = errors + 1;
        end
        if (core_halt_events != 1) begin
            $error("[tb_iop_core_bootstrap_intc] expected 1 halt event, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (map_bios_reset_reads < 1) begin
            $error("[tb_iop_core_bootstrap_intc] no BIOS fetch at reset vector 0x%08h",
                   RESET_VECTOR);
            errors = errors + 1;
        end
        if (map_bios_reads < 20) begin
            $error("[tb_iop_core_bootstrap_intc] expected >=20 BIOS reads, got %0d",
                   map_bios_reads);
            errors = errors + 1;
        end
        if (map_intc_reads_stat != 0) begin
            $error("[tb_iop_core_bootstrap_intc] mainline polled INTC_STAT (%0d reads)",
                   map_intc_reads_stat);
            errors = errors + 1;
        end
        if (core_delay_slot_events != core_branch_taken_events) begin
            $error("[tb_iop_core_bootstrap_intc] delay-slot mismatch: %0d vs %0d taken",
                   core_delay_slot_events, core_branch_taken_events);
            errors = errors + 1;
        end

        $display("[tb_iop_core_bootstrap_intc] retired=%0d taken=%0d delay=%0d except=%0d rfe=%0d halt=%0d done=%0d acks=%0d bios_reads=%0d reset_reads=%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_ack_events,
                 map_bios_reads, map_bios_reset_reads,
                 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_bios_reset_reads  >= 1 &&
            map_intc_reads_stat   == 0)
            $display("[tb_iop_core_bootstrap_intc] PASS");
        else
            $display("[tb_iop_core_bootstrap_intc] FAIL");

        $finish;
    end

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

endmodule : tb_iop_core_bootstrap_intc
