This commit is contained in:
Theron Spiegl 2019-11-11 18:04:07 -06:00
commit 03e949eb19
17 changed files with 2262 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
roms

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "nestur"
version = "0.1.0"
authors = ["Theron <tspiegl@gmail.com>"]
edition = "2018"
[dependencies]
sdl2 = "0.32"
cpuprofiler = "0.0.3"

42
README.md Normal file
View File

@ -0,0 +1,42 @@
# nestur
The NES you left outside in the rain but let dry and still kind of works.
This is an NES emulator and a work in progress. The CPU and PPU work, though there are still at least a couple bugs. I've mostly tested on Donkey Kong and Super Mario Bros. so far. There are plenty of full-featured emulators out there; this is primarily an educational project but I do want it to run well.
- One dependency (SDL)
- One line of `unsafe` (`std::mem::transmute::<u8>() -> i8`)
- No heap allocation
- NTSC timing
## Controls:
```
Button | Key
___________________
| A | D |
| B | F |
| Start | Enter |
| Select | L-Shift|
| Up | Up |
| Down | Down |
| Left | Left |
| Right | Right |
-------------------
```
The code aims to follow the explanations from https://wiki.nesdev.com/w/index.php/NES_reference_guide where possible, especially in the PPU, and the comments quote from it often.
Thanks to Michael Fogleman's https://github.com/fogleman/nes for getting me unstuck at several points.
## To do:
- More mappers (only NROM/mapper 0 implemented so far)
- Audio
- Player 2 controller?
- Sprite bug when Goomba smashed in Mario
- Figure out performance issue on Windows

BIN
SDL2.dll Normal file

Binary file not shown.

31
src/cartridge/mappers.rs Normal file
View File

@ -0,0 +1,31 @@
pub fn nrom_cpu(cpu: &mut crate::cpu::Cpu, address: usize, writing: bool) -> Option<&mut u8> {
// PRG-ROM, not -RAM
if writing { return None };
// CPU $8000-$BFFF: First 16 KB of ROM.
// CPU $C000-$FFFF: Last 16 KB of ROM (NROM-256) or mirror of $8000-$BFFF (NROM-128).
let l = cpu.prg_rom.len();
match address {
0x8000...0xBFFF => Some(&mut cpu.prg_rom[0][address % 0x4000]),
0xC000...0xFFFF => Some(&mut cpu.prg_rom[l - 1][address % 0x4000]),
_ => panic!("bad cpu address passed to nrom mapper"),
}
}
pub fn nrom_ppu(ppu: &mut crate::ppu::Ppu, address: usize, writing: bool) -> Option<&mut u8> {
let l = ppu.pattern_tables.len();
// NROM/mapper 0 doesn't allow writes to CHR-ROM
if writing || l == 0 { return None };
match address {
0x0000...0x1FFF => Some(&mut ppu.pattern_tables[l-1][address]),
_ => panic!("bad ppu address passed to nrom mapper: 0x{:04x}", address),
}
}
pub fn get_mapper_funcs(mapper: u8) -> (super::CpuMapperFunc, super::PpuMapperFunc) {
match mapper {
0 => (nrom_cpu, nrom_ppu),
_ => panic!("unimplemented mapper: {}", mapper),
}
}

81
src/cartridge/mod.rs Normal file
View File

@ -0,0 +1,81 @@
mod mappers;
use mappers::get_mapper_funcs;
use std::io::Read;
// To avoid separate read and write functions for every mapper, the mapper functions returns a reference to the CPU or PPU's own
// byte of memory, which the calling method can dereference to read or set the value.
pub type CpuMapperFunc = fn(&mut crate::cpu::Cpu, usize, bool) -> Option<&mut u8>;
pub type PpuMapperFunc = fn(&mut crate::ppu::Ppu, usize, bool) -> Option<&mut u8>;
pub struct Cartridge {
prg_rom_size: usize,
chr_rom_size: usize,
pub mirroring: u8, // 0 horizontal, 1 vertical
_bb_prg_ram_present: u8, // 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
trainer_present: u8, // 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
_four_screen_vram: u8, // 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
// TODO: other iNES header flags
pub prg_rom: Vec<Vec<u8>>, // 16 KiB chunks for CPU
pub chr_rom: Vec<Vec<u8>>, // 8 KiB chunks for PPU
all_data: Vec<u8>,
pub cpu_mapper_func: CpuMapperFunc,
pub ppu_mapper_func: PpuMapperFunc,
}
impl Cartridge {
pub fn new() -> Self {
let argv: Vec<String> = std::env::args().collect();
assert!(argv.len() > 1, "must include .nes ROM as argument");
let filename = &argv[1];
let mut f = std::fs::File::open(filename).unwrap();
let mut data = vec![];
f.read_to_end(&mut data).unwrap();
assert!(data[0..4] == [0x4E, 0x45, 0x53, 0x1A], "signature mismatch, not an iNES file");
let mapper = ((data[7] >> 4) << 4) + (data[6] >> 4);
let (cpu_mapper_func, ppu_mapper_func) = get_mapper_funcs(mapper);
let mut cart = Cartridge {
prg_rom_size: data[4] as usize,
chr_rom_size: data[5] as usize,
mirroring: (data[6] & (1 << 0) != 0) as u8,
_bb_prg_ram_present: (data[6] & (1 << 1) != 0) as u8,
trainer_present: (data[6] & (1 << 2) != 0) as u8,
_four_screen_vram: (data[6] & (1 << 3) != 0) as u8,
prg_rom: Vec::new(),
chr_rom: Vec::new(),
all_data: data,
cpu_mapper_func: cpu_mapper_func,
ppu_mapper_func: ppu_mapper_func,
};
cart.fill();
cart
}
fn fill(&mut self) {
let prg_chunk_size: usize = 1<<14;
let chr_chunk_size: usize = 1<<13;
let prg_offset: usize = 0x10 + if self.trainer_present == 1 { 0x200 } else { 0 }; // header plus trainer if present
let chr_offset: usize = prg_offset + (self.prg_rom_size * prg_chunk_size); // chr comes after prg
// fill vecs with chunks
for i in 0..self.prg_rom_size {
let offset = prg_offset + (i * prg_chunk_size);
let chunk = self.all_data[offset..(offset + prg_chunk_size)].to_vec();
self.prg_rom.push(chunk.clone());
};
for i in 0..self.chr_rom_size {
let offset = chr_offset + (i * chr_chunk_size);
let chunk = self.all_data[offset..offset + chr_chunk_size].to_vec();
self.chr_rom.push(chunk);
};
}
}
/*
The mappings above are the fixed addresses from which the PPU uses to fetch data during rendering. The actual device that the PPU fetches data from, however, may be configured by the cartridge.
$0000-1FFF is normally mapped by the cartridge to a CHR-ROM or CHR-RAM, often with a bank switching mechanism.
$2000-2FFF is normally mapped to the 2kB NES internal VRAM, providing 2 nametables with a mirroring configuration controlled by the cartridge, but it can be partly or fully remapped to RAM on the cartridge, allowing up to 4 simultaneous nametables.
$3000-3EFF is usually a mirror of the 2kB region from $2000-2EFF. The PPU does not render from this address range, so this space has negligible utility.
$3F00-3FFF is not configurable, always mapped to the internal palette control.
*/

122
src/cpu/addressing_modes.rs Normal file
View File

@ -0,0 +1,122 @@
impl super::Cpu {
pub fn absolute(&mut self) -> usize {
self.clock += 4;
<usize>::from(
((self.read(self.PC + 2) as usize) << 8) + // high byte, little endian
(self.read(self.PC + 1)) as usize // low byte
)
}
pub fn absolute_x(&mut self) -> usize {
let current_opcode = self.read(self.PC);
let old_address = self.absolute();
let new_address = old_address + self.X as usize;
match current_opcode {
0x1C | 0x1D | 0x3C | 0x3D | 0x5C | 0x5D | 0x7C | 0x7D | 0xBC | 0xBD | 0xDC | 0xDD | 0xFC | 0xFD
=> self.address_page_cross(old_address, new_address),
0x1E | 0x1F | 0x3E | 0x3F | 0x5E | 0x5F | 0x7E | 0x7F | 0x9D | 0xC3 | 0xC7 | 0xCF | 0xD3 | 0xD7 | 0xDB | 0xDE | 0xDF | 0xFE | 0xFF
=> self.clock += 1,
_ => panic!("illegal opcode using abs x: {:02x}", current_opcode),
}
new_address
}
pub fn absolute_y(&mut self) -> usize {
let current_opcode = self.PC;
let old_address = self.absolute() as u16; // coerce to u16 for wrapping addition
let new_address = old_address.wrapping_add(self.Y as u16);
let old_address = old_address as usize; // coerce back
let new_address = new_address as usize;
if current_opcode == 0x99 {
self.clock += 1;
} else {
self.address_page_cross(old_address, new_address);
}
new_address
}
pub fn accumulator(&mut self) -> usize {
self.clock += 2;
0
}
pub fn immediate(&mut self) -> usize {
self.clock += 2;
self.PC + 1
}
pub fn implied(&mut self) -> usize {
self.clock += 2;
0
}
pub fn indexed_indirect(&mut self) -> usize {
self.clock += 6;
let operand = self.read(self.PC + 1);
let zp_low_addr = operand.wrapping_add(self.X);
let zp_high_addr = zp_low_addr.wrapping_add(1); // take account of zero page wraparound
let zp_low_byte = self.read(zp_low_addr as usize);
let zp_high_byte = self.read(zp_high_addr as usize);
((zp_high_byte as usize) << 8) + zp_low_byte as usize
}
pub fn indirect(&mut self) -> usize {
let operand_address = ((self.read(self.PC + 2) as usize) << 8)
+ (self.read(self.PC + 1) as usize);
let low_byte = self.read(operand_address) as usize;
// BUG TIME! from https://wiki.nesdev.com/w/index.php/Errata
// "JMP ($xxyy), or JMP indirect, does not advance pages if the lower eight bits
// of the specified address is $FF; the upper eight bits are fetched from $xx00,
// 255 bytes earlier, instead of the expected following byte."
let high_byte = if operand_address & 0xFF == 0xFF {
(self.read(operand_address as usize - 0xFF) as usize) << 8
} else {
(self.read(operand_address as usize + 1) as usize) << 8
};
let real_address = high_byte + low_byte;
self.clock += 5;
real_address
}
pub fn indirect_indexed(&mut self) -> usize {
let operand = self.read(self.PC + 1);
let zp_low_addr = operand;
let zp_high_addr = operand.wrapping_add(1);
let zp_low_byte = self.read(zp_low_addr as usize);
let zp_high_byte = self.read(zp_high_addr as usize);
let old_address = ((zp_high_byte as u16) << 8) + zp_low_byte as u16;
let new_address = old_address.wrapping_add(self.Y as u16);
if self.PC == 0xF1 {
self.clock += 1;
} else {
self.address_page_cross(old_address as usize, new_address as usize);
}
self.clock += 5;
new_address as usize
}
pub fn relative(&mut self) -> usize {
self.clock += 2;
self.PC + 1
}
pub fn zero_page(&mut self) -> usize {
let operand = self.read(self.PC + 1);
self.clock += 3;
operand as usize
}
pub fn zero_page_x(&mut self) -> usize {
let operand = self.read(self.PC + 1);
self.clock += 4;
operand.wrapping_add(self.X) as usize
}
pub fn zero_page_y(&mut self) -> usize {
let operand = self.read(self.PC + 1);
self.clock += 4;
operand.wrapping_add(self.Y) as usize
}
}

287
src/cpu/mod.rs Normal file
View File

@ -0,0 +1,287 @@
mod addressing_modes;
mod opcodes;
mod utility;
// RAM locations
const STACK_OFFSET: usize = 0x100;
const NMI_VECTOR: usize = 0xFFFA;
const RESET_VECTOR: usize = 0xFFFC;
const IRQ_VECTOR: usize = 0xFFFE;
// status register flags
const CARRY_FLAG: u8 = 1 << 0;
const ZERO_FLAG: u8 = 1 << 1;
const INTERRUPT_DISABLE_FLAG: u8 = 1 << 2;
const DECIMAL_FLAG: u8 = 1 << 3;
// bits 4 and 5 are unused except when status register is copied to stack
const OVERFLOW_FLAG: u8 = 1 << 6;
const NEGATIVE_FLAG: u8 = 1 << 7;
#[derive(Clone, Copy)]
pub enum Mode {
ABS, ABX, ABY, ACC,
IMM, IMP, IDX, IND,
INX, REL, ZPG, ZPX,
ZPY,
}
type AddressingFunction = fn(&mut Cpu) -> usize;
impl Mode {
fn get(&self) -> AddressingFunction {
match self {
Mode::ABS => Cpu::absolute,
Mode::ABX => Cpu::absolute_x,
Mode::ABY => Cpu::absolute_y,
Mode::ACC => Cpu::accumulator,
Mode::IMM => Cpu::immediate,
Mode::IMP => Cpu::implied,
Mode::IDX => Cpu::indexed_indirect,
Mode::IND => Cpu::indirect,
Mode::INX => Cpu::indirect_indexed,
Mode::REL => Cpu::relative,
Mode::ZPG => Cpu::zero_page,
Mode::ZPX => Cpu::zero_page_x,
Mode::ZPY => Cpu::zero_page_y,
}
}
}
pub struct Cpu {
mem: Vec<u8>,
A: u8, // accumulator
X: u8, // general purpose
Y: u8, // general purpose
PC: usize, // 16-bit program counter
S: u8, // stack pointer
P: u8, // status
// number of ticks in current cycle
clock: u64,
// for skipping cycles during OAM DMA
delay: usize,
// function table
opcode_table: Vec<fn(&mut Self, usize, Mode)>,
// address mode table
mode_table: Vec<Mode>,
// cartridge data
pub prg_rom: Vec<Vec<u8>>, // one 16 KiB chunk for each specified in iNES header
mapper_func: crate::cartridge::CpuMapperFunc,
// ppu
pub ppu: super::Ppu,
// controller
pub strobe: u8,
pub button_states: u8, // Player 1 controller
button_number: u8,
}
impl Cpu {
pub fn new(cart: &super::Cartridge, ppu: super::Ppu) -> Self {
let mut cpu = Cpu{
mem: vec![0; 0x2000],
A: 0, X: 0, Y: 0,
PC: 0,
S: 0xFD,
P: 0x24, // TODO: change this back to 0x34? nestest.nes ROM has it as 0x24 at start.
clock: 0,
delay: 0,
prg_rom: cart.prg_rom.clone(),
mapper_func: cart.cpu_mapper_func,
ppu: ppu,
strobe: 0,
button_states: 0,
button_number: 0,
opcode_table: vec![
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
/*00*/ Cpu::brk, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::php, Cpu::ora, Cpu::asl, Cpu::nop, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*00*/
/*10*/ Cpu::bpl, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::clc, Cpu::ora, Cpu::nop, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*10*/
/*20*/ Cpu::jsr, Cpu::and, Cpu::bad, Cpu::rla, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, Cpu::plp, Cpu::and, Cpu::rol, Cpu::nop, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, /*20*/
/*30*/ Cpu::bmi, Cpu::and, Cpu::bad, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, Cpu::sec, Cpu::and, Cpu::nop, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, /*30*/
/*40*/ Cpu::rti, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::pha, Cpu::eor, Cpu::lsr, Cpu::nop, Cpu::jmp, Cpu::eor, Cpu::lsr, Cpu::sre, /*40*/
/*50*/ Cpu::bvc, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::cli, Cpu::eor, Cpu::nop, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, /*50*/
/*60*/ Cpu::rts, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::pla, Cpu::adc, Cpu::ror, Cpu::nop, Cpu::jmp, Cpu::adc, Cpu::ror, Cpu::rra, /*60*/
/*70*/ Cpu::bvs, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::sei, Cpu::adc, Cpu::nop, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, /*70*/
/*80*/ Cpu::nop, Cpu::sta, Cpu::nop, Cpu::sax, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::dey, Cpu::nop, Cpu::txa, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, /*80*/
/*90*/ Cpu::bcc, Cpu::sta, Cpu::bad, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::tya, Cpu::sta, Cpu::txs, Cpu::nop, Cpu::nop, Cpu::sta, Cpu::nop, Cpu::nop, /*90*/
/*A0*/ Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::tay, Cpu::lda, Cpu::tax, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*A0*/
/*B0*/ Cpu::bcs, Cpu::lda, Cpu::bad, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::clv, Cpu::lda, Cpu::tsx, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*B0*/
/*C0*/ Cpu::cpy, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::iny, Cpu::cmp, Cpu::dex, Cpu::nop, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, /*C0*/
/*D0*/ Cpu::bne, Cpu::cmp, Cpu::bad, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::cld, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, /*D0*/
/*E0*/ Cpu::cpx, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::inx, Cpu::sbc, Cpu::nop, Cpu::sbc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, /*E0*/
/*F0*/ Cpu::beq, Cpu::sbc, Cpu::bad, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::sed, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, /*F0*/
],
mode_table: vec![
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
/*00*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*00*/
/*10*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*10*/
/*20*/ Mode::ABS, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*20*/
/*30*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*30*/
/*40*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*40*/
/*50*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*50*/
/*60*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::IND, Mode::ABS, Mode::ABS, Mode::ABS, /*60*/
/*70*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*70*/
/*80*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*80*/
/*90*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*90*/
/*A0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*A0*/
/*B0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*B0*/
/*C0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*C0*/
/*D0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*D0*/
/*E0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*E0*/
/*F0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*F0*/
],
};
cpu.PC = ((cpu.read(RESET_VECTOR + 1) as usize) << 8) + cpu.read(RESET_VECTOR) as usize;
cpu
}
pub fn step(&mut self) -> u64 {
// skip cycles from OAM DMA if necessary
if self.delay > 0 {
self.delay -= 1;
return 1;
}
if self.ppu.trigger_nmi {
self.nmi();
}
self.ppu.trigger_nmi = false;
// back up clock so we know how many cycles we complete
let clock = self.clock;
let opcode = <usize>::from(self.read(self.PC));
// debugging
// let pc = self.PC;
// print!("{:04X} {:02X} [{:02x} {:02x}] A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X}",
// pc, self.read(pc), self.read(pc + 1), self.read(pc+2),
// self.A, self.X, self.Y, self.P, self.S,
// );
// let mut zpg = Vec::<u8>::new();
// for i in 0..32 {
// zpg.push(self.read(i));
// }
// print!(" zpg: {:x?}", zpg);
// print!("\n");
// get addressing mode
let mode = self.mode_table[opcode].clone();
let address = mode.get()(self);
// advance program counter according to how many bytes that instruction operated on
self.advance_pc(mode);
// look up instruction in table and execute
self.opcode_table[opcode](self, address, mode);
// return how many cycles it took
self.clock - clock
}
// memory interface
fn read(&mut self, address: usize) -> u8 {
let val = match address {
0x0000...0x1FFF => self.mem[address % 0x0800],
0x2000...0x3FFF => self.read_ppu_reg(address % 8),
0x4014 => self.read_ppu_reg(8),
0x4016 => self.read_controller(),
0x4000...0x4017 => 0, // APU stuff
0x4018...0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020...0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers
*(self.mapper_func)(self, address, false).unwrap() // unwraping because mapper funcs won't return None for reads.
},
_ => panic!("invalid read from 0x{:02x}", address),
};
val
}
// memory interface
fn write(&mut self, address: usize, val: u8) {
match address {
0x0000...0x1FFF => self.mem[address % 0x0800] = val,
0x2000...0x3FFF => self.write_ppu_reg(address % 8, val),
0x4014 => self.write_ppu_reg(8, val),
0x4016 => self.write_controller(val),
0x4000...0x4017 => (), // APU stuff
0x4018...0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020...0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers
match (self.mapper_func)(self, address, true) {
Some(loc) => *loc = val,
None => (),
};
},
_ => panic!("invalid write to {:02x}", address),
}
}
fn read_controller(&mut self) -> u8 {
let bit = match self.button_number < 8 {
true => (self.button_states & (1<<self.button_number) != 0) as u8,
false => 1,
};
if self.strobe & 1 != 0 {
self.button_number = 0;
} else {
self.button_number += 1;
}
bit
}
fn write_controller(&mut self, val: u8) {
self.strobe = val;
if self.strobe & 1 != 0 {
self.button_number = 0;
}
}
fn read_ppu_reg(&mut self, reg_num: usize) -> u8 {
match reg_num {
2 => self.ppu.read_status(),
4 => self.ppu.read_oam_data(),
7 => self.ppu.read_data(),
_ => 0,
}
}
fn write_ppu_reg(&mut self, reg_num: usize, val: u8) {
self.ppu.recent_bits = val;
match reg_num {
0 => self.ppu.write_controller(val),
1 => self.ppu.write_mask(val),
3 => self.ppu.write_oam_address(val as usize),
4 => self.ppu.write_oam_data(val),
5 => self.ppu.write_scroll(val),
6 => self.ppu.write_address(val),
7 => self.ppu.write_data(val),
8 => {
let page = (val as usize) << 8;
let mut data = vec![];
for i in 0..=255 {
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),
}
}
}
/*
Address range Size Device
$0000-$07FF $0800 2KB internal RAM
$0800-$0FFF $0800 ]---+
$1000-$17FF $0800 |---- Mirrors of $0000-$07FF
$1800-$1FFF $0800 ]---+
$2000-$2007 $0008 NES PPU registers
$2008-$3FFF $1FF8 Mirrors of $2000-2007 (repeats every 8 bytes)
$4000-$4017 $0018 NES APU and I/O registers
$4018-$401F $0008 APU and I/O functionality that is normally disabled. See CPU Test Mode.
$4020-$FFFF $BFE0 Cartridge space: PRG ROM, PRG RAM, and mapper registers (See Note)
*/

535
src/cpu/opcodes.rs Normal file
View File

@ -0,0 +1,535 @@
use super::{CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR, OVERFLOW_FLAG, ZERO_FLAG, Mode};
// TODO: check unofficial opcodes for page crosses
impl super::Cpu {
pub fn adc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
let carry_bit = if self.P & CARRY_FLAG == 0 {0} else {1};
let mut new_val = self.A.wrapping_add(byte); // add the byte at the _address to accum
new_val = new_val.wrapping_add(carry_bit); // add carry flag to accumulator
// set carry flag if we wrapped around and added something
if new_val <= self.A && (byte != 0 || carry_bit != 0) {
self.P |= CARRY_FLAG;
} else {
self.P &= 0xFF - CARRY_FLAG;
}
self.set_zero_flag(new_val);
self.set_negative_flag(new_val);
// signed 8-bit overflow can only happen if both signs were positive but result was negative, or if both signs were negative and the result was positive
// sign is positive if num & 0x80 == 0, negative if num & 0x80 != 0
// ((sum & 0x80 != 0) && (acc & 0x80 == 0) && (operand & 0x80 == 0)) || ((sum & 0x80 == 0) && (acc & 0x80 != 0) && (operand & 0x80 != 0))
// simplifies to the below, thanks http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
if (byte ^ new_val) & (self.A ^ new_val) & 0x80 != 0 {
self.P |= OVERFLOW_FLAG;
} else {
self.P &= 0xFF - OVERFLOW_FLAG;
}
self.A = new_val; // actually change the accumulator
}
pub fn and(&mut self, _address: usize, _mode: Mode) {
self.A &= self.read(_address);
self.set_zero_flag(self.A);
self.set_negative_flag(self.A);
}
pub fn asl(&mut self, _address: usize, _mode: Mode) {
let mut val = match _mode {
Mode::ACC => self.A,
_ => {
self.clock += 2;
self.read(_address)
},
};
// put top bit in carry flag
if val & (1<<7) != 0 {
self.P |= CARRY_FLAG;
} else {
self.P &= 0xFF - CARRY_FLAG;
}
val <<= 1;
match _mode {
Mode::ACC => self.A = val,
_ => self.write(_address, val),
};
self.set_zero_flag(val);
self.set_negative_flag(val);
}
pub fn bcc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & CARRY_FLAG == 0 {
self.branch(byte);
}
}
pub fn bcs(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & CARRY_FLAG != 0 {
self.branch(byte);
}
}
pub fn beq(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & ZERO_FLAG != 0 {
self.branch(byte);
}
}
pub fn bit(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
let tested = byte & self.A;
self.set_zero_flag(tested);
let bit6 = byte & (1 << 6);
if bit6 != 0 {
self.P |= OVERFLOW_FLAG;
} else {
self.P &= 0xFF - OVERFLOW_FLAG;
}
let bit7 = byte & (1 << 7);
if bit7 != 0 {
self.P |= NEGATIVE_FLAG;
} else {
self.P &= 0xFF - NEGATIVE_FLAG;
}
}
pub fn bmi(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & NEGATIVE_FLAG != 0 {
self.branch(byte);
}
}
pub fn bne(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & ZERO_FLAG == 0 {
self.branch(byte);
}
}
pub fn bpl(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & NEGATIVE_FLAG == 0 {
self.branch(byte);
}
}
pub fn brk(&mut self, _address: usize, _mode: Mode) {
self.push((self.PC >> 8) as u8); // push high byte
self.push((self.PC & 0xFF) as u8); // push low byte
self.push(self.P | 0b00110000); // push status register with break bits set
self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag
self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte
+ (self.read(IRQ_VECTOR) as usize); // and low byte
self.clock += 5; // total of 7 cycles, 2 come from implied()
}
pub fn bvc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & OVERFLOW_FLAG == 0 {
self.branch(byte);
}
}
pub fn bvs(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
if self.P & OVERFLOW_FLAG != 0 {
self.branch(byte);
}
}
pub fn clc(&mut self, _address: usize, _mode: Mode) {
self.P &= 0xFF - CARRY_FLAG;
}
pub fn cld(&mut self, _address: usize, _mode: Mode) {
self.P &= 0xFF - DECIMAL_FLAG;
}
pub fn cli(&mut self, _address: usize, _mode: Mode) {
self.P &= 0xFF - INTERRUPT_DISABLE_FLAG;
}
pub fn clv(&mut self, _address: usize, _mode: Mode) {
self.P &= 0xFF - OVERFLOW_FLAG;
}
pub fn cmp(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.compare(self.A, byte);
}
pub fn cpx(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.compare(self.X, byte);
}
pub fn cpy(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.compare(self.Y, byte);
}
pub fn dcp(&mut self, _address: usize, _mode: Mode) {
// unofficial
let val = self.read(_address).wrapping_sub(1);
self.write(_address, val);
self.compare(self.A, val);
}
pub fn dec(&mut self, _address: usize, _mode: Mode) {
let val = self.read(_address).wrapping_sub(1);
self.write(_address, val);
self.set_zero_flag(val);
self.set_negative_flag(val);
self.clock += 2; // extra cycles for all addressing modes of this instruction
}
pub fn dex(&mut self, _address: usize, _mode: Mode) {
self.X = self.X.wrapping_sub(1);
self.set_zero_flag(self.X);
self.set_negative_flag(self.X);
}
pub fn dey(&mut self, _address: usize, _mode: Mode) {
self.Y = self.Y.wrapping_sub(1);
self.set_zero_flag(self.Y);
self.set_negative_flag(self.Y);
}
pub fn eor(&mut self, _address: usize, _mode: Mode) {
self.A ^= self.read(_address);
self.set_negative_flag(self.A);
self.set_zero_flag(self.A);
}
pub fn inc(&mut self, _address: usize, _mode: Mode) {
let val = self.read(_address).wrapping_add(1);
self.write(_address, val);
self.set_zero_flag(val);
self.set_negative_flag(val);
self.clock += 2; // extra cycles for all addressing modes of this instruction
}
pub fn isc(&mut self, _address: usize, _mode: Mode) {
// unofficial
self.inc(_address, _mode);
self.sbc(_address, _mode);
}
pub fn inx(&mut self, _address: usize, _mode: Mode) {
self.X = self.X.wrapping_add(1);
self.set_zero_flag(self.X);
self.set_negative_flag(self.X);
}
pub fn iny(&mut self, _address: usize, _mode: Mode) {
self.Y = self.Y.wrapping_add(1);
self.set_zero_flag(self.Y);
self.set_negative_flag(self.Y);
}
pub fn jmp(&mut self, _address: usize, _mode: Mode) {
// TODO: bug here?
self.PC = _address;
}
pub fn jsr(&mut self, _address: usize, _mode: Mode) {
// call to absolute already advances program counter by 3
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.PC = _address;
}
pub fn lax(&mut self, _address: usize, _mode: Mode) {
// unofficial opcode that sets both X and accumulator
// TODO: check cycle count? https://wiki.nesdev.com/w/index.php/Programming_with_unofficial_opcodes
let byte = self.read(_address);
self.A = byte;
self.X = byte;
self.set_zero_flag(byte);
self.set_negative_flag(byte);
}
pub fn lda(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.A = byte;
self.set_zero_flag(byte);
self.set_negative_flag(byte);
}
pub fn ldx(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.X = byte;
self.set_zero_flag(byte);
self.set_negative_flag(byte);
}
pub fn ldy(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
self.Y = byte;
self.set_zero_flag(byte);
self.set_negative_flag(byte);
}
pub fn lsr(&mut self, _address: usize, _mode: Mode) {
let mut val = match _mode {
Mode::ACC => self.A,
_ => {
self.clock += 2;
self.read(_address)
},
};
if val & 0x1 == 0x1 {
self.P |= CARRY_FLAG;
} else {
self.P &= 0xFF - CARRY_FLAG;
}
val >>= 1;
match _mode {
Mode::ACC => self.A = val,
_ => self.write(_address, val),
};
self.set_zero_flag(val);
self.set_negative_flag(val);
}
pub fn nop(&mut self, _address: usize, _mode: Mode) {
}
pub fn ora(&mut self, _address: usize, _mode: Mode) {
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) {
self.clock += 1;
self.push(self.A);
}
pub fn php(&mut self, _address: usize, _mode: Mode) {
self.clock += 1;
self.push(self.P | 0b00110000);
}
pub fn pla(&mut self, _address: usize, _mode: Mode) {
self.clock += 2;
self.A = self.pop();
self.set_zero_flag(self.A);
self.set_negative_flag(self.A);
}
pub fn plp(&mut self, _address: usize, _mode: Mode) {
self.clock += 2;
let status = self.pop();
// for each bit in the popped status, if it's 1,
// set that bit of self.P to 1. if it's 0, set that
// bit of self.P to 0.
for i in 0..=7 {
if i == 4 || i == 5 {
continue; // ignore B flags
}
let bit = if status & (1 << i) == 0 {0} else {1};
if bit != 0 {
self.P |= 1 << i;
} else {
self.P &= 0xFF - (1 << i);
}
}
self.P |= 1 << 5; // turn on bit 5
self.P &= 0xFF - (1 << 4); // and turn off bit 4 because god knows why
}
pub fn rla(&mut self, _address: usize, _mode: Mode) {
// unofficial
self.rol(_address, _mode);
self.and(_address, _mode);
}
pub fn rol(&mut self, _address: usize, _mode: Mode) {
let mut val = match _mode {
Mode::ACC => self.A,
_ => {
self.clock += 2;
self.read(_address)
},
};
let carry_flag_bit = if self.P & CARRY_FLAG != 0 {1} else {0};
let new_cfb = if val & 0x80 != 0 {1} else {0};
val <<= 1;
val += carry_flag_bit;
match _mode {
Mode::ACC => self.A = val,
_ => self.write(_address, val),
};
if new_cfb != 0 { self.P |= CARRY_FLAG; }
else { self.P &= 0xFF - CARRY_FLAG; }
self.set_zero_flag(val);
self.set_negative_flag(val);
}
pub fn ror(&mut self, _address: usize, _mode: Mode) {
let mut val = match _mode {
Mode::ACC => self.A,
_ => {
self.clock += 2; // extra cycles
self.read(_address)
}
};
let cfb = if self.P & CARRY_FLAG != 0 {1} else {0};
let new_cfb = val & 0x1;
val >>= 1;
val += cfb * 0x80;
if new_cfb != 0 { self.P |= CARRY_FLAG; }
else { self.P &= 0xFF - CARRY_FLAG; }
match _mode {
Mode::ACC => self.A = val,
_ => self.write(_address, val),
};
self.set_zero_flag(val);
self.set_negative_flag(val);
}
pub fn rra(&mut self, _address: usize, _mode: Mode) {
// unofficial
self.ror(_address, _mode);
self.adc(_address, _mode);
}
pub fn rti(&mut self, _address: usize, _mode: Mode) {
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;
}
pub fn rts(&mut self, _address: usize, _mode: Mode) {
self.PC = self.pop() as usize;
self.PC += ((self.pop() as usize) << 8) + 1;
self.clock += 4;
}
pub fn sax(&mut self, _address: usize, _mode: Mode) {
// unofficial combo of stx and sta
self.write(_address, self.A & self.X);
}
pub fn sbc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address);
let carry_bit = if self.P & CARRY_FLAG == 0 {1} else {0};
let mut new_val = self.A.wrapping_sub(byte);
new_val = new_val.wrapping_sub(carry_bit);
// if overflow occurs and we subtracted something, CLEAR the carry bit
if new_val >= self.A && (byte != 0 || carry_bit != 0) {
self.P &= 0xFF - CARRY_FLAG;
} else {
self.P |= CARRY_FLAG;
}
self.set_zero_flag(new_val);
self.set_negative_flag(new_val);
// if acc is positive, mem is negative, and result is negative
// or if acc is negative, mem is positive, and result is positive
let acc = self.A & 0x80 == 0;
let mem = byte & 0x80 == 0;
let res = new_val & 0x80 == 0;
// if sign is wrong, SET overflow flag
if (acc && !mem && !res) || (!acc && mem && res) {
self.P |= OVERFLOW_FLAG;
} else {
self.P &= 0xFF - OVERFLOW_FLAG;
}
self.A = new_val; // actually change the accumulator
}
pub fn sec(&mut self, _address: usize, _mode: Mode) {
self.P |= CARRY_FLAG;
}
pub fn sed(&mut self, _address: usize, _mode: Mode) {
self.P |= DECIMAL_FLAG; // don't think this is necessary since the NES's 6502 doesn't have decimal _mode but whatever
}
pub fn sei(&mut self, _address: usize, _mode: Mode) {
self.P |= INTERRUPT_DISABLE_FLAG;
}
pub fn slo(&mut self, _address: usize, _mode: Mode) {
self.asl(_address, _mode);
self.ora(_address, _mode);
// can get away with ignoring that asl handles accumulator addressing mode because slo doesn't handle accumulator addressing mode.
}
pub fn sre(&mut self, _address: usize, _mode: Mode) {
// unofficial
self.lsr(_address, _mode);
self.eor(_address, _mode);
}
pub fn sta(&mut self, _address: usize, _mode: Mode) {
self.write(_address, self.A);
}
pub fn stx(&mut self, _address: usize, _mode: Mode) {
self.write(_address, self.X);
}
pub fn sty(&mut self, _address: usize, _mode: Mode) {
self.write(_address, self.Y);
}
pub fn tax(&mut self, _address: usize, _mode: Mode) {
self.X = self.A;
self.set_zero_flag(self.X);
self.set_negative_flag(self.X);
}
pub fn tay(&mut self, _address: usize, _mode: Mode) {
self.Y = self.A;
self.set_zero_flag(self.Y);
self.set_negative_flag(self.Y);
}
pub fn tsx(&mut self, _address: usize, _mode: Mode) {
self.X = self.S;
self.set_zero_flag(self.X);
self.set_negative_flag(self.X);
}
pub fn txa(&mut self, _address: usize, _mode: Mode) {
self.A = self.X;
self.set_zero_flag(self.A);
self.set_negative_flag(self.A);
}
pub fn txs(&mut self, _address: usize, _mode: Mode) {
self.S = self.X;
}
pub fn tya(&mut self, _address: usize, _mode: Mode) {
self.A = self.Y;
self.set_zero_flag(self.A);
self.set_negative_flag(self.A);
}
pub fn bad(&mut self, _address: usize, _mode: Mode) {
panic!("illegal opcode: 0x{:02X}", self.read(self.PC)); // this won't be the illegal opcode because the PC somehow hasn't been updated yet
}
// Interrupts
pub fn nmi(&mut self) {
self.push((self.PC >> 8) as u8); // push high byte
self.push((self.PC & 0xFF) as u8); // push low byte
self.push(self.P | 0b00110000); // push status register with break bits set
self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag
self.PC = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte
+ (self.read(NMI_VECTOR) as usize); // and low byte
self.clock += 7;
}
}

99
src/cpu/utility.rs Normal file
View File

@ -0,0 +1,99 @@
use super::{CARRY_FLAG, NEGATIVE_FLAG, STACK_OFFSET, ZERO_FLAG, Mode};
impl super::Cpu {
pub fn advance_pc(&mut self, mode: Mode) {
self.PC += match mode {
Mode::ABS => 3,
Mode::ABX => 3,
Mode::ABY => 3,
Mode::ACC => 1,
Mode::IMM => 2,
Mode::IMP => 1,
Mode::IDX => 2,
Mode::IND => 3,
Mode::INX => 2,
Mode::REL => 2,
Mode::ZPG => 2,
Mode::ZPX => 2,
Mode::ZPY => 2,
}
}
pub fn add_offset_to_pc(&mut self, offset: i8) {
match offset >= 0 {
true => {
let decoded_offset = offset as usize;
self.PC += decoded_offset;
},
false => {
let decoded_offset = (-offset) as usize;
self.PC -= decoded_offset;
},
}
}
pub fn address_page_cross(&mut self, old_address: usize, new_address: usize) {
if old_address / 0xFF != new_address / 0xFF {
self.clock += 1;
}
}
pub fn branch_page_cross(&mut self, old_address: usize, new_address: usize) {
if old_address / 0xFF != new_address / 0xFF {
self.clock += 2;
}
}
pub fn branch(&mut self, unsigned_offset: u8) {
let offset: i8 = u8_to_i8(unsigned_offset);
self.clock += 1;
let old_addr = self.PC;
self.add_offset_to_pc(offset);
let new_addr = self.PC;
self.branch_page_cross(old_addr, new_addr);
}
pub fn compare(&mut self, reg: u8, byte: u8) {
if reg >= byte {
self.P |= CARRY_FLAG;
} else {
self.P &= 0xFF - CARRY_FLAG;
}
self.set_zero_flag(if reg == byte {0} else {1});
let diff = reg.wrapping_sub(byte);
self.set_negative_flag(diff);
}
pub fn pop(&mut self) -> u8 {
self.S = self.S.wrapping_add(1);
let byte = self.read(STACK_OFFSET + self.S as usize);
byte
}
pub fn push(&mut self, byte: u8) {
self.write(STACK_OFFSET + self.S as usize, byte);
self.S = self.S.wrapping_sub(1);
}
pub fn set_negative_flag(&mut self, num: u8) {
if num & 0x80 == 0x80 {
self.P |= NEGATIVE_FLAG;
} else {
self.P &= 0xFF - NEGATIVE_FLAG;
}
}
pub fn set_zero_flag(&mut self, num: u8) {
if num == 0 {
self.P |= ZERO_FLAG;
} else {
self.P &= 0xFF - ZERO_FLAG;
}
}
}
pub fn u8_to_i8(offset: u8) -> i8 {
unsafe { std::mem::transmute(offset) }
}

25
src/input/mod.rs Normal file
View File

@ -0,0 +1,25 @@
use sdl2::keyboard::Scancode;
use std::collections::HashSet;
pub fn poll_buttons(strobe: &u8, event_pump: &sdl2::EventPump) -> Option<u8> { // Called when CPU writes to $4016. Stores current key-presses in button_states.
if *strobe & 1 == 1 {
let mut button_states = 0;
let pressed_keys: HashSet<Scancode> = event_pump.keyboard_state().pressed_scancodes().collect();
for key in pressed_keys.iter() {
match key {
Scancode::D => button_states |= 1 << 0, // A
Scancode::F => button_states |= 1 << 1, // B
Scancode::RShift => button_states |= 1 << 2, // Select
Scancode::Return => button_states |= 1 << 3, // Start
Scancode::Up => button_states |= 1 << 4, // Up
Scancode::Down => button_states |= 1 << 5, // Down
Scancode::Left => button_states |= 1 << 6, // Left
Scancode::Right => button_states |= 1 << 7, // Right
_ => (),
}
}
Some(button_states)
} else {
None
}
}

81
src/main.rs Normal file
View File

@ -0,0 +1,81 @@
use std::time::{Instant, Duration};
mod cpu;
mod ppu;
mod cartridge;
mod screen;
mod input;
use cpu::Cpu;
use ppu::Ppu;
use cartridge::Cartridge;
use screen::Screen;
use input::poll_buttons;
use sdl2::keyboard::Keycode;
use sdl2::event::Event;
// use cpuprofiler::PROFILER;
fn main() -> Result<(), String> {
let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?;
let mut screen = Screen::new(&sdl_context)?;
let cart = Cartridge::new();
let ppu = Ppu::new(&cart);
let mut cpu = Cpu::new(&cart, ppu);
let mut timer = Instant::now();
let mut fps_timer = Instant::now();
let mut fps = 0;
// PROFILER.lock().unwrap().start("./main.profile").unwrap();
'running: loop {
// perform 1 cpu instruction, getting back number of clock cycles it took
let num_cycles = cpu.step();
// maintain ratio of 3 ppu cycles for 1 cpu step
for _i in 0..num_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.step();
match pixel {
Some((x, y, color)) => screen.draw_pixel(x, y, color),
None => Ok(()),
}?;
if end_of_frame {
fps += 1;
screen.canvas.present();
let now = Instant::now();
if now < timer + Duration::from_millis(1000/60) {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
}
timer = Instant::now();
for event in event_pump.poll_iter() {
match event {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
break 'running
},
_ => (),
}
}
}
}
// handle keyboard events
match poll_buttons(&cpu.strobe, &event_pump) {
Some(button_states) => cpu.button_states = button_states,
None => (),
};
// calculate fps
let now = Instant::now();
if now > fps_timer + Duration::from_secs(1) {
println!("fps: {}", fps);
fps = 0;
fps_timer = now;
}
}
// PROFILER.lock().unwrap().stop().unwrap();
Ok(())
}
// TODO: reset function?
// TODO: save/load functionality

209
src/ppu/cpu_registers.rs Normal file
View File

@ -0,0 +1,209 @@
impl super::Ppu {
// cpu writes to 0x2000, PPUCTRL
pub fn write_controller(&mut self, byte: u8) {
// VRAM address increment per CPU read/write of PPUDATA
self.address_increment = match byte & (1<<2) == 0 { // (0: add 1, going across; 1: add 32, going down)
true => 1,
false => 32,
};
// Sprite pattern table address for 8x8 sprites
self.sprite_pattern_table_base = match byte & (1<<3) == 0 {
true => 0x0,
false => 0x1000,
};
// Background pattern table address
self.background_pattern_table_base = match byte & (1<<4) == 0 {
true => 0x0,
false => 0x1000,
};
// Sprite size (0: 8x8 pixels; 1: 8x16 pixels)
self.sprite_size = if byte & (1<<5) != 0 { 16 } else {8};
// Ignoring PPU master/slave select for now
self.should_generate_nmi = byte & (1<<7) != 0;
self.nmi_change();
// Take care of t
set_bit(&mut self.t, 10, byte as u16, 0);
set_bit(&mut self.t, 11, byte as u16, 1);
}
// cpu writes to 0x2001, PPUMASK
pub fn write_mask(&mut self, byte: u8) {
self.grayscale = byte & (1<<0) != 0;
self.show_background_left = byte & (1<<1) != 0;
self.show_sprites_left = byte & (1<<2) != 0;
self.show_background = byte & (1<<3) != 0;
self.show_sprites = byte & (1<<4) != 0;
self.emphasize_red = byte & (1<<5) != 0;
self.emphasize_blue = byte & (1<<6) != 0;
self.emphasize_green = byte & (1<<7) != 0;
}
// cpu reads ppu status from 0x2002, PPUSTATUS
pub fn read_status(&mut self) -> u8 {
let mut byte = self.recent_bits & 0b0001_1111;
byte |= if self.sprite_overflow { 0b0010_0000 } else {0};
byte |= if self.sprite_zero_hit { 0b0100_0000 } else {0};
byte |= if self.vertical_blank { 0b1000_0000 } else {0};
self.w = 0;
self.vertical_blank = false;
self.nmi_change();
byte
}
// cpu writes to 0x2003, OAMADDR
pub fn write_oam_address(&mut self, addr: usize) {
self.oam_address = addr;
}
// cpu reads from 0x2004, OAMDATA
pub fn read_oam_data(&mut self) -> u8 {
self.primary_oam[self.oam_address]
}
// cpu writes to 0x2004, OAMDATA
pub fn write_oam_data(&mut self, val: u8) {
// Writes will increment OAMADDR after the write
self.primary_oam[self.oam_address] = val;
self.oam_address += 1;
}
// cpu writes to 0x2005, PPUSCROLL
pub fn write_scroll(&mut self, val: u8) {
match self.w { // first write
0 => {
// t: ....... ...HGFED = d: HGFED...
self.t &= !((1<<5)-1); // turn off bottom 5 bits of t
self.t |= val as u16 >> 3; // set bottom 5 bits of t to top 5 bits of d
// x: CBA = d: .....CBA
self.x = val & ((1<<3) - 1);
self.w = 1;
},
1 => { // second write
let d = val as u16;
// t: CBA..HG FED..... = d: HGFEDCBA
set_bit(&mut self.t, 0xC, d, 0x0);
set_bit(&mut self.t, 0xD, d, 0x1);
set_bit(&mut self.t, 0xE, d, 0x2);
set_bit(&mut self.t, 0x5, d, 0x3);
set_bit(&mut self.t, 0x6, d, 0x4);
set_bit(&mut self.t, 0x7, d, 0x5);
set_bit(&mut self.t, 0x8, d, 0x6);
set_bit(&mut self.t, 0x9, d, 0x7);
self.w = 0;
},
_ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w),
}
}
// cpu writes to 0x2006, PPUADDR
pub fn write_address(&mut self, val: u8) {
let d = val as u16;
match self.w { // first write
0 => {
// t: .FEDCBA ........ = d: ..FEDCBA
set_bit(&mut self.t, 0x8, d, 0x0);
set_bit(&mut self.t, 0x9, d, 0x1);
set_bit(&mut self.t, 0xA, d, 0x2);
set_bit(&mut self.t, 0xB, d, 0x3);
set_bit(&mut self.t, 0xC, d, 0x4);
set_bit(&mut self.t, 0xD, d, 0x5);
// t: X...... ........ = 0
set_bit(&mut self.t, 0xF, 0, 0);
self.w = 1;
},
1 => { // second write
// t: ....... HGFEDCBA = d: HGFEDCBA
self.t &= 0xFF00; // mask off bottom byte
self.t += d; // apply d
self.v = self.t; // After t is updated, contents of t copied into v
self.w = 0;
},
_ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w),
}
}
// cpu reads from 0x2007, PPUDATA
pub fn read_data(&mut self) -> u8 {
/*
The PPUDATA read buffer (post-fetch)
When reading while the VRAM address is in the range 0-$3EFF (i.e., before the palettes),
the read will return the contents of an internal read buffer. This internal buffer is
updated only when reading PPUDATA, and so is preserved across frames. After the CPU reads
and gets the contents of the internal buffer, the PPU will immediately update the internal
buffer with the byte at the current VRAM address. Thus, after setting the VRAM address,
one should first read this register and discard the result.
Reading palette data from $3F00-$3FFF works differently. The palette data is placed
immediately on the data bus, and hence no dummy read is required. Reading the palettes
still updates the internal buffer though, but the data placed in it is the mirrored nametable
data that would appear "underneath" the palette. (Checking the PPU memory map should make this clearer.)
So: reading returns value of buffer, not v. Therefore, if v has changed since it was last read, which it
probably has, need to read twice, but that's advice to the programmer, not the emulator developer.
As for 0x3F00 through 0x3FFF, the palette RAM indexes and their mirrors, need to find corresponding nametable?
There are 4 nametables, duplicated once, so 8. There is one palette RAM index, mirrored 7 times, so 8.
So to get from the fifth pallete RAM mirror, which would be 0x3F80, you'd select the 5th nametable,
which would be the first mirrored nametable, 0x3000?
No, just subtract 0x1000. https://forums.nesdev.com/viewtopic.php?f=3&t=18627:
"However, I couldn't find any info on exactly which address should be used to populate the read buffer in this scenario.
From other emulators, it appears to be PPU_ADDR - 0x1000, but I can't really intuit why that is the case."
"It's the case because the majority of the time (that is, on just about every board but GTROM),
video memory $3000-$3FFF mirrors $2000-$2FFF. When PA13 is high ($2000-$3FFF), nothing is listening
to PA12 (the line that distinguishes $0000-$0FFF from $1000-$1FFF and distinguishes $2000-$2FFF from $3000-$3FFF)."
*/
// read value at v, current VRAM address (15 bits)
let mem_val = self.read(self.v as usize);
let ret_val;
match self.v % 0x4000 {
0x0000...0x3EFF => {
ret_val = self.read_buffer;
self.read_buffer = mem_val;
},
0x3F00...0x3FFF => {
ret_val = mem_val;
self.read_buffer = self.read(self.v as usize - 0x1000);
},
_ => panic!("reading from invalid PPU address: 0x{:04x}", self.v),
};
if self.rendering() && (self.scanline < 240 || self.scanline == 261) {
// During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled),
// it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously (with normal wrapping behavior).
self.inc_coarse_x();
self.inc_y();
} else {
// 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;
}
ret_val
}
// cpu writes to 0x2007, PPUDATA
pub fn write_data(&mut self, val: u8) {
self.write(self.v as usize, val);
if self.rendering() && (self.scanline < 240 || self.scanline == 261) {
// During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled),
// it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously (with normal wrapping behavior).
self.inc_coarse_x();
self.inc_y();
} else {
// 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;
}
}
// cpu writes to 0x4014, OAMDATA
pub fn write_oam_dma(&mut self, data: Vec<u8>) {
self.primary_oam = data;
}
}
pub fn set_bit(dest: &mut u16, dest_pos: usize, src: u16, src_pos: usize) {
*dest &= 0xFFFF - (1 << dest_pos); // mask off destination bit
*dest += (if src & (1 << src_pos) == 0 {0} else {1}) << dest_pos; // apply bit from src in src position
}

118
src/ppu/memory.rs Normal file
View File

@ -0,0 +1,118 @@
impl super::Ppu {
pub fn read(&mut self, addr: usize) -> u8 {
let address = addr % 0x4000;
match addr {
0x0000...0x1FFF => {
if self.pattern_tables.len() > 0 {
*(self.mapper_func)(self, address, false).unwrap() // unwrapping because mapper funcs won't return None for reads
} else {
0
}
},
0x2000...0x3EFF => self.read_nametable(address),
0x3F00...0x3FFF => {
let a = address % 0x0020;
let value = self.palette_ram[a];
value
},
_ => 0,
}
}
pub fn write(&mut self, addr: usize, value: u8) {
let address = addr % 0x4000;
match addr {
0x0000...0x1FFF => {
match (self.mapper_func)(self, address, true) {
Some(loc) => *loc = value,
None => (),
}
},
0x2000...0x3EFF => self.write_nametable(address, value),
0x3F00...0x3FFF => {
// I did not read this closely enough for a long time.
// Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C.
// Note that this goes for writing as well as reading.
// A symptom of not having implemented this correctly in an emulator is the sky being black in Super Mario Bros.,
// which writes the backdrop color through $3F10.
match address % 0x10 {
0x00 => {
self.palette_ram[0] = value;
self.palette_ram[0x10] = value;
},
0x04 => {
self.palette_ram[0x04] = value;
self.palette_ram[0x14] = value;
},
0x08 => {
self.palette_ram[0x08] = value;
self.palette_ram[0x18] = value;
},
0x0C => {
self.palette_ram[0x0C] = value;
self.palette_ram[0x1C] = value;
},
_ => self.palette_ram[address % 0x0020] = value,
}
},
_ => (),
}
}
fn read_nametable(&mut self, address: usize) -> u8 {
let base = address % 0x1000;
let offset = base % 0x0400;
if self.mirroring == 0 { // horizontal
match base {
0x0000...0x07FF => {
self.nametable_0[offset]
},
0x0800...0x0FFF => {
self.nametable_2[offset]
},
_ => panic!("panicked writing nametable base: {}", base),
}
} else { // vertical
match base {
0x0000...0x03FF | 0x0800...0x0BFF => {
self.nametable_0[offset]
},
0x0400...0x07FF | 0x0C00...0x0FFF => {
self.nametable_1[offset]
},
_ => panic!("panicked writing nametable base: {}", base),
}
}
}
fn write_nametable(&mut self, address: usize, value: u8) {
let base = address % 0x1000;
let offset = base % 0x0400;
if self.mirroring == 0 { // horizontal
match base {
0x0000...0x07FF => {
self.nametable_0[offset] = value;
self.nametable_1[offset] = value;
},
0x0800...0x0FFF => {
self.nametable_2[offset] = value;
self.nametable_3[offset] = value;
},
_ => panic!("panicked writing nametable base: {}", base),
}
} else { // vertical
match base {
0x0000...0x03FF | 0x0800...0x0BFF => {
self.nametable_0[offset] = value;
self.nametable_2[offset] = value;
},
0x0400...0x07FF | 0x0C00...0x0FFF => {
self.nametable_1[offset] = value;
self.nametable_3[offset] = value;
},
_ => panic!("panicked writing nametable base: {}", base),
}
}
}
}

249
src/ppu/mod.rs Normal file
View File

@ -0,0 +1,249 @@
mod cpu_registers;
mod rendering;
mod memory;
pub struct Ppu {
line_cycle: usize, // x coordinate
scanline: usize, // y coordinate
frame: usize,
// Internal registers
v: u16,
t: u16,
x: u8, // Fine X scroll (3 bits)
w: u8, // First or second write toggle (1 bit)
// Cartridge things
pub pattern_tables: Vec<Vec<u8>>, // CHR-ROM, one 8 KiB chunk for each specified in iNES header
mapper_func: crate::cartridge::PpuMapperFunc,
mirroring: u8, // 0: horizontal, 1: vertical
// Each nametable byte is a reference to the start of an 8-byte sequence in the pattern table.
// That sequence represents an 8x8 tile, from top row to bottom.
nametable_0: Vec<u8>, // Nametable 0, 0x2000
nametable_1: Vec<u8>, // Nametable 1, 0x2400
nametable_2: Vec<u8>, // Nametable 2, 0x2800
nametable_3: Vec<u8>, // Nametable 3, 0x2C00
// The palette shared by both background and sprites.
// Consists of 32 bytes, each of which represents an index into the global PALETTE_TABLE.
// The first 16 bytes are for the background, the second half for the sprites.
palette_ram: Vec<u8>, // Palette RAM indexes.
// Background pattern shift registers and latches
background_pattern_sr_low: u16, // 2 16-bit shift registers - These contain the pattern table data for two tiles. Every 8 cycles, the data for the next tile is loaded
background_pattern_sr_high: u16, // into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits.
nametable_byte: u8, // "The data fetched from these accesses is placed into internal latches,
attribute_table_byte: u8, // and then fed to the appropriate shift registers when it's time to do so
low_pattern_table_byte: u8, // (every 8 cycles)."
high_pattern_table_byte: u8,
// Background palette shift registers and latches
background_palette_sr_low: u8, // 2 8-bit shift registers -
background_palette_sr_high: u8, // These contain the palette attributes for the lower 8 pixels of the 16-bit [pattern/tile] shift register.
background_palette_latch: u8, // These registers are fed by a latch which contains the palette attribute for the next tile. Every 8 cycles,
// the latch is loaded with the palette attribute for the next tile. Because the PPU can only fetch an attribute byte every 8 cycles, each
// sequential string of 8 pixels is forced to have the same palette attribute.
// Sprite memory, shift registers, and latch
pub primary_oam: Vec<u8>, // Primary OAM (holds 64 sprites for the frame)
secondary_oam: Vec<u8>, // Secondary OAM (holds 8 sprites for the current scanline)
sprite_attribute_latches: Vec<u8>, // 8 latches - These contain the attribute bytes [palette data] for up to 8 sprites.
sprite_counters: Vec<u8>, // 8 counters - These contain the X positions for up to 8 sprites.
sprite_indexes: Vec<u8>, // Indexes of the sprites-in-the-attribute-latches' within primary OAM
sprite_pattern_table_srs: Vec<(u8, u8)>, // 8 pairs of 8-bit shift registers - These contain the pattern table data for up to 8 sprites, to be rendered on the current scanline.
// Unused sprites are loaded with an all-transparent set of values.
num_sprites: usize, // Number of sprites in the shift registers for the current scanline
// Various flags set by registers
address_increment: u16,
sprite_pattern_table_base: usize,
background_pattern_table_base: usize,
oam_address: usize,
sprite_size: u8,
grayscale: bool,
show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide
show_sprites_left: bool, // 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
show_background: bool, // 1: Show background
show_sprites: bool, // 1: Show sprites
emphasize_red: bool, // Emphasize red
emphasize_green: bool, // Emphasize green
emphasize_blue: bool, // Emphasize blue
sprite_overflow: bool, // Set if there are more than 8 sprites on a single line
sprite_zero_hit: bool, // Set when the first pixel of the sprite in the zero index of primary OAM is rendered
should_generate_nmi: bool, // Allows CPU to control whether NMIs trigger
vertical_blank: bool, // true == in vertical blank, false == not
// These three: god knows.
// TODO: experiment more with NMI
pub trigger_nmi: bool, // triggers NMI in the CPU when it checks in its step()
previous_nmi: bool,
nmi_delay: usize,
read_buffer: u8, // used with PPUDATA register
pub recent_bits: u8, // Least significant bits previously written into a PPU register
}
impl Ppu {
pub fn new(cart: &super::Cartridge) -> Self {
Ppu {
line_cycle: 0,
scanline: 0,
frame: 0,
v: 0,
t: 0,
x: 0,
w: 0,
pattern_tables: cart.chr_rom.clone(),
mapper_func: cart.ppu_mapper_func,
mirroring: cart.mirroring,
nametable_0: vec![0u8; 0x0400],
nametable_1: vec![0u8; 0x0400],
nametable_2: vec![0u8; 0x0400],
nametable_3: vec![0u8; 0x0400],
palette_ram: vec![0u8; 0x0020],
background_pattern_sr_low: 0,
background_pattern_sr_high: 0,
nametable_byte: 0,
attribute_table_byte: 0,
low_pattern_table_byte: 0,
high_pattern_table_byte: 0,
background_palette_sr_low: 0,
background_palette_sr_high: 0,
background_palette_latch: 0,
primary_oam: vec![0u8; 0x0100],
secondary_oam: vec![0u8; 0x0020],
sprite_attribute_latches: vec![0u8; 8],
sprite_counters: vec![0u8; 8],
sprite_indexes: vec![0u8; 8],
sprite_pattern_table_srs: vec![(0u8, 0u8); 8],
num_sprites: 0,
address_increment: 0,
sprite_pattern_table_base: 0,
background_pattern_table_base: 0,
oam_address: 0,
sprite_size: 0,
grayscale: false,
show_background_left: false,
show_sprites_left: false,
show_background: false,
show_sprites: false,
emphasize_red: false, //
emphasize_green: false, // TODO: implement these
emphasize_blue: false, //
sprite_overflow: false,
sprite_zero_hit: false,
should_generate_nmi: false,
vertical_blank: false,
trigger_nmi: false,
previous_nmi: false,
nmi_delay: 0,
read_buffer: 0,
recent_bits: 0,
}
}
pub fn step(&mut self) -> (Option<(usize, usize, (u8, u8, u8))>, bool) {
if self.nmi_delay > 0 {
self.nmi_delay -= 1;
if self.nmi_delay == 0 && self.should_generate_nmi && self.vertical_blank {
self.trigger_nmi = true;
}
}
let mut pixel: Option<(usize, usize, (u8, u8, u8))> = None;
let rendering = self.rendering();
// Visible scanlines (0-239)
if rendering && (self.scanline < 240 || self.scanline == 261) {
// background-related things
match self.line_cycle {
0 => (), // This is an idle cycle.
1...256 => {
if self.scanline != 261 {
pixel = Some(self.render_pixel());
}
self.load_data_into_registers();
self.shift_registers();
self.perform_memory_fetch();
},
257 => self.copy_horizontal(), // At dot 257 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from t to v
321...336 => {
self.load_data_into_registers();
self.shift_registers();
self.perform_memory_fetch();
},
x if x > 340 => panic!("cycle beyond 340"),
_ => (),
}
}
// sprite-related things
if rendering && self.scanline < 240 {
match self.line_cycle {
1 => self.secondary_oam = vec![0xFF; 0x20],
257 => {
self.evaluate_sprites(); // ignoring all timing details
self.fetch_sprites();
},
321...340 => (), // Read the first byte in secondary OAM (while the PPU fetches the first two background tiles for the next scanline)
_ => (),
}
}
// During dots 280 to 304 of the pre-render scanline (end of vblank)
// If rendering is enabled, at the end of vblank, shortly after the horizontal bits
// are copied from t to v at dot 257, the PPU will repeatedly copy the vertical bits
// from t to v from dots 280 to 304, completing the full initialization of v from t:
if rendering && self.scanline == 261 && self.line_cycle >= 280 && self.line_cycle <= 304 {
self.copy_vertical();
}
// At dot 256 of each scanline, if rendering is enabled, the PPU increments the vertical position in v.
if rendering && self.line_cycle == 256 && (self.scanline < 240 || self.scanline == 261) {
self.inc_y();
}
// v blank
if self.scanline == 241 && self.line_cycle == 1 {
self.vertical_blank = true;
self.nmi_change();
}
if self.scanline == 261 && self.line_cycle == 1 {
self.vertical_blank = false;
self.nmi_change();
self.sprite_zero_hit = false;
self.sprite_overflow = false;
}
// signal time to draw frame
let end_of_frame = self.line_cycle == 256 && self.scanline == 240;
// advance clock
// For odd frames, the cycle at the end of the pre-render scanline is skipped
if self.line_cycle == 339 && self.scanline == 261 && self.frame % 2 != 0 {
self.line_cycle = 0;
self.scanline = 0;
self.frame = self.frame.wrapping_add(1);
// Otherwise, if at the last cycle of the last row of a frame, advance it.
} else if self.line_cycle == 340 && self.scanline == 261 {
self.line_cycle = 0;
self.scanline = 0;
self.frame = self.frame.wrapping_add(1);
// If at a normal line end, advance to next
} else if self.line_cycle == 340 {
self.line_cycle = 0;
self.scanline += 1;
// If none of the above, just go to next cycle in the row
} else {
self.line_cycle += 1;
}
(pixel, end_of_frame)
}
}
const PALETTE_TABLE: [(u8,u8,u8); 64] = [
( 84, 84, 84), ( 0, 30, 116), ( 8, 16, 144), ( 48, 0, 136), ( 68, 0, 100), ( 92, 0, 48), ( 84, 4, 0), ( 60, 24, 0), ( 32, 42, 0), ( 8, 58, 0), ( 0, 64, 0), ( 0, 60, 0), ( 0, 50, 60), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
(152, 150, 152), ( 8, 76, 196), ( 48, 50, 236), ( 92, 30, 228), (136, 20, 176), (160, 20, 100), (152, 34, 32), (120, 60, 0), ( 84, 90, 0), ( 40, 114, 0), ( 8, 124, 0), ( 0, 118, 40), ( 0, 102, 120), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
(236, 238, 236), ( 76, 154, 236), (120, 124, 236), (176, 98, 236), (228, 84, 236), (236, 88, 180), (236, 106, 100), (212, 136, 32), (160, 170, 0), (116, 196, 0), ( 76, 208, 32), ( 56, 204, 108), ( 56, 180, 204), ( 60, 60, 60), ( 0, 0, 0), ( 0, 0, 0),
(236, 238, 236), (168, 204, 236), (188, 188, 236), (212, 178, 236), (236, 174, 236), (236, 174, 212), (236, 180, 176), (228, 196, 144), (204, 210, 120), (180, 222, 120), (168, 226, 144), (152, 226, 180), (160, 214, 228), (160, 162, 160), ( 0, 0, 0), ( 0, 0, 0),
];

323
src/ppu/rendering.rs Normal file
View File

@ -0,0 +1,323 @@
use super::cpu_registers::set_bit;
impl super::Ppu {
pub fn perform_memory_fetch(&mut self) {
match self.line_cycle % 8 {
0 => self.inc_coarse_x(),
1 => self.fetch_nametable_byte(),
3 => self.fetch_attribute_table_byte(),
5 => self.fetch_low_pattern_table_byte(),
7 => self.fetch_high_pattern_table_byte(),
_ => (),
};
}
pub fn shift_registers(&mut self) {
// Shift pattern output registers
self.background_pattern_sr_low <<= 1;
self.background_pattern_sr_high <<= 1;
// Shift the palette shift registers
self.background_palette_sr_low <<= 1;
self.background_palette_sr_high <<= 1;
// Shift bit 0 of the palette attribute latch into the bottom bit of the low palette shift register,
self.background_palette_sr_low |= (self.background_palette_latch & 1 << 0 != 0) as u8;
// and bit 1 of the palette attribute latch into the bottom bit of the high palette shift register
self.background_palette_sr_high |= (self.background_palette_latch & 1 << 1 != 0) as u8;
}
pub fn load_data_into_registers(&mut self) {
if self.line_cycle % 8 == 1 { // The shifters are reloaded during ticks 9, 17, 25, ..., 257.
// These contain the pattern table data for two tiles. Every 8 cycles, the data for the next
// tile is loaded into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits.
self.background_pattern_sr_low |= self.low_pattern_table_byte as u16;
self.background_pattern_sr_high |= self.high_pattern_table_byte as u16;
self.background_palette_latch = self.attribute_table_byte;
}
}
pub fn fetch_nametable_byte(&mut self) {
// nametable address is the bottom 12 bits of v in the 0x2000 range
self.nametable_byte = self.read(0x2000 | (self.v & 0b00001111_11111111) as usize);
}
pub fn fetch_attribute_table_byte(&mut self) {
let address = 0x23C0 | (self.v & 0x0C00) | ((self.v >> 4) & 0x38) | ((self.v >> 2) & 0x07);
let byte = self.read(address as usize);
// figure out which two bits are being represented, ignoring fine x and fine y
// left or right:
let coarse_x = self.v & 0b00000000_00011111;
let coarse_y = (self.v & 0b00000011_11100000) >> 5;
let left_or_right = (coarse_x / 2) % 2; // 0 == left, 1 == right
let top_or_bottom = (coarse_y / 2) % 2; // 0 == top, 1 == bottom
// grab the needed two bits
self.attribute_table_byte = match (top_or_bottom, left_or_right) {
(0,0) => (byte >> 0) & 0b00000011,
(0,1) => (byte >> 2) & 0b00000011,
(1,0) => (byte >> 4) & 0b00000011,
(1,1) => (byte >> 6) & 0b00000011,
_ => panic!("should not get here"),
};
}
pub fn fetch_low_pattern_table_byte(&mut self) {
// pattern table address should be the pattern table base (0x0 or 0x1000)
let mut address = self.background_pattern_table_base;
// plus the value of the nametable byte left-shifted by 4 (which the wiki doesn't really explain)
address += (self.nametable_byte as usize) << 4;
// plus the fine Y scroll
address += ((self.v as usize) >> 12) & 7;
self.low_pattern_table_byte = self.read(address);
}
pub fn fetch_high_pattern_table_byte(&mut self) {
// same as low pattern table byte, but "Fetch the high-order byte of this sliver from an address 8 bytes higher."
let mut address = self.background_pattern_table_base;
address += (self.nametable_byte as usize) << 4;
address += (self.v as usize >> 12) & 7;
self.high_pattern_table_byte = self.read(address + 8);
}
pub fn render_pixel(&mut self) -> (usize, usize, (u8, u8, u8)) {
let (x, y) = (self.line_cycle - 1, self.scanline);
let mut background_pixel = self.select_background_pixel();
let (mut sprite_pixel, current_sprite) = self.select_sprite_pixel();
// extract low and high bits from palette shift registers according to fine x, starting from left
let low_palette_bit = (self.background_palette_sr_low & (1 << (7-self.x)) != 0) as u8;
let high_palette_bit = (self.background_palette_sr_high & (1 << (7-self.x)) != 0) as u8;
let palette_offset = (high_palette_bit << 1) | low_palette_bit;
if x < 8 && !self.show_background_left {
background_pixel = 0;
}
if x < 8 && !self.show_sprites_left {
sprite_pixel = 0;
}
let mut palette_address = 0;
if background_pixel == 0 && sprite_pixel != 0 { // displaying the sprite
palette_address += 0x10; // second half of palette table, "Background/Sprite select"
palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; // bottom two bits of attribute byte, left shifted by two
palette_address += sprite_pixel; // bottom two bits are the value of the sprite pixel from pattern table
} else if background_pixel != 0 && sprite_pixel == 0 { // displaying the background pixel
palette_address += palette_offset << 2; // Palette number from attribute table or OAM
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 self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 { // sprite has high priority
palette_address += 0x10;
palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2;
palette_address += sprite_pixel;
} else {
palette_address += palette_offset << 2;
palette_address += background_pixel;
}
}
// let pixel = self.read(palette_address as usize) as usize;
let pixel = self.palette_ram[palette_address as usize] as usize;
let color: (u8, u8, u8) = super::PALETTE_TABLE[pixel];
(x,y,color)
}
pub fn select_background_pixel(&mut self) -> u8 {
if self.show_background {
// Returned background pixel is a value between 0 and 3.
// the bit from background_pattern_sr_low (low pattern table byte) in the 0th place,
// and the value of the background_pattern_sr_high (high pattern table byte) in the 1st place.
let low_bit = (self.background_pattern_sr_low & (1 << (15 - self.x)) != 0) as u8;
let high_bit = (self.background_pattern_sr_high & (1 << (15 - self.x)) != 0) as u8;
(high_bit << 1) | low_bit
} else {
0
}
}
pub fn select_sprite_pixel(&mut self) -> (u8, usize) {
// Returns (sprite_pixel, index of sprite_pixel within secondary_oam/shift registers)
if self.show_sprites {
// sprite pixel is a value between 0 and 3 representing the two sprite pattern table shift registers
let mut low_bit = 0;
let mut high_bit = 0;
let mut secondary_index = 0;
// Every cycle, the 8 x-position counters for the sprites are decremented by one.
for i in 0..self.num_sprites {
if self.sprite_counters[i] > 0 {
self.sprite_counters[i] -= 1;
}
}
for i in 0..self.num_sprites {
// If the counter is zero, the sprite becomes "active", and the respective pair of shift registers for the sprite is shifted once every cycle.
// This output accompanies the data in the sprite's latch, to form a pixel.
if self.sprite_counters[i] == 0 {
// The current pixel for each "active" sprite is checked (from highest to lowest priority),
// and the first non-transparent pixel moves on to a multiplexer, where it joins the BG pixel.
secondary_index = i;
low_bit = (self.sprite_pattern_table_srs[i].0 & 1<<7 != 0) as u8;
high_bit = (self.sprite_pattern_table_srs[i].1 & 1<<7 != 0) as u8;
if !(low_bit == 0 && high_bit == 0) {
break;
}
}
}
// Have to shift pixels of all sprites with counter 0, whether or not they're the selected pixel. otherwise the pixels get pushed to the right.
for i in 0..self.num_sprites {
if self.sprite_counters[i] == 0 {
self.sprite_pattern_table_srs[i].0 <<= 1;
self.sprite_pattern_table_srs[i].1 <<= 1;
}
}
((high_bit << 1) | low_bit, secondary_index)
} else {
(0, 0)
}
}
pub fn evaluate_sprites(&mut self) {
let mut sprite_count = 0;
for n in 0..64 {
let y_coord = self.primary_oam[(n*4)+0];
if self.y_in_range(y_coord) {
for i in 0..4 {
self.secondary_oam[(sprite_count*4)+i] = self.primary_oam[(n*4)+i];
}
self.sprite_indexes[sprite_count] = n as u8;
sprite_count += 1;
} else {
// TODO: sprite evaluation bug
}
if sprite_count == 8 {
break;
}
}
self.num_sprites = sprite_count;
}
pub fn fetch_sprites(&mut self) {
for i in 0..self.num_sprites {
let mut address: usize;
let sprite_y_position = self.secondary_oam[(4*i)+0] as usize; // byte 0 of sprite, sprite's vertical position on screen
let sprite_tile_index = self.secondary_oam[(4*i)+1] as usize; // byte 1 of sprite, sprite's location within pattern table
let sprite_attributes = self.secondary_oam[(4*i)+2]; // byte 2 of sprite, sprite's palette, priority, and flip attributes
let sprite_x_position = self.secondary_oam[(4*i)+3] + 1; // byte 3 of sprite, sprite's horizontal position on screen
// For 8x8 sprites, this is the tile number of this sprite within the pattern table selected in bit 3 of PPUCTRL ($2000).
if self.sprite_size == 8 {
address = self.sprite_pattern_table_base;
address += sprite_tile_index*16;
// For 8x16 sprites, the PPU ignores the pattern table selection and selects a pattern table from bit 0 of this number.
} else {
address = if sprite_tile_index & 1 == 0 { 0x0 } else { 0x1000 };
address += (sprite_tile_index*16) & (0xFF - 1); // turn off bottom bit
}
let fine_y: usize;
// Handle vertical and horizontal flips, then write to shift registers
if sprite_attributes & (1<<7) == 0 { // if vertical flip bit not set
fine_y = self.scanline - sprite_y_position; // row-within-sprite offset is difference between current scanline and top of sprite
} else { // if flipped vertically
fine_y = self.sprite_size as usize - (self.scanline - sprite_y_position);
}
address += fine_y;
let low_pattern_table_byte = self.read(address);
let high_pattern_table_byte = self.read(address + 8);
let mut shift_reg_vals = (0, 0);
for j in 0..8 {
let current_bits = (low_pattern_table_byte & (1 << j), high_pattern_table_byte & (1 << j));
if sprite_attributes & (1<<6) == 0 { // if horizontal flip bit not set
// just copy each bit in same order
shift_reg_vals.0 |= current_bits.0;
shift_reg_vals.1 |= current_bits.1;
} else { // if flipped horizontally
// get bit of pattern table byte, left shift it by 7 - bit position
shift_reg_vals.0 |= ((current_bits.0 != 0) as u8) << (7 - j);
shift_reg_vals.1 |= ((current_bits.1 != 0) as u8) << (7 - j);
}
}
// put pattern table bytes into the shift registers, ready to be rendered
self.sprite_pattern_table_srs[i] = shift_reg_vals;
// In addition to this, the X positions and attributes for each sprite are loaded from the secondary OAM into their respective counters/latches.
// This happens during the second garbage nametable fetch, with the attribute byte loaded during the first tick and the X coordinate during the second.
self.sprite_attribute_latches[i] = sprite_attributes;
self.sprite_counters[i] = sprite_x_position;
}
}
pub fn inc_coarse_x(&mut self) {
if self.v & 0x001F == 0x001F { // if coarse X == 31
self.v &= !0x001F; // coarse X = 0
self.v ^= 1<<10; // switch horizontal nametable
} else {
self.v += 1;
}
}
pub fn inc_y(&mut self) {
// If rendering is enabled, fine Y is incremented at dot 256 of each scanline,
// overflowing to coarse Y, and finally adjusted to wrap among the nametables vertically.
let mut fine_y = (self.v & 0b01110000_00000000) >> 12;
let mut coarse_y = (self.v & 0b00000011_11100000) >> 5;
if fine_y < 7 {
fine_y += 1;
} else {
fine_y = 0;
// Row 29 is the last row of tiles in a nametable. To wrap to the next nametable when
// incrementing coarse Y from 29, the vertical nametable is switched by toggling bit
// 11, and coarse Y wraps to row 0.
if coarse_y == 29 {
self.v ^= 1<<11;
coarse_y = 0;
// Coarse Y can be set out of bounds (> 29), which will cause the PPU to read the
// attribute data stored there as tile data. If coarse Y is incremented from 31,
// it will wrap to 0, but the nametable will not switch.
} else if coarse_y == 32 {
coarse_y = 0;
} else {
coarse_y += 1;
}
}
// set resulting coarse y
set_bit(&mut self.v, 0x5, coarse_y, 0x0);
set_bit(&mut self.v, 0x6, coarse_y, 0x1);
set_bit(&mut self.v, 0x7, coarse_y, 0x2);
set_bit(&mut self.v, 0x8, coarse_y, 0x3);
set_bit(&mut self.v, 0x9, coarse_y, 0x4);
// and fine y
set_bit(&mut self.v, 0xC, fine_y, 0x0);
set_bit(&mut self.v, 0xD, fine_y, 0x1);
set_bit(&mut self.v, 0xE, fine_y, 0x2);
}
pub fn copy_horizontal(&mut self) {
// v: ....F.. ...EDCBA = t: ....F.. ...EDCBA
let mask = 0b00000100_00011111;
let t_vals = self.t & mask; // grab bits of t
self.v &= !mask; // turn off bits of v
self.v |= t_vals; // apply bits of t
}
pub fn copy_vertical(&mut self) {
// v: IHGF.ED CBA..... = t: IHGF.ED CBA.....
let mask = 0b01111011_11100000;
let t_vals = self.t & mask;
self.v &= !mask;
self.v |= t_vals;
}
pub fn rendering(&self) -> bool {
(self.show_background || self.show_sprites)
}
pub fn y_in_range(&self, y_coord: u8) -> bool {
self.scanline >= (y_coord as usize) &&
self.scanline - (y_coord as usize) < self.sprite_size as usize
}
pub fn nmi_change(&mut self) {
let nmi = self.should_generate_nmi && self.vertical_blank;
if nmi && !self.previous_nmi {
self.nmi_delay = 1;
}
self.previous_nmi = nmi;
}
}

39
src/screen/mod.rs Normal file
View File

@ -0,0 +1,39 @@
extern crate sdl2;
use sdl2::Sdl;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
const SCALE_FACTOR: usize = 4;
type RGBColor = (u8, u8, u8);
pub struct Screen {
pub canvas: Canvas<Window>,
}
impl Screen {
pub fn new(context: &Sdl) -> Result<Self, String> {
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok(Screen{canvas})
}
pub fn draw_pixel(&mut self, x: usize, y: usize, color: RGBColor) -> Result<(), String> {
let (r, g, b) = color;
self.canvas.set_draw_color(Color::RGB(r, g, b));
self.canvas.fill_rect(Rect::new((x * SCALE_FACTOR) as i32, (y * SCALE_FACTOR) as i32,
SCALE_FACTOR as u32, SCALE_FACTOR as u32))?;
Ok(())
}
}