Emulation Fixes
This commit is contained in:
parent
6d838e2b89
commit
f7110dcd50
22
README.md
22
README.md
|
@ -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|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue