// retroDE_ps2 — iop_core_stub
//
// Minimal MIPS R3000 subset for the IOP side, now with real interrupt
// exception entry. The engine sits where `iop_exec_stub` sat, drives
// `iop_memory_map_stub`'s CPU-side port for ifetch and data accesses,
// and finally *uses* `cpu_irq` from the IOP INTC instead of ignoring it.
//
// Wave 1 (decode): LUI/ORI/ADDIU/LW/SW/BEQ/BNE/J/NOP/SYSCALL, honest
// branch delay slots. Programs polled INTC_STAT through the real map.
//
// Wave 2 (this module revision): minimal COP0 + asynchronous interrupt
// exception entry. cpu_irq becomes a real vectoring event when
// enabled through Status. Mainline no longer needs to touch INTC_STAT;
// an ISR at the exception vector handles acknowledgement.
//
// Intentionally still NOT a full R3000:
//   - No TLB / cache / HI/LO / R-type ALU / shifts / mul / div.
//   - No syscall / break exception *handling* beyond SYSCALL-as-halt.
//   - No BD bit in Cause for branch-delay exceptions (we simply
//     refuse to take exceptions between a taken branch and its delay
//     slot — see "delay-slot rule" below).
//   - No kernel/user mode enforcement: KU state exists on the stack
//     for forward compatibility but nothing in the core consults it.
//
// Supported opcodes (MIPS encoding):
//   SPECIAL (opcode = 0x00):
//     func 0x08 (JR)         — pc <= rs_val; has delay slot.
//     func 0x0C (SYSCALL)    — halt_o asserts; FSM stops fetching.
//     any other func         — treated as NOP (incl. SLL $0,$0,0).
//   0x02 J                   — jump; has delay slot.
//   0x04 BEQ / 0x05 BNE      — conditional branch; has delay slot.
//   0x09 ADDIU               — no overflow trap.
//   0x0D ORI / 0x0F LUI      — logical immediate / upper load.
//   0x10 COP0:
//     rs 0x00 (MFC0)         — rt <= COP0[rd]
//     rs 0x04 (MTC0)         — COP0[rd] <= rt
//     rs 0x10, func 0x10 (RFE)
//                            — shift IE/KU stack right (pop)
//   0x23 LW / 0x2B SW        — word memory access.
//   Anything else            — treated as NOP.
//
// COP0 register surface (subset):
//   12  Status      [0]=IEc [1]=KUc [2]=IEp [3]=KUp [4]=IEo [5]=KUo
//                   [15:8]=IM   (bit 10 = IM2 gates the HW interrupt
//                   wired to cpu_irq)
//   13  Cause       [6:2]=ExcCode, [15:8]=IP. IP[2] reflects cpu_irq.
//                   Software may write Cause but we only latch SW
//                   interrupt pending bits IP[1:0] — not load-bearing
//                   in the first TB.
//   14  EPC         saved PC on exception entry.
//
// Exception entry semantics:
//   Sampled at *instruction-retire boundaries*, never mid-fetch or
//   mid-memory. An exception is taken iff all of the following hold
//   at the retire boundary:
//     - Status.IEc == 1                 (master interrupts enabled)
//     - Cause.IP[i] & Status.IM[i]      (any unmasked pending source)
//     - new_branch_pending == 0         (delay slot already resolved)
//   On entry:
//     EPC  <= next_pc                   (the pc that would have been
//                                        fetched next; branch_target
//                                        if a delay slot just resolved,
//                                        pc+4 otherwise)
//     Cause.ExcCode <= 5'h00            (Int exception)
//     Status stack pushes left:
//         IEo <= IEp; IEp <= IEc; IEc <= 0
//         KUo <= KUp; KUp <= KUc; KUc <= 0
//     pc <= EXC_VECTOR                  (fixed, parameter)
//     branch_pending <= 0               (any pending control flow is
//                                        canceled; EPC captured it)
//
// RFE semantics (pop stack, one level):
//   IEc <= IEp; IEp <= IEo
//   KUc <= KUp; KUp <= KUo
//   (IEo, KUo left intact — matches impl-defined R3000 behaviour
//   for non-nested use)
//
// Trace (SUBSYS_IOP, EV_IFETCH one-per-retire as before):
//   flags bit 0 = SW (write)            (unchanged)
//   flags bit 1 = LW (read)             (unchanged)
//   flags bit 2 = branch / jump taken   (unchanged)
//   flags bit 3 = SYSCALL (halt)        (unchanged)
//   flags bit 4 = this instruction was in a delay slot
//   flags bit 5 = exception taken at the end of this instruction
//                 (EPC saved = next_pc, PC redirected to EXC_VECTOR)
//   flags bit 6 = RFE retired (IE stack popped)
//   flags bit 7 = strict trap (unsupported instruction halted the core)
//
// Strict mode (STRICT_UNSUPPORTED parameter):
//   Default is 0 (lenient) to preserve every prior bench's regression
//   behaviour — any instruction the core doesn't actively decode retires
//   as a NOP. When STRICT_UNSUPPORTED=1, the core instead halts on the
//   first unsupported opcode it encounters, latches the offending PC +
//   instruction word into trap_pc_o / trap_instr_o, asserts trap_o, and
//   emits a retire trace with flag bit 7 set. Intended for real-BIOS
//   smoke bring-up — "the first missing opcode is the one the core
//   needs to grow next." The canonical NOP (32'h0000_0000 =
//   SLL $0,$0,0) is always treated as a NOP regardless of strict mode.

`timescale 1ns/1ps

module iop_core_stub
    import trace_pkg::*;
#(
    // Architectural MIPS R3000 reset vector (kseg1 into the shared BIOS
    // window). kseg1 strip in iop_memory_map_stub maps this to physical
    // 0x1FC0_0000, which the map now routes to bios_rom_stub.
    // Tests that don't have a BIOS image must override PC_RESET.
    parameter logic [31:0] PC_RESET    = 32'hBFC0_0000,
    parameter logic [31:0] EXC_VECTOR  = 32'h0000_0080,
    // See header comment "Strict mode". Default 0 preserves existing
    // regression behaviour; BIOS-oriented benches should set to 1.
    parameter bit          STRICT_UNSUPPORTED = 1'b0
) (
    input  logic          clk,
    input  logic          rst_n,

    input  logic          go_i,

    output logic          map_rd_en,
    output logic [31:0]   map_rd_addr,
    input  logic [31:0]   map_rd_data,
    input  logic          map_rd_valid,

    output logic          map_wr_en,
    output logic [31:0]   map_wr_addr,
    output logic [31:0]   map_wr_data,
    output logic [3:0]    map_wr_be,

    input  logic          cpu_irq,

    output logic          halt_o,
    output logic [31:0]   pc_o,

    // Strict-mode trap reporting. `trap_o` rises the cycle the core
    // halts on an unsupported instruction; `trap_pc_o` / `trap_instr_o`
    // latch the offending fetch. All three stay stable after the halt.
    output logic          trap_o,
    output logic [31:0]   trap_pc_o,
    output logic [31:0]   trap_instr_o,

    output logic          ev_valid,
    output subsys_e       ev_subsys,
    output event_e        ev_event,
    output logic [63:0]   ev_arg0,
    output logic [63:0]   ev_arg1,
    output logic [63:0]   ev_arg2,
    output logic [63:0]   ev_arg3,
    output logic [31:0]   ev_flags
);

    // ------------------------------------------------------------------
    // Opcode / func / COP0 rs constants
    // ------------------------------------------------------------------

    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;

    localparam logic [5:0] FUNC_JR       = 6'h08;
    localparam logic [5:0] FUNC_SYSCALL  = 6'h0C;
    localparam logic [5:0] FUNC_RFE      = 6'h10;

    localparam logic [4:0] COP0_RS_MF = 5'h00;
    localparam logic [4:0] COP0_RS_MT = 5'h04;
    localparam logic [4:0] COP0_RS_CO = 5'h10;

    localparam logic [4:0] COP0_REG_STATUS = 5'd12;
    localparam logic [4:0] COP0_REG_CAUSE  = 5'd13;
    localparam logic [4:0] COP0_REG_EPC    = 5'd14;

    // ------------------------------------------------------------------
    // FSM state
    // ------------------------------------------------------------------

    typedef enum logic [3:0] {
        S_IDLE         = 4'd0,
        S_IFETCH_REQ   = 4'd1,
        S_IFETCH_WAIT  = 4'd2,
        S_EXECUTE      = 4'd3,
        S_MEM_REQ      = 4'd4,
        S_MEM_WAIT     = 4'd5,
        S_MEM_WRITE    = 4'd6,
        S_HALT         = 4'd7
    } state_e;

    state_e state;

    // Architectural state
    logic [31:0] pc;
    logic [31:0] instr;
    logic [31:0] regfile [0:31];

    // Branch delay-slot tracking
    logic        branch_pending;
    logic [31:0] branch_target;
    logic        instr_in_delay_slot;

    // COP0 — Status (IE/KU triple stack + IM)
    logic        status_iec, status_iep, status_ieo;
    logic        status_kuc, status_kup, status_kuo;
    logic [7:0]  status_im;

    // COP0 — Cause / EPC
    logic [4:0]  cause_exc_code;
    logic [7:0]  cause_ip_sw;          // software-writable pending bits (IP[1:0])
    logic [31:0] epc;

    // Combinational composition of IP. IP[2] mirrors cpu_irq directly;
    // higher sources are not wired in the current scope.
    logic [7:0]  cause_ip;
    always_comb begin
        cause_ip        = 8'd0;
        cause_ip[1:0]   = cause_ip_sw[1:0];
        cause_ip[2]     = cpu_irq;
    end

    // Composed Status word (for MFC0) and Cause word (for MFC0)
    logic [31:0] status_word;
    logic [31:0] cause_word;
    always_comb begin
        status_word     = 32'd0;
        status_word[0]  = status_iec;
        status_word[1]  = status_kuc;
        status_word[2]  = status_iep;
        status_word[3]  = status_kup;
        status_word[4]  = status_ieo;
        status_word[5]  = status_kuo;
        status_word[15:8] = status_im;

        cause_word      = 32'd0;
        cause_word[6:2] = cause_exc_code;
        cause_word[15:8] = cause_ip;
    end

    // ------------------------------------------------------------------
    // Decode — combinational extraction from `instr`
    // ------------------------------------------------------------------

    logic [5:0]  opcode;
    logic [4:0]  rs_idx;
    logic [4:0]  rt_idx;
    logic [4:0]  rd_idx;
    logic [5:0]  func;
    logic [15:0] imm16;
    logic [25:0] imm26;
    logic [31:0] imm_sx;
    logic [31:0] imm_zx;
    logic [31:0] branch_offset;
    logic [31:0] branch_tgt;
    logic [31:0] j_tgt;
    logic [31:0] rs_val;
    logic [31:0] rt_val;
    logic [31:0] ea;

    assign opcode        = instr[31:26];
    assign rs_idx        = instr[25:21];
    assign rt_idx        = instr[20:16];
    assign rd_idx        = instr[15:11];
    assign imm16         = instr[15:0];
    assign imm26         = instr[25:0];
    assign func          = instr[5:0];
    assign imm_sx        = {{16{imm16[15]}}, imm16};
    assign imm_zx        = {16'd0, imm16};
    assign branch_offset = {{14{imm16[15]}}, imm16, 2'b00};
    assign branch_tgt    = pc + 32'd4 + branch_offset;
    assign j_tgt         = {pc[31:28], imm26, 2'b00};
    assign rs_val        = (rs_idx == 5'd0) ? 32'd0 : regfile[rs_idx];
    assign rt_val        = (rt_idx == 5'd0) ? 32'd0 : regfile[rt_idx];
    assign ea            = rs_val + imm_sx;

    // Instruction classification
    logic is_special, is_syscall, is_jr;
    logic is_cop0, is_mfc0, is_mtc0, is_rfe;
    logic is_nop_class;
    logic is_lui, is_ori, is_addiu, is_lw, is_sw, is_beq, is_bne, is_j;
    logic is_branch, is_jump;
    logic branch_taken;
    logic is_taken_branch_or_jump;

    assign is_special = (opcode == OP_SPECIAL);
    assign is_syscall = is_special && (func == FUNC_SYSCALL);
    assign is_jr      = is_special && (func == FUNC_JR);
    assign is_cop0    = (opcode == OP_COP0);
    assign is_mfc0    = is_cop0 && (rs_idx == COP0_RS_MF);
    assign is_mtc0    = is_cop0 && (rs_idx == COP0_RS_MT);
    assign is_rfe     = is_cop0 && (rs_idx == COP0_RS_CO) && (func == FUNC_RFE);

    assign is_lui     = (opcode == OP_LUI);
    assign is_ori     = (opcode == OP_ORI);
    assign is_addiu   = (opcode == OP_ADDIU);
    assign is_lw      = (opcode == OP_LW);
    assign is_sw      = (opcode == OP_SW);
    assign is_beq     = (opcode == OP_BEQ);
    assign is_bne     = (opcode == OP_BNE);
    assign is_j       = (opcode == OP_J);

    assign is_branch  = is_beq || is_bne;
    assign is_jump    = is_j || is_jr;
    assign branch_taken = (is_beq && (rs_val == rt_val)) ||
                          (is_bne && (rs_val != rt_val));
    assign is_taken_branch_or_jump = branch_taken || is_jump;

    // "NOP class" = anything we don't actively decode. In lenient mode
    // these retire as a NOP; in strict mode the core halts on them
    // (see `is_unsupported` / `strict_trap` below).
    assign is_nop_class = (is_special && !is_syscall && !is_jr)
                          || (is_cop0 && !is_mfc0 && !is_mtc0 && !is_rfe)
                          || (!is_special && !is_cop0
                              && !is_lui && !is_ori && !is_addiu
                              && !is_lw && !is_sw && !is_beq && !is_bne
                              && !is_j);

    // The canonical NOP is the all-zero instruction word
    // (SLL $0,$0,0). It is always treated as a NOP even in strict mode
    // so the bios_rom_stub default NOP sled doesn't look like a field
    // of traps.
    logic is_nop_instr;
    logic is_unsupported;
    logic strict_trap;
    assign is_nop_instr  = (instr == 32'd0);
    assign is_unsupported = is_nop_class && !is_nop_instr;
    assign strict_trap   = STRICT_UNSUPPORTED && is_unsupported;

    // ALU writeback value (for LUI/ORI/ADDIU)
    logic [31:0] alu_wb;
    always_comb begin
        if      (is_lui)   alu_wb = {imm16, 16'd0};
        else if (is_ori)   alu_wb = rs_val | imm_zx;
        else if (is_addiu) alu_wb = rs_val + imm_sx;
        else               alu_wb = 32'd0;
    end

    // MFC0 source value (selected by rd_idx)
    logic [31:0] cop0_read_val;
    always_comb begin
        unique case (rd_idx)
            COP0_REG_STATUS: cop0_read_val = status_word;
            COP0_REG_CAUSE:  cop0_read_val = cause_word;
            COP0_REG_EPC:    cop0_read_val = epc;
            default:         cop0_read_val = 32'd0;
        endcase
    end

    // Taken-branch / jump target selection
    logic [31:0] taken_target;
    always_comb begin
        if (is_jr)       taken_target = rs_val;
        else if (is_j)   taken_target = j_tgt;
        else             taken_target = branch_tgt;
    end

    // ------------------------------------------------------------------
    // Trace book-keeping (captured at retire)
    // ------------------------------------------------------------------

    logic [31:0] retired_pc;
    logic [31:0] retired_instr;
    logic [31:0] retired_arg2;
    logic [31:0] retired_arg3;
    logic        retired_flag_write;
    logic        retired_flag_read;
    logic        retired_flag_branch;
    logic        retired_flag_halt;
    logic        retired_flag_in_delay;
    logic        retired_flag_except;
    logic        retired_flag_rfe;
    logic        retired_flag_trap;
    logic        retire_pulse;

    // ------------------------------------------------------------------
    // Map-port drive (combinational on state)
    // ------------------------------------------------------------------

    always_comb begin
        map_rd_en   = 1'b0;
        map_rd_addr = 32'd0;
        map_wr_en   = 1'b0;
        map_wr_addr = 32'd0;
        map_wr_data = 32'd0;
        map_wr_be   = 4'd0;

        case (state)
            S_IFETCH_REQ: begin
                map_rd_en   = 1'b1;
                map_rd_addr = pc;
            end
            S_MEM_REQ: begin
                map_rd_en   = 1'b1;
                map_rd_addr = ea;
            end
            S_MEM_WRITE: begin
                map_wr_en   = 1'b1;
                map_wr_addr = ea;
                map_wr_data = rt_val;
                map_wr_be   = 4'b1111;
            end
            default: ;
        endcase
    end

    // ------------------------------------------------------------------
    // Retire helper — applies pc advance, branch queuing, and
    // exception entry at a clean instruction boundary.
    //
    // Inputs (implicit from decoded state):
    //   - is_taken_branch_or_jump, taken_target
    //   - branch_pending (current, pre-advance)
    //   - branch_target  (the pending target if any)
    //   - Status / Cause state for exception gating
    //
    // Outputs (all registered on this clock edge):
    //   - pc, branch_pending, branch_target
    //   - epc, status/cause on exception
    //   - retired_flag_except set when exception fires
    // ------------------------------------------------------------------

    task automatic retire_advance;
        logic [31:0] next_pc;
        logic        new_branch_pending;
        logic [31:0] new_branch_target;
        logic        irq_pending_masked;
        logic        exception_now;

        next_pc             = branch_pending ? branch_target : pc + 32'd4;
        new_branch_pending  = is_taken_branch_or_jump;
        new_branch_target   = taken_target;

        irq_pending_masked  = |(cause_ip & status_im);
        exception_now       = !new_branch_pending
                              && status_iec
                              && irq_pending_masked;

        if (exception_now) begin
            epc             <= next_pc;
            cause_exc_code  <= 5'h00;     // Int exception code
            status_ieo      <= status_iep;
            status_iep      <= status_iec;
            status_iec      <= 1'b0;
            status_kuo      <= status_kup;
            status_kup      <= status_kuc;
            status_kuc      <= 1'b0;
            pc              <= EXC_VECTOR;
            branch_pending  <= 1'b0;
            retired_flag_except <= 1'b1;
        end else begin
            pc              <= next_pc;
            branch_pending  <= new_branch_pending;
            if (new_branch_pending) branch_target <= new_branch_target;
            retired_flag_except <= 1'b0;
        end
    endtask

    // ------------------------------------------------------------------
    // Main FSM
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            state               <= S_IDLE;
            pc                  <= PC_RESET;
            instr               <= 32'd0;
            branch_pending      <= 1'b0;
            branch_target       <= 32'd0;
            instr_in_delay_slot <= 1'b0;

            // COP0 reset state: interrupts disabled, mask cleared.
            status_iec          <= 1'b0;
            status_iep          <= 1'b0;
            status_ieo          <= 1'b0;
            status_kuc          <= 1'b0;
            status_kup          <= 1'b0;
            status_kuo          <= 1'b0;
            status_im           <= 8'd0;
            cause_exc_code      <= 5'd0;
            cause_ip_sw         <= 8'd0;
            epc                 <= 32'd0;

            retire_pulse          <= 1'b0;
            retired_pc            <= 32'd0;
            retired_instr         <= 32'd0;
            retired_arg2          <= 32'd0;
            retired_arg3          <= 32'd0;
            retired_flag_write    <= 1'b0;
            retired_flag_read     <= 1'b0;
            retired_flag_branch   <= 1'b0;
            retired_flag_halt     <= 1'b0;
            retired_flag_in_delay <= 1'b0;
            retired_flag_except   <= 1'b0;
            retired_flag_rfe      <= 1'b0;
            retired_flag_trap     <= 1'b0;

            trap_o                <= 1'b0;
            trap_pc_o             <= 32'd0;
            trap_instr_o          <= 32'd0;

            for (int i = 0; i < 32; i++) regfile[i] <= 32'd0;
        end else begin
            retire_pulse <= 1'b0;

            case (state)
                S_IDLE: begin
                    if (go_i) state <= S_IFETCH_REQ;
                end

                S_IFETCH_REQ: state <= S_IFETCH_WAIT;

                S_IFETCH_WAIT: begin
                    if (map_rd_valid) begin
                        instr               <= map_rd_data;
                        instr_in_delay_slot <= branch_pending;
                        state               <= S_EXECUTE;
                    end
                end

                S_EXECUTE: begin
                    // Defaults for retire bookkeeping
                    retired_pc            <= pc;
                    retired_instr         <= instr;
                    retired_arg2          <= 32'd0;
                    retired_arg3          <= 32'd0;
                    retired_flag_write    <= 1'b0;
                    retired_flag_read     <= 1'b0;
                    retired_flag_branch   <= is_taken_branch_or_jump;
                    retired_flag_halt     <= 1'b0;
                    retired_flag_in_delay <= instr_in_delay_slot;
                    retired_flag_except   <= 1'b0;
                    retired_flag_rfe      <= 1'b0;
                    retired_flag_trap     <= 1'b0;

                    if (is_syscall) begin
                        // SYSCALL halts the core unconditionally; no
                        // exception vectoring in this scope.
                        retired_flag_halt <= 1'b1;
                        retire_pulse      <= 1'b1;
                        state             <= S_HALT;
                    end else if (strict_trap) begin
                        // Unsupported instruction under strict mode.
                        // Halt and latch the offending fetch; no pc
                        // advance, no regfile write, no COP0 side
                        // effect. Trap output stays asserted for the
                        // TB to inspect after halt_o rises.
                        retired_flag_trap <= 1'b1;
                        retire_pulse      <= 1'b1;
                        trap_o            <= 1'b1;
                        trap_pc_o         <= pc;
                        trap_instr_o      <= instr;
                        state             <= S_HALT;
                    end else if (is_lw) begin
                        state <= S_MEM_REQ;
                    end else if (is_sw) begin
                        state <= S_MEM_WRITE;
                    end else begin
                        // ALU / branch / COP0 / NOP: retire in this
                        // cycle. Handle per-op writebacks and COP0
                        // side effects, then advance pc.

                        if ((is_lui || is_ori || is_addiu) && (rt_idx != 5'd0))
                            regfile[rt_idx] <= alu_wb;

                        if (is_mfc0 && (rt_idx != 5'd0))
                            regfile[rt_idx] <= cop0_read_val;

                        if (is_mtc0) begin
                            unique case (rd_idx)
                                COP0_REG_STATUS: begin
                                    status_iec   <= rt_val[0];
                                    status_kuc   <= rt_val[1];
                                    status_iep   <= rt_val[2];
                                    status_kup   <= rt_val[3];
                                    status_ieo   <= rt_val[4];
                                    status_kuo   <= rt_val[5];
                                    status_im    <= rt_val[15:8];
                                end
                                COP0_REG_CAUSE:  begin
                                    // Only the software IP[1:0] bits
                                    // are writable; ExcCode is normally
                                    // written by the core on exception
                                    // entry, but allow SW override too
                                    // since the minimal scope doesn't
                                    // dispatch on ExcCode.
                                    cause_exc_code <= rt_val[6:2];
                                    cause_ip_sw[1:0] <= rt_val[9:8];
                                end
                                COP0_REG_EPC:    epc <= rt_val;
                                default: ;
                            endcase
                        end

                        if (is_rfe) begin
                            status_iec       <= status_iep;
                            status_iep       <= status_ieo;
                            status_kuc       <= status_kup;
                            status_kup       <= status_kuo;
                            retired_flag_rfe <= 1'b1;
                        end

                        // Trace payload for ALU / branch / COP0 / NOP
                        if (is_mfc0) begin
                            retired_arg2 <= {27'd0, rd_idx};
                            retired_arg3 <= cop0_read_val;
                        end else if (is_mtc0) begin
                            retired_arg2 <= {27'd0, rd_idx};
                            retired_arg3 <= rt_val;
                        end else if (is_taken_branch_or_jump) begin
                            retired_arg2 <= taken_target;
                            retired_arg3 <= 32'd0;
                        end else if (is_lui || is_ori || is_addiu) begin
                            retired_arg3 <= alu_wb;
                        end

                        retire_pulse <= 1'b1;
                        retire_advance();
                        state <= S_IFETCH_REQ;
                    end
                end

                S_MEM_REQ: state <= S_MEM_WAIT;

                S_MEM_WAIT: begin
                    if (map_rd_valid) begin
                        if (rt_idx != 5'd0) regfile[rt_idx] <= map_rd_data;

                        retired_pc            <= pc;
                        retired_instr         <= instr;
                        retired_arg2          <= ea;
                        retired_arg3          <= map_rd_data;
                        retired_flag_write    <= 1'b0;
                        retired_flag_read     <= 1'b1;
                        retired_flag_branch   <= 1'b0;
                        retired_flag_halt     <= 1'b0;
                        retired_flag_in_delay <= instr_in_delay_slot;
                        retired_flag_rfe      <= 1'b0;
                        retire_pulse          <= 1'b1;

                        retire_advance();
                        state <= S_IFETCH_REQ;
                    end
                end

                S_MEM_WRITE: begin
                    retired_pc            <= pc;
                    retired_instr         <= instr;
                    retired_arg2          <= ea;
                    retired_arg3          <= rt_val;
                    retired_flag_write    <= 1'b1;
                    retired_flag_read     <= 1'b0;
                    retired_flag_branch   <= 1'b0;
                    retired_flag_halt     <= 1'b0;
                    retired_flag_in_delay <= instr_in_delay_slot;
                    retired_flag_rfe      <= 1'b0;
                    retire_pulse          <= 1'b1;

                    retire_advance();
                    state <= S_IFETCH_REQ;
                end

                S_HALT: state <= S_HALT;

                default: state <= S_IDLE;
            endcase
        end
    end

    assign halt_o = (state == S_HALT);
    assign pc_o   = pc;

    // ------------------------------------------------------------------
    // Trace emission — one event per retire
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_IFETCH;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (retire_pulse) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_IFETCH;
            ev_arg0   <= {32'd0, retired_pc};
            ev_arg1   <= {32'd0, retired_instr};
            ev_arg2   <= {32'd0, retired_arg2};
            ev_arg3   <= {32'd0, retired_arg3};
            ev_flags  <= {24'd0,
                          retired_flag_trap,
                          retired_flag_rfe,
                          retired_flag_except,
                          retired_flag_in_delay,
                          retired_flag_halt,
                          retired_flag_branch,
                          retired_flag_read,
                          retired_flag_write};
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : iop_core_stub
