Files
retroDE_ps2/rtl/iop/iop_core_stub.sv
T
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

712 lines
28 KiB
Systemverilog

// 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