Skip to main content

rustyn64_rsp/
sp.rs

1//! The **SP interface** register file (T-21-002).
2//!
3//! Eight registers at `0x0404_0000` plus `SP_PC` at `0x0408_0000`, memory-mapped
4//! into the VR4300's address space and simultaneously exposed to the RSP itself
5//! as COP0 registers `c0`–`c7`. There is one set of physical registers behind
6//! both views (N64brew *RSP Interface* §RSP Internal Registers), which is why
7//! this module holds them rather than either side owning a copy.
8//!
9//! # Why the write layout differs from the read layout
10//!
11//! `SP_STATUS` reads as a flag word and writes as a list of **set/clear
12//! commands** — two bits per flag. That is not an encoding quirk to normalize
13//! away: it exists so either processor can change one flag with a single store,
14//! without the read-modify-write that would race the other. Collapsing the two
15//! layouts into one would reintroduce exactly the race the hardware design
16//! removes.
17//!
18//! The corollary is the rule that catches naive implementations: writing a
19//! flag's **set and clear bits together leaves it unchanged**. n64-systemtest
20//! checks this for every flag it can reach.
21
22use serde::{Deserialize, Serialize};
23
24/// `SP_STATUS.HALTED` — the RSP is paused and fetches nothing.
25pub const STATUS_HALTED: u32 = 1 << 0;
26/// `SP_STATUS.BROKE` — a `BREAK` has executed since this was last cleared.
27pub const STATUS_BROKE: u32 = 1 << 1;
28/// `SP_STATUS.DMA_BUSY` — a transfer is in progress.
29pub const STATUS_DMA_BUSY: u32 = 1 << 2;
30/// `SP_STATUS.DMA_FULL` — a second transfer is queued behind the current one.
31pub const STATUS_DMA_FULL: u32 = 1 << 3;
32/// `SP_STATUS.IO_BUSY`.
33pub const STATUS_IO_BUSY: u32 = 1 << 4;
34/// `SP_STATUS.SSTEP` — single-step mode.
35pub const STATUS_SSTEP: u32 = 1 << 5;
36/// `SP_STATUS.INTBREAK` — raise the MI interrupt when `BREAK` executes.
37pub const STATUS_INTBREAK: u32 = 1 << 6;
38/// `SP_STATUS.SIG0` — the first of eight software-defined signal bits.
39///
40/// `SIG<n>` is bit `7 + n`, so the eight occupy bits 7..=14.
41pub const STATUS_SIG0: u32 = 1 << 7;
42
43/// Register indices, shared by the CPU's `0x0404_00xx` window and the RSP's
44/// COP0 `c0`–`c7`.
45pub mod reg {
46    /// `SP_DMA_SPADDR` — the DMEM/IMEM side of a transfer.
47    pub const DMA_SPADDR: u32 = 0;
48    /// `SP_DMA_RAMADDR` — the RDRAM side.
49    pub const DMA_RAMADDR: u32 = 1;
50    /// `SP_DMA_RDLEN` — writing it starts an RDRAM to DMEM/IMEM transfer.
51    pub const DMA_RDLEN: u32 = 2;
52    /// `SP_DMA_WRLEN` — writing it starts a DMEM/IMEM to RDRAM transfer.
53    pub const DMA_WRLEN: u32 = 3;
54    /// `SP_STATUS`.
55    pub const STATUS: u32 = 4;
56    /// `SP_DMA_FULL` — a read-only mirror of `SP_STATUS.DMA_FULL`.
57    pub const DMA_FULL: u32 = 5;
58    /// `SP_DMA_BUSY` — a read-only mirror of `SP_STATUS.DMA_BUSY`.
59    pub const DMA_BUSY: u32 = 6;
60    /// `SP_SEMAPHORE` — the hardware-assisted mutex bit.
61    pub const SEMAPHORE: u32 = 7;
62}
63
64/// A programmed DMA, latched from the address and length registers.
65///
66/// Returned to the Bus to execute rather than performed here: the RSP does not
67/// own RDRAM, and a chip reaching back into its owner is the dependency cycle
68/// `docs/architecture.md` exists to prevent. The PI engine returns a transfer
69/// description for the same reason.
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
71pub struct Dma {
72    /// DMEM/IMEM byte offset, with bit 12 selecting IMEM.
73    pub sp_addr: u32,
74    /// RDRAM byte address.
75    pub ram_addr: u32,
76    /// Bytes per row. Already rounded **up** to a multiple of 8.
77    pub row_len: u32,
78    /// Number of rows.
79    pub rows: u32,
80    /// Bytes skipped in RDRAM between rows. The SP side stays contiguous.
81    ///
82    /// 8-byte aligned: the field's low three bits *"are always 0"*, so an
83    /// unaligned value written by guest code cannot drag the RDRAM pointer off
84    /// alignment mid-transfer.
85    pub skip: u32,
86    /// `true` for DMEM/IMEM to RDRAM (`SP_DMA_WRLEN`).
87    pub to_dram: bool,
88}
89
90/// The SP interface registers.
91#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
92pub struct SpRegs {
93    /// The flag word, in its **read** layout.
94    status: u32,
95    /// `SP_PC`, 12 bits.
96    pc: u32,
97    /// The semaphore bit.
98    ///
99    /// Starts **taken**. A read returns the current value and then sets it, so
100    /// the sequence the hardware produces is: write (any value) clears it, the
101    /// next read returns 0 and takes it, and every read after that returns 1.
102    /// n64-systemtest states exactly that in its own words — *"If Semaphore is
103    /// written to (value doesn't matter), the next read will return 0.
104    /// Otherwise it returns 1"* — and checks five consecutive reads.
105    semaphore: bool,
106    /// The SP-side address of the current (or last completed) transfer.
107    sp_addr: u32,
108    /// The RDRAM-side address of the current (or last completed) transfer.
109    ram_addr: u32,
110    /// The length word as it reads back.
111    ///
112    /// Not the value written: after a transfer completes, the length field
113    /// reads `0xFF8`, because the hardware decrements it by 8 per 64-bit word
114    /// and stops at `-8`. `SP_DMA_RDLEN` and `SP_DMA_WRLEN` return the *same*
115    /// data regardless of the direction that was programmed.
116    len: u32,
117    /// The address/length values a write has staged but not yet started.
118    pending_sp_addr: u32,
119    /// Staged RDRAM address.
120    pending_ram_addr: u32,
121}
122
123impl Default for SpRegs {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl SpRegs {
130    /// Power-on state: **halted**, semaphore free.
131    ///
132    /// The RSP comes out of reset halted and idles until the CPU clears the bit;
133    /// n64-systemtest's `StartupTest` reads `SP_STATUS` expecting exactly `0x1`.
134    #[must_use]
135    pub const fn new() -> Self {
136        Self {
137            status: STATUS_HALTED,
138            pc: 0,
139            semaphore: false,
140            sp_addr: 0,
141            ram_addr: 0,
142            len: 0,
143            pending_sp_addr: 0,
144            pending_ram_addr: 0,
145        }
146    }
147
148    /// The `SP_STATUS` flag word, as read.
149    #[must_use]
150    pub const fn status(&self) -> u32 {
151        self.status
152    }
153
154    /// Is the RSP halted?
155    #[must_use]
156    pub const fn halted(&self) -> bool {
157        self.status & STATUS_HALTED != 0
158    }
159
160    /// Has a `BREAK` executed since `BROKE` was last cleared? Distinct from
161    /// [`Self::halted`]: a `SET_HALT` write halts without setting `BROKE`, so a
162    /// consumer that wants "the microcode reached a `break`" must check this.
163    #[must_use]
164    pub const fn broke(&self) -> bool {
165        self.status & STATUS_BROKE != 0
166    }
167
168    /// Latch `BROKE`. Set by a `BREAK` and cleared only by a `CLR_BROKE` write:
169    /// it remembers that a break happened, independently of run state.
170    pub const fn set_broke(&mut self, broke: bool) {
171        if broke {
172            self.status |= STATUS_BROKE;
173        } else {
174            self.status &= !STATUS_BROKE;
175        }
176    }
177
178    /// Halt or release the RSP from within the chip (a `BREAK`, or single-step).
179    pub const fn set_halted(&mut self, halted: bool) {
180        if halted {
181            self.status |= STATUS_HALTED;
182        } else {
183            self.status &= !STATUS_HALTED;
184        }
185    }
186
187    /// `SP_PC`.
188    #[must_use]
189    pub const fn pc(&self) -> u32 {
190        self.pc
191    }
192
193    /// Set `SP_PC`, masked to the 12 bits IMEM has.
194    pub const fn set_pc(&mut self, pc: u32) {
195        self.pc = pc & 0xFFC;
196    }
197
198    /// Read a register by index. `mi_sp` is not touched here — see [`Self::write`].
199    #[must_use]
200    pub const fn read(&mut self, index: u32) -> u32 {
201        match index & 7 {
202            reg::DMA_SPADDR => self.sp_addr,
203            reg::DMA_RAMADDR => self.ram_addr,
204            // Both length registers report the same transfer, whichever
205            // direction it was programmed in.
206            reg::DMA_RDLEN | reg::DMA_WRLEN => self.len,
207            reg::STATUS => self.status,
208            reg::DMA_FULL => (self.status & STATUS_DMA_FULL != 0) as u32,
209            reg::DMA_BUSY => (self.status & STATUS_DMA_BUSY != 0) as u32,
210            // The read that takes the mutex: the previous value is returned and
211            // the bit is set, so a reader that sees 0 has just acquired it.
212            _ => {
213                let was = self.semaphore;
214                self.semaphore = true;
215                was as u32
216            }
217        }
218    }
219
220    /// Write a register by index.
221    ///
222    /// Returns a [`Dma`] when the write started one — the Bus performs it, for
223    /// the reason given on that type.
224    ///
225    /// The SP interrupt line is **not** updated here. `SP_STATUS` can both raise
226    /// and acknowledge it, so the caller reads [`Self::interrupt_change`]
227    /// afterwards; returning it through the same channel as a DMA would conflate
228    /// two independent effects of one write.
229    pub const fn write(&mut self, index: u32, val: u32) -> Option<Dma> {
230        match index & 7 {
231            // The address registers latch as *pending* and only become visible
232            // when a transfer starts. Reads keep returning the ongoing or last
233            // completed transfer's values until then.
234            reg::DMA_SPADDR => {
235                self.pending_sp_addr = val & 0x1FF8;
236                None
237            }
238            reg::DMA_RAMADDR => {
239                self.pending_ram_addr = val & 0x00FF_FFF8;
240                None
241            }
242            reg::DMA_RDLEN => Some(self.start_dma(val, false)),
243            reg::DMA_WRLEN => Some(self.start_dma(val, true)),
244            reg::STATUS => {
245                self.write_status(val);
246                None
247            }
248            // The two mirrors are read-only, and the semaphore ignores the
249            // value written -- writing it *releases* the mutex whatever the
250            // operand, which is why the suite writes 0, 1 and 0xFFFFFFFF and
251            // expects the same result from each.
252            reg::SEMAPHORE => {
253                self.semaphore = false;
254                None
255            }
256            _ => None,
257        }
258    }
259
260    /// Apply a `SP_STATUS` write, in its set/clear-command layout.
261    const fn write_status(&mut self, val: u32) {
262        /// `CLR_HALT` is bit 0 and `SET_HALT` bit 1; every later flag follows the
263        /// same clear-then-set pairing.
264        const CLR_HALT: u32 = 1 << 0;
265        const SET_HALT: u32 = 1 << 1;
266        const CLR_BROKE: u32 = 1 << 2;
267        const CLR_INTR: u32 = 1 << 3;
268        const SET_INTR: u32 = 1 << 4;
269        const CLR_SSTEP: u32 = 1 << 5;
270        const SET_SSTEP: u32 = 1 << 6;
271        const CLR_INTBREAK: u32 = 1 << 7;
272        const SET_INTBREAK: u32 = 1 << 8;
273
274        self.apply(val, CLR_HALT, SET_HALT, STATUS_HALTED);
275        // BROKE has a clear command and no set: it is a latch the hardware owns.
276        if val & CLR_BROKE != 0 {
277            self.status &= !STATUS_BROKE;
278        }
279        self.apply(val, CLR_SSTEP, SET_SSTEP, STATUS_SSTEP);
280        self.apply(val, CLR_INTBREAK, SET_INTBREAK, STATUS_INTBREAK);
281
282        // The eight signal bits, at CLR = 9 + 2n and SET = 10 + 2n. Signals have
283        // no hardware meaning -- they exist purely so the two processors can
284        // hand-shake -- so they are pure storage, but they obey the same
285        // set-and-clear-together rule as everything else.
286        let mut n = 0;
287        while n < 8 {
288            let clr = 1 << (9 + 2 * n);
289            let set = 1 << (10 + 2 * n);
290            self.apply(val, clr, set, STATUS_SIG0 << n);
291            n += 1;
292        }
293
294        // INTR is deliberately absent: it is not a `SP_STATUS` flag at all but
295        // the MI's SP line, and `take_interrupt_change` reports it.
296        let _ = (CLR_INTR, SET_INTR);
297    }
298
299    /// Apply one clear/set command pair to one flag.
300    ///
301    /// **Both bits set means no change** — the rule the whole layout exists for.
302    /// Implementing this as "clear then set" instead would silently make the
303    /// combination equivalent to a set, which n64-systemtest checks for every
304    /// reachable flag.
305    const fn apply(&mut self, val: u32, clr: u32, set: u32, flag: u32) {
306        let clearing = val & clr != 0;
307        let setting = val & set != 0;
308        if clearing && setting {
309            return;
310        }
311        if clearing {
312            self.status &= !flag;
313        } else if setting {
314            self.status |= flag;
315        }
316    }
317
318    /// What a `SP_STATUS` write did to the MI's SP interrupt line.
319    ///
320    /// `Some(true)` raises it, `Some(false)` acknowledges it, `None` leaves it
321    /// alone. Separate from [`Self::write`] because the line lives in the MI,
322    /// not here, and because one write can start a DMA *and* touch the line.
323    #[must_use]
324    pub const fn interrupt_change(val: u32) -> Option<bool> {
325        const CLR_INTR: u32 = 1 << 3;
326        const SET_INTR: u32 = 1 << 4;
327        match (val & CLR_INTR != 0, val & SET_INTR != 0) {
328            // Set and clear together: unchanged, exactly as for the flags.
329            (true, true) | (false, false) => None,
330            (true, false) => Some(false),
331            (false, true) => Some(true),
332        }
333    }
334
335    /// Latch a length write and describe the transfer it starts.
336    const fn start_dma(&mut self, len_word: u32, to_dram: bool) -> Dma {
337        // The length field is bytes-minus-one and the engine works in 64-bit
338        // words, so it rounds **up**: "writing 0 (or any value up to and
339        // including 7) starts a transfer of exactly 8 bytes". Rounding down --
340        // `(len + 1) & !7` -- turns a 12-byte request into 8 and silently drops
341        // the tail.
342        let row_len = ((len_word & 0xFFF) | 7) + 1;
343        let rows = ((len_word >> 12) & 0xFF) + 1;
344        // SKIP's low three bits "are always 0" (N64brew *RSP Interface*), like
345        // every other address and length field here -- the DMA engine works in
346        // 64-bit words, so an unaligned stride is not expressible.
347        let skip = (len_word >> 20) & 0xFF8;
348
349        // The pending address latches become the visible ones as the transfer
350        // starts; until now, reads returned the previous transfer's values.
351        self.sp_addr = self.pending_sp_addr;
352        self.ram_addr = self.pending_ram_addr;
353        // SKIP survives the transfer ("COUNT is reset to 0, and SKIP is
354        // unchanged"), so it is captured here and preserved by `complete_dma`.
355        self.len = skip << 20;
356
357        Dma {
358            sp_addr: self.sp_addr,
359            ram_addr: self.ram_addr,
360            row_len,
361            rows,
362            skip,
363            to_dram,
364        }
365    }
366
367    /// Record where a completed transfer left the address and length registers.
368    ///
369    /// Hardware leaves the pointers **past** the data it moved, and the length
370    /// field at `0xFF8` — it is decremented by 8 per word and ends at `-8`.
371    /// `COUNT` resets to 0 and `SKIP` is preserved, which is why only the low
372    /// field is rewritten here.
373    pub const fn complete_dma(&mut self, sp_addr: u32, ram_addr: u32) {
374        self.sp_addr = sp_addr;
375        self.ram_addr = ram_addr;
376        self.len = (self.len & 0xFFF0_0000) | 0xFF8;
377        self.status &= !(STATUS_DMA_BUSY | STATUS_DMA_FULL);
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    /// **Set and clear together leaves the flag alone.** The rule the read/write
386    /// asymmetry exists for, and the one a "clear then set" implementation gets
387    /// wrong in a way that looks like a set.
388    #[test]
389    fn setting_and_clearing_a_flag_together_changes_nothing() {
390        const CLR_SSTEP: u32 = 1 << 5;
391        const SET_SSTEP: u32 = 1 << 6;
392        let mut sp = SpRegs::new();
393
394        sp.write(reg::STATUS, SET_SSTEP);
395        assert_ne!(sp.status() & STATUS_SSTEP, 0, "set on its own works");
396        sp.write(reg::STATUS, SET_SSTEP | CLR_SSTEP);
397        assert_ne!(
398            sp.status() & STATUS_SSTEP,
399            0,
400            "both bits together must preserve the prior state (set)"
401        );
402
403        sp.write(reg::STATUS, CLR_SSTEP);
404        assert_eq!(sp.status() & STATUS_SSTEP, 0, "clear on its own works");
405        sp.write(reg::STATUS, SET_SSTEP | CLR_SSTEP);
406        assert_eq!(
407            sp.status() & STATUS_SSTEP,
408            0,
409            "and must preserve the prior state when clear, too"
410        );
411    }
412
413    /// All eight signal bits, at `SIG<n>` = bit `7 + n`, driven by commands at
414    /// `9 + 2n` / `10 + 2n`. Tested across the whole range because an off-by-one
415    /// in the pairing works for `SIG0` and fails for the rest.
416    #[test]
417    fn every_signal_bit_sets_and_clears_independently() {
418        for n in 0..8 {
419            let mut sp = SpRegs::new();
420            let clr = 1 << (9 + 2 * n);
421            let set = 1 << (10 + 2 * n);
422            let flag = STATUS_SIG0 << n;
423
424            sp.write(reg::STATUS, set);
425            assert_eq!(sp.status() & flag, flag, "SIG{n} did not set");
426            assert_eq!(
427                sp.status() & !flag & !STATUS_HALTED,
428                0,
429                "SIG{n}'s command disturbed another flag"
430            );
431            sp.write(reg::STATUS, set | clr);
432            assert_eq!(sp.status() & flag, flag, "SIG{n} changed on set+clear");
433            sp.write(reg::STATUS, clr);
434            assert_eq!(sp.status() & flag, 0, "SIG{n} did not clear");
435        }
436    }
437
438    /// **The semaphore's first read after a write is 0; every later one is 1.**
439    ///
440    /// Quoted from n64-systemtest's own header comment, and it checks five
441    /// consecutive reads. The value written is irrelevant — the suite writes 0,
442    /// 1 and `0xFFFF_FFFF` and expects identical behavior from each.
443    #[test]
444    fn the_semaphore_is_taken_by_reading_it() {
445        for written in [0u32, 1, 0xFFFF_FFFF] {
446            let mut sp = SpRegs::new();
447            sp.write(reg::SEMAPHORE, written);
448            assert_eq!(sp.read(reg::SEMAPHORE), 0, "first read acquires it");
449            for _ in 0..4 {
450                assert_eq!(sp.read(reg::SEMAPHORE), 1, "and it stays taken");
451            }
452        }
453    }
454
455    /// Writing twice without reading is the same as writing once — the write
456    /// sets a state, it does not queue.
457    #[test]
458    fn writing_the_semaphore_twice_is_the_same_as_once() {
459        let mut sp = SpRegs::new();
460        sp.write(reg::SEMAPHORE, 6);
461        sp.write(reg::SEMAPHORE, 6);
462        assert_eq!(sp.read(reg::SEMAPHORE), 0);
463        assert_eq!(sp.read(reg::SEMAPHORE), 1);
464    }
465
466    /// `SP_STATUS`'s interrupt commands drive the **MI line**, not a status
467    /// flag, and obey the same set-and-clear-together rule.
468    #[test]
469    fn the_interrupt_commands_report_a_line_change_not_a_flag() {
470        const CLR_INTR: u32 = 1 << 3;
471        const SET_INTR: u32 = 1 << 4;
472        assert_eq!(SpRegs::interrupt_change(SET_INTR), Some(true));
473        assert_eq!(SpRegs::interrupt_change(CLR_INTR), Some(false));
474        assert_eq!(
475            SpRegs::interrupt_change(SET_INTR | CLR_INTR),
476            None,
477            "both together must leave the line alone"
478        );
479        assert_eq!(SpRegs::interrupt_change(0), None);
480
481        // And it must not leak into the status word.
482        let mut sp = SpRegs::new();
483        sp.write(reg::STATUS, SET_INTR);
484        assert_eq!(
485            sp.status(),
486            STATUS_HALTED,
487            "SET_INTR is not a SP_STATUS flag"
488        );
489    }
490
491    /// **The length field rounds up to a multiple of 8, never down.**
492    ///
493    /// "Writing 0 (or any value up to and including 7) starts a transfer of
494    /// exactly 8 bytes". Rounding down turns n64-systemtest's `length = 11`
495    /// case (12 bytes requested) into 8 and drops the tail.
496    #[test]
497    fn the_dma_length_rounds_up_to_a_multiple_of_eight() {
498        let mut sp = SpRegs::new();
499        for (written, want) in [(0u32, 8u32), (7, 8), (8, 16), (11, 16), (15, 16)] {
500            let dma = sp.write(reg::DMA_RDLEN, written).expect("a length write");
501            assert_eq!(dma.row_len, want, "length field {written}");
502            assert_eq!(dma.rows, 1, "count 0 is a single row");
503        }
504    }
505
506    /// `COUNT` and `SKIP` are real fields. Reading only the low 12 bits moves
507    /// one row and silently drops the rest, which is the failure mode for
508    /// anything transferring a 2D block.
509    #[test]
510    fn the_dma_word_carries_count_and_skip() {
511        let mut sp = SpRegs::new();
512        let dma = sp
513            .write(reg::DMA_RDLEN, 7 | (1 << 12) | (8 << 20))
514            .expect("a length write");
515        assert_eq!(dma.row_len, 8);
516        assert_eq!(dma.rows, 2, "count is rows minus one");
517        assert_eq!(dma.skip, 8);
518    }
519
520    /// The address registers stage as **pending** and only become readable when
521    /// a transfer starts; until then reads report the previous transfer.
522    #[test]
523    fn the_address_registers_are_double_buffered() {
524        let mut sp = SpRegs::new();
525        sp.write(reg::DMA_SPADDR, 0x40);
526        sp.write(reg::DMA_RAMADDR, 0x100);
527        assert_eq!(sp.read(reg::DMA_SPADDR), 0, "still pending, not visible");
528        assert_eq!(sp.read(reg::DMA_RAMADDR), 0);
529
530        sp.write(reg::DMA_RDLEN, 7);
531        assert_eq!(sp.read(reg::DMA_SPADDR), 0x40, "visible once it starts");
532        assert_eq!(sp.read(reg::DMA_RAMADDR), 0x100);
533    }
534
535    /// After completion the pointers sit past the data and the length field
536    /// reads `0xFF8` — the hardware's `-8` after decrementing per word. Both
537    /// length registers report it, whichever direction was programmed.
538    #[test]
539    fn a_completed_dma_leaves_the_registers_past_the_transfer() {
540        let mut sp = SpRegs::new();
541        sp.write(reg::DMA_SPADDR, 0x50);
542        sp.write(reg::DMA_RAMADDR, 0x10);
543        sp.write(reg::DMA_WRLEN, 15);
544        sp.complete_dma(0x60, 0x20);
545
546        assert_eq!(sp.read(reg::DMA_SPADDR), 0x60);
547        assert_eq!(sp.read(reg::DMA_RAMADDR), 0x20);
548        assert_eq!(sp.read(reg::DMA_RDLEN), 0xFF8);
549        assert_eq!(
550            sp.read(reg::DMA_WRLEN),
551            0xFF8,
552            "both registers report the same transfer"
553        );
554    }
555
556    /// **`SKIP` is 8-byte aligned and survives the transfer.**
557    ///
558    /// Its low three bits *"are always 0"*, so an unaligned stride is not
559    /// expressible and cannot drag the RDRAM pointer off alignment mid-copy.
560    /// After completion *"COUNT is reset to 0, and SKIP is unchanged"*, so it
561    /// must still be there to read back.
562    #[test]
563    fn the_dma_skip_is_eight_byte_aligned_and_survives() {
564        let mut sp = SpRegs::new();
565        let dma = sp
566            .write(reg::DMA_RDLEN, 7 | (1 << 12) | (0xF << 20))
567            .expect("a length write");
568        assert_eq!(dma.skip, 8, "0xF masks down to 8, not 15");
569
570        sp.complete_dma(0x40, 0x80);
571        assert_eq!(
572            sp.read(reg::DMA_RDLEN),
573            (8 << 20) | 0xFF8,
574            "SKIP preserved, COUNT cleared, length reading back as -8"
575        );
576    }
577
578    /// Power-on is halted, and `SP_STATUS` reads exactly `0x1` — the value
579    /// n64-systemtest's startup check expects.
580    #[test]
581    fn power_on_is_halted_and_nothing_else() {
582        let sp = SpRegs::new();
583        assert_eq!(sp.status(), 0x1);
584        assert!(sp.halted());
585    }
586}