feat(serial): support disconnected Joybus configuration and local registers
This commit is contained in:
parent
8b3b7ae62d
commit
7546d17630
15 changed files with 462 additions and 62 deletions
|
|
@ -77,6 +77,41 @@ fn raw_original_program_runs_with_a_bounded_report_without_changing_the_file() {
|
|||
assert_eq!(fs::read(path).unwrap(), original);
|
||||
}
|
||||
|
||||
fn joybus_setup() -> Vec<u32> {
|
||||
// Original ARM: select Joybus, enable IRQ, supply a reply, inspect local status/data.
|
||||
vec![
|
||||
0xe59f1024, // LDR r1, RCNT
|
||||
0xe3a00903, // MOV r0, #0xc000
|
||||
0xe1c100b0, // STRH r0, [r1]
|
||||
0xe3a00040, // MOV r0, #0x40
|
||||
0xe1c100bc, // STRH r0, [r1, #12] (JOYCNT)
|
||||
0xe3a000a5, // MOV r0, #0xa5
|
||||
0xe5810020, // STR r0, [r1, #32] (JOY_TRANS)
|
||||
0xe1d122b4, // LDRH r2, [r1, #36] (JOYSTAT)
|
||||
0xe1d130bc, // LDRH r3, [r1, #12]
|
||||
0xe5914020, // LDR r4, [r1, #32]
|
||||
0xeafffffe, 0x04000134,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn original_joybus_program_reports_pending_data_without_a_remote_completion() {
|
||||
let fixture = Fixture::new();
|
||||
let path = fixture.rom(&joybus_setup());
|
||||
let original = fs::read(&path).unwrap();
|
||||
let output = run(&path, "1000");
|
||||
assert!(output.status.success(), "{}", stderr(&output));
|
||||
for expected in [
|
||||
"r2=0x00000002",
|
||||
"r3=0x00000040",
|
||||
"r4=0x000000a5",
|
||||
"IRQ entries: 0",
|
||||
] {
|
||||
assert!(stdout(&output).contains(expected), "{}", stdout(&output));
|
||||
}
|
||||
assert_eq!(fs::read(path).unwrap(), original);
|
||||
}
|
||||
|
||||
fn rtc_setup() -> Vec<u32> {
|
||||
// Original LDR/MOV/STRH/LDRH/loop: enable GPIO reads, then inspect control.
|
||||
vec![
|
||||
|
|
@ -423,6 +458,22 @@ fn native_rom_window_presents_file_bytes_and_reports_stop_cpu_and_video_errors()
|
|||
);
|
||||
assert!(stdout(&output).contains("Captured ROM frames: 3"));
|
||||
assert!(stdout(&output).contains("Result: window frame limit reached"));
|
||||
let joybus_path = fixture.rom(&joybus_setup());
|
||||
let joybus_output = window(&joybus_path, false, None);
|
||||
assert!(joybus_output.status.success(), "{}", stderr(&joybus_output));
|
||||
for expected in [
|
||||
"Captured ROM frames: 3",
|
||||
"r2=0x00000002",
|
||||
"r3=0x00000040",
|
||||
"r4=0x000000a5",
|
||||
"IRQ entries: 0",
|
||||
] {
|
||||
assert!(
|
||||
stdout(&joybus_output).contains(expected),
|
||||
"{}",
|
||||
stdout(&joybus_output)
|
||||
);
|
||||
}
|
||||
let rtc_path = fixture.rom(&rtc_setup());
|
||||
let rtc_output = window(&rtc_path, true, None);
|
||||
assert!(rtc_output.status.success(), "{}", stderr(&rtc_output));
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use crate::audio::Audio;
|
|||
pub use crate::audio::{FIFO_A, FIFO_B, SOUNDBIAS, SOUNDCNT_H, SOUNDCNT_X, SOUND_START, WAVE_RAM};
|
||||
pub(crate) use serial::Serial;
|
||||
pub use serial::{
|
||||
JOYCNT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT, SIODATA32, SIODATA8, SIOMLT_SEND, SIOMULTI0,
|
||||
SIOMULTI1, SIOMULTI2, SIOMULTI3,
|
||||
JOYCNT, JOYSTAT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT, SIODATA32, SIODATA8, SIOMLT_SEND,
|
||||
SIOMULTI0, SIOMULTI1, SIOMULTI2, SIOMULTI3,
|
||||
};
|
||||
mod timer_step;
|
||||
pub(crate) use timer_step::TimerStep;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Disconnected serial: normal shifting and idle multiplayer child configuration.
|
||||
//! Disconnected serial: normal shifting, idle multiplayer, and local Joybus registers.
|
||||
use super::replace_byte;
|
||||
|
||||
pub const SIODATA32: u32 = 0x0400_0120;
|
||||
|
|
@ -13,6 +13,7 @@ pub const RCNT: u32 = 0x0400_0134;
|
|||
pub const JOYCNT: u32 = 0x0400_0140;
|
||||
pub const JOY_RECV: u32 = 0x0400_0150;
|
||||
pub const JOY_TRANS: u32 = 0x0400_0154;
|
||||
pub const JOYSTAT: u32 = 0x0400_0158;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct Serial {
|
||||
|
|
@ -21,6 +22,10 @@ pub(crate) struct Serial {
|
|||
send: u16, // Shared low byte; high byte is accessible only in multiplayer format.
|
||||
control: u16,
|
||||
rcnt: u16,
|
||||
joy_control: u8, // IRQ enable only; no external master can set event flags.
|
||||
joy_receive: u32,
|
||||
joy_transmit: u32,
|
||||
joy_status: u8, // Software flags and transmit pending; no remote reception.
|
||||
bits_left: u8,
|
||||
until_bit: u32,
|
||||
}
|
||||
|
|
@ -30,28 +35,28 @@ impl Serial {
|
|||
matches!(address,
|
||||
SIODATA32..=0x0400_012b |
|
||||
RCNT..=0x0400_0135 | JOYCNT..=0x0400_0141 |
|
||||
JOY_RECV..=0x0400_0157)
|
||||
JOY_RECV..=0x0400_0159)
|
||||
}
|
||||
|
||||
pub(crate) fn unsupported(address: u32, value: u8) -> Option<&'static str> {
|
||||
match address {
|
||||
0x0400_0129 if value & 0x30 == 0x30 => Some("UART serial mode"),
|
||||
0x0400_0135 if value & 0xc0 == 0xc0 => Some("Joybus serial mode"),
|
||||
0x0400_0135 if value & 0x81 == 0x81 => Some("GPIO serial interrupt enable"),
|
||||
JOYCNT if value & 0x40 != 0 => Some("Joybus interrupt enable"),
|
||||
JOY_RECV..=0x0400_0157 if value != 0 => Some("Joybus data access beyond reset"),
|
||||
0x0400_0135 if value & 0xc1 == 0x81 => Some("GPIO serial interrupt enable"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn gpio(&self) -> bool {
|
||||
self.rcnt & 0x8000 != 0
|
||||
self.rcnt & 0xc000 == 0x8000
|
||||
}
|
||||
fn joybus(&self) -> bool {
|
||||
self.rcnt & 0xc000 == 0xc000
|
||||
}
|
||||
fn multiplayer_format(&self) -> bool {
|
||||
self.control & 0x3000 == 0x2000
|
||||
}
|
||||
fn normal(&self) -> bool {
|
||||
!self.gpio() && !self.multiplayer_format()
|
||||
self.rcnt & 0x8000 == 0 && !self.multiplayer_format()
|
||||
}
|
||||
fn busy(&self) -> bool {
|
||||
self.control & 0x80 != 0
|
||||
|
|
@ -137,7 +142,14 @@ impl Serial {
|
|||
.multiplayer_format()
|
||||
.then(|| self.multi_extra.to_le_bytes()[(address - SIOMULTI2) as usize]);
|
||||
}
|
||||
for (base, data) in [(JOY_RECV, self.joy_receive), (JOY_TRANS, self.joy_transmit)] {
|
||||
if (base..base + 4).contains(&address) {
|
||||
// No master can set receive status, so its read-clear bit is already zero.
|
||||
return Some(data.to_le_bytes()[(address - base) as usize]);
|
||||
}
|
||||
}
|
||||
let value = match address & !1 {
|
||||
SIOCNT if self.joybus() => self.control, // Unused; do not invent pin samples.
|
||||
SIOCNT => {
|
||||
let pins = if self.gpio() { self.gpio_pins() } else { 15 };
|
||||
// SI is pulled high without a cable. Multiplayer drives SD high while idle.
|
||||
|
|
@ -163,7 +175,8 @@ impl Serial {
|
|||
}
|
||||
(self.rcnt & !15) | self.gpio_pins()
|
||||
}
|
||||
JOYCNT | JOY_RECV..=0x0400_0156 => 0,
|
||||
JOYCNT => u16::from(self.joy_control),
|
||||
JOYSTAT => u16::from(self.joy_status),
|
||||
_ => return None,
|
||||
};
|
||||
Some(value.to_le_bytes()[(address & 1) as usize])
|
||||
|
|
@ -198,8 +211,22 @@ impl Serial {
|
|||
let shift = (at - SIOMULTI2) * 8;
|
||||
self.multi_extra =
|
||||
(self.multi_extra & !(255 << shift)) | (u32::from(value) << shift);
|
||||
} else if (JOY_RECV..JOY_TRANS).contains(&at) {
|
||||
// GBATEK describes R/W data latches. CPU writes are not remote events.
|
||||
let shift = (at - JOY_RECV) * 8;
|
||||
self.joy_receive =
|
||||
(self.joy_receive & !(255 << shift)) | (u32::from(value) << shift);
|
||||
} else if (JOY_TRANS..JOYSTAT).contains(&at) {
|
||||
let shift = (at - JOY_TRANS) * 8;
|
||||
self.joy_transmit =
|
||||
(self.joy_transmit & !(255 << shift)) | (u32::from(value) << shift);
|
||||
self.joy_status |= 2; // Data available, not transmission complete.
|
||||
} else {
|
||||
match at & !1 {
|
||||
JOYCNT if at & 1 == 0 => self.joy_control = value & 0x40,
|
||||
JOYSTAT if at & 1 == 0 => {
|
||||
self.joy_status = (self.joy_status & 2) | (value & 0x30);
|
||||
}
|
||||
SIODATA8 if self.multiplayer_format() || at & 1 == 0 => {
|
||||
self.send = replace_byte(self.send, at, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ mod iwram_bus;
|
|||
#[cfg(test)]
|
||||
mod iwram_history_tests;
|
||||
#[cfg(test)]
|
||||
mod joybus_step_tests;
|
||||
#[cfg(test)]
|
||||
mod multiplayer_step_tests;
|
||||
#[cfg(test)]
|
||||
mod prefetch_tests;
|
||||
|
|
|
|||
91
crates/gba-core/src/memory/joybus_step_tests.rs
Normal file
91
crates/gba-core/src/memory/joybus_step_tests.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
//! Original Joybus transaction, CPU access, and preflight regressions.
|
||||
use super::{timing_event_tests::prepared, *};
|
||||
use crate::{
|
||||
io::{IF, JOYCNT, JOYSTAT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT},
|
||||
machine::Machine,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn arm_and_thumb_transmit_stores_commit_pending_status_once() {
|
||||
for thumb in [false, true] {
|
||||
let (cpu, mut memory) = prepared(
|
||||
thumb,
|
||||
&[if thumb { 0x6008 } else { 0xe5810000 }], // STR r0,[r1]
|
||||
&[(0, 0x12345678), (1, JOY_TRANS)],
|
||||
);
|
||||
memory.write16(RCNT, 0xc000).unwrap();
|
||||
memory.write16(JOYCNT, 0x40).unwrap();
|
||||
let mut machine = Machine::new(cpu, memory);
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.memory().read32(JOY_TRANS).unwrap(), 0x12345678);
|
||||
assert_eq!(machine.memory().read16(JOYSTAT).unwrap(), 2);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
||||
assert_eq!(machine.memory().io.serial.next_event_cycles(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joybus_mode_selection_observes_the_normal_completion_bus_phase() {
|
||||
for elapsed in [56, 57] {
|
||||
let (cpu, mut memory) = prepared(false, &[0xe5c10000], &[(0, 0xc1), (1, RCNT + 1)]);
|
||||
memory.write16(SIOCNT, 0x4083).unwrap();
|
||||
memory.advance_cycles(elapsed);
|
||||
let serial = memory.io.serial;
|
||||
let mut machine = Machine::new(cpu, memory);
|
||||
let cpu = machine.cpu().clone();
|
||||
if elapsed == 57 {
|
||||
machine.step().unwrap();
|
||||
assert_eq!(machine.cycles(), 64);
|
||||
assert_eq!(machine.memory().read8(RCNT + 1).unwrap(), 0xc1);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x80);
|
||||
assert_eq!(machine.memory().io.serial.next_event_cycles(), None);
|
||||
} else {
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error.to_string().contains("serial reconfiguration"));
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
assert_eq!(machine.cpu(), &cpu);
|
||||
assert_eq!(machine.cycles(), 56);
|
||||
assert_eq!(machine.memory().io.serial, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_block_padding_failure_discards_data_pending_status_and_writeback() {
|
||||
let (cpu, mut memory) = prepared(
|
||||
false,
|
||||
&[0xe8a0000e], // STMIA r0!,{r1-r3}; final word hits JOYSTAT padding.
|
||||
&[(0, JOY_RECV), (1, 0x12345678), (2, 0xabcdef90), (3, 0x30)],
|
||||
);
|
||||
memory.write16(RCNT, 0xc000).unwrap();
|
||||
let serial = memory.io.serial;
|
||||
let mut machine = Machine::new(cpu, memory);
|
||||
let cpu = machine.cpu().clone();
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error.to_string().contains("0x0400015a"));
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
assert_eq!(machine.cpu(), &cpu);
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().io.serial, serial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_reads_see_transmit_pending_before_commit_and_discard_restores_every_lane() {
|
||||
let (_, mut memory) = prepared(false, &[0xe1a00000], &[]);
|
||||
let serial = memory.io.serial;
|
||||
memory.begin_timer_step();
|
||||
memory.write16(RCNT, 0xc100).unwrap();
|
||||
memory.write16(JOYCNT, 0x40).unwrap();
|
||||
memory.write32(JOY_TRANS, 0x12345678).unwrap();
|
||||
memory.write32(JOY_RECV, 0xabcdef90).unwrap();
|
||||
memory.write16(JOYSTAT, 0x30).unwrap();
|
||||
assert_eq!(memory.read32(JOY_TRANS).unwrap(), 0x12345678);
|
||||
assert_eq!(memory.read32(JOY_RECV).unwrap(), 0xabcdef90);
|
||||
assert_eq!(memory.read16(JOYSTAT).unwrap(), 0x32);
|
||||
assert_eq!(memory.read16(JOYCNT).unwrap(), 0x40);
|
||||
assert_eq!(memory.read8(RCNT), Err(MemoryError::Unmapped(RCNT)));
|
||||
assert_eq!(memory.io.serial, serial);
|
||||
memory.discard_timer_step();
|
||||
assert_eq!(memory.io.serial, serial);
|
||||
assert_eq!(memory.read16(JOYSTAT).unwrap(), 0);
|
||||
}
|
||||
|
|
@ -288,6 +288,37 @@ fn serial_sound_reset_flags_are_selective_and_all_devices_return() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serial_reset_clears_joybus_data_and_irq_enable_without_fabricating_a_master() {
|
||||
use gba_core::io::{JOYCNT, JOYSTAT, JOY_RECV, JOY_TRANS, RCNT};
|
||||
for thumb in [false, true] {
|
||||
for flags in [0, 0x20, 0xff] {
|
||||
let (mut m, pc) = prepare(flags, thumb, 0x1f);
|
||||
m.memory_mut().write16(RCNT, 0xc100).unwrap();
|
||||
m.memory_mut().write16(JOYCNT, 0x40).unwrap();
|
||||
m.memory_mut().write32(JOY_RECV, MARKER).unwrap();
|
||||
m.memory_mut().write32(JOY_TRANS, MARKER).unwrap();
|
||||
m.memory_mut().write16(JOYSTAT, 0x30).unwrap();
|
||||
complete(&mut m, pc);
|
||||
let selected = flags & 0x20 != 0;
|
||||
for address in [JOY_RECV, JOY_TRANS] {
|
||||
assert_eq!(
|
||||
m.memory().read32(address).unwrap(),
|
||||
if selected { 0 } else { MARKER }
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
m.memory().read16(JOYCNT).unwrap(),
|
||||
if selected { 0 } else { 0x40 }
|
||||
);
|
||||
// The replacement writes zero to TRANS, but does not write JOYSTAT.
|
||||
// Pending data is not a completed transfer and cannot request an IRQ.
|
||||
assert_eq!(m.memory().read16(JOYSTAT).unwrap(), 0x32);
|
||||
assert_eq!(m.memory().read16(IF).unwrap() & 0x80, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serial_reset_cancels_an_external_clock_request_only_when_selected() {
|
||||
use gba_core::io::{RCNT, SIOCNT};
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
use gba_core::{
|
||||
cpu::Cpu,
|
||||
dma::DMA_BASE,
|
||||
io::{
|
||||
JOYCNT, JOY_RECV, RCNT, SIOCNT, SIODATA32, SIODATA8, SOUNDBIAS, SOUNDCNT_H, SOUNDCNT_X,
|
||||
WAVE_RAM,
|
||||
},
|
||||
io::{JOYCNT, RCNT, SIOCNT, SIODATA32, SIODATA8, SOUNDBIAS, SOUNDCNT_H, SOUNDCNT_X, WAVE_RAM},
|
||||
machine::Machine,
|
||||
memory::{Memory, MemoryError, ROM_START},
|
||||
};
|
||||
|
|
@ -81,10 +78,7 @@ fn unsupported_operations_have_specific_retryable_diagnostics() {
|
|||
let mut bus = memory();
|
||||
for (address, value, description) in [
|
||||
(SIOCNT + 1, 0x30, "UART serial mode"),
|
||||
(RCNT + 1, 0xc0, "Joybus serial mode"),
|
||||
(RCNT + 1, 0x81, "GPIO serial interrupt"),
|
||||
(JOYCNT, 0x40, "Joybus interrupt"),
|
||||
(JOY_RECV, 1, "Joybus data access"),
|
||||
] {
|
||||
let error = bus.write8(address, value).unwrap_err();
|
||||
assert!(matches!(error, MemoryError::UnsupportedIo { .. }));
|
||||
|
|
@ -92,7 +86,7 @@ fn unsupported_operations_have_specific_retryable_diagnostics() {
|
|||
assert_eq!(bus.write8(address, value), Err(error));
|
||||
}
|
||||
bus.write16(RCNT, 0x8055).unwrap();
|
||||
assert!(bus.write16(RCNT, 0xc0aa).is_err());
|
||||
assert!(bus.write16(RCNT, 0x81aa).is_err());
|
||||
assert_eq!(bus.read16(RCNT).unwrap(), 0x805f); // Low byte was not committed.
|
||||
bus.write16(SIOCNT, 0x4008).unwrap();
|
||||
assert!(bus.write32(SIOCNT, 0xaabb3080).is_err());
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ fn memory() -> Memory {
|
|||
#[test]
|
||||
fn rcnt_high_byte_selection_is_independent_of_previous_mode_and_low_byte() {
|
||||
let mut bus = memory();
|
||||
for previous in [0x8000, 0x80f5, 0x0100, 0x4100] {
|
||||
for previous in [0x8000, 0x80f5, 0x0100, 0x4100, 0xc155] {
|
||||
for low in [0, 0x55, 0xff] {
|
||||
for high in 0..=u8::MAX {
|
||||
for halfword in [false, true] {
|
||||
|
|
@ -28,25 +28,21 @@ fn rcnt_high_byte_selection_is_independent_of_previous_mode_and_low_byte() {
|
|||
// Decode individual mode bits, separately from the implementation masks.
|
||||
let gpio_or_joybus = high & 0x80 != 0;
|
||||
let joybus = gpio_or_joybus && high & 0x40 != 0;
|
||||
let gpio_irq = gpio_or_joybus && high & 1 != 0;
|
||||
if joybus || gpio_irq {
|
||||
let gpio_irq = gpio_or_joybus && !joybus && high & 1 != 0;
|
||||
if gpio_irq {
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(MemoryError::UnsupportedIo {
|
||||
address: RCNT + 1,
|
||||
value: high,
|
||||
operation: if joybus {
|
||||
"Joybus serial mode"
|
||||
} else {
|
||||
"GPIO serial interrupt enable"
|
||||
},
|
||||
operation: "GPIO serial interrupt enable",
|
||||
})
|
||||
);
|
||||
assert_eq!((bus.read8(RCNT), bus.read8(RCNT + 1)), before);
|
||||
} else {
|
||||
result.unwrap();
|
||||
assert_eq!(bus.read8(RCNT + 1).unwrap(), high & 0xc1);
|
||||
if gpio_or_joybus {
|
||||
if gpio_or_joybus && !joybus {
|
||||
let latch = if halfword { low } else { previous as u8 };
|
||||
let outputs = latch >> 4;
|
||||
let pins = (latch & outputs | !outputs) & 15;
|
||||
|
|
@ -109,17 +105,13 @@ fn cpu_byte_and_halfword_writes_accept_inactive_bits_but_reject_active_modes_ato
|
|||
for high in [1, 0x41, 0x81, 0xc0, 0xc1] {
|
||||
let mut machine = prepared_store(instruction, address, high << shift);
|
||||
let cpu = machine.cpu().clone();
|
||||
if high & 0x80 == 0 {
|
||||
if high != 0x81 {
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Instruction);
|
||||
assert_eq!(machine.memory().read8(RCNT + 1).unwrap(), high as u8);
|
||||
assert!(machine.cycles() > 0);
|
||||
} else {
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error.to_string().contains(if high & 0x40 != 0 {
|
||||
"Joybus serial mode"
|
||||
} else {
|
||||
"GPIO serial interrupt enable"
|
||||
}));
|
||||
assert!(error.to_string().contains("GPIO serial interrupt enable"));
|
||||
assert_eq!(machine.cpu(), &cpu);
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(RCNT).unwrap(), 0x80f5);
|
||||
|
|
@ -132,7 +124,7 @@ fn cpu_byte_and_halfword_writes_accept_inactive_bits_but_reject_active_modes_ato
|
|||
|
||||
#[test]
|
||||
fn dma_halfword_rejection_is_retryable_then_inactive_configuration_completes() {
|
||||
for high in [0x81, 0xc0, 0xc1] {
|
||||
for high in [0x81, 0x83, 0xbf] {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0x80f5).unwrap();
|
||||
bus.write16(0x02000000, high << 8).unwrap();
|
||||
|
|
|
|||
142
crates/gba-core/tests/serial_joybus.rs
Normal file
142
crates/gba-core/tests/serial_joybus.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! Original disconnected Joybus probes. No external commands or linked transfers.
|
||||
use gba_core::{
|
||||
cpu::Cpu,
|
||||
dma::DMA_BASE,
|
||||
io::{HALTCNT, IE, IF, IME, JOYCNT, JOYSTAT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT, SIODATA8},
|
||||
machine::{Machine, StepKind},
|
||||
memory::{Memory, MemoryError, ROM_START},
|
||||
};
|
||||
|
||||
fn memory() -> Memory {
|
||||
Memory::new(0xeaff_fffe_u32.to_le_bytes().to_vec()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_register_masks_do_not_fabricate_remote_events_in_any_mode() {
|
||||
let mut bus = memory();
|
||||
for mode in [0, 0x8000, 0xc000, 0xc100, 0x4100] {
|
||||
bus.write16(RCNT, mode).unwrap();
|
||||
for value in 0..=255 {
|
||||
bus.write16(JOYCNT, 0xff00 | value).unwrap();
|
||||
bus.write16(JOYSTAT, 0xff00 | value).unwrap();
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), value & 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), value & 0x30);
|
||||
bus.write8(JOYCNT + 1, 0xff).unwrap();
|
||||
bus.write8(JOYSTAT + 1, 0xff).unwrap();
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), value & 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), value & 0x30);
|
||||
}
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_lanes_retain_words_and_transmit_writes_set_pending_not_completion() {
|
||||
for mode in [0, 0x8000, 0xc000] {
|
||||
for base in [JOY_RECV, JOY_TRANS] {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, mode).unwrap();
|
||||
for lane in 0..4 {
|
||||
bus.write8(base + lane, (0x12 + lane * 0x22) as u8).unwrap();
|
||||
}
|
||||
assert_eq!(bus.read32(base).unwrap(), 0x78563412);
|
||||
bus.write16(base + 2, 0xabcd).unwrap();
|
||||
assert_eq!(bus.read32(base).unwrap(), 0xabcd3412);
|
||||
bus.write32(base, 0x98765432).unwrap();
|
||||
assert_eq!(bus.read16(base).unwrap(), 0x5432);
|
||||
assert_eq!(bus.read16(base + 2).unwrap(), 0x9876);
|
||||
let pending = if base == JOY_TRANS { 2 } else { 0 };
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
bus.write16(JOYSTAT, 0xffff).unwrap();
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending | 0x30);
|
||||
bus.write16(JOYSTAT, 0).unwrap(); // Software cannot clear transmit pending.
|
||||
bus.write16(JOYCNT, 0x47).unwrap();
|
||||
bus.read32(JOY_RECV).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), 0x40);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
bus.write16(RCNT, 0x8000).unwrap();
|
||||
bus.write16(RCNT, 0xc100).unwrap();
|
||||
assert_eq!(bus.read32(base).unwrap(), 0x98765432);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
}
|
||||
}
|
||||
// Writing zero also makes a reply available; zero is not a reset strobe.
|
||||
let mut bus = memory();
|
||||
bus.write8(JOY_TRANS + 3, 0).unwrap();
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joybus_does_not_run_normal_clocks_or_wake_halt_or_stop() {
|
||||
for stop in [false, true] {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0xc100).unwrap(); // Bit 8 is inactive, not a GPIO IRQ enable.
|
||||
bus.write16(SIOCNT, 0x4083).unwrap(); // Unused in Joybus, even with internal start retained.
|
||||
bus.write8(SIODATA8, 0x12).unwrap();
|
||||
bus.write16(JOYCNT, 0x40).unwrap();
|
||||
bus.write32(JOY_TRANS, 0xaabbccdd).unwrap();
|
||||
bus.write16(IE, 0x80).unwrap();
|
||||
bus.write16(IME, 1).unwrap();
|
||||
bus.write8(HALTCNT, if stop { 0x80 } else { 0 }).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.stopped(), stop);
|
||||
assert_eq!(bus.halted(), !stop);
|
||||
assert_eq!(bus.read8(SIODATA8).unwrap(), 0x12);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x4083);
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), 2);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
assert_eq!(bus.read8(RCNT), Err(MemoryError::Unmapped(RCNT)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_normal_transfer_requires_completion_or_explicit_cancellation_before_joybus() {
|
||||
let mut bus = memory();
|
||||
bus.write16(SIOCNT, 0x4083).unwrap();
|
||||
for elapsed in [0, 8] {
|
||||
bus.advance_cycles(elapsed);
|
||||
let error = bus.write16(RCNT, 0xc100).unwrap_err();
|
||||
assert!(error.to_string().contains("serial reconfiguration"));
|
||||
assert_eq!(bus.write16(RCNT, 0xc100), Err(error));
|
||||
assert_eq!(bus.read8(RCNT + 1).unwrap(), 0);
|
||||
}
|
||||
bus.write16(SIOCNT, 0).unwrap();
|
||||
bus.write16(RCNT, 0xc100).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joy_control_and_status_word_padding_fail_without_partial_side_effects() {
|
||||
let mut bus = memory();
|
||||
for address in [JOYCNT, JOYSTAT] {
|
||||
assert_eq!(
|
||||
bus.write32(address, u32::MAX),
|
||||
Err(MemoryError::Unmapped(address + 2))
|
||||
);
|
||||
assert_eq!(bus.read32(address), Err(MemoryError::Unmapped(address + 2)));
|
||||
assert_eq!(bus.read16(address).unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dma_transmit_write_has_only_dma_completion_irq() {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0xc000).unwrap();
|
||||
bus.write16(JOYCNT, 0x40).unwrap();
|
||||
bus.write32(0x02000000, 0x12345678).unwrap();
|
||||
bus.write32(DMA_BASE, 0x02000000).unwrap();
|
||||
bus.write32(DMA_BASE + 4, JOY_TRANS).unwrap();
|
||||
bus.write32(DMA_BASE + 8, 0xc4000001).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 0 });
|
||||
assert_eq!(machine.memory().read32(JOY_TRANS).unwrap(), 0x12345678);
|
||||
assert_eq!(machine.memory().read16(JOYSTAT).unwrap(), 2);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100);
|
||||
machine.memory_mut().advance_cycles(1000000);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100);
|
||||
}
|
||||
|
|
@ -75,11 +75,13 @@ The repository is a Cargo workspace with three crates:
|
|||
| `crates/gba-core/tests/pulse_sound/channel2.rs` | Channel 2 mapping, register gaps, independent state, shared clocks, and sum-before-rounding mixing |
|
||||
| `crates/gba-core/tests/direct_sound.rs` | FIFO lanes/reset, signed mixing, timer selection, DMA refill, and HALT/STOP |
|
||||
| `crates/gba-core/src/memory/audio_step_tests.rs` | Audio access phases, shadow validation, and CPU/DMA failure rollback |
|
||||
| `crates/gba-core/src/io/serial.rs` | Disconnected normal serial shifter, internal clocks, external waiting, idle multiplayer registers, GPIO latches, and live-write validation |
|
||||
| `crates/gba-core/src/io/serial.rs` | Disconnected normal serial shifter, internal clocks, external waiting, idle multiplayer and local Joybus registers, GPIO latches, and live-write validation |
|
||||
| `crates/gba-core/tests/inactive_devices.rs` | Device masks, wave RAM, GPIO pull-ups, serial data, and failed CPU/DMA write isolation |
|
||||
| `crates/gba-core/tests/serial_control.rs` | RCNT mode gating, inactive bits, byte/halfword lanes, CPU/DMA diagnostics, and interrupt isolation |
|
||||
| `crates/gba-core/tests/serial_external.rs` | External-clock waiting, cancellation, HALT/STOP, ARM/Thumb/DMA starts, and atomic diagnostics |
|
||||
| `crates/gba-core/tests/serial_internal.rs` | Nominal bit edges, widths/rates, cancellation, completion IRQs, HALT/STOP, and live-write limits |
|
||||
| `crates/gba-core/tests/serial_joybus.rs` | Local Joybus masks/data, pending status, inactive IRQs, mode gating, HALT/STOP, DMA, and padding diagnostics |
|
||||
| `crates/gba-core/src/memory/joybus_step_tests.rs` | Joybus ARM/Thumb stores, staged reads, normal completion boundaries, and failed block-store isolation |
|
||||
| `crates/gba-core/tests/serial_multiplayer.rs` | Disconnected child status, receive/send lanes, mode aliases, ignored child start, HALT/STOP, and ARM/Thumb/DMA access |
|
||||
| `crates/gba-core/src/memory/multiplayer_step_tests.rs` | Multiplayer mode changes at normal completion, block-transfer preflight, staged reads, and atomic diagnostics |
|
||||
| `crates/gba-core/src/memory/serial_step_tests.rs` | Serial bus-phase observations, preflight validation, DMA, IF ordering, capture isolation, and rollback |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Disconnected serial subset
|
||||
|
||||
The core supports disconnected normal-mode transfers, external-clock waiting, idle multiplayer configuration, and general-purpose pins.
|
||||
The core supports disconnected normal-mode transfers, external-clock waiting, idle multiplayer, local Joybus registers, and general-purpose pins.
|
||||
Internal clocks shift pulled-high input at nominal GBA rates. No link partner is emulated.
|
||||
External clock edges, connected multiplayer transfers, UART, Joybus communication, and GPIO interrupts remain unsupported.
|
||||
Unsupported activity remains diagnostic rather than reporting a fabricated completion.
|
||||
|
|
@ -16,13 +16,12 @@ Unsupported activity remains diagnostic rather than reporting a fabricated compl
|
|||
Completion or software cancellation clears start. Multiplayer uses the separate format described below; UART remains diagnostic.
|
||||
- `RCNT` at `0x04000134` retains mode bits, interrupt configuration, output latches, and pin directions with mask `0xc1ff`.
|
||||
With bit 15 clear, bits 8 and 14 are writable latches without GPIO interrupt or Joybus effects.
|
||||
With bit 15 set, bit 14 selects Joybus; otherwise bit 8 enables GPIO interrupts. Both active configurations remain diagnostic.
|
||||
With bit 15 set, bit 14 selects Joybus; bit 8 is unused in that mode.
|
||||
With bit 15 set and bit 14 clear, bit 8 enables GPIO interrupts, which remain diagnostic.
|
||||
In general-purpose mode, each input reads high through its pull-up. Each output reads its output latch.
|
||||
`RCNT=0x8000` therefore reads back as `0x800f`, not `0x8000`.
|
||||
Normal and multiplayer low-byte pin readback remains unmapped rather than guessed.
|
||||
- `JOYCNT` permits clearing already-empty status flags. Joybus interrupt enable is unsupported.
|
||||
- `JOY_RECV` and `JOY_TRANS` permit only zero writes and zero readback for reset initialization.
|
||||
Nonzero writes return an explicit error. Joybus status and data-transfer side effects are not implemented.
|
||||
Normal, multiplayer, and Joybus low-byte pin readback remains unmapped rather than guessed.
|
||||
- `JOYCNT`, `JOY_RECV`, `JOY_TRANS`, and `JOYSTAT` support the disconnected configuration described below.
|
||||
|
||||
GPIO means general-purpose input/output. UART means universal asynchronous receiver/transmitter.
|
||||
No external serial device is connected. Only an enabled internal serial clock can shift data or generate a completion IRQ.
|
||||
|
|
@ -86,7 +85,7 @@ The actual transfers then execute once from the original state. Other IF sources
|
|||
### Bounded behavior
|
||||
|
||||
After any bit has shifted, changes to width, clock selection/rate, or the selected data register remain diagnostic while start stays set.
|
||||
Selecting GPIO during an internally clocked request also remains diagnostic. Clear start before these operations.
|
||||
Selecting GPIO or Joybus during an internally clocked request also remains diagnostic. Clear start before these operations.
|
||||
IRQ-enable and idle SO configuration writes remain supported without restarting progress.
|
||||
These limits also apply to BIOS services that write serial registers; cancel active transfers before requesting serial reset.
|
||||
Unverified live reconfiguration is not silently approximated.
|
||||
|
|
@ -141,11 +140,52 @@ Original tests cover control masks, all baud selections, register lanes, aliases
|
|||
Bus-phase tests cover normal completion, retryable diagnostics, block loads/stores, and mode-dependent staged reads.
|
||||
There is no parent, cable topology, external clock input, multiplayer timeout, or linked-game compatibility claim.
|
||||
|
||||
## Disconnected Joybus configuration
|
||||
|
||||
RCNT bits 14–15 both set select Joybus. The GBA is a slave; an external master must initiate communication.
|
||||
This subset has no master, cable, commands, or external clock input. Selecting the mode does not start communication.
|
||||
RCNT bit 8 remains a writable inactive latch, not a GPIO interrupt enable.
|
||||
RCNT low-byte pin samples remain unmapped: GPIO output latches must not substitute for unverified Joybus pin levels.
|
||||
SIOCNT is unused in Joybus. Its existing format latches remain readable without synthetic SI/SD status or internal shifting.
|
||||
UART-format writes still return the existing diagnostic, including while Joybus is selected.
|
||||
|
||||
- `JOYCNT` at `0x04000140` retains interrupt enable in bit 6. Unused bits read zero.
|
||||
Reset, receive-complete, and send-complete flags in bits 0–2 are write-one-to-clear.
|
||||
They remain zero because no master can produce those events. Enabling interrupts cannot itself request IF bit 7.
|
||||
- `JOY_RECV` at `0x04000150` and `JOY_TRANS` at `0x04000154` retain independent 32-bit data latches.
|
||||
Byte, halfword, and word accesses preserve other lanes. Synthetic initial data is zero, not received peer data.
|
||||
Local receive writes do not set receive status or a completion flag.
|
||||
- `JOYSTAT` at `0x04000158` retains software flags in bits 4–5.
|
||||
Every local transmit-data write sets bit 1, including a zero write or a write to an upper lane.
|
||||
This means a reply is available for a master; it does not mean transmission completed.
|
||||
Software cannot directly set or clear hardware status bits 1/3 by writing JOYSTAT.
|
||||
Receive-pending bit 3 remains zero. Receive reads therefore have no observable read-clear effect in this disconnected subset.
|
||||
|
||||
Transmit pending survives elapsed time, mode changes, status writes, and empty-event acknowledgements.
|
||||
No Joybus event wakes HALT or STOP. Joybus selection disables the normal serial clock even if SIOCNT retains start.
|
||||
The existing live normal-transfer limits still apply when entering Joybus. Clear start or wait for normal completion first.
|
||||
A normal completion at the write's bus phase remains pending in IF after the mode change.
|
||||
Registers participate in serial transactions, including failed CPU/DMA access rollback and block-store preflight.
|
||||
Word accesses to JOYCNT and JOYSTAT reject unmapped upper padding without partial writes.
|
||||
|
||||
Local Joybus registers remain accessible in other modes under this bounded latch model.
|
||||
Receive writes follow GBATEK's R/W description; the pinned mGBA implementation leaves them unhandled rather than storing them.
|
||||
mGBA corroborates JOYCNT/JOYSTAT masks in Joybus and sets transmit pending from either halfword outside mode dispatch.
|
||||
Cross-mode masks, CPU receive writes, synthetic reset values, and byte-lane behavior lack physical-hardware validation.
|
||||
These choices do not establish GameCube communication, hardware reset accuracy, or linked-game compatibility.
|
||||
|
||||
Original tests cover masks, lanes, pending status, mode gating, inactive IRQ bits, HALT/STOP, and ARM/Thumb/DMA accesses.
|
||||
Staged tests cover completion boundaries, local readback, rollback, and later block-store failures.
|
||||
Original desktop programs also check status/data through terminal and native-window execution.
|
||||
|
||||
## BIOS reset
|
||||
|
||||
RegisterRamReset always clears the low halfword of SIODATA32, even without bit 5.
|
||||
The upper halfword remains unchanged in this functional subset.
|
||||
With bit 5 selected, the firmware clears SIOCNT and SIODATA8, selects general-purpose inputs, and clears the supported Joybus reset registers.
|
||||
With bit 5 selected, the firmware clears SIOCNT and SIODATA8 and selects general-purpose inputs.
|
||||
It clears JOYCNT interrupt enable and both Joybus data latches.
|
||||
Its zero write to JOY_TRANS sets transmit pending. It does not write JOYSTAT; software flags remain unchanged.
|
||||
This is the replacement's functional sequence, not a claim about complete hardware BIOS reset side effects.
|
||||
It uses ordinary ARM bus stores, not a host-side reset bypass.
|
||||
Clearing SIOCNT cancels a pending external-clock request. Unselected serial reset leaves that request pending.
|
||||
The firmware writes SIODATA8 after selecting normal format. This does not clear the retained upper multiplayer SEND byte.
|
||||
|
|
@ -163,6 +203,8 @@ Inactive configuration bits do not wake HALT. RCNT word accesses still reject th
|
|||
- [GBATEK normal serial mode](https://problemkaputt.de/gbatek-sio-normal-mode.htm): data widths, MSB-first shifting, rates, pulled-high SI, start/completion, IRQ selection, and external-clock waiting.
|
||||
- [GBATEK multiplayer mode](https://problemkaputt.de/gbatek-sio-multi-player-mode.htm): child start restrictions, SI/SD status, undefined pre-transfer ID, register widths, and R/W receive registers.
|
||||
- [LinkRawCable at c61bf351](https://github.com/afska/gba-link-connection/blob/c61bf351f68ad2d6e1c9d72d70e21bec19adfc0b/lib/LinkRawCable.hpp): public API usage for mode selection, SEND, receive words, and separate role/readiness/ID checks. Its disconnected-cable warning is not a pin-level measurement.
|
||||
- [GBATEK Joybus mode](https://problemkaputt.de/gbatek-sio-joy-bus-mode.htm): slave-only communication, RCNT/SIOCNT roles, local register masks, R/W data, and status side effects.
|
||||
- [mGBA I/O implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/io.c): transmit-pending writes and receive-status read clearing; compare with masks and unhandled receive writes in its serial implementation below.
|
||||
- [GBATEK mode summary](https://problemkaputt.de/gbatek-sio-control-registers-summary.htm): RCNT/SIOCNT mode selection.
|
||||
- [mGBA serial implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/sio.c): cross-checked normal transfer-duration arithmetic and completion IRQ handling. Its no-driver scheduled completion and zero receive data are not copied; they do not establish disconnected hardware behavior. Multiplayer comparison covers SI/SD defaults, receive-write disagreement, and uncertain floating SC timing.
|
||||
- [GBATEK](https://mgba-emu.github.io/gbatek/): GBA general-purpose pin directions, internal pull-ups, SI falling-edge interrupts, and the SIO mode-selection table.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Use a local ROM that you may lawfully use:
|
|||
|
||||
```sh
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --steps 3000000
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --window --frames 60
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --window --frames 600
|
||||
```
|
||||
|
||||
The earlier reset-only implementation stopped at master sound enable (`0x04000084`, PC `0x082e0518`).
|
||||
|
|
@ -36,7 +36,8 @@ Both pulse channels then reached the noise trigger (`0x0400007d`, PC `0x082e03c4
|
|||
Noise channel 4 support then reached an incorrectly rejected RCNT write: `0x0100` at `0x04000134`, PC `0x082e2aac`.
|
||||
RCNT bit 15 was clear, so bit 8 did not enable general-purpose input/output (GPIO) interrupts.
|
||||
GBATEK documents bits 8 and 14 as writable but unused in normal mode.
|
||||
The serial model now retains these inactive bits. Effective GPIO interrupt enable and Joybus selection still return explicit errors.
|
||||
The serial model now retains these inactive bits. Effective GPIO interrupt enable still returns an explicit error.
|
||||
Joybus configuration was added later, as described below.
|
||||
|
||||
That correction reached `read-only memory at 0x00000000` inside the original BIOS replacement, PC `0x00000378`.
|
||||
A bounded local trace found a Thumb `CpuSet` call with source `0x02022a74`, destination zero, and count `0x1c` halfwords.
|
||||
|
|
@ -77,19 +78,34 @@ Select `--save-type flash128` for this bounded run; the device begins erased and
|
|||
A later redundant `0xf0` write occurred after returning to idle array mode.
|
||||
The subset accepts that no-op without claiming direct Flash128 ID exit or busy-command cancellation.
|
||||
|
||||
Both terminal and native runs with these options now reach Joybus mode selection:
|
||||
Flash identification then reached Joybus selection (`RCNT=0xc000`, PC `0x082deeca`) after sixteen captured frames.
|
||||
The [disconnected Joybus subset](../hardware/serial.md#disconnected-joybus-configuration) now accepts local configuration and status/data accesses.
|
||||
No master, command, remote receive word, or completion IRQ is fabricated.
|
||||
|
||||
The terminal run reaches its requested three-million-step limit with exit status zero:
|
||||
|
||||
```text
|
||||
Steps: 1389249; instructions: 1386088; IRQ entries: 89; DMA units: 3072; HALT idle: 0
|
||||
Nominal cycles: 4688039
|
||||
PC=0x082deeca
|
||||
unsupported Joybus serial mode: write 0xc0 at 0x04000135
|
||||
Result: step limit reached
|
||||
Steps: 3000000; instructions: 2985620; IRQ entries: 124; DMA units: 14256; HALT idle: 0
|
||||
Nominal cycles: 9626104
|
||||
PC=0x080008cc
|
||||
```
|
||||
|
||||
The native run captures sixteen startup frames. These do not establish a working title screen or gameplay.
|
||||
Both 60-frame and 600-frame native runs now reach their frame limits with exit status zero.
|
||||
The longer run reports:
|
||||
|
||||
```text
|
||||
Result: window frame limit reached
|
||||
Captured ROM frames: 600
|
||||
Steps: 58456876; instructions: 58087737; IRQ entries: 1251; DMA units: 367888; HALT idle: 0
|
||||
Nominal cycles: 168453824
|
||||
PC=0x080008ca
|
||||
```
|
||||
|
||||
These runs establish bounded execution without a diagnostic, not visible menu progress or gameplay.
|
||||
No scripted input or pixel assertions were used. Repeated frames can still come from a waiting loop.
|
||||
Audio-device behavior is validated by original tests, not by audible game output.
|
||||
The window run does not reach its requested frame limit and correctly exits with failure.
|
||||
Terminal execution is also diagnostic, not a compatibility pass.
|
||||
Host RTC values can change between runs; these totals are observations, not deterministic golden assertions.
|
||||
Results apply to this local file; no cartridge revision or checksum was inferred from its filename.
|
||||
|
||||
## Validation and next requirement
|
||||
|
|
@ -98,8 +114,9 @@ Workspace debug/release tests, strict Clippy, public ARM/Thumb/memory/BIOS check
|
|||
Native ROM-window tests and graphics smoke modes pass on Darwin arm64.
|
||||
The pinned Pong debug/release reports match.
|
||||
|
||||
The next startup requirement is disconnected Joybus configuration, beginning with `RCNT=0xc000`.
|
||||
Research idle registers, absent-device status, and mode transitions before implementing them. Do not fabricate a connected peer.
|
||||
Next, inspect visible startup/menu progress and exercise button presses and releases in a bounded run.
|
||||
Identify any waiting loop or new diagnostic before selecting another compatibility correction.
|
||||
Do not treat frame-limit success as a title-screen or gameplay pass.
|
||||
Flash programming, erase, busy behavior, and safe persistence remain required before claiming save compatibility.
|
||||
RTC persistence and cartridge IRQ behavior remain separate missing features.
|
||||
Ordinary ROM writes remain read-only. Without `--rtc`, the earlier GPIO diagnostic is still expected.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ Update this file when a feature lands or a limit is removed.
|
|||
- 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.
|
||||
- RegisterRamReset supports disabled sound and disconnected serial initialization, including the common `r0=0xff` request.
|
||||
- Disconnected Joybus selection, local data/status registers, and transmit-pending state without invented remote events or IRQs.
|
||||
- Direct Sound A/B support timer-driven FIFO playback, stereo digital levels, and DMA1/A or DMA2/B refill blocks.
|
||||
- PSG pulse channels 1 and 2 support independent nominal frequency/duty playback, length, envelope, status, and stereo mixing. Channel 1 also supports sweep.
|
||||
- Noise channel 4 supports nominal 7/15-bit generation, divider clocks, shared length/envelope behavior, status, and stereo mixing.
|
||||
|
|
@ -157,8 +158,9 @@ The [serial subset](hardware/serial.md) supports disconnected normal 8/32-bit tr
|
|||
Internal clocks shift high input at nominal rates, clear busy, and request completion IRQs. External requests retain busy without clock edges.
|
||||
Disconnected multiplayer exposes child/ready status and 16-bit send/receive registers without inventing transfers or completion IRQs.
|
||||
Pre-transfer ID is a zero placeholder; receive-write behavior and reset/alias details lack physical-hardware validation.
|
||||
Connected links, multiplayer transfers, UART, Joybus, GPIO interrupts, and unverified mid-transfer reconfiguration remain unsupported.
|
||||
RCNT retains inactive bits 8/14 in normal mode. Effective GPIO interrupt enable and Joybus selection remain diagnostic.
|
||||
Joybus supports local configuration, data latches, and transmit-pending status, without an external master or completion IRQs.
|
||||
Connected links, multiplayer transfers, UART, Joybus commands, GPIO interrupts, and unverified mid-transfer reconfiguration remain unsupported.
|
||||
RCNT retains inactive bits 8/14 in normal mode and bit 8 in Joybus. Effective GPIO interrupt enable remains diagnostic.
|
||||
Mapped I/O covers DMA, timers, interrupts, WAITCNT, display control/status, background control/scroll/affine registers, window/effect registers, KEYINPUT/KEYCNT, POSTFLG, and HALTCNT.
|
||||
Keypad IRQs use a documented polling model; hardware retrigger edges and asynchronous input timing remain unverified.
|
||||
STOP implements keypad wake only. Serial/Game Pak wake sources, oscillator restart delays, and exact wake timing remain unimplemented or unverified.
|
||||
|
|
@ -174,7 +176,9 @@ It also accepts an external-clock serial request without inventing clock edges o
|
|||
It now also completes internally clocked normal transfers and selects idle multiplayer mode.
|
||||
With explicit `--rtc` selection, it also passes Game Pak GPIO configuration and RTC calendar reads.
|
||||
With `--save-type flash128`, it also passes Flash identification and reaches a later startup check.
|
||||
It next stops at Joybus mode selection (`0x04000135`, PC `0x082deeca`). Flash writing and persistence remain unimplemented.
|
||||
It now passes disconnected Joybus setup and completes a bounded 600-frame native run without a diagnostic error.
|
||||
This run uses no scripted input or pixel assertions; menu progress and gameplay remain unverified.
|
||||
Flash writing and persistence remain unimplemented.
|
||||
Without `--rtc`, cartridge GPIO writes still return read-only diagnostics.
|
||||
No title-screen, gameplay, or audible-game compatibility is established.
|
||||
See [the startup result](research/emerald-reset.md) for the bounded run and next investigation.
|
||||
|
|
|
|||
|
|
@ -311,6 +311,9 @@ Bus-phase tests cover start/load timing, IF acknowledgement order, CPU/DMA rollb
|
|||
Disconnected multiplayer tests cover every control value in that format, all baud settings, send/receive lanes, and normal-mode aliases.
|
||||
They verify idle child status, ignored start, no completion IRQ or HALT wake, GPIO sampling, and ARM/Thumb/DMA stores.
|
||||
Staged tests check mode changes at normal completion, block loads/stores, rollback, and reads after leaving multiplayer format.
|
||||
Joybus tests cover mode selection, local control/data/status lanes, transmit pending, and absence of remote events or IRQs.
|
||||
They check HALT/STOP, BIOS reset stores, unmapped padding, ARM/Thumb/DMA accesses, staged reads, and failed block-store rollback.
|
||||
Original desktop programs check Joybus status and reply data in both terminal and native-window execution.
|
||||
These tests validate the documented bounded serial model, not physical pin timing, receive-write behavior, or linked-game compatibility.
|
||||
Timer/IRQ tests verify that changing WAITCNT changes when an interrupt is sampled.
|
||||
Timer tests cover all prescalers, reload changes, start/stop behavior, byte writes, large clock advances, and cascades.
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@ Frame-based scenarios and the preparation adapter now make the result repeatable
|
|||
A local Emerald run now passes its initial all-device reset, master sound enable, and pulse/noise triggers.
|
||||
It also passes normal serial transfers and idle multiplayer configuration.
|
||||
With `--rtc --save-type flash128`, it passes Game Pak GPIO setup, RTC reads, and Flash identification.
|
||||
It next stops at Joybus serial-mode selection.
|
||||
It also passes disconnected Joybus configuration and completes a bounded 600-frame native run without a diagnostic error.
|
||||
That run does not verify screen contents, button responses, or gameplay.
|
||||
The core has tested Direct Sound, both pulse channels, and noise channel 4.
|
||||
Wave channel 3, a continuous sample stream, and desktop audio output remain missing.
|
||||
See [the runtime result](research/emerald-reset.md). Disconnected Joybus configuration is the next demonstrated startup requirement.
|
||||
See [the runtime result](research/emerald-reset.md). Next, verify visible menu progress and button responses rather than treating frame counts as playability.
|
||||
The separate save requirement includes Flash programming/erase and safe host persistence. Identification/read support is not successful saving.
|
||||
SRAM is byte-addressable battery-backed save memory. Flash and EEPROM remain separate protocols.
|
||||
|
||||
|
|
@ -57,7 +58,8 @@ A reproducible source build or an accessible upstream ROM is needed before recor
|
|||
|
||||
Next implementation steps:
|
||||
|
||||
1. Research the demonstrated Joybus configuration failure without inventing a connected device.
|
||||
1. Inspect visible startup/menu progress and exercise input in the bounded local game run. Record the first demonstrated failure.
|
||||
Use original regressions for any hardware correction; do not invent connected serial devices.
|
||||
2. Extend explicit Flash selection with verified programming, erase, and busy behavior. Keep absent devices and unsupported commands diagnostic.
|
||||
Add SRAM separately when the independent candidate requires it; verify its widths, mirroring, and wait behavior first.
|
||||
3. Add bounded save loading and safe persistence in the desktop crate. Do not put file operations in the core.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue