fix(bios): initialize affine scales before cartridge boot
This commit is contained in:
parent
bf2ab54fc2
commit
1b39a7f33f
8 changed files with 157 additions and 3 deletions
|
|
@ -18,7 +18,7 @@ mod run_length;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cpu::Cpu,
|
cpu::Cpu,
|
||||||
io::{HALTCNT, IME, POSTFLG},
|
io::{BG2PA, HALTCNT, IME, POSTFLG},
|
||||||
machine::Machine,
|
machine::Machine,
|
||||||
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START},
|
memory::{Memory, MemoryError, BIOS_SIZE, ROM_START},
|
||||||
};
|
};
|
||||||
|
|
@ -34,8 +34,8 @@ pub const UNSUPPORTED_TRAP: u32 = 0xe7f0_00f0;
|
||||||
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.
|
||||||
/// Boot initializes three stacks, POSTFLG, and the IRQ communication words, then
|
/// Boot initializes three stacks, POSTFLG, the IRQ communication words, and
|
||||||
/// enters ROM_START in ARM System mode. It does not validate a cartridge header,
|
/// identity BG2/BG3 matrices, then enters ROM_START in ARM System mode. It does not validate a cartridge header,
|
||||||
/// reproduce Nintendo's boot process, or provide file loading.
|
/// reproduce Nintendo's boot process, or provide file loading.
|
||||||
pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
||||||
Ok(Machine::new(
|
Ok(Machine::new(
|
||||||
|
|
@ -86,6 +86,14 @@ pub fn image() -> Vec<u8> {
|
||||||
a.emit(0xe581_0004); // STR r0,[r1,#4] (callback pointer)
|
a.emit(0xe581_0004); // STR r0,[r1,#4] (callback pointer)
|
||||||
a.literal(1, IME);
|
a.literal(1, IME);
|
||||||
a.emit(0xe581_0000); // STR r0,[r1] (disable IRQ delivery during minimal boot)
|
a.emit(0xe581_0000); // STR r0,[r1] (disable IRQ delivery during minimal boot)
|
||||||
|
// Bitmap and affine ROMs can rely on firmware's identity transform.
|
||||||
|
// Fresh device state supplies zero PB/PC/origins; do not change raw I/O reset defaults.
|
||||||
|
a.literal(1, BG2PA);
|
||||||
|
a.emit(0xe3a0_0c01); // MOV r0,#256
|
||||||
|
a.emit(0xe1c1_00b0); // STRH r0,[r1] (BG2PA)
|
||||||
|
a.emit(0xe1c1_00b6); // STRH r0,[r1,#6] (BG2PD)
|
||||||
|
a.emit(0xe1c1_01b0); // STRH r0,[r1,#16] (BG3PA)
|
||||||
|
a.emit(0xe1c1_01b6); // STRH r0,[r1,#22] (BG3PD)
|
||||||
a.literal(1, POSTFLG);
|
a.literal(1, POSTFLG);
|
||||||
a.emit(0xe3a0_0001); // MOV r0,#1
|
a.emit(0xe3a0_0001); // MOV r0,#1
|
||||||
a.emit(0xe5c1_0000); // STRB r0,[r1]
|
a.emit(0xe5c1_0000); // STRB r0,[r1]
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ mod angles;
|
||||||
mod arithmetic;
|
mod arithmetic;
|
||||||
#[path = "bios/bit_unpack.rs"]
|
#[path = "bios/bit_unpack.rs"]
|
||||||
mod bit_unpack;
|
mod bit_unpack;
|
||||||
|
#[path = "bios/boot_video.rs"]
|
||||||
|
mod boot_video;
|
||||||
#[path = "bios/differential.rs"]
|
#[path = "bios/differential.rs"]
|
||||||
mod differential;
|
mod differential;
|
||||||
#[path = "bios/huffman.rs"]
|
#[path = "bios/huffman.rs"]
|
||||||
|
|
|
||||||
85
crates/gba-core/tests/bios/boot_video.rs
Normal file
85
crates/gba-core/tests/bios/boot_video.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
//! Original boot regressions. ROMs deliberately do not initialize affine matrices.
|
||||||
|
use super::*;
|
||||||
|
use gba_core::{
|
||||||
|
io::{BG0CNT, DISPCNT},
|
||||||
|
video::{rgb555_to_rgb888, Framebuffer, WIDTH},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn started(control: u16) -> Machine {
|
||||||
|
let mut machine = bios::boot(words(&[
|
||||||
|
0xe3a0_1301, // MOV r1,#0x04000000
|
||||||
|
0xe59f_0004, // LDR r0,[pc,#4]
|
||||||
|
0xe1c1_00b0, // STRH r0,[r1] (DISPCNT only)
|
||||||
|
0xeaff_fffe, // B .
|
||||||
|
u32::from(control),
|
||||||
|
]))
|
||||||
|
.unwrap();
|
||||||
|
reach(&mut machine, ROM_START + 12, 100);
|
||||||
|
machine
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boot_supplies_identity_for_bitmap_coordinates_without_rom_matrix_writes() {
|
||||||
|
let mut machine = started(0x0403);
|
||||||
|
for (x, y, color) in [
|
||||||
|
(0, 0, 0x001f),
|
||||||
|
(7, 0, 0x03e0),
|
||||||
|
(0, 9, 0x7c00),
|
||||||
|
(7, 9, 0x7fff),
|
||||||
|
] {
|
||||||
|
machine
|
||||||
|
.memory_mut()
|
||||||
|
.write16(VRAM_START + 2 * (y * 240 + x), color)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let mut frame = Framebuffer::default();
|
||||||
|
machine.memory().render_frame(&mut frame).unwrap();
|
||||||
|
for (x, y, color) in [
|
||||||
|
(0, 0, 0x001f),
|
||||||
|
(7, 0, 0x03e0),
|
||||||
|
(0, 9, 0x7c00),
|
||||||
|
(7, 9, 0x7fff),
|
||||||
|
] {
|
||||||
|
assert_eq!(frame.pixels()[y * WIDTH + x], rgb555_to_rgb888(color));
|
||||||
|
}
|
||||||
|
machine.memory_mut().set_scanline_rendering(true);
|
||||||
|
for _ in 0..200000 {
|
||||||
|
machine.step().unwrap();
|
||||||
|
if machine.memory().captured_vblank() == Some(2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(machine.memory().captured_vblank(), Some(2));
|
||||||
|
assert!(machine.memory().present_frame(&mut frame).unwrap());
|
||||||
|
assert_eq!(frame.pixels()[9 * WIDTH + 7], 0xffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boot_supplies_identity_for_both_affine_backgrounds() {
|
||||||
|
for bg in [2, 3] {
|
||||||
|
let mut machine = started(2 | (0x100 << bg));
|
||||||
|
let bus = machine.memory_mut();
|
||||||
|
bus.write16(BG0CNT + bg * 2, 0x1000).unwrap(); // map at VRAM+0x8000
|
||||||
|
bus.write16(0x05000002, 0x001f).unwrap();
|
||||||
|
bus.write16(0x05000004, 0x03e0).unwrap();
|
||||||
|
bus.write16(0x05000006, 0x7c00).unwrap();
|
||||||
|
bus.write16(VRAM_START, 0x0201).unwrap();
|
||||||
|
bus.write16(VRAM_START + 8, 0x0003).unwrap();
|
||||||
|
let mut frame = Framebuffer::default();
|
||||||
|
bus.render_frame(&mut frame).unwrap();
|
||||||
|
assert_eq!(frame.pixels()[0], 0xff0000, "BG{bg}");
|
||||||
|
assert_eq!(frame.pixels()[1], 0x00ff00, "BG{bg}");
|
||||||
|
assert_eq!(frame.pixels()[WIDTH], 0x0000ff, "BG{bg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn constructing_memory_does_not_apply_firmware_defaults() {
|
||||||
|
let mut bus = Memory::new(vec![]).unwrap();
|
||||||
|
bus.write16(DISPCNT, 0x0403).unwrap();
|
||||||
|
bus.write16(VRAM_START, 0x001f).unwrap();
|
||||||
|
bus.write16(VRAM_START + 2, 0x03e0).unwrap();
|
||||||
|
let mut frame = Framebuffer::default();
|
||||||
|
bus.render_frame(&mut frame).unwrap();
|
||||||
|
assert!(frame.pixels().iter().all(|pixel| *pixel == 0xff0000));
|
||||||
|
}
|
||||||
|
|
@ -76,6 +76,7 @@ The repository is a Cargo workspace with three crates:
|
||||||
| `crates/gba-core/src/lib.rs` | Core modules |
|
| `crates/gba-core/src/lib.rs` | Core modules |
|
||||||
| `crates/gba-demos/src/lib.rs` | Demo modules, original instruction/exception bytes, and raw input-test ROM |
|
| `crates/gba-demos/src/lib.rs` | Demo modules, original instruction/exception bytes, and raw input-test ROM |
|
||||||
| `crates/gba-core/src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services |
|
| `crates/gba-core/src/bios.rs` | Original ARM BIOS image builder, minimal boot, IRQ dispatch, waits, and memory services |
|
||||||
|
| `crates/gba-core/tests/bios/boot_video.rs` | Firmware identity scales, bitmap/affine startup rendering, capture, and raw-memory isolation |
|
||||||
| `crates/gba-core/src/bios/reset.rs` | Emitted ARM SoftReset and selective RegisterRamReset; CPU banks, RAM clearing, and supported device-register resets |
|
| `crates/gba-core/src/bios/reset.rs` | Emitted ARM SoftReset and selective RegisterRamReset; CPU banks, RAM clearing, and supported device-register resets |
|
||||||
| `crates/gba-core/src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines |
|
| `crates/gba-core/src/bios/arithmetic.rs` | Emitted ARM division and integer-square-root routines |
|
||||||
| `crates/gba-core/src/bios/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table |
|
| `crates/gba-core/src/bios/affine.rs` | Emitted ARM background/sprite matrix services, range checks, and generated sine table |
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ Minimal boot initializes:
|
||||||
- Supervisor stack: `0x03007fe0`.
|
- Supervisor stack: `0x03007fe0`.
|
||||||
- BIOS IRQ flags at `0x03007ff8` and the callback pointer at `0x03007ffc`: zero.
|
- BIOS IRQ flags at `0x03007ff8` and the callback pointer at `0x03007ffc`: zero.
|
||||||
- IME: zero; POSTFLG: one.
|
- IME: zero; POSTFLG: one.
|
||||||
|
- BG2/BG3 affine PA and PD: 256 (identity scale), through executed ARM halfword stores.
|
||||||
|
Fresh device state supplies zero PB, PC, and origins. Raw `Memory::new` defaults remain unchanged.
|
||||||
|
|
||||||
|
This prevents bitmap and affine ROMs from sampling one pixel across the screen when they rely on firmware initialization.
|
||||||
|
Original regressions verify bitmap coordinates, both affine backgrounds, and scanline capture without ROM-side matrix writes.
|
||||||
|
See the [boot graphics investigation](../research/boot-video.md) for the independent homebrew failure and evidence.
|
||||||
|
|
||||||
It then enters `0x08000000` in ARM System mode with IRQ/FIQ masks clear.
|
It then enters `0x08000000` in ARM System mode with IRQ/FIQ masks clear.
|
||||||
It does not reproduce Nintendo's logo, cartridge-header checks, RAM clearing, boot delays, or full hardware initialization.
|
It does not reproduce Nintendo's logo, cartridge-header checks, RAM clearing, boot delays, or full hardware initialization.
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,13 @@ Shared timer prescaler phase:
|
||||||
|
|
||||||
See [shared divider scope and tests](research/timer-prescaler.md). The correction does not implement delayed register writes or startup edges.
|
See [shared divider scope and tests](research/timer-prescaler.md). The correction does not implement delayed register writes or startup edges.
|
||||||
|
|
||||||
|
General gameplay and boot graphics:
|
||||||
|
|
||||||
|
- [ZeroDayArcade Pong at c784b603](https://github.com/ZeroDayArcade/Pong-Homebrew-GBA/tree/c784b6036a4f188c50932b411e98126bfcbd07d6), for a licensed independent homebrew, source-defined menu/input behavior, and bitmap startup requirements.
|
||||||
|
- [mGBA I/O initialization at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/io.c), cross-checked with its BIOS reset implementation for identity affine scales.
|
||||||
|
- [gbadoc register summary](https://gbadev.net/gbadoc/registers.html), for affine coefficient meanings. See [boot graphics evidence](research/boot-video.md).
|
||||||
|
- [tolik518/GBA_Pong](https://github.com/tolik518/GBA_Pong/tree/422e22a5d1f7d11710e03ffc19d5bff6ac95240f): a future homebrew candidate found during research. It has not been built or tested here.
|
||||||
|
|
||||||
Use [GBATEK](https://problemkaputt.de/gbatek.htm) and ARM7TDMI documentation for further hardware work.
|
Use [GBATEK](https://problemkaputt.de/gbatek.htm) and ARM7TDMI documentation for further hardware work.
|
||||||
Only use game ROMs that you may lawfully use. Do not commit game ROMs, BIOS files, or game assets.
|
Only use game ROMs that you may lawfully use. Do not commit game ROMs, BIOS files, or game assets.
|
||||||
The ignore file excludes `roms/`, common game ROM extensions, and save files.
|
The ignore file excludes `roms/`, common game ROM extensions, and save files.
|
||||||
|
|
|
||||||
44
docs/research/boot-video.md
Normal file
44
docs/research/boot-video.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# BIOS boot graphics initialization
|
||||||
|
|
||||||
|
## Observed failure
|
||||||
|
|
||||||
|
The MIT-licensed ZeroDayArcade Pong homebrew booted and serviced VBlank interrupts without a CPU diagnostic.
|
||||||
|
Version 2 ROM scenarios then found a black captured screen despite running menu and gameplay logic.
|
||||||
|
The same checks passed after firmware initialized the affine scales. The ROM bytes and assertions did not change.
|
||||||
|
|
||||||
|
The source selects Mode 3 and BG2 but does not write an affine matrix.
|
||||||
|
Our raw I/O state starts with zero coefficients. The original boot sequence previously left those values unchanged.
|
||||||
|
Zero PA and PD make the renderer sample the same source pixel across the screen.
|
||||||
|
This is a firmware initialization gap, not a reason to bypass affine rendering for bitmap modes.
|
||||||
|
|
||||||
|
## Correction and independent regressions
|
||||||
|
|
||||||
|
The original BIOS now executes ARM stores of 256 to BG2PA, BG2PD, BG3PA, and BG3PD before entering the cartridge.
|
||||||
|
Fresh device state already supplies zero PB, PC, and origins.
|
||||||
|
The change does not alter `Memory::new`, external firmware, or SoftReset behavior.
|
||||||
|
It does not claim complete hardware initialization or exact startup timing.
|
||||||
|
|
||||||
|
Original test programs select a video mode without writing matrices.
|
||||||
|
Separate horizontal and vertical pixels establish the expected identity transform for bitmap graphics and both affine backgrounds.
|
||||||
|
A captured frame checks the timed rendering path. A raw-memory control retains the zero-matrix behavior.
|
||||||
|
The bitmap and affine startup tests failed before the correction and pass afterward.
|
||||||
|
|
||||||
|
## Compatibility evidence
|
||||||
|
|
||||||
|
Pong revision `c784b6036a4f188c50932b411e98126bfcbd07d6` passes bounded menu, settings, return, match-start, ball-motion, and paddle press/release checks.
|
||||||
|
Debug and release scenario reports match on Darwin arm64.
|
||||||
|
A bounded native Pong window run and the original native-window smoke test complete successfully.
|
||||||
|
The automated input checks use the core input interface; they do not establish manual keyboard playthrough or full-game correctness.
|
||||||
|
|
||||||
|
The pinned public ARM, Thumb, memory, and BIOS checkpoints still pass in both profiles.
|
||||||
|
Additional boot instructions intentionally change startup step/cycle counters and internal firmware addresses.
|
||||||
|
No public ROM, game asset, or third-party implementation source is included in this repository.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Pinned Pong source and license](https://github.com/ZeroDayArcade/Pong-Homebrew-GBA/tree/c784b6036a4f188c50932b411e98126bfcbd07d6): `main.c` selects Mode 3; `graphics.h` defines rectangles and text positions.
|
||||||
|
- [Pinned mGBA I/O initialization](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/io.c): `GBAIOInit` supplies identity PA/PD values.
|
||||||
|
- [Pinned mGBA RegisterRamReset](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/bios.c): independent cross-check of affine reset values, not copied implementation.
|
||||||
|
- [gbadoc register summary](https://gbadev.net/gbadoc/registers.html) and [GBATEK](https://mgba-emu.github.io/gbatek/): affine coefficient roles and register addresses.
|
||||||
|
|
||||||
|
These sources support functional initialization. They are not a hardware trace of Nintendo firmware startup.
|
||||||
|
|
@ -76,6 +76,7 @@ Update this file when a feature lands or a limit is removed.
|
||||||
- 256 KiB external work RAM and 32 KiB internal work RAM, including mirrors.
|
- 256 KiB external work RAM and 32 KiB internal work RAM, including mirrors.
|
||||||
- Read-only cartridge bytes in the three GBA cartridge windows.
|
- Read-only cartridge bytes in the three GBA cartridge windows.
|
||||||
- Raw ROM-file loading with original BIOS boot, terminal diagnostics, and final CPU/step reports.
|
- Raw ROM-file loading with original BIOS boot, terminal diagnostics, and final CPU/step reports.
|
||||||
|
- Firmware boot initializes BG2/BG3 identity scales for bitmap and affine programs that do not program their own matrices.
|
||||||
- ROM windows with scanline capture, keyboard input, focus-loss release, bounded execution slices, and interactive keypad wake from STOP.
|
- ROM windows with scanline capture, keyboard input, focus-loss release, bounded execution slices, and interactive keypad wake from STOP.
|
||||||
- Optional ROM-window frame limits and an original input-test ROM generator.
|
- Optional ROM-window frame limits and an original input-test ROM generator.
|
||||||
- Headless JSON ROM suites with PC or VBlank completion, register/CPSR/memory/pixel assertions, scheduled buttons, bounded budgets, and structured failure reports.
|
- Headless JSON ROM suites with PC or VBlank completion, register/CPSR/memory/pixel assertions, scheduled buttons, bounded budgets, and structured failure reports.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue