fix(bios): initialize affine scales before cartridge boot

This commit is contained in:
0xrsydn 2026-10-06 22:35:26 +07:00
commit 1b39a7f33f
8 changed files with 157 additions and 3 deletions

View file

@ -18,7 +18,7 @@ mod run_length;
use crate::{
cpu::Cpu,
io::{HALTCNT, IME, POSTFLG},
io::{BG2PA, HALTCNT, IME, POSTFLG},
machine::Machine,
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;
/// Start at reset with our optional firmware. Call Machine::step to execute boot.
/// Boot initializes three stacks, POSTFLG, and the IRQ communication words, then
/// enters ROM_START in ARM System mode. It does not validate a cartridge header,
/// Boot initializes three stacks, POSTFLG, the IRQ communication words, and
/// 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.
pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
Ok(Machine::new(
@ -86,6 +86,14 @@ pub fn image() -> Vec<u8> {
a.emit(0xe581_0004); // STR r0,[r1,#4] (callback pointer)
a.literal(1, IME);
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.emit(0xe3a0_0001); // MOV r0,#1
a.emit(0xe5c1_0000); // STRB r0,[r1]

View file

@ -20,6 +20,8 @@ mod angles;
mod arithmetic;
#[path = "bios/bit_unpack.rs"]
mod bit_unpack;
#[path = "bios/boot_video.rs"]
mod boot_video;
#[path = "bios/differential.rs"]
mod differential;
#[path = "bios/huffman.rs"]

View 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));
}