Skip to main content

rustyn64_cpu/
exec.rs

1//! `EX`-stage execution: [`Decoded`] plus operands in, result out (T-11-002).
2//!
3//! The bridge between [`mod@crate::decode`] and [`crate::alu`]. Kept separate from
4//! the pipeline so it stays a pure function of `(op, rs_val, rt_val, hi, lo)` —
5//! testable without a machine, and with no way to accidentally read register
6//! state the decode did not name.
7
8use crate::Exception;
9use crate::alu::{self, HiLo, MulDiv};
10use crate::decode::{Decoded, Op};
11use crate::mem::{LoadKind, StoreKind};
12use serde::{Deserialize, Serialize};
13
14/// What an executed instruction wants written back.
15#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
16pub enum WriteBack {
17    /// Nothing to commit.
18    #[default]
19    None,
20    /// Write `value` to general register `dest`.
21    Gpr {
22        /// Destination register index.
23        dest: u8,
24        /// Value to commit.
25        value: u64,
26    },
27    /// Write both `HI` and `LO` (every multiply and divide does).
28    HiLo(HiLo),
29    /// Write `HI` alone (`MTHI`).
30    Hi(u64),
31    /// Write `LO` alone (`MTLO`).
32    Lo(u64),
33}
34
35/// A memory access `EX` computed and `DC` must perform.
36///
37/// `EX` resolves the effective address and hands the access to `DC`; it does not
38/// touch the bus itself. That split is the point of the pipeline — `DC` is the
39/// cycle the scheduler interleaves the RCP around (ADR 0007).
40#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
41pub enum MemOp {
42    /// An aligned load into `dest`.
43    Load {
44        /// Width and signedness.
45        kind: LoadKind,
46        /// Effective address.
47        addr: u64,
48        /// Destination register.
49        dest: u8,
50    },
51    /// An aligned store of `value`.
52    Store {
53        /// Width.
54        kind: StoreKind,
55        /// Effective address.
56        addr: u64,
57        /// Value from `rt`.
58        value: u64,
59    },
60    /// A **load linked**: an aligned load that also arms the link bit and
61    /// records the physical address in `LLAddr` (UM §16 p. 453).
62    LinkedLoad {
63        /// Width and signedness.
64        kind: LoadKind,
65        /// Effective address.
66        addr: u64,
67        /// Destination register.
68        dest: u8,
69    },
70    /// A **store conditional**: stores `value` only if the link bit is set, and
71    /// writes the outcome (1 = stored, 0 = not) to `dest` either way.
72    ///
73    /// Carrying `dest` is what makes this distinct from [`MemOp::Store`] — the
74    /// flag is architecturally visible even when nothing is written to memory
75    /// (UM §16 p. 487).
76    ConditionalStore {
77        /// Width.
78        kind: StoreKind,
79        /// Effective address.
80        addr: u64,
81        /// Value from `rt`.
82        value: u64,
83        /// Destination register for the success flag.
84        dest: u8,
85    },
86    /// An **EMUX** emulator-extension operation (COP0 CO `funct` 0x20-0x3F).
87    ///
88    /// Carried to `DC` because it needs the bus: `xlog` reads a string out of
89    /// guest memory and hands it to the host. `execute` is pure and cannot.
90    Emux {
91        /// The CO `funct`: `0x20` xdetect, `0x25` xlog, `0x2C` xioctl.
92        funct: u8,
93        /// The 9-bit `code` field (bits 14:6).
94        code: u16,
95        /// `GPR[rd]` — `xlog`'s string pointer.
96        ptr: u64,
97        /// `GPR[rt]` — `xlog`'s length.
98        len: u64,
99        /// `rd`, where `xdetect` returns its capability mask.
100        dest: u8,
101    },
102    /// A `CACHE` maintenance operation.
103    ///
104    /// Carries the effective address so `DC` translates it — the instruction can
105    /// raise a TLB fault — and the 5-bit operation selector so a trace can name
106    /// what was requested. No data moves.
107    Cache {
108        /// Effective address.
109        addr: u64,
110        /// The `op` field (the instruction's `rt` slot): bits 1..=0 select the
111        /// cache, bits 4..=2 the operation.
112        op: u8,
113    },
114    /// An FP load or store (`LWC1`/`LDC1`/`SWC1`/`SDC1`).
115    ///
116    /// Kept separate from [`MemOp::Load`]/[`MemOp::Store`] because the value
117    /// moves to or from the **FP** register file, and `DC` needs to know which —
118    /// a shared variant would need a "which file" flag anyway.
119    Fp {
120        /// Which of the four forms.
121        op: Op,
122        /// Effective address.
123        addr: u64,
124        /// The FPR (`ft`).
125        ft: u8,
126    },
127    /// One half of an unaligned access. `rt` is needed for both directions: a
128    /// partial load merges into it, and a partial store merges out of it.
129    Unaligned {
130        /// Which of the eight forms.
131        op: Op,
132        /// Effective address (deliberately **not** aligned down here — `DC`
133        /// needs the low bits to know which bytes are covered).
134        addr: u64,
135        /// Current `rt`.
136        rt: u64,
137        /// Destination register, or 0 for the store forms.
138        dest: u8,
139    },
140}
141
142/// A control-flow redirect `EX` resolved.
143///
144/// The delay slot has *already been fetched* by the time `EX` resolves a branch,
145/// which is the whole point of the architectural delay slot. What `EX` decides is
146/// where the fetch *after* the delay slot goes, and whether the delay slot runs
147/// at all.
148#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
149pub struct Redirect {
150    /// Where to fetch next.
151    pub target: u64,
152    /// Nullify the already-fetched delay slot.
153    ///
154    /// True only for a **branch-likely** form that was *not* taken. An ordinary
155    /// branch executes its delay slot whether or not it is taken; a likely branch
156    /// squashes it when not taken. Confusing the two silently runs or skips one
157    /// instruction per untaken branch.
158    pub nullify_delay_slot: bool,
159}
160
161/// A **coprocessor** access that `EX` resolved but must not itself perform.
162///
163/// Despite the name this now covers COP0, the TLB **and** COP1 control (`Cop1`).
164/// They share a variant because they share the stage split below, not because
165/// they are the same unit — the name is kept for churn reasons and this note
166/// exists so it does not mislead as COP1 grows in Sprint 3.
167///
168/// The stage split is the manual's, not a convenience: UM §4.6.9 describes the
169/// CP0 bypass interlock as firing when *"an instruction which caused an
170/// exception reaches the WB stage and the subsequent instruction in the DC stage
171/// requests a read of any CP0 register"* — so a coprocessor **read happens in
172/// DC** and a **write happens in WB**. Performing both in `EX` would make that
173/// interlock unexpressible, which is the same mistake ADR 0007 exists to prevent
174/// one level up.
175#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
176pub enum Cop0Access {
177    /// Read COP0 register `src` into GPR `dest`, in `DC`.
178    Read {
179        /// COP0 register number.
180        src: u8,
181        /// GPR destination.
182        dest: u8,
183        /// 64-bit (`DMFC0`) rather than 32-bit sign-extended (`MFC0`).
184        wide: bool,
185    },
186    /// A COP1 **control** access, performed where the state lives.
187    Cop1(Cop1Access),
188    /// A TLB instruction. Performed where the TLB lives, not in `EX`.
189    Tlb(TlbOp),
190    /// `ERET` — restore `PC` from `EPC`/`ErrorEPC`, clear `EXL`/`ERL`, clear
191    /// the link bit.
192    ///
193    /// Cannot be resolved in `EX` like an ordinary redirect, because the target
194    /// comes out of COP0 rather than out of the instruction.
195    Eret,
196    /// Write `value` to COP0 register `dest`, in `WB`.
197    Write {
198        /// COP0 register number.
199        dest: u8,
200        /// Value from `rt`.
201        value: u64,
202        /// 64-bit (`DMTC0`) rather than 32-bit (`MTC0`).
203        wide: bool,
204    },
205}
206
207/// The COP1 control moves (T-12-006). Arithmetic is Sprint 3.
208#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
209pub enum Cop1Access {
210    /// `MFC1`/`DMFC1` — move an FPR to a GPR.
211    ReadFpr {
212        /// FPR number (`fs`).
213        src: u8,
214        /// GPR destination.
215        dest: u8,
216        /// 64-bit (`DMFC1`) rather than 32-bit sign-extended (`MFC1`).
217        wide: bool,
218    },
219    /// `MTC1`/`DMTC1` — move a GPR to an FPR.
220    WriteFpr {
221        /// FPR number (`fs`).
222        dest: u8,
223        /// Value from `rt`.
224        value: u64,
225        /// 64-bit (`DMTC1`) rather than 32-bit (`MTC1`).
226        wide: bool,
227    },
228    /// A COP1 arithmetic operation, performed by the pipeline because it reads
229    /// two FPRs and writes a third, and `execute` has no FPR access.
230    Arith {
231        /// Format from `rs`: 16 = single, 17 = double.
232        fmt: u8,
233        /// Operation from `funct`.
234        funct: u8,
235        /// Second source FPR.
236        ft: u8,
237        /// First source FPR.
238        fs: u8,
239        /// Destination FPR.
240        fd: u8,
241    },
242    /// `CFC1` — read control register `src` into GPR `dest`.
243    ReadControl {
244        /// COP1 control register number.
245        src: u8,
246        /// GPR destination.
247        dest: u8,
248    },
249    /// `CTC1` — write `value` to control register `dest`.
250    WriteControl {
251        /// COP1 control register number.
252        dest: u8,
253        /// Value from `rt`.
254        value: u32,
255    },
256}
257
258/// The four TLB instructions.
259#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
260pub enum TlbOp {
261    /// `TLBR` — entry → COP0 registers.
262    Read,
263    /// `TLBWI` — COP0 registers → entry `Index`.
264    WriteIndexed,
265    /// `TLBWR` — COP0 registers → entry `Random`.
266    WriteRandom,
267    /// `TLBP` — probe, reporting through `Index`.
268    Probe,
269}
270
271/// The outcome of executing one instruction in `EX`.
272#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
273pub struct Executed {
274    /// What to commit at `WB`.
275    pub write_back: WriteBack,
276    /// Extra `PCycle`s the whole pipeline stalls for (multiply/divide only).
277    pub stall_cycles: u32,
278    /// A memory access for `DC` to perform, if any.
279    pub mem: Option<MemOp>,
280    /// A control-flow redirect, if this was a taken branch, a jump, or an
281    /// untaken branch-likely.
282    pub redirect: Option<Redirect>,
283    /// A COP0 access for `DC` (read) or `WB` (write) to perform.
284    pub cop0: Option<Cop0Access>,
285    /// The GPR a jump-and-link writes its return address to, if any.
286    ///
287    /// Carried separately from `write_back` because the **value** cannot be
288    /// computed here: the link is the address of the instruction that runs
289    /// after this one's delay slot, which is `pc + 8` only when the jump is
290    /// *not itself in a delay slot*. `EX` fills it in from the live `next_pc`,
291    /// which is that address by construction. See the jump/branch helper in this
292    /// module, and accuracy ledger C-19.
293    pub link: Option<u8>,
294}
295
296/// An `Executed` that does nothing: no write-back, no stall, no access, no
297/// redirect. Base for the arms that only set one field.
298const NOTHING: Executed = Executed {
299    write_back: WriteBack::None,
300    stall_cycles: 0,
301    mem: None,
302    redirect: None,
303    cop0: None,
304    link: None,
305};
306
307/// Sign-extend a 16-bit immediate — the arithmetic and `SLT` immediate forms.
308const fn sext_imm(imm: u16) -> u64 {
309    imm as i16 as i64 as u64
310}
311
312/// A jump or taken branch: redirect, and link if the form links.
313const fn control(d: Decoded, target: u64, nullify: bool) -> Executed {
314    Executed {
315        // The linking forms save the address *after* the delay slot, so a
316        // returning `JR $31` resumes past it rather than re-executing it.
317        //
318        // **That address is not always `pc + 8`.** When the jump is itself in
319        // another jump's delay slot, its own delay slot never runs — the outer
320        // jump has already redirected — so the instruction after it is the
321        // outer *target*, and the link is `target + 4`. n64-systemtest states
322        // it directly: "JAL in delay slot writes target address+4 of original
323        // jump into delay slot".
324        //
325        // So the register is named here and the value is filled in by `EX`
326        // from the live `next_pc`, which holds exactly that address in both
327        // cases. Computing `pc + 8` here was right for the ordinary case and
328        // silently wrong for the nested one.
329        write_back: WriteBack::None,
330        link: if d.dest == 0 { None } else { Some(d.dest) },
331        stall_cycles: 0,
332        mem: None,
333        redirect: Some(Redirect {
334            target,
335            nullify_delay_slot: nullify,
336        }),
337        cop0: None,
338    }
339}
340
341/// Resolve a conditional branch. Cannot fail — a branch raises no exception.
342const fn branch(d: Decoded, taken: bool, pc: u64) -> Executed {
343    if taken {
344        // Target is relative to the DELAY SLOT's address, not this one.
345        let target = pc
346            .wrapping_add(4)
347            .wrapping_add((sext_imm(d.imm) as i64 as u64) << 2);
348        return control(d, target, false);
349    }
350    // Not taken. A branch-likely nullifies its delay slot; an ordinary branch
351    // lets it run. Either way the linking forms STILL link -- BLTZAL writes $31
352    // even when the branch is not taken, which is easy to miss.
353    Executed {
354        // The link goes through `link`, not `write_back`, for the same reason
355        // the taken path does: a NOT-taken `BGEZAL` in another jump's delay
356        // slot still links, and still links to the OUTER target + 4. Computing
357        // `pc + 8` here was the one remaining place that formula survived.
358        link: if d.dest == 0 { None } else { Some(d.dest) },
359        write_back: WriteBack::None,
360        stall_cycles: 0,
361        mem: None,
362        cop0: None,
363        redirect: if d.op.is_likely() {
364            Some(Redirect {
365                target: pc.wrapping_add(8),
366                nullify_delay_slot: true,
367            })
368        } else {
369            None
370        },
371    }
372}
373
374/// A conditional trap.
375///
376/// # Errors
377/// [`Exception::Trap`] when the condition holds.
378const fn trap_if(cond: bool) -> Result<Executed, Exception> {
379    if cond {
380        Err(Exception::Trap)
381    } else {
382        Ok(Executed {
383            link: None,
384            write_back: WriteBack::None,
385            stall_cycles: 0,
386            mem: None,
387            redirect: None,
388            cop0: None,
389        })
390    }
391}
392
393/// Zero-extend a 16-bit immediate — the *logical* immediate forms only.
394///
395/// This asymmetry is real and easy to get wrong: `ADDI` sign-extends while
396/// `ANDI`/`ORI`/`XORI` zero-extend, so `ORI $t0, $0, 0xFFFF` yields `0x0000FFFF`
397/// and not `0xFFFFFFFFFFFFFFFF`.
398const fn zext_imm(imm: u16) -> u64 {
399    imm as u64
400}
401
402/// Execute one decoded instruction.
403///
404/// `rs_val` / `rt_val` are the register values resolved through the bypass
405/// network at `EX`; `hilo` is the current multiply-divide pair; `pc` is the
406/// address of *this* instruction, needed by the control-flow forms.
407///
408/// # Errors
409///
410/// [`Exception::Overflow`] from the trapping arithmetic forms, and
411/// [`Exception::ReservedInstruction`] for anything not yet decoded. Returning
412/// `Reserved` rather than treating an unknown encoding as a `NOP` is deliberate:
413/// a missing opcode should be loud, not silently produce wrong results.
414#[allow(clippy::too_many_lines)]
415// a flat match over the opcode table
416// `rs_val`/`rt_val` trip `similar_names`, but they mirror the MIPS operand
417// naming used throughout the manual and this crate. Any pair dissimilar enough
418// to satisfy the lint would be less clear, so the names stay and the lint goes.
419#[allow(clippy::similar_names)]
420/// `fp_condition` is `FCSR.C`, which only the `BC1` family reads. It is passed
421/// in rather than reached for because this function is pure and has no view of
422/// the coprocessor state — and passing it as a parameter makes every call site a
423/// compile error until it supplies one, which is what stopped `BC1` from being
424/// wired up to a stale or defaulted condition.
425pub const fn execute(
426    d: Decoded,
427    rs_val: u64,
428    rt_val: u64,
429    hilo: HiLo,
430    pc: u64,
431    fp_condition: bool,
432) -> Result<Executed, Exception> {
433    // Most instructions write one general register with no stall.
434    macro_rules! gpr {
435        ($v:expr) => {
436            Ok(Executed {
437                link: None,
438                write_back: WriteBack::Gpr {
439                    dest: d.dest,
440                    value: $v,
441                },
442                stall_cycles: 0,
443                mem: None,
444                redirect: None,
445                cop0: None,
446            })
447        };
448    }
449    // A load: EX resolves the address, DC performs the access and produces the
450    // write-back. Nothing is committed here.
451    macro_rules! mem_load {
452        ($kind:expr) => {
453            Ok(Executed {
454                link: None,
455                write_back: WriteBack::None,
456                stall_cycles: 0,
457                mem: Some(MemOp::Load {
458                    kind: $kind,
459                    addr: rs_val.wrapping_add(sext_imm(d.imm)),
460                    dest: d.dest,
461                }),
462                redirect: None,
463                cop0: None,
464            })
465        };
466    }
467    macro_rules! mem_store {
468        ($kind:expr) => {
469            Ok(Executed {
470                link: None,
471                write_back: WriteBack::None,
472                stall_cycles: 0,
473                mem: Some(MemOp::Store {
474                    kind: $kind,
475                    addr: rs_val.wrapping_add(sext_imm(d.imm)),
476                    value: rt_val,
477                }),
478                redirect: None,
479                cop0: None,
480            })
481        };
482    }
483    // Multiply/divide write HI/LO and stall the ENTIRE pipeline for the
484    // documented count (UM Table 3-12) -- they are not background operations.
485    macro_rules! muldiv {
486        ($res:expr, $kind:expr) => {
487            Ok(Executed {
488                link: None,
489                write_back: WriteBack::HiLo($res),
490                stall_cycles: alu::muldiv_stall_cycles($kind),
491                mem: None,
492                redirect: None,
493                cop0: None,
494            })
495        };
496    }
497
498    match d.op {
499        Op::Reserved => Err(Exception::ReservedInstruction),
500
501        // --- arithmetic, register form
502        Op::Add => match alu::add(rs_val, rt_val) {
503            Ok(v) => gpr!(v),
504            Err(e) => Err(e),
505        },
506        Op::Addu => gpr!(alu::addu(rs_val, rt_val)),
507        Op::Sub => match alu::sub(rs_val, rt_val) {
508            Ok(v) => gpr!(v),
509            Err(e) => Err(e),
510        },
511        Op::Subu => gpr!(alu::subu(rs_val, rt_val)),
512        Op::Dadd => match alu::dadd(rs_val, rt_val) {
513            Ok(v) => gpr!(v),
514            Err(e) => Err(e),
515        },
516        Op::Daddu => gpr!(alu::daddu(rs_val, rt_val)),
517        Op::Dsub => match alu::dsub(rs_val, rt_val) {
518            Ok(v) => gpr!(v),
519            Err(e) => Err(e),
520        },
521        Op::Dsubu => gpr!(alu::dsubu(rs_val, rt_val)),
522        Op::Slt => gpr!(alu::slt(rs_val, rt_val)),
523        Op::Sltu => gpr!(alu::sltu(rs_val, rt_val)),
524
525        // --- logical, register form
526        Op::And => gpr!(alu::and(rs_val, rt_val)),
527        Op::Or => gpr!(alu::or(rs_val, rt_val)),
528        Op::Xor => gpr!(alu::xor(rs_val, rt_val)),
529        Op::Nor => gpr!(alu::nor(rs_val, rt_val)),
530
531        // --- immediate forms. Note the sign/zero-extension asymmetry.
532        Op::Addi => match alu::add(rs_val, sext_imm(d.imm)) {
533            Ok(v) => gpr!(v),
534            Err(e) => Err(e),
535        },
536        Op::Addiu => gpr!(alu::addu(rs_val, sext_imm(d.imm))),
537        Op::Daddi => match alu::dadd(rs_val, sext_imm(d.imm)) {
538            Ok(v) => gpr!(v),
539            Err(e) => Err(e),
540        },
541        Op::Daddiu => gpr!(alu::daddu(rs_val, sext_imm(d.imm))),
542        Op::Slti => gpr!(alu::slt(rs_val, sext_imm(d.imm))),
543        Op::Sltiu => gpr!(alu::sltu(rs_val, sext_imm(d.imm))),
544        Op::Andi => gpr!(alu::and(rs_val, zext_imm(d.imm))),
545        Op::Ori => gpr!(alu::or(rs_val, zext_imm(d.imm))),
546        Op::Xori => gpr!(alu::xor(rs_val, zext_imm(d.imm))),
547        Op::Lui => gpr!(alu::lui(d.imm)),
548
549        // --- shifts. The immediate forms shift `rt`; the variable forms take
550        // the amount from `rs` (masked by the helper).
551        Op::Sll => gpr!(alu::sll(rt_val, d.sa)),
552        Op::Srl => gpr!(alu::srl(rt_val, d.sa)),
553        Op::Sra => gpr!(alu::sra(rt_val, d.sa)),
554        Op::Dsll | Op::Dsll32 => gpr!(alu::dsll(rt_val, d.sa)),
555        Op::Dsrl | Op::Dsrl32 => gpr!(alu::dsrl(rt_val, d.sa)),
556        Op::Dsra | Op::Dsra32 => gpr!(alu::dsra(rt_val, d.sa)),
557        Op::Sllv => gpr!(alu::sll(rt_val, rs_val as u32)),
558        Op::Srlv => gpr!(alu::srl(rt_val, rs_val as u32)),
559        Op::Srav => gpr!(alu::sra(rt_val, rs_val as u32)),
560        Op::Dsllv => gpr!(alu::dsll(rt_val, rs_val as u32)),
561        Op::Dsrlv => gpr!(alu::dsrl(rt_val, rs_val as u32)),
562        Op::Dsrav => gpr!(alu::dsra(rt_val, rs_val as u32)),
563
564        // --- multiply / divide
565        Op::Mult => muldiv!(alu::mult(rs_val, rt_val), MulDiv::Mult),
566        Op::Multu => muldiv!(alu::multu(rs_val, rt_val), MulDiv::Multu),
567        Op::Div => muldiv!(alu::div(rs_val, rt_val), MulDiv::Div),
568        Op::Divu => muldiv!(alu::divu(rs_val, rt_val), MulDiv::Divu),
569        Op::Dmult => muldiv!(alu::dmult(rs_val, rt_val), MulDiv::Dmult),
570        Op::Dmultu => muldiv!(alu::dmultu(rs_val, rt_val), MulDiv::Dmultu),
571        Op::Ddiv => muldiv!(alu::ddiv(rs_val, rt_val), MulDiv::Ddiv),
572        Op::Ddivu => muldiv!(alu::ddivu(rs_val, rt_val), MulDiv::Ddivu),
573
574        // --- HI/LO moves
575        Op::Mfhi => gpr!(hilo.hi),
576        Op::Mflo => gpr!(hilo.lo),
577        Op::Mthi => Ok(Executed {
578            link: None,
579            write_back: WriteBack::Hi(rs_val),
580            stall_cycles: 0,
581            mem: None,
582            redirect: None,
583            cop0: None,
584        }),
585        Op::Mtlo => Ok(Executed {
586            link: None,
587            write_back: WriteBack::Lo(rs_val),
588            stall_cycles: 0,
589            mem: None,
590            redirect: None,
591            cop0: None,
592        }),
593
594        // --- memory. EX resolves the effective address only; DC performs the
595        // access. The address is base + SIGN-extended offset, always.
596        Op::Lb => mem_load!(LoadKind::SignedByte),
597        Op::Lbu => mem_load!(LoadKind::UnsignedByte),
598        Op::Lh => mem_load!(LoadKind::SignedHalf),
599        Op::Lhu => mem_load!(LoadKind::UnsignedHalf),
600        Op::Lw => mem_load!(LoadKind::SignedWord),
601        Op::Lwu => mem_load!(LoadKind::UnsignedWord),
602        Op::Ld => mem_load!(LoadKind::Double),
603        Op::Sb => mem_store!(StoreKind::Byte),
604        Op::Sh => mem_store!(StoreKind::Half),
605        Op::Sw => mem_store!(StoreKind::Word),
606        Op::Sd => mem_store!(StoreKind::Double),
607        // The synchronization pair. `EX` treats them exactly like their ordinary
608        // counterparts; all the link-bit behavior is in `DC`, because that is
609        // where the bus access it is conditional on happens.
610        Op::Ll => Ok(Executed {
611            link: None,
612            write_back: WriteBack::None,
613            stall_cycles: 0,
614            mem: Some(MemOp::LinkedLoad {
615                kind: LoadKind::SignedWord,
616                addr: rs_val.wrapping_add(sext_imm(d.imm)),
617                dest: d.dest,
618            }),
619            redirect: None,
620            cop0: None,
621        }),
622        Op::Lld => Ok(Executed {
623            link: None,
624            write_back: WriteBack::None,
625            stall_cycles: 0,
626            mem: Some(MemOp::LinkedLoad {
627                kind: LoadKind::Double,
628                addr: rs_val.wrapping_add(sext_imm(d.imm)),
629                dest: d.dest,
630            }),
631            redirect: None,
632            cop0: None,
633        }),
634        Op::Sc => Ok(Executed {
635            link: None,
636            write_back: WriteBack::None,
637            stall_cycles: 0,
638            mem: Some(MemOp::ConditionalStore {
639                kind: StoreKind::Word,
640                addr: rs_val.wrapping_add(sext_imm(d.imm)),
641                value: rt_val,
642                dest: d.dest,
643            }),
644            redirect: None,
645            cop0: None,
646        }),
647        Op::Scd => Ok(Executed {
648            link: None,
649            write_back: WriteBack::None,
650            stall_cycles: 0,
651            mem: Some(MemOp::ConditionalStore {
652                kind: StoreKind::Double,
653                addr: rs_val.wrapping_add(sext_imm(d.imm)),
654                value: rt_val,
655                dest: d.dest,
656            }),
657            redirect: None,
658            cop0: None,
659        }),
660        // --- control flow. `pc` is this instruction's address; the delay slot
661        // is at `pc + 4` and the instruction after it at `pc + 8`, which is what
662        // the linking forms save.
663        Op::J | Op::Jal => {
664            // The 26-bit region form keeps the top 4 bits of the DELAY SLOT's
665            // address, not this instruction's -- they differ across a 256 MiB
666            // boundary, which is exactly where a naive implementation breaks.
667            let region = pc.wrapping_add(4) & 0xFFFF_FFFF_F000_0000;
668            let target = region | ((d.target as u64) << 2);
669            Ok(control(d, target, false))
670        }
671        // JR and JALR differ only in whether decode gave them a destination:
672        // `control` links iff `d.dest != 0`, so one arm serves both.
673        Op::Jr | Op::Jalr => Ok(control(d, rs_val, false)),
674
675        // `BC1` — the FP condition is the predicate; the target arithmetic and
676        // the branch-likely nullification are shared with every other branch.
677        Op::Bc1t | Op::Bc1tl => Ok(branch(d, fp_condition, pc)),
678        Op::Bc1f | Op::Bc1fl => Ok(branch(d, !fp_condition, pc)),
679
680        Op::Beq | Op::Beql => Ok(branch(d, rs_val == rt_val, pc)),
681        Op::Bne | Op::Bnel => Ok(branch(d, rs_val != rt_val, pc)),
682        Op::Blez | Op::Blezl => Ok(branch(d, (rs_val as i64) <= 0, pc)),
683        Op::Bgtz | Op::Bgtzl => Ok(branch(d, (rs_val as i64) > 0, pc)),
684        Op::Bltz | Op::Bltzl | Op::Bltzal | Op::Bltzall => Ok(branch(d, (rs_val as i64) < 0, pc)),
685        Op::Bgez | Op::Bgezl | Op::Bgezal | Op::Bgezall => Ok(branch(d, (rs_val as i64) >= 0, pc)),
686
687        // --- traps. The comparison is 64-bit; the `*U` forms are unsigned.
688        Op::Tge => trap_if((rs_val as i64) >= (rt_val as i64)),
689        Op::Tgeu => trap_if(rs_val >= rt_val),
690        Op::Tlt => trap_if((rs_val as i64) < (rt_val as i64)),
691        Op::Tltu => trap_if(rs_val < rt_val),
692        Op::Teq => trap_if(rs_val == rt_val),
693        Op::Tne => trap_if(rs_val != rt_val),
694        // The immediate trap forms SIGN-extend, including the unsigned
695        // comparisons -- the `U` refers to the comparison, not the extension.
696        Op::Tgei => trap_if((rs_val as i64) >= (sext_imm(d.imm) as i64)),
697        Op::Tgeiu => trap_if(rs_val >= sext_imm(d.imm)),
698        Op::Tlti => trap_if((rs_val as i64) < (sext_imm(d.imm) as i64)),
699        Op::Tltiu => trap_if(rs_val < sext_imm(d.imm)),
700        Op::Teqi => trap_if(rs_val == sext_imm(d.imm)),
701        Op::Tnei => trap_if(rs_val != sext_imm(d.imm)),
702
703        // "all load/store instructions in this processor are executed in program
704        // order since the SYNC instruction is handled as a NOP" (UM §3.1). It
705        // retires normally -- what it must NOT do is raise reserved-instruction.
706        // COP0 access. `rd` is the COP0 register; `EX` only resolves which.
707        Op::Mfc0 | Op::Dmfc0 => Ok(Executed {
708            link: None,
709            cop0: Some(Cop0Access::Read {
710                src: d.rd,
711                dest: d.dest,
712                wide: matches!(d.op, Op::Dmfc0),
713            }),
714            ..NOTHING
715        }),
716        // `fs` is the COP1 control register, encoded in the `rd` field.
717        Op::Mfc1 | Op::Dmfc1 => Ok(Executed {
718            link: None,
719            cop0: Some(Cop0Access::Cop1(Cop1Access::ReadFpr {
720                src: d.rd,
721                dest: d.dest,
722                wide: matches!(d.op, Op::Dmfc1),
723            })),
724            ..NOTHING
725        }),
726        Op::Mtc1 | Op::Dmtc1 => Ok(Executed {
727            link: None,
728            cop0: Some(Cop0Access::Cop1(Cop1Access::WriteFpr {
729                dest: d.rd,
730                value: rt_val,
731                wide: matches!(d.op, Op::Dmtc1),
732            })),
733            ..NOTHING
734        }),
735        // FP loads and stores resolve their address exactly like the integer
736        // forms; only the register file they land in differs.
737        Op::Lwc1 | Op::Ldc1 | Op::Swc1 | Op::Sdc1 => Ok(Executed {
738            link: None,
739            mem: Some(MemOp::Fp {
740                op: d.op,
741                addr: rs_val.wrapping_add(sext_imm(d.imm)),
742                ft: d.rt,
743            }),
744            ..NOTHING
745        }),
746        Op::Cfc1 => Ok(Executed {
747            link: None,
748            cop0: Some(Cop0Access::Cop1(Cop1Access::ReadControl {
749                src: d.rd,
750                dest: d.dest,
751            })),
752            ..NOTHING
753        }),
754        Op::Ctc1 => Ok(Executed {
755            link: None,
756            cop0: Some(Cop0Access::Cop1(Cop1Access::WriteControl {
757                dest: d.rd,
758                value: rt_val as u32,
759            })),
760            ..NOTHING
761        }),
762        // Two distinct cases that share one behavior -- retire with no
763        // architectural effect -- and are merged only because clippy rejects
764        // identical arms:
765        //
766        // - `Cop1Unimplemented`: a valid COP1 encoding we do not implement.
767        //   Raising here would be wrong -- with `Status.CU1` SET hardware would
768        //   execute it, so pretending otherwise would make Sprint 3's arrival a
769        //   behavior change rather than an addition. The coprocessor-usable
770        //   check happens in the pipeline.
771        // - `Cop0Extension`: a COP0 CO instruction in the emux `funct`
772        //   0x20-0x3F extension range, inert on hardware (ledger C-8). Notably
773        //   the target GPR is **not** written, so a probe reads back whatever
774        //   was already there and concludes emux is absent.
775        Op::FpArith => Ok(Executed {
776            link: None,
777            cop0: Some(Cop0Access::Cop1(Cop1Access::Arith {
778                fmt: d.rs,
779                funct: (d.imm & 0x3F) as u8,
780                ft: d.rt,
781                fs: d.rd,
782                fd: d.sa as u8,
783            })),
784            ..NOTHING
785        }),
786        // Everything here is completed or refused in `EX` rather than by
787        // `execute`: the reserved-control forms trap there, and the COP2 moves
788        // need the latch, which `EX` owns. They are listed individually rather
789        // than left to a catch-all so that adding a coprocessor op forces a
790        // decision at this site.
791        // EMUX needs the bus, so it becomes a `MemOp` handled in `DC`. The
792        // `funct` is recovered from `sa`, which decode left holding it.
793        Op::Cop0Extension => Ok(Executed {
794            link: None,
795            mem: Some(MemOp::Emux {
796                funct: d.sa as u8,
797                code: d.imm,
798                ptr: rs_val,
799                len: rt_val,
800                dest: d.dest,
801            }),
802            ..NOTHING
803        }),
804        Op::Cop1Unimplemented
805        | Op::Cop2
806        | Op::Cop1ReservedControl
807        | Op::Cop2ReservedControl
808        | Op::Mfc2
809        | Op::Dmfc2
810        | Op::Mtc2 => Ok(NOTHING),
811        Op::Tlbr => Ok(Executed {
812            link: None,
813            cop0: Some(Cop0Access::Tlb(TlbOp::Read)),
814            ..NOTHING
815        }),
816        Op::Tlbwi => Ok(Executed {
817            link: None,
818            cop0: Some(Cop0Access::Tlb(TlbOp::WriteIndexed)),
819            ..NOTHING
820        }),
821        Op::Tlbwr => Ok(Executed {
822            link: None,
823            cop0: Some(Cop0Access::Tlb(TlbOp::WriteRandom)),
824            ..NOTHING
825        }),
826        Op::Tlbp => Ok(Executed {
827            link: None,
828            cop0: Some(Cop0Access::Tlb(TlbOp::Probe)),
829            ..NOTHING
830        }),
831        Op::Eret => Ok(Executed {
832            link: None,
833            cop0: Some(Cop0Access::Eret),
834            ..NOTHING
835        }),
836        Op::Mtc0 | Op::Dmtc0 => Ok(Executed {
837            link: None,
838            cop0: Some(Cop0Access::Write {
839                dest: d.rd,
840                value: rt_val,
841                wide: matches!(d.op, Op::Dmtc0),
842            }),
843            ..NOTHING
844        }),
845        // CACHE resolves its effective address like any load/store -- so it can
846        // raise a TLB fault, and DC must perform the translation -- but performs
847        // no data transfer. Modeled as a zero-width probe: see `MemOp::Cache`.
848        Op::Cache => Ok(Executed {
849            link: None,
850            mem: Some(MemOp::Cache {
851                addr: rs_val.wrapping_add(sext_imm(d.imm)),
852                op: d.rt,
853            }),
854            ..NOTHING
855        }),
856        Op::Sync => Ok(Executed {
857            link: None,
858            write_back: WriteBack::None,
859            stall_cycles: 0,
860            mem: None,
861            redirect: None,
862            cop0: None,
863        }),
864        Op::Syscall => Err(Exception::Syscall),
865        Op::Break => Err(Exception::Breakpoint),
866
867        Op::Lwl | Op::Lwr | Op::Ldl | Op::Ldr | Op::Swl | Op::Swr | Op::Sdl | Op::Sdr => {
868            Ok(Executed {
869                link: None,
870                write_back: WriteBack::None,
871                stall_cycles: 0,
872                mem: Some(MemOp::Unaligned {
873                    op: d.op,
874                    addr: rs_val.wrapping_add(sext_imm(d.imm)),
875                    rt: rt_val,
876                    dest: d.dest,
877                }),
878                redirect: None,
879                cop0: None,
880            })
881        }
882    }
883}
884
885#[cfg(test)]
886mod tests {
887    use super::*;
888    use crate::decode::decode;
889
890    #[allow(clippy::similar_names)] // mirrors the MIPS operand naming
891    fn run(word: u32, rs_val: u64, rt_val: u64) -> Result<Executed, Exception> {
892        execute(
893            decode(word),
894            rs_val,
895            rt_val,
896            HiLo { hi: 0, lo: 0 },
897            0,
898            false,
899        )
900    }
901    const fn r(funct: u32, rs: u32, rt: u32, rd: u32, sa: u32) -> u32 {
902        (rs << 21) | (rt << 16) | (rd << 11) | (sa << 6) | funct
903    }
904    const fn i(opcode: u32, rs: u32, rt: u32, imm: u16) -> u32 {
905        (opcode << 26) | (rs << 21) | (rt << 16) | imm as u32
906    }
907
908    #[test]
909    fn register_form_arithmetic_reaches_the_alu() {
910        // ADDU $3, $1, $2  with $1 = 2, $2 = 3
911        let e = run(r(0o41, 1, 2, 3, 0), 2, 3).unwrap();
912        assert_eq!(e.write_back, WriteBack::Gpr { dest: 3, value: 5 });
913        assert_eq!(e.stall_cycles, 0);
914    }
915
916    /// The immediate extension asymmetry: arithmetic sign-extends, logical
917    /// zero-extends. Getting this backwards is a classic MIPS bug — `ORI` with
918    /// 0xFFFF would produce all-ones instead of 0xFFFF.
919    #[test]
920    fn immediates_sign_extend_for_arithmetic_and_zero_extend_for_logical() {
921        // ADDIU $2, $0, -1  =>  sign-extended to 0xFFFF_FFFF_FFFF_FFFF
922        let e = run(i(0o11, 0, 2, 0xFFFF), 0, 0).unwrap();
923        assert_eq!(
924            e.write_back,
925            WriteBack::Gpr {
926                dest: 2,
927                value: u64::MAX
928            }
929        );
930        // ORI $2, $0, 0xFFFF  =>  ZERO-extended to 0x0000_0000_0000_FFFF
931        let e = run(i(0o15, 0, 2, 0xFFFF), 0, 0).unwrap();
932        assert_eq!(
933            e.write_back,
934            WriteBack::Gpr {
935                dest: 2,
936                value: 0xFFFF
937            }
938        );
939        // ANDI and XORI likewise.
940        let e = run(i(0o14, 1, 2, 0xFFFF), u64::MAX, 0).unwrap();
941        assert_eq!(
942            e.write_back,
943            WriteBack::Gpr {
944                dest: 2,
945                value: 0xFFFF
946            }
947        );
948    }
949
950    #[test]
951    fn trapping_arithmetic_returns_overflow_instead_of_a_value() {
952        // ADD $3, $1, $2 with 0x7FFF_FFFF + 1
953        assert_eq!(
954            run(r(0o40, 1, 2, 3, 0), 0x7FFF_FFFF, 1),
955            Err(Exception::Overflow)
956        );
957        // ADDU with the same inputs must NOT trap.
958        assert!(run(r(0o41, 1, 2, 3, 0), 0x7FFF_FFFF, 1).is_ok());
959    }
960
961    #[test]
962    fn multiply_and_divide_write_hi_lo_and_stall_the_documented_count() {
963        // MULT $1, $2 with 6 * 7
964        let e = run(r(0o30, 1, 2, 0, 0), 6, 7).unwrap();
965        match e.write_back {
966            WriteBack::HiLo(hl) => assert_eq!((hl.hi, hl.lo), (0, 42)),
967            other => panic!("MULT should write HI/LO, got {other:?}"),
968        }
969        assert_eq!(e.stall_cycles, 5, "UM Table 3-12");
970        // DDIV is the expensive one.
971        assert_eq!(run(r(0o36, 1, 2, 0, 0), 100, 7).unwrap().stall_cycles, 69);
972        // ...and an ordinary ALU op stalls not at all.
973        assert_eq!(run(r(0o41, 1, 2, 3, 0), 1, 1).unwrap().stall_cycles, 0);
974    }
975
976    #[test]
977    fn hi_lo_moves_read_and_write_the_pair() {
978        // MFHI $5 reads HI
979        let e = execute(
980            decode(r(0o20, 0, 0, 5, 0)),
981            0,
982            0,
983            HiLo {
984                hi: 0xDEAD,
985                lo: 0xBEEF,
986            },
987            0,
988            false,
989        )
990        .unwrap();
991        assert_eq!(
992            e.write_back,
993            WriteBack::Gpr {
994                dest: 5,
995                value: 0xDEAD
996            }
997        );
998        // MFLO $5 reads LO
999        let e = execute(
1000            decode(r(0o22, 0, 0, 5, 0)),
1001            0,
1002            0,
1003            HiLo {
1004                hi: 0xDEAD,
1005                lo: 0xBEEF,
1006            },
1007            0,
1008            false,
1009        )
1010        .unwrap();
1011        assert_eq!(
1012            e.write_back,
1013            WriteBack::Gpr {
1014                dest: 5,
1015                value: 0xBEEF
1016            }
1017        );
1018        // MTHI $1 writes HI from rs
1019        let e = execute(
1020            decode(r(0o21, 1, 0, 0, 0)),
1021            0x1234,
1022            0,
1023            HiLo { hi: 0, lo: 0 },
1024            0,
1025            false,
1026        )
1027        .unwrap();
1028        assert_eq!(e.write_back, WriteBack::Hi(0x1234));
1029    }
1030
1031    /// An unimplemented opcode must be **loud**. Treating it as a `NOP` would let
1032    /// a program run past instructions that did nothing, producing wrong results
1033    /// with no indication of why.
1034    /// **The `SRAV` instruction path shares the `SRA` erratum.**
1035    ///
1036    /// Tested through `execute` rather than by calling `alu::sra`, because the
1037    /// risk being guarded against is precisely that `Op::Srav` stops routing
1038    /// through the shared helper — gaining its own "corrected" implementation
1039    /// while `SRA` stays right. A test that calls the helper directly cannot see
1040    /// that happen, which is what an earlier version of this test did.
1041    ///
1042    /// Source: `n64brew_wiki/markdown/VR4300.md` § Known Bugs.
1043    #[test]
1044    fn the_srav_instruction_path_shares_the_sra_erratum() {
1045        let rt = 0x0123_4567_89AB_CDEF;
1046        // SRAV $3, $2, $1  -- amount from rs, value from rt.
1047        let word = r(0o07, 1, 2, 3, 0);
1048        for (amount, want) in [
1049            (1u64, 0xFFFF_FFFF_C4D5_E6F7u64),
1050            (8, 0x0000_0000_6789_ABCD),
1051            (16, 0x0000_0000_4567_89AB),
1052            (31, 0x0000_0000_0246_8ACF),
1053        ] {
1054            let e = run(word, amount, rt).unwrap();
1055            assert_eq!(
1056                e.write_back,
1057                WriteBack::Gpr {
1058                    dest: 3,
1059                    value: want
1060                },
1061                "SRAV by {amount} must leak the upper half, as SRA does"
1062            );
1063        }
1064        // And the immediate form agrees with it, through its own path.
1065        let e = run(r(0o03, 0, 2, 3, 16), 0, rt).unwrap();
1066        assert_eq!(
1067            e.write_back,
1068            WriteBack::Gpr {
1069                dest: 3,
1070                value: 0x0000_0000_4567_89AB
1071            },
1072            "SRA and SRAV must not diverge"
1073        );
1074    }
1075
1076    #[test]
1077    fn unimplemented_opcodes_raise_reserved_instruction() {
1078        // These must be encodings the VR4300 genuinely leaves UNASSIGNED, not
1079        // merely ones this project has not implemented yet. Primary opcodes
1080        // 0o34..0o37 are reserved on MIPS III, and SPECIAL funct 0o01 is unused.
1081        // Earlier revisions of this test used LW and then BEQ, and had to be
1082        // repointed each time that opcode landed -- which made the test track
1083        // implementation progress instead of the architecture.
1084        assert_eq!(
1085            run(i(0o35, 1, 2, 0), 0, 0),
1086            Err(Exception::ReservedInstruction)
1087        );
1088        assert_eq!(
1089            run(r(0o01, 1, 2, 3, 0), 0, 0),
1090            Err(Exception::ReservedInstruction)
1091        );
1092    }
1093
1094    /// `SLL $0, $0, 0` is `NOP`: it executes successfully and its write-back
1095    /// targets `$zero`, which `Regs::write` discards.
1096    #[test]
1097    fn nop_executes_and_commits_nothing() {
1098        let e = run(0, 0, 0).unwrap();
1099        assert_eq!(e.write_back, WriteBack::Gpr { dest: 0, value: 0 });
1100        assert_eq!(e.stall_cycles, 0);
1101    }
1102}