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