// retroDE_ps2 — tb_ee_core_dmac_intc
//
// Fifth EE-core chapter. Mirrors the IOP arc cleanly: program DMA,
// take a real interrupt, vector, ack, continue. Chapters 3 and 4
// built the DMAC register ↔ EE core path (write-side then read-side).
// This chapter builds the interrupt path. `dmac_reg_stub.irq_completion_o`
// is latched by an EE-side `intc_stub` instance; the INTC's aggregate
// `cpu_irq` drives `ee_core_stub.cpu_irq` through `cause_ip[2]`; the
// map's new `ee_intc_{wr,rd}_*` ports carry EE CPU reads and writes of
// INTC_STAT / INTC_MASK at 0x1000_F000 and 0x1000_F010.
//
// Chain (added to chapter 4):
//   dmac_reg_stub.irq_completion_o ──► intc_stub.irq_src[0]
//                                      intc_stub.cpu_irq  ──► ee_core_stub.cpu_irq
//   ee_core_stub  ──(LW/SW 0x1000_F000 / 0x1000_F010)──►
//       ee_memory_map_stub.ee_intc_{rd,wr}_*  ──►  intc_stub.reg_{rd,wr}_*
//
// RAM layout (preloaded by TB via bridge_wr_*):
//   0x0000_0000   128-bit DMA payload (witness only; not checked)
//   0x0000_0080   ISR (8 instructions = 32 bytes)
//   0x0000_0200   witness qword (low 32 bits = final DONE_COUNT)
//
// BIOS bootstrap (at 0xBFC0_0100):
//   setup      lui   $1, 0x1000             ; $1 upper
//              ori   $1, $1, 0xA000          ; $1 = 0x1000_A000 (DMAC ch2)
//              lui   $5, 0x1000
//              ori   $5, $5, 0xF010          ; $5 = 0x1000_F010 (INTC_MASK)
//              ori   $4, $0,  1              ; $4 = 1 (mask/start/ack)
//              sw    $4,  0($5)              ; INTC_MASK = 1
//              lui   $9, 0
//              ori   $9, $9, 0x0401          ; $9 = IEc | IM[2]
//              mtc0  $9, $12                 ; Status = 0x401 (enables)
//   done_base  addiu $6, $1, 0x0040          ; $6 = DONE_COUNT addr
//              lw    $7, 0($6)                ; $7 = initial DONE_COUNT (0)
//              addiu $10, $7, 1               ; $10 = initial + 1 (1)
//   program    sw    $0,  0x10($1)            ; MADR = 0
//              sw    $4,  0x20($1)            ; QWC  = 1
//              sw    $4,  0x00($1)            ; CHCR = 1 (start)
//   wait_loop  lw    $11, 0($6)                ; $11 = DONE_COUNT
//              bne   $11, $10, wait_loop      ; spin until == 1
//              nop                             ; delay slot
//   witness    ori   $13, $0, 0x0200          ; witness RAM addr
//              sw    $11, 0($13)              ; RAM[0x200] = DONE_COUNT
//   halt       syscall
//
// ISR at EXC_VECTOR = 0x0000_0080 (in RAM):
//   0x80   lui   $26, 0x1000
//   0x84   ori   $26, $26, 0xF000              ; $k0 = INTC_STAT
//   0x88   ori   $27, $0, 1                    ; $k1 = 1
//   0x8C   sw    $27, 0($26)                   ; INTC_STAT = 1 (W1C ack)
//   0x90   mfc0  $26, $14                      ; $k0 = EPC
//   0x94   nop                                  ; hazard slot
//   0x98   jr    $26                           ; return to interrupted PC
//   0x9C   rfe                                  ; delay slot: pop IE stack
//
// Acceptance:
//   - halt = 1
//   - exactly 1 DMA_START, 1 DMA_BEAT, 1 DMA_DONE
//   - exactly 1 INTC assert, 1 INTC ack
//   - exactly 1 exception-entry retire (core flag bit 5)
//   - exactly 1 RFE retire (core flag bit 6)
//   - map EE_DMAC writes = 3 (MADR/QWC/CHCR)
//   - map EE_INTC writes >= 2 (MASK setup + W1C ack)
//   - exception cycle > DMA_DONE cycle (the interrupt actually caused
//     the exception, not a transient CPU condition)
//   - RAM[0x200] low 32 bits == 1 (DONE_COUNT delta witness)
//   - core takes no trap

`timescale 1ns/1ps

module tb_ee_core_dmac_intc;

    localparam int EE_RAM_BYTES   = 4 * 1024;
    localparam int EE_RAM_ADDR_W  = $clog2(EE_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 int          RESET_SLOT     = 0;
    localparam int          BOOTSTRAP_SLOT = 32'h100 >> 2;

    // ISR lives in EE RAM at the architectural exception vector 0x80.
    // `u_ee_ram.mem[]` is qword-indexed; slot 0x80/16 = 8 gives us four
    // qwords = 16 instructions of room. ISR is 8 instructions.
    localparam int ISR_VA_BASE       = 32'h0000_0080;
    localparam int ISR_QW_BASE       = ISR_VA_BASE >> 4;   // qword index 8
    localparam int WITNESS_RAM_ADDR  = 32'h0000_0200;
    localparam int WITNESS_QW_IDX    = WITNESS_RAM_ADDR >> 4;

    // Opcodes / func codes
    localparam logic [5:0] OP_SPECIAL = 6'h00;
    localparam logic [5:0] OP_J       = 6'h02;
    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;
    localparam logic [5:0] FUNC_JR    = 6'h08;
    localparam logic [5:0] FUNC_RFE   = 6'h10;

    // Bootstrap slot layout (relative to BOOTSTRAP_SLOT = 0x40)
    localparam int B_LUI_1      = BOOTSTRAP_SLOT + 0;
    localparam int B_ORI_1      = BOOTSTRAP_SLOT + 1;
    localparam int B_LUI_5      = BOOTSTRAP_SLOT + 2;
    localparam int B_ORI_5      = BOOTSTRAP_SLOT + 3;
    localparam int B_ORI_4      = BOOTSTRAP_SLOT + 4;
    localparam int B_SW_MASK    = BOOTSTRAP_SLOT + 5;
    localparam int B_LUI_9      = BOOTSTRAP_SLOT + 6;
    localparam int B_ORI_9      = BOOTSTRAP_SLOT + 7;
    localparam int B_MTC0_ST    = BOOTSTRAP_SLOT + 8;
    localparam int B_ADDIU_6    = BOOTSTRAP_SLOT + 9;
    localparam int B_LW_DC0     = BOOTSTRAP_SLOT + 10;
    localparam int B_ADDIU_10   = BOOTSTRAP_SLOT + 11;
    localparam int B_SW_MADR    = BOOTSTRAP_SLOT + 12;
    localparam int B_SW_QWC     = BOOTSTRAP_SLOT + 13;
    localparam int B_SW_CHCR    = BOOTSTRAP_SLOT + 14;
    localparam int B_WAIT_LW    = BOOTSTRAP_SLOT + 15;  // wait_loop entry
    localparam int B_WAIT_BNE   = BOOTSTRAP_SLOT + 16;
    localparam int B_WAIT_DS    = BOOTSTRAP_SLOT + 17;  // delay slot
    localparam int B_ORI_13     = BOOTSTRAP_SLOT + 18;
    localparam int B_SW_WIT     = BOOTSTRAP_SLOT + 19;
    localparam int B_HALT       = BOOTSTRAP_SLOT + 20;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // EE core
    // ------------------------------------------------------------------

    logic        core_go;
    logic        core_halt;
    logic [31:0] core_pc;
    logic        core_trap;
    logic [31:0] core_trap_pc;
    logic [31:0] core_trap_instr;

    logic        ee_rd_en;
    logic [31:0] ee_rd_addr;
    logic [31:0] ee_rd_data;
    logic        ee_rd_valid;

    logic        ee_wr_en;
    logic [31:0] ee_wr_addr;
    logic [31:0] ee_wr_data;
    logic [3:0]  ee_wr_be;

    logic        ee_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;

    // Ch33: route the exception vector through kseg0 so the ISR
    // fetches land in u_ee_ram (bridge-preloaded). Useg 0x80 now
    // maps to the separate useg-shadow backing and would execute as
    // all-zero NOPs, wedging the core in the ISR.
    ee_core_stub #(.EXC_VECTOR(32'h8000_0080)) u_core (
        .clk(clk), .rst_n(rst_n),
        .go_i(core_go),
        .map_rd_en(ee_rd_en), .map_rd_addr(ee_rd_addr),
        .map_rd_data(ee_rd_data), .map_rd_valid(ee_rd_valid),
        .map_wr_en(ee_wr_en), .map_wr_addr(ee_wr_addr),
        .map_wr_data(ee_wr_data), .map_wr_be(ee_wr_be),
        .cpu_irq(ee_cpu_irq),
        .halt_o(core_halt), .pc_o(core_pc),
        .trap_o(core_trap), .trap_pc_o(core_trap_pc),
        .trap_instr_o(core_trap_instr),
        .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)
    );

    // ------------------------------------------------------------------
    // EE memory map + EE RAM + shared BIOS ROM + DMAC + INTC plumbing
    // ------------------------------------------------------------------

    logic         bios_rd_en;
    logic [21:0]  bios_rd_addr;
    logic [31:0]  bios_rd_data;
    logic         bios_rd_valid;

    logic         ee_ram_rd_en;
    logic [24:0]  ee_ram_rd_addr;
    logic [127:0] ee_ram_rd_data;
    logic         ee_ram_rd_valid;
    logic         ee_ram_wr_en;
    logic [24:0]  ee_ram_wr_addr;
    logic [127:0] ee_ram_wr_data;
    logic [15:0]  ee_ram_wr_be;
    logic [7:0]   ee_ram_master_id;

    logic         dmac_rd_en;
    logic [31:0]  dmac_rd_addr;
    logic [127:0] dmac_rd_data;
    logic         dmac_rd_valid;

    logic        map_dmac_ch2_wr_en;
    logic [7:0]  map_dmac_ch2_wr_addr;
    logic [31:0] map_dmac_ch2_wr_data;
    logic        map_dmac_ch2_rd_en;
    logic [7:0]  map_dmac_ch2_rd_addr;
    logic [31:0] map_dmac_ch2_rd_data;
    logic        map_dmac_ch2_rd_valid;

    logic [7:0]  dmac_reg_offset_mux;
    assign dmac_reg_offset_mux = map_dmac_ch2_wr_en ? map_dmac_ch2_wr_addr
                                                   : map_dmac_ch2_rd_addr;

    logic        map_intc_wr_en;
    logic [7:0]  map_intc_wr_addr;
    logic [31:0] map_intc_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 [7:0]  intc_reg_addr_mux;
    assign intc_reg_addr_mux = map_intc_wr_en ? map_intc_wr_addr
                                              : map_intc_rd_addr;

    logic         tb_br_wr_en;
    logic [31:0]  tb_br_wr_addr;
    logic [127:0] tb_br_wr_data;
    logic [15:0]  tb_br_wr_be;

    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(ee_rd_en), .ee_rd_addr(ee_rd_addr),
        .ee_rd_data(ee_rd_data), .ee_rd_valid(ee_rd_valid),
        .ee_wr_en(ee_wr_en), .ee_wr_addr(ee_wr_addr),
        .ee_wr_data(ee_wr_data), .ee_wr_be(ee_wr_be),
        .dmac_rd_en(dmac_rd_en), .dmac_rd_addr(dmac_rd_addr),
        .dmac_rd_data(dmac_rd_data), .dmac_rd_valid(dmac_rd_valid),
        .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'd9),
        .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(ee_ram_rd_en), .ram_rd_addr(ee_ram_rd_addr),
        .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
        .ram_wr_en(ee_ram_wr_en), .ram_wr_addr(ee_ram_wr_addr),
        .ram_wr_data(ee_ram_wr_data), .ram_wr_be(ee_ram_wr_be),
        .ram_master_id(ee_ram_master_id),
        .ee_dmac_ch2_wr_en(map_dmac_ch2_wr_en),
        .ee_dmac_ch2_wr_addr(map_dmac_ch2_wr_addr),
        .ee_dmac_ch2_wr_data(map_dmac_ch2_wr_data),
        .ee_dmac_ch2_rd_en(map_dmac_ch2_rd_en),
        .ee_dmac_ch2_rd_addr(map_dmac_ch2_rd_addr),
        .ee_dmac_ch2_rd_data(map_dmac_ch2_rd_data),
        .ee_dmac_ch2_rd_valid(map_dmac_ch2_rd_valid),
        .ee_intc_wr_en(map_intc_wr_en),
        .ee_intc_wr_addr(map_intc_wr_addr),
        .ee_intc_wr_data(map_intc_wr_data),
        .ee_intc_rd_en(map_intc_rd_en),
        .ee_intc_rd_addr(map_intc_rd_addr),
        .ee_intc_rd_data(map_intc_rd_data),
        .ee_intc_rd_valid(map_intc_rd_valid),
        .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_ram_rd_en), .rd_addr(ee_ram_rd_addr[EE_RAM_ADDR_W-1:0]),
        .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
        .wr_en(ee_ram_wr_en), .wr_addr(ee_ram_wr_addr[EE_RAM_ADDR_W-1:0]),
        .wr_data(ee_ram_wr_data), .wr_be(ee_ram_wr_be),
        .master_id(ee_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()
    );

    // ------------------------------------------------------------------
    // DMAC ch2
    // ------------------------------------------------------------------

    logic         dmac_ep_valid;
    logic [127:0] dmac_ep_data;
    logic         dmac_ep_last;
    logic         dmac_ep_ready;
    assign dmac_ep_ready = 1'b1;

    logic dmac_irq_completion;

    logic                dmac_ev_valid;
    trace_pkg::subsys_e  dmac_ev_subsys;
    trace_pkg::event_e   dmac_ev_event;
    logic [63:0]         dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3;
    logic [31:0]         dmac_ev_flags;

    dmac_reg_stub #(.CHANNEL(4'd2), .PATH_ID(4'd2)) u_dmac_ch2 (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_dmac_ch2_wr_en),
        .reg_offset(dmac_reg_offset_mux),
        .reg_wr_data(map_dmac_ch2_wr_data),
        .reg_rd_en(map_dmac_ch2_rd_en),
        .reg_rd_data(map_dmac_ch2_rd_data),
        .reg_rd_valid(map_dmac_ch2_rd_valid),
        .mem_rd_en(dmac_rd_en), .mem_rd_addr(dmac_rd_addr),
        .mem_rd_data(dmac_rd_data), .mem_rd_valid(dmac_rd_valid),
        .ep_valid(dmac_ep_valid), .ep_data(dmac_ep_data),
        .ep_last(dmac_ep_last), .ep_ready(dmac_ep_ready),
        .irq_completion_o(dmac_irq_completion),
        .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
        .ev_event(dmac_ev_event),
        .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
        .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
        .ev_flags(dmac_ev_flags)
    );

    // ------------------------------------------------------------------
    // EE INTC — generic intc_stub, EE side. DMAC ch2 completion is
    // wired to source bit 0. INTC aggregate `cpu_irq` drives the core.
    // ------------------------------------------------------------------

    logic [15:0] ee_irq_src;
    assign ee_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'h00),
        .INTC_MASK_OFFSET(8'h10)
    ) u_intc (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en),
        .reg_addr(intc_reg_addr_mux),
        .reg_wr_data(map_intc_wr_data),
        .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid),
        .irq_src(ee_irq_src), .cpu_irq(ee_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("ee_core_dmac_intc_core.trace"), .SINK_LABEL("ee_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("ee_core_dmac_intc_map.trace"), .SINK_LABEL("ee_map"))
        u_trace_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_core_dmac_intc_dmac.trace"), .SINK_LABEL("dmac_ch2"))
        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("ee_core_dmac_intc_intc.trace"), .SINK_LABEL("ee_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 core_retired_events;
    int core_halt_events;
    int core_except_events;
    int core_rfe_events;
    int core_trap_events;
    int map_dmac_writes;
    int map_intc_writes;
    int dmac_start_events;
    int dmac_beat_events;
    int dmac_done_events;
    int intc_assert_events;
    int intc_ack_events;
    int dma_done_cycle;
    int exception_cycle;
    int errors;

    initial begin
        core_retired_events = 0;
        core_halt_events    = 0;
        core_except_events  = 0;
        core_rfe_events     = 0;
        core_trap_events    = 0;
        map_dmac_writes     = 0;
        map_intc_writes     = 0;
        dmac_start_events   = 0;
        dmac_beat_events    = 0;
        dmac_done_events    = 0;
        intc_assert_events  = 0;
        intc_ack_events     = 0;
        dma_done_cycle      = -1;
        exception_cycle     = -1;
        errors              = 0;
    end

    int cycle_ctr;
    always_ff @(posedge clk) begin
        if (!rst_n) cycle_ctr <= 0;
        else        cycle_ctr <= cycle_ctr + 1;
    end

    always_ff @(posedge clk) begin
        if (rst_n && core_ev_valid) begin
            core_retired_events <= core_retired_events + 1;
            if ((core_ev_flags & 32'h08) != 32'd0)
                core_halt_events <= core_halt_events + 1;
            if ((core_ev_flags & 32'h20) != 32'd0) begin
                core_except_events <= core_except_events + 1;
                exception_cycle    <= cycle_ctr;
            end
            if ((core_ev_flags & 32'h40) != 32'd0)
                core_rfe_events <= core_rfe_events + 1;
            if ((core_ev_flags & 32'h80) != 32'd0)
                core_trap_events <= core_trap_events + 1;
        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_flags & 32'h1) != 32'd0) begin
            if (ee_map_ev_arg3[7:0] == 8'd7)  // REGION_EE_DMAC
                map_dmac_writes <= map_dmac_writes + 1;
            if (ee_map_ev_arg3[7:0] == 8'd8)  // REGION_EE_INTC
                map_intc_writes <= map_intc_writes + 1;
        end

        if (rst_n && dmac_ev_valid &&
            dmac_ev_subsys == trace_pkg::SUBSYS_DMAC) begin
            if (dmac_ev_event == trace_pkg::EV_DMA_START)
                dmac_start_events <= dmac_start_events + 1;
            if (dmac_ev_event == trace_pkg::EV_DMA_BEAT)
                dmac_beat_events <= dmac_beat_events + 1;
            if (dmac_ev_event == trace_pkg::EV_DMA_DONE) begin
                dmac_done_events <= dmac_done_events + 1;
                dma_done_cycle   <= cycle_ctr;
            end
        end

        if (rst_n && intc_ev_valid &&
            intc_ev_subsys == trace_pkg::SUBSYS_INTC &&
            intc_ev_event  == trace_pkg::EV_IRQ) begin
            // intc_stub sets arg3=0 for new assertion, arg3=1 for W1C ack.
            if (intc_ev_arg3 == 64'd0 && (intc_ev_flags & 32'h1) == 32'd0)
                intc_assert_events <= intc_assert_events + 1;
            if (intc_ev_arg3 == 64'd1)
                intc_ack_events <= intc_ack_events + 1;
        end
    end

    // ------------------------------------------------------------------
    // Memory preload helpers
    // ------------------------------------------------------------------

    task automatic bios_word(input int slot, input logic [31:0] word);
        u_bios.mem[slot] = 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 [31:0] enc_j(input logic [31:0] target_addr);
        return {OP_J, target_addr[27:2]};
    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

    // MFC0 encoding: opcode=COP0, rs=0x00 (MF), rt=<reg>, rd=<cop0 reg>,
    // pad = 0. Matches iop_dmac / iop core encoding.
    function automatic logic [31:0] enc_mfc0(input int rt, input int rd);
        return {OP_COP0, 5'h00, rt[4:0], rd[4:0], 11'd0};
    endfunction

    // MTC0 encoding: opcode=COP0, rs=0x04 (MT), rt=<reg>, rd=<cop0 reg>.
    function automatic logic [31:0] enc_mtc0(input int rt, input int rd);
        return {OP_COP0, 5'h04, rt[4:0], rd[4:0], 11'd0};
    endfunction

    // RFE encoding: opcode=COP0 (6), rs=0x10 CO (5), rt=0 (5), rd=0 (5),
    // sa=0 (5), func=0x10 (6). The middle rt+rd+sa = 15 bits of zero.
    function automatic logic [31:0] enc_rfe();
        return {OP_COP0, 5'h10, 15'd0, FUNC_RFE};
    endfunction

    // JR encoding: SPECIAL, rs, 0, 0, 0, func=0x08.
    function automatic logic [31:0] enc_jr(input int rs);
        return {OP_SPECIAL, rs[4:0], 15'd0, FUNC_JR};
    endfunction

    task automatic bios_j    (input int slot, input logic [31:0] tgt);
        bios_word(slot, enc_j(tgt));
    endtask
    task automatic bios_nop  (input int slot);
        bios_word(slot, 32'd0);
    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_mtc0 (input int slot, input int rt, input int rd);
        bios_word(slot, enc_mtc0(rt, rd));
    endtask
    task automatic bios_syscall(input int slot);
        bios_word(slot, 32'h0000_000C);
    endtask

    // ISR preload: write one instruction into a 32-bit lane of the ISR
    // qword region via the bridge port. `isr_idx` is the ISR-relative
    // word index (0..7).
    task automatic ram_write_word(input logic [31:0] addr,
                                  input logic [31:0] word);
        logic [127:0] data128;
        logic [15:0]  be16;
        data128 = 128'd0;
        be16    = 16'd0;
        case (addr[3:2])
            2'd0: begin data128[31:0]   = word; be16[3:0]   = 4'hF; end
            2'd1: begin data128[63:32]  = word; be16[7:4]   = 4'hF; end
            2'd2: begin data128[95:64]  = word; be16[11:8]  = 4'hF; end
            2'd3: begin data128[127:96] = word; be16[15:12] = 4'hF; end
        endcase
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data128;
        tb_br_wr_be   = be16;
        @(negedge clk);
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 128'd0;
        tb_br_wr_be   = 16'd0;
    endtask

    task automatic isr_word(input int idx, input logic [31:0] word);
        ram_write_word(ISR_VA_BASE + (idx << 2), word);
    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 = 128'd0;
        tb_br_wr_be   = 16'd0;

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

        // --------------------------------------------------------------
        // ISR preload (RAM 0x80..0x9F)
        //   0x80   lui  $26, 0x1000
        //   0x84   ori  $26, $26, 0xF000
        //   0x88   ori  $27, $0, 1
        //   0x8C   sw   $27, 0($26)     ; W1C INTC_STAT bit 0
        //   0x90   mfc0 $26, $14         ; $k0 = EPC
        //   0x94   nop
        //   0x98   jr   $26
        //   0x9C   rfe                    ; delay slot
        // --------------------------------------------------------------
        isr_word(0, enc_i(OP_LUI, 0, 26, 16'h1000));
        isr_word(1, enc_i(OP_ORI, 26, 26, 16'hF000));
        isr_word(2, enc_i(OP_ORI, 0, 27, 16'h0001));
        isr_word(3, enc_i(OP_SW, 26, 27, 16'h0000));
        isr_word(4, enc_mfc0(26, 14));
        isr_word(5, 32'd0);                           // nop
        isr_word(6, enc_jr(26));
        isr_word(7, enc_rfe());

        // --------------------------------------------------------------
        // Reset entry
        // --------------------------------------------------------------
        bios_j  (RESET_SLOT,     BOOTSTRAP_VA);
        bios_nop(RESET_SLOT + 1);

        // --------------------------------------------------------------
        // Bootstrap
        // --------------------------------------------------------------
        bios_lui  (B_LUI_1,     1,    16'h1000);             // $1 = 0x1000_0000
        bios_ori  (B_ORI_1,     1, 1, 16'hA000);             // $1 = 0x1000_A000
        bios_lui  (B_LUI_5,     5,    16'h1000);
        bios_ori  (B_ORI_5,     5, 5, 16'hF010);             // $5 = INTC_MASK
        bios_ori  (B_ORI_4,     4, 0, 16'h0001);             // $4 = 1
        bios_sw   (B_SW_MASK,   4, 5, 16'h0000);             // INTC_MASK = 1
        bios_lui  (B_LUI_9,     9,    16'h0000);
        bios_ori  (B_ORI_9,     9, 9, 16'h0401);             // IEc + IM[2]
        bios_mtc0 (B_MTC0_ST,   9, 12);                      // Status = 0x401

        bios_addiu(B_ADDIU_6,  6, 1, 16'h0040);              // $6 = DONE_COUNT
        bios_lw   (B_LW_DC0,   7, 6, 16'h0000);              // $7 = init DC
        bios_addiu(B_ADDIU_10, 10, 7, 16'h0001);             // $10 = init + 1

        bios_sw   (B_SW_MADR,  0, 1, 16'h0010);              // MADR = 0
        bios_sw   (B_SW_QWC,   4, 1, 16'h0020);              // QWC  = 1
        bios_sw   (B_SW_CHCR,  4, 1, 16'h0000);              // CHCR = 1

        bios_lw   (B_WAIT_LW,  11, 6, 16'h0000);             // $11 = DC
        bios_bne  (B_WAIT_BNE, 11, 10, B_WAIT_LW);           // spin if != $10
        bios_nop  (B_WAIT_DS);                                // delay slot

        // Ch33: witness via kseg0 0x80000000 base so the SW lands
        // in u_ee_ram (the post-run check reads u_ee_ram.mem[WITNESS_QW_IDX]).
        // Useg 0x200 now maps to the separate useg-shadow backing.
        bios_lui  (B_ORI_13,   13,    16'h8000);             // $13 = 0x80000000
        bios_sw   (B_SW_WIT,   11, 13, 16'h0200);            // RAM[0x200] = DC

        bios_syscall(B_HALT);

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

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

        // --------------------------------------------------------------
        // Checks
        // --------------------------------------------------------------
        if (core_halt_events != 1) begin
            $error("[tb_ee_core_dmac_intc] expected 1 halt, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (core_except_events != 1) begin
            $error("[tb_ee_core_dmac_intc] expected 1 exception entry, got %0d",
                   core_except_events);
            errors = errors + 1;
        end
        if (core_rfe_events != 1) begin
            $error("[tb_ee_core_dmac_intc] expected 1 RFE retire, got %0d",
                   core_rfe_events);
            errors = errors + 1;
        end
        if (core_trap_events != 0) begin
            $error("[tb_ee_core_dmac_intc] unexpected strict trap: %0d",
                   core_trap_events);
            errors = errors + 1;
        end
        if (dmac_start_events != 1 || dmac_beat_events != 1 ||
            dmac_done_events  != 1) begin
            $error("[tb_ee_core_dmac_intc] DMAC event counts: start=%0d beat=%0d done=%0d",
                   dmac_start_events, dmac_beat_events, dmac_done_events);
            errors = errors + 1;
        end
        if (intc_assert_events != 1) begin
            $error("[tb_ee_core_dmac_intc] expected 1 INTC assert, got %0d",
                   intc_assert_events);
            errors = errors + 1;
        end
        if (intc_ack_events != 1) begin
            $error("[tb_ee_core_dmac_intc] expected 1 INTC ack, got %0d",
                   intc_ack_events);
            errors = errors + 1;
        end
        if (map_dmac_writes != 3) begin
            $error("[tb_ee_core_dmac_intc] expected 3 EE_DMAC writes, got %0d",
                   map_dmac_writes);
            errors = errors + 1;
        end
        if (map_intc_writes < 2) begin
            $error("[tb_ee_core_dmac_intc] expected >= 2 EE_INTC writes (MASK setup + W1C), got %0d",
                   map_intc_writes);
            errors = errors + 1;
        end
        if (dma_done_cycle < 0 || exception_cycle < 0 ||
            exception_cycle <= dma_done_cycle) begin
            $error("[tb_ee_core_dmac_intc] causality: dma_done_cycle=%0d exception_cycle=%0d",
                   dma_done_cycle, exception_cycle);
            errors = errors + 1;
        end
        if (u_ee_ram.mem[WITNESS_QW_IDX][31:0] !== 32'd1) begin
            $error("[tb_ee_core_dmac_intc] witness RAM[0x200] low32 = 0x%08h; expected 1",
                   u_ee_ram.mem[WITNESS_QW_IDX][31:0]);
            errors = errors + 1;
        end

        $display("[tb_ee_core_dmac_intc] retired=%0d halt=%0d except=%0d rfe=%0d dmac_wr=%0d intc_wr=%0d start=%0d beat=%0d done=%0d assert=%0d ack=%0d done_cyc=%0d exc_cyc=%0d witness_lo32=0x%08h errors=%0d",
                 core_retired_events, core_halt_events,
                 core_except_events, core_rfe_events,
                 map_dmac_writes, map_intc_writes,
                 dmac_start_events, dmac_beat_events, dmac_done_events,
                 intc_assert_events, intc_ack_events,
                 dma_done_cycle, exception_cycle,
                 u_ee_ram.mem[WITNESS_QW_IDX][31:0], errors);

        if (errors == 0 &&
            core_halt_events     == 1 &&
            core_except_events   == 1 &&
            core_rfe_events      == 1 &&
            core_trap_events     == 0 &&
            dmac_start_events    == 1 &&
            dmac_beat_events     == 1 &&
            dmac_done_events     == 1 &&
            intc_assert_events   == 1 &&
            intc_ack_events      == 1 &&
            map_dmac_writes      == 3 &&
            exception_cycle > dma_done_cycle &&
            u_ee_ram.mem[WITNESS_QW_IDX][31:0] == 32'd1)
            $display("[tb_ee_core_dmac_intc] PASS");
        else
            $display("[tb_ee_core_dmac_intc] FAIL");

        $finish;
    end

    initial begin
        #4000000;
        $error("[tb_ee_core_dmac_intc] timeout");
        $finish;
    end

endmodule : tb_ee_core_dmac_intc
