feat(eeprom): Fix eeprom emulation timeout problem, passing the nintendo eeprom tests

Former-commit-id: 5eea390de806d03eee8c043203ae4c57d7355caa
This commit is contained in:
Michel Heily 2020-01-31 12:41:13 +02:00
parent 451be2036f
commit 3fb75079a2
4 changed files with 102 additions and 70 deletions

View file

@ -43,7 +43,7 @@ impl Default for SpiState {
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
struct EepromChip<M> pub struct EepromChip<M>
where where
M: BackupMemoryInterface, M: BackupMemoryInterface,
{ {
@ -57,6 +57,10 @@ where
tx_buffer: u64, tx_buffer: u64,
address: usize, address: usize,
chip_ready: bool, // used to signal that the eeprom program was finished
// In real hardware, it takes some time for the values to be programmed into the eeprom,
// But we do it right away.
} }
impl<M> EepromChip<M> impl<M> EepromChip<M>
@ -75,6 +79,8 @@ where
tx_buffer: 0, tx_buffer: 0,
address: 0, address: 0,
chip_ready: false,
} }
} }
@ -98,12 +104,12 @@ where
self.tx_count = 0; self.tx_count = 0;
} }
fn clock_data_in(&mut self, si: u8) { fn clock_data_in(&mut self, address: u32, si: u8) {
use SpiInstruction::*; use SpiInstruction::*;
use SpiState::*; use SpiState::*;
// Read the si signal into the rx_buffer // Read the si signal into the rx_buffer
trace!("({:?}) RX bit {}", self.state, si); trace!("({:?}) addr={:#x} RX bit {}", self.state, address, si);
self.rx_buffer = (self.rx_buffer << 1) | (if si & 1 != 0 { 1 } else { 0 }); self.rx_buffer = (self.rx_buffer << 1) | (if si & 1 != 0 { 1 } else { 0 });
self.rx_count += 1; self.rx_count += 1;
@ -124,15 +130,17 @@ where
RxAddress(insn) => { RxAddress(insn) => {
if self.rx_count == 6 { if self.rx_count == 6 {
self.address = (self.rx_buffer as usize) * 8; self.address = (self.rx_buffer as usize) * 8;
debug!("recvd address = {:#x}", self.address); debug!(
"{:?} mode , recvd address = {:#x} (rx_buffer={:#x})",
insn, self.address, self.rx_buffer
);
match insn { match insn {
Read => { Read => {
self.reset_rx_buffer(); next_state = Some(StopBit(Read));
self.reset_tx_buffer();
next_state = Some(StopBit(insn));
} }
Write => { Write => {
next_state = Some(RxData); next_state = Some(RxData);
self.chip_ready = false;
self.reset_rx_buffer(); self.reset_rx_buffer();
} }
} }
@ -146,7 +154,7 @@ where
RxData => { RxData => {
if self.rx_count == 64 { if self.rx_count == 64 {
let mut data = self.rx_buffer; let mut data = self.rx_buffer;
debug!("writing {:x} to memory", data); debug!("writing {:#x} to memory address {:#x}", data, self.address);
for i in 0..8 { for i in 0..8 {
self.memory self.memory
.write(self.address + (7 - i), (data & 0xff) as u8); .write(self.address + (7 - i), (data & 0xff) as u8);
@ -157,6 +165,7 @@ where
} }
} }
StopBit(Write) => { StopBit(Write) => {
self.chip_ready = true;
self.state = RxInstruction; self.state = RxInstruction;
self.reset_rx_buffer(); self.reset_rx_buffer();
self.reset_tx_buffer(); self.reset_tx_buffer();
@ -168,7 +177,7 @@ where
} }
} }
fn clock_data_out(&mut self) -> u8 { fn clock_data_out(&mut self, address: u32) -> u8 {
use SpiState::*; use SpiState::*;
let mut next_state = None; let mut next_state = None;
@ -183,22 +192,26 @@ where
0 0
} }
TxData => { TxData => {
let result = ((self.tx_buffer >> 63) & 1) as u8;
self.tx_buffer = self.tx_buffer.wrapping_shl(1);
self.tx_count += 1; self.tx_count += 1;
if self.tx_count == 64 { if self.tx_count == 64 {
self.reset_rx_buffer();
self.reset_tx_buffer(); self.reset_tx_buffer();
self.reset_rx_buffer();
next_state = Some(RxInstruction); next_state = Some(RxInstruction);
0 }
} else {
let result = ((self.tx_buffer >> 63) & 1) as u8;
self.tx_buffer = self.tx_buffer << 1;
result result
} }
_ => {
if self.chip_ready {
1
} else {
0
}
} }
_ => 0,
}; };
trace!("({:?}) TX bit {}", self.state, result); trace!("({:?}) addr={:#x} TX bit {}", self.state, address, result);
if let Some(next_state) = next_state { if let Some(next_state) = next_state {
self.state = next_state; self.state = next_state;
} }
@ -206,13 +219,19 @@ where
result result
} }
fn data_available(&self) -> bool { pub(in crate) fn is_transmitting(&self) -> bool {
if let SpiState::TxData = self.state { use SpiState::*;
true match self.state {
} else { TxData | TxDummy => true,
false _ => false,
} }
} }
pub(in crate) fn reset(&mut self) {
self.state = SpiState::RxInstruction;
self.reset_rx_buffer();
self.reset_tx_buffer();
}
} }
/// The Eeprom controller is usually mapped to the top 256 bytes of the cartridge memory /// The Eeprom controller is usually mapped to the top 256 bytes of the cartridge memory
@ -222,7 +241,7 @@ pub struct SpiController<M>
where where
M: BackupMemoryInterface, M: BackupMemoryInterface,
{ {
chip: RefCell<EepromChip<M>>, pub(in crate) chip: RefCell<EepromChip<M>>,
} }
impl<M> SpiController<M> impl<M> SpiController<M>
@ -235,53 +254,23 @@ where
} }
} }
pub fn write_half(&mut self, value: u16) { pub fn write_half(&mut self, address: u32, value: u16) {
self.chip.borrow_mut().clock_data_in(value as u8); self.chip.borrow_mut().clock_data_in(address, value as u8);
} }
pub fn read_half(&self) -> u16 { pub fn read_half(&self, address: u32) -> u16 {
let mut chip = self.chip.borrow_mut(); let mut chip = self.chip.borrow_mut();
let bit = chip.clock_data_out() as u16; chip.clock_data_out(address) as u16
if chip.data_available() {
bit
} else {
0
}
}
#[cfg(test)]
fn consume_dummy_cycles(&self) {
// ignore the dummy bits
self.read_half();
self.read_half();
self.read_half();
self.read_half();
}
#[cfg(test)]
fn rx_data(&self) -> [u8; 8] {
let mut bytes = [0; 8];
for byte_index in 0..8 {
let mut byte = 0;
for _ in 0..8 {
let bit = self.read_half() as u8;
byte = (byte << 1) | bit;
}
bytes[byte_index] = byte;
}
bytes
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::super::EEPROM_BASE_ADDR;
use super::super::BackupMemoryInterface; use super::super::BackupMemoryInterface;
use super::*; use super::*;
use bit::BitIndex; use bit::BitIndex;
use hexdump;
use std::io::Write;
#[derive(Debug)] #[derive(Debug)]
struct MockMemory { struct MockMemory {
@ -298,6 +287,29 @@ mod tests {
} }
} }
impl SpiController<MockMemory> {
fn consume_dummy_cycles(&self) {
// ignore the dummy bits
self.read_half(EEPROM_BASE_ADDR);
self.read_half(EEPROM_BASE_ADDR);
self.read_half(EEPROM_BASE_ADDR);
self.read_half(EEPROM_BASE_ADDR);
}
fn rx_data(&self) -> [u8; 8] {
let mut bytes = [0; 8];
for byte_index in 0..8 {
let mut byte = 0u8;
for _ in 0..8 {
let bit = self.read_half(EEPROM_BASE_ADDR) as u8;
byte = (byte.wrapping_shl(1)) | bit;
}
bytes[byte_index] = byte;
}
bytes
}
}
fn make_mock_memory() -> MockMemory { fn make_mock_memory() -> MockMemory {
let mut buffer = vec![0; 512]; let mut buffer = vec![0; 512];
buffer[16] = 'T' as u8; buffer[16] = 'T' as u8;
@ -369,12 +381,12 @@ mod tests {
// 1 bit "0" - stop bit // 1 bit "0" - stop bit
let stream = make_spi_read_request(2); let stream = make_spi_read_request(2);
for half in stream.into_iter() { for half in stream.into_iter() {
spi.write_half(half); spi.write_half(EEPROM_BASE_ADDR, half);
} }
spi.consume_dummy_cycles(); spi.consume_dummy_cycles();
assert!(spi.chip.borrow().data_available()); assert!(spi.chip.borrow().is_transmitting());
let data = spi.rx_data(); let data = spi.rx_data();
@ -398,7 +410,7 @@ mod tests {
// First, lets test a read request // First, lets test a read request
let stream = make_spi_read_request(2); let stream = make_spi_read_request(2);
for half in stream.into_iter() { for half in stream.into_iter() {
spi.write_half(half); spi.write_half(EEPROM_BASE_ADDR, half);
} }
spi.consume_dummy_cycles(); spi.consume_dummy_cycles();
let data = spi.rx_data(); let data = spi.rx_data();
@ -420,7 +432,7 @@ mod tests {
bytes[4] = expected[4]; bytes[4] = expected[4];
let stream = make_spi_write_request(2, bytes); let stream = make_spi_write_request(2, bytes);
for half in stream.into_iter() { for half in stream.into_iter() {
spi.write_half(half); spi.write_half(EEPROM_BASE_ADDR, half);
} }
{ {
@ -434,10 +446,16 @@ mod tests {
// Also lets again read the result // Also lets again read the result
let stream = make_spi_read_request(2); let stream = make_spi_read_request(2);
for half in stream.into_iter() { for half in stream.into_iter() {
spi.write_half(half); spi.write_half(EEPROM_BASE_ADDR, half);
} }
spi.consume_dummy_cycles(); spi.consume_dummy_cycles();
let data = spi.rx_data(); let data = spi.rx_data();
assert_eq!(expected, &data[0..5]); assert_eq!(expected, &data[0..5]);
{
let chip = spi.chip.borrow();
assert_eq!(SpiState::RxInstruction, chip.state);
assert_eq!(0, chip.rx_count);
assert_eq!(0, chip.tx_count);
}
} }
} }

View file

@ -100,7 +100,7 @@ pub struct Cartridge {
pub header: CartridgeHeader, pub header: CartridgeHeader,
bytes: Box<[u8]>, bytes: Box<[u8]>,
size: usize, size: usize,
backup: BackupMedia, pub(in crate) backup: BackupMedia,
} }
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> { fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
@ -199,7 +199,7 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
use super::sysbus::consts::*; use super::sysbus::consts::*;
const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
impl Bus for Cartridge { impl Bus for Cartridge {
fn read_8(&self, addr: Addr) -> u8 { fn read_8(&self, addr: Addr) -> u8 {
@ -221,9 +221,11 @@ impl Bus for Cartridge {
} }
fn read_16(&self, addr: u32) -> u16 { fn read_16(&self, addr: u32) -> u16 {
if addr & 0xff000000 == GAMEPAK_WS2_HI && (self.bytes.len() <= 16*1024*1024 || addr >= EEPROM_BASE_ADDR) { if addr & 0xff000000 == GAMEPAK_WS2_HI
&& (self.bytes.len() <= 16 * 1024 * 1024 || addr >= EEPROM_BASE_ADDR)
{
if let BackupMedia::Eeprom(spi) = &self.backup { if let BackupMedia::Eeprom(spi) = &self.backup {
return spi.read_half(); return spi.read_half(addr);
} }
} }
self.default_read_16(addr) self.default_read_16(addr)
@ -236,14 +238,16 @@ impl Bus for Cartridge {
BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value), BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value),
_ => {} _ => {}
}, },
_ => {}, // TODO allow the debugger to write _ => {} // TODO allow the debugger to write
}; };
} }
fn write_16(&mut self, addr: u32, value: u16) { fn write_16(&mut self, addr: u32, value: u16) {
if addr & 0xff000000 == GAMEPAK_WS2_HI && (self.bytes.len() <= 16*1024*1024 || addr >= EEPROM_BASE_ADDR) { if addr & 0xff000000 == GAMEPAK_WS2_HI
&& (self.bytes.len() <= 16 * 1024 * 1024 || addr >= EEPROM_BASE_ADDR)
{
if let BackupMedia::Eeprom(spi) = &mut self.backup { if let BackupMedia::Eeprom(spi) = &mut self.backup {
return spi.write_half(value); return spi.write_half(addr, value);
} }
} }
self.default_write_16(addr, value); self.default_write_16(addr, value);

View file

@ -1,3 +1,4 @@
use super::cartridge::BackupMedia;
use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B}; use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
use super::sysbus::SysBus; use super::sysbus::SysBus;
use super::{Bus, Interrupt, IrqBitmask}; use super::{Bus, Interrupt, IrqBitmask};
@ -129,6 +130,15 @@ impl DmaChannel {
} }
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) { fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
if self.id == 3 {
if let BackupMedia::Eeprom(eeprom) = &mut sb.cartridge.backup {
// this might be a eeprom request, so we need to reset the eeprom state machine if its dirty (due to bad behaving games, or tests roms)
if !eeprom.chip.borrow().is_transmitting() {
eeprom.chip.borrow_mut().reset();
}
}
}
let word_size = if self.ctrl.is_32bit() { 4 } else { 2 }; let word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
let count = match self.internal.count { let count = match self.internal.count {
0 => match self.id { 0 => match self.id {

View file

@ -115,7 +115,7 @@ pub struct SysBus {
bios: BoxedMemory, bios: BoxedMemory,
onboard_work_ram: BoxedMemory, onboard_work_ram: BoxedMemory,
internal_work_ram: BoxedMemory, internal_work_ram: BoxedMemory,
cartridge: Cartridge, pub cartridge: Cartridge,
dummy: DummyBus, dummy: DummyBus,
pub trace_access: bool, pub trace_access: bool,