Skip to main content

Cop0

Struct Cop0 

Source
pub struct Cop0 { /* private fields */ }
Expand description

The VR4300 system control coprocessor register file.

Implementations§

Source§

impl Cop0

Source

pub const UNUSED: [u8; 7]

The COP0 register numbers that are “Reserved for future use” (UM Table 1-2, p. 46) and behave as a single shared write latch rather than as storage.

Writing one goes nowhere. Reading one returns the value of the most recent MTC0/DMTC0 to any COP0 register — so a write-then-read of register 7 returns what was written, and the same sequence with any other COP0 write in between returns that value instead.

Measured, replacing a guess. The manual says nothing about these, so this implementation previously discarded writes and read zero, recorded as accuracy-ledger U-1. n64-systemtest documents and exercises the real behavior, sweeping five written values against three interposed ones specifically so an emulator cannot pass by echoing the first.

Source

pub const fn new() -> Self

Cold-reset state (UM §6.4.4, p. 183; Fig. 6-16, p. 205).

Defined by the manual: Status.ERL and Status.BEV set, Status.TS/ SR/RP clear, Config.BE set, Config.EP clear, Random = 31, Wired = 0. Everything else the manual calls undefined, and is a deterministic zero here (ADR 0004).

Source

pub const fn set_now(&mut self, now: u64)

Advance the Count timeline to now (the scheduler’s count_ticks).

Called once per CPU step. Note this sets rather than increments: the position is derived, so a dropped or repeated call cannot desynchronize it from the master clock the way an increment would.

Source

pub const fn count_now(&self) -> u64

The current Count timeline position.

Source

pub const fn count(&self) -> u32

Count, computed rather than stored.

Source

pub const fn timer_matches(&self) -> bool

Has the timer fired — Count == Compare?

UM §6.3.4 (p. 165). The comparison is on the computed Count, so it stays true regardless of how the timeline was reached.

Source

pub const fn timer_edge(&mut self) -> bool

Has the timer just reached Compare — the rising edge of the match?

§Why the edge and not the equality

Both Count and Compare reset to zero, so a plain Cop0::timer_matches is true on the very first step and latches IP7 before a single instruction retires. n64-systemtest catches this exactly: it reads Cause during an AdEL exception and expects 0x10, while a spuriously-latched IP7 (bit 15) makes it 0x8010.

The timer fires when Count becomes equal to Compare, which is once per wrap of the 32-bit counter, not continuously while they happen to be equal. Tracking the previous value is what distinguishes the two, and at power-on there is no transition into equality — the two simply start there.

§Why this asks whether Compare was crossed, not whether it is hit

Count is derived from the master clock, so it keeps advancing while the pipeline is stalled — this poll is not guaranteed to land on every value the counter takes. An instantaneous now == Compare test therefore misses the match whenever the gap between two polls steps over it, and a 69-PCycle multiply interlock (UM Table 3-12) steps over it comfortably. The failure that produces is not a late interrupt but a lost one: IP7 never latches, and software waiting on the timer hangs forever.

So the question is whether Compare lies in the half-open interval (last_count, now], in wrapping arithmetic so a counter wrap is just another interval. Excluding last_count itself preserves the edge semantics above: sitting on Compare is not a transition into it.

Source

pub const fn set_ip(&mut self, bit: u8, on: bool)

Set or clear a Cause.IP bit.

bit is 0..=7. IP1:IP0 are software interrupts and are written through MTC0 instead; this is the hardware path for IP2 (RCP) and IP7 (timer).

Source

pub const fn interrupt_pending(&self) -> bool

Is an interrupt currently recognized?

All four conditions, and each one matters (UM §6.1 p. 160, §6.3.5 p. 168, Fig. 14-4 p. 357):

  • Status.IE — the global enable.
  • Status.EXL clear — a handler is not interrupted by the thing it is handling. This is why EXL implies interrupts-off without IE being touched.
  • Status.ERL clear — likewise for the error path.
  • Cause.IP & Status.IM — at least one pending and unmasked.

Dropping the EXL/ERL terms is the classic version of this bug: it works until the first interrupt arrives inside a handler, and then re-enters it forever.

Source

pub const fn read(&self, n: u8) -> u64

Read a register’s architectural value.

ARCH_MASK is applied here as well as on the way in, so a 32-bit register can never return non-zero upper bits and EntryHi.Fill always reads zero — regardless of how the stored value arrived. That matters because Cop0::set_hardware exists precisely to bypass the write masks, and exception dispatch will feed it raw faulting addresses.

Source

pub const fn is_unused(n: u8) -> bool

Is this one of the Cop0::UNUSED register numbers?

Source

pub const fn write(&mut self, n: u8, value: u64)

Write a register, applying its writable-bit mask.

Bits outside WRITE_MASK keep their previous value, which is what hardware does and is not the same as writing zero to them.

Registers 7, 21..=25 and 31 are “Reserved for future use” (UM Table 1-2, p. 46) and are not storage — see Cop0::UNUSED. Writes to them go nowhere; reads return the shared write latch.

Source

pub const fn set_hardware(&mut self, n: u8, value: u64)

Force a value past the writable-bit mask, for hardware-owned fields.

Exception dispatch writes Cause.ExcCode, EPC and BadVAddr, all of which are read-only or partly read-only to software. Routing those through Cop0::write would require widening the masks, which would also let MTC0 write them — the exact bug the masks exist to prevent.

Source

pub const fn mfc0(&self, n: u8) -> u64

MFC0 rt, rd — read the low 32 bits, sign-extended into the 64-bit GPR.

Sign-extension applies even to a register that is architecturally 64 bits wide: MFC0 is defined as a 32-bit move, so MFC0 of an EPC whose bit 31 is set yields a sign-extended value, not a truncated one.

Source

pub const fn dmfc0(&self, n: u8) -> u64

DMFC0 rt, rd — read the full 64 bits.

On a 32-bit-wide register this is the same as Cop0::mfc0 except for sign-extension: the upper half is zero rather than a copy of bit 31.

Source

pub const fn mtc0(&mut self, n: u8, value: u64)

MTC0 rd, rt — write the low 32 bits.

For a 64-bit register the upper half is cleared, not preserved: the value written is the sign-extended 32-bit operand, which is how software legitimately writes a KSEG0 address into a 64-bit register.

Source

pub const fn dmtc0(&mut self, n: u8, value: u64)

DMTC0 rd, rt — write the full 64 bits.

Source

pub const fn tick_random(&mut self)

Advance Random by one instruction (UM §5.4.2, p. 147).

“decrements as each instruction executes”, reloading 31 when it reaches the Wired floor.

§It is a plain 6-bit down-counter, and that matters when Wired > 31

The reload fires on cur == wired, not cur <= wired, and the decrement wraps 0 → 63. For the ordinary case (Wired <= 31) the two readings agree: the counter walks 31 down to Wired either way.

They diverge once Wired exceeds 31, which software can arrange because Wired is six bits wide. Under <= the counter is immediately at or below the floor and pins at 31 forever; under == it walks 31 → 0 → 63 → Wired, covering the whole range. n64-systemtest checks exactly that, and sampling a pinned register cannot be distinguished from sampling a slow one without it.

Trait Implementations§

Source§

impl Clone for Cop0

Source§

fn clone(&self) -> Cop0

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Cop0

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Cop0

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Cop0

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for Cop0

Source§

fn eq(&self, other: &Cop0) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for Cop0

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Eq for Cop0

Source§

impl StructuralPartialEq for Cop0

Auto Trait Implementations§

§

impl Freeze for Cop0

§

impl RefUnwindSafe for Cop0

§

impl Send for Cop0

§

impl Sync for Cop0

§

impl Unpin for Cop0

§

impl UnsafeUnpin for Cop0

§

impl UnwindSafe for Cop0

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,