Skip to content

cpu-tests: grow the fpu group from 26 tests to 88 - #87

Merged
techomancer merged 1 commit into
techomancer:mainfrom
danifunker:add-fpu-tests
Aug 20, 2026
Merged

cpu-tests: grow the fpu group from 26 tests to 88#87
techomancer merged 1 commit into
techomancer:mainfrom
danifunker:add-fpu-tests

Conversation

@danifunker

Copy link
Copy Markdown
Contributor

The FPU was the thinnest group in the suite relative to its surface. 26 tests covered arithmetic and FCSR bookkeeping; the entire trapped exception path, denormals, half the compare predicates and most of double precision had no coverage at all. Seven new files under tests/fpu/, one under tests/mips4/, and a generator for the expectations:

fpu_trap.c (14) enabled exceptions — the Cause/Enable/Flag interaction,
what a trap must not write, EPC and Cause.BD
fpu_denorm.c (9) denormal operands and results, underflow, FCSR.FS, and
the Unimplemented Operation path they all lead to
fpu_compare.c (7) all sixteen C.cond predicates in both formats, quiet vs
signalling NaNs, the condition bit's ownership
fpu_vectors.c (10) arithmetic, all four rounding modes and every integer
conversion, against generated tables
fpu_double.c (8) the double paths with no vector: NaN results, the bit
operations, and conversion between the two formats
fpu_fr0.c (8) the FR=0 paired register file — the mode every o32 IRIX
binary runs the FPU in
fpu_breadth.c (6) all 32 registers, signalling-NaN operands, out-of-range
conversions, unaligned FP access
mips4_fp.c (6) RECIP.D/RSQRT.D, LDXC1/SWXC1/SDXC1/PREFX, MSUB/NMADD/
NMSUB in both formats, and the FP conditional moves —
each computing on R5000 and required to raise Reserved
Instruction on R4400

gen/fpvectors.py computes every expectation with exact rational arithmetic and cross-checks it against the host FPU before writing anything (make vectors; CI fails if the checked-in tables are stale). That is what makes it practical to assert the FCSR flags exactly, for every vector, rather than hand-writing a hundred Inexact bits — and its own first bug is written up in gotchas.md, because computing an expectation from a value the register cannot hold fails in the most misleading way available.

Five findings, all in docs/findings.md §6-§10, each traced to the line responsible: a trapped exception still writes its result and its Flag bit; FCSR.Cause accumulates instead of being rewritten per operation; denormals never raise Unimplemented Operation and FCSR.FS is inert; a signalling NaN raises Invalid only where a quiet one would; ABS.fmt and NEG.fmt never touch FCSR at all. rules/testing/ gets the short version.

Everything the FPU is asked to compute passes on both CPUs: every generated vector, all 128 predicate results, both formats, all four rounding modes, every conversion.

Three fixes in the runner, none in the tests:

  • run/run-local.sh never passed --config run/bare.toml, so make run died before the guest ran an instruction (the default config attaches a scsi1.raw that does not exist). Its --serial-log was also always empty: under --load-elf the SCC is never programmed, so nothing reaches the backend to be teed. It now tees stdout, like matrix.sh and CI already did.
  • the suite halted the machine before the SCC drained, and run-prom.sh read the log the instant the DONE token appeared. Either one truncates "IRIS-CPUTEST-DONE rc=100" to "rc=" — the line run-prom.sh matches on to decide pass or fail.
  • run-prom.sh defaulted to a per-cell binary name matrix.sh stopped using.

R4400 interpreter: 2041 checks passed, 121 failed (240 tests, 29 failing). R5000 interpreter: 2095 passed, 37 failed (13 failing). Booted through the PROM from a mkvh image: 2041/121, the same answer down the path the bootable disc will use.

The FPU was the thinnest group in the suite relative to its surface. 26 tests
covered arithmetic and FCSR bookkeeping; the entire *trapped* exception path,
denormals, half the compare predicates and most of double precision had no
coverage at all. Seven new files under tests/fpu/, one under tests/mips4/, and
a generator for the expectations:

  fpu_trap.c    (14)  enabled exceptions — the Cause/Enable/Flag interaction,
                      what a trap must not write, EPC and Cause.BD
  fpu_denorm.c   (9)  denormal operands and results, underflow, FCSR.FS, and
                      the Unimplemented Operation path they all lead to
  fpu_compare.c  (7)  all sixteen C.cond predicates in both formats, quiet vs
                      signalling NaNs, the condition bit's ownership
  fpu_vectors.c (10)  arithmetic, all four rounding modes and every integer
                      conversion, against generated tables
  fpu_double.c   (8)  the double paths with no vector: NaN results, the bit
                      operations, and conversion between the two formats
  fpu_fr0.c      (8)  the FR=0 paired register file — the mode every o32 IRIX
                      binary runs the FPU in
  fpu_breadth.c  (6)  all 32 registers, signalling-NaN operands, out-of-range
                      conversions, unaligned FP access
  mips4_fp.c     (6)  RECIP.D/RSQRT.D, LDXC1/SWXC1/SDXC1/PREFX, MSUB/NMADD/
                      NMSUB in both formats, and the FP conditional moves —
                      each computing on R5000 and required to raise Reserved
                      Instruction on R4400

gen/fpvectors.py computes every expectation with exact rational arithmetic and
cross-checks it against the host FPU before writing anything (`make vectors`;
CI fails if the checked-in tables are stale). That is what makes it practical
to assert the FCSR flags *exactly*, for every vector, rather than hand-writing
a hundred Inexact bits — and its own first bug is written up in gotchas.md,
because computing an expectation from a value the register cannot hold fails in
the most misleading way available.

Five findings, all in docs/findings.md §6-§10, each traced to the line
responsible: a trapped exception still writes its result and its Flag bit;
FCSR.Cause accumulates instead of being rewritten per operation; denormals
never raise Unimplemented Operation and FCSR.FS is inert; a signalling NaN
raises Invalid only where a quiet one would; ABS.fmt and NEG.fmt never touch
FCSR at all. rules/testing/ gets the short version.

Everything the FPU is asked to *compute* passes on both CPUs: every generated
vector, all 128 predicate results, both formats, all four rounding modes,
every conversion.

Three fixes in the runner, none in the tests:

- run/run-local.sh never passed --config run/bare.toml, so `make run` died
  before the guest ran an instruction (the default config attaches a scsi1.raw
  that does not exist). Its --serial-log was also always empty: under
  --load-elf the SCC is never programmed, so nothing reaches the backend to be
  teed. It now tees stdout, like matrix.sh and CI already did.
- the suite halted the machine before the SCC drained, and run-prom.sh read
  the log the instant the DONE token appeared. Either one truncates
  "IRIS-CPUTEST-DONE rc=100" to "rc=" — the line run-prom.sh matches on to
  decide pass or fail.
- run-prom.sh defaulted to a per-cell binary name matrix.sh stopped using.

R4400 interpreter: 2041 checks passed, 121 failed (240 tests, 29 failing).
R5000 interpreter: 2095 passed, 37 failed (13 failing). Booted through the
PROM from a mkvh image: 2041/121, the same answer down the path the bootable
disc will use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@techomancer
techomancer merged commit 29f5602 into techomancer:main Aug 20, 2026
2 of 6 checks passed
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