feat(serial): retain disconnected external-clock requests
This commit is contained in:
parent
a0516de748
commit
d329c76dc3
9 changed files with 299 additions and 22 deletions
|
|
@ -1,5 +1,5 @@
|
|||
//! GBA I/O subset: DMA, timers, interrupts, WAITCNT, display, keypad input,
|
||||
//! Direct Sound, and disconnected serial initialization.
|
||||
//! Direct Sound, and disconnected serial state with external-clock waiting.
|
||||
//! Timers share a free-running prescaler phase. Hardware startup/write delays
|
||||
//! and interrupt delivery delays are not modeled.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Disconnected serial initialization state. Active transfers remain diagnostic.
|
||||
//! Disconnected serial state, including external-clock requests that cannot progress.
|
||||
|
||||
use super::replace_byte;
|
||||
|
||||
|
|
@ -32,7 +32,9 @@ impl Inactive {
|
|||
/// supplies the complete selection, independent of the previous low byte.
|
||||
pub(super) fn unsupported(address: u32, value: u8) -> Option<&'static str> {
|
||||
match address {
|
||||
SIOCNT if value & 0x80 != 0 => Some("serial transfer start"),
|
||||
// Clock source and start share the low byte. With no external
|
||||
// clock edges, an external-clock request only latches start/busy.
|
||||
SIOCNT if value & 0x81 == 0x81 => Some("internally clocked serial transfer"),
|
||||
0x0400_0129 if value & 0x20 != 0 => Some("multiplayer/UART serial mode"),
|
||||
0x0400_0135 if value & 0xc0 == 0xc0 => Some("Joybus serial mode"),
|
||||
0x0400_0135 if value & 0x81 == 0x81 => Some("GPIO serial interrupt enable"),
|
||||
|
|
@ -82,7 +84,7 @@ impl Inactive {
|
|||
}
|
||||
match address & !1 {
|
||||
SIOCNT => {
|
||||
self.serial_control = replace_byte(self.serial_control, address, value) & 0x500b
|
||||
self.serial_control = replace_byte(self.serial_control, address, value) & 0x508b
|
||||
}
|
||||
SIODATA8 if address & 1 == 0 => self.serial_send = value,
|
||||
RCNT => self.rcnt = replace_byte(self.rcnt, address, value) & 0xc1ff,
|
||||
|
|
|
|||
|
|
@ -288,6 +288,28 @@ fn serial_sound_reset_flags_are_selective_and_all_devices_return() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serial_reset_cancels_an_external_clock_request_only_when_selected() {
|
||||
use gba_core::io::{RCNT, SIOCNT};
|
||||
for thumb in [false, true] {
|
||||
for flags in [0, 0x20, 0x40, 0x80, 0xff] {
|
||||
let (mut m, pc) = prepare(flags, thumb, 0x1f);
|
||||
m.memory_mut().write16(RCNT, 0).unwrap();
|
||||
m.memory_mut().write16(SIOCNT, 0x5080).unwrap();
|
||||
complete(&mut m, pc);
|
||||
assert_eq!(
|
||||
m.memory().read16(SIOCNT).unwrap(),
|
||||
if flags & 0x20 != 0 { 4 } else { 0x5084 }
|
||||
);
|
||||
assert_eq!(
|
||||
m.memory().read8(RCNT + 1).unwrap(),
|
||||
if flags & 0x20 != 0 { 0x80 } else { 0 }
|
||||
);
|
||||
assert_eq!(m.memory().read16(IF).unwrap() & 0x80, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_registers_flags_modes_and_interrupt_masks_are_preserved() {
|
||||
for thumb in [false, true] {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ fn idle_normal_serial_data_and_control_have_distinct_widths() {
|
|||
fn unsupported_operations_have_specific_retryable_diagnostics() {
|
||||
let mut bus = memory();
|
||||
for (address, value, description) in [
|
||||
(SIOCNT, 0x80, "serial transfer start"),
|
||||
(SIOCNT, 0x81, "internally clocked serial transfer"),
|
||||
(SIOCNT + 1, 0x20, "multiplayer/UART"),
|
||||
(RCNT + 1, 0xc0, "Joybus serial mode"),
|
||||
(RCNT + 1, 0x81, "GPIO serial interrupt"),
|
||||
|
|
|
|||
225
crates/gba-core/tests/serial_external.rs
Normal file
225
crates/gba-core/tests/serial_external.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
//! Original disconnected external-clock probes. No partner, clock edges, or completion is invented.
|
||||
use gba_core::{
|
||||
cpu::Cpu,
|
||||
dma::DMA_BASE,
|
||||
io::{HALTCNT, IE, IF, IME, RCNT, SIOCNT, SIODATA32, 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 every_low_control_byte_retains_external_busy_but_rejects_internal_start_atomically() {
|
||||
let mut bus = memory();
|
||||
for high in [0, 0x10, 0x40, 0x50] {
|
||||
for previous in [0x0003, 0x1000, 0x4080, 0x5082] {
|
||||
for low in 0..=u8::MAX {
|
||||
bus.write16(SIOCNT, previous).unwrap();
|
||||
let before = bus.read16(SIOCNT).unwrap();
|
||||
let result = bus.write8(SIOCNT, low);
|
||||
if low & 0x80 != 0 && low & 1 != 0 {
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(MemoryError::UnsupportedIo {
|
||||
address: SIOCNT,
|
||||
value: low,
|
||||
operation: "internally clocked serial transfer",
|
||||
})
|
||||
);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), before);
|
||||
} else {
|
||||
result.unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(previous & 0x5000) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
bus.write8(SIOCNT + 1, high).unwrap();
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
(u16::from(high) << 8) | u16::from(low & 0x8b) | 4
|
||||
);
|
||||
}
|
||||
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnected_external_requests_wait_at_both_widths_and_rates_until_software_cancels() {
|
||||
let mut bus = memory();
|
||||
bus.write16(IE, 0x80).unwrap();
|
||||
bus.write16(IME, 1).unwrap();
|
||||
for control in [0x4080, 0x4082, 0x5080, 0x5082] {
|
||||
bus.write32(SIODATA32, 0x12345678).unwrap();
|
||||
bus.write16(SIODATA8, 0xab).unwrap();
|
||||
bus.write16(SIOCNT, control).unwrap();
|
||||
for cycles in [1, 63, 64, 512, 2048, 1000000, u32::MAX] {
|
||||
bus.advance_cycles(cycles);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), control | 4);
|
||||
assert_eq!(bus.read32(SIODATA32).unwrap(), 0x12345678);
|
||||
assert_eq!(bus.read16(SIODATA8).unwrap(), 0xab);
|
||||
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
|
||||
assert!(!bus.irq_pending());
|
||||
}
|
||||
// Rewriting start does not manufacture a clock edge or transfer completion.
|
||||
bus.write16(SIOCNT, control).unwrap();
|
||||
bus.write8(SIOCNT, 8).unwrap(); // Explicit software cancellation; SO high.
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), (control & 0x5000) | 12);
|
||||
assert_eq!(bus.read32(SIODATA32).unwrap(), 0x12345678);
|
||||
assert_eq!(bus.read16(SIODATA8).unwrap(), 0xab);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gpio_selection_does_not_turn_a_retained_start_bit_into_a_transfer_or_irq() {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0x8040).unwrap(); // SI output low, GPIO IRQ disabled.
|
||||
bus.write16(SIOCNT, 0x5080).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5080);
|
||||
bus.write16(RCNT, 0).unwrap(); // Disconnected normal SI is pulled high.
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(bus.read16(IF).unwrap() & 0x80, 0);
|
||||
// Clock-source changes with start still set remain explicit diagnostics.
|
||||
assert!(bus.write8(SIOCNT, 0x81).is_err());
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5084);
|
||||
bus.write16(SIOCNT, 0x5001).unwrap(); // Cancel first, then select internal clock while idle.
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5005);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_request_does_not_wake_halt_or_stop() {
|
||||
for stop in [false, true] {
|
||||
let mut bus = memory();
|
||||
bus.write16(IE, 0x80).unwrap();
|
||||
bus.write16(IME, 1).unwrap();
|
||||
bus.write16(SIOCNT, 0x5080).unwrap();
|
||||
bus.write8(HALTCNT, if stop { 0x80 } else { 0 }).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
||||
for _ in 0..100 {
|
||||
assert_eq!(
|
||||
machine.step().unwrap(),
|
||||
if stop {
|
||||
StepKind::StopIdle
|
||||
} else {
|
||||
StepKind::HaltIdle
|
||||
}
|
||||
);
|
||||
}
|
||||
assert_eq!(machine.stopped(), stop);
|
||||
assert_eq!(machine.halted(), !stop);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap() & 0x80, 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(machine.cycles() == 0, stop);
|
||||
}
|
||||
}
|
||||
|
||||
fn prepared(thumb: bool, instruction: u32, value: u32) -> Machine {
|
||||
let code = if thumb {
|
||||
vec![
|
||||
0xe59f0010, 0xe59f1010, 0xe28f2001, 0xe12fff12, 0xe7fe8001, 0xeafffffe, SIOCNT, value,
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
0xe59f0008,
|
||||
0xe59f1008,
|
||||
instruction,
|
||||
0xeafffffe,
|
||||
SIOCNT,
|
||||
value,
|
||||
]
|
||||
};
|
||||
let mut bus = Memory::new(code.into_iter().flat_map(u32::to_le_bytes).collect()).unwrap();
|
||||
let mut cpu = Cpu::new(ROM_START);
|
||||
for _ in 0..if thumb { 4 } else { 2 } {
|
||||
cpu.step(&mut bus).unwrap();
|
||||
}
|
||||
bus.write16(SIOCNT, 0x1003).unwrap();
|
||||
Machine::new(cpu, bus)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arm_and_thumb_external_starts_complete_as_instructions_not_as_serial_transfers() {
|
||||
for thumb in [false, true] {
|
||||
for value in [0x5084, 0x5081] {
|
||||
let mut machine = prepared(thumb, 0xe1c010b0, value); // STRH r1,[r0]
|
||||
let cpu = machine.cpu().clone();
|
||||
if value == 0x5084 {
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Instruction);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert!(machine.cycles() > 0);
|
||||
for _ in 0..100 {
|
||||
machine.step().unwrap();
|
||||
}
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap() & 0x80, 0);
|
||||
} else {
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("internally clocked serial transfer"));
|
||||
assert_eq!(machine.cpu(), &cpu);
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x1007);
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_word_and_block_writes_do_not_leave_a_partial_external_start() {
|
||||
let mut bus = memory();
|
||||
bus.write16(SIOCNT, 0x5000).unwrap();
|
||||
bus.write16(SIODATA8, 0x12).unwrap();
|
||||
assert!(bus.write32(SIOCNT, 0x00ab2080).is_err());
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5004);
|
||||
assert_eq!(bus.read16(SIODATA8).unwrap(), 0x12);
|
||||
bus.write32(SIOCNT, 0x00ab5080).unwrap();
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(bus.read16(SIODATA8).unwrap(), 0xab);
|
||||
|
||||
let mut machine = prepared(false, 0xe8a00006, 0x00ab5080); // STMIA r0!,{r1,r2}; second word unmapped.
|
||||
let before = machine.cpu().clone();
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error.to_string().contains("unmapped memory at 0x0400012c"));
|
||||
assert_eq!(machine.cpu(), &before);
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x1007);
|
||||
assert_eq!(machine.memory().read16(SIODATA8).unwrap(), 0);
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dma_start_preserves_serial_wait_and_reports_only_dma_completion() {
|
||||
let mut bus = memory();
|
||||
bus.write32(0x02000000, 0x00ab5081).unwrap();
|
||||
bus.write32(DMA_BASE, 0x02000000).unwrap();
|
||||
bus.write32(DMA_BASE + 4, SIOCNT).unwrap();
|
||||
bus.write32(DMA_BASE + 8, 0xc4000001).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
||||
let error = machine.step().unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("internally clocked serial transfer"));
|
||||
assert_eq!(machine.step(), Err(error));
|
||||
assert_eq!(machine.cycles(), 0);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 4);
|
||||
assert_eq!(machine.memory().read16(SIODATA8).unwrap(), 0);
|
||||
machine
|
||||
.memory_mut()
|
||||
.write32(0x02000000, 0x00ab5080)
|
||||
.unwrap();
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 0 });
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(machine.memory().read16(SIODATA8).unwrap(), 0xab);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100); // DMA IRQ, not serial IRQ.
|
||||
machine.memory_mut().advance_cycles(1000000);
|
||||
assert_eq!(machine.memory().read16(SIOCNT).unwrap(), 0x5084);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100);
|
||||
}
|
||||
|
|
@ -65,9 +65,10 @@ 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/inactive.rs` | Disconnected serial initialization with explicit unsupported-activity validation |
|
||||
| `crates/gba-core/src/io/inactive.rs` | Disconnected serial registers, external-clock waiting, and unsupported-activity 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/src/io/timer_step.rs` | Staged timer/audio state, divider phase, timer IF bits, and sound refill requests at ordered CPU/DMA bus phases |
|
||||
| `crates/gba-core/src/memory/timer_step_tests.rs` | Timer access phases, coherent lanes, cascades, IF ordering, rollback, DMA, idle, and clock ownership |
|
||||
| `crates/gba-core/src/display.rs` | Display clock, scanline status, comparison edges, and display IRQ events |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Disconnected serial initialization subset
|
||||
# Disconnected serial subset
|
||||
|
||||
The core supports idle normal-mode data registers and disconnected general-purpose pins.
|
||||
It does not support link transfers, multiplayer, UART, Joybus communication, or external serial interrupts.
|
||||
The core supports normal-mode data registers, disconnected external-clock waiting, and general-purpose pins.
|
||||
It does not support internally clocked transfers, external clock edges, multiplayer, UART, Joybus communication, or external serial interrupts.
|
||||
Unsupported activity remains diagnostic rather than reporting a fabricated completion.
|
||||
|
||||
## Registers
|
||||
|
|
@ -9,8 +9,9 @@ Unsupported activity remains diagnostic rather than reporting a fabricated compl
|
|||
- `SIODATA32` at `0x04000120` is a four-byte latch in the supported normal-mode subset.
|
||||
- `SIODATA8` at `0x0400012a` retains its low byte; its upper byte reads zero.
|
||||
- `SIOCNT` at `0x04000128` retains normal 8/32-bit mode, IRQ configuration, clock selection, and the output-data bit.
|
||||
The retained mask is `0x500b`. The disconnected serial input is high, except when a general-purpose output drives it low.
|
||||
Transfer-start bit 7 and multiplayer/UART mode bit 13 return explicit errors.
|
||||
The retained mask is `0x508b`. The disconnected serial input is high, except when a general-purpose output drives it low.
|
||||
Start bit 7 can latch when clock-source bit 0 is clear. Software clears bit 7 to cancel the request.
|
||||
Setting both bits 7 and 0 returns an internally-clocked-transfer diagnostic. Multiplayer/UART mode bit 13 remains unsupported.
|
||||
- `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.
|
||||
|
|
@ -25,12 +26,29 @@ GPIO means general-purpose input/output. UART means universal asynchronous recei
|
|||
No external serial device is connected. Elapsed cycles alone do not change serial data or generate an IRQ.
|
||||
Configuration that hardware accepts in other modes can still be rejected by this intentionally limited subset.
|
||||
|
||||
## External-clock waiting
|
||||
|
||||
In normal 8-bit or 32-bit mode, setting start with clock-source bit 0 clear requests externally clocked communication.
|
||||
No device is connected, so no external clock edges arrive. Start remains set; data does not shift and no completion IRQ occurs.
|
||||
The internal-rate selector, bit 1, does not supply clocks when bit 0 is clear.
|
||||
CPU cycles, timer overflows, display events, and DMA activity cannot manufacture external serial clocks.
|
||||
Repeated start writes do not complete the request. Software can cancel it by clearing start, including through BIOS serial reset.
|
||||
|
||||
General-purpose mode does not shift serial data. A retained external-clock start bit has no link-port effect there.
|
||||
Returning to normal mode still cannot complete a transfer without external clock edges.
|
||||
Selecting internal clock while start is set remains diagnostic, including after an external request.
|
||||
This is a disconnected waiting model, not an implementation of a connected link or an external clock-input API.
|
||||
|
||||
Original tests cover both widths, rate settings, control lanes, cancellation, GPIO selection, HALT/STOP, and ARM/Thumb stores.
|
||||
DMA tests distinguish its own completion IRQ from a serial IRQ. Invalid word/block stores cannot leave a partial start request.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
Original tests cover independent byte lanes, input pull-ups, output directions, reset flag selection, and explicit unsupported operations.
|
||||
Whole-access and block-store validation prevent a rejected control value from committing preceding bytes or registers.
|
||||
|
|
@ -41,7 +59,8 @@ Inactive configuration bits do not wake HALT. RCNT word accesses still reject th
|
|||
## References
|
||||
|
||||
- [GBATEK reset functions](https://problemkaputt.de/gbatek-bios-reset-functions.htm): reset flags, general-purpose selection, and the unconditional SIODATA32 side effect.
|
||||
- [GBATEK normal serial mode](https://problemkaputt.de/gbatek-sio-normal-mode.htm): data widths, input status, control bits, and writable unused RCNT bits 8/14.
|
||||
- [GBATEK normal serial mode](https://problemkaputt.de/gbatek-sio-normal-mode.htm): data widths, input status, writable unused RCNT bits 8/14, and external-clock slave start/wait/timeout behavior.
|
||||
- [mGBA serial implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/sio.c): compared normal-mode control and completion paths. Its no-driver scheduled completion does not establish disconnected external-clock hardware behavior; this core does not copy that fallback.
|
||||
- [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.
|
||||
- [mGBA BIOS implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/bios.c): functional serial reset defaults, not copied source.
|
||||
|
||||
|
|
|
|||
|
|
@ -48,13 +48,18 @@ The original copy service still executes all its reads and writes. No ROM patch
|
|||
Host writes remain strict. Reference-emulator comparisons and original tests support this bounded bus behavior.
|
||||
The trace stayed local; no game code or assets were added to the repository.
|
||||
|
||||
Both runs pass the copy and now stop at an unsupported serial-transfer start:
|
||||
The copy correction reached a serial-transfer start at PC `0x082e6e5c`.
|
||||
A local register trace found RCNT zero and a SIOCNT write of `0x5084`: 32-bit mode, IRQ enabled, external clock selected.
|
||||
With no connected clock source, this request must wait rather than complete on elapsed CPU cycles.
|
||||
The [serial subset](../hardware/serial.md#external-clock-waiting) now retains the request without shifting data or raising a serial IRQ.
|
||||
|
||||
Both runs pass that request and next stop when software selects internal clock while start remains set:
|
||||
|
||||
```text
|
||||
Steps: 426406; instructions: 424636; IRQ entries: 2; DMA units: 1768; HALT idle: 0
|
||||
Nominal cycles: 1416670
|
||||
PC=0x082e6e5c
|
||||
unsupported serial transfer start: write 0x84 at 0x04000128
|
||||
Steps: 426523; instructions: 424753; IRQ entries: 2; DMA units: 1768; HALT idle: 0
|
||||
Nominal cycles: 1416980
|
||||
PC=0x082e6eb8
|
||||
unsupported internally clocked serial transfer: write 0x85 at 0x04000128
|
||||
```
|
||||
|
||||
The native run captures five startup frames. These do not establish a working title screen or gameplay.
|
||||
|
|
@ -69,10 +74,11 @@ 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 investigation is the serial-transfer start at SIOCNT (`0x04000128`).
|
||||
Check RCNT mode, clock selection, disconnected-input behavior, and completion/IRQ timing before implementing a transfer model.
|
||||
The next requirement is internally clocked normal serial communication, including switching from a pending external-clock request.
|
||||
Implement nominal shift periods, disconnected input sampling, completion, and IRQ behavior with deterministic tests.
|
||||
Keep completion consistent with HALT, STOP, instruction timing, and diagnostic rollback.
|
||||
Do not invent a connected partner or immediate completion to suppress the diagnostic.
|
||||
The corrected inactive RCNT write does not establish a need for GPIO interrupts.
|
||||
The external-clock waiting result does not establish linked gameplay or GPIO interrupt support.
|
||||
|
||||
Noise now has deterministic counter clocks and shares tested length/envelope logic with the independent pulse channels.
|
||||
Sweep applies only to channel 1. Direct Sound has FIFO clocks and nominal DMA requests.
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ There is no host audio output or cartridge save support.
|
|||
The [audio subset](hardware/audio.md) supports Direct Sound, both PSG pulse channels, noise channel 4, idle wave channel 3 state, and instantaneous digital mixing.
|
||||
It does not yet provide a continuous sample stream. Wave channel 3 playback and unverified sound-DMA pairings remain diagnostic.
|
||||
PSG timing and signed mixing are nominal; hardware startup edges and analog behavior remain unverified.
|
||||
The [serial initialization subset](hardware/serial.md) supports idle normal data and disconnected general-purpose pins, not active transfers.
|
||||
The [serial subset](hardware/serial.md) supports normal data, disconnected external-clock waiting, and general-purpose pins.
|
||||
External requests retain start/busy without completion or IRQ. Internally clocked transfers and connected links remain unsupported.
|
||||
RCNT retains inactive bits 8/14 in normal mode. Effective GPIO interrupt enable and Joybus selection remain 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.
|
||||
|
|
@ -155,7 +156,8 @@ Audio, saves, and external BIOS loading remain unavailable. ROM loading does not
|
|||
A local Emerald run now completes its all-device reset and master sound enable.
|
||||
It also passes the pulse/noise triggers and normal-mode RCNT configuration.
|
||||
It also completes a CpuSet call to address zero through the corrected ignored BIOS-write behavior.
|
||||
It next stops at an unsupported serial-transfer start (`0x04000128`, PC `0x082e6e5c`).
|
||||
It also accepts an external-clock serial request without inventing clock edges or completion.
|
||||
It next stops when software selects internal serial clock (`0x04000128`, PC `0x082e6eb8`).
|
||||
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.
|
||||
See [cartridge loading](hardware/cartridge.md) for file limits, step budgets, and exit status.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue