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}