feat(bios): implement SoftReset restart service
Execute SWI 0x00 through original ARM code. Read the restart flag before clearing 512 bytes of BIOS work RAM, reset System/SVC/IRQ stacks and specified registers, then branch to ROM or EWRAM in ARM System mode. Keep device state and clocks active, preserve unrelated CPU banks, and document the deterministic IRQ-masked restart status. Add eleven tests for every flag byte, ARM/Thumb and User/System callers, exact memory bounds, bank state, restart execution, DMA/timers, and unsupported exception callers. Validated on Darwin arm64: 705 default and 694 core-only tests in debug and release; formatting, Clippy, rustdoc, ten native window smoke tests, and CPU/timer demos pass.
This commit is contained in:
parent
f60db7e43b
commit
2dfab137a6
5 changed files with 468 additions and 12 deletions
64
README.md
64
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 694 tests pass in debug and release builds.
|
- All 705 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/fixed-point arithmetic, affine matrices, bit unpacking, LZ77/run-length/Huffman decompression, and differential filters.
|
- Optional original BIOS replacement: minimal boot, SoftReset, IRQ dispatch, interrupt waits, memory copy/fill, integer/fixed-point arithmetic, affine matrices, 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`.
|
||||||
|
|
@ -507,6 +507,7 @@ Supported software interrupt services:
|
||||||
|
|
||||||
| Number | Service | Behavior |
|
| Number | Service | Behavior |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| `0x00` | SoftReset | Clear BIOS work RAM and reset CPU registers/stacks; restart in ROM or RAM without returning |
|
||||||
| `0x02` | Halt | Wait for `IE & IF`; preserve IME and caller registers |
|
| `0x02` | Halt | Wait for `IE & IF`; preserve IME and caller registers |
|
||||||
| `0x04` | IntrWait | `r0`: discard old selected flags when nonzero; `r1`: flags to wait for |
|
| `0x04` | IntrWait | `r0`: discard old selected flags when nonzero; `r1`: flags to wait for |
|
||||||
| `0x05` | VBlankIntrWait | IntrWait with discard enabled and the VBlank flag selected |
|
| `0x05` | VBlankIntrWait | IntrWait with discard enabled and the VBlank flag selected |
|
||||||
|
|
@ -531,7 +532,8 @@ Supported software interrupt services:
|
||||||
|
|
||||||
ARM code uses `SWI service_number << 16`; Thumb code uses the service number directly.
|
ARM code uses `SWI service_number << 16`; Thumb code uses the service number directly.
|
||||||
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.
|
Returning services preserve CPSR and every caller register except the documented arithmetic outputs.
|
||||||
|
SoftReset instead initializes CPU registers and status as described below.
|
||||||
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, including ArcTan. Division and ArcTan2 use 40 bytes.
|
Supervisor stack use is 28 bytes normally, including ArcTan. Division and ArcTan2 use 40 bytes.
|
||||||
CpuFastSet, affine-matrix services, BitUnPack, all three decompression formats, and differential filters use 60 bytes.
|
CpuFastSet, affine-matrix services, BitUnPack, all three decompression formats, and differential filters use 60 bytes.
|
||||||
|
|
@ -560,6 +562,45 @@ Other invalid accesses use normal memory diagnostics. Earlier successful writes
|
||||||
These copy services keep CPU IRQ delivery masked until return; device clocks and DMA continue throughout.
|
These copy services keep CPU IRQ delivery masked until return; device clocks and DMA continue throughout.
|
||||||
BIOS bus protection, exact firmware timing, undocumented side effects, and nested service calls remain incomplete.
|
BIOS bus protection, exact firmware timing, undocumented side effects, and nested service calls remain incomplete.
|
||||||
|
|
||||||
|
#### SoftReset
|
||||||
|
|
||||||
|
`SoftReset` (`SWI 0x00`) does not return to its caller.
|
||||||
|
It reads the byte at `0x03007ffa` before clearing the containing RAM:
|
||||||
|
zero selects cartridge ROM at `0x08000000`; any nonzero value selects work RAM at `0x02000000`.
|
||||||
|
Surrounding bytes do not affect this choice. Both destinations start in ARM state, even for Thumb callers.
|
||||||
|
|
||||||
|
The service clears exactly `0x03007e00..0x03007fff`, including BIOS IRQ flags, the callback pointer, and the restart flag.
|
||||||
|
It resets r0–r12 to zero and initializes these CPU banks:
|
||||||
|
|
||||||
|
| Mode | Stack pointer | Link register | Saved status |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| System/User | `0x03007f00` | Selected restart address | Not present |
|
||||||
|
| Supervisor | `0x03007fe0` | Zero | Zero |
|
||||||
|
| IRQ | `0x03007fa0` | Zero | Zero |
|
||||||
|
|
||||||
|
Other banked registers and saved status registers remain unchanged.
|
||||||
|
The service enters System mode with CPSR `0x9f`: IRQ masked, FIQ unmasked, condition flags clear.
|
||||||
|
This is the subset's deterministic status policy, not independently verified hardware behavior.
|
||||||
|
It jumps through the System link register and does not execute the cold-boot sequence.
|
||||||
|
|
||||||
|
SoftReset does not reset I/O registers or devices.
|
||||||
|
IME, IE, pending IF bits, POSTFLG, display configuration, and timer configuration remain in place.
|
||||||
|
Device clocks and DMA continue, so counters and pending requests can change during the service.
|
||||||
|
IRQ delivery stays masked throughout the service and after restart.
|
||||||
|
Restart code must install an IRQ callback and configure interrupts before unmasking IRQ.
|
||||||
|
|
||||||
|
The common SWI entry still requires an initialized, writable Supervisor stack and saves a 28-byte frame.
|
||||||
|
SoftReset abandons that frame. It uses no further stack storage and never restores the old caller state.
|
||||||
|
With default stacks, the frame lies inside the cleared RAM.
|
||||||
|
If the Supervisor stack is outside this region, its entry writes remain there after reset.
|
||||||
|
The clearing loop performs ordinary word stores; stopping execution partway leaves a partially cleared region.
|
||||||
|
Active DMA can still modify memory. Software must stop conflicting transfers before requesting a restart.
|
||||||
|
|
||||||
|
Lower internal RAM, external RAM, palette RAM, video RAM, and sprite attribute memory are not cleared by SoftReset.
|
||||||
|
Use separate initialization code for those regions. `RegisterRamReset` is not implemented yet.
|
||||||
|
Calls from exception modes retain the existing unsupported-service diagnostic.
|
||||||
|
Exact firmware timing, bus-protection behavior, and undocumented side effects remain incomplete.
|
||||||
|
|
||||||
#### Integer arithmetic
|
#### Integer arithmetic
|
||||||
|
|
||||||
Div truncates the signed quotient toward zero. The remainder has the numerator's sign, or is zero.
|
Div truncates the signed quotient toward zero. The remainder has the numerator's sign, or is zero.
|
||||||
|
|
@ -808,7 +849,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.
|
||||||
BIOS reset services and STOP remain unimplemented.
|
RegisterRamReset, HardReset, 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
|
||||||
|
|
@ -1559,6 +1600,7 @@ Important timing limits:
|
||||||
| `src/timer_demo.rs` | Original timer-configuration program and IRQ handler |
|
| `src/timer_demo.rs` | Original timer-configuration program and IRQ handler |
|
||||||
| `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/reset.rs` | Emitted ARM SoftReset, BIOS work RAM clearing, bank initialization, and ROM/RAM restart |
|
||||||
| `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/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table |
|
| `src/bios/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table |
|
||||||
| `src/bios/angles.rs` | Emitted ARM ArcTan polynomial, ArcTan2 ratio/quadrant handling, and input validation |
|
| `src/bios/angles.rs` | Emitted ARM ArcTan polynomial, ArcTan2 ratio/quadrant handling, and input validation |
|
||||||
|
|
@ -1619,6 +1661,7 @@ Important timing limits:
|
||||||
| `tests/dma.rs` | DMA widths, latches, priority, triggers, IRQs, timing, errors, and CPU integration |
|
| `tests/dma.rs` | DMA widths, latches, priority, triggers, IRQs, timing, errors, and CPU integration |
|
||||||
| `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/reset.rs` | All restart flags, exact RAM boundaries, CPU banks, restart execution, device continuity, and IRQ masking |
|
||||||
| `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/affine.rs` | Matrix/origin references, all angle phases, strides, live rendering, register preservation, and diagnostics |
|
| `tests/bios/affine.rs` | Matrix/origin references, all angle phases, strides, live rendering, register preservation, and diagnostics |
|
||||||
| `tests/bios/angles.rs` | Fixed-point references, axes/quadrants, rounding, caller flags, stack bounds, DMA, and diagnostics |
|
| `tests/bios/angles.rs` | Fixed-point references, axes/quadrants, rounding, caller flags, stack bounds, DMA, and diagnostics |
|
||||||
|
|
@ -1694,12 +1737,12 @@ 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 683 core and integration tests; the eleven desktop and command-line tests are excluded.
|
This runs 694 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 694 tests.
|
The default suite has 705 tests.
|
||||||
Angle-service tests compare results with wide-integer polynomial and signed-sector references, plus floating-point accuracy checks in supported ranges.
|
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.
|
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.
|
Other tests verify all caller flag combinations, User/System masks, non-result registers, stack bounds, repeated calls, diagnostics, and DMA/IRQ progress.
|
||||||
|
|
@ -1774,6 +1817,10 @@ The CPU demo test checks sprite controls, OAM attributes, disabled unused object
|
||||||
Tile tests cover both color depths, palette banks, flips, all map sizes, scrolling, transparency, priorities, and register masks.
|
Tile tests cover both color depths, palette banks, flips, all map sizes, scrolling, transparency, priorities, and register masks.
|
||||||
They check errors for unsupported features and out-of-range fetches without changing the output image.
|
They check errors for unsupported features and out-of-range fetches without changing the output image.
|
||||||
The CPU tile demo test verifies asset copies, every pixel, wrapping, opposite directions, reset, and VBlank synchronization.
|
The CPU tile demo test verifies asset copies, every pixel, wrapping, opposite directions, reset, and VBlank synchronization.
|
||||||
|
SoftReset tests check every restart flag byte from ARM and Thumb, exact RAM clearing, and preserved memory.
|
||||||
|
They seed CPU banks and verify stack pointers, link registers, saved status, caller modes, and restart status.
|
||||||
|
Other tests execute RAM restart code, call BIOS services again, and restart from code inside the erased region.
|
||||||
|
Device tests check retained I/O state, advancing timers/DMA, pending IRQs, and instruction-by-instruction RAM clearing.
|
||||||
BIOS affine tests check all 256 angle phases against independent trigonometric and wide-integer references.
|
BIOS affine tests check all 256 angle phases against independent trigonometric and wide-integer references.
|
||||||
They cover signed scales, origin wraparound, rounding, ignored angle bits, strides, alignment, and partial failures.
|
They cover signed scales, origin wraparound, rounding, ignored angle bits, strides, alignment, and partial failures.
|
||||||
Live rendering tests apply generated matrices to background registers and sprite attribute memory.
|
Live rendering tests apply generated matrices to background registers and sprite attribute memory.
|
||||||
|
|
@ -1835,7 +1882,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 reset functions; add STOP and remaining DMA device modes.
|
3. Add RegisterRamReset with explicit limits for unimplemented sound/serial registers; 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.
|
||||||
|
|
||||||
|
|
@ -1846,11 +1893,12 @@ Hardware references used for the core:
|
||||||
|
|
||||||
- [GBATEK ARM instruction cycle times](https://problemkaputt.de/gbatek-arm-cpu-instruction-cycle-times.htm).
|
- [GBATEK ARM instruction cycle times](https://problemkaputt.de/gbatek-arm-cpu-instruction-cycle-times.htm).
|
||||||
- [GBATEK GBA system control and WAITCNT](https://problemkaputt.de/gbatek-gba-system-control.htm).
|
- [GBATEK GBA system control and WAITCNT](https://problemkaputt.de/gbatek-gba-system-control.htm).
|
||||||
|
- [GBATEK BIOS reset functions](https://problemkaputt.de/gbatek-bios-reset-functions.htm), for SoftReset RAM bounds, CPU banks, stack pointers, and restart selection.
|
||||||
- [GBATEK BIOS halt functions](https://problemkaputt.de/gbatek-bios-halt-functions.htm), for wait contracts and STOP differences.
|
- [GBATEK BIOS halt functions](https://problemkaputt.de/gbatek-bios-halt-functions.htm), for wait contracts and STOP differences.
|
||||||
- [GBATEK BIOS function calling conventions](https://problemkaputt.de/gbatek-bios-functions.htm).
|
- [GBATEK BIOS function calling conventions](https://problemkaputt.de/gbatek-bios-functions.htm).
|
||||||
- [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 rotation/scaling services](https://problemkaputt.de/gbatek-bios-rotation-scaling-functions.htm), for record layouts, angle units, and output strides.
|
- [GBATEK BIOS rotation/scaling services](https://problemkaputt.de/gbatek-bios-rotation-scaling-functions.htm), for record layouts, angle units, and output strides.
|
||||||
- [VisualBoyAdvance-M BIOS services](https://github.com/visualboyadvance-m/visualboyadvance-m/blob/master/src/core/gba/internal/gbaBios.cpp), reviewed for integer affine rounding and signed matrix intermediates. Our sine table is generated mathematically.
|
- [VisualBoyAdvance-M BIOS services](https://github.com/visualboyadvance-m/visualboyadvance-m/blob/master/src/core/gba/internal/gbaBios.cpp), reviewed for integer affine rounding, signed matrix intermediates, and SoftReset status handling. Our sine table is generated mathematically.
|
||||||
- [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 angle-polynomial coefficients and quadrant conventions, BitUnPack ordering/offsets, and Huffman tree layout and 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.
|
||||||
|
|
|
||||||
11
src/bios.rs
11
src/bios.rs
|
|
@ -1,5 +1,5 @@
|
||||||
//! Original, optional ARM BIOS replacement. No Nintendo firmware is included.
|
//! Original, optional ARM BIOS replacement. No Nintendo firmware is included.
|
||||||
//! Supports interrupt waits, integer/fixed-point arithmetic, affine matrices,
|
//! Supports SoftReset, interrupt waits, integer/fixed-point arithmetic, affine matrices,
|
||||||
//! memory copy/fill, bit unpacking, LZ77/run-length/Huffman decoding, and differential filters.
|
//! memory copy/fill, bit unpacking, 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.
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@ mod decompression;
|
||||||
mod differential;
|
mod differential;
|
||||||
mod huffman;
|
mod huffman;
|
||||||
mod lz77;
|
mod lz77;
|
||||||
|
mod reset;
|
||||||
mod run_length;
|
mod run_length;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -46,8 +47,10 @@ pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
||||||
/// Build a deterministic 16 KiB image from original ARM instructions and a
|
/// Build a deterministic 16 KiB image from original ARM instructions and a
|
||||||
/// mathematically generated sine table. No Nintendo firmware bytes are included.
|
/// mathematically generated sine table. No Nintendo firmware bytes are included.
|
||||||
/// SWIs use bits 16–23 of the ARM immediate, or the Thumb immediate byte.
|
/// SWIs use bits 16–23 of the ARM immediate, or the Thumb immediate byte.
|
||||||
/// Services support User/System callers and preserve caller status. Registers
|
/// Services support User/System callers. Returning services preserve caller status
|
||||||
/// other than each arithmetic service's documented outputs are preserved.
|
/// and registers other than each arithmetic service's documented outputs.
|
||||||
|
/// SoftReset instead clears BIOS work RAM, resets specified CPU banks, and enters
|
||||||
|
/// ROM or RAM in ARM System mode with IRQ masked. It does not reset devices.
|
||||||
/// IRQ callbacks must preserve r4–r11, acknowledge IF, update IRQ_FLAGS for wait
|
/// IRQ callbacks must preserve r4–r11, acknowledge IF, update IRQ_FLAGS for wait
|
||||||
/// services, and return with BX lr. Nested IRQs and SWIs from callbacks are unsupported.
|
/// services, and return with BX lr. Nested IRQs and SWIs from callbacks are unsupported.
|
||||||
/// Service routines use the Supervisor stack; exact firmware stack layout and
|
/// Service routines use the Supervisor stack; exact firmware stack layout and
|
||||||
|
|
@ -118,6 +121,7 @@ pub fn image() -> Vec<u8> {
|
||||||
a.emit(0x01a0_c82c); // MOVEQ r12,r12,LSR #16
|
a.emit(0x01a0_c82c); // MOVEQ r12,r12,LSR #16
|
||||||
a.emit(0xe20c_c0ff); // AND r12,r12,#0xff
|
a.emit(0xe20c_c0ff); // AND r12,r12,#0xff
|
||||||
for (number, target) in [
|
for (number, target) in [
|
||||||
|
(0, "soft_reset"),
|
||||||
(2, "halt"),
|
(2, "halt"),
|
||||||
(4, "intr_wait"),
|
(4, "intr_wait"),
|
||||||
(5, "vblank_wait"),
|
(5, "vblank_wait"),
|
||||||
|
|
@ -233,6 +237,7 @@ pub fn image() -> Vec<u8> {
|
||||||
// The preceding routine ends with an unconditional branch. Place a literal
|
// The preceding routine ends with an unconditional branch. Place a literal
|
||||||
// pool here so reset/dispatcher loads stay within ARM's 4 KiB reach.
|
// pool here so reset/dispatcher loads stay within ARM's 4 KiB reach.
|
||||||
a.flush_literals();
|
a.flush_literals();
|
||||||
|
reset::emit(&mut a);
|
||||||
arithmetic::emit(&mut a);
|
arithmetic::emit(&mut a);
|
||||||
affine::emit(&mut a);
|
affine::emit(&mut a);
|
||||||
angles::emit(&mut a);
|
angles::emit(&mut a);
|
||||||
|
|
|
||||||
38
src/bios/reset.rs
Normal file
38
src/bios/reset.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
//! Original ARM SoftReset. This is a restart service, not a hardware reset.
|
||||||
|
|
||||||
|
use super::{ArmImage, IRQ_STACK, ROM_START, SVC_STACK, SYSTEM_STACK};
|
||||||
|
|
||||||
|
pub(super) fn emit(a: &mut ArmImage) {
|
||||||
|
a.label("soft_reset");
|
||||||
|
// Read the byte before clearing its containing RAM and the common SWI frame.
|
||||||
|
// No stack accesses follow: this service never returns to its caller.
|
||||||
|
a.literal(0, 0x0300_7ffa);
|
||||||
|
a.emit(0xe5d0_2000); // LDRB r2,[r0] (zero: ROM, nonzero: EWRAM)
|
||||||
|
a.literal(0, 0x0300_7e00);
|
||||||
|
a.emit(0xe3a0_1080); // MOV r1,#128 (512 bytes)
|
||||||
|
a.emit(0xe3a0_3000); // MOV r3,#0
|
||||||
|
a.label("soft_reset_clear");
|
||||||
|
a.emit(0xe480_3004); // STR r3,[r0],#4
|
||||||
|
a.emit(0xe251_1001); // SUBS r1,r1,#1
|
||||||
|
a.branch(1, "soft_reset_clear");
|
||||||
|
|
||||||
|
a.emit(0xe169_f003); // MSR SPSR_fc,r3 (SVC saved status = 0)
|
||||||
|
a.emit(0xe1a0_e003); // MOV lr,r3 (SVC link = 0)
|
||||||
|
a.literal(13, SVC_STACK);
|
||||||
|
a.emit(0xe321_f0d2); // MSR CPSR_c,#IRQ|I|F
|
||||||
|
a.emit(0xe169_f003); // MSR SPSR_fc,r3 (IRQ saved status = 0)
|
||||||
|
a.emit(0xe1a0_e003); // MOV lr,r3 (IRQ link = 0)
|
||||||
|
a.literal(13, IRQ_STACK);
|
||||||
|
a.emit(0xe321_f0df); // MSR CPSR_c,#System|I|F
|
||||||
|
a.literal(13, SYSTEM_STACK);
|
||||||
|
a.emit(0xe352_0000); // CMP r2,#0
|
||||||
|
a.literal(14, ROM_START);
|
||||||
|
a.emit(0x13a0_e402); // MOVNE lr,#0x02000000
|
||||||
|
for register in 0..=12 {
|
||||||
|
a.emit(0xe3a0_0000 | register << 12); // MOV rN,#0
|
||||||
|
}
|
||||||
|
// Deterministic subset policy: clear NZCV and F, keep I set. Do not change
|
||||||
|
// IME, IE, IF, devices, or unrelated CPU banks. IRQ stays masked throughout.
|
||||||
|
a.emit(0xe329_f09f); // MSR CPSR_fc,#System|I
|
||||||
|
a.emit(0xe12f_ff1e); // BX lr (ARM target, no return through erased SWI frame)
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ mod differential;
|
||||||
mod huffman;
|
mod huffman;
|
||||||
#[path = "bios/lz77.rs"]
|
#[path = "bios/lz77.rs"]
|
||||||
mod lz77;
|
mod lz77;
|
||||||
|
#[path = "bios/reset.rs"]
|
||||||
|
mod reset;
|
||||||
#[path = "bios/run_length.rs"]
|
#[path = "bios/run_length.rs"]
|
||||||
mod run_length;
|
mod run_length;
|
||||||
|
|
||||||
|
|
@ -227,7 +229,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, 0x0d, 0x19, 0xff] {
|
for service in [1, 3, 0x0d, 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())
|
||||||
|
|
|
||||||
363
tests/bios/reset.rs
Normal file
363
tests/bios/reset.rs
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
use super::*;
|
||||||
|
use gba_rust::{
|
||||||
|
dma::DMA_BASE,
|
||||||
|
io::{BG0CNT, DISPCNT, WAITCNT},
|
||||||
|
memory::{OAM_START, PALETTE_START},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FLAG: u32 = 0x0300_7ffa;
|
||||||
|
const CLEAR_START: u32 = 0x0300_7e00;
|
||||||
|
const CLEAR_END: u32 = 0x0300_8000;
|
||||||
|
|
||||||
|
fn restart(m: &mut Machine, flag: u8) {
|
||||||
|
m.memory_mut().write8(FLAG, flag).unwrap();
|
||||||
|
m.step().unwrap(); // Enter the real SWI vector.
|
||||||
|
reach(m, if flag == 0 { ROM_START } else { SOURCE }, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_restart_state(m: &Machine, target: u32) {
|
||||||
|
assert_eq!(m.cpu().pc(), target);
|
||||||
|
assert_eq!(m.cpu().mode(), Mode::System);
|
||||||
|
assert_eq!(m.cpu().instruction_set(), InstructionSet::Arm);
|
||||||
|
assert_eq!(m.cpu().cpsr(), 0x9f); // Deterministic policy: IRQ masked, FIQ clear, NZCV clear.
|
||||||
|
assert_eq!(&m.cpu().registers()[..13], &[0; 13]);
|
||||||
|
assert_eq!(m.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
assert_eq!(m.cpu().registers()[14], target);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_cleared(m: &Machine) {
|
||||||
|
for address in CLEAR_START..CLEAR_END {
|
||||||
|
assert_eq!(m.memory().read8(address).unwrap(), 0, "{address:#x}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline literal with a branch over its data. No host register mutation.
|
||||||
|
fn literal(code: &mut Vec<u32>, register: u32, value: u32) {
|
||||||
|
code.extend([0xe59f_0000 | register << 12, 0xea00_0000, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dirty_sp(mode: u32) -> u32 {
|
||||||
|
0x0300_7000 + mode * 32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dirty_lr(mode: u32) -> u32 {
|
||||||
|
0xa100_0000 | mode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seeded_call(thumb: bool, status: u32, location: Option<u32>) -> Machine {
|
||||||
|
let mut code = Vec::new();
|
||||||
|
for mode in [0x11, 0x12, 0x13, 0x17, 0x1b] {
|
||||||
|
code.push(0xe321_f0c0 | mode); // Switch bank with IRQ/FIQ masked.
|
||||||
|
literal(&mut code, 13, dirty_sp(mode));
|
||||||
|
literal(&mut code, 14, dirty_lr(mode));
|
||||||
|
literal(&mut code, 0, 0xb000_00df);
|
||||||
|
code.push(0xe169_f000); // MSR SPSR_fc,r0
|
||||||
|
if mode == 0x11 {
|
||||||
|
for r in 8..=12 {
|
||||||
|
code.push(0xe3a0_00e0 | r << 12 | r); // Distinct FIQ high registers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.push(0xe321_f0df);
|
||||||
|
literal(&mut code, 13, 0x0300_7dc0);
|
||||||
|
for r in 0..=12 {
|
||||||
|
code.push(0xe3a0_0080 | r << 12 | r);
|
||||||
|
}
|
||||||
|
code.extend([0xe328_f20f, 0xe321_f000 | status]); // NZCV set; caller mode/masks.
|
||||||
|
let swi_pc = if let Some(address) = location {
|
||||||
|
literal(&mut code, 14, address | u32::from(thumb));
|
||||||
|
code.push(0xe12f_ff1e);
|
||||||
|
address
|
||||||
|
} else {
|
||||||
|
code.extend([
|
||||||
|
if thumb { 0xe28f_e001 } else { 0xe1a0_e00f },
|
||||||
|
if thumb { 0xe12f_ff1e } else { 0xe1a0_0000 },
|
||||||
|
]);
|
||||||
|
let address = ROM_START + code.len() as u32 * 4;
|
||||||
|
code.extend([if thumb { 0xe7fe_df00 } else { 0xef00_0000 }, 0xeaff_fffe]);
|
||||||
|
address
|
||||||
|
};
|
||||||
|
let mut m = bios::boot(words(&code)).unwrap();
|
||||||
|
if let Some(address) = location {
|
||||||
|
m.memory_mut()
|
||||||
|
.write32(address, if thumb { 0xe7fe_df00 } else { 0xef00_0000 })
|
||||||
|
.unwrap();
|
||||||
|
m.memory_mut().write32(address + 4, 0xeaff_fffe).unwrap();
|
||||||
|
}
|
||||||
|
reach(&mut m, swi_pc, 500);
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_flag_byte_selects_rom_or_ram_from_arm_and_thumb() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for flag in 0..=255 {
|
||||||
|
let (mut m, _) = call(0, thumb, [0x123, 0x456, 0x789]);
|
||||||
|
// Only the byte at 7ffa selects the target, not surrounding bytes.
|
||||||
|
m.memory_mut().write32(IRQ_FLAGS, 0xffff_ffff).unwrap();
|
||||||
|
restart(&mut m, flag);
|
||||||
|
assert_restart_state(&m, if flag == 0 { ROM_START } else { SOURCE });
|
||||||
|
assert_cleared(&m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clears_exactly_top_512_bytes_and_keeps_other_memory() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let (mut m, _) = call(0, thumb, [0, 0, 0]);
|
||||||
|
for address in (0x0300_0000..CLEAR_END).step_by(4) {
|
||||||
|
m.memory_mut()
|
||||||
|
.write32(address, address ^ 0xa55a_c33c)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let sentinels = [
|
||||||
|
SOURCE,
|
||||||
|
SOURCE + 0x3fffc,
|
||||||
|
PALETTE_START,
|
||||||
|
PALETTE_START + 0x3fc,
|
||||||
|
VRAM_START,
|
||||||
|
VRAM_START + 0x17ffc,
|
||||||
|
OAM_START,
|
||||||
|
OAM_START + 0x3fc,
|
||||||
|
];
|
||||||
|
for address in sentinels {
|
||||||
|
m.memory_mut().write32(address, 0x1357_2468).unwrap();
|
||||||
|
}
|
||||||
|
let rom_first = m.memory().read32(ROM_START).unwrap();
|
||||||
|
restart(&mut m, 0);
|
||||||
|
assert_cleared(&m);
|
||||||
|
for address in (0x0300_0000..CLEAR_START).step_by(4) {
|
||||||
|
assert_eq!(m.memory().read32(address).unwrap(), address ^ 0xa55a_c33c);
|
||||||
|
}
|
||||||
|
for address in sentinels {
|
||||||
|
assert_eq!(m.memory().read32(address).unwrap(), 0x1357_2468);
|
||||||
|
}
|
||||||
|
assert_eq!(m.memory().read32(ROM_START).unwrap(), rom_first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resets_nonzero_registers_and_status_for_user_system_callers() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for status in [0x10, 0x50, 0x90, 0xd0, 0x1f, 0x5f, 0x9f, 0xdf] {
|
||||||
|
for flag in [0, 1] {
|
||||||
|
let mut m = seeded_call(thumb, status, None);
|
||||||
|
assert!(m.cpu().registers()[..13].iter().all(|r| *r != 0));
|
||||||
|
assert_eq!(m.cpu().cpsr() & 0xf000_0000, 0xf000_0000);
|
||||||
|
restart(&mut m, flag);
|
||||||
|
assert_restart_state(&m, if flag == 0 { ROM_START } else { SOURCE });
|
||||||
|
assert_cleared(&m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reinitializes_supervisor_irq_stacks_links_and_saved_status() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let mut m = seeded_call(thumb, 0x1f, None);
|
||||||
|
for (i, mode) in [0xd3, 0xd2, 0xdf].into_iter().enumerate() {
|
||||||
|
m.memory_mut()
|
||||||
|
.write32(SOURCE + i as u32 * 4, 0xe321_f000 | mode)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
restart(&mut m, 1);
|
||||||
|
for (mode, stack) in [
|
||||||
|
(Mode::Supervisor, bios::SVC_STACK),
|
||||||
|
(Mode::Irq, bios::IRQ_STACK),
|
||||||
|
] {
|
||||||
|
m.step().unwrap();
|
||||||
|
assert_eq!(m.cpu().mode(), mode);
|
||||||
|
assert_eq!(m.cpu().registers()[13], stack);
|
||||||
|
assert_eq!(m.cpu().registers()[14], 0);
|
||||||
|
assert_eq!(m.cpu().spsr(), Some(0));
|
||||||
|
}
|
||||||
|
m.step().unwrap();
|
||||||
|
assert_eq!(m.cpu().mode(), Mode::System);
|
||||||
|
assert_eq!(m.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
assert_eq!(m.cpu().registers()[14], SOURCE);
|
||||||
|
assert_cleared(&m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unrelated_banked_registers_and_saved_status_are_preserved() {
|
||||||
|
let mut m = seeded_call(false, 0x1f, None);
|
||||||
|
for (i, mode) in [0x11, 0x17, 0x1b].into_iter().enumerate() {
|
||||||
|
m.memory_mut()
|
||||||
|
.write32(SOURCE + i as u32 * 4, 0xe321_f0c0 | mode)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
restart(&mut m, 1);
|
||||||
|
for mode in [Mode::Fiq, Mode::Abort, Mode::Undefined] {
|
||||||
|
m.step().unwrap();
|
||||||
|
assert_eq!(m.cpu().mode(), mode);
|
||||||
|
assert_eq!(m.cpu().registers()[13], dirty_sp(mode as u32));
|
||||||
|
assert_eq!(m.cpu().registers()[14], dirty_lr(mode as u32));
|
||||||
|
assert_eq!(m.cpu().spsr(), Some(0xb000_00df));
|
||||||
|
if mode == Mode::Fiq {
|
||||||
|
assert_eq!(&m.cpu().registers()[8..13], &[0xe8, 0xe9, 0xea, 0xeb, 0xec]);
|
||||||
|
} else {
|
||||||
|
assert_eq!(&m.cpu().registers()[8..13], &[0; 5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_restart_from_work_ram_including_code_in_erased_area() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for location in [SOURCE + 0x100, 0x0300_1000, CLEAR_START, 0x03ff_fe00] {
|
||||||
|
let mut m = seeded_call(thumb, 0x1f, Some(location));
|
||||||
|
restart(&mut m, 1);
|
||||||
|
assert_restart_state(&m, SOURCE);
|
||||||
|
assert_cleared(&m);
|
||||||
|
if location < CLEAR_START {
|
||||||
|
assert_eq!(
|
||||||
|
m.memory().read32(location).unwrap(),
|
||||||
|
if thumb { 0xe7fe_df00 } else { 0xef00_0000 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ram_restart_executes_arm_code_and_can_call_bios_again() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let mut m = seeded_call(thumb, 0x10, None);
|
||||||
|
// This program is only valid as ARM: Sqrt(81), then a self-branch.
|
||||||
|
for (i, instruction) in [0xe3a0_0051, 0xef08_0000, 0xeaff_fffe]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
m.memory_mut()
|
||||||
|
.write32(SOURCE + i as u32 * 4, instruction)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
restart(&mut m, 1);
|
||||||
|
reach(&mut m, SOURCE + 8, 1000);
|
||||||
|
assert_eq!(m.cpu().registers()[0], 9);
|
||||||
|
assert_eq!(m.cpu().registers()[13], SYSTEM_STACK);
|
||||||
|
assert_eq!(m.cpu().registers()[14], SOURCE);
|
||||||
|
assert_eq!(m.cpu().cpsr(), 0x9f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_reset_io_or_device_clocks() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
let (mut m, _) = call(0, thumb, [0, 0, 0]);
|
||||||
|
for (address, value) in [
|
||||||
|
(DISPCNT, 0x0403),
|
||||||
|
(BG0CNT, 0x0141),
|
||||||
|
(WAITCNT, 0x4317),
|
||||||
|
(DISPSTAT, 0x7b38),
|
||||||
|
(IE, 0x1234),
|
||||||
|
(IME, 1),
|
||||||
|
] {
|
||||||
|
m.memory_mut().write16(address, value).unwrap();
|
||||||
|
}
|
||||||
|
m.memory_mut().write32(TIMER_BASE, 0x0080_0000).unwrap();
|
||||||
|
let timer_before = m.memory().read16(TIMER_BASE).unwrap();
|
||||||
|
let cycles = m.cycles();
|
||||||
|
restart(&mut m, 0);
|
||||||
|
for (address, value) in [
|
||||||
|
(DISPCNT, 0x0403),
|
||||||
|
(BG0CNT, 0x0141),
|
||||||
|
(WAITCNT, 0x4317),
|
||||||
|
(IE, 0x1234),
|
||||||
|
(IME, 1),
|
||||||
|
(POSTFLG, 1),
|
||||||
|
(TIMER_BASE + 2, 0x80),
|
||||||
|
] {
|
||||||
|
assert_eq!(m.memory().read16(address).unwrap(), value, "{address:#x}");
|
||||||
|
}
|
||||||
|
assert_eq!(m.memory().read16(DISPSTAT).unwrap() & 0xff38, 0x7b38);
|
||||||
|
let elapsed = m.cycles() - cycles;
|
||||||
|
assert!(elapsed > 512 && elapsed < 65536);
|
||||||
|
assert_eq!(
|
||||||
|
m.memory().read16(TIMER_BASE).unwrap(),
|
||||||
|
timer_before.wrapping_add(elapsed as u16)
|
||||||
|
);
|
||||||
|
assert!(!m.halted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dma_timers_and_pending_irq_continue_but_irq_stays_masked_after_restart() {
|
||||||
|
for thumb in [false, true] {
|
||||||
|
for ime in [0, 1] {
|
||||||
|
let (mut m, _) = call(0, thumb, [0, 0, 0]);
|
||||||
|
m.memory_mut().write8(FLAG, 1).unwrap();
|
||||||
|
m.memory_mut().write32(SOURCE, 0xeaff_fffe).unwrap();
|
||||||
|
m.step().unwrap();
|
||||||
|
// Queue IRQ and DMA only after SWI entry; otherwise IRQ can win first.
|
||||||
|
m.memory_mut().write16(IE, 8).unwrap();
|
||||||
|
m.memory_mut().write16(IME, ime).unwrap();
|
||||||
|
m.memory_mut().write32(TIMER_BASE, 0x00c0_fff0).unwrap();
|
||||||
|
m.memory_mut().write32(DEST, 0x8765_4321).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE, DEST).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE + 4, DEST + 4).unwrap();
|
||||||
|
m.memory_mut().write32(DMA_BASE + 8, 0x8400_0001).unwrap();
|
||||||
|
let mut dma = 0;
|
||||||
|
for _ in 0..2000 {
|
||||||
|
if m.cpu().pc() == SOURCE {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let step = m.step().unwrap();
|
||||||
|
assert_ne!(step, StepKind::IrqEntry);
|
||||||
|
if matches!(step, StepKind::Dma { channel: 0 }) {
|
||||||
|
dma += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_restart_state(&m, SOURCE);
|
||||||
|
assert_eq!(dma, 1);
|
||||||
|
assert_eq!(m.memory().read32(DEST + 4).unwrap(), 0x8765_4321);
|
||||||
|
assert_eq!(m.memory().read16(IF).unwrap() & 8, 8);
|
||||||
|
assert_eq!(m.memory().read16(IME).unwrap(), ime);
|
||||||
|
assert_cleared(&m);
|
||||||
|
assert_eq!(m.step().unwrap(), StepKind::Instruction);
|
||||||
|
assert_eq!(m.cpu().pc(), SOURCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_is_not_an_atomic_host_memory_clear() {
|
||||||
|
let (mut m, _) = call(0, false, [0, 0, 0]);
|
||||||
|
for address in (CLEAR_START..CLEAR_END).step_by(4) {
|
||||||
|
m.memory_mut().write32(address, 0xa55a_c33c).unwrap();
|
||||||
|
}
|
||||||
|
m.memory_mut().write8(FLAG, 0).unwrap();
|
||||||
|
m.step().unwrap();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
if m.memory().read32(CLEAR_START).unwrap() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m.step().unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(m.memory().read32(CLEAR_START).unwrap(), 0);
|
||||||
|
assert_eq!(m.memory().read32(CLEAR_START + 4).unwrap(), 0xa55a_c33c);
|
||||||
|
assert_eq!(m.memory().read32(IRQ_HANDLER).unwrap(), 0xa55a_c33c);
|
||||||
|
assert!(m.cpu().pc() < BIOS_SIZE as u32);
|
||||||
|
reach(&mut m, ROM_START, 2000);
|
||||||
|
assert_cleared(&m);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exception_mode_callers_still_receive_a_diagnostic() {
|
||||||
|
for status in [0x12, 0x13] {
|
||||||
|
let (mut m, _) = call_status(0, false, [0, 0, 0], status);
|
||||||
|
m.memory_mut().write32(CLEAR_START, 0x1357_2468).unwrap();
|
||||||
|
let error = (0..100).find_map(|_| m.step().err()).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
error,
|
||||||
|
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
||||||
|
instruction: UNSUPPORTED_TRAP,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
));
|
||||||
|
assert_eq!(m.memory().read32(CLEAR_START).unwrap(), 0x1357_2468);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue