Skip to main content

rustyn64_rsp/
su.rs

1//! The **scalar unit** — the RSP's MIPS-like integer core (T-21-004).
2//!
3//! Close enough to a 32-bit R4000 that standard MIPS documentation covers most
4//! of it, so what this module records is the *differences* (N64brew *RSP CPU
5//! Core* §Scalar unit):
6//!
7//! - **No multiply/divide unit.** `MULT`, `MULTU`, `DIV`, `DIVU`, `MFHI`,
8//!   `MFLO`, `MTHI`, `MTLO` do not exist, and neither does `HI`/`LO`.
9//! - **No 64-bit anything.** The registers are 32 bits, so every `D*` opcode is
10//!   absent, as are `LD`/`SD`/`LDL`/`SDL`.
11//! - **No misaligned-access opcodes**, because none are needed: `LW` and `SW`
12//!   work at *any* address. `LWL`/`LWR`/`SWL`/`SWR` are absent.
13//! - **No traps or exceptions at all** — no interrupts, no `SYSCALL`, no `TGE`
14//!   family. `BREAK` exists and halts the core instead of raising anything.
15//! - **No likely branches** (`BEQL`, `BLEZL`, …).
16//!
17//! # The two rules that catch a MIPS core reused wholesale
18//!
19//! **The PC is 12 bits and wraps.** Every high bit of a branch or jump target
20//! is discarded, and running off the end of IMEM at `0xFFC` continues at
21//! `0x000` rather than faulting. n64-systemtest's `RSP Wrap around` places two
22//! `nop`s at `0xFF8` and a `BREAK` at `0x000`, runs from `0xFF8`, and expects
23//! to stop at `0x4`.
24//!
25//! **Misaligned data accesses are correct, not faults.** A `LW` at `0x001`
26//! returns the four bytes at `0x1..=0x4`; the address is masked to 12 bits and
27//! each byte wraps inside DMEM independently, so a word read at `0xFFE` takes
28//! two bytes from the end and two from the start. On the VR4300 the same access
29//! is an `AddressError`, which makes this the single easiest place to get the
30//! RSP wrong by reusing CPU code.
31
32use crate::Rsp;
33use crate::sp::{self, STATUS_INTBREAK};
34use serde::{Deserialize, Serialize};
35
36/// What one scalar step asked the rest of the machine to do.
37///
38/// The RSP cannot reach RDRAM or the MI itself — it does not own them — so it
39/// reports rather than acts, and `rustyn64-core::Bus` carries it out. This is
40/// the same shape the PI engine uses, and it is what lets the RSP be stepped in
41/// isolation.
42#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
43pub struct StepResult {
44    /// A transfer an `MTC0` to a length register started.
45    pub dma: Option<sp::Dma>,
46    /// A change to the MI's SP interrupt line: `Some(true)` raises it,
47    /// `Some(false)` acknowledges it, `None` leaves it alone.
48    ///
49    /// Three states rather than two, because the RSP acknowledges its **own**
50    /// interrupt by writing `CLR_INTR` through `MTC0` — with a plain `bool`,
51    /// "clear the line" and "this step said nothing about the line" are the
52    /// same value, and the acknowledgment is silently dropped.
53    pub interrupt_change: Option<bool>,
54    /// An `MTC0` to a DP command register (`c8`–`c15`), reported as a
55    /// **DPC word offset** `0`–`7` (`0`=`DP_START`, `1`=`DP_END`,
56    /// `2`=`DP_CURRENT`, `3`=`DP_STATUS`, `4`–`7` the clock/busy counters) and
57    /// the value.
58    ///
59    /// Those COP0 registers *are* the RDP's command registers, but the RSP
60    /// crate may not depend on `rustyn64-rdp` (the crate graph forbids that
61    /// chip→chip edge — `docs/architecture.md`). So the write is reported here
62    /// and `rustyn64-core::Bus` forwards it to `Rdp::dpc_write`, exactly as
63    /// `dma` and `interrupt_change` are carried out by the owner.
64    pub dp_write: Option<(u8, u32)>,
65}
66
67/// Decoded fields, named as the MIPS encoding names them.
68#[derive(Serialize, Deserialize)]
69struct Fields {
70    op: u32,
71    rs: usize,
72    rt: usize,
73    rd: usize,
74    sa: u32,
75    funct: u32,
76    imm: u32,
77    target: u32,
78}
79
80const fn decode(word: u32) -> Fields {
81    Fields {
82        op: word >> 26,
83        rs: ((word >> 21) & 31) as usize,
84        rt: ((word >> 16) & 31) as usize,
85        rd: ((word >> 11) & 31) as usize,
86        sa: (word >> 6) & 31,
87        funct: word & 63,
88        imm: word & 0xFFFF,
89        target: word & 0x03FF_FFFF,
90    }
91}
92
93/// Sign-extend a 16-bit immediate to 32 bits.
94const fn sext(imm: u32) -> u32 {
95    ((imm as u16).cast_signed() as i32).cast_unsigned()
96}
97
98impl Rsp {
99    /// Read one scalar register, with `r0` pinned to zero.
100    ///
101    /// Public so the vector load/store family can compute its base address —
102    /// those instructions live in [`crate::vu`] but address DMEM through a GPR.
103    #[must_use]
104    pub const fn r(&self, i: usize) -> u32 {
105        if i == 0 { 0 } else { self.su_regs[i] }
106    }
107
108    /// Write one scalar register; writes to `r0` are discarded.
109    ///
110    /// Public under this name so the VU's move instructions can reach it —
111    /// `MFC2` and `CFC2` write a GPR, and they live in [`crate::vu`].
112    pub const fn set_su(&mut self, i: usize, v: u32) {
113        self.set_r(i, v);
114    }
115
116    /// Write one register; writes to `r0` are discarded.
117    const fn set_r(&mut self, i: usize, v: u32) {
118        if i != 0 {
119            self.su_regs[i] = v;
120        }
121    }
122
123    /// Read a byte of DMEM. The address is 12 bits — everything above is
124    /// ignored, so a load never escapes the 4 KiB and never faults.
125    const fn dmem_read(&self, addr: u32) -> u8 {
126        self.dmem[(addr & 0xFFF) as usize]
127    }
128
129    /// Write a byte of DMEM, under the same 12-bit rule.
130    const fn dmem_write(&mut self, addr: u32, val: u8) {
131        self.dmem[(addr & 0xFFF) as usize] = val;
132    }
133
134    /// Read `n` bytes big-endian from DMEM, each byte wrapping independently.
135    ///
136    /// Byte-at-a-time rather than a word fetch because the access may be
137    /// misaligned *and* may straddle the end of DMEM — `LWU` from `0xFFD` takes
138    /// three bytes from the end and one from the start, which n64-systemtest
139    /// checks directly.
140    fn dmem_load(&self, addr: u32, n: u32) -> u32 {
141        let mut v = 0u32;
142        for i in 0..n {
143            v = (v << 8) | u32::from(self.dmem_read(addr.wrapping_add(i)));
144        }
145        v
146    }
147
148    /// Store the low `n` bytes of `val` big-endian into DMEM.
149    fn dmem_store(&mut self, addr: u32, n: u32, val: u32) {
150        for i in 0..n {
151            let shift = 8 * (n - 1 - i);
152            self.dmem_write(addr.wrapping_add(i), (val >> shift) as u8);
153        }
154    }
155
156    /// Fetch the instruction word at `pc` from IMEM.
157    ///
158    /// IMEM is a separate 4 KiB (the RSP is a Harvard machine), and the PC is
159    /// masked to 12 bits so the fetch wraps rather than running off the end.
160    fn imem_word(&self, pc: u32) -> u32 {
161        let base = (pc & 0xFFF) as usize;
162        u32::from_be_bytes([
163            self.imem[base],
164            self.imem[(base + 1) & 0xFFF],
165            self.imem[(base + 2) & 0xFFF],
166            self.imem[(base + 3) & 0xFFF],
167        ])
168    }
169
170    /// Execute one scalar instruction, if the core is running.
171    ///
172    /// Returns what the step asked the machine to do; see [`StepResult`].
173    pub fn su_step(&mut self) -> StepResult {
174        let mut out = StepResult::default();
175        if self.sp.halted() {
176            return out;
177        }
178        self.count_retired();
179
180        let pc = self.sp.pc();
181        let word = self.imem_word(pc);
182        // The branch target latched by the *previous* instruction. Taken now,
183        // because the instruction being executed is that branch's delay slot.
184        let after_delay = self.branch.take();
185        let sequential = pc.wrapping_add(4) & 0xFFC;
186
187        let d = decode(word);
188        let mut halt_at = None;
189
190        match d.op {
191            // SPECIAL — the register-form ALU, shifts, jumps and BREAK.
192            0 => halt_at = self.special(&d, sequential),
193            // REGIMM: the four conditional branches on rs's sign.
194            1 => {
195                let v = self.r(d.rs).cast_signed();
196                let take = match d.rt {
197                    0o00 | 0o20 => v < 0,
198                    0o01 | 0o21 => v >= 0,
199                    _ => false,
200                };
201                // The AL forms link unconditionally, even when not taken.
202                if d.rt & 0o20 != 0 {
203                    self.set_r(31, sequential.wrapping_add(4) & 0xFFC);
204                }
205                if take {
206                    self.branch = Some(branch_target(sequential, d.imm));
207                }
208            }
209            0o02 => self.branch = Some((d.target << 2) & 0xFFC),
210            0o03 => {
211                self.set_r(31, sequential.wrapping_add(4) & 0xFFC);
212                self.branch = Some((d.target << 2) & 0xFFC);
213            }
214            0o04 => {
215                if self.r(d.rs) == self.r(d.rt) {
216                    self.branch = Some(branch_target(sequential, d.imm));
217                }
218            }
219            0o05 => {
220                if self.r(d.rs) != self.r(d.rt) {
221                    self.branch = Some(branch_target(sequential, d.imm));
222                }
223            }
224            0o06 => {
225                if self.r(d.rs).cast_signed() <= 0 {
226                    self.branch = Some(branch_target(sequential, d.imm));
227                }
228            }
229            0o07 => {
230                if self.r(d.rs).cast_signed() > 0 {
231                    self.branch = Some(branch_target(sequential, d.imm));
232                }
233            }
234            // ADDI and ADDIU likewise coincide: no overflow trap exists.
235            0o10 | 0o11 => self.set_r(d.rt, self.r(d.rs).wrapping_add(sext(d.imm))),
236            0o12 => self.set_r(
237                d.rt,
238                u32::from(self.r(d.rs).cast_signed() < sext(d.imm).cast_signed()),
239            ),
240            0o13 => self.set_r(d.rt, u32::from(self.r(d.rs) < sext(d.imm))),
241            0o14 => self.set_r(d.rt, self.r(d.rs) & d.imm),
242            0o15 => self.set_r(d.rt, self.r(d.rs) | d.imm),
243            0o16 => self.set_r(d.rt, self.r(d.rs) ^ d.imm),
244            0o17 => self.set_r(d.rt, d.imm << 16),
245            // COP0 — the SP and DP register files, reached by MFC0/MTC0.
246            0o20 => match d.rs {
247                0o00 => {
248                    let v = self.cop0_read(d.rd as u32);
249                    self.set_r(d.rt, v);
250                }
251                0o04 => out = self.cop0_write(d.rd as u32, self.r(d.rt)),
252                _ => {}
253            },
254            // Loads. `LW` and `LWU` are the same operation on a 32-bit machine:
255            // there is no upper half for the sign to extend into.
256            0o40 => self.set_r(
257                d.rt,
258                ((self.load(&d, 1) as u8).cast_signed() as i32).cast_unsigned(),
259            ),
260            0o41 => self.set_r(
261                d.rt,
262                ((self.load(&d, 2) as u16).cast_signed() as i32).cast_unsigned(),
263            ),
264            0o43 | 0o47 => self.set_r(d.rt, self.load(&d, 4)),
265            0o44 => self.set_r(d.rt, self.load(&d, 1)),
266            0o45 => self.set_r(d.rt, self.load(&d, 2)),
267            0o50 => self.store(&d, 1),
268            0o51 => self.store(&d, 2),
269            0o53 => self.store(&d, 4),
270            // COP2 — both the SU/VU moves and the computational vector
271            // instructions (bit 25 set) dispatch through `cop2` to the VU.
272            0o22 => self.cop2(&d),
273            // The vector load/store family. `opcode` is at 15..11 where `rd`
274            // sits, `element` at 10..7, and the offset is a **signed 7-bit**
275            // field -- not the 16-bit immediate an ordinary load carries.
276            0o62 | 0o72 => self.vector_memory(&d),
277            _ => {}
278        }
279
280        // `r0` is pinned; a write may have slipped through a path above.
281        self.su_regs[0] = 0;
282
283        if let Some(next) = halt_at {
284            // A BREAK in a *taken* branch's delay slot halts at the branch
285            // target, not the sequential address: the branch redirect that was
286            // already latched still wins. n64-systemtest pins this -- `beq`
287            // taken into a `break` leaves PC at the target (0x1C), while the
288            // same `break` after an untaken branch leaves it sequential (0x8).
289            self.sp.set_pc(after_delay.unwrap_or(next));
290            self.sp.set_halted(true);
291            self.sp.set_broke(true);
292            if self.sp.status() & STATUS_INTBREAK != 0 {
293                out.interrupt_change = Some(true);
294            }
295            return out;
296        }
297
298        self.sp.set_pc(after_delay.unwrap_or(sequential));
299        out
300    }
301
302    /// The `SPECIAL` opcode group: register-form ALU, shifts, `JR`/`JALR` and
303    /// `BREAK`. Split out so [`Rsp::su_step`] stays readable, not because the
304    /// group is separable — it is one arm of the same decode.
305    ///
306    /// Returns the PC to halt at when the instruction was a `BREAK`.
307    fn special(&mut self, d: &Fields, sequential: u32) -> Option<u32> {
308        let mut halt_at = None;
309        match d.funct {
310            0o00 => self.set_r(d.rd, self.r(d.rt) << d.sa),
311            0o02 => self.set_r(d.rd, self.r(d.rt) >> d.sa),
312            0o03 => self.set_r(d.rd, (self.r(d.rt).cast_signed() >> d.sa).cast_unsigned()),
313            0o04 => self.set_r(d.rd, self.r(d.rt) << (self.r(d.rs) & 31)),
314            0o06 => self.set_r(d.rd, self.r(d.rt) >> (self.r(d.rs) & 31)),
315            0o07 => self.set_r(
316                d.rd,
317                (self.r(d.rt).cast_signed() >> (self.r(d.rs) & 31)).cast_unsigned(),
318            ),
319            0o10 => self.branch = Some(self.r(d.rs) & 0xFFC),
320            0o11 => {
321                let target = self.r(d.rs) & 0xFFC;
322                self.set_r(d.rd, sequential.wrapping_add(4) & 0xFFC);
323                self.branch = Some(target);
324            }
325            // BREAK. Halts and latches BROKE; the interrupt is conditional.
326            0o15 => halt_at = Some(sequential),
327            // ADD and ADDU are the same instruction here: the RSP has no
328            // exceptions, so there is no overflow trap to distinguish them.
329            0o40 | 0o41 => self.set_r(d.rd, self.r(d.rs).wrapping_add(self.r(d.rt))),
330            0o42 | 0o43 => self.set_r(d.rd, self.r(d.rs).wrapping_sub(self.r(d.rt))),
331            0o44 => self.set_r(d.rd, self.r(d.rs) & self.r(d.rt)),
332            0o45 => self.set_r(d.rd, self.r(d.rs) | self.r(d.rt)),
333            0o46 => self.set_r(d.rd, self.r(d.rs) ^ self.r(d.rt)),
334            0o47 => self.set_r(d.rd, !(self.r(d.rs) | self.r(d.rt))),
335            0o52 => self.set_r(
336                d.rd,
337                u32::from(self.r(d.rs).cast_signed() < self.r(d.rt).cast_signed()),
338            ),
339            0o53 => self.set_r(d.rd, u32::from(self.r(d.rs) < self.r(d.rt))),
340            // Everything else in SPECIAL is one of the absent opcodes
341            // (multiply, divide, HI/LO, traps). They are not errors on this
342            // core -- there is no exception mechanism to report them with --
343            // so they retire doing nothing.
344            _ => {}
345        }
346        halt_at
347    }
348
349    /// `LWC2`/`SWC2` — the vector load/store family.
350    ///
351    /// `opcode` sits at 15..11 where `rd` is, `element` at 10..7, and the offset
352    /// is a **signed 7-bit** field rather than the 16-bit immediate an ordinary
353    /// load carries -- so reading `imm` as a whole would give a wildly wrong
354    /// address.
355    fn vector_memory(&mut self, d: &Fields) {
356        let element = ((d.sa >> 1) & 0xF) as usize;
357        self.vector_mem(d.op == 0o72, d.rd as u32, d.rs, d.rt, element, d.imm & 0x7F);
358    }
359
360    /// The COP2 escape: the SU/VU moves and the computational vector instructions.
361    ///
362    /// Bit 25 separates the two groups. When it is set the instruction is a
363    /// computational one, whose `element` field is a *broadcast modifier*; when
364    /// clear it is a move, whose element field is a **byte offset**. Conflating
365    /// them is the first thing to get wrong here.
366    fn cop2(&mut self, d: &Fields) {
367        // Bit 25 of the word is the top bit of the `rs` field, which is how the
368        // two groups share one opcode. The four moves are `rs` 0/2/4/6, all
369        // with it clear.
370        if d.rs & 0x10 != 0 {
371            // Computational. Note the operand fields are NOT in the usual MIPS
372            // positions: `vt` is at 20..16 (where `rt` normally sits), `vs` at
373            // 15..11 and `vd` at 10..6 -- so the natural reading of a MIPS
374            // R-type would swap the source and destination.
375            self.count_vu_funct(d.funct);
376            let element = d.rs as u32 & 0xF;
377            // The single-lane group reads `vs` as a destination *element*
378            // rather than a source register, so it is dispatched first.
379            if self.vu_single_lane(d.funct, element, d.rt, d.rd, d.sa as usize) {
380                return;
381            }
382            self.vu_compute(d.funct, element, d.rd, d.rt, d.sa as usize);
383            return;
384        }
385        // `vs_elem` is bits 10..=7 of the word, a byte offset into the register.
386        let elem = ((d.sa >> 1) & 0xF) as usize;
387        match d.rs {
388            0o00 => {
389                self.mfc2(d.rt, d.rd, elem);
390            }
391            0o02 => {
392                self.cfc2(d.rt, d.rd as u32);
393            }
394            0o04 => self.mtc2(self.r(d.rt), d.rd, elem),
395            0o06 => self.ctc2(self.r(d.rt), d.rd as u32),
396            _ => {}
397        }
398    }
399
400    /// Address for a load or store: `base + sign-extended offset`, 12 bits.
401    fn addr(&self, d: &Fields) -> u32 {
402        self.r(d.rs).wrapping_add(sext(d.imm)) & 0xFFF
403    }
404
405    fn load(&self, d: &Fields, n: u32) -> u32 {
406        self.dmem_load(self.addr(d), n)
407    }
408
409    fn store(&mut self, d: &Fields, n: u32) {
410        let addr = self.addr(d);
411        self.dmem_store(addr, n, self.r(d.rt));
412    }
413
414    /// `MFC0` — read an SP register, or a DP register.
415    ///
416    /// The `MFC0`/`MTC0` `rd` field is 5 bits, but the RSP has only **sixteen**
417    /// COP0 registers: `c0`–`c7` are the SP interface registers (the *same*
418    /// physical registers the CPU sees at `0x0404_0000`), and `c8`–`c15` are the
419    /// RDP's command registers, held in a [`shadow`](Rsp::dp) so a read sees a
420    /// prior write in the same run (the authoritative copy is in `rustyn64-rdp`).
421    /// `c16`–`c31` do not exist — they must **not** alias into `c8`–`c15` (that
422    /// would let a stray `MFC0 c16` read `DP_START`), so they read zero.
423    fn cop0_read(&mut self, index: u32) -> u32 {
424        match index {
425            0..=7 => self.sp.read(index),
426            8..=15 => self.dp[(index - 8) as usize],
427            _ => 0,
428        }
429    }
430
431    /// `MTC0` — write an SP register (possibly starting a DMA), or a DP command
432    /// register. DP writes update the [`shadow`](Rsp::dp) and are reported via
433    /// [`StepResult::dp_write`] for the Bus to forward to `Rdp::dpc_write`.
434    /// `c16`–`c31` do not exist and are ignored (see [`Self::cop0_read`]).
435    fn cop0_write(&mut self, index: u32, value: u32) -> StepResult {
436        let mut out = StepResult::default();
437        match index {
438            8..=15 => {
439                let off = (index - 8) as u8;
440                self.dp[off as usize] = value;
441                out.dp_write = Some((off, value));
442                return out;
443            }
444            16..=31 => return out,
445            _ => {}
446        }
447        if index == sp::reg::STATUS {
448            // Both directions propagate. The RSP acknowledging its own
449            // interrupt is a `CLR_INTR` write, and it must reach the MI.
450            out.interrupt_change = sp::SpRegs::interrupt_change(value);
451        }
452        out.dma = self.sp.write(index, value);
453        out
454    }
455}
456
457/// A branch target: PC-relative, from the delay slot, masked to 12 bits.
458const fn branch_target(sequential: u32, imm: u32) -> u32 {
459    sequential.wrapping_add(sext(imm) << 2) & 0xFFC
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465
466    /// Assemble into IMEM and run until halted, returning the RSP.
467    fn run(program: &[u32], start: u32) -> Rsp {
468        let mut rsp = Rsp::new();
469        for (i, w) in program.iter().enumerate() {
470            let at = (start as usize + i * 4) & 0xFFF;
471            for (b, byte) in w.to_be_bytes().iter().enumerate() {
472                rsp.imem[(at + b) & 0xFFF] = *byte;
473            }
474        }
475        rsp.sp.set_pc(start);
476        rsp.sp.set_halted(false);
477        for _ in 0..10_000 {
478            rsp.su_step();
479            if rsp.sp.halted() {
480                break;
481            }
482        }
483        rsp
484    }
485
486    const NOP: u32 = 0;
487    const BREAK: u32 = 0o15;
488
489    const fn ori(rt: u32, rs: u32, imm: u32) -> u32 {
490        (0o15 << 26) | (rs << 21) | (rt << 16) | imm
491    }
492    const fn sw(rt: u32, base: u32, off: u32) -> u32 {
493        (0o53 << 26) | (base << 21) | (rt << 16) | off
494    }
495    const fn addu(rd: u32, rs: u32, rt: u32) -> u32 {
496        (rs << 21) | (rt << 16) | (rd << 11) | 0o41
497    }
498
499    /// **`BREAK` halts and latches `BROKE`, and the PC stops after it.**
500    ///
501    /// `SP_STATUS` reads `0x3` — both bits — which is what n64-systemtest's
502    /// `RSP Wrap around` expects.
503    #[test]
504    fn break_halts_and_sets_broke() {
505        let rsp = run(&[NOP, BREAK], 0);
506        assert!(rsp.sp.halted());
507        assert_eq!(rsp.sp.status(), 0x3, "HALTED | BROKE");
508        assert_eq!(rsp.sp.pc(), 0x8, "the PC sits past the BREAK");
509    }
510
511    /// **The 12-bit PC wraps at the end of IMEM instead of running off it.**
512    ///
513    /// n64-systemtest's own case: two `nop`s at `0xFF8`, a `BREAK` at `0x000`,
514    /// started at `0xFF8`, expecting to stop at `0x4` with status `0x3`. A core
515    /// that lets the PC grow past `0xFFF` fetches garbage and never halts.
516    #[test]
517    fn the_pc_wraps_from_the_end_of_imem_to_the_start() {
518        let mut rsp = Rsp::new();
519        for (at, w) in [(0xFF8u32, NOP), (0xFFC, NOP), (0x000, BREAK)] {
520            for (b, byte) in w.to_be_bytes().iter().enumerate() {
521                rsp.imem[(at as usize + b) & 0xFFF] = *byte;
522            }
523        }
524        rsp.sp.set_pc(0xFF8);
525        rsp.sp.set_halted(false);
526        for _ in 0..16 {
527            rsp.su_step();
528            if rsp.sp.halted() {
529                break;
530            }
531        }
532        assert_eq!(rsp.sp.pc(), 0x4, "wrapped to 0x000 and stopped after BREAK");
533        assert_eq!(rsp.sp.status(), 0x3);
534    }
535
536    /// **A misaligned word load is correct, not a fault.**
537    ///
538    /// The VR4300 raises `AddressError` for exactly this access, which is what
539    /// makes it the easiest thing to get wrong by reusing CPU code. Values from
540    /// n64-systemtest's `RSP LWU`, which seeds `BADDECAF01234567` at DMEM 0.
541    #[test]
542    fn a_misaligned_word_load_reads_across_the_boundary() {
543        let mut rsp = Rsp::new();
544        for (i, b) in 0xBADD_ECAF_0123_4567u64.to_be_bytes().iter().enumerate() {
545            rsp.dmem[i] = *b;
546        }
547        assert_eq!(rsp.dmem_load(0x000, 4), 0xBADD_ECAF);
548        assert_eq!(rsp.dmem_load(0x001, 4), 0xDDEC_AF01, "misaligned by one");
549        assert_eq!(rsp.dmem_load(0x003, 4), 0xAF01_2345);
550    }
551
552    /// A load that runs off the end of DMEM **wraps to the start**, because
553    /// every byte address is masked independently.
554    #[test]
555    fn a_load_at_the_end_of_dmem_wraps_to_the_beginning() {
556        let mut rsp = Rsp::new();
557        rsp.dmem[0xFFE] = 0xBC;
558        rsp.dmem[0xFFF] = 0xAD;
559        rsp.dmem[0x000] = 0x7E;
560        rsp.dmem[0x001] = 0x8F;
561        assert_eq!(rsp.dmem_load(0xFFE, 4), 0xBCAD_7E8F);
562    }
563
564    /// The integer core computes and stores: `ori` builds a value, `addu` sums
565    /// it, `sw` lands it in DMEM. Chosen so a no-op decode arm cannot pass —
566    /// the stored word depends on all three instructions.
567    #[test]
568    fn the_integer_core_computes_and_stores() {
569        let rsp = run(
570            &[
571                ori(1, 0, 0x1234),
572                ori(2, 0, 0x1111),
573                addu(3, 1, 2),
574                sw(3, 0, 0x20),
575                BREAK,
576            ],
577            0,
578        );
579        assert_eq!(rsp.dmem_load(0x20, 4), 0x2345, "0x1234 + 0x1111");
580    }
581
582    /// `r0` stays zero however hard an instruction tries to write it.
583    #[test]
584    fn register_zero_is_pinned() {
585        let rsp = run(&[ori(0, 0, 0xFFFF), sw(0, 0, 0x30), BREAK], 0);
586        assert_eq!(rsp.r(0), 0);
587        assert_eq!(rsp.dmem_load(0x30, 4), 0, "and it stores as zero");
588    }
589
590    /// A taken branch executes its **delay slot** before redirecting. The slot
591    /// here writes a value nothing else writes, so a core that skips it fails.
592    #[test]
593    fn a_branch_executes_its_delay_slot() {
594        // beq r0, r0, +2  /  ori r1, 0x55 (delay slot)  /  ori r1, 0x99 (skipped)
595        // ... target: sw r1, 0x40 / break
596        let beq = (0o04 << 26) | 2;
597        let rsp = run(
598            &[beq, ori(1, 0, 0x55), ori(1, 0, 0x99), sw(1, 0, 0x40), BREAK],
599            0,
600        );
601        assert_eq!(
602            rsp.dmem_load(0x40, 4),
603            0x55,
604            "the delay slot ran and the skipped instruction did not"
605        );
606    }
607
608    /// **`BREAK` in a *taken* branch's delay slot halts at the branch target.**
609    /// The redirect the `beq` latched still wins over the sequential address:
610    /// n64-systemtest pins PC to `0x1C` (`0x4 + (6 << 2)`), not `0x8`. Reading
611    /// the sequential value instead is the natural mistake, and it is the exact
612    /// difference between this case and the untaken one below.
613    #[test]
614    fn break_in_a_taken_delay_slot_halts_at_the_branch_target() {
615        // beq r0, r0, +6 (always taken)  /  break (delay slot)
616        let beq = (0o04 << 26) | 6;
617        let rsp = run(&[beq, BREAK], 0);
618        assert_eq!(
619            rsp.sp.pc(),
620            0x1C,
621            "PC follows the branch, not sequential 0x8"
622        );
623    }
624
625    /// The mirror: a `BREAK` after an *untaken* branch halts sequentially. With
626    /// no redirect latched, `after_delay` is `None` and the PC is `0x8` — the
627    /// case that must NOT regress when the taken case is fixed.
628    #[test]
629    fn break_after_an_untaken_delay_slot_halts_sequentially() {
630        // bne r0, r0, +6 (never taken)  /  break (delay slot)
631        let bne = (0o05 << 26) | 6;
632        let rsp = run(&[bne, BREAK], 0);
633        assert_eq!(rsp.sp.pc(), 0x8, "no branch taken, so PC is sequential");
634    }
635
636    /// **The RSP can acknowledge its own interrupt.**
637    ///
638    /// `MTC0 SP_STATUS` with `CLR_INTR` must reach the MI as a *clear*. With a
639    /// plain `bool` on [`StepResult`], "clear the line" and "this step said
640    /// nothing about the line" are the same value, so the acknowledgment is
641    /// dropped and the CPU's `IP2` stays asserted for ever. The three cases are
642    /// asserted separately because that is exactly what a two-state flag cannot
643    /// express.
644    #[test]
645    fn the_rsp_can_raise_and_clear_its_own_interrupt() {
646        const SET_INTR: u32 = 1 << 4;
647        const CLR_INTR: u32 = 1 << 3;
648        let mut rsp = Rsp::new();
649
650        let out = rsp.cop0_write(sp::reg::STATUS, SET_INTR);
651        assert_eq!(out.interrupt_change, Some(true), "raise");
652
653        let out = rsp.cop0_write(sp::reg::STATUS, CLR_INTR);
654        assert_eq!(out.interrupt_change, Some(false), "acknowledge");
655
656        let out = rsp.cop0_write(sp::reg::STATUS, SET_INTR | CLR_INTR);
657        assert_eq!(out.interrupt_change, None, "both together: no change");
658
659        // A write that mentions neither must not disturb the line.
660        let out = rsp.cop0_write(sp::reg::STATUS, 1 << 10);
661        assert_eq!(out.interrupt_change, None, "unrelated flag write");
662    }
663
664    /// `BREAK` raises the line **only** when `INTBREAK` is set.
665    ///
666    /// Both configurations execute the same single `BREAK` and differ *only* in
667    /// that flag, so the differing `interrupt_change` is attributable to nothing
668    /// else. The disabled case asserts `None` rather than merely "not raised":
669    /// `BREAK` must leave a previously-raised line alone, and `Some(false)`
670    /// would clear it.
671    #[test]
672    fn break_raises_the_interrupt_only_when_enabled() {
673        /// Run one `BREAK` at IMEM 0 and return what the step reported.
674        fn break_once(intbreak: bool) -> (StepResult, u32) {
675            let mut rsp = Rsp::new();
676            for (b, byte) in BREAK.to_be_bytes().iter().enumerate() {
677                rsp.imem[b] = *byte;
678            }
679            if intbreak {
680                rsp.sp.write(sp::reg::STATUS, 1 << 8); // SET_INTBREAK
681            }
682            rsp.sp.set_pc(0);
683            rsp.sp.set_halted(false);
684            let out = rsp.su_step();
685            (out, rsp.sp.status())
686        }
687
688        let (out, status) = break_once(false);
689        assert_eq!(
690            out.interrupt_change, None,
691            "with INTBREAK clear, BREAK must not touch the line at all -- \
692             Some(false) would acknowledge an interrupt it never raised"
693        );
694        assert_eq!(status & 0x3, 0x3, "still HALTED | BROKE");
695
696        let (out, status) = break_once(true);
697        assert_eq!(out.interrupt_change, Some(true), "INTBREAK set");
698        assert_eq!(status & 0x3, 0x3, "and it halts either way");
699    }
700
701    /// `MFC0` reads the SP registers the CPU shares, and `MTC0` writes them.
702    #[test]
703    fn cop0_reaches_the_sp_registers() {
704        let mut rsp = Rsp::new();
705        rsp.sp.write(sp::reg::STATUS, 1 << 10); // SET_SIG0
706        let v = rsp.cop0_read(sp::reg::STATUS);
707        assert_ne!(v & sp::STATUS_SIG0, 0, "MFC0 sees the signal bit");
708
709        // c8–c15 are the RDP's; the shadow starts zeroed.
710        assert_eq!(rsp.cop0_read(9), 0);
711    }
712
713    /// `MTC0`/`MFC0` to `c8`–`c15` reach the DP shadow and report the write; the
714    /// non-existent `c16`–`c31` must **not** alias back into `c8`–`c15`.
715    #[test]
716    fn cop0_dp_registers_do_not_alias_beyond_c15() {
717        let mut rsp = Rsp::new();
718
719        // c9 = DP_END -> shadow[1], reported as offset 1.
720        let out = rsp.cop0_write(9, 0xABCD_0000);
721        assert_eq!(out.dp_write, Some((1, 0xABCD_0000)));
722        assert_eq!(
723            rsp.cop0_read(9),
724            0xABCD_0000,
725            "MFC0 reads the shadowed write"
726        );
727
728        // c16/c24 do not exist: writing them must neither report a DP write nor
729        // disturb any shadow register (the `(index-8) & 7` bug wrapped c16->c8).
730        let before = rsp.dp;
731        for idx in [16, 24, 31] {
732            let out = rsp.cop0_write(idx, 0xDEAD_BEEF);
733            assert_eq!(out.dp_write, None, "c{idx} is not a DP register");
734            assert_eq!(rsp.cop0_read(idx), 0, "c{idx} reads zero");
735        }
736        assert_eq!(
737            rsp.dp, before,
738            "c16–c31 writes must not touch the DP shadow"
739        );
740    }
741}