pub struct Rsp {
pub su_regs: [u32; 32],
pub vu_regs: [[u16; 8]; 32],
pub vu_acc: [u64; 8],
pub pc: u16,
pub halted: bool,
pub dmem: Box<[u8; 4096]>,
pub imem: Box<[u8; 4096]>,
pub sp: SpRegs,
pub dp: [u32; 8],
pub vu_ctrl: Control,
pub div: Divide,
/* private fields */
}Expand description
RSP architectural state.
Holds the SU register file, the VU vector register file + accumulator, the program counter into IMEM, the halted flag, and the DMEM/IMEM scratch. The execution engine (SU + VU) runs the microcode instruction stream.
Fields§
§su_regs: [u32; 32]Scalar unit: 32 × 32-bit general registers.
vu_regs: [[u16; 8]; 32]Vector unit: 32 registers × 8 lanes of 16-bit.
vu_acc: [u64; 8]48-bit-per-lane VU accumulator (modeled as [u64; 8], low 48 used).
pc: u16never written; read Rsp::pc() instead, which returns SP_STATUS’s PC
Vestigial. The authoritative PC lives in Self::sp
(SpRegs::pc()), which is what su_step fetches from; this
field is never written. Kept only so the save-state layout is unchanged —
removing it is a format break (ADR 0005), which module 70 reserves for an
announced major release — and deprecated so any read is a loud warning
(the workspace treats warnings as errors). Use Rsp::pc instead.
This is not hypothetical tidiness: while it was pub it was sampled twice
in one debugging session and produced two confident, wrong conclusions —
“the RSP never starts” and “its PC never advances” — when in fact retail
microcode was executing hundreds of distinct instructions. That is the
inert-API hazard in docs/engineering-lessons.md §3.2 exactly.
halted: boolnever written; read Rsp::halted() instead, which returns SP_STATUS.halt
Vestigial — see Self::pc. The authoritative halt state is
sp.halted() (SP_STATUS.halt), which is what gates execution. Use
Rsp::halted instead.
dmem: Box<[u8; 4096]>4 KiB data memory.
imem: Box<[u8; 4096]>4 KiB instruction memory.
sp: SpRegsThe SP interface registers, shared by the CPU’s memory-mapped window and the RSP’s own COP0 – one set of physical registers, so one field.
dp: [u32; 8]Shadow of the eight DP command registers (COP0 c8–c15 =
DP_START/END/CURRENT/STATUS/…), so an MFC0 reads back what a
prior MTC0 in the same run wrote. The authoritative copy lives in
rustyn64-rdp; the Bus forwards each write there (StepResult::dp_write)
and this shadow is the RSP-local view — which is why reads of DP state the
RDP mutated on its own are not yet reflected here (Phase 3).
vu_ctrl: ControlThe VU’s three control registers (VCO, VCC, VCE).
div: DivideThe reciprocal unit’s staging latches (DIVIN/DIVOUT/DIVDP).
Implementations§
Source§impl Rsp
impl Rsp
Sourcepub const fn r(&self, i: usize) -> u32
pub const fn r(&self, i: usize) -> u32
Read one scalar register, with r0 pinned to zero.
Public so the vector load/store family can compute its base address —
those instructions live in crate::vu but address DMEM through a GPR.
Sourcepub const fn set_su(&mut self, i: usize, v: u32)
pub const fn set_su(&mut self, i: usize, v: u32)
Write one scalar register; writes to r0 are discarded.
Public under this name so the VU’s move instructions can reach it —
MFC2 and CFC2 write a GPR, and they live in crate::vu.
Sourcepub fn su_step(&mut self) -> StepResult
pub fn su_step(&mut self) -> StepResult
Execute one scalar instruction, if the core is running.
Returns what the step asked the machine to do; see StepResult.
Source§impl Rsp
impl Rsp
Sourcepub const fn vu_byte(&self, reg: usize, byte: usize) -> u8
pub const fn vu_byte(&self, reg: usize, byte: usize) -> u8
Read a byte of a vector register, big-endian within the 128 bits.
Byte 0 is the most significant half of lane 0, matching the wiki’s convention that byte indices count “from the higher part of the register (in big-endian order)”.
Sourcepub const fn set_vu_byte(&mut self, reg: usize, byte: usize, val: u8)
pub const fn set_vu_byte(&mut self, reg: usize, byte: usize, val: u8)
Write a byte of a vector register.
Sourcepub fn mfc2(&mut self, rt: usize, vs: usize, elem: usize) -> u32
pub fn mfc2(&mut self, rt: usize, vs: usize, elem: usize) -> u32
MFC2 — copy two bytes of a vector register into a GPR, sign-extended.
The pair is taken at a byte offset, so an odd offset straddles two lanes. At offset 15 the second byte wraps to byte 0 of the same register rather than reading past the end — a rule that is invisible until something actually addresses the last byte.
Sourcepub const fn mtc2(&mut self, value: u32, vs: usize, elem: usize)
pub const fn mtc2(&mut self, value: u32, vs: usize, elem: usize)
MTC2 — copy the low 16 bits of a GPR into a vector register at a byte
offset.
At offset 15 only one byte is written, taken from rt[15..8]: there
is no byte 16 to receive the other half, and unlike MFC2 it does not
wrap around to byte 0. The asymmetry between the two is deliberate on
hardware and is exactly what a lane-oriented implementation loses.
Source§impl Rsp
The 48-bit-per-lane accumulator, and the computational instructions.
impl Rsp
The 48-bit-per-lane accumulator, and the computational instructions.
§The accumulator is one 48-bit register per lane, not three 16-bit ones
VSAR slices it into ACC_HI (bits 47..32), ACC_MD (31..16) and ACC_LO
(15..0), which invites modeling it as three separate halfwords. It is not:
the multiply instructions write and accumulate across the full 48 bits, and
the extraction that produces vd reads a 32-bit window spanning two of
those slices. Splitting the storage makes carries between them disappear.
Sourcepub const fn vt_lane(&self, vt: usize, element: u32, lane: usize) -> u16
pub const fn vt_lane(&self, vt: usize, element: u32, lane: usize) -> u16
Broadcast-modified read of a vt lane (N64brew RSP CPU Core
§Broadcast modifier).
element 0 and 1 both mean “no broadcast” — the table lists them
separately and gives them identical lane sets, so this is not a
simplification.
Source§impl Rsp
The vector load/store family (Sprint 3, brought forward).
impl Rsp
The vector load/store family (Sprint 3, brought forward).
Encoding: LWC2/SWC2 | base (25..21) | vt (20..16) | opcode
(15..11) | element (10..7) | offset (6..0, signed 7-bit).
The offset is scaled by the access size, and element is a byte index
into the vector register naming the first byte the operation touches — so a
non-zero element means fewer bytes move, not a shifted window.
Source§impl Rsp
The single-lane reciprocal group: VRCP, VRSQ, their L/H partners,
VMOV and VNOP.
impl Rsp
The single-lane reciprocal group: VRCP, VRSQ, their L/H partners,
VMOV and VNOP.
These do not operate lane-wise. They read one lane of vt (selected by
the element field), write one lane of vd (selected by the de field,
which occupies the vs position), and set the whole accumulator low slice to
the broadcast vt.
Source§impl Rsp
impl Rsp
Sourcepub const fn pc(&self) -> u32
pub const fn pc(&self) -> u32
The RSP’s program counter into IMEM — the value execution actually uses.
Delegates to SP_STATUS’s register file rather than the struct field of
the same name, which is vestigial.
Returns u32, not the u16 the vestigial field used, because that is
SpRegs::pc()’s type and widening here would be a lossless re-narrowing
for every caller. The value is a 12-bit IMEM offset either way — su_step
masks it with 0xFFC — so the wider type costs nothing and avoids a cast
at every call site.
Sourcepub const fn halted(&self) -> bool
pub const fn halted(&self) -> bool
Is the RSP halted? This is SP_STATUS.halt, the flag that actually gates
Rsp::tick — not the vestigial halted field.
Sourcepub fn mem_read(&self, off: u32) -> u8
pub fn mem_read(&self, off: u32) -> u8
Read a byte of DMEM/IMEM as the CPU sees it.
Bit 12 of the folded offset selects IMEM over DMEM, and each bank wraps within its own 4 KiB — a transfer or access never spills from one into the other (N64brew RSP Interface §DMEM and IMEM).
Sourcepub const fn mem_write(&mut self, off: u32, val: u8)
pub const fn mem_write(&mut self, off: u32, val: u8)
Write a byte of DMEM/IMEM as the CPU sees it.
Sourcepub fn tick(&mut self) -> StepResult
pub fn tick(&mut self) -> StepResult
Advance the RSP by one instruction when running.
Returns what the step asked of the rest of the machine — see
su::StepResult. It reports rather than acting because the RSP
owns neither RDRAM nor the MI, and a chip reaching back into its owner is
the dependency cycle docs/architecture.md exists to prevent.
This is also why it no longer borrows a bus: the caller needed to move
the whole chip out of the Bus to satisfy the borrow checker, and moving
it out meant Default-constructing a replacement — two 4 KiB
allocations on every RCP step, behind a comment that claimed there were
none.