Skip to content

feat(cpu): raise unimplemented operation for subnormals (1,098 → 584) - #29

Merged
doublegate merged 3 commits into
mainfrom
feat/unimplemented-operation-subnormals
Jul 20, 2026
Merged

feat(cpu): raise unimplemented operation for subnormals (1,098 → 584)#29
doublegate merged 3 commits into
mainfrom
feat/unimplemented-operation-subnormals

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Motivation

After #28, the dominant remaining n64-systemtest block was the unmaskable
unimplemented-operation cause. The VR4300 has no subnormal datapath: rather than producing a
subnormal, or silently flushing one, it raises FCSR.Cause.E (bit 17) and traps.

Result

n64-systemtest: 1,098 → 584.

ADD.S, SUB.S, ADD.D, DIV.D, ABS.* and NEG.* are now at zero failures; the
CVT.W/CVT.L families and CVT.D.fmt fell off the list entirely. What remains is dominated by
the LLE RSP, cart DMA and SP registers — i.e. Phase 2 work rather than COP1.

The rule

Four occasions, and they are not interchangeable:

Occasion Outcome
Subnormal operand unimplemented
Subnormal result, FCSR.FS clear unimplemented
Subnormal result, FS set and underflow or inexact enabled unimplemented
Subnormal result, FS set, both enables clear flush + underflow/inexact
MSB-clear NaN operand (quiet by this processor's convention, C-12) unimplemented

The third row is the interaction of two features that each work on their own: flushing works, and
enabled traps work, but together they do not — the processor cannot deliver a trapped underflow's
defined result. n64-systemtest's own comment on this case reads "(wow)".

Where it flushes to depends on the rounding mode: ±0 under nearest and toward-zero, but the
smallest normal of that sign under a mode that rounds away from zero, because zero is on the
wrong side of the true result. Getting that wrong gives -0 in all four cases, which looks
entirely reasonable.

Out-of-range float-to-integer conversions now raise unimplemented rather than the IEEE Invalid
that fpu::to_i32 reports. The translation happens at the call site, so the IEEE answer stays
available to anything that wants it.

Three things this surfaced

MOV is not ABS/NEG. All three look like sign-or-bit manipulation and only MOV is:
ABS/NEG classify their operand, raise Invalid on a signalling NaN, and replace
FCSR.Cause, while MOV transports the bits and leaves FCSR alone. The oracle settles this by
construction rather than description — MOV.S is driven through
test_floating_point_f32_which_preserves_cause_bits, ABS.S/NEG.S through the ordinary harness
that asserts Cause was cleared. Worth 52 assertions. The earlier finding that MOV must not
touch Cause (ledger C-10) stands; it simply does not generalise to its neighbours.

Compares are exempt. "This FPU cannot do subnormals" sounds like it should be universal and is
not: C.cond.fmt compares a subnormal as an ordinary number and raises nothing. Applying the rule
there would have regressed all sixteen compare tests, which had just reached zero in #28.

The subnormal-result policy is shared by the arithmetic and the narrowing CVT.S.D, so it
lives in one helper rather than being written twice and drifting.

Testing

355 passing. Seven new tests, each mutation-checked — removing the operand check, disabling
the result policy, ignoring FS, ignoring the rounding mode in the flush, keeping the IEEE
Invalid on conversions, and folding ABS/NEG back into MOV each turn a specific test red.

The subnormal-result test deliberately uses two normal operands, so it exercises the result
path and not the operand one — a test using a subnormal input would pass with the result check
deleted.

Not in scope

Phase 1 is not complete and v0.2.0 is not cut.

  • CVT.S.D does not honour FCSR.RM and computes its flags by hand rather than through
    softfloat (21 assertions). This is the conversions' version of ledger C-11 and wants the same
    fix: a narrowing softfloat::convert that rounds once.
  • SQRT (funct 4) remains neither decoded nor implemented (58 assertions).

n64-systemtest: 1,098 -> 584. `ADD.S`, `SUB.S`, `ADD.D`, `DIV.D`, `ABS.*` and
`NEG.*` are at zero failures; the `CVT.W`/`CVT.L` families and `CVT.D.fmt` fell
off the list entirely.

The VR4300 has no subnormal datapath. Rather than producing a subnormal, or
silently flushing one, it raises the unmaskable unimplemented-operation cause
(`FCSR.Cause.E`, bit 17) and traps. Four occasions, and they are not
interchangeable: a subnormal operand; a subnormal result with `FCSR.FS` clear;
a subnormal result with `FS` set but underflow or inexact ENABLED (it cannot
deliver a trapped underflow's defined result either -- the suite's own comment
on this case reads "(wow)"); and an MSB-clear NaN operand, quiet by this
processor's inverted convention (C-12).

Only with `FS` set and both enables clear does it flush -- and where it flushes
to depends on the rounding mode: +/-0 under nearest and toward-zero, but the
smallest NORMAL of that sign under a mode that rounds away from zero, because
zero is on the wrong side of the true result.

Out-of-range float-to-integer conversions now raise unimplemented rather than
the IEEE Invalid that `fpu::to_i32` reports. The translation happens at the call
site so the IEEE answer stays available.

Three things this surfaced, all now recorded in ledger C-13:

- `MOV` is not `ABS`/`NEG`. All three look like sign-or-bit manipulation and
  only `MOV` is: `ABS`/`NEG` classify their operand, raise Invalid on a
  signalling NaN, and REPLACE `FCSR.Cause`, while `MOV` transports the bits and
  leaves `FCSR` alone. The oracle settles it by construction -- `MOV.S` runs
  through the cause-preserving harness and `ABS.S`/`NEG.S` through the ordinary
  one. Worth 52 assertions. The earlier finding that `MOV` must not touch
  `Cause` stands; it simply does not generalise to its neighbours.
- Compares are EXEMPT. "This FPU cannot do subnormals" sounds like it should be
  universal and is not: `C.cond.fmt` compares a subnormal as an ordinary number
  and raises nothing. Applying the rule there would have regressed all sixteen
  compare tests, which had just reached zero.
- The subnormal-result policy is shared by the arithmetic and by the narrowing
  `CVT.S.D`, so it lives in one helper rather than being written twice.

Seven tests, each mutation-checked: removing the operand check, disabling the
result policy, ignoring `FS`, ignoring the rounding mode in the flush, keeping
the IEEE Invalid on conversions, and folding `ABS`/`NEG` back into `MOV` each
turn a specific test red.

Still open here: `CVT.S.D` does not honour `FCSR.RM` and computes its flags by
hand (21 assertions) -- the conversions' version of C-11, wanting a narrowing
`softfloat::convert`. `SQRT` remains neither decoded nor implemented (58).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 23:32
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced VR4300 floating-point handling for subnormal operands/results, with unmaskable unimplemented-operation traps and FCSR.FS-dependent subnormal flushing across rounding modes.
  • Bug Fixes

    • Refined COP1 MOV vs ABS/NEG semantics: MOV preserves FCSR.Cause, while ABS/NEG update/replace the exception cause and treat subnormals differently.
    • Out-of-range float-to-integer conversions now report unimplemented-operation (not IEEE Invalid).
  • Documentation

    • Updated accuracy/status notes and n64-systemtest failure counts (1,098 → 584), plus clarified remaining COP1 limitations.

Walkthrough

Changes

The VR4300 FPU now classifies subnormals and selected NaNs as unimplemented operations, applies FCSR-dependent flushing, separates MOV from ABS/NEG semantics, and rejects affected conversions. Tests and documentation record the behaviour and updated system-test results.

VR4300 FPU behaviour

Layer / File(s) Summary
FPU classification and flushing helpers
crates/rustyn64-cpu/src/fpu.rs
Adds subnormal and unimplemented-NaN detection, arithmetic predicates, and rounding-aware subnormal flushing helpers.
COP1 exception and conversion paths
crates/rustyn64-cpu/src/pipeline.rs
Separates MOV from ABS/NEG, applies unimplemented-operation handling to arithmetic and conversions, and updates FCSR cause and trap behaviour.
Behaviour validation and recorded conventions
crates/rustyn64-cpu/src/pipeline.rs, AGENTS.md, CHANGELOG.md, docs/STATUS.md, docs/accuracy-ledger.md
Adds coverage for subnormal, NaN, flushing, sign-operation, and conversion cases, and records the updated VR4300 conventions and system-test count.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant COP1Pipeline
  participant FPUHelpers
  participant FCSR
  COP1Pipeline->>FPUHelpers: classify operands and conversion sources
  FPUHelpers-->>COP1Pipeline: return unimplemented-operation decision
  COP1Pipeline->>FCSR: replace Cause and raise FloatingPoint exception
  FCSR-->>COP1Pipeline: apply trap or flush state
Loading

Possibly related PRs

  • doublegate/RustyN64#28: Related COP1 unimplemented-operation, FCSR cause, NaN classification, and exception-wiring changes.
🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docs-As-Spec Sync ⚠️ Warning FAIL: rustyn64-cpu behaviour changed, but docs/cpu.md was not updated; only accuracy-ledger/STATUS changed, and the PR body gives no reason the subsystem doc could be omitted. Add a docs/cpu.md update for the new subnormal/unimplemented-op, MOV/ABS/NEG, compare, and conversion rules, or explain in the PR body why the subsystem doc needn't change.
✅ Passed checks (9 passed)
Check name Status Explanation
Title check ✅ Passed The title matches Conventional Commits and accurately summarises the subnormal unimplemented-operation change.
Description check ✅ Passed The description is clearly about the same VR4300 FPU and n64-systemtest changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Oracle Number Is Stated ✅ Passed PASS: the emulation change is paired with an oracle number in CHANGELOG/STATUS (n64-systemtest: 1,098 → 584 / 584 failing), satisfying the rule.
Changelog Entry For User-Visible Changes ✅ Passed CHANGELOG.md has an [Unreleased] entry for the user-visible COP1/subnormal fix, so the changelog rule is satisfied.
Measured, Never Tuned ✅ Passed The new subnormal/bit-17 rules are anchored in UM §7.5.2 and ledger C-13; I found no untethered hardware or timing constant.
Unsafe Stays Out Of The Chip Crates ✅ Passed PASS: PR diff adds no unsafe, removes no #![forbid(unsafe_code)]; chip crates/core still keep the forbid, and no frontend unsafe block was introduced.

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refines the VR4300 COP1 model to correctly raise the unmaskable unimplemented-operation cause (FCSR.Cause.E, bit 17) for subnormal-related cases (and related edge conditions), aligning the emulator’s behavior with n64-systemtest expectations and significantly reducing remaining assertion failures.

Changes:

  • Implement unimplemented-operation behavior for subnormal operands/results (including FS + enable interactions) and MSB-clear NaN operands across relevant COP1 ops, with shared helpers to avoid drift.
  • Adjust ABS/NEG handling to classify operands (unlike MOV), including correct Cause replacement and trap behavior.
  • Add targeted regression tests (plus documentation/ledger/status/changelog updates) capturing the rule details and the new systemtest delta (1,098 → 584).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
docs/STATUS.md Updates COP1 status to reflect that unimplemented-operation handling is now implemented and updates the n64-systemtest failure count.
docs/accuracy-ledger.md Adds ledger entry C-13 documenting subnormal/unimplemented-operation rules and observed effects.
crates/rustyn64-cpu/src/pipeline.rs Implements the new COP1 behavior (subnormal policy, operand checks, ABS/NEG classification) and adds regression tests.
crates/rustyn64-cpu/src/fpu.rs Adds helpers for subnormal detection, “unimplemented NaN” classification, operand gating, and rounding-mode-sensitive flush behavior.
CHANGELOG.md Documents the new unimplemented-operation behavior and the corrected ABS/NEG vs MOV semantics under Unreleased.
AGENTS.md Updates project status notes and adds a new “no subnormal datapath” rule summary referencing ledger C-13.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rustyn64-cpu/src/pipeline.rs Outdated
The new paragraph explaining that ABS and NEG classify their operand was added
directly BELOW the old one saying all three raise nothing, so the two sat
adjacent and contradicted each other. The stale half is gone and the surviving
half keeps the still-true point about `ft` never being read.

Found by Copilot on PR #29 -- the same class of finding as the last one, and
the reason this repo treats comment and code as independent claims.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/rustyn64-cpu/src/pipeline.rs (1)

1504-1510: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale doc block: fp_arith now produces exactly what this says it does not.

The # Still not handled section asserts the unimplemented-operation cause "is not produced by the arithmetic here ... which this FPU computes normally instead." This PR routes fp_binary/fp_convert/fp_sign_op into precisely that cause, so the comment now contradicts the code directly beneath it. Per path instructions, comment and code are independent claims and a comment asserting behaviour the code contradicts is a flagged defect.

Suggested removal/rewrite of the stale section
-    /// # Still not handled
-    ///
-    /// The **unimplemented-operation** cause (bit 17) is unmaskable and is not
-    /// produced by the arithmetic here — the VR4300 raises it for subnormal
-    /// operands and results, which this FPU computes normally instead. That is
-    /// a separate body of work from the maskable enables, and the suite's
-    /// `expected_unimplemented` cases still fail.
+    /// # Unimplemented operation
+    ///
+    /// The **unimplemented-operation** cause (bit 17) is unmaskable and IS
+    /// raised here: subnormal operands/results and MSB-clear NaNs route through
+    /// `fp_binary`/`fp_convert`/`fp_sign_op` to the unimplemented path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/rustyn64-cpu/src/pipeline.rs` around lines 1504 - 1510, Update the
stale “Still not handled” documentation above fp_arith to match the current
behavior of fp_binary, fp_convert, and fp_sign_op. Remove the claim that
unimplemented-operation is not produced by this arithmetic and that
expected_unimplemented cases still fail; document the actual routing to that
cause instead.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/accuracy-ledger.md`:
- Around line 700-712: Update C-12’s closing note to mark the MSB-clear NaN
issue resolved, adding a forward reference to C-13 and removing the outdated
“still open” and failing-tests assertions. Preserve the superseded claim in the
append-mostly ledger by flagging the edited-in-place text rather than silently
deleting it.

---

Outside diff comments:
In `@crates/rustyn64-cpu/src/pipeline.rs`:
- Around line 1504-1510: Update the stale “Still not handled” documentation
above fp_arith to match the current behavior of fp_binary, fp_convert, and
fp_sign_op. Remove the claim that unimplemented-operation is not produced by
this arithmetic and that expected_unimplemented cases still fail; document the
actual routing to that cause instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31c9e8be-a695-4fa4-8642-b7a7b4b943bf

📥 Commits

Reviewing files that changed from the base of the PR and between bf54699 and fe8a497.

📒 Files selected for processing (6)
  • AGENTS.md
  • CHANGELOG.md
  • crates/rustyn64-cpu/src/fpu.rs
  • crates/rustyn64-cpu/src/pipeline.rs
  • docs/STATUS.md
  • docs/accuracy-ledger.md

Comment thread docs/accuracy-ledger.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/rustyn64-cpu/src/pipeline.rs`:
- Around line 1531-1540: Update the preceding FPU documentation block near the
arithmetic and conversion handling to remove the stale claim that
unimplemented-operation is not produced and that those cases remain unhandled.
Make the comments reflect that arithmetic and conversion paths now raise
unimplemented-operation, while preserving the accurate behavior described around
the funct 5/6/7 handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6cddb305-3a44-43c4-a8bc-90c5d28674db

📥 Commits

Reviewing files that changed from the base of the PR and between fe8a497 and 8ae0d88.

📒 Files selected for processing (1)
  • crates/rustyn64-cpu/src/pipeline.rs

Comment thread crates/rustyn64-cpu/src/pipeline.rs
C-12 closed with a 'still open, and adjacent' note about MSB-clear NaN operands
raising unimplemented operation. C-13 implements exactly that, so the note was
asserting something false -- including that the arithmetic tests still fail on
NaN inputs, which they no longer do.

Marked resolved with a forward reference rather than rewritten, matching how
C-10 was handled and this file's own append-mostly rule: what each entry
believed when it was written is the record worth keeping.

Found by CodeRabbit, citing the ledger path instruction it was given.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate
doublegate merged commit 92c269e into main Jul 20, 2026
8 of 9 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/accuracy-ledger.md`:
- Line 697: Update the sentence in the accuracy ledger entry by adding a comma
after “written,” while preserving the rest of the wording.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0a98b1fb-dde9-4bcd-9de0-d24070911ec5

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae0d88 and 5686ecf.

📒 Files selected for processing (1)
  • docs/accuracy-ledger.md

Comment thread docs/accuracy-ledger.md
**Adjacent, and since RESOLVED in C-13:** an **IEEE-signalling / VR4300-quiet**
NaN operand (MSB clear) to an arithmetic operation raises **unimplemented
operation** rather than nothing — the VR4300 cannot propagate one in hardware.
When this entry was written that was still open and the arithmetic tests failed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the missing comma after “written”.

Use: “When this entry was written, that was still open …”.

🧰 Tools
🪛 LanguageTool

[typographical] ~697-~697: Consider adding a comma here.
Context: ...te one in hardware. When this entry was written that was still open and the arithmetic ...

(IF_THAT_S_NOT_POSSIBLE_COMMA)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/accuracy-ledger.md` at line 697, Update the sentence in the accuracy
ledger entry by adding a comma after “written,” while preserving the rest of the
wording.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants