Skip to main content

rustyn64_cpu/
decode.rs

1//! MIPS III instruction decode (T-11-002).
2//!
3//! Word in, [`Decoded`] out. Pure and total: **every** 32-bit pattern decodes to
4//! something, with anything unrecognized becoming [`Op::Reserved`] rather than a
5//! panic or a silent no-op. A guest can execute arbitrary bytes, so decode must
6//! not be able to fail.
7//!
8//! # Encoding
9//!
10//! Three formats, distinguished by the primary opcode in bits 31..26:
11//!
12//! ```text
13//!  31    26 25  21 20  16 15  11 10   6 5    0
14//! ┌────────┬──────┬──────┬──────┬──────┬──────┐
15//! │ opcode │  rs  │  rt  │  rd  │  sa  │ funct│  R-type (opcode == 0, SPECIAL)
16//! ├────────┼──────┼──────┼──────┴──────┴──────┤
17//! │ opcode │  rs  │  rt  │       immediate    │  I-type
18//! ├────────┼──────┴──────┴──────┴──────┴──────┤
19//! │ opcode │              target              │  J-type
20//! └────────┴──────────────────────────────────┘
21//! ```
22//!
23//! This module covers the **integer subset** that [`crate::alu`] implements.
24//! Loads, stores, branches, jumps, COP0, COP1 and the trap family decode to
25//! [`Op::Reserved`] for now — they arrive with T-11-003 and T-11-004. That is
26//! deliberate: an unimplemented opcode that decodes to `Reserved` raises a
27//! reserved-instruction exception, which is visible, rather than executing as a
28//! `NOP`, which silently produces wrong results.
29
30use serde::{Deserialize, Serialize};
31
32/// The decoded operation. Only the integer subset so far; see the module docs.
33#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
34pub enum Op {
35    /// Not (yet) a recognized encoding — raises a reserved-instruction
36    /// exception rather than behaving as a `NOP`.
37    #[default]
38    Reserved,
39
40    // --- arithmetic, register form
41    /// `ADD rd, rs, rt` — traps on overflow.
42    Add,
43    /// `ADDU rd, rs, rt`.
44    Addu,
45    /// `SUB rd, rs, rt` — traps on overflow.
46    Sub,
47    /// `SUBU rd, rs, rt`.
48    Subu,
49    /// `DADD rd, rs, rt` — traps on overflow.
50    Dadd,
51    /// `DADDU rd, rs, rt`.
52    Daddu,
53    /// `DSUB rd, rs, rt` — traps on overflow.
54    Dsub,
55    /// `DSUBU rd, rs, rt`.
56    Dsubu,
57    /// `SLT rd, rs, rt`.
58    Slt,
59    /// `SLTU rd, rs, rt`.
60    Sltu,
61
62    // --- logical, register form
63    /// `AND rd, rs, rt`.
64    And,
65    /// `OR rd, rs, rt`.
66    Or,
67    /// `XOR rd, rs, rt`.
68    Xor,
69    /// `NOR rd, rs, rt`.
70    Nor,
71
72    // --- arithmetic / logical, immediate form
73    /// `ADDI rt, rs, imm` — traps on overflow.
74    Addi,
75    /// `ADDIU rt, rs, imm`.
76    Addiu,
77    /// `DADDI rt, rs, imm` — traps on overflow.
78    Daddi,
79    /// `DADDIU rt, rs, imm`.
80    Daddiu,
81    /// `SLTI rt, rs, imm`.
82    Slti,
83    /// `SLTIU rt, rs, imm`.
84    Sltiu,
85    /// `ANDI rt, rs, imm` — immediate is **zero**-extended.
86    Andi,
87    /// `ORI rt, rs, imm` — immediate is **zero**-extended.
88    Ori,
89    /// `XORI rt, rs, imm` — immediate is **zero**-extended.
90    Xori,
91    /// `LUI rt, imm`.
92    Lui,
93
94    // --- shifts, immediate amount
95    /// `SLL rd, rt, sa`. `SLL $0, $0, 0` is the canonical `NOP`.
96    Sll,
97    /// `SRL rd, rt, sa`.
98    Srl,
99    /// `SRA rd, rt, sa` — reproduces the VR4300 erratum.
100    Sra,
101    /// `DSLL rd, rt, sa`.
102    Dsll,
103    /// `DSRL rd, rt, sa`.
104    Dsrl,
105    /// `DSRA rd, rt, sa`.
106    Dsra,
107    /// `DSLL32 rd, rt, sa` — `sa + 32`.
108    Dsll32,
109    /// `DSRL32 rd, rt, sa` — `sa + 32`.
110    Dsrl32,
111    /// `DSRA32 rd, rt, sa` — `sa + 32`.
112    Dsra32,
113
114    // --- shifts, register amount
115    /// `SLLV rd, rt, rs`.
116    Sllv,
117    /// `SRLV rd, rt, rs`.
118    Srlv,
119    /// `SRAV rd, rt, rs` — reproduces the VR4300 erratum.
120    Srav,
121    /// `DSLLV rd, rt, rs`.
122    Dsllv,
123    /// `DSRLV rd, rt, rs`.
124    Dsrlv,
125    /// `DSRAV rd, rt, rs`.
126    Dsrav,
127
128    // --- multiply / divide and the HI/LO moves
129    /// `MULT rs, rt`.
130    Mult,
131    /// `MULTU rs, rt`.
132    Multu,
133    /// `DIV rs, rt`.
134    Div,
135    /// `DIVU rs, rt`.
136    Divu,
137    /// `DMULT rs, rt`.
138    Dmult,
139    /// `DMULTU rs, rt`.
140    Dmultu,
141    /// `DDIV rs, rt`.
142    Ddiv,
143    /// `DDIVU rs, rt`.
144    Ddivu,
145    /// `MFHI rd`.
146    Mfhi,
147    /// `MTHI rs`.
148    Mthi,
149    /// `MFLO rd`.
150    Mflo,
151    /// `MTLO rs`.
152    Mtlo,
153
154    // --- aligned loads
155    /// `LB rt, off(base)` — signed byte.
156    Lb,
157    /// `LBU rt, off(base)`.
158    Lbu,
159    /// `LH rt, off(base)` — signed halfword.
160    Lh,
161    /// `LHU rt, off(base)`.
162    Lhu,
163    /// `LW rt, off(base)` — sign-extended into the 64-bit register.
164    Lw,
165    /// `LWU rt, off(base)` — zero-extended.
166    Lwu,
167    /// `LD rt, off(base)`.
168    Ld,
169
170    // --- aligned stores
171    /// `SB rt, off(base)`.
172    Sb,
173    /// `SH rt, off(base)`.
174    Sh,
175    /// `SW rt, off(base)`.
176    Sw,
177    /// `SD rt, off(base)`.
178    Sd,
179
180    // --- the synchronization pair (UM §16, pp. 453 and 487)
181    //
182    // The VR4300 is not a multiprocessor, but it implements these "in order to
183    // maintain compatibility with VR4400 and VR4200" (UM §3.1), so they are real
184    // instructions with observable behavior, not reserved encodings.
185    /// `LL rt, off(base)` — load word, sign-extend, set `LLbit` and `LLAddr`.
186    Ll,
187    /// `LLD rt, off(base)` — the doubleword form.
188    Lld,
189    /// `SC rt, off(base)` — store word iff `LLbit`; write the outcome to `rt`.
190    Sc,
191    /// `SCD rt, off(base)` — the doubleword form.
192    Scd,
193
194    // --- the unaligned family (used in pairs; see [`crate::mem`])
195    /// `LWL rt, off(base)`.
196    Lwl,
197    /// `LWR rt, off(base)`.
198    Lwr,
199    /// `LDL rt, off(base)`.
200    Ldl,
201    /// `LDR rt, off(base)`.
202    Ldr,
203    /// `SWL rt, off(base)`.
204    Swl,
205    /// `SWR rt, off(base)`.
206    Swr,
207    /// `SDL rt, off(base)`.
208    Sdl,
209    /// `SDR rt, off(base)`.
210    Sdr,
211
212    // --- jumps
213    /// `J target` — 26-bit region form.
214    J,
215    /// `JAL target` — links to `$31`.
216    Jal,
217    /// `JR rs` — register indirect.
218    Jr,
219    /// `JALR rd, rs` — register indirect, links to `rd`.
220    Jalr,
221
222    // --- branches. The `*L` forms are **branch-likely**: when NOT taken they
223    // nullify the delay slot instead of executing it.
224    /// `BEQ rs, rt, off`.
225    Beq,
226    /// `BNE rs, rt, off`.
227    Bne,
228    /// `BLEZ rs, off`.
229    Blez,
230    /// `BGTZ rs, off`.
231    Bgtz,
232    /// `BLTZ rs, off`.
233    Bltz,
234    /// `BGEZ rs, off`.
235    Bgez,
236    /// `BLTZAL rs, off` — links to `$31`.
237    Bltzal,
238    /// `BGEZAL rs, off` — links to `$31`.
239    Bgezal,
240    /// `BEQL` — branch-likely.
241    Beql,
242    /// `BNEL` — branch-likely.
243    Bnel,
244    /// `BLEZL` — branch-likely.
245    Blezl,
246    /// `BGTZL` — branch-likely.
247    Bgtzl,
248    /// `BLTZL` — branch-likely.
249    Bltzl,
250    /// `BGEZL` — branch-likely.
251    Bgezl,
252    /// `BLTZALL` — branch-likely, links.
253    Bltzall,
254    /// `BGEZALL` — branch-likely, links.
255    Bgezall,
256    /// `BC1F off` — branch if the FP condition is **clear**.
257    Bc1f,
258    /// `BC1T off` — branch if the FP condition is **set**.
259    Bc1t,
260    /// `BC1FL` — branch-likely on a clear FP condition.
261    Bc1fl,
262    /// `BC1TL` — branch-likely on a set FP condition.
263    Bc1tl,
264
265    // --- the trap family
266    /// `TGE rs, rt`.
267    Tge,
268    /// `TGEU rs, rt`.
269    Tgeu,
270    /// `TLT rs, rt`.
271    Tlt,
272    /// `TLTU rs, rt`.
273    Tltu,
274    /// `TEQ rs, rt`.
275    Teq,
276    /// `TNE rs, rt`.
277    Tne,
278    /// `TGEI rs, imm`.
279    Tgei,
280    /// `TGEIU rs, imm`.
281    Tgeiu,
282    /// `TLTI rs, imm`.
283    Tlti,
284    /// `TLTIU rs, imm`.
285    Tltiu,
286    /// `TEQI rs, imm`.
287    Teqi,
288    /// `TNEI rs, imm`.
289    Tnei,
290
291    // --- COP0 access (T-12-001). The TLB and `ERET` encodings of this opcode
292    // are NOT here: they are separate instructions landing in T-12-002/T-12-004,
293    // and lumping them in would make `Op` claim support this crate lacks.
294    /// `CACHE op, off(base)` — a cache maintenance operation.
295    ///
296    /// Operates on **modeled cache state** as of T-11-003: both primary caches
297    /// hold real tags and data, so invalidate, write-back and the tag moves all
298    /// act. This doc said "executed as an address-translating no-op" until the
299    /// caches landed, which was true under ledger **D-5** and is not any more —
300    /// D-5 is superseded by **D-6**.
301    ///
302    /// `op`'s `rt` slot is the operation selector, not a destination. What
303    /// mattered first is that it does **not raise** — IPL3 and libdragon both
304    /// issue it, so a `Reserved` decode blocks every real ROM. See `docs/cpu.md`.
305    Cache,
306    /// A COP0 **CO-class instruction in the `funct` 0x20-0x3F extension range**,
307    /// executed as a no-op.
308    ///
309    /// # Why this is not `Reserved`
310    ///
311    /// n64-systemtest probes for the `emux` emulator by executing
312    /// `COP0 CO funct 0x20` (its `XDETECT`) and reading the result out of a GPR.
313    /// It does this from `init_allocator`, inside `entrypoint` -- **before**
314    /// `main` installs any exception handler. If a real VR4300 raised Reserved
315    /// Instruction there, the suite would derail on every N64 it has ever run
316    /// on, before printing a single line. It does not, so hardware must retire
317    /// these encodings harmlessly.
318    ///
319    /// The range is not a guess: the suite's own constant for the probe is named
320    /// `XDETECT_CODE_EXTENSIONS_20_3F`, i.e. emux claims `funct` 0x20-0x3F as
321    /// extension space precisely because the VR4300 leaves it inert.
322    ///
323    /// Decoding these to `Reserved` is what made the suite appear to hang: the
324    /// RI dispatched to an uninstalled `0x8000_0180`, ran zeros as `NOP`s into
325    /// `.text`, and faulted there instead.
326    ///
327    /// Recorded as an **inference** in the accuracy ledger (C-8), not a manual
328    /// citation -- the writeback behavior of the target GPR is untested.
329    Cop0Extension,
330    /// `CFC1 rt, fs` — read a COP1 **control** register.
331    Cfc1,
332    /// `CTC1 rt, fs` — write a COP1 **control** register.
333    Ctc1,
334    /// `MFC1 rt, fs` — move the low 32 bits of an FPR to a GPR, sign-extended.
335    Mfc1,
336    /// `DMFC1 rt, fs` — move a full 64-bit FGR to a GPR.
337    Dmfc1,
338    /// `MTC1 rt, fs` — move the low 32 bits of a GPR to an FPR.
339    Mtc1,
340    /// `DMTC1 rt, fs` — move a full 64-bit GPR to an FGR.
341    Dmtc1,
342    /// `LWC1 ft, off(base)` — load a word into an FPR.
343    Lwc1,
344    /// `LDC1 ft, off(base)` — load a doubleword into an FPR.
345    Ldc1,
346    /// `SWC1 ft, off(base)` — store an FPR word.
347    Swc1,
348    /// `SDC1 ft, off(base)` — store an FPR doubleword.
349    Sdc1,
350    /// A COP1 encoding this crate does not implement.
351    ///
352    /// Distinct from [`Op::Reserved`]: the encoding is *valid*, so it must raise
353    /// **Coprocessor Unusable** when `Status.CU1` is clear rather than Reserved
354    /// Instruction. Conflating the two sends the handler the wrong `ExcCode`.
355    Cop1Unimplemented,
356    /// `DCFC1` / `DCTC1` — the 64-bit forms of `CFC1`/`CTC1`, which the VR4300
357    /// **does not implement**.
358    ///
359    /// They are not a silent no-op and not Reserved Instruction: with `CU1`
360    /// set they raise a floating-point exception whose `FCSR.Cause` is
361    /// *only* the unimplemented-operation bit, every other cause bit cleared.
362    /// With `CU1` clear they raise Coprocessor Unusable like any COP1
363    /// instruction, and `FCSR` is left untouched.
364    ///
365    /// Distinct from [`Op::Cop1Unimplemented`] on purpose: that one really does
366    /// retire silently, and folding these into it hides a trap behind a no-op.
367    Cop1ReservedControl,
368    /// `DCFC2` / `DCTC2` — the 64-bit COP2 control moves.
369    ///
370    /// COP2 exists on the VR4300 only as a stub, and these two encodings are
371    /// not implemented at all: with `CU2` set they raise **Reserved
372    /// Instruction**, and with `CU2` clear, Coprocessor Unusable.
373    ///
374    /// Note the asymmetry with [`Op::Cop1ReservedControl`], which raises a
375    /// *floating-point* exception in the equivalent position. The two
376    /// coprocessors decline in different ways and the encodings are otherwise
377    /// identical, so this is easy to get uniformly wrong.
378    Cop2ReservedControl,
379    /// `MFC2` — read the COP2 latch's low 32 bits, sign-extended.
380    Mfc2,
381    /// `DMFC2` — read all 64 bits of the COP2 latch.
382    Dmfc2,
383    /// `MTC2` / `DMTC2` — write the COP2 latch.
384    ///
385    /// Both write the **whole 64-bit** GPR, despite `MTC2` being nominally a
386    /// 32-bit move: n64-systemtest writes a 64-bit value with `MTC2` and reads
387    /// all of it back with `DMFC2`.
388    Mtc2,
389    /// Any **COP2** encoding.
390    ///
391    /// The VR4300 has a COP2 unit, so these are architecturally *valid*
392    /// encodings. With `Status.CU2` clear they raise **Coprocessor Unusable**,
393    /// not Reserved Instruction — the same distinction as
394    /// [`Op::Cop1Unimplemented`], and for the same reason.
395    ///
396    /// Decoding them as `Reserved` is what produced n64-systemtest's
397    /// "Exception storm detected. Aborting." during `MFC2/MTC2/DMFC2/DMTC2`:
398    /// the suite expects `ExcCode 11` and got `10` five times running, which
399    /// tripped its recovery limit and truncated the whole run.
400    Cop2,
401    /// A COP1 **arithmetic** operation, format and operation carried in the
402    /// already-decoded fields: `rs` is the format, `funct` the operation, with
403    /// `rt`=ft, `rd`=fs and `sa`=fd.
404    ///
405    /// One variant rather than ~60, because the pipeline dispatches into
406    /// `crate::fpu` on `(fmt, funct)` anyway and a variant per opcode would just
407    /// be a second copy of that table.
408    FpArith,
409    /// `TLBR` — read the TLB entry `Index` names into the COP0 registers.
410    Tlbr,
411    /// `TLBWI` — write the COP0 registers into the entry **`Index`** names.
412    Tlbwi,
413    /// `TLBWR` — write them into the entry **`Random`** names.
414    Tlbwr,
415    /// `TLBP` — probe for an entry matching `EntryHi`.
416    Tlbp,
417    /// `ERET` — return from exception (UM Ch. 16, p. 434).
418    ///
419    /// Has **no delay slot** and must not be placed in one, unlike every other
420    /// control transfer in the instruction set.
421    Eret,
422    /// `MFC0 rt, rd` — 32-bit read of a COP0 register, sign-extended.
423    Mfc0,
424    /// `DMFC0 rt, rd` — 64-bit read of a COP0 register.
425    Dmfc0,
426    /// `MTC0 rt, rd` — 32-bit write to a COP0 register.
427    Mtc0,
428    /// `DMTC0 rt, rd` — 64-bit write to a COP0 register.
429    Dmtc0,
430
431    /// `SYNC` — *"handled as a NOP"* on this processor (UM §3.1).
432    ///
433    /// Not folded into [`Op::Sll`]-as-NOP: it is a distinct encoding that
434    /// compilers emit, and decoding it to [`Op::Reserved`] would raise a
435    /// reserved-instruction exception on code that runs fine on hardware.
436    Sync,
437    /// `SYSCALL`.
438    Syscall,
439    /// `BREAK`.
440    Break,
441}
442
443impl Op {
444    /// Is this one of the MIPS III **64-bit operations**?
445    ///
446    /// They raise a Reserved Instruction exception when executed in 32-bit User
447    /// or Supervisor mode. The manual states it once, as the **epsilon** marker
448    /// in the opcode table (UM Figure 16-1, Key): *"The operation code marked
449    /// with an epsilon is valid in the 64-bit mode and 32-bit Kernel mode. In
450    /// the 32-bit User or Supervisor mode, this code generates the reserved
451    /// instruction exception."*
452    ///
453    /// That legend — not the per-instruction "Exceptions" notes — is the
454    /// authority, and reading it is what caught `LWU`: the set was first built
455    /// from n64-systemtest's 28 tested instructions, which do not include it. Kernel mode may use them at any width.
456    ///
457    /// The `*32` shift forms are included on the same rule rather than by
458    /// extrapolation: `DSLL32`/`DSRL32`/`DSRA32` carry the identical exception
459    /// note in the manual, being 64-bit operations by the same definition.
460    ///
461    /// **Not** included: `DMFC0`/`DMTC0` and `DMFC1`/`DMTC1`, which the table
462    /// does mark epsilon. Doubleword moves to and from a coprocessor are also
463    /// governed by that coprocessor's own usability and reserved-encoding rules
464    /// (ledger C-18), and those raise a *different* exception; in User mode COP0
465    /// is unusable, so `CpU` is what hardware reports. n64-systemtest exercises
466    /// neither, so rather than pick an ordering on no evidence they stay out —
467    /// recorded here so the omission is a decision and not an oversight.
468    #[must_use]
469    pub const fn is_64_bit(self) -> bool {
470        matches!(
471            self,
472            Self::Dadd
473                | Self::Daddi
474                | Self::Daddiu
475                | Self::Daddu
476                | Self::Ddiv
477                | Self::Ddivu
478                | Self::Dmult
479                | Self::Dmultu
480                | Self::Dsll
481                | Self::Dsll32
482                | Self::Dsllv
483                | Self::Dsra
484                | Self::Dsra32
485                | Self::Dsrav
486                | Self::Dsrl
487                | Self::Dsrl32
488                | Self::Dsrlv
489                | Self::Dsub
490                | Self::Dsubu
491                | Self::Ld
492                | Self::Lwu
493                | Self::Ldl
494                | Self::Ldr
495                | Self::Lld
496                | Self::Scd
497                | Self::Sd
498                | Self::Sdl
499                | Self::Sdr
500        )
501    }
502
503    /// Does this instruction have a branch delay slot?
504    ///
505    /// Every jump and branch on MIPS does. The instruction *after* it executes
506    /// before the target — which is why `in_delay_slot` has to travel with the
507    /// instruction rather than live in a global flag.
508    #[must_use]
509    pub const fn has_delay_slot(self) -> bool {
510        matches!(
511            self,
512            Self::J
513                | Self::Jal
514                | Self::Jr
515                | Self::Jalr
516                | Self::Beq
517                | Self::Bne
518                | Self::Blez
519                | Self::Bgtz
520                | Self::Bltz
521                | Self::Bgez
522                | Self::Bltzal
523                | Self::Bgezal
524                | Self::Beql
525                | Self::Bnel
526                | Self::Blezl
527                | Self::Bgtzl
528                | Self::Bltzl
529                | Self::Bgezl
530                | Self::Bltzall
531                | Self::Bgezall
532                | Self::Bc1f
533                | Self::Bc1t
534                | Self::Bc1fl
535                | Self::Bc1tl
536        )
537    }
538
539    /// Does this instruction read `FCSR.C`?
540    ///
541    /// Only the `BC1` family does, which is why the condition can be interlocked
542    /// against rather than bypassed everywhere — see accuracy-ledger **R-2** for
543    /// the interlock that is still outstanding.
544    #[must_use]
545    pub const fn reads_fp_condition(self) -> bool {
546        matches!(self, Self::Bc1f | Self::Bc1t | Self::Bc1fl | Self::Bc1tl)
547    }
548
549    /// Is this a **branch-likely** form?
550    ///
551    /// When a likely branch is *not* taken it **nullifies** its delay slot — the
552    /// instruction is fetched and then squashed. An ordinary branch executes its
553    /// delay slot either way. Getting this backwards silently executes or skips
554    /// one instruction per untaken branch.
555    #[must_use]
556    pub const fn is_likely(self) -> bool {
557        matches!(
558            self,
559            Self::Beql
560                | Self::Bnel
561                | Self::Blezl
562                | Self::Bgtzl
563                | Self::Bltzl
564                | Self::Bgezl
565                | Self::Bltzall
566                | Self::Bgezall
567                | Self::Bc1fl
568                | Self::Bc1tl
569        )
570    }
571
572    /// Does this operation write `HI`/`LO` rather than a general register?
573    ///
574    /// Used for the `MFHI`/`MFLO` hazard window: a `MFHI` followed within two
575    /// instructions by anything that writes `HI` produces hardware's wrong
576    /// result, and that is a *non-interlocked* hazard (see
577    /// [`crate::alu::MFHI_MFLO_HAZARD_INSTRUCTIONS`]).
578    #[must_use]
579    pub const fn writes_hi_lo(self) -> bool {
580        matches!(
581            self,
582            Self::Mult
583                | Self::Multu
584                | Self::Div
585                | Self::Divu
586                | Self::Dmult
587                | Self::Dmultu
588                | Self::Ddiv
589                | Self::Ddivu
590                | Self::Mthi
591                | Self::Mtlo
592        )
593    }
594}
595
596/// A decoded instruction: the operation plus its raw encoded fields.
597///
598/// The fields are kept as encoded (`rs`, `rt`, `rd`, `sa`, `imm`) rather than
599/// resolved into "operands", because the load-delay interlock matches on the
600/// **fields** whether or not they are used as sources
601/// (see [`crate::pipeline::load_interlocks`]). Resolving them away would make
602/// that check impossible to state correctly.
603#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
604pub struct Decoded {
605    /// The operation.
606    pub op: Op,
607    /// `rs` field, bits 25..21.
608    pub rs: u8,
609    /// `rt` field, bits 20..16.
610    pub rt: u8,
611    /// `rd` field, bits 15..11.
612    pub rd: u8,
613    /// Shift amount, bits 10..6 — already adjusted by +32 for the `*32` forms,
614    /// so it is the **effective** amount the shift helpers expect.
615    pub sa: u32,
616    /// Immediate, bits 15..0, unextended.
617    pub imm: u16,
618    /// The general register this writes, or 0 for none. `$zero` is never
619    /// actually written, so 0 doubles as "no destination".
620    pub dest: u8,
621    /// The J-type 26-bit target field, bits 25..0. Shifted left 2 and combined
622    /// with the delay slot's region bits to form the address.
623    pub target: u32,
624}
625
626impl Decoded {
627    /// Does this instruction load into a general register?
628    ///
629    /// The load-delay interlock keys off this: only a *load* result is
630    /// unavailable in time to bypass, which is why the interlock exists at all.
631    #[must_use]
632    pub const fn is_load(self) -> bool {
633        matches!(
634            self.op,
635            Op::Lb
636                | Op::Lbu
637                | Op::Lh
638                | Op::Lhu
639                | Op::Lw
640                | Op::Lwu
641                | Op::Ld
642                | Op::Lwl
643                | Op::Lwr
644                | Op::Ldl
645                | Op::Ldr
646                | Op::Ll
647                | Op::Lld
648        )
649    }
650
651    /// Does this instruction write `rt` with a value the `DC` stage produces
652    /// *without* going to memory for it?
653    ///
654    /// `SC`/`SCD` are the only such forms: they write the success flag to `rt`
655    /// whether or not the store happens (UM §16 p. 487, *"A successful SC
656    /// instruction sets the contents of general purpose register rt to 1; an
657    /// unsuccessful SC instruction sets it to 0"*). They are therefore stores
658    /// that also have a register destination — a shape nothing else in the
659    /// integer set has, and one that a `is_load`-vs-store dichotomy silently
660    /// gets wrong in both directions.
661    ///
662    /// Deliberately **not** folded into [`Self::is_load`]: the load-delay
663    /// interlock exists because a *memory* result is not ready in time, and the
664    /// `SC` flag is not a memory result. Treating it as a load would stall a
665    /// cycle the hardware does not.
666    #[must_use]
667    pub const fn is_store_conditional(self) -> bool {
668        matches!(self.op, Op::Sc | Op::Scd)
669    }
670
671    /// Does this instruction target a floating-point register?
672    ///
673    /// Always `false` for the integer subset. Present because the load-delay
674    /// interlock does **not** cross the GPR/FPR boundary, so the check needs to
675    /// know which file a destination belongs to.
676    #[must_use]
677    pub const fn targets_fpr(self) -> bool {
678        false
679    }
680}
681
682// Primary opcodes (bits 31..26).
683const OP_SPECIAL: u32 = 0o00;
684const OP_ADDI: u32 = 0o10;
685const OP_ADDIU: u32 = 0o11;
686const OP_SLTI: u32 = 0o12;
687const OP_SLTIU: u32 = 0o13;
688const OP_ANDI: u32 = 0o14;
689const OP_ORI: u32 = 0o15;
690const OP_XORI: u32 = 0o16;
691const OP_LUI: u32 = 0o17;
692const OP_DADDI: u32 = 0o30;
693const OP_DADDIU: u32 = 0o31;
694const OP_LDL: u32 = 0o32;
695const OP_LDR: u32 = 0o33;
696const OP_LB: u32 = 0o40;
697const OP_LH: u32 = 0o41;
698const OP_LWL: u32 = 0o42;
699const OP_LW: u32 = 0o43;
700const OP_LBU: u32 = 0o44;
701const OP_LHU: u32 = 0o45;
702const OP_LWR: u32 = 0o46;
703const OP_LWU: u32 = 0o47;
704const OP_SB: u32 = 0o50;
705const OP_SH: u32 = 0o51;
706const OP_SWL: u32 = 0o52;
707const OP_SW: u32 = 0o53;
708const OP_SDL: u32 = 0o54;
709const OP_SDR: u32 = 0o55;
710const OP_SWR: u32 = 0o56;
711const OP_COP0: u32 = 0o20;
712const OP_COP1: u32 = 0o21;
713/// COP2.
714const OP_COP2: u32 = 0o22;
715const OP_LWC1: u32 = 0o61;
716const OP_LDC1: u32 = 0o65;
717const OP_SWC1: u32 = 0o71;
718const OP_SDC1: u32 = 0o75;
719const OP_CACHE: u32 = 0o57;
720const OP_LL: u32 = 0o60;
721const OP_LLD: u32 = 0o64;
722const OP_SC: u32 = 0o70;
723const OP_SCD: u32 = 0o74;
724const OP_LD: u32 = 0o67;
725const OP_SD: u32 = 0o77;
726const OP_REGIMM: u32 = 0o01;
727const OP_J: u32 = 0o02;
728const OP_JAL: u32 = 0o03;
729const OP_BEQ: u32 = 0o04;
730const OP_BNE: u32 = 0o05;
731const OP_BLEZ: u32 = 0o06;
732const OP_BGTZ: u32 = 0o07;
733const OP_BEQL: u32 = 0o24;
734const OP_BNEL: u32 = 0o25;
735const OP_BLEZL: u32 = 0o26;
736const OP_BGTZL: u32 = 0o27;
737
738/// Decode one instruction word. Total — never fails, never panics.
739#[must_use]
740#[allow(clippy::too_many_lines)] // a flat opcode table reads better than nested helpers
741pub const fn decode(word: u32) -> Decoded {
742    let opcode = word >> 26;
743    let rs = ((word >> 21) & 0x1F) as u8;
744    let rt = ((word >> 16) & 0x1F) as u8;
745    let rd = ((word >> 11) & 0x1F) as u8;
746    let sa = (word >> 6) & 0x1F;
747    let imm = (word & 0xFFFF) as u16;
748    let funct = word & 0x3F;
749
750    let base = Decoded {
751        op: Op::Reserved,
752        rs,
753        rt,
754        rd,
755        sa,
756        imm,
757        dest: 0,
758        target: word & 0x03FF_FFFF,
759    };
760
761    // R-type: the operation is in `funct`, and the destination is `rd`.
762    macro_rules! r {
763        ($op:expr) => {
764            Decoded {
765                op: $op,
766                dest: rd,
767                ..base
768            }
769        };
770    }
771    // R-type shift by 32: the encoded 5-bit `sa` means `sa + 32`.
772    macro_rules! r32 {
773        ($op:expr) => {
774            Decoded {
775                op: $op,
776                dest: rd,
777                sa: sa + 32,
778                ..base
779            }
780        };
781    }
782    // I-type: the destination is `rt`.
783    macro_rules! i {
784        ($op:expr) => {
785            Decoded {
786                op: $op,
787                dest: rt,
788                ..base
789            }
790        };
791    }
792    // Writes HI/LO, so no general-register destination.
793    macro_rules! hilo {
794        ($op:expr) => {
795            Decoded { op: $op, ..base }
796        };
797    }
798
799    match opcode {
800        OP_SPECIAL => match funct {
801            0o00 => r!(Op::Sll),
802            0o02 => r!(Op::Srl),
803            0o03 => r!(Op::Sra),
804            0o04 => r!(Op::Sllv),
805            0o06 => r!(Op::Srlv),
806            0o07 => r!(Op::Srav),
807            0o20 => r!(Op::Mfhi),
808            0o21 => hilo!(Op::Mthi),
809            0o22 => r!(Op::Mflo),
810            0o23 => hilo!(Op::Mtlo),
811            0o24 => r!(Op::Dsllv),
812            0o26 => r!(Op::Dsrlv),
813            0o27 => r!(Op::Dsrav),
814            0o30 => hilo!(Op::Mult),
815            0o31 => hilo!(Op::Multu),
816            0o32 => hilo!(Op::Div),
817            0o33 => hilo!(Op::Divu),
818            0o34 => hilo!(Op::Dmult),
819            0o35 => hilo!(Op::Dmultu),
820            0o36 => hilo!(Op::Ddiv),
821            0o37 => hilo!(Op::Ddivu),
822            0o40 => r!(Op::Add),
823            0o41 => r!(Op::Addu),
824            0o42 => r!(Op::Sub),
825            0o43 => r!(Op::Subu),
826            0o44 => r!(Op::And),
827            0o45 => r!(Op::Or),
828            0o46 => r!(Op::Xor),
829            0o47 => r!(Op::Nor),
830            0o52 => r!(Op::Slt),
831            0o53 => r!(Op::Sltu),
832            0o54 => r!(Op::Dadd),
833            0o55 => r!(Op::Daddu),
834            0o56 => r!(Op::Dsub),
835            0o57 => r!(Op::Dsubu),
836            0o10 => Decoded { op: Op::Jr, ..base },
837            0o11 => r!(Op::Jalr),
838            0o14 => Decoded {
839                op: Op::Syscall,
840                ..base
841            },
842            0o15 => Decoded {
843                op: Op::Break,
844                ..base
845            },
846            0o17 => Decoded {
847                op: Op::Sync,
848                ..base
849            },
850            0o60 => Decoded {
851                op: Op::Tge,
852                ..base
853            },
854            0o61 => Decoded {
855                op: Op::Tgeu,
856                ..base
857            },
858            0o62 => Decoded {
859                op: Op::Tlt,
860                ..base
861            },
862            0o63 => Decoded {
863                op: Op::Tltu,
864                ..base
865            },
866            0o64 => Decoded {
867                op: Op::Teq,
868                ..base
869            },
870            0o66 => Decoded {
871                op: Op::Tne,
872                ..base
873            },
874            0o70 => r!(Op::Dsll),
875            0o72 => r!(Op::Dsrl),
876            0o73 => r!(Op::Dsra),
877            0o74 => r32!(Op::Dsll32),
878            0o76 => r32!(Op::Dsrl32),
879            0o77 => r32!(Op::Dsra32),
880            _ => base,
881        },
882        OP_ADDI => i!(Op::Addi),
883        OP_ADDIU => i!(Op::Addiu),
884        OP_SLTI => i!(Op::Slti),
885        OP_SLTIU => i!(Op::Sltiu),
886        OP_ANDI => i!(Op::Andi),
887        OP_ORI => i!(Op::Ori),
888        OP_XORI => i!(Op::Xori),
889        OP_LUI => i!(Op::Lui),
890        OP_DADDI => i!(Op::Daddi),
891        OP_DADDIU => i!(Op::Daddiu),
892
893        // Loads write `rt`; stores read it and write memory, so they have no
894        // register destination.
895        OP_LB => i!(Op::Lb),
896        OP_LBU => i!(Op::Lbu),
897        OP_LH => i!(Op::Lh),
898        OP_LHU => i!(Op::Lhu),
899        OP_LW => i!(Op::Lw),
900        OP_LWU => i!(Op::Lwu),
901        OP_LD => i!(Op::Ld),
902        OP_LWL => i!(Op::Lwl),
903        OP_LWR => i!(Op::Lwr),
904        OP_LDL => i!(Op::Ldl),
905        OP_LDR => i!(Op::Ldr),
906        // LL/SC write `rt`, so they take the `i!` (destination = rt) shape even
907        // though SC also stores. A store form with a destination is unusual
908        // enough that giving SC the store shape here is the natural mistake:
909        // the success flag would then never reach the register file.
910        // COP0. The form is in `rs`; `rd` names the COP0 register, and for the
911        // move-from forms `rt` is the GPR destination.
912        OP_COP0 => {
913            let rs = ((word >> 21) & 31) as u8;
914            match rs {
915                0o00 => Decoded {
916                    op: Op::Mfc0,
917                    dest: ((word >> 16) & 31) as u8,
918                    ..base
919                },
920                0o01 => Decoded {
921                    op: Op::Dmfc0,
922                    dest: ((word >> 16) & 31) as u8,
923                    ..base
924                },
925                // The move-TO forms write COP0, not a GPR, so `dest` stays 0 --
926                // giving them a GPR destination would corrupt the register the
927                // instruction reads its value from.
928                0o04 => Decoded {
929                    op: Op::Mtc0,
930                    ..base
931                },
932                0o05 => Decoded {
933                    op: Op::Dmtc0,
934                    ..base
935                },
936                // rs bit 4 set: the CP0 "CO" forms -- TLBR/TLBWI/TLBWR/TLBP
937                // and ERET, distinguished by the `funct` field. Only ERET is
938                // implemented; the TLB forms arrive with T-12-004 and stay
939                // `Reserved` until then rather than decoding to a no-op.
940                rs if rs & 0o20 != 0 => match word & 0o77 {
941                    0o01 => Decoded {
942                        op: Op::Tlbr,
943                        ..base
944                    },
945                    0o02 => Decoded {
946                        op: Op::Tlbwi,
947                        ..base
948                    },
949                    0o06 => Decoded {
950                        op: Op::Tlbwr,
951                        ..base
952                    },
953                    0o10 => Decoded {
954                        op: Op::Tlbp,
955                        ..base
956                    },
957                    0o30 => Decoded {
958                        op: Op::Eret,
959                        ..base
960                    },
961                    // funct 0x20-0x3F: the emux extension space, inert on
962                    // hardware. See `Op::Cop0Extension`.
963                    0o40..=0o77 => Decoded {
964                        op: Op::Cop0Extension,
965                        // EMUX uses its OWN field positions, not the MIPS ones:
966                        // `rd` at 24:20, `rt` at 19:15, `code` at 14:6 (see
967                        // n64-systemtest `src/emux.rs::encode_*`). They are
968                        // unpacked into the standard slots here so `execute`
969                        // stays a pure function of `Decoded`:
970                        //   rs   <- the EMUX `rd` register (xlog's pointer)
971                        //   rt   <- the EMUX `rt` register (xlog's length)
972                        //   dest <- the EMUX `rd` register (xdetect's result)
973                        //   imm  <- the 9-bit `code`
974                        rs: ((word >> 20) & 0x1F) as u8,
975                        rt: ((word >> 15) & 0x1F) as u8,
976                        dest: ((word >> 20) & 0x1F) as u8,
977                        imm: ((word >> 6) & 0x1FF) as u16,
978                        //   sa   <- the CO `funct`, so `execute` can dispatch
979                        sa: word & 0o77,
980                        ..base
981                    },
982                    _ => base,
983                },
984                _ => base,
985            }
986        }
987        // COP2. Every encoding is valid on the VR4300, so the usability check
988        // in EX decides between executing and Coprocessor Unusable. No COP2
989        // operation is implemented, which is why one arm covers the opcode.
990        OP_COP2 => {
991            // `DCFC2` (3) and `DCTC2` (7) are not implemented; every other COP2
992            // encoding retires silently. See `Op::Cop2ReservedControl`.
993            let rs = (word >> 21) & 31;
994            match rs {
995                0o03 | 0o07 => Decoded {
996                    op: Op::Cop2ReservedControl,
997                    ..base
998                },
999                // The move-FROM forms write a GPR, so they need `dest`.
1000                0o00 => Decoded {
1001                    op: Op::Mfc2,
1002                    dest: ((word >> 16) & 31) as u8,
1003                    ..base
1004                },
1005                0o01 => Decoded {
1006                    op: Op::Dmfc2,
1007                    dest: ((word >> 16) & 31) as u8,
1008                    ..base
1009                },
1010                // `MTC2` and `DMTC2` behave identically -- see `Op::Mtc2`.
1011                0o04 | 0o05 => Decoded {
1012                    op: Op::Mtc2,
1013                    ..base
1014                },
1015                _ => Decoded {
1016                    op: Op::Cop2,
1017                    ..base
1018                },
1019            }
1020        }
1021        // COP1. The CONTROL moves (T-12-006), the DATA moves (T-13-001) and the
1022        // S/D arithmetic below are implemented; the remaining formats and the
1023        // conversions are not, and the FP load/store forms have their own
1024        // primary opcodes. Everything unhandled decodes to `Cop1Unimplemented`
1025        // rather than `Reserved`, because the encodings are valid and must raise
1026        // Coprocessor Unusable, not Reserved Instruction.
1027        OP_COP1 => {
1028            let rs = ((word >> 21) & 31) as u8;
1029            match rs {
1030                // The move-FROM forms write a GPR; the move-TO forms write an
1031                // FPR, so `dest` (a GPR index) stays 0 for those or they would
1032                // clobber the register they read their value from.
1033                0o00 => Decoded {
1034                    op: Op::Mfc1,
1035                    dest: ((word >> 16) & 31) as u8,
1036                    ..base
1037                },
1038                0o01 => Decoded {
1039                    op: Op::Dmfc1,
1040                    dest: ((word >> 16) & 31) as u8,
1041                    ..base
1042                },
1043                0o02 => Decoded {
1044                    op: Op::Cfc1,
1045                    dest: ((word >> 16) & 31) as u8,
1046                    ..base
1047                },
1048                // Format 16 = single, 17 = double. `funct` 0..=3 are
1049                // ADD/SUB/MUL/DIV, 5..=7 are ABS/MOV/NEG, and `funct` 4 is
1050                // `SQRT` (wired to `pipeline::fp_sqrt`, T-13-005) — all decode to
1051                // `Op::FpArith`. Everything above 7 (the conversions and
1052                // `C.cond.fmt`) is handled by its own decode arms.
1053                //
1054                // **`MOV` matters far more than its size suggests.** It is
1055                // funct 6, so admitting only `<= 3` made every `MOV.fmt` a
1056                // silent no-op — and the compiler emits one for each FP
1057                // argument and each FP return value. A single n64-systemtest
1058                // FP thunk contains three, so its operands were stale and its
1059                // result never left the callee. That accounted for the whole
1060                // `Result after <op>` failure block, which had been read as an
1061                // FPU arithmetic fault for nine rounds (ledger C-10).
1062                // The whole S/D funct space that exists is now wired:
1063                //
1064                // | `funct` | Operation |
1065                // | --- | --- |
1066                // | `0..=3` | `ADD` / `SUB` / `MUL` / `DIV` |
1067                // | `4` | `SQRT` |
1068                // | `5..=7` | `ABS` / `MOV` / `NEG` |
1069                // | `0o10..=0o17` | `ROUND`/`TRUNC`/`CEIL`/`FLOOR` to `.L` then `.W` |
1070                // | `0o40`/`0o41`/`0o44`/`0o45` | `CVT.S` / `CVT.D` / `CVT.W` / `CVT.L` |
1071                // | `0o60..=0o77` | `C.cond.fmt`, the low 4 bits being the condition |
1072                0o20 | 0o21
1073                    if matches!(
1074                        word & 0o77,
1075                        0..=7 | 0o10..=0o17 | 0o40 | 0o41 | 0o44 | 0o45 | 0o60..=0o77
1076                    ) =>
1077                {
1078                    Decoded {
1079                        op: Op::FpArith,
1080                        ..base
1081                    }
1082                }
1083                // The **integer** source formats, `.W` (20) and `.L` (21).
1084                //
1085                // Easy to miss: `CVT.S.W` carries its source format in the same
1086                // `fmt` field, so a decoder that only admits 16/17 leaves every
1087                // integer-to-float conversion a silent no-op — the same shape of
1088                // gap that made `MOV.fmt` cost nine rounds. Only `CVT.S` and
1089                // `CVT.D` are defined from these formats; converting an integer
1090                // to an integer is **not an instruction**.
1091                //
1092                // The to-integer functs are admitted here anyway, so that they
1093                // reach the arithmetic path and raise *Unimplemented Operation*
1094                // there. Leaving them to the `Cop1Unimplemented` fallthrough
1095                // retires them silently, which is what n64-systemtest caught:
1096                // `CVT.W.W`, `CVT.L.W` and their `.L`-source siblings expect an
1097                // exception and saw none.
1098                0o24 | 0o25 if matches!(word & 0o77, 0o10..=0o17 | 0o40 | 0o41 | 0o44 | 0o45) => {
1099                    Decoded {
1100                        op: Op::FpArith,
1101                        ..base
1102                    }
1103                }
1104                // `BC1` — branch on the FP condition. `rs = 0o10` selects the
1105                // branch family; bits 17:16 are `nd:tf`, so the four encodings
1106                // are TF (true/false) crossed with likely/not.
1107                //
1108                // `imm` carries the offset, as for every other branch, so the
1109                // target arithmetic in `exec` is shared rather than duplicated.
1110                0o10 => Decoded {
1111                    op: match (word >> 16) & 0b11 {
1112                        0b00 => Op::Bc1f,
1113                        0b01 => Op::Bc1t,
1114                        0b10 => Op::Bc1fl,
1115                        _ => Op::Bc1tl,
1116                    },
1117                    ..base
1118                },
1119                0o04 => Decoded {
1120                    op: Op::Mtc1,
1121                    ..base
1122                },
1123                0o05 => Decoded {
1124                    op: Op::Dmtc1,
1125                    ..base
1126                },
1127                0o06 => Decoded {
1128                    op: Op::Ctc1,
1129                    ..base
1130                },
1131                // `DCFC1` (3) and `DCTC1` (7): the doubleword control moves,
1132                // which this processor does not implement. See
1133                // `Op::Cop1ReservedControl` -- they trap rather than no-op.
1134                0o03 | 0o07 => Decoded {
1135                    op: Op::Cop1ReservedControl,
1136                    ..base
1137                },
1138                _ => Decoded {
1139                    op: Op::Cop1Unimplemented,
1140                    ..base
1141                },
1142            }
1143        }
1144        // CACHE writes no register: `rt` is the operation selector, not a
1145        // destination. Giving it the `i!` shape would clobber a GPR chosen by
1146        // the cache-op encoding, which is a spectacularly confusing bug.
1147        OP_CACHE => Decoded {
1148            op: Op::Cache,
1149            ..base
1150        },
1151        // The FP load/store forms. `rt` names an FPR, not a GPR, so `dest`
1152        // stays 0 -- giving them a GPR destination corrupts an integer register.
1153        OP_LWC1 => Decoded {
1154            op: Op::Lwc1,
1155            ..base
1156        },
1157        OP_LDC1 => Decoded {
1158            op: Op::Ldc1,
1159            ..base
1160        },
1161        OP_SWC1 => Decoded {
1162            op: Op::Swc1,
1163            ..base
1164        },
1165        OP_SDC1 => Decoded {
1166            op: Op::Sdc1,
1167            ..base
1168        },
1169        OP_LL => i!(Op::Ll),
1170        OP_LLD => i!(Op::Lld),
1171        OP_SC => i!(Op::Sc),
1172        OP_SCD => i!(Op::Scd),
1173        OP_SB => Decoded { op: Op::Sb, ..base },
1174        OP_SH => Decoded { op: Op::Sh, ..base },
1175        OP_SW => Decoded { op: Op::Sw, ..base },
1176        OP_SD => Decoded { op: Op::Sd, ..base },
1177        OP_SWL => Decoded {
1178            op: Op::Swl,
1179            ..base
1180        },
1181        OP_SWR => Decoded {
1182            op: Op::Swr,
1183            ..base
1184        },
1185        OP_SDL => Decoded {
1186            op: Op::Sdl,
1187            ..base
1188        },
1189        OP_SDR => Decoded {
1190            op: Op::Sdr,
1191            ..base
1192        },
1193
1194        // Jumps and branches write no general register except the linking forms,
1195        // which target $31 (or `rd` for JALR).
1196        OP_J => Decoded { op: Op::J, ..base },
1197        OP_JAL => Decoded {
1198            op: Op::Jal,
1199            dest: 31,
1200            ..base
1201        },
1202        OP_BEQ => Decoded {
1203            op: Op::Beq,
1204            ..base
1205        },
1206        OP_BNE => Decoded {
1207            op: Op::Bne,
1208            ..base
1209        },
1210        OP_BLEZ => Decoded {
1211            op: Op::Blez,
1212            ..base
1213        },
1214        OP_BGTZ => Decoded {
1215            op: Op::Bgtz,
1216            ..base
1217        },
1218        OP_BEQL => Decoded {
1219            op: Op::Beql,
1220            ..base
1221        },
1222        OP_BNEL => Decoded {
1223            op: Op::Bnel,
1224            ..base
1225        },
1226        OP_BLEZL => Decoded {
1227            op: Op::Blezl,
1228            ..base
1229        },
1230        OP_BGTZL => Decoded {
1231            op: Op::Bgtzl,
1232            ..base
1233        },
1234
1235        // REGIMM: the `rt` field selects the operation, not a register.
1236        OP_REGIMM => {
1237            let linking = matches!(rt, 0o20..=0o23);
1238            let op = match rt {
1239                0o00 => Op::Bltz,
1240                0o01 => Op::Bgez,
1241                0o02 => Op::Bltzl,
1242                0o03 => Op::Bgezl,
1243                0o10 => Op::Tgei,
1244                0o11 => Op::Tgeiu,
1245                0o12 => Op::Tlti,
1246                0o13 => Op::Tltiu,
1247                0o14 => Op::Teqi,
1248                0o16 => Op::Tnei,
1249                0o20 => Op::Bltzal,
1250                0o21 => Op::Bgezal,
1251                0o22 => Op::Bltzall,
1252                0o23 => Op::Bgezall,
1253                _ => Op::Reserved,
1254            };
1255            Decoded {
1256                op,
1257                dest: if linking { 31 } else { 0 },
1258                ..base
1259            }
1260        }
1261        _ => base,
1262    }
1263}
1264
1265#[cfg(test)]
1266mod tests {
1267    use super::*;
1268
1269    /// Assemble an R-type word, so the tests read as assembly rather than hex.
1270    const fn r(funct: u32, rs: u32, rt: u32, rd: u32, sa: u32) -> u32 {
1271        (rs << 21) | (rt << 16) | (rd << 11) | (sa << 6) | funct
1272    }
1273    /// Assemble an I-type word.
1274    const fn i(opcode: u32, rs: u32, rt: u32, imm: u16) -> u32 {
1275        (opcode << 26) | (rs << 21) | (rt << 16) | imm as u32
1276    }
1277
1278    #[test]
1279    fn fields_land_in_the_right_places() {
1280        // ADD $t0($8), $s0($16), $s1($17)
1281        let d = decode(r(0o40, 16, 17, 8, 0));
1282        assert_eq!(d.op, Op::Add);
1283        assert_eq!((d.rs, d.rt, d.rd, d.dest), (16, 17, 8, 8));
1284        // ADDI $t0, $s0, -1 -- rt is the DESTINATION for I-type, not a source
1285        let d = decode(i(0o10, 16, 8, 0xFFFF));
1286        assert_eq!(d.op, Op::Addi);
1287        assert_eq!((d.rs, d.rt, d.dest, d.imm), (16, 8, 8, 0xFFFF));
1288    }
1289
1290    /// `SLL $0, $0, 0` — the all-zero word — is the canonical `NOP`, and it must
1291    /// decode as a real instruction rather than as `Reserved`. Getting this wrong
1292    /// makes every padding byte raise an exception.
1293    #[test]
1294    fn the_all_zero_word_is_nop_not_reserved() {
1295        let d = decode(0);
1296        assert_eq!(d.op, Op::Sll);
1297        assert_eq!(d.dest, 0, "writes $zero, so it commits nothing");
1298    }
1299
1300    /// Decode is **total**: no 32-bit pattern may panic, and anything
1301    /// unrecognized becomes `Reserved` rather than silently acting as a `NOP`.
1302    /// A guest can execute arbitrary bytes.
1303    #[test]
1304    fn decode_is_total_over_every_opcode_and_funct() {
1305        // Every primary opcode with every SPECIAL funct, plus a sweep of the
1306        // rest of the encoding space.
1307        for opcode in 0..64u32 {
1308            for low in 0..64u32 {
1309                let word = (opcode << 26) | low;
1310                let _ = decode(word);
1311            }
1312        }
1313        for bit in 0..32 {
1314            let _ = decode(1u32 << bit);
1315        }
1316        // These must be encodings the VR4300 genuinely leaves UNASSIGNED, not
1317        // merely ones this project has not implemented yet. Primary opcodes
1318        // 0o34..0o37 are reserved on MIPS III, and SPECIAL funct 0o01 is unused.
1319        // Earlier revisions of this test used LW and then BEQ, and had to be
1320        // repointed each time that opcode landed -- which made the test track
1321        // implementation progress instead of the architecture.
1322        assert_eq!(decode(r(0o01, 1, 2, 3, 0)).op, Op::Reserved);
1323        assert_eq!(decode(i(0o35, 1, 2, 0)).op, Op::Reserved);
1324        assert_eq!(decode(i(0o36, 1, 2, 0)).op, Op::Reserved);
1325    }
1326
1327    /// The `*32` shift variants add 32 to the encoded 5-bit field, so `sa` is the
1328    /// effective amount the helpers expect.
1329    #[test]
1330    fn the_32_shift_variants_add_32_to_the_encoded_field() {
1331        assert_eq!(decode(r(0o70, 0, 1, 2, 5)).sa, 5, "DSLL keeps sa");
1332        assert_eq!(decode(r(0o74, 0, 1, 2, 5)).sa, 37, "DSLL32 adds 32");
1333        assert_eq!(decode(r(0o76, 0, 1, 2, 0)).sa, 32, "DSRL32 of 0 is 32");
1334        assert_eq!(decode(r(0o77, 0, 1, 2, 31)).sa, 63, "DSRA32 tops out at 63");
1335    }
1336
1337    /// Multiply/divide and `MTHI`/`MTLO` write `HI`/`LO`, so they have no general
1338    /// destination — and `dest = 0` must mean "nothing", not "$zero".
1339    #[test]
1340    fn hi_lo_writers_have_no_general_destination() {
1341        for funct in [0o30, 0o31, 0o32, 0o33, 0o34, 0o35, 0o36, 0o37, 0o21, 0o23] {
1342            let d = decode(r(funct, 1, 2, 3, 0));
1343            assert_eq!(d.dest, 0, "funct {funct:o} must not target rd");
1344            assert!(d.op.writes_hi_lo(), "funct {funct:o} should write HI/LO");
1345        }
1346        // MFHI/MFLO read them and DO have a destination.
1347        assert_eq!(decode(r(0o20, 0, 0, 9, 0)).dest, 9);
1348        assert!(!decode(r(0o20, 0, 0, 9, 0)).op.writes_hi_lo());
1349    }
1350
1351    /// `SC` is a store that nonetheless writes `rt`, so it must decode with a
1352    /// destination. The unit tests in `pipeline` construct `MemOp` directly and
1353    /// therefore cannot catch a decode that drops it — this one can.
1354    #[test]
1355    fn the_synchronization_pair_decodes_with_rt_as_the_destination() {
1356        // opcode, rs=1 (base), rt=9, imm=0x20
1357        let enc = |opcode: u32| (opcode << 26) | (1 << 21) | (9 << 16) | 0x20;
1358
1359        for (opcode, op) in [
1360            (0o60u32, Op::Ll),
1361            (0o64, Op::Lld),
1362            (0o70, Op::Sc),
1363            (0o74, Op::Scd),
1364        ] {
1365            let d = decode(enc(opcode));
1366            assert_eq!(d.op, op, "opcode {opcode:#o}");
1367            assert_eq!(d.rs, 1, "{op:?} base");
1368            assert_eq!(d.imm, 0x20, "{op:?} offset");
1369            assert_eq!(
1370                d.dest, 9,
1371                "{op:?} must write rt -- SC reports success there even when it stores nothing"
1372            );
1373        }
1374    }
1375
1376    /// The interlock must treat `LL`/`LLD` as loads (their value comes from
1377    /// memory) but `SC`/`SCD` as not loads (the flag does not).
1378    #[test]
1379    fn only_the_linked_loads_count_as_loads_for_the_interlock() {
1380        let enc = |opcode: u32| (opcode << 26) | (1 << 21) | (9 << 16);
1381        assert!(decode(enc(0o60)).is_load(), "LL");
1382        assert!(decode(enc(0o64)).is_load(), "LLD");
1383        assert!(!decode(enc(0o70)).is_load(), "SC is not a load");
1384        assert!(!decode(enc(0o74)).is_load(), "SCD is not a load");
1385        assert!(decode(enc(0o70)).is_store_conditional(), "SC");
1386        assert!(decode(enc(0o74)).is_store_conditional(), "SCD");
1387        assert!(!decode(enc(0o53)).is_store_conditional(), "SW is not");
1388    }
1389
1390    /// `SYNC` is a real encoding the VR4300 retires as a NOP (UM §3.1).
1391    /// Decoding it to `Reserved` raises a reserved-instruction exception on
1392    /// code that runs fine on hardware — compilers emit it.
1393    #[test]
1394    fn sync_decodes_to_a_nop_not_a_reserved_instruction() {
1395        let d = decode(0o17);
1396        assert_eq!(d.op, Op::Sync);
1397        assert_ne!(d.op, Op::Reserved, "SYNC must not raise");
1398        assert_eq!(d.dest, 0, "SYNC writes no register");
1399    }
1400
1401    /// COP0 CO `funct` 0x20-0x3F is the **emux extension range** and must retire
1402    /// as a no-op, not raise Reserved Instruction.
1403    ///
1404    /// n64-systemtest probes it from `init_allocator`, inside `entrypoint`,
1405    /// **before** `main` installs an exception handler -- so an RI here derails
1406    /// the suite before it prints a line. Decoding it to `Reserved` is exactly
1407    /// what made the suite appear to hang: the RI dispatched to an uninstalled
1408    /// `0x8000_0180`, ran zeros as `NOP`s into `.text`, and faulted there.
1409    #[test]
1410    fn cop0_co_extension_functs_are_inert_not_reserved() {
1411        // The exact word n64-systemtest executes: COP0, rs = CO, funct = 0x20.
1412        assert_eq!(decode(0x4280_0060).op, Op::Cop0Extension, "emux XDETECT");
1413        // The rest of the documented extension space.
1414        for funct in 0x20u32..=0x3F {
1415            let word = (0x10 << 26) | (0x10 << 21) | funct;
1416            assert_eq!(
1417                decode(word).op,
1418                Op::Cop0Extension,
1419                "COP0 CO funct {funct:#04X} is extension space"
1420            );
1421        }
1422        // Below 0x20 the real CO instructions and the genuinely reserved
1423        // encodings are unaffected.
1424        assert_eq!(decode((0x10 << 26) | (0x10 << 21) | 0x18).op, Op::Eret);
1425        assert_eq!(decode((0x10 << 26) | (0x10 << 21) | 0x02).op, Op::Tlbwi);
1426        assert_eq!(
1427            decode((0x10 << 26) | (0x10 << 21) | 0x1F).op,
1428            Op::Reserved,
1429            "funct 0x1F is still reserved -- the range starts at 0x20"
1430        );
1431    }
1432
1433    /// **COP2 encodings are valid**, so they must not decode to `Reserved`.
1434    ///
1435    /// With `Status.CU2` clear they raise Coprocessor Unusable (`ExcCode 11`);
1436    /// `Reserved` would raise `10`. n64-systemtest's `MFC2/MTC2/DMFC2/DMTC2`
1437    /// test saw `10` five times running, tripped its recovery limit, and
1438    /// aborted the entire run with "Exception storm detected".
1439    #[test]
1440    fn cop2_encodings_are_valid_not_reserved() {
1441        // None of these is `Reserved` -- that is the point of the test. The
1442        // moves now decode to themselves rather than to the generic `Cop2`,
1443        // and the doubleword control forms trap (ledger C-18), so each is
1444        // named rather than lumped together.
1445        for (rs, want) in [
1446            (0o00u32, Op::Mfc2),
1447            (0o01, Op::Dmfc2),
1448            (0o02, Op::Cop2), // CFC2 -- still a no-op
1449            (0o04, Op::Mtc2),
1450            (0o05, Op::Mtc2), // DMTC2 behaves identically to MTC2
1451            (0o06, Op::Cop2), // CTC2 -- still a no-op
1452            (0o03, Op::Cop2ReservedControl),
1453            (0o07, Op::Cop2ReservedControl),
1454        ] {
1455            let word = (0o22 << 26) | (rs << 21);
1456            assert_eq!(decode(word).op, want, "COP2 rs={rs:#o}");
1457            assert_ne!(decode(word).op, Op::Reserved, "COP2 rs={rs:#o}");
1458        }
1459        // The move-FROM forms must carry `dest`, or they write GPR 0.
1460        assert_eq!(decode((0o22 << 26) | (7 << 16)).dest, 7, "MFC2 dest = rt");
1461    }
1462
1463    /// COP1 S/D arithmetic decodes to [`Op::FpArith`], not `Cop1Unimplemented`.
1464    ///
1465    /// The FPU has been implemented in `fpu.rs` since Sprint 3, but nothing
1466    /// decoded to it, so the whole unit was unreachable from an instruction
1467    /// stream — which is why COP1 accounted for 85% of n64-systemtest's
1468    /// failures.
1469    #[test]
1470    fn cop1_single_and_double_arithmetic_decode_to_fp_arith() {
1471        // COP1, fmt, ft, fs, fd, funct
1472        let enc = |fmt: u32, funct: u32| {
1473            (0o21 << 26) | (fmt << 21) | (2 << 16) | (3 << 11) | (4 << 6) | funct
1474        };
1475        for fmt in [0o20u32, 0o21] {
1476            for funct in 0..=3u32 {
1477                let d = decode(enc(fmt, funct));
1478                assert_eq!(d.op, Op::FpArith, "fmt {fmt:#o} funct {funct}");
1479                assert_eq!(d.rs, fmt as u8, "rs carries the format");
1480                assert_eq!(d.rt, 2, "rt = ft");
1481                assert_eq!(d.rd, 3, "rd = fs");
1482                assert_eq!(d.sa, 4, "sa = fd");
1483            }
1484        }
1485        // funct 4 is `SQRT`, a different operation -- it must not be swept
1486        // into the arithmetic range as a wrong ADD.
1487        assert_eq!(decode(enc(0o20, 4)).op, Op::FpArith);
1488    }
1489
1490    /// **The compares and conversions must decode.** They are implemented in
1491    /// `fpu.rs` and were unreachable for the same reason `MOV` was: the decode
1492    /// arm admitted only `funct 0..=3` and `5..=7`.
1493    ///
1494    /// Enumerated rather than spot-checked. The failure mode here is a *gap* in
1495    /// a range, and a gap is exactly what a single representative encoding does
1496    /// not find.
1497    #[test]
1498    fn the_compares_and_conversions_decode_rather_than_no_op() {
1499        let enc = |fmt: u32, funct: u32| {
1500            (0o21 << 26) | (fmt << 21) | (2 << 16) | (3 << 11) | (4 << 6) | funct
1501        };
1502        for fmt in [0o20u32, 0o21] {
1503            // ROUND/TRUNC/CEIL/FLOOR to .L (8..=11) then to .W (12..=15).
1504            for funct in 0o10..=0o17u32 {
1505                assert_eq!(
1506                    decode(enc(fmt, funct)).op,
1507                    Op::FpArith,
1508                    "fmt {fmt:#o} funct {funct:#o}"
1509                );
1510            }
1511            // CVT.S / CVT.D / CVT.W / CVT.L.
1512            for funct in [0o40u32, 0o41, 0o44, 0o45] {
1513                assert_eq!(
1514                    decode(enc(fmt, funct)).op,
1515                    Op::FpArith,
1516                    "CVT funct {funct:#o}"
1517                );
1518            }
1519            // All sixteen C.cond.fmt forms.
1520            for funct in 0o60..=0o77u32 {
1521                assert_eq!(
1522                    decode(enc(fmt, funct)).op,
1523                    Op::FpArith,
1524                    "C.cond funct {funct:#o}"
1525                );
1526            }
1527        }
1528        // The INTEGER source formats. Easy to miss, because `CVT.S.W` carries
1529        // its source format in the same field as `.S`/`.D` — a decoder that
1530        // admits only 16/17 leaves every integer-to-float conversion a no-op.
1531        for fmt in [0o24u32, 0o25] {
1532            for funct in [0o40u32, 0o41] {
1533                assert_eq!(
1534                    decode(enc(fmt, funct)).op,
1535                    Op::FpArith,
1536                    "fmt {fmt:#o} funct {funct:#o}"
1537                );
1538            }
1539        }
1540        // `SQRT` (funct 4) is wired too, as of T-13-005 — it is a distinct
1541        // operation, so this also guards against it being swept into the
1542        // arithmetic range and silently becoming an `ADD`.
1543        let d = decode(enc(0o20, 4));
1544        assert_eq!(d.op, Op::FpArith, "SQRT decodes");
1545        assert_eq!(d.rd, 3, "and carries fs");
1546    }
1547
1548    /// **`MOV.fmt` (funct 6) must decode.** With the arm admitting only
1549    /// `funct <= 3` it did not, and executed as a silent no-op.
1550    ///
1551    /// That is not a cosmetic gap. The compiler emits `MOV.fmt` for every FP
1552    /// argument and every FP return value, so a no-op left callees reading
1553    /// stale operands and callers reading a register the callee never wrote.
1554    /// It cost the whole `Result after <op>` block in n64-systemtest and nine
1555    /// rounds of investigation aimed at the FPU (ledger C-10) — the arithmetic
1556    /// was correct the entire time.
1557    ///
1558    /// ABS (5) and NEG (7) share the arm and are covered here for the same
1559    /// reason: nothing fails when a move quietly does nothing.
1560    #[test]
1561    fn abs_mov_and_neg_decode_rather_than_silently_doing_nothing() {
1562        let enc = |fmt: u32, funct: u32| (0o21 << 26) | (fmt << 21) | (3 << 11) | (4 << 6) | funct;
1563        for fmt in [0o20u32, 0o21] {
1564            for funct in 5..=7u32 {
1565                let d = decode(enc(fmt, funct));
1566                assert_eq!(
1567                    d.op,
1568                    Op::FpArith,
1569                    "fmt {fmt:#o} funct {funct} must not be a no-op"
1570                );
1571                assert_eq!(d.rd, 3, "rd = fs");
1572                assert_eq!(d.sa, 4, "sa = fd");
1573            }
1574        }
1575        // The exact encoding the correlated capture found in the delay slot of
1576        // the failing test's `jr $ra`: `MOV.S $f0, $f4`.
1577        let d = decode(0x4600_2006);
1578        assert_eq!(d.op, Op::FpArith, "MOV.S $f0, $f4 must decode");
1579        assert_eq!((d.rs, d.rd, d.sa), (0o20, 4, 0), "fmt=S, fs=4, fd=0");
1580    }
1581}