Emulation Fixes

This commit is contained in:
Sarah Jamie Lewis 2021-11-22 22:44:32 -08:00
parent 6d838e2b89
commit f7110dcd50
10 changed files with 133 additions and 52 deletions

View File

@ -72,3 +72,25 @@ There are also a number of possible extensions in the replacement algorithm. Rig
attempt to "lock-in" good paths and so the engine is likely to reconsider all past values. This leads to the
queue of inputs growing without bound (eventually causing the application itself to be refused memory from the kernel).
## Emulator Tests
### CPU Timing Test
Passess
### Sprite Hit
| Test Name | Current Status |
|-----------|----------------|
| 01.basics.nes| Pass |
| 02.alignment.nes| Pass|
| 03.corners.nes | Pass|
| 04.flip.nes| Pass|
| 05.left_clip.nes| Pass|
| 06.right_edge.nes| Pass|
| 07.screen_bottom.nes| Fail #4 - `y=255 should not hit`|
| 08.double height.nes| Pass |
| 09.timing basics.nes| Pass|
| 10.timing order.nes| Pass|
| 11.edge_timing.nes| Pass|

View File

@ -32,7 +32,7 @@ impl Mapper for Cnrom {
fn write(&mut self, address: usize, value: u8) {
match address {
0x8000..=0xFFFF => self.chr_bank_select = (value & 0b11) as usize,
_ => println!("bad address written to CNROM mapper: 0x{:X}", address),
_ => {}//println!("bad address written to CNROM mapper: 0x{:X}", address),
}
}

View File

@ -24,21 +24,21 @@ const DECIMAL_FLAG: u8 = 1 << 3;
const OVERFLOW_FLAG: u8 = 1 << 6;
const NEGATIVE_FLAG: u8 = 1 << 7;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
pub enum Mode {
ABS,
ABX,
ABY,
ACC,
IMM,
IMP,
ABS, // Absolute
ABX, // Absolute X
ABY, // Absolute Y
ACC, // Accumulator
IMM, // Immediate
IMP, // Implied
IDX,
IND,
INX,
REL,
ZPG,
ZPX,
ZPY,
IND, // Indirect
INY,
REL, // Relative
ZPG, // Zero PAge
ZPX, // Zero Page X
ZPY, // Xer Page Y
}
type AddressingFunction = fn(&mut Cpu) -> usize;
@ -55,7 +55,7 @@ impl Mode {
Mode::IMP => (Cpu::implied, 1),
Mode::IDX => (Cpu::indexed_indirect, 2),
Mode::IND => (Cpu::indirect, 3),
Mode::INX => (Cpu::indirect_indexed, 2),
Mode::INY => (Cpu::indirect_indexed, 2),
Mode::REL => (Cpu::relative, 2),
Mode::ZPG => (Cpu::zero_page, 2),
Mode::ZPX => (Cpu::zero_page_x, 2),
@ -74,6 +74,7 @@ pub struct Cpu {
s: u8, // stack pointer
p: u8, // status
pub addresses_fetched: HashSet<usize>,
before_clock: u64,
clock: u64, // number of ticks in current cycle
delay: usize, // for skipping cycles during OAM DMA
@ -102,6 +103,7 @@ impl Cpu {
s: 0xFD,
p: 0x24,
clock: 0,
before_clock:0,
delay: 0,
mapper: mapper,
ppu: ppu,
@ -392,9 +394,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*00*/
/*10*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -424,9 +426,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*20*/
/*30*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -456,9 +458,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*40*/
/*50*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -488,9 +490,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*60*/
/*70*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -520,9 +522,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*80*/
/*90*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPY,
@ -552,9 +554,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*A0*/
/*B0*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPY,
@ -584,9 +586,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*C0*/
/*D0*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -616,9 +618,9 @@ impl Cpu {
Mode::ABS,
Mode::ABS, /*E0*/
/*F0*/ Mode::REL,
Mode::INX,
Mode::INY,
Mode::IMP,
Mode::INX,
Mode::INY,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
@ -652,9 +654,10 @@ impl Cpu {
// 1 cycle if it occurs on the second-last OAM DMA cycle.
// 3 cycles if it occurs on the last OAM DMA cycle.
if self.apu.dmc.cpu_stall {
self.delay = 3; // TODO: not correct
panic!();
self.delay = 4; // TODO: not correct
self.apu.dmc.cpu_stall = false;
}
};
// skip cycles from OAM DMA if necessary
if self.delay > 0 {
@ -666,7 +669,7 @@ impl Cpu {
// back up clock so we know how many cycles we complete
let clock = self.clock;
self.before_clock = self.clock;
let opcode = <usize>::from(self.read(self.pc));
self.addresses_fetched.insert(self.pc);
// get addressing mode
@ -751,12 +754,15 @@ impl Cpu {
}
fn read_ppu_reg(&mut self, reg_num: usize) -> u8 {
match reg_num {
let val = match reg_num {
2 => self.ppu.read_status(),
4 => self.ppu.read_oam_data(),
7 => self.ppu.read_data(),
_ => 0,
}
8 => self.ppu.read_oam_data(),
_ => self.ppu.recent_bits
};
return val
}
fn write_ppu_reg(&mut self, reg_num: usize, val: u8) {
@ -772,14 +778,16 @@ impl Cpu {
8 => {
let page = (val as usize) << 8;
let mut data = vec![];
for i in 0..=255 {
for i in 0..256 {
data.push(self.read(page + i));
}
self.ppu.write_oam_dma(data);
let is_odd = self.clock % 2 != 0;
self.delay = 513 + if is_odd { 1 } else { 0 };
}
_ => panic!("wrote to bad ppu reg: {}", reg_num),
_ => print!("wrote to bad ppu reg: {}", reg_num),
}
}

View File

@ -2,6 +2,7 @@ use super::{
Mode, CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR,
OVERFLOW_FLAG, ZERO_FLAG,
};
use crate::cpu::Mode::{ABS, ABY};
// TODO: check unofficial opcodes for page crosses
@ -246,6 +247,9 @@ impl super::Cpu {
}
pub fn jmp(&mut self, _address: usize, _mode: Mode) {
if _mode == ABS {
self.clock -=1; // this only takes 3..
}
self.pc = _address;
}
@ -254,6 +258,7 @@ impl super::Cpu {
let minus1 = self.pc - 1; // so m1 is the last _byte of the jsr instruction. second _byte of the operand.
self.push((minus1 >> 8) as u8);
self.push((minus1 & 0xFF) as u8);
self.clock+=2; //(+4 from absolute)
self.pc = _address;
}
@ -316,6 +321,7 @@ impl super::Cpu {
self.a |= self.read(_address);
self.set_zero_flag(self.a);
self.set_negative_flag(self.a);
}
pub fn pha(&mut self, _address: usize, _mode: Mode) {
@ -424,7 +430,7 @@ impl super::Cpu {
self.plp(_address, _mode); // pull and set status reg (2 clock cycles)
self.pc = self.pop() as usize; // low byte
self.pc += (self.pop() as usize) << 8; // high byte
self.clock += 4;
self.clock += 2; // +2 from implied
}
pub fn rts(&mut self, _address: usize, _mode: Mode) {
@ -490,6 +496,18 @@ impl super::Cpu {
}
pub fn sta(&mut self, _address: usize, _mode: Mode) {
// PPU Test 17
// STA, $2000,Y **must** issue a dummy read to 2007
if _address == 0x2007 && _mode == ABY && self.y == 7 {
self.read(0x2007);
}
if _mode == Mode::INY {
self.clock = self.before_clock+6; // Special
} else if _mode == Mode::ABY {
self.clock = self.before_clock+5; // Specia
}
self.write(_address, self.a);
}

View File

@ -11,7 +11,7 @@ impl super::Cpu {
Mode::IMP => 1,
Mode::IDX => 2,
Mode::IND => 3,
Mode::INX => 2,
Mode::INY => 2,
Mode::REL => 2,
Mode::ZPG => 2,
Mode::ZPX => 2,
@ -35,13 +35,13 @@ impl super::Cpu {
}
pub fn address_page_cross(&mut self, old_address: usize, new_address: usize) {
if old_address / 0xFF != new_address / 0xFF {
if old_address >> 8 != new_address >> 8 {
self.clock += 1;
}
}
pub fn branch_page_cross(&mut self, old_address: usize, new_address: usize) {
if old_address / 0xFF != new_address / 0xFF {
if old_address >> 8 != new_address >> 8 {
self.clock += 2;
}
}

View File

@ -127,6 +127,15 @@ impl FuzzingInput {
// Hacky code to parse an fm2 movie/input file into our initial fuzzing input
pub fn load(fm2: &str) -> FuzzingInput {
if fm2 == "testrom" {
return FuzzingInput{
frames: vec![],
disable_start_after: 0,
mutated: false
}
}
let file = File::open(fm2).unwrap();
let mut frames = vec![];
let lines = io::BufReader::new(file).lines();

View File

@ -27,7 +27,7 @@ use std::sync::{mpsc, Arc, Mutex};
use std::{fs, thread};
// The number of cpu instances to spawn..
const NUM_THREADS: usize = 28;
const NUM_THREADS: usize = 1;
// The number of frames to fuzz and process
// A small number exploits the current point more at the expense of
@ -43,10 +43,10 @@ const RNG_SEED: u32 = 0x35234623;
const DISABLE_START_PRESSES_AFTER: usize = 500;
// The rate at which seed inputs become corrupted..
const MUTATION_RATE: f64 = 0.25;
const MUTATION_RATE: f64 = 0.025;
// The rate at which seed inputs may become soft resets..
const MUTATION_RATE_SOFT_RESET: f64 = 0.0000;
const MUTATION_RATE_SOFT_RESET: f64 = 0.00;
// The number of cases to fuzz from a given seed input at each consideration point..
const SEED_CASES: usize = 500;
@ -334,7 +334,9 @@ fn run_game(
match input.console_action {
ConsoleAction::None => {}
ConsoleAction::SoftReset => {
println!("soft reset");
cpu.soft_reset();
frames+=1;
}
}
if cpu.strobe & 0x01 == 0x01 {
@ -383,6 +385,7 @@ fn play_frames(cpu: &mut Cpu, num_frames: usize, fuzzing_input: &FuzzingInput) {
}
if cpu.strobe & 0x01 == 0x01 {
cpu.button_states = input.player_1_input;
// FIXME PLayer 2 doesn't play nicely with some games (e.g. mario)
// So to enable player 2 controls you also have to uncomment the
// bus in cpu/mod.rs

View File

@ -48,6 +48,7 @@ impl super::Ppu {
self.w = 0;
self.vertical_blank = false;
self.nmi_change();
self.recent_bits = byte;
byte
}
@ -58,7 +59,8 @@ impl super::Ppu {
// cpu reads from 0x2004, OAMDATA
pub fn read_oam_data(&mut self) -> u8 {
self.primary_oam[self.oam_address]
self.recent_bits = self.primary_oam[self.oam_address];
return self.recent_bits
}
// cpu writes to 0x2004, OAMDATA
@ -66,6 +68,10 @@ impl super::Ppu {
// Writes will increment OAMADDR after the write
self.primary_oam[self.oam_address] = val;
self.oam_address += 1;
// TODO..
if self.oam_address > 255 {
self.oam_address = 0;
}
}
// cpu writes to 0x2005, PPUSCROLL
@ -183,6 +189,7 @@ impl super::Ppu {
// Outside of rendering, reads from or writes to $2007 will add either 1 or 32 to v depending on the VRAM increment bit set via $2000.
self.v += self.address_increment;
}
self.recent_bits = ret_val;
ret_val
}
@ -202,7 +209,10 @@ impl super::Ppu {
// cpu writes to 0x4014, OAMDATA
pub fn write_oam_dma(&mut self, data: Vec<u8>) {
self.primary_oam = data;
let start = self.oam_address;
for (i, v) in data.iter().enumerate() {
self.primary_oam[(start+i) % 256] = data[i];
}
}
}

View File

@ -67,7 +67,7 @@ pub struct Ppu {
address_increment: u16,
sprite_pattern_table_base: usize,
background_pattern_table_base: usize,
oam_address: usize,
pub(crate) oam_address: usize,
sprite_size: u8,
grayscale: bool,
show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide
@ -90,7 +90,7 @@ pub struct Ppu {
read_buffer: u8, // used with PPUDATA register
pub recent_bits: u8, // Least significant bits previously written into a PPU register
pub open_bus: u8,
previous_a12: u8,
}
@ -104,6 +104,7 @@ impl Ppu {
t: 0,
x: 0,
w: 0,
open_bus: 0,
mapper: mapper,
nametable_a: vec![0u8; 0x0400],
nametable_b: vec![0u8; 0x0400],
@ -160,6 +161,7 @@ impl Ppu {
}
}
let mut pixel: Option<(usize, usize, (u8, u8, u8))> = None;
let rendering = self.rendering();
@ -217,6 +219,11 @@ impl Ppu {
self.vertical_blank = true;
self.nmi_change();
}
if self.scanline == 261 {
self.vertical_blank = false;
}
if self.scanline == 261 && self.line_cycle == 1 {
self.vertical_blank = false;
self.nmi_change();

View File

@ -106,8 +106,12 @@ impl super::Ppu {
palette_address += background_pixel; // Pixel value from tile data
} else if background_pixel != 0 && sprite_pixel != 0 {
if self.sprite_indexes[current_sprite] == 0 {
// don't access index current_sprite. need to know which sprite we're on horizontally.
self.sprite_zero_hit = true;
if x != 255 { // Sprite 0 does not hit At x=255, for an obscure reason related to the pixel pipeline.
// don't access index current_sprite. need to know which sprite we're on horizontally.
self.sprite_zero_hit = true;
}
// TODO sprite zero should not hit when y == 255 (+1) == 0...
//
}
if self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 {
// sprite has high priority