rustyn64_cpu/fpu.rs
1//! FPU arithmetic (T-13-002).
2//!
3//! Pure functions over IEEE-754 values, with the VR4300's `FCSR` semantics
4//! layered on top. Kept separate from [`crate::fpr`] (the register file) and
5//! [`crate::cop1`] (the control registers) so each can be tested without the
6//! others.
7//!
8//! # Rounding
9//!
10//! `FCSR.RM` selects the mode (UM §7.2.4): 0 nearest-even, 1 toward zero,
11//! 2 toward +∞, 3 toward −∞.
12//!
13//! **Every operation here honors it**, arithmetic included. The four
14//! arithmetic operations are adapters over [`crate::softfloat`], which takes
15//! the mode as a parameter and rounds exactly once; the conversions take a
16//! [`Rounding`] directly.
17//!
18//! This paragraph has been wrong in both directions and is worth reading
19//! carefully before it is edited again. It first claimed the modes applied
20//! throughout while the arithmetic ignored them; it was then corrected to say
21//! the arithmetic could not honor them, which became false when the soft-float
22//! path landed. See `docs/engineering-lessons.md` §3.3c — the rule is that a
23//! comment asserting *what the code does* goes stale silently, because no test
24//! fails when it is wrong.
25//!
26//! # What is deliberately absent
27//!
28//! The **FP multiplication erratum** is not here. It is a property of specific
29//! early console revisions (`n64brew_wiki/markdown/VR4300.md`) and belongs with
30//! the revision model, not with the arithmetic; implementing it inline would
31//! make every multiply on every console wrong.
32
33// Two lints are allowed for this module, both because the thing they warn about
34// is the thing being modeled:
35//
36// * `cast_precision_loss` -- an FPU's conversion instructions exist precisely
37// to lose precision in a defined way. The loss is the behavior, and it is
38// reported through the Inexact flag rather than avoided.
39// * `float_cmp` -- `C.EQ` is IEEE equality and the exactness checks are exact
40// by definition. An epsilon here would make the emulator report relations
41// and flags the hardware does not.
42#![allow(clippy::cast_precision_loss, clippy::float_cmp)]
43
44use serde::{Deserialize, Serialize};
45
46/// Which VR4300 stepping a console carries.
47///
48/// The only behavior that currently depends on it is the **FP multiplication
49/// erratum**, and that dependency is why this is modeled as console state
50/// rather than folded into `mul`: an unconditional erratum would make every
51/// multiply on *every* console wrong, and most consoles do not have it.
52#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
53pub enum Stepping {
54 /// A later stepping, with the multiplication erratum **fixed**.
55 ///
56 /// The default, because it is the majority of hardware and because the
57 /// erratum's output is undocumented — see [`Stepping::has_mul_erratum`].
58 #[default]
59 Fixed,
60 /// An early stepping carrying the erratum: NUS-01 and NUS-02 (Japan only)
61 /// and NUS-03 (the first US revision).
62 Early,
63}
64
65impl Stepping {
66 /// Does this stepping carry the FP multiplication erratum?
67 ///
68 /// # Why the erratum is not implemented
69 ///
70 /// The trigger is documented (`n64brew_wiki/markdown/VR4300.md`): a
71 /// multiply whose *preceding* multiply had a NaN, zero or infinity operand
72 /// *"may produce unexpected results"*. GCC's `-mfix4300` works around it by
73 /// inserting two `nop`s after every `MUL.S`/`MUL.D`/`MULT`.
74 ///
75 /// **What the corrupted output actually is has never been characterized** —
76 /// our own `ref-docs/2026-07-20-vr4300-timing-supplement.md` lists it under
77 /// undocumented constants, with only trigger conditions known. So the
78 /// erratum can be *detected* here but not *reproduced*, and inventing a
79 /// plausible wrong value would be exactly the fitted-constant failure
80 /// `docs/accuracy-ledger.md` forbids: every later result built on it would
81 /// stop being evidence.
82 ///
83 /// Accuracy-ledger **U-7**. Selecting [`Stepping::Early`] therefore changes
84 /// nothing yet — it exists so that when the output *is* characterized, the
85 /// switch is already in the right place rather than threaded through
86 /// afterwards.
87 #[must_use]
88 pub const fn has_mul_erratum(self) -> bool {
89 matches!(self, Self::Early)
90 }
91}
92
93/// Would the erratum's trigger condition fire for this multiply?
94///
95/// True when an operand of the **previous** multiply was a NaN, zero or
96/// infinity. Exposed so a future characterization has a tested trigger to hang
97/// the corrupted output on, and so a trace can flag affected instructions today.
98#[must_use]
99pub fn mul_erratum_triggers(prev_a: f64, prev_b: f64) -> bool {
100 let suspicious = |v: f64| v.is_nan() || v == 0.0 || v.is_infinite();
101 suspicious(prev_a) || suspicious(prev_b)
102}
103
104/// The `FCSR` cause/flag bits an operation can raise (UM §7.2.2).
105///
106/// # Completeness
107///
108/// All five are modeled for the four arithmetic operations, which compute
109/// through [`crate::softfloat`] and so have the exact pre-rounding result
110/// available. `inexact` and `underflow` were the two that previously never set
111/// for ordinary rounding — accuracy-ledger C-11 — and detecting them is
112/// precisely why that module exists.
113///
114/// Still **not** produced anywhere: the unmaskable *unimplemented-operation*
115/// cause (bit 17), which the VR4300 raises for subnormal operands and results.
116/// It is not an IEEE exception and is not part of this struct; see
117/// [`CAUSE_UNIMPLEMENTED`].
118///
119/// Returned rather than written, because `FCSR` belongs to
120/// [`crate::cop1::Cop1Control`] and an arithmetic helper that reached into it
121/// would need to own it.
122// Five bools, one per IEEE exception. Clippy suggests a bitflags type; the
123// architectural field IS five independent conditions that can co-occur (an
124// overflow is also inexact), and naming them costs nothing at this size.
125#[allow(clippy::struct_excessive_bools)]
126#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
127pub struct Flags {
128 /// Invalid operation — a signaling NaN, or an undefined form like `0 × ∞`.
129 pub invalid: bool,
130 /// Division by zero, with a finite non-zero numerator.
131 pub div_by_zero: bool,
132 /// The result overflowed the format's range.
133 pub overflow: bool,
134 /// The result underflowed to a subnormal or zero.
135 pub underflow: bool,
136 /// The result was not exactly representable.
137 pub inexact: bool,
138}
139
140impl Flags {
141 /// No exceptions raised.
142 pub const NONE: Self = Self {
143 invalid: false,
144 div_by_zero: false,
145 overflow: false,
146 underflow: false,
147 inexact: false,
148 };
149
150 /// Just the invalid-operation flag.
151 pub const INVALID: Self = Self {
152 invalid: true,
153 ..Self::NONE
154 };
155
156 /// Pack into the `FCSR` **Cause** bits 16..=12 and **Flags** bits 6..=2.
157 ///
158 /// Both at once because hardware sets them together: `Cause` is what the
159 /// current operation raised, `Flags` is the sticky accumulation.
160 ///
161 /// **Bit 17 (`Unimplemented Operation`) is not produced here** — it is not
162 /// an IEEE exception, and it is raised on its own by the conversions via
163 /// [`CAUSE_UNIMPLEMENTED`]. Documenting the range as 17..=12 implied this
164 /// helper could set it, which it cannot.
165 #[must_use]
166 pub const fn to_fcsr_bits(self) -> u32 {
167 let mut cause = 0u32;
168 let mut flags = 0u32;
169 if self.invalid {
170 cause |= 1 << 16;
171 flags |= 1 << 6;
172 }
173 if self.div_by_zero {
174 cause |= 1 << 15;
175 flags |= 1 << 5;
176 }
177 if self.overflow {
178 cause |= 1 << 14;
179 flags |= 1 << 4;
180 }
181 if self.underflow {
182 cause |= 1 << 13;
183 flags |= 1 << 3;
184 }
185 if self.inexact {
186 cause |= 1 << 12;
187 flags |= 1 << 2;
188 }
189 cause | flags
190 }
191}
192
193/// The rounding mode from `FCSR.RM` (UM §7.2.4).
194#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
195pub enum Rounding {
196 /// Round to nearest, ties to even. The IEEE default and `FCSR.RM = 0`.
197 Nearest,
198 /// Toward zero (truncate).
199 TowardZero,
200 /// Toward +∞.
201 TowardPlusInf,
202 /// Toward −∞.
203 TowardMinusInf,
204}
205
206impl Rounding {
207 /// Decode `FCSR.RM` (bits 1..=0).
208 #[must_use]
209 pub const fn from_rm(rm: u8) -> Self {
210 match rm & 0b11 {
211 1 => Self::TowardZero,
212 2 => Self::TowardPlusInf,
213 3 => Self::TowardMinusInf,
214 _ => Self::Nearest,
215 }
216 }
217}
218
219/// A result plus the flags producing it raised.
220///
221/// Deliberately **not** `Eq`: `T` is a float, and `NaN != NaN`. Deriving `Eq`
222/// would assert a reflexivity that FP values do not have.
223#[allow(
224 clippy::derived_hash_with_manual_eq,
225 clippy::derive_partial_eq_without_eq
226)]
227#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
228pub struct Outcome<T> {
229 /// The computed value.
230 pub value: T,
231 /// What the operation raised.
232 pub flags: Flags,
233}
234
235impl<T> Outcome<T> {
236 /// Did the operation underflow?
237 #[must_use]
238 pub const fn underflowed(&self) -> bool {
239 self.flags.underflow
240 }
241}
242
243/// Is this `f32` a **signaling** NaN *as the VR4300 classifies one*?
244///
245/// # The convention is inverted from IEEE-754:2008
246///
247/// IEEE-754:2008 says the significand's MSB **set** means *quiet*. The VR4300
248/// predates that edition and uses the **legacy MIPS convention**, where the
249/// significand MSB **set** means *signaling*. So `0x7FC0_0000` — the pattern
250/// every modern language calls a quiet NaN, and what Rust's `f32::NAN` is — is
251/// a **signaling** NaN to this processor, and raises Invalid.
252///
253/// # How this was established
254///
255/// Not from a manual: from n64-systemtest's own expectations, which name their
256/// constants by the IEEE convention and then assert the opposite behavior.
257/// For a non-signaling compare (`C.EQ`, `C.F`, …) it expects
258/// `QUIET_NAN_START_32` (`0x7FC0_0000`, MSB set) to raise Invalid and
259/// `SIGNALING_NAN_END_32` (`0x7FBF_FFFF`, MSB clear) to raise nothing. The
260/// signaling compare forms (`C.SF`, `C.SEQ`, …) raise Invalid for both, which
261/// is the ordinary IEEE rule for those forms and so does not distinguish them.
262///
263/// The corroboration that makes this more than a curve fit: the VR4300's own
264/// default NaN result is `0x7FBF_FFFF`, MSB **clear**. Under IEEE that would be
265/// a processor whose invalid-operation result is a *signaling* NaN — absurd,
266/// since it would re-trap on first use. Under this convention it is exactly
267/// what it should be, a quiet one.
268///
269/// Accuracy ledger **C-12**.
270#[must_use]
271pub const fn is_snan_f32(v: f32) -> bool {
272 let b = v.to_bits();
273 // Exponent all ones, significand MSB SET. No payload check is needed: the
274 // MSB being set already makes the significand non-zero, so this cannot
275 // catch an infinity.
276 b & 0x7F80_0000 == 0x7F80_0000 && b & 0x0040_0000 != 0
277}
278
279/// Is this `f64` a **signaling** NaN as the VR4300 classifies one?
280///
281/// See [`is_snan_f32`] — the convention is inverted from IEEE-754:2008.
282#[must_use]
283pub const fn is_snan_f64(v: f64) -> bool {
284 let b = v.to_bits();
285 b & 0x7FF0_0000_0000_0000 == 0x7FF0_0000_0000_0000 && b & 0x0008_0000_0000_0000 != 0
286}
287
288/// Is this a **subnormal** — a non-zero value with a zero exponent field?
289///
290/// Load-bearing on the VR4300 in a way it is not on most FPUs: this processor
291/// **cannot compute with subnormals at all**. A subnormal operand, or a
292/// subnormal result with `FCSR.FS` clear, raises the unmaskable
293/// *unimplemented operation* cause rather than producing a number. See
294/// [`arith_unimplemented_s`].
295#[must_use]
296pub const fn is_subnormal_f32(v: f32) -> bool {
297 let b = v.to_bits();
298 b & 0x7F80_0000 == 0 && b & 0x007F_FFFF != 0
299}
300
301/// Is this `f64` a subnormal? See [`is_subnormal_f32`].
302#[must_use]
303pub const fn is_subnormal_f64(v: f64) -> bool {
304 let b = v.to_bits();
305 b & 0x7FF0_0000_0000_0000 == 0 && b & 0x000F_FFFF_FFFF_FFFF != 0
306}
307
308/// Is this a NaN the VR4300's arithmetic cannot handle?
309///
310/// A NaN with the significand MSB **clear** — quiet by this processor's own
311/// inverted convention (ledger C-12). It cannot propagate one in hardware, so
312/// arithmetic on it raises *unimplemented operation*.
313///
314/// The complementary case is the opposite of what IEEE would lead you to
315/// expect: an MSB-**set** NaN is *signaling* here and raises the ordinary
316/// Invalid, which [`is_snan_f32`] covers. Both NaN classes trap — they trap
317/// **differently**, and swapping them is invisible until `FCSR` is read back.
318#[must_use]
319pub const fn is_unimplemented_nan_f32(v: f32) -> bool {
320 let b = v.to_bits();
321 b & 0x7F80_0000 == 0x7F80_0000 && b & 0x007F_FFFF != 0 && b & 0x0040_0000 == 0
322}
323
324/// See [`is_unimplemented_nan_f32`].
325#[must_use]
326pub const fn is_unimplemented_nan_f64(v: f64) -> bool {
327 let b = v.to_bits();
328 b & 0x7FF0_0000_0000_0000 == 0x7FF0_0000_0000_0000
329 && b & 0x000F_FFFF_FFFF_FFFF != 0
330 && b & 0x0008_0000_0000_0000 == 0
331}
332
333/// Do these arithmetic operands force *unimplemented operation* before the
334/// operation is even attempted?
335///
336/// **Operand subnormality wins over everything**, including a NaN that would
337/// otherwise raise Invalid: n64-systemtest pairs a quiet NaN with a subnormal
338/// and expects unimplemented, not invalid.
339///
340/// Applies to `ADD`/`SUB`/`MUL`/`DIV` only. **Compares are exempt** — the
341/// suite's compare tests expect a subnormal operand to compare as an ordinary
342/// number with no flags at all. Worth stating, because "this FPU cannot do
343/// subnormals" sounds like it should be universal and is not.
344#[must_use]
345pub const fn arith_unimplemented_s(a: f32, b: f32) -> bool {
346 is_subnormal_f32(a)
347 || is_subnormal_f32(b)
348 || is_unimplemented_nan_f32(a)
349 || is_unimplemented_nan_f32(b)
350}
351
352/// See [`arith_unimplemented_s`].
353#[must_use]
354pub const fn arith_unimplemented_d(a: f64, b: f64) -> bool {
355 is_subnormal_f64(a)
356 || is_subnormal_f64(b)
357 || is_unimplemented_nan_f64(a)
358 || is_unimplemented_nan_f64(b)
359}
360
361/// Flush a subnormal result to zero or to the smallest normal, per `FCSR.FS`.
362///
363/// **Which one depends on the rounding mode**, and that is the part most easily
364/// got wrong: "flush to zero" is the whole story only for round-to-nearest and
365/// toward-zero. A directed mode that rounds *away* from zero must deliver the
366/// smallest **normal** of that sign instead, because zero is on the wrong side
367/// of the true result.
368///
369/// n64-systemtest pins all four: a tiny negative result gives `-0` under
370/// nearest, toward-zero and toward-`+inf`, and `-f32::MIN_POSITIVE` under
371/// toward-`-inf`.
372#[must_use]
373pub fn flush_subnormal_f32(v: f32, mode: Rounding) -> f32 {
374 let negative = v.is_sign_negative();
375 if flushes_away_from_zero(negative, mode) {
376 if negative {
377 -f32::MIN_POSITIVE
378 } else {
379 f32::MIN_POSITIVE
380 }
381 } else if negative {
382 -0.0
383 } else {
384 0.0
385 }
386}
387
388/// See [`flush_subnormal_f32`].
389#[must_use]
390pub fn flush_subnormal_f64(v: f64, mode: Rounding) -> f64 {
391 let negative = v.is_sign_negative();
392 if flushes_away_from_zero(negative, mode) {
393 if negative {
394 -f64::MIN_POSITIVE
395 } else {
396 f64::MIN_POSITIVE
397 }
398 } else if negative {
399 -0.0
400 } else {
401 0.0
402 }
403}
404
405/// Does the rounding mode push this sign away from zero?
406const fn flushes_away_from_zero(negative: bool, mode: Rounding) -> bool {
407 match mode {
408 Rounding::Nearest | Rounding::TowardZero => false,
409 Rounding::TowardPlusInf => !negative,
410 Rounding::TowardMinusInf => negative,
411 }
412}
413
414/// `ADD.S`, `SUB.S`, `MUL.S`, `DIV.S` and their `.D` counterparts.
415///
416/// Each is a thin adapter over [`crate::softfloat`], which computes the
417/// correctly-rounded result **and** the exact IEEE exception flags in one pass.
418///
419/// # Why these are not `a + b`
420///
421/// The native operators produce the right value and discard everything else:
422/// there is no way to ask them whether the operation was inexact, whether it
423/// underflowed, or what a directed rounding mode would have given. The VR4300
424/// reports all three through `FCSR`, so an FPU built on `+`/`-`/`*`/`/` is
425/// bit-exact on values and silently wrong on flags — which is where accuracy
426/// ledger **C-11** found it, with `inexact` set only as a side effect of
427/// overflow and `underflow` never set at all.
428///
429/// The soft-float path is checked against those same native operators as an
430/// independent oracle: in round-to-nearest its results must be bit-identical
431/// across a large pseudo-random corpus in both formats. See
432/// `softfloat::tests`.
433fn arith_s(a: f32, b: f32, op: u8, mode: Rounding) -> Outcome<f32> {
434 use crate::softfloat::{self, F32};
435 let (a_bits, b_bits) = (u64::from(a.to_bits()), u64::from(b.to_bits()));
436 let out = match op {
437 0 => softfloat::add(a_bits, b_bits, F32, mode),
438 1 => softfloat::sub(a_bits, b_bits, F32, mode),
439 2 => softfloat::mul(a_bits, b_bits, F32, mode),
440 _ => softfloat::div(a_bits, b_bits, F32, mode),
441 };
442 Outcome {
443 value: f32::from_bits(out.bits as u32),
444 flags: out.flags,
445 }
446}
447
448fn arith_d(a: f64, b: f64, op: u8, mode: Rounding) -> Outcome<f64> {
449 use crate::softfloat::{self, F64};
450 let (a_bits, b_bits) = (a.to_bits(), b.to_bits());
451 let out = match op {
452 0 => softfloat::add(a_bits, b_bits, F64, mode),
453 1 => softfloat::sub(a_bits, b_bits, F64, mode),
454 2 => softfloat::mul(a_bits, b_bits, F64, mode),
455 _ => softfloat::div(a_bits, b_bits, F64, mode),
456 };
457 Outcome {
458 value: f64::from_bits(out.bits),
459 flags: out.flags,
460 }
461}
462
463/// `ADD.S`.
464#[must_use]
465pub fn add_s(a: f32, b: f32, mode: Rounding) -> Outcome<f32> {
466 arith_s(a, b, 0, mode)
467}
468
469/// `SUB.S`.
470#[must_use]
471pub fn sub_s(a: f32, b: f32, mode: Rounding) -> Outcome<f32> {
472 arith_s(a, b, 1, mode)
473}
474
475/// `MUL.S`.
476///
477/// **Does not model the VR4300 multiplication erratum** — see the module docs.
478#[must_use]
479pub fn mul_s(a: f32, b: f32, mode: Rounding) -> Outcome<f32> {
480 arith_s(a, b, 2, mode)
481}
482
483/// `DIV.S`.
484#[must_use]
485pub fn div_s(a: f32, b: f32, mode: Rounding) -> Outcome<f32> {
486 arith_s(a, b, 3, mode)
487}
488
489/// `ADD.D`.
490#[must_use]
491pub fn add_d(a: f64, b: f64, mode: Rounding) -> Outcome<f64> {
492 arith_d(a, b, 0, mode)
493}
494
495/// `SUB.D`.
496#[must_use]
497pub fn sub_d(a: f64, b: f64, mode: Rounding) -> Outcome<f64> {
498 arith_d(a, b, 1, mode)
499}
500
501/// `MUL.D`.
502#[must_use]
503pub fn mul_d(a: f64, b: f64, mode: Rounding) -> Outcome<f64> {
504 arith_d(a, b, 2, mode)
505}
506
507/// `DIV.D`.
508#[must_use]
509pub fn div_d(a: f64, b: f64, mode: Rounding) -> Outcome<f64> {
510 arith_d(a, b, 3, mode)
511}
512
513/// `ABS.S` — clears the sign bit.
514///
515/// Written as an explicit bit clear rather than `f32::abs`. The two are
516/// **equivalent**, including for NaN payloads — mutation testing confirmed
517/// swapping them changes nothing — so this is a readability choice, not a
518/// correctness one: the hardware operation *is* a bit clear, and saying so makes
519/// the NaN-payload behavior obvious instead of something to look up.
520#[must_use]
521pub const fn abs_s(a: f32) -> f32 {
522 f32::from_bits(a.to_bits() & 0x7FFF_FFFF)
523}
524
525/// `NEG.S` — flips the sign bit.
526#[must_use]
527pub const fn neg_s(a: f32) -> f32 {
528 f32::from_bits(a.to_bits() ^ 0x8000_0000)
529}
530
531/// `ABS.D`.
532#[must_use]
533pub const fn abs_d(a: f64) -> f64 {
534 f64::from_bits(a.to_bits() & 0x7FFF_FFFF_FFFF_FFFF)
535}
536
537/// `NEG.D`.
538#[must_use]
539pub const fn neg_d(a: f64) -> f64 {
540 f64::from_bits(a.to_bits() ^ 0x8000_0000_0000_0000)
541}
542
543/// The outcome of an ordered comparison: which of the three mutually exclusive
544/// relations holds.
545///
546/// Named rather than three bools, because exactly one is true and a bool triple
547/// makes the impossible states representable.
548#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
549pub enum Relation {
550 /// `fs < ft`.
551 Less,
552 /// `fs == ft`.
553 Equal,
554 /// `fs > ft`.
555 Greater,
556 /// At least one operand is a NaN, so no ordering exists.
557 Unordered,
558}
559
560/// `C.cond.fmt` — compare two single-precision values.
561///
562/// # The condition encoding
563///
564/// The 4-bit `cond` field is **systematic**, not sixteen unrelated mnemonics
565/// (UM Table 7-11):
566///
567/// | Bit | Meaning |
568/// | --- | --- |
569/// | 3 | raise Invalid when the operands are **unordered** (the signaling forms) |
570/// | 2 | true when `fs < ft` |
571/// | 1 | true when `fs == ft` |
572/// | 0 | true when the operands are **unordered** |
573///
574/// So `C.EQ` is `cond = 2`, `C.OLT` is `4`, `C.OLE` is `6`, `C.UN` is `1`, and
575/// each signaling variant is its ordinary form plus 8. Writing the sixteen
576/// mnemonics as sixteen cases invites getting one wrong; deriving them from the
577/// bits makes all sixteen correct or none.
578///
579/// Note **`Greater` appears in no bit**: `fs > ft` is simply "none of less,
580/// equal or unordered", which is why three condition bits suffice. Software
581/// tests it by branching on the complement of `C.OLE`.
582#[must_use]
583pub fn compare_s(a: f32, b: f32, cond: u8) -> Outcome<bool> {
584 let rel = relation_f32(a, b);
585 compare_result(rel, cond, is_snan_f32(a) || is_snan_f32(b))
586}
587
588/// `C.cond.fmt` — compare two double-precision values.
589#[must_use]
590pub fn compare_d(a: f64, b: f64, cond: u8) -> Outcome<bool> {
591 let rel = relation_f64(a, b);
592 compare_result(rel, cond, is_snan_f64(a) || is_snan_f64(b))
593}
594
595/// Which relation holds between two `f32`s.
596///
597/// Exact equality is **correct here and epsilon comparison would be wrong**:
598/// `C.EQ` is defined as IEEE equality, not approximate equality, so a tolerance
599/// would make the instruction report a relation the hardware does not.
600fn relation_f32(a: f32, b: f32) -> Relation {
601 if a.is_nan() || b.is_nan() {
602 Relation::Unordered
603 } else if a < b {
604 Relation::Less
605 } else if a == b {
606 Relation::Equal
607 } else {
608 Relation::Greater
609 }
610}
611
612/// Which relation holds between two `f64`s.
613///
614/// Exact equality is **correct here and epsilon comparison would be wrong**:
615/// `C.EQ` is defined as IEEE equality, not approximate equality, so a tolerance
616/// would make the instruction report a relation the hardware does not.
617fn relation_f64(a: f64, b: f64) -> Relation {
618 if a.is_nan() || b.is_nan() {
619 Relation::Unordered
620 } else if a < b {
621 Relation::Less
622 } else if a == b {
623 Relation::Equal
624 } else {
625 Relation::Greater
626 }
627}
628
629/// Apply the condition bits to a computed relation.
630fn compare_result(rel: Relation, cond: u8, snan: bool) -> Outcome<bool> {
631 let unordered = matches!(rel, Relation::Unordered);
632 let value = (matches!(rel, Relation::Less) && cond & 0b100 != 0)
633 || (matches!(rel, Relation::Equal) && cond & 0b010 != 0)
634 || (unordered && cond & 0b001 != 0);
635
636 let mut flags = Flags::NONE;
637 // Bit 3 selects the SIGNALING forms, which raise Invalid on *any*
638 // unordered comparison -- including one caused by a merely quiet NaN. That
639 // is the whole difference between `C.EQ` and `C.SEQ`, and it is why the
640 // quiet/signaling test used elsewhere is not sufficient on its own here.
641 if unordered && cond & 0b1000 != 0 {
642 flags.invalid = true;
643 }
644 // A signaling NaN operand raises Invalid whatever the condition.
645 if snan {
646 flags.invalid = true;
647 }
648 Outcome { value, flags }
649}
650
651/// `FCSR.Cause` — bits **17:12**, the field an operation replaces wholesale.
652///
653/// **UM §7.2.4, Figure 7-2** ("Control/Status Register Bit Assignments") gives
654/// `FCR31`'s layout with `Cause` spanning 17:12, six bits wide. **Figure 7-3**
655/// then names the bits, and the asymmetry below is visible in it directly:
656/// `Cause` carries `E V Z O U I` while `Enables` and `Flags` carry only
657/// `V Z O U I`.
658///
659/// **17:12, not 16:12.** That extra bit is `Cause.E`, Unimplemented Operation —
660/// part of `Cause` despite having no `Enable` bit and no sticky `Flags` twin,
661/// which makes this mask the *only* thing that ever clears it. A narrower 16:12
662/// mask left bit 17 permanently set once raised: a shipped bug in this project's
663/// history, and the reason the constant lives here rather than being written out
664/// at each site. It had reached four copies.
665///
666/// Only the five *maskable* conditions live in 16:12; that narrower range is what
667/// an enable comparison uses, and it is a different statement from this one.
668pub const CAUSE_MASK: u32 = 0x3F << 12;
669
670/// The `Unimplemented Operation` cause bit (`FCSR` bit 17).
671///
672/// Distinct from Invalid: it means *"this processor cannot do this in
673/// hardware — trap to software"*, not *"the operation is mathematically
674/// undefined"*. The VR4300 uses it for the long-integer conversion restriction
675/// below, which is a **hardware limitation**, not a numerical error.
676pub const CAUSE_UNIMPLEMENTED: u32 = 1 << 17;
677
678/// Does a 64-bit integer satisfy `CVT.[S,D].L`'s range restriction?
679///
680/// > *"When converting a long integer to a single- or double-precision
681/// > floating-point number (`CVT.[S,D].L`), bits 63:55 of the 64-bit integer
682/// > must be all zeroes or ones, otherwise the VR4300 processor raises a
683/// > floating-point instruction exception."* — UM §7.5.2
684///
685/// This is a **VR4300-specific hardware limitation**, not IEEE behavior: the
686/// value is perfectly representable, the processor simply declines. An emulator
687/// that converts it anyway produces a *correct* number where hardware traps, so
688/// software's fixup path never runs and the difference surfaces far downstream.
689#[must_use]
690pub const fn long_convertible(v: i64) -> bool {
691 let top = (v >> 55) & 0x1FF;
692 top == 0 || top == 0x1FF
693}
694
695/// `CVT.D.S` — single to double. Always exact: every `f32` is an `f64`.
696#[must_use]
697pub fn cvt_d_s(v: f32) -> Outcome<f64> {
698 Outcome {
699 value: f64::from(v),
700 flags: if is_snan_f32(v) {
701 Flags::INVALID
702 } else {
703 Flags::NONE
704 },
705 }
706}
707
708/// `CVT.S.D` — double to single. Can overflow, underflow or lose precision.
709#[must_use]
710pub fn cvt_s_d(v: f64) -> Outcome<f32> {
711 let value = v as f32;
712 let mut flags = Flags::NONE;
713 if is_snan_f64(v) {
714 flags.invalid = true;
715 }
716 if v.is_finite() {
717 if value.is_infinite() {
718 flags.overflow = true;
719 flags.inexact = true;
720 } else if value == 0.0 && v != 0.0 {
721 // A non-zero double that narrows to zero has underflowed -- the
722 // one underflow case this module can detect without the exact
723 // pre-rounding result.
724 flags.underflow = true;
725 flags.inexact = true;
726 } else if f64::from(value) != v {
727 flags.inexact = true;
728 }
729 }
730 Outcome { value, flags }
731}
732
733/// `2^63`, the bound an `i64` conversion must stay strictly below.
734const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
735
736/// `2^52` — above this magnitude every `f64` is already an integer, so the
737/// rounding helpers can return the value untouched.
738const F64_INTEGRAL_THRESHOLD: f64 = 4_503_599_627_370_496.0;
739
740/// `|v|`, by clearing the sign bit.
741///
742/// `f64::abs` and friends live in `std`; this crate is `#![no_std]`, so the
743/// handful of float operations the FPU needs are implemented here rather than
744/// pulling in `libm` for four functions.
745const fn fabs(v: f64) -> f64 {
746 f64::from_bits(v.to_bits() & 0x7FFF_FFFF_FFFF_FFFF)
747}
748
749/// Truncate toward zero.
750fn trunc(v: f64) -> f64 {
751 if !v.is_finite() || fabs(v) >= F64_INTEGRAL_THRESHOLD {
752 // Already integral (or not a number), so there is nothing to remove.
753 return v;
754 }
755 (v as i64) as f64
756}
757
758/// Round toward −∞.
759fn floor(v: f64) -> f64 {
760 let t = trunc(v);
761 if v < 0.0 && t != v { t - 1.0 } else { t }
762}
763
764/// Round toward +∞.
765fn ceil(v: f64) -> f64 {
766 let t = trunc(v);
767 if v > 0.0 && t != v { t + 1.0 } else { t }
768}
769
770/// Round to nearest, **ties to even**.
771///
772/// Not "round half away from zero", which is what most `round` functions do and
773/// which no MIPS rounding mode selects.
774fn round_ties_even(v: f64) -> f64 {
775 if !v.is_finite() || fabs(v) >= F64_INTEGRAL_THRESHOLD {
776 return v;
777 }
778 let f = floor(v);
779 let diff = v - f;
780 if diff > 0.5 {
781 f + 1.0
782 } else if diff < 0.5 {
783 f
784 } else if (f as i64) % 2 == 0 {
785 // Exactly halfway: pick the even neighbor.
786 f
787 } else {
788 f + 1.0
789 }
790}
791
792/// Round a float to an integer under an explicit [`Rounding`] mode.
793///
794/// Split out because `CVT.W`, `ROUND.W`, `TRUNC.W`, `CEIL.W` and `FLOOR.W`
795/// differ **only** in this: `CVT` uses `FCSR.RM`, and the other four hard-code
796/// one mode each. Sharing the body means a rounding bug cannot exist in one and
797/// not the others.
798#[must_use]
799pub fn round_f64(v: f64, mode: Rounding) -> f64 {
800 match mode {
801 // `round_ties_even` rather than `round`, which rounds half AWAY from
802 // zero -- a different mode that no MIPS setting selects.
803 Rounding::Nearest => round_ties_even(v),
804 Rounding::TowardZero => trunc(v),
805 Rounding::TowardPlusInf => ceil(v),
806 Rounding::TowardMinusInf => floor(v),
807 }
808}
809
810/// Convert a float to a 32-bit integer under `mode`.
811///
812/// An out-of-range or NaN input raises **Invalid**. The value returned in that
813/// case is `i32::MAX`, which is the conventional MIPS result — and a *choice*
814/// here, since the architecture leaves it undefined when the exception is
815/// masked.
816#[must_use]
817pub fn to_i32(v: f64, mode: Rounding) -> Outcome<i32> {
818 let r = round_f64(v, mode);
819 if v.is_nan() || r < f64::from(i32::MIN) || r > f64::from(i32::MAX) {
820 return Outcome {
821 value: i32::MAX,
822 flags: Flags::INVALID,
823 };
824 }
825 let value = r as i32;
826 let mut flags = Flags::NONE;
827 if f64::from(value) != v {
828 flags.inexact = true;
829 }
830 Outcome { value, flags }
831}
832
833/// Convert a float to a 64-bit integer under `mode`.
834#[must_use]
835pub fn to_i64(v: f64, mode: Rounding) -> Outcome<i64> {
836 let r = round_f64(v, mode);
837 // The bounds are compared as f64 deliberately: `i64::MAX` is not exactly
838 // representable, so `r > i64::MAX as f64` is the correct test and
839 // `r as i64 == i64::MAX` is not.
840 if v.is_nan() || !(-TWO_POW_63..TWO_POW_63).contains(&r) {
841 return Outcome {
842 value: i64::MAX,
843 flags: Flags::INVALID,
844 };
845 }
846 let value = r as i64;
847 let mut flags = Flags::NONE;
848 if value as f64 != v {
849 flags.inexact = true;
850 }
851 Outcome { value, flags }
852}
853
854/// `fmt` for single precision, as the COP1 encoding gives it.
855pub const FMT_S: u8 = 16;
856/// `fmt` for double precision.
857pub const FMT_D: u8 = 17;
858/// `fmt` for 32-bit fixed point (`W`), the source format of `CVT.fmt.W`.
859pub const FMT_W: u8 = 20;
860/// `fmt` for 64-bit fixed point (`L`).
861pub const FMT_L: u8 = 21;
862
863/// Pipeline cycles for a COP1 arithmetic instruction — **UM Table 7-14**.
864///
865/// A direct transcription of the table, indexed by `funct` and the source
866/// `fmt`. Written as data for the same reason the COP0 tables are: every entry
867/// is a documented number with no generating rule, and a `match` over prose
868/// becomes a place to forget one.
869///
870/// | Instruction | S | D | W | L |
871/// | --- | --- | --- | --- | --- |
872/// | `ADD`/`SUB` | 3 | 3 | | |
873/// | `MUL` | 5 | 8 | | |
874/// | `DIV`/`SQRT` | 29 | 58 | | |
875/// | `ABS`/`MOV`/`NEG` | 1 | 1 | | |
876/// | `ROUND`/`TRUNC`/`CEIL`/`FLOOR` (`.W` and `.L`) | 5 | 5 | | |
877/// | `CVT.S` | — | 2 | 5 | 5 |
878/// | `CVT.D` | 1 | — | 5 | 5 |
879/// | `CVT.W`/`CVT.L` | 5 | 5 | | |
880/// | `C.cond` | 1 | 1 | | |
881///
882/// # What a `1` means, and why it is not charged
883///
884/// One cycle is what an ordinary instruction already takes, so a rate of 1 is
885/// *no* stall — `ABS`, `MOV`, `NEG` and `C.cond` cost nothing extra. Charging
886/// them one cycle would make them uniquely slow among single-cycle
887/// instructions.
888///
889/// # The `+1` for a dependent consumer is not in this table
890///
891/// The manual's note is that *"if a floating-point result for these
892/// instructions is needed by the subsequent instruction, the latency is the
893/// execution rate plus one, due to the fact that an EX-to-RF bypass is not
894/// performed"*. That extra cycle is not added here: the stall these numbers
895/// produce holds the whole pipeline, so the consumer spends its own cycle after
896/// the stall drains and arrives at rate + 1 on its own.
897///
898/// # Not modeled: the early exit
899///
900/// UM §7.5.6 and this table's own note 2 say a multicycle operation whose
901/// result is *obvious* — a zero or infinity operand, a power-of-two multiplier
902/// — completes in two cycles instead. That is documented behavior we do not
903/// yet reproduce, so trivial operands are charged the full rate and the model
904/// runs **slower** than hardware there. Accuracy ledger C-29.
905#[must_use]
906// One arm per table row, kept separate on purpose. Clippy would merge the arms
907// that happen to share a number and fold the 1s into the wildcard; that turns a
908// transcription anyone can check line-by-line against the manual into an
909// expression they have to re-derive. The rows are the documentation.
910#[allow(clippy::match_same_arms, clippy::manual_range_patterns)]
911pub const fn delay_cycles(funct: u8, fmt: u8) -> u32 {
912 let double = fmt == FMT_D;
913 match funct {
914 // ADD, SUB.
915 0o00 | 0o01 => 3,
916 // MUL.
917 0o02 => {
918 if double {
919 8
920 } else {
921 5
922 }
923 }
924 // DIV, and SQRT which costs the same.
925 0o03 | 0o04 => {
926 if double {
927 58
928 } else {
929 29
930 }
931 }
932 // ABS, MOV, NEG — one cycle, i.e. no stall.
933 0o05 | 0o06 | 0o07 => 1,
934 // ROUND.L, TRUNC.L, CEIL.L, FLOOR.L, ROUND.W, TRUNC.W, CEIL.W, FLOOR.W.
935 0o10..=0o17 => 5,
936 // CVT.S: 2 from double, 5 from a fixed-point format.
937 0o40 => {
938 if double {
939 2
940 } else {
941 5
942 }
943 }
944 // CVT.D: 1 from single, 5 from a fixed-point format.
945 0o41 => {
946 if fmt == FMT_S {
947 1
948 } else {
949 5
950 }
951 }
952 // CVT.W, CVT.L.
953 0o44 | 0o45 => 5,
954 // C.cond.fmt and anything else: one cycle.
955 _ => 1,
956 }
957}
958
959/// The stall a COP1 arithmetic instruction adds, in `PCycle`s.
960///
961/// Zero for the single-cycle operations; see [`delay_cycles`].
962#[must_use]
963pub const fn stall_cycles(funct: u8, fmt: u8) -> u32 {
964 let rate = delay_cycles(funct, fmt);
965 if rate > 1 { rate } else { 0 }
966}
967
968#[cfg(test)]
969mod tests {
970 use super::*;
971
972 /// **The VR4300 NaN convention is inverted from IEEE-754:2008**: the
973 /// significand MSB **set** means *signaling*, not quiet.
974 ///
975 /// So `0x7FC0_0001` — what every modern language calls a quiet NaN, and
976 /// what Rust produces — raises Invalid here, and `0x7F80_0001` does not.
977 /// The bit patterns are named for what they are *on this processor*, since
978 /// naming them the IEEE way is what made the original implementation
979 /// backwards. Accuracy ledger C-12.
980 #[test]
981 fn the_signaling_nan_is_the_one_with_the_significand_msb_set() {
982 let signals_here = f32::from_bits(0x7FC0_0001); // IEEE would call this quiet
983 let quiet_here = f32::from_bits(0x7F80_0001); // IEEE would call this signaling
984 assert!(
985 is_snan_f32(signals_here),
986 "MSB set is SIGNALING on the VR4300"
987 );
988 assert!(!is_snan_f32(quiet_here), "MSB clear is quiet");
989 assert!(!is_snan_f32(f32::INFINITY), "infinity is not a NaN");
990
991 assert!(add_s(signals_here, 1.0, Rounding::Nearest).flags.invalid);
992 assert!(
993 !add_s(quiet_here, 1.0, Rounding::Nearest).flags.invalid,
994 "a quiet NaN propagates quietly"
995 );
996
997 // Rust's own NaN is signaling to this processor. Stated explicitly
998 // because it is the case most likely to be reintroduced by someone
999 // "fixing" the convention back to IEEE.
1000 assert!(is_snan_f32(f32::NAN), "even f32::NAN signals here");
1001 }
1002
1003 /// The same, for doubles — the bit sits at a different position, so this is
1004 /// not a free consequence of the `f32` case.
1005 #[test]
1006 fn the_double_precision_signaling_bit_is_at_bit_51() {
1007 let signals_here = f64::from_bits(0x7FF8_0000_0000_0001);
1008 let quiet_here = f64::from_bits(0x7FF0_0000_0000_0001);
1009 assert!(is_snan_f64(signals_here));
1010 assert!(!is_snan_f64(quiet_here));
1011 assert!(add_d(signals_here, 1.0, Rounding::Nearest).flags.invalid);
1012 assert!(!add_d(quiet_here, 1.0, Rounding::Nearest).flags.invalid);
1013 }
1014
1015 /// The processor's own default NaN result must be **quiet by its own
1016 /// convention**, or every invalid operation would produce a value that
1017 /// re-traps the moment anything touches it.
1018 ///
1019 /// This is the corroboration that the inverted convention is real rather
1020 /// than a curve fit to the compare tests: `0x7FBF_FFFF` is the value
1021 /// hardware delivers, and it is only sane under the VR4300 reading.
1022 #[test]
1023 fn the_default_nan_result_is_quiet_by_the_vr4300_convention() {
1024 use crate::softfloat::{F32, F64};
1025 assert!(!is_snan_f32(f32::from_bits(F32.default_nan() as u32)));
1026 assert!(!is_snan_f64(f64::from_bits(F64.default_nan())));
1027 }
1028
1029 /// **`x/0` is `DivByZero`; `0/0` is Invalid.** They are different flags, and a
1030 /// handler distinguishes them — collapsing both into `DivByZero` reports a
1031 /// division fault for what is actually an undefined form.
1032 #[test]
1033 fn divide_by_zero_and_zero_over_zero_raise_different_flags() {
1034 let f = div_s(1.0, 0.0, Rounding::Nearest).flags;
1035 assert!(f.div_by_zero, "finite non-zero over zero");
1036 assert!(!f.invalid);
1037 assert!(!f.overflow, "infinite, but not an overflow");
1038
1039 let f = div_s(0.0, 0.0, Rounding::Nearest).flags;
1040 assert!(f.invalid, "0/0 is an undefined form");
1041 assert!(!f.div_by_zero);
1042 }
1043
1044 /// **`inf / 0` is not a division by zero.** IEEE reserves the flag for a
1045 /// *finite* non-zero numerator, because only there does a zero divisor
1046 /// create an infinity out of nothing — `inf / 0` was already infinite.
1047 ///
1048 /// The condition originally tested `!a.is_nan()`, which let infinities
1049 /// through and disagreed with the comment directly above it.
1050 #[test]
1051 fn an_infinite_numerator_over_zero_is_not_a_division_by_zero() {
1052 for a in [f32::INFINITY, f32::NEG_INFINITY] {
1053 let f = div_s(a, 0.0, Rounding::Nearest).flags;
1054 assert!(!f.div_by_zero, "{a} / 0 is not DivByZero");
1055 assert!(!f.invalid, "nor is it an undefined form");
1056 }
1057 // The finite case still is.
1058 assert!(div_s(1.0, 0.0, Rounding::Nearest).flags.div_by_zero);
1059 assert!(div_d(-2.5, 0.0, Rounding::Nearest).flags.div_by_zero);
1060 for a in [f64::INFINITY, f64::NEG_INFINITY] {
1061 assert!(!div_d(a, 0.0, Rounding::Nearest).flags.div_by_zero);
1062 }
1063 }
1064
1065 /// A double that narrows to zero has **underflowed** — the one underflow
1066 /// case detectable without the exact pre-rounding result.
1067 #[test]
1068 fn a_double_narrowing_to_zero_underflows() {
1069 let out = cvt_s_d(1e-300);
1070 assert!(out.underflowed(), "1e-300 has no f32 representation");
1071 assert!(out.flags.inexact);
1072 assert_eq!(out.value, 0.0);
1073 // A representable small value does not.
1074 assert!(!cvt_s_d(1e-30).underflowed());
1075 // ...and a genuine zero is not an underflow.
1076 assert!(!cvt_s_d(0.0).underflowed());
1077 }
1078
1079 /// A NaN produced from **non-NaN** inputs is an undefined form and raises
1080 /// Invalid — `∞ - ∞` here — even though neither operand was a NaN.
1081 #[test]
1082 fn a_nan_from_finite_or_infinite_inputs_is_invalid() {
1083 let f = sub_s(f32::INFINITY, f32::INFINITY, Rounding::Nearest).flags;
1084 assert!(f.invalid, "inf - inf is an undefined form");
1085 let f = mul_s(0.0, f32::INFINITY, Rounding::Nearest).flags;
1086 assert!(f.invalid, "0 * inf likewise");
1087 }
1088
1089 /// Overflow from finite operands sets both Overflow and Inexact.
1090 #[test]
1091 fn overflow_from_finite_operands_is_also_inexact() {
1092 let f = mul_s(f32::MAX, 2.0, Rounding::Nearest).flags;
1093 assert!(f.overflow);
1094 assert!(f.inexact, "an overflowed result is never exact");
1095 // But an infinity that was already infinite is not an overflow.
1096 assert!(!add_s(f32::INFINITY, 1.0, Rounding::Nearest).flags.overflow);
1097 }
1098
1099 /// `ABS`/`NEG` are **bit operations**, so they pass NaN payloads through
1100 /// rather than canonicalizing. `f32::abs` happens to agree, but specifying
1101 /// it as a bit clear is what makes the NaN behavior predictable.
1102 #[test]
1103 fn abs_and_neg_are_bit_operations_that_preserve_nan_payloads() {
1104 let nan = f32::from_bits(0xFF80_1234); // negative NaN with a payload
1105 assert_eq!(
1106 abs_s(nan).to_bits(),
1107 0x7F80_1234,
1108 "sign cleared, payload kept"
1109 );
1110 assert_eq!(neg_s(nan).to_bits(), 0x7F80_1234, "sign flipped");
1111 assert_eq!(
1112 neg_s(neg_s(nan)).to_bits(),
1113 nan.to_bits(),
1114 "and is an involution"
1115 );
1116 assert_eq!(abs_s(-0.0).to_bits(), 0.0f32.to_bits(), "negative zero too");
1117 }
1118
1119 /// `FCSR.RM` decodes per UM §7.2.4, and the default is nearest-even.
1120 #[test]
1121 fn the_rounding_mode_decodes_from_rm() {
1122 assert_eq!(Rounding::from_rm(0), Rounding::Nearest);
1123 assert_eq!(Rounding::from_rm(1), Rounding::TowardZero);
1124 assert_eq!(Rounding::from_rm(2), Rounding::TowardPlusInf);
1125 assert_eq!(Rounding::from_rm(3), Rounding::TowardMinusInf);
1126 assert_eq!(
1127 Rounding::from_rm(0xFC),
1128 Rounding::Nearest,
1129 "masked to 2 bits"
1130 );
1131 }
1132
1133 /// Flags map onto **both** the `Cause` and sticky `Flags` fields, because
1134 /// hardware sets them together. Writing only one leaves software unable to
1135 /// distinguish "raised now" from "raised at some point".
1136 #[test]
1137 fn flags_populate_both_the_cause_and_sticky_fields() {
1138 let bits = Flags {
1139 invalid: true,
1140 ..Flags::NONE
1141 }
1142 .to_fcsr_bits();
1143 assert_ne!(bits & (1 << 16), 0, "Cause.invalid");
1144 assert_ne!(bits & (1 << 6), 0, "Flags.invalid");
1145
1146 let bits = Flags {
1147 div_by_zero: true,
1148 inexact: true,
1149 ..Flags::NONE
1150 }
1151 .to_fcsr_bits();
1152 assert_eq!(bits, (1 << 15) | (1 << 5) | (1 << 12) | (1 << 2));
1153 assert_eq!(Flags::NONE.to_fcsr_bits(), 0);
1154 }
1155
1156 /// **Exact** arithmetic raises nothing — the flags must not be noisy, or
1157 /// software with the enables set traps constantly.
1158 ///
1159 /// # Why the operands are all dyadic
1160 ///
1161 /// This case previously included `(1e10, 1e-4)`, which is *not* exact in
1162 /// `f32` for any of the four operations. It passed only because `inexact`
1163 /// was never detected (accuracy ledger C-11), so the test was asserting the
1164 /// absence of a flag the implementation could not raise — and would have
1165 /// gone on passing however wrong the arithmetic became.
1166 ///
1167 /// Every pair below is a dyadic rational whose sum, difference, product
1168 /// **and** quotient are all exactly representable, so "no flags" is a real
1169 /// claim about the result rather than a claim about missing machinery.
1170 #[test]
1171 fn exact_arithmetic_raises_no_flags() {
1172 for (a, b) in [
1173 (1.0f32, 2.0f32),
1174 (-3.5, 0.25),
1175 (6.0, 0.5),
1176 // Large but still exact: 2^20 and 2^12, whose product 2^32 and
1177 // quotient 2^8 both land on the grid.
1178 (1_048_576.0, 4096.0),
1179 ] {
1180 for out in [
1181 add_s(a, b, Rounding::Nearest),
1182 sub_s(a, b, Rounding::Nearest),
1183 mul_s(a, b, Rounding::Nearest),
1184 div_s(a, b, Rounding::Nearest),
1185 ] {
1186 assert_eq!(out.flags, Flags::NONE, "{a} op {b} raised something");
1187 }
1188 }
1189 // Exact bit comparison, not a tolerance: these values are exactly
1190 // representable, so anything but the exact result is a bug -- and a
1191 // tolerance would hide it.
1192 assert_eq!(
1193 add_d(1.0, 2.0, Rounding::Nearest).value.to_bits(),
1194 3.0f64.to_bits()
1195 );
1196 assert_eq!(
1197 mul_d(3.0, 4.0, Rounding::Nearest).value.to_bits(),
1198 12.0f64.to_bits()
1199 );
1200 }
1201
1202 /// The condition field is **systematic**, so this checks the named mnemonics
1203 /// against the derivation rather than trusting it.
1204 #[test]
1205 fn the_named_compare_conditions_fall_out_of_the_bit_encoding() {
1206 // cond, name, expected for [1<2, 2==2, 3>2, NaN]
1207 let cases: &[(u8, &str, [bool; 4])] = &[
1208 (0, "F", [false, false, false, false]),
1209 (1, "UN", [false, false, false, true]),
1210 (2, "EQ", [false, true, false, false]),
1211 (3, "UEQ", [false, true, false, true]),
1212 (4, "OLT", [true, false, false, false]),
1213 (5, "ULT", [true, false, false, true]),
1214 (6, "OLE", [true, true, false, false]),
1215 (7, "ULE", [true, true, false, true]),
1216 ];
1217 for &(cond, name, want) in cases {
1218 let got = [
1219 compare_s(1.0, 2.0, cond).value,
1220 compare_s(2.0, 2.0, cond).value,
1221 compare_s(3.0, 2.0, cond).value,
1222 compare_s(f32::NAN, 2.0, cond).value,
1223 ];
1224 assert_eq!(got, want, "C.{name} (cond {cond})");
1225 }
1226 }
1227
1228 /// **The signaling forms raise Invalid on any unordered compare**, even for
1229 /// a *quiet* NaN. That is the entire difference between `C.EQ` and `C.SEQ`,
1230 /// and it means the quiet/signaling test used elsewhere is not sufficient
1231 /// on its own here.
1232 #[test]
1233 fn the_signaling_compare_forms_raise_on_a_quiet_nan() {
1234 // Quiet **by the VR4300 convention**: significand MSB clear (C-12).
1235 let qnan = f32::from_bits(0x7F80_0001);
1236 assert!(!is_snan_f32(qnan), "it really is quiet");
1237
1238 let out = compare_s(qnan, 1.0, 2); // C.EQ
1239 assert!(!out.value);
1240 assert!(!out.flags.invalid, "the non-signaling form stays quiet");
1241
1242 let out = compare_s(qnan, 1.0, 10); // C.SEQ
1243 assert!(!out.value, "the comparison result is unchanged");
1244 assert!(out.flags.invalid, "only the exception differs");
1245 }
1246
1247 /// A **signaling** NaN raises Invalid whatever the condition, including the
1248 /// non-signaling forms.
1249 #[test]
1250 fn a_signaling_nan_operand_raises_for_every_condition() {
1251 // Signaling **by the VR4300 convention**: significand MSB set (C-12).
1252 let snan = f32::from_bits(0x7FC0_0001);
1253 for cond in 0..16u8 {
1254 assert!(
1255 compare_s(snan, 1.0, cond).flags.invalid,
1256 "cond {cond} must raise on a signaling NaN"
1257 );
1258 }
1259 }
1260
1261 /// `Greater` appears in **no** condition bit — it is "none of less, equal or
1262 /// unordered", which is why three bits suffice for the relation.
1263 #[test]
1264 fn greater_matches_no_condition_bit() {
1265 for cond in 0..8u8 {
1266 assert!(
1267 !compare_s(3.0, 2.0, cond).value,
1268 "cond {cond}: greater matches no bit"
1269 );
1270 }
1271 }
1272
1273 /// Doubles use the same derivation, and `-0.0 == 0.0` as IEEE requires.
1274 #[test]
1275 fn double_compares_agree_and_signed_zeros_are_equal() {
1276 assert!(compare_d(1.0, 2.0, 4).value, "C.OLT");
1277 assert!(compare_d(2.0, 2.0, 2).value, "C.EQ");
1278 assert!(compare_d(-0.0, 0.0, 2).value, "-0.0 == 0.0 (IEEE)");
1279 assert!(compare_d(f64::NAN, 1.0, 1).value, "C.UN");
1280 assert!(
1281 !compare_d(f64::NAN, 1.0, 2).value,
1282 "C.EQ is false when unordered"
1283 );
1284 }
1285
1286 /// **The VR4300 long-conversion restriction** (UM §7.5.2): bits 63:55 must
1287 /// be all-zero or all-one. This is a *hardware limitation*, not IEEE
1288 /// behavior — the value is representable, the processor declines.
1289 ///
1290 /// Converting it anyway produces a correct number where hardware traps, so
1291 /// software's fixup path never runs and the divergence surfaces far
1292 /// downstream from its cause.
1293 #[test]
1294 fn cvt_from_long_rejects_values_outside_the_vr4300_range() {
1295 // Small positives and negatives: bits 63:55 uniform.
1296 for v in [0i64, 1, -1, 1 << 40, -(1 << 40), (1 << 55) - 1] {
1297 assert!(long_convertible(v), "{v} must be convertible");
1298 }
1299 // Bits 63:55 mixed -- declines.
1300 for v in [1i64 << 55, 1 << 60, i64::MAX, i64::MIN + 1] {
1301 assert!(!long_convertible(v), "{v} must be rejected");
1302 }
1303 // i64::MIN is 0x8000_0000_0000_0000, so bits 63:55 are 0b1_0000_0000 --
1304 // neither all-zero nor all-one, so it is NOT convertible. Easy to
1305 // assume otherwise from "the sign bit is set".
1306 assert!(!long_convertible(i64::MIN));
1307 // The largest magnitudes that ARE convertible sit at the 2^55 boundary.
1308 assert!(long_convertible((1i64 << 55) - 1));
1309 assert!(long_convertible(-(1i64 << 55)));
1310 }
1311
1312 /// `Unimplemented` is **not** `Invalid`: it means "this processor cannot do
1313 /// this", not "the operation is undefined". Conflating them sends the
1314 /// handler down the numerical-error path for a hardware limitation.
1315 #[test]
1316 fn unimplemented_is_a_different_cause_bit_from_invalid() {
1317 assert_eq!(CAUSE_UNIMPLEMENTED, 1 << 17);
1318 assert_ne!(
1319 CAUSE_UNIMPLEMENTED,
1320 Flags::INVALID.to_fcsr_bits() & 0x0003_F000,
1321 "distinct from the Invalid cause bit"
1322 );
1323 }
1324
1325 /// Integer-to-float rounding lives in `softfloat::from_int`, not here.
1326 ///
1327 /// This module used to carry `cvt_s_w`/`cvt_s_l`/`cvt_d_w`/`cvt_d_l`, each a
1328 /// Rust `as` cast plus a round-trip inexact check. They were **deleted**
1329 /// rather than left unused once the pipeline moved to `softfloat::from_int`:
1330 /// an `as` cast rounds to nearest-even unconditionally, so every one of them
1331 /// ignored `FCSR.RM`, and an unused function that quietly gets an operation
1332 /// wrong is the inert-API hazard `docs/engineering-lessons.md` §3.2
1333 /// describes. `addr.rs` deleted a stale `translate` for the same reason.
1334 ///
1335 /// [`long_convertible`] stays: it is the VR4300 range restriction, which is
1336 /// a separate rule from the rounding and is still consulted.
1337 #[test]
1338 fn int_to_single_rounding_is_not_in_this_module() {
1339 // A value needing more than 24 significand bits, converted toward zero.
1340 // Nearest-even would give 0x4E93_2C06; the mode must be honored.
1341 let r =
1342 crate::softfloat::from_int(1_234_567_891, crate::softfloat::F32, Rounding::TowardZero);
1343 assert_eq!(r.bits as u32, 0x4E93_2C05);
1344 assert!(r.flags.inexact);
1345 }
1346
1347 /// `CVT.S.D` can overflow; `CVT.D.S` never can, since every `f32` is an
1348 /// `f64`.
1349 #[test]
1350 fn narrowing_can_overflow_but_widening_cannot() {
1351 let out = cvt_s_d(1e300);
1352 assert!(out.flags.overflow, "1e300 has no f32 representation");
1353 assert!(out.flags.inexact);
1354 assert!(!cvt_s_d(1.0).flags.overflow);
1355 assert_eq!(cvt_d_s(1.5).value.to_bits(), 1.5f64.to_bits());
1356 assert_eq!(cvt_d_s(f32::MAX).flags, Flags::NONE, "widening is exact");
1357 }
1358
1359 /// The four rounding modes differ, and `Nearest` is **ties-to-even** — not
1360 /// `f64::round`, which rounds half away from zero and matches no MIPS mode.
1361 #[test]
1362 fn the_rounding_modes_differ_and_nearest_is_ties_to_even() {
1363 assert_eq!(round_f64(2.5, Rounding::Nearest), 2.0, "ties to EVEN");
1364 assert_eq!(round_f64(3.5, Rounding::Nearest), 4.0);
1365 assert_eq!(round_f64(2.5, Rounding::TowardZero), 2.0);
1366 assert_eq!(round_f64(2.5, Rounding::TowardPlusInf), 3.0);
1367 assert_eq!(round_f64(2.5, Rounding::TowardMinusInf), 2.0);
1368 assert_eq!(round_f64(-2.5, Rounding::TowardZero), -2.0);
1369 assert_eq!(round_f64(-2.5, Rounding::TowardMinusInf), -3.0);
1370 }
1371
1372 /// Out-of-range and NaN conversions raise Invalid rather than wrapping.
1373 #[test]
1374 fn an_out_of_range_conversion_raises_invalid() {
1375 for v in [1e30f64, -1e30, f64::NAN, f64::INFINITY] {
1376 let out = to_i32(v, Rounding::Nearest);
1377 assert!(out.flags.invalid, "{v} does not fit in an i32");
1378 assert_eq!(out.value, i32::MAX, "the conventional MIPS result");
1379 }
1380 assert_eq!(to_i32(42.0, Rounding::Nearest).value, 42);
1381 assert_eq!(to_i32(42.0, Rounding::Nearest).flags, Flags::NONE);
1382 assert!(to_i32(42.5, Rounding::TowardZero).flags.inexact);
1383 }
1384
1385 /// The 64-bit bound is compared as an `f64` because `i64::MAX` is **not
1386 /// exactly representable** — `r > i64::MAX as f64` is the correct test, and
1387 /// casting first is not.
1388 #[test]
1389 fn the_64_bit_conversion_bound_accounts_for_representability() {
1390 // 2^63 exactly: out of range, since i64::MAX is 2^63 - 1.
1391 let out = to_i64(9_223_372_036_854_775_808.0, Rounding::Nearest);
1392 assert!(out.flags.invalid, "2^63 does not fit in an i64");
1393 // Just inside.
1394 let out = to_i64(9_223_372_036_854_774_784.0, Rounding::Nearest);
1395 assert!(!out.flags.invalid);
1396 assert_eq!(to_i64(-1.0, Rounding::Nearest).value, -1);
1397 }
1398
1399 /// The FP multiplication erratum is **detectable but not reproducible**:
1400 /// the trigger is documented, the corrupted output never was.
1401 ///
1402 /// Selecting the affected stepping therefore changes no arithmetic. That is
1403 /// deliberate — inventing a plausible wrong value would be the
1404 /// fitted-constant failure the accuracy ledger forbids, and every later
1405 /// result built on it would stop being evidence.
1406 /// **UM Table 7-14, asserted row by row.** These are documented numbers with
1407 /// no generating rule, so the test is the transcription check.
1408 #[test]
1409 fn the_fpu_delay_table_matches_the_manual() {
1410 // ADD, SUB: 3 in both formats.
1411 for funct in [0o00, 0o01] {
1412 assert_eq!(delay_cycles(funct, FMT_S), 3);
1413 assert_eq!(delay_cycles(funct, FMT_D), 3);
1414 }
1415 assert_eq!(delay_cycles(0o02, FMT_S), 5, "MUL.S");
1416 assert_eq!(delay_cycles(0o02, FMT_D), 8, "MUL.D");
1417 for funct in [0o03, 0o04] {
1418 assert_eq!(delay_cycles(funct, FMT_S), 29, "DIV/SQRT.S");
1419 assert_eq!(delay_cycles(funct, FMT_D), 58, "DIV/SQRT.D");
1420 }
1421 // ABS, MOV, NEG and C.cond are single-cycle, so they add no stall.
1422 for funct in [0o05, 0o06, 0o07, 0o60, 0o62, 0o74] {
1423 assert_eq!(delay_cycles(funct, FMT_S), 1);
1424 assert_eq!(stall_cycles(funct, FMT_S), 0, "no stall for a 1-cycle op");
1425 }
1426 // The eight ROUND/TRUNC/CEIL/FLOOR forms.
1427 for funct in 0o10..=0o17 {
1428 assert_eq!(delay_cycles(funct, FMT_S), 5);
1429 assert_eq!(delay_cycles(funct, FMT_D), 5);
1430 }
1431 // The CVT rows are the only ones that differ by SOURCE format, which is
1432 // the part a uniform table would get wrong.
1433 assert_eq!(delay_cycles(0o40, FMT_D), 2, "CVT.S from double");
1434 assert_eq!(delay_cycles(0o40, FMT_W), 5, "CVT.S from word");
1435 assert_eq!(delay_cycles(0o40, FMT_L), 5, "CVT.S from long");
1436 assert_eq!(delay_cycles(0o41, FMT_S), 1, "CVT.D from single");
1437 assert_eq!(delay_cycles(0o41, FMT_W), 5, "CVT.D from word");
1438 assert_eq!(delay_cycles(0o41, FMT_L), 5, "CVT.D from long");
1439 assert_eq!(delay_cycles(0o44, FMT_S), 5, "CVT.W");
1440 assert_eq!(delay_cycles(0o45, FMT_D), 5, "CVT.L");
1441 }
1442
1443 /// A rate of 1 is an ordinary instruction, not a one-cycle penalty.
1444 #[test]
1445 fn single_cycle_fpu_ops_are_charged_nothing() {
1446 assert_eq!(stall_cycles(0o06, FMT_S), 0, "MOV.S");
1447 assert_eq!(stall_cycles(0o41, FMT_S), 0, "CVT.D.S is one cycle");
1448 assert_eq!(stall_cycles(0o02, FMT_D), 8, "but MUL.D is not");
1449 }
1450
1451 #[test]
1452 fn the_multiplication_erratum_is_modeled_but_not_invented() {
1453 assert!(!Stepping::default().has_mul_erratum(), "fixed by default");
1454 assert!(Stepping::Early.has_mul_erratum());
1455
1456 // Selecting the affected stepping changes nothing about a multiply,
1457 // because there is nothing documented to change it to.
1458 let a = mul_s(3.0, 4.0, Rounding::Nearest);
1459 assert_eq!(a.value, 12.0, "arithmetic is stepping-independent today");
1460 assert_eq!(a.flags, Flags::NONE);
1461 }
1462
1463 /// The trigger is a property of the **previous** multiply's operands: a NaN,
1464 /// zero or infinity. It is tested now so a future characterization has
1465 /// somewhere correct to attach the output.
1466 #[test]
1467 fn the_erratum_trigger_keys_off_the_previous_multiplys_operands() {
1468 for (a, b) in [
1469 (f64::NAN, 1.0),
1470 (0.0, 1.0),
1471 (1.0, -0.0),
1472 (f64::INFINITY, 1.0),
1473 (1.0, f64::NEG_INFINITY),
1474 ] {
1475 assert!(mul_erratum_triggers(a, b), "{a} * {b} arms the erratum");
1476 }
1477 assert!(!mul_erratum_triggers(2.0, 3.0), "ordinary operands do not");
1478 assert!(!mul_erratum_triggers(-1.5, 1e30));
1479 }
1480}