feat(bios): implement fixed-point ArcTan and ArcTan2 services
Add SWI 0x09 and 0x0a as original ARM routines. Evaluate the fixed-point angle polynomial with wrapped products, reduce ArcTan2 ratios with integer division, and handle axes and quadrants. Require sign-extended 16-bit inputs and preserve non-result caller registers. Add 15 regression tests for fixed-point and floating-point references, signed limits, rounding, quadrant boundaries, caller flags, stack bounds, repeated calls, and DMA/IRQ progress. Validate 671 default and 660 core-only tests in debug and release, plus all 10 native Darwin ARM64 window smoke tests.
This commit is contained in:
parent
f0e5240d44
commit
1edaa4151d
5 changed files with 537 additions and 11 deletions
47
README.md
47
README.md
|
|
@ -253,7 +253,7 @@ Verified locally on macOS 15.7.3:
|
||||||
- Nix system: `aarch64-darwin`.
|
- Nix system: `aarch64-darwin`.
|
||||||
- Rust host: `aarch64-apple-darwin`.
|
- Rust host: `aarch64-apple-darwin`.
|
||||||
- Release executable: native Mach-O `arm64`, without Rosetta.
|
- Release executable: native Mach-O `arm64`, without Rosetta.
|
||||||
- All 656 tests pass in debug and release builds.
|
- All 671 tests pass in debug and release builds.
|
||||||
- All ten native window smoke tests pass. The mosaic test submits 128 frames; the other nine submit 60 frames each.
|
- All ten native window smoke tests pass. The mosaic test submits 128 frames; the other nine submit 60 frames each.
|
||||||
- The eight CPU-driven smoke tests check CPU state, every output pixel, and presentation at VBlank entry.
|
- The eight CPU-driven smoke tests check CPU state, every output pixel, and presentation at VBlank entry.
|
||||||
- The terminal-only CPU and timer IRQ demos run successfully.
|
- The terminal-only CPU and timer IRQ demos run successfully.
|
||||||
|
|
@ -358,7 +358,7 @@ nix develop -c cargo test
|
||||||
- Game Pak wait-state control (`WAITCNT`) for all three ROM windows.
|
- Game Pak wait-state control (`WAITCNT`) for all three ROM windows.
|
||||||
- Sequential/non-sequential data accesses, branch refill costs, and variable multiply timing.
|
- Sequential/non-sequential data accesses, branch refill costs, and variable multiply timing.
|
||||||
- Optional caller-supplied 16 KiB BIOS mapping for vector code, without CPU BIOS read protection.
|
- Optional caller-supplied 16 KiB BIOS mapping for vector code, without CPU BIOS read protection.
|
||||||
- Optional original BIOS replacement: minimal boot, IRQ dispatch, interrupt waits, memory copy/fill, integer arithmetic, bit unpacking, LZ77/run-length/Huffman decompression, and differential filters.
|
- Optional original BIOS replacement: minimal boot, IRQ dispatch, interrupt waits, memory copy/fill, integer/fixed-point arithmetic, bit unpacking, LZ77/run-length/Huffman decompression, and differential filters.
|
||||||
- All 16 ARM data-processing operations, with immediate and shifted-register operands.
|
- All 16 ARM data-processing operations, with immediate and shifted-register operands.
|
||||||
- Logical operations: `AND`, `EOR`, `TST`, `TEQ`, `ORR`, `MOV`, `BIC`, and `MVN`.
|
- Logical operations: `AND`, `EOR`, `TST`, `TEQ`, `ORR`, `MOV`, `BIC`, and `MVN`.
|
||||||
- Arithmetic operations: `SUB`, `RSB`, `ADD`, `ADC`, `SBC`, `RSC`, `CMP`, and `CMN`.
|
- Arithmetic operations: `SUB`, `RSB`, `ADD`, `ADC`, `SBC`, `RSC`, `CMP`, and `CMN`.
|
||||||
|
|
@ -509,6 +509,8 @@ Supported software interrupt services:
|
||||||
| `0x06` | Div | Signed `r0 / r1`; return quotient in r0, remainder in r1, and quotient magnitude in r3 |
|
| `0x06` | Div | Signed `r0 / r1`; return quotient in r0, remainder in r1, and quotient magnitude in r3 |
|
||||||
| `0x07` | DivArm | Div with the incoming numerator and denominator exchanged |
|
| `0x07` | DivArm | Div with the incoming numerator and denominator exchanged |
|
||||||
| `0x08` | Sqrt | Unsigned integer square root of r0; return the rounded-down result in r0 |
|
| `0x08` | Sqrt | Unsigned integer square root of r0; return the rounded-down result in r0 |
|
||||||
|
| `0x09` | ArcTan | Signed fixed-point tangent in r0; return a signed angle in r0 |
|
||||||
|
| `0x0a` | ArcTan2 | Signed fixed-point X/Y in r0/r1; return the unsigned direction angle in r0 |
|
||||||
| `0x0b` | CpuSet | `r0`: source; `r1`: destination; `r2`: count, fill, and width control |
|
| `0x0b` | CpuSet | `r0`: source; `r1`: destination; `r2`: count, fill, and width control |
|
||||||
| `0x0c` | CpuFastSet | Word copy/fill in eight-word blocks; round count upward to a multiple of eight |
|
| `0x0c` | CpuFastSet | Word copy/fill in eight-word blocks; round count upward to a multiple of eight |
|
||||||
| `0x10` | BitUnPack | Expand packed units from r0 to r1 using the descriptor at r2; write complete words |
|
| `0x10` | BitUnPack | Expand packed units from r0 to r1 using the descriptor at r2; write complete words |
|
||||||
|
|
@ -525,7 +527,7 @@ ARM code uses `SWI service_number << 16`; Thumb code uses the service number dir
|
||||||
Only User/System callers are supported. Calls from exception modes return a diagnostic rather than risking nested stack corruption.
|
Only User/System callers are supported. Calls from exception modes return a diagnostic rather than risking nested stack corruption.
|
||||||
Services preserve CPSR and every caller register except the documented arithmetic outputs.
|
Services preserve CPSR and every caller register except the documented arithmetic outputs.
|
||||||
This is deterministic behavior, not a claim about undocumented firmware outputs.
|
This is deterministic behavior, not a claim about undocumented firmware outputs.
|
||||||
Supervisor stack use is 28 bytes normally and 40 bytes for division.
|
Supervisor stack use is 28 bytes normally, including ArcTan. Division and ArcTan2 use 40 bytes.
|
||||||
CpuFastSet, BitUnPack, all three decompression formats, and differential filters use 60 bytes.
|
CpuFastSet, BitUnPack, all three decompression formats, and differential filters use 60 bytes.
|
||||||
They do not reproduce the original firmware's internal stack layout or cycle counts.
|
They do not reproduce the original firmware's internal stack layout or cycle counts.
|
||||||
|
|
||||||
|
|
@ -567,6 +569,30 @@ Its integer algorithm uses 16 iterations without floating-point calculations.
|
||||||
Arithmetic services keep IRQ delivery masked until return, without changing IME.
|
Arithmetic services keep IRQ delivery masked until return, without changing IME.
|
||||||
Device clocks and DMA continue. Instruction costs do not reproduce the original BIOS algorithms' timing.
|
Device clocks and DMA continue. Instruction costs do not reproduce the original BIOS algorithms' timing.
|
||||||
|
|
||||||
|
#### Fixed-point angles
|
||||||
|
|
||||||
|
`ArcTan` (`SWI 0x09`) and `ArcTan2` (`SWI 0x0a`) accept signed 16-bit fixed-point inputs with 14 fractional bits.
|
||||||
|
An input value of `0x4000` represents 1.0. This format is also called signed Q2.14.
|
||||||
|
Inputs must be sign-extended into their 32-bit registers. For example, -1 uses `0xffffffff`, not `0x0000ffff`.
|
||||||
|
This subset rejects values outside `-32768..32767` through `bios::INVALID_ARGUMENT_TRAP`, rather than interpreting undocumented wider inputs.
|
||||||
|
|
||||||
|
Angles use 65,536 units per full turn. A quarter turn is `0x4000`; a half turn is `0x8000`.
|
||||||
|
ArcTan reads the tangent from r0 and returns a sign-extended signed angle in r0.
|
||||||
|
For example, tangents +1.0 and -1.0 return +`0x2000` and -`0x2000`, respectively.
|
||||||
|
The routine evaluates a fixed-point polynomial using low-32-bit products and arithmetic shifts.
|
||||||
|
Negative intermediate results round down. The implementation does not substitute a host floating-point calculation.
|
||||||
|
The polynomial has poor accuracy for tangent magnitudes above 1.0; this known limitation is retained.
|
||||||
|
|
||||||
|
ArcTan2 reads X from r0 and Y from r1. It returns an unsigned angle in `0..65535` in r0.
|
||||||
|
It divides the smaller coordinate magnitude by the larger, then applies the shared polynomial and quadrant correction.
|
||||||
|
The signed ratio truncates toward zero and stays within `[-1.0, 1.0]` before polynomial evaluation.
|
||||||
|
Axes return exact quarter-turn angles. The zero vector returns zero without division.
|
||||||
|
Use ArcTan2 for direction calculations that require all four quadrants.
|
||||||
|
|
||||||
|
Both services preserve r1–r14 and the caller's status. Undocumented BIOS scratch-register outputs are not reproduced.
|
||||||
|
They execute original ARM instructions and keep CPU IRQ delivery masked until return, without changing IME.
|
||||||
|
Device clocks and DMA continue. Exact firmware timing and full hardware compatibility remain unverified.
|
||||||
|
|
||||||
#### LZ77 decompression
|
#### LZ77 decompression
|
||||||
|
|
||||||
Both variants read a word-aligned header at r0. Its low byte must be `0x10`; the upper 24 bits specify output length.
|
Both variants read a word-aligned header at r0. Its low byte must be `0x10`; the upper 24 bits specify output length.
|
||||||
|
|
@ -726,7 +752,7 @@ Unsupported SWIs, unsupported exception vectors, and null/misaligned IRQ callbac
|
||||||
The CPU reports `CpuError::UnsupportedInstruction` with `bios::UNSUPPORTED_TRAP`, rather than silently treating a service as a no-op.
|
The CPU reports `CpuError::UnsupportedInstruction` with `bios::UNSUPPORTED_TRAP`, rather than silently treating a service as a no-op.
|
||||||
Invalid arithmetic/decompression arguments use the same CPU error type with the distinct `bios::INVALID_ARGUMENT_TRAP` instruction.
|
Invalid arithmetic/decompression arguments use the same CPU error type with the distinct `bios::INVALID_ARGUMENT_TRAP` instruction.
|
||||||
Prior boot/service steps remain committed on failure.
|
Prior boot/service steps remain committed on failure.
|
||||||
Trigonometry, reset services, and STOP remain unimplemented.
|
BIOS affine-matrix helpers, reset services, and STOP remain unimplemented.
|
||||||
This subset is not sufficient for Pokémon Emerald compatibility.
|
This subset is not sufficient for Pokémon Emerald compatibility.
|
||||||
|
|
||||||
### Timers, interrupt registers, and the machine clock
|
### Timers, interrupt registers, and the machine clock
|
||||||
|
|
@ -1478,6 +1504,7 @@ Important timing limits:
|
||||||
| `src/lib.rs` | Core modules and original demo bytes |
|
| `src/lib.rs` | Core modules and original demo bytes |
|
||||||
| `src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services |
|
| `src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services |
|
||||||
| `src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines |
|
| `src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines |
|
||||||
|
| `src/bios/angles.rs` | Emitted ARM ArcTan polynomial, ArcTan2 ratio/quadrant handling, and input validation |
|
||||||
| `src/bios/lz77.rs` | Emitted ARM LZ77 decoder with byte and halfword output |
|
| `src/bios/lz77.rs` | Emitted ARM LZ77 decoder with byte and halfword output |
|
||||||
| `src/bios/run_length.rs` | Emitted ARM run-length decoder, header validation, and block bounds |
|
| `src/bios/run_length.rs` | Emitted ARM run-length decoder, header validation, and block bounds |
|
||||||
| `src/bios/bit_unpack.rs` | Emitted ARM packed-unit expansion, offsets, word stores, and validation |
|
| `src/bios/bit_unpack.rs` | Emitted ARM packed-unit expansion, offsets, word stores, and validation |
|
||||||
|
|
@ -1536,6 +1563,7 @@ Important timing limits:
|
||||||
| `tests/halt.rs` | HALT wake masks, idle timing, BIOS-only writes, DMA progress, and STOP diagnostics |
|
| `tests/halt.rs` | HALT wake masks, idle timing, BIOS-only writes, DMA progress, and STOP diagnostics |
|
||||||
| `tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot |
|
| `tests/bios.rs` | ARM/Thumb service calls, copy/fill boundaries, wait races, callback contracts, and boot |
|
||||||
| `tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics |
|
| `tests/bios/arithmetic.rs` | Arithmetic boundaries, wide-integer references, status restoration, and zero-division diagnostics |
|
||||||
|
| `tests/bios/angles.rs` | Fixed-point references, axes/quadrants, rounding, caller flags, stack bounds, DMA, and diagnostics |
|
||||||
| `tests/bios/lz77.rs` | Token-reference tests, overlaps, output widths, malformed streams, and partial failures |
|
| `tests/bios/lz77.rs` | Token-reference tests, overlaps, output widths, malformed streams, and partial failures |
|
||||||
| `tests/bios/run_length.rs` | All block controls, mixed streams, output widths, status, diagnostics, DMA, and IRQ masking |
|
| `tests/bios/run_length.rs` | All block controls, mixed streams, output widths, status, diagnostics, DMA, and IRQ masking |
|
||||||
| `tests/bios/bit_unpack.rs` | Width pairs, bit references, offsets, maximum length, memory boundaries, status, DMA, and diagnostics |
|
| `tests/bios/bit_unpack.rs` | Width pairs, bit references, offsets, maximum length, memory boundaries, status, DMA, and diagnostics |
|
||||||
|
|
@ -1608,12 +1636,15 @@ To test the core without building the window dependency:
|
||||||
direnv exec . cargo test --locked --no-default-features
|
direnv exec . cargo test --locked --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs 645 core and integration tests; the eleven desktop and command-line tests are excluded.
|
This runs 660 core and integration tests; the eleven desktop and command-line tests are excluded.
|
||||||
|
|
||||||
On Apple Silicon, `file` must report a Mach-O `arm64` executable.
|
On Apple Silicon, `file` must report a Mach-O `arm64` executable.
|
||||||
Do not set a Linux cross-compilation target for this validation.
|
Do not set a Linux cross-compilation target for this validation.
|
||||||
|
|
||||||
The default suite has 656 tests.
|
The default suite has 671 tests.
|
||||||
|
Angle-service tests compare results with wide-integer polynomial and signed-sector references, plus floating-point accuracy checks in supported ranges.
|
||||||
|
They cover a dense ArcTan unit-interval grid, sampled full-domain and seeded inputs, axes, quadrant boundaries, signed extremes, and scale invariance.
|
||||||
|
Other tests verify all caller flag combinations, User/System masks, non-result registers, stack bounds, repeated calls, diagnostics, and DMA/IRQ progress.
|
||||||
Huffman tests use an independent tree serializer and path encoder, with expected output taken directly from original symbols.
|
Huffman tests use an independent tree serializer and path encoder, with expected output taken directly from original symbols.
|
||||||
They cover every byte/nibble value, maximum tree size and offsets, deep paths across input-word boundaries, and seeded variable-length paths.
|
They cover every byte/nibble value, maximum tree size and offsets, deep paths across input-word boundaries, and seeded variable-length paths.
|
||||||
Other tests check 24-bit byte lengths, malformed trees, output packing, truncation, partial failures, source regions, and DMA/IRQ behavior.
|
Other tests check 24-bit byte lengths, malformed trees, output packing, truncation, partial failures, source regions, and DMA/IRQ behavior.
|
||||||
|
|
@ -1741,7 +1772,7 @@ These tests do not replace validation against GBA hardware or public hardware te
|
||||||
|
|
||||||
1. Validate CPU behavior with public ARM7TDMI test programs before claiming instruction compatibility.
|
1. Validate CPU behavior with public ARM7TDMI test programs before claiming instruction compatibility.
|
||||||
2. Refine nominal timing with a fetch pipeline, Game Pak prefetch, per-access device updates, and verified timer/IRQ delays.
|
2. Refine nominal timing with a fetch pipeline, Game Pak prefetch, per-access device updates, and verified timer/IRQ delays.
|
||||||
3. Expand BIOS services with trigonometry and reset functions; add STOP and remaining DMA device modes.
|
3. Expand BIOS services with affine-matrix helpers and reset functions; add STOP and remaining DMA device modes.
|
||||||
4. Replace nominal sprite work limits with verified individual fetch timing; add background fetch timing and per-pixel composition.
|
4. Replace nominal sprite work limits with verified individual fetch timing; add background fetch timing and per-pixel composition.
|
||||||
5. Expand graphics, audio, cartridge loading, and saves before testing Emerald compatibility.
|
5. Expand graphics, audio, cartridge loading, and saves before testing Emerald compatibility.
|
||||||
|
|
||||||
|
|
@ -1757,7 +1788,7 @@ Hardware references used for the core:
|
||||||
- [GBATEK BIOS memory-copy services](https://problemkaputt.de/gbatek-bios-memory-copy.htm).
|
- [GBATEK BIOS memory-copy services](https://problemkaputt.de/gbatek-bios-memory-copy.htm).
|
||||||
- [GBATEK BIOS arithmetic services](https://problemkaputt.de/gbatek-bios-arithmetic-functions.htm).
|
- [GBATEK BIOS arithmetic services](https://problemkaputt.de/gbatek-bios-arithmetic-functions.htm).
|
||||||
- [GBATEK BIOS decompression services](https://problemkaputt.de/gbatek-bios-decompression-functions.htm), including BitUnPack descriptor fields, differential-filter headers, Huffman tree layout, and width constraints.
|
- [GBATEK BIOS decompression services](https://problemkaputt.de/gbatek-bios-decompression-functions.htm), including BitUnPack descriptor fields, differential-filter headers, Huffman tree layout, and width constraints.
|
||||||
- [mGBA BIOS service implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c), reviewed for BitUnPack ordering/offsets and Huffman tree-size interpretation, input bits, and symbol packing.
|
- [mGBA BIOS service implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c), reviewed for angle-polynomial coefficients and quadrant conventions, BitUnPack ordering/offsets, and Huffman tree layout and packing.
|
||||||
- [HALTCNT hardware-access tests](https://github.com/mgba-emu/mgba/issues/2309), for BIOS-only writes and halfword access.
|
- [HALTCNT hardware-access tests](https://github.com/mgba-emu/mgba/issues/2309), for BIOS-only writes and halfword access.
|
||||||
- [GBATEK display status and IRQs](https://problemkaputt.de/gbatek-lcd-i-o-interrupts-and-status.htm).
|
- [GBATEK display status and IRQs](https://problemkaputt.de/gbatek-lcd-i-o-interrupts-and-status.htm).
|
||||||
- [GBATEK display dimensions and timings](https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm).
|
- [GBATEK display dimensions and timings](https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm).
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//! Original, optional ARM BIOS replacement. No Nintendo firmware is included.
|
//! Original, optional ARM BIOS replacement. No Nintendo firmware is included.
|
||||||
//! Supports interrupt waits, integer arithmetic, memory copy/fill, bit unpacking,
|
//! Supports interrupt waits, integer/fixed-point arithmetic, memory copy/fill, bit unpacking,
|
||||||
//! LZ77/run-length/Huffman decoding, and differential filters.
|
//! LZ77/run-length/Huffman decoding, and differential filters.
|
||||||
//! This is a functional subset, not a complete boot ROM or a timing-compatible BIOS.
|
//! This is a functional subset, not a complete boot ROM or a timing-compatible BIOS.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
mod angles;
|
||||||
mod arithmetic;
|
mod arithmetic;
|
||||||
mod bit_unpack;
|
mod bit_unpack;
|
||||||
mod decompression;
|
mod decompression;
|
||||||
|
|
@ -27,7 +28,7 @@ pub const IRQ_FLAGS: u32 = 0x0300_7ff8;
|
||||||
pub const IRQ_HANDLER: u32 = 0x0300_7ffc;
|
pub const IRQ_HANDLER: u32 = 0x0300_7ffc;
|
||||||
/// An intentional undefined instruction used for unsupported SWIs/vectors and invalid IRQ pointers.
|
/// An intentional undefined instruction used for unsupported SWIs/vectors and invalid IRQ pointers.
|
||||||
pub const UNSUPPORTED_TRAP: u32 = 0xe7f0_00f0;
|
pub const UNSUPPORTED_TRAP: u32 = 0xe7f0_00f0;
|
||||||
/// A diagnostic trap for division by zero or invalid unpacking, decompression, or filter arguments.
|
/// A diagnostic trap for invalid arithmetic or asset-conversion arguments.
|
||||||
pub const INVALID_ARGUMENT_TRAP: u32 = 0xe7f0_00f1;
|
pub const INVALID_ARGUMENT_TRAP: u32 = 0xe7f0_00f1;
|
||||||
|
|
||||||
/// Start at reset with our optional firmware. Call Machine::step to execute boot.
|
/// Start at reset with our optional firmware. Call Machine::step to execute boot.
|
||||||
|
|
@ -121,6 +122,8 @@ pub fn image() -> Vec<u8> {
|
||||||
(6, "div"),
|
(6, "div"),
|
||||||
(7, "div_arm"),
|
(7, "div_arm"),
|
||||||
(8, "sqrt"),
|
(8, "sqrt"),
|
||||||
|
(9, "arctan"),
|
||||||
|
(0x0a, "arctan2"),
|
||||||
(0x0b, "cpu_set"),
|
(0x0b, "cpu_set"),
|
||||||
(0x0c, "cpu_fast_set"),
|
(0x0c, "cpu_fast_set"),
|
||||||
(0x10, "bit_unpack"),
|
(0x10, "bit_unpack"),
|
||||||
|
|
@ -224,6 +227,7 @@ pub fn image() -> Vec<u8> {
|
||||||
);
|
);
|
||||||
|
|
||||||
arithmetic::emit(&mut a);
|
arithmetic::emit(&mut a);
|
||||||
|
angles::emit(&mut a);
|
||||||
bit_unpack::emit(&mut a);
|
bit_unpack::emit(&mut a);
|
||||||
lz77::emit(&mut a);
|
lz77::emit(&mut a);
|
||||||
run_length::emit(&mut a);
|
run_length::emit(&mut a);
|
||||||
|
|
|
||||||
112
src/bios/angles.rs
Normal file
112
src/bios/angles.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
//! Original ARM implementations of the BIOS fixed-point angle services.
|
||||||
|
//! Inputs must be sign-extended signed 16-bit values. Only r0 is returned;
|
||||||
|
//! undocumented scratch-register results and original BIOS timing are not modeled.
|
||||||
|
|
||||||
|
use super::ArmImage;
|
||||||
|
|
||||||
|
pub(super) fn emit(a: &mut ArmImage) {
|
||||||
|
a.label("arctan");
|
||||||
|
validate_input(a, 0);
|
||||||
|
a.call("atan_polynomial");
|
||||||
|
a.emit(0xe58d_0004); // STR r0,[sp,#4] (saved caller r0)
|
||||||
|
a.branch(14, "return");
|
||||||
|
|
||||||
|
a.label("arctan2");
|
||||||
|
validate_input(a, 0);
|
||||||
|
validate_input(a, 1);
|
||||||
|
a.emit(0xe92d_0070); // STMDB sp!,{r4-r6} (40 bytes total SVC stack use)
|
||||||
|
a.emit(0xe351_0000); // CMP r1,#0 (Y)
|
||||||
|
a.branch(0, "atan2_horizontal_axis");
|
||||||
|
a.emit(0xe350_0000); // CMP r0,#0 (X)
|
||||||
|
a.branch(0, "atan2_vertical_axis");
|
||||||
|
a.emit(0xe1a0_4000); // MOV r4,r0 (original X)
|
||||||
|
a.emit(0xe1a0_5001); // MOV r5,r1 (original Y)
|
||||||
|
a.emit(0xe020_6001); // EOR r6,r0,r1 (ratio sign in bit31)
|
||||||
|
a.emit(0xe350_0000); // CMP r0,#0
|
||||||
|
a.emit(0x4260_0000); // RSBMI r0,r0,#0 (|X|, including -32768)
|
||||||
|
a.emit(0xe351_0000); // CMP r1,#0
|
||||||
|
a.emit(0x4261_1000); // RSBMI r1,r1,#0 (|Y|)
|
||||||
|
a.emit(0xe150_0001); // CMP r0,r1
|
||||||
|
a.branch(3, "atan2_vertical_ratio"); // BLO |X| < |Y|
|
||||||
|
|
||||||
|
// Horizontal sectors: ArcTan(Y/X) plus 0 or half a turn.
|
||||||
|
a.emit(0xe1a0_2000); // MOV r2,r0
|
||||||
|
a.emit(0xe1a0_0701); // MOV r0,r1,LSL #14 (unsigned numerator)
|
||||||
|
a.emit(0xe1a0_1002); // MOV r1,r2 (unsigned denominator)
|
||||||
|
a.emit(0xe354_0000); // CMP r4,#0 (original X)
|
||||||
|
a.emit(0xa3a0_4000); // MOVGE r4,#0
|
||||||
|
a.emit(0xb3a0_4902); // MOVLT r4,#0x8000
|
||||||
|
a.emit(0xe3a0_5000); // MOV r5,#0 (add angle to base)
|
||||||
|
a.branch(14, "atan2_divide");
|
||||||
|
|
||||||
|
a.label("atan2_vertical_ratio");
|
||||||
|
// Vertical sectors: quarter/three-quarter turn minus ArcTan(X/Y).
|
||||||
|
a.emit(0xe1a0_0700); // MOV r0,r0,LSL #14
|
||||||
|
a.emit(0xe355_0000); // CMP r5,#0 (original Y)
|
||||||
|
a.emit(0xa3a0_4901); // MOVGE r4,#0x4000
|
||||||
|
a.emit(0xb3a0_4903); // MOVLT r4,#0xc000
|
||||||
|
a.emit(0xe3a0_5001); // MOV r5,#1 (subtract angle from base)
|
||||||
|
|
||||||
|
a.label("atan2_divide");
|
||||||
|
// Binary long division of (smaller magnitude << 14) by larger magnitude.
|
||||||
|
// Axis cases excluded zero denominators; the quotient is at most 0x4000.
|
||||||
|
a.emit(0xe3a0_2000); // MOV r2,#0 (quotient)
|
||||||
|
a.emit(0xe3a0_3000); // MOV r3,#0 (remainder)
|
||||||
|
a.emit(0xe3a0_c020); // MOV r12,#32
|
||||||
|
a.label("atan2_divide_bit");
|
||||||
|
a.emit(0xe1b0_0080); // MOVS r0,r0,LSL #1
|
||||||
|
a.emit(0xe0a3_3003); // ADC r3,r3,r3
|
||||||
|
a.emit(0xe153_0001); // CMP r3,r1
|
||||||
|
a.emit(0x2043_3001); // SUBCS r3,r3,r1
|
||||||
|
a.emit(0xe0a2_2002); // ADC r2,r2,r2
|
||||||
|
a.emit(0xe25c_c001); // SUBS r12,r12,#1
|
||||||
|
a.branch(1, "atan2_divide_bit");
|
||||||
|
a.emit(0xe1a0_0002); // MOV r0,r2
|
||||||
|
a.emit(0xe316_0102); // TST r6,#0x80000000
|
||||||
|
a.emit(0x1260_0000); // RSBNE r0,r0,#0 (signed ratio, truncated toward zero)
|
||||||
|
a.call("atan_polynomial");
|
||||||
|
a.emit(0xe355_0000); // CMP r5,#0
|
||||||
|
a.emit(0x0084_0000); // ADDEQ r0,r4,r0
|
||||||
|
a.emit(0x1060_0004); // RSBNE r0,r0,r4
|
||||||
|
a.branch(14, "atan2_done");
|
||||||
|
|
||||||
|
a.label("atan2_horizontal_axis");
|
||||||
|
a.emit(0xe350_0000); // CMP r0,#0
|
||||||
|
a.emit(0xa3a0_0000); // MOVGE r0,#0 (includes X=Y=0)
|
||||||
|
a.emit(0xb3a0_0902); // MOVLT r0,#0x8000
|
||||||
|
a.branch(14, "atan2_done");
|
||||||
|
a.label("atan2_vertical_axis");
|
||||||
|
a.emit(0xe351_0000); // CMP r1,#0
|
||||||
|
a.emit(0xa3a0_0901); // MOVGE r0,#0x4000
|
||||||
|
a.emit(0xb3a0_0903); // MOVLT r0,#0xc000
|
||||||
|
a.label("atan2_done");
|
||||||
|
a.emit(0xe1a0_0800); // MOV r0,r0,LSL #16
|
||||||
|
a.emit(0xe1a0_0820); // MOV r0,r0,LSR #16 (unsigned turn angle)
|
||||||
|
a.emit(0xe58d_0010); // STR r0,[sp,#16] (saved caller r0 below local frame)
|
||||||
|
a.emit(0xe8bd_0070); // LDMIA sp!,{r4-r6}
|
||||||
|
a.branch(14, "return");
|
||||||
|
|
||||||
|
// Shared leaf kernel: r0 is signed Q2.14; r1-r3 are scratch.
|
||||||
|
// Every multiply keeps its low 32 bits before the arithmetic shift.
|
||||||
|
a.label("atan_polynomial");
|
||||||
|
a.emit(0xe001_0090); // MUL r1,r0,r0
|
||||||
|
a.emit(0xe1a0_1741); // MOV r1,r1,ASR #14
|
||||||
|
a.emit(0xe261_1000); // RSB r1,r1,#0 (negative squared ratio)
|
||||||
|
a.emit(0xe3a0_20a9); // MOV r2,#0xa9
|
||||||
|
for coefficient in [0x390, 0x91c, 0xfb6, 0x16aa, 0x2081, 0x3651, 0xa2f9] {
|
||||||
|
a.emit(0xe003_0192); // MUL r3,r2,r1
|
||||||
|
a.emit(0xe1a0_2743); // MOV r2,r3,ASR #14
|
||||||
|
a.literal(3, coefficient);
|
||||||
|
a.emit(0xe082_2003); // ADD r2,r2,r3
|
||||||
|
}
|
||||||
|
a.emit(0xe003_0290); // MUL r3,r0,r2
|
||||||
|
a.emit(0xe1a0_0843); // MOV r0,r3,ASR #16 (sign-extended 16-bit angle)
|
||||||
|
a.emit(0xe12f_ff1e); // BX lr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_input(a: &mut ArmImage, register: u32) {
|
||||||
|
a.emit(0xe1a0_2800 | register); // MOV r2,rN,LSL #16
|
||||||
|
a.emit(0xe1a0_2842); // MOV r2,r2,ASR #16
|
||||||
|
a.emit(0xe150_0002 | register << 16); // CMP rN,r2
|
||||||
|
a.branch(1, "invalid_argument");
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@ const DEST: u32 = 0x0201_0000;
|
||||||
const CALLBACK: u32 = 0x0300_0100;
|
const CALLBACK: u32 = 0x0300_0100;
|
||||||
const ROM_DATA: u32 = ROM_START + 13 * 4;
|
const ROM_DATA: u32 = ROM_START + 13 * 4;
|
||||||
|
|
||||||
|
#[path = "bios/angles.rs"]
|
||||||
|
mod angles;
|
||||||
#[path = "bios/arithmetic.rs"]
|
#[path = "bios/arithmetic.rs"]
|
||||||
mod arithmetic;
|
mod arithmetic;
|
||||||
#[path = "bios/bit_unpack.rs"]
|
#[path = "bios/bit_unpack.rs"]
|
||||||
|
|
@ -223,7 +225,7 @@ fn vblank_wait_halts_dispatches_irq_and_returns_through_original_code() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unsupported_services_fail_at_an_explicit_instruction_trap() {
|
fn unsupported_services_fail_at_an_explicit_instruction_trap() {
|
||||||
for service in [0, 1, 3, 9, 0x0a, 0x19, 0xff] {
|
for service in [0, 1, 3, 0x0d, 0x0e, 0x0f, 0x19, 0xff] {
|
||||||
let (mut machine, _) = call(service, false, [0, 0, 0]);
|
let (mut machine, _) = call(service, false, [0, 0, 0]);
|
||||||
let error = (0..100)
|
let error = (0..100)
|
||||||
.find_map(|_| machine.step().err())
|
.find_map(|_| machine.step().err())
|
||||||
|
|
|
||||||
377
tests/bios/angles.rs
Normal file
377
tests/bios/angles.rs
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
use super::*;
|
||||||
|
use gba_rust::{bios::INVALID_ARGUMENT_TRAP, dma::DMA_BASE};
|
||||||
|
|
||||||
|
/// Wide-integer reference for signed low-word multiplication and floor division.
|
||||||
|
/// This avoids both host signed overflow and the emitted instruction sequence.
|
||||||
|
fn low_signed(value: i64) -> i64 {
|
||||||
|
let bits = value.rem_euclid(1i64 << 32);
|
||||||
|
if bits >= 1i64 << 31 {
|
||||||
|
bits - (1i64 << 32)
|
||||||
|
} else {
|
||||||
|
bits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn atan_reference(tangent: i32) -> i32 {
|
||||||
|
let a = -low_signed(i64::from(tangent).pow(2)).div_euclid(16384);
|
||||||
|
let mut polynomial = 0xa9i64;
|
||||||
|
for coefficient in [0x390, 0x91c, 0xfb6, 0x16aa, 0x2081, 0x3651, 0xa2f9] {
|
||||||
|
polynomial = low_signed(low_signed(polynomial * a).div_euclid(16384) + coefficient);
|
||||||
|
}
|
||||||
|
low_signed(i64::from(tangent) * polynomial).div_euclid(65536) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Eight-sector signed-coordinate reference; production uses absolute magnitudes.
|
||||||
|
fn atan2_reference(x: i32, y: i32) -> u32 {
|
||||||
|
if y == 0 {
|
||||||
|
return if x < 0 { 0x8000 } else { 0 };
|
||||||
|
}
|
||||||
|
if x == 0 {
|
||||||
|
return if y < 0 { 0xc000 } else { 0x4000 };
|
||||||
|
}
|
||||||
|
let ratio = |n: i32, d: i32| atan_reference((i64::from(n) * 16384 / i64::from(d)) as i32);
|
||||||
|
let angle = if y > 0 {
|
||||||
|
if x > 0 && x >= y {
|
||||||
|
ratio(y, x)
|
||||||
|
} else if x < 0 && -x >= y {
|
||||||
|
0x8000 + ratio(y, x)
|
||||||
|
} else {
|
||||||
|
0x4000 - ratio(x, y)
|
||||||
|
}
|
||||||
|
} else if x < 0 && -x > -y {
|
||||||
|
0x8000 + ratio(y, x)
|
||||||
|
} else if x > 0 && x >= -y {
|
||||||
|
0x10000 + ratio(y, x)
|
||||||
|
} else {
|
||||||
|
0xc000 - ratio(x, y)
|
||||||
|
};
|
||||||
|
(angle & 0xffff) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(service: u8, x: i32, y: i32, thumb: bool) -> u32 {
|
||||||
|
let (mut m, pc) = call(service, thumb, [x as u32, y as u32, 0xcafe_babe]);
|
||||||
|
let before = m.cpu().clone();
|
||||||
|
let guard = bios::SVC_STACK - if service == 9 { 32 } else { 44 };
|
||||||
|
m.memory_mut().write32(guard, 0xa55a_1234).unwrap();
|
||||||
|
finish(&mut m, pc);
|
||||||
|
assert_eq!(&m.cpu().registers()[1..15], &before.registers()[1..15]);
|
||||||
|
assert_eq!(m.cpu().cpsr(), before.cpsr());
|
||||||
|
assert_eq!(m.memory().read32(guard).unwrap(), 0xa55a_1234);
|
||||||
|
let actual = m.cpu().registers()[0];
|
||||||
|
let expected = if service == 9 {
|
||||||
|
atan_reference(x) as u32
|
||||||
|
} else {
|
||||||
|
atan2_reference(x, y)
|
||||||
|
};
|
||||||
|
assert_eq!(actual, expected, "service={service}, x={x}, y={y}");
|
||||||
|
actual
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_direction(x: i32, y: i32, thumb: bool) {
|
||||||
|
let actual = run(0x0a, x, y, thumb);
|
||||||
|
if x == 0 && y == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ideal = f64::from(y).atan2(f64::from(x)) * 32768.0 / std::f64::consts::PI;
|
||||||
|
let error = (f64::from(actual) - ideal + 32768.0).rem_euclid(65536.0) - 32768.0;
|
||||||
|
assert!(
|
||||||
|
error.abs() < 3.0,
|
||||||
|
"({x},{y}) actual={actual} ideal={ideal} error={error}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan_known_fixed_point_values_include_signed_results() {
|
||||||
|
for (input, expected) in [
|
||||||
|
(-16384, -8192),
|
||||||
|
(-8192, -4836),
|
||||||
|
(-1, -1),
|
||||||
|
(0, 0),
|
||||||
|
(1, 0),
|
||||||
|
(8192, 4836),
|
||||||
|
(16384, 8192),
|
||||||
|
] {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
assert_eq!(run(9, input, 0x12345678, thumb) as i32, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan_dense_unit_interval_grid_matches_polynomial_and_real_angle() {
|
||||||
|
for input in (-16384..=16384).step_by(16) {
|
||||||
|
let actual = run(9, input, 0, input & 16 != 0) as i32;
|
||||||
|
let ideal = (f64::from(input) / 16384.0).atan() * 32768.0 / std::f64::consts::PI;
|
||||||
|
assert!((f64::from(actual) - ideal).abs() < 2.0, "input={input}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan_full_signed_domain_uses_wrapped_products_not_host_float() {
|
||||||
|
for input in (-32768..=32767).step_by(257) {
|
||||||
|
run(9, input, 0, input & 1 != 0);
|
||||||
|
}
|
||||||
|
// Known out-of-unit-range accuracy problems are retained, not corrected with host atan.
|
||||||
|
assert_eq!(run(9, -32768, 0, false) as i32, -5795);
|
||||||
|
assert_eq!(run(9, 32767, 0, true), 5848);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan_seeded_inputs_match_wide_integer_reference() {
|
||||||
|
let mut seed = 0x9a2b_14cdu32;
|
||||||
|
for index in 0..1024 {
|
||||||
|
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||||
|
run(9, i32::from((seed >> 16) as i16), 0, index % 2 == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_arctan_rounds_down_instead_of_truncating_toward_zero() {
|
||||||
|
for input in [1, 2, 3, 127, 8191, 8192, 16383, 16384] {
|
||||||
|
let positive = run(9, input, 0, false) as i32;
|
||||||
|
let negative = run(9, -input, 0, true) as i32;
|
||||||
|
assert!([0, -1].contains(&(positive + negative)));
|
||||||
|
}
|
||||||
|
assert_eq!(run(9, -1, 0, true), u32::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan2_axes_origin_and_diagonals_have_exact_turn_angles() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for (x, y, expected) in [
|
||||||
|
(0, 0, 0),
|
||||||
|
(1, 0, 0),
|
||||||
|
(-32768, 0, 0x8000),
|
||||||
|
(0, 32767, 0x4000),
|
||||||
|
(0, -32768, 0xc000),
|
||||||
|
(1, 1, 0x2000),
|
||||||
|
(-1, 1, 0x6000),
|
||||||
|
(-1, -1, 0xa000),
|
||||||
|
(1, -1, 0xe000),
|
||||||
|
(-32768, -32768, 0xa000),
|
||||||
|
(32767, 32767, 0x2000),
|
||||||
|
] {
|
||||||
|
assert_eq!(run(0x0a, x, y, thumb), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan2_all_quadrants_and_signed_extremes_match_both_references() {
|
||||||
|
let values = [
|
||||||
|
-32768, -32767, -16384, -1024, -2, -1, 0, 1, 2, 1024, 16384, 32766, 32767,
|
||||||
|
];
|
||||||
|
for x in values {
|
||||||
|
for y in values {
|
||||||
|
check_direction(x, y, (x ^ y) & 1 != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan2_sector_boundaries_handle_equal_and_adjacent_magnitudes() {
|
||||||
|
for magnitude in [1, 2, 3, 127, 1024, 16384, 32766] {
|
||||||
|
for delta in [-1, 0, 1] {
|
||||||
|
for sx in [-1, 1] {
|
||||||
|
for sy in [-1, 1] {
|
||||||
|
check_direction(sx * magnitude, sy * (magnitude + delta), delta != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan2_seeded_signed_coordinates_match_integer_and_float_references() {
|
||||||
|
let mut seed = 0x5017_e3b9u32;
|
||||||
|
for index in 0..1024 {
|
||||||
|
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||||
|
let x = i32::from((seed >> 16) as i16);
|
||||||
|
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||||
|
let y = i32::from((seed >> 16) as i16);
|
||||||
|
check_direction(x, y, index % 2 != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arctan2_preserves_direction_when_coordinates_scale_equally() {
|
||||||
|
for (x, y) in [
|
||||||
|
(3, 7),
|
||||||
|
(7, 3),
|
||||||
|
(-3, 7),
|
||||||
|
(-7, 3),
|
||||||
|
(-3, -7),
|
||||||
|
(-7, -3),
|
||||||
|
(3, -7),
|
||||||
|
(7, -3),
|
||||||
|
] {
|
||||||
|
let expected = run(0x0a, x, y, false);
|
||||||
|
for scale in [2, 7, 31, 4096] {
|
||||||
|
assert_eq!(run(0x0a, x * scale, y * scale, true), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_system_modes_masks_and_all_nonresult_registers_are_preserved() {
|
||||||
|
for service in [9, 0x0a] {
|
||||||
|
for status in [0x10, 0x50, 0x90, 0xd0, 0x1f, 0x5f, 0x9f, 0xdf] {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let (mut m, pc) = call_status(
|
||||||
|
service,
|
||||||
|
thumb,
|
||||||
|
[(-1234i32) as u32, 2345, 0xabcd_ef01],
|
||||||
|
status,
|
||||||
|
);
|
||||||
|
let before = m.cpu().clone();
|
||||||
|
finish(&mut m, pc);
|
||||||
|
assert_eq!(&m.cpu().registers()[1..15], &before.registers()[1..15]);
|
||||||
|
assert_eq!(m.cpu().cpsr(), before.cpsr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_caller_flag_combination_survives_angle_calculation() {
|
||||||
|
for service in [9, 0x0au32] {
|
||||||
|
for flags in 0..16u32 {
|
||||||
|
let mut m = bios::boot(words(&[
|
||||||
|
0xe3a0_0a02, // MOV r0,#0x2000
|
||||||
|
0xe3a0_1901, // MOV r1,#0x4000
|
||||||
|
0xe328_f200 | flags, // MSR CPSR_f,#flags<<28
|
||||||
|
0xef00_0000 | service << 16,
|
||||||
|
0xeaff_fffe,
|
||||||
|
]))
|
||||||
|
.unwrap();
|
||||||
|
reach(&mut m, ROM_START + 12, 100);
|
||||||
|
assert_eq!(m.cpu().cpsr() >> 28, flags);
|
||||||
|
let before = m.cpu().clone();
|
||||||
|
finish(&mut m, ROM_START + 16);
|
||||||
|
assert_eq!(m.cpu().cpsr(), before.cpsr());
|
||||||
|
assert_eq!(&m.cpu().registers()[1..15], &before.registers()[1..15]);
|
||||||
|
assert_eq!(
|
||||||
|
m.cpu().registers()[0],
|
||||||
|
if service == 9 {
|
||||||
|
atan_reference(8192) as u32
|
||||||
|
} else {
|
||||||
|
atan2_reference(8192, 16384)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn noncanonical_signed_sixteen_bit_arguments_are_repeatable_diagnostics() {
|
||||||
|
for value in [
|
||||||
|
0x8000,
|
||||||
|
0xffff,
|
||||||
|
0x10000,
|
||||||
|
0xffff_7fff,
|
||||||
|
0x7fff_ffff,
|
||||||
|
0x8000_0000,
|
||||||
|
] {
|
||||||
|
for (service, args) in [
|
||||||
|
(9, [value, 0, 0]),
|
||||||
|
(0x0a, [value, 1, 0]),
|
||||||
|
(0x0a, [1, value, 0]),
|
||||||
|
] {
|
||||||
|
let (mut m, _) = call(service, false, args);
|
||||||
|
let mut failed = false;
|
||||||
|
for _ in 0..150 {
|
||||||
|
let before = m.cpu().clone();
|
||||||
|
let cycles = m.cycles();
|
||||||
|
let timing = m.last_timing();
|
||||||
|
if let Err(error) = m.step() {
|
||||||
|
assert!(matches!(
|
||||||
|
error,
|
||||||
|
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
||||||
|
instruction: INVALID_ARGUMENT_TRAP,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
));
|
||||||
|
assert_eq!(m.cpu(), &before);
|
||||||
|
assert_eq!(m.cycles(), cycles);
|
||||||
|
assert_eq!(m.last_timing(), timing);
|
||||||
|
assert_eq!(m.step(), Err(error));
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repeated_calls_restore_supervisor_stack_and_do_not_retain_ratio_state() {
|
||||||
|
for service in [9u32, 0x0a] {
|
||||||
|
let mut m = bios::boot(words(&[
|
||||||
|
0xe3a0_0a02, // MOV r0,#0x2000
|
||||||
|
0xe3a0_1901, // MOV r1,#0x4000
|
||||||
|
0xef00_0000 | service << 16,
|
||||||
|
0xeaff_fffb, // B ROM_START
|
||||||
|
]))
|
||||||
|
.unwrap();
|
||||||
|
reach(&mut m, ROM_START, 100);
|
||||||
|
for _ in 0..100 {
|
||||||
|
reach(&mut m, ROM_START + 8, 10);
|
||||||
|
m.step().unwrap();
|
||||||
|
assert_eq!(m.cpu().registers()[13], bios::SVC_STACK);
|
||||||
|
reach(&mut m, ROM_START + 12, 1000);
|
||||||
|
assert_eq!(m.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
assert_eq!(
|
||||||
|
m.cpu().registers()[0],
|
||||||
|
if service == 9 {
|
||||||
|
atan_reference(8192) as u32
|
||||||
|
} else {
|
||||||
|
atan2_reference(8192, 16384)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
m.step().unwrap();
|
||||||
|
assert_eq!(m.cpu().pc(), ROM_START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn devices_and_dma_progress_and_pending_irq_waits_until_return() {
|
||||||
|
for service in [9, 0x0a] {
|
||||||
|
let (mut m, pc) = call(service, false, [8192, 16384, 0]);
|
||||||
|
m.step().unwrap(); // Enter SWI before enabling IRQ sources.
|
||||||
|
m.memory_mut().write16(IE, 8).unwrap();
|
||||||
|
m.memory_mut().write16(IME, 1).unwrap();
|
||||||
|
m.memory_mut().write32(TIMER_BASE, 0x00c0_fff0).unwrap();
|
||||||
|
m.memory_mut().write32(SOURCE, 0x8765_4321).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE, SOURCE).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE + 4, SOURCE + 4).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE + 8, 0x8400_0001).unwrap();
|
||||||
|
let mut dma = 0;
|
||||||
|
for _ in 0..1000 {
|
||||||
|
if m.cpu().pc() == pc {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let step = m.step().unwrap();
|
||||||
|
assert_ne!(step, StepKind::IrqEntry);
|
||||||
|
if matches!(step, StepKind::Dma { channel: 0 }) {
|
||||||
|
dma += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(m.cpu().pc(), pc);
|
||||||
|
assert_eq!(dma, 1);
|
||||||
|
assert_eq!(m.memory().read32(SOURCE + 4).unwrap(), 0x8765_4321);
|
||||||
|
assert_eq!(m.memory().read16(IF).unwrap() & 8, 8);
|
||||||
|
assert_eq!(m.memory().read16(IME).unwrap(), 1);
|
||||||
|
assert_eq!(m.memory().read16(IRQ_FLAGS).unwrap(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
m.cpu().registers()[0],
|
||||||
|
if service == 9 {
|
||||||
|
atan_reference(8192) as u32
|
||||||
|
} else {
|
||||||
|
atan2_reference(8192, 16384)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(m.step().unwrap(), StepKind::IrqEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue