commit 9768a71474f3c0af9f1f348d2a832ce94a849932 Author: Steven Hugg Date: Wed Jan 15 00:05:42 2014 -0500 first cut of cpu + apple i/o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2bfd360 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ + +test: + rustc -o appletest --test apple.rs + ./appletest + + diff --git a/a2.rs b/a2.rs new file mode 100644 index 0000000..dc0d0e3 --- /dev/null +++ b/a2.rs @@ -0,0 +1,239 @@ + +use mem::Mem; +use util::Xorshift; + +static GR_TXMODE: int = 1; +static GR_MIXMODE: int = 2; +static GR_PAGE1: int = 4; +static GR_HIRES: int = 8; +static DBG_CPU: int = 1; +static DBG_RDMEM: int = 2; +static DBG_WRMEM: int = 4; + +static HW_LO: u16 = 0xC000; +static ROM_LO: u16 = 0xD000; +static ROM_LEN: u16 = 0x3000; + +pub trait Peripheral +{ + fn doIO(&mut self, addr: u16, val: u8) -> u8; + fn doHighIO(&mut self, addr: u16, val: u8) -> u8; +} + +struct LangCardState +{ + // language card switches + auxRAMselected: bool, + auxRAMbank: u8, + writeinhibit: bool, + + // value to add when reading & writing each of these banks + // bank 1 is D000-FFFF, bank 2 is D000-DFFF + bank1rdoffset: int, + bank2rdoffset: int, + bank1wroffset: int, + bank2wroffset: int, +} + +impl LangCardState +{ + fn new(auxRAMselected: bool, auxRAMbank: u8, writeinhibit: bool) -> LangCardState { + LangCardState { + auxRAMselected: auxRAMselected, + auxRAMbank: auxRAMbank, + writeinhibit: writeinhibit, + // reset language card constants + // 0x3000 = map 0xd000-0xffff -> 0x10000-0x12fff + // -0x1000 = map 0xd000-0xdfff -> 0xc000-0xcfff + bank1rdoffset: if auxRAMselected { 0x3000 } else { 0x0 }, + bank2rdoffset: if auxRAMselected { if auxRAMbank==2 { -0x1000 } else { 0x3000 } } else { 0x0 }, + bank1wroffset: if !writeinhibit { 0x3000 } else { 0x0 }, + bank2wroffset: if !writeinhibit { if auxRAMbank==2 { -0x1000 } else { 0x3000 } } else { 0x0 }, + } + } +} + +pub struct AppleII +{ + mem: [u8, ..0x13000], + + slots: [Option<~Peripheral>, ..8], + + debugflags: int, + kbdlatch: u8, + grswitch: u16, + soundstate: bool, + aux: LangCardState, + nreads: u16 // counts # of reads for noise() fn +} + +impl Mem for AppleII +{ + fn loadb(&mut self, addr: u16) -> u8 + { + debug!("Read {:x}", addr); + self.nreads += 1; + let val = + // see if it's from main memory (0x0000-0xbfff) + if (addr < HW_LO) { + self.mem[addr] & 0xff + // see if it came from the ROM/LC area (0xd000-0xffff) + } else if (addr >= ROM_LO) { + if (addr >= 0xe000) { + self.mem[addr as int + self.aux.bank1rdoffset] & 0xff + } else { + self.mem[addr as int + self.aux.bank2rdoffset] & 0xff + } + } + // it must be an I/O location (0xc000-0xcfff) + else if (addr < HW_LO + 0x100) { + self.doIO(addr, 0) + } else { + match self.slots[(addr >> 8) & 7] { + None => self.noise(), + Some(ref mut p) => p.doHighIO(addr, 0) // TODO: maybe have optional value, or new method + } + }; + debug!("Read {:x} = {:x}", addr, val); + return val; + } + + fn storeb(&mut self, addr: u16, val: u8) + { + debug!("write {:x} = {:x}", addr, val); + // see if it's from main memory (0x0000-0xbfff) + if (addr < HW_LO) + { + self.mem[addr] = val; + //dirty[addr >> 7] = true; + } + // see if it came from the ROM/LC area (0xd000-0xffff) + else if (addr >= ROM_LO && /* auxRAMselected && */ !self.aux.writeinhibit) + { + if (addr >= 0xe000) { + self.mem[addr as int + self.aux.bank1wroffset] = val; + } else { + self.mem[addr as int + self.aux.bank2wroffset] = val; + } + } + // it must be an I/O location (0xc000-0xcfff) + else if (addr < HW_LO + 0x100) { + self.doIO(addr, val); + } else { + match self.slots[(addr >> 8) & 7] { + None => (), // no-op + Some(ref mut p) => { p.doHighIO(addr, val); } + } + } + } +} + +impl AppleII +{ + pub fn new() -> AppleII { AppleII { + mem: [ 0, ..0x13000 ], + // TODO: slots: [ None, ..8 ], + // https://gist.github.com/carl-eastlund/6264938 + slots: [ None, None, None, None, None, None, None, None ], + aux: LangCardState::new(false, 1, true), + debugflags: 0, + kbdlatch: 0, + grswitch: 0, + soundstate: false, + nreads: 0 + } } + + fn noise(&mut self) -> u8 { self.mem[self.nreads & 0xffff] } + + fn setGrSwitch(&mut self, addr: u16) + { + // graphics + if ((addr & 1) != 0) { + self.grswitch |= 1 << ((addr >> 1) & 0x07); + } else { + self.grswitch &= !(1 << ((addr >> 1) & 0x07)); + } + debug!("switch {} grswitch = {}", addr, self.grswitch); + } + + fn setAnnunciator(&mut self, addr: u16) + { + // nothing yet + } + + fn fakeJoystick(&mut self, addr: u16) -> u8 + { + // tapein, joystick, buttons + match addr & 7 { + 1..3 => self.noise() & 0x7f, // buttons (off) + 4..5 => self.noise() | 0x80, // joystick + _ => self.noise() + } + } + + fn doIO(&mut self, addr: u16, val: u8) -> u8 + { + let slot = (addr >> 4) & 0x0f; + match slot { + 0 => self.kbdlatch, // keyboard + 1 => { self.clearStrobe(); self.noise() } // reset kbd strobe + 3 => { self.soundstate = !self.soundstate; self.noise() } // speaker + 5 => { if ((addr & 0x0f) < 8) { self.setGrSwitch(addr); } else { self.setAnnunciator(addr); } self.noise() } + 6 => self.fakeJoystick(addr), + 7 if (addr == 0xc070) => self.noise() | 0x80, // joystick reset + 8 => { self.doLanguageCardIO(addr); self.noise() } + 9..15 => match self.slots[slot-8] { + None => self.noise(), + Some(ref mut p) => p.doIO(addr, val) + }, + _ => self.noise() + } + } + + fn clearStrobe(&mut self) + { + self.kbdlatch &= 0x7f; + debug!("Clear strobe"); + } + + pub fn keyPressed(&mut self, keycode: u8) + { + let mut key = (keycode | 0x80) & 0xff; + // since we're an Apple II+, we don't do lowercase + if (key >= 0xe1 && key <= 0xfa) { key -= 0x20; } + self.kbdlatch = key; + debug!("Key pressed: {}", key); + } + + fn doLanguageCardIO(&mut self, addr:u16) + { + self.aux = match addr & 0xf + { + // Select aux RAM bank 2, write protected. + 0|4 => LangCardState::new(true, 2, true), + // Select ROM, write enable aux RAM bank 2. + 1|5 => LangCardState::new(false, 2, false), + // Select ROM, write protect aux RAM (either bank). + 2|6|10|14 => LangCardState::new(false, self.aux.auxRAMbank, true), + // Select aux RAM bank 2, write enabled. + 3|7 => LangCardState::new(true, 2, false), + // Select aux RAM bank 1, write protected. + 8|12 => LangCardState::new(true, 1, false), + // Select ROM, write enable aux RAM bank 1. + 9|13 => LangCardState::new(false, 1, false), + // Select aux RAM bank 1, write enabled. + 11|15 => LangCardState::new(true, 1, false), + // TODO: shouldn't need this + _ => fail!() + } + } + + pub fn read_roms(&mut self) + { + use std::io::File; + use std::vec::bytes::copy_memory; + let ap2rom = File::open(&Path::new("apple2.rom")).read_bytes(0x3000); + copy_memory(self.mem.mut_slice(0xd000, 0xd000+0x3000), ap2rom); + debug!("loaded apple2.rom"); + } +} diff --git a/apple.rs b/apple.rs new file mode 100644 index 0000000..878d3f3 --- /dev/null +++ b/apple.rs @@ -0,0 +1,31 @@ +// +// sprocketnes/nes.rs +// +// Author: Patrick Walton +// + +#[feature(link_args, macro_rules)]; +//#[no_main]; + +//extern mod native; +//extern mod sdl; + +// NB: This must be first to pick up the macro definitions. What a botch. +#[macro_escape] +pub mod util; + +#[macro_escape] +pub mod cpu; +pub mod mem; +pub mod a2; +//pub mod diskii; + +mod tests; + +/* +#[no_mangle] +pub extern "C" fn SDL_main(argc: i32, argv: **u8) -> i32 { + native::start(argc as int, argv, proc() main::start(argc, argv)) as i32 +} + +*/ \ No newline at end of file diff --git a/cpu.rs b/cpu.rs new file mode 100644 index 0000000..438f509 --- /dev/null +++ b/cpu.rs @@ -0,0 +1,726 @@ +// +// sprocketnes/cpu.rs +// +// Author: Patrick Walton +// + +use mem::{Mem, MemUtil}; + +// +// Constants +// + +static CARRY_FLAG: u8 = 1 << 0; +static ZERO_FLAG: u8 = 1 << 1; +static IRQ_FLAG: u8 = 1 << 2; +static DECIMAL_FLAG: u8 = 1 << 3; +static BREAK_FLAG: u8 = 1 << 4; +static OVERFLOW_FLAG: u8 = 1 << 6; +static NEGATIVE_FLAG: u8 = 1 << 7; + +static NMI_VECTOR: u16 = 0xfffa; +static RESET_VECTOR: u16 = 0xfffc; +static BRK_VECTOR: u16 = 0xfffe; + +/// The number of cycles that each machine operation takes. Indexed by opcode number. +/// +/// FIXME: This is copied from FCEU. + +static CYCLE_TABLE: [u8, ..256] = [ + /*0x00*/ 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6, + /*0x10*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + /*0x20*/ 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6, + /*0x30*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + /*0x40*/ 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6, + /*0x50*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + /*0x60*/ 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6, + /*0x70*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + /*0x80*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, + /*0x90*/ 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5, + /*0xA0*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, + /*0xB0*/ 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4, + /*0xC0*/ 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, + /*0xD0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + /*0xE0*/ 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6, + /*0xF0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, +]; + +// +// Registers +// + +struct Regs { + a: u8, + x: u8, + y: u8, + s: u8, + flags: u8, + pc: u16 +} + +impl Regs { + fn new() -> Regs { Regs { a: 0, x: 0, y: 0, s: 0xfd, flags: 0x24, pc: 0xc000 } } +} + +// +// Addressing modes +// + +trait AddressingMode { + fn load(&self, cpu: &mut Cpu) -> u8; + fn store(&self, cpu: &mut Cpu, val: u8); +} + +struct AccumulatorAddressingMode; +impl AddressingMode for AccumulatorAddressingMode { + fn load(&self, cpu: &mut Cpu) -> u8 { cpu.regs.a } + fn store(&self, cpu: &mut Cpu, val: u8) { cpu.regs.a = val } +} + +struct ImmediateAddressingMode; +impl AddressingMode for ImmediateAddressingMode { + fn load(&self, cpu: &mut Cpu) -> u8 { cpu.loadb_bump_pc() } + fn store(&self, _: &mut Cpu, _: u8) { + // Not particularly type-safe, but probably not worth using trait inheritance for this. + fail!("can't store to immediate") + } +} + +struct MemoryAddressingMode(u16); +impl AddressingMode for MemoryAddressingMode { + fn load(&self, cpu: &mut Cpu) -> u8 { match *self { MemoryAddressingMode(addr) => cpu.loadb(addr) } } + fn store(&self, cpu: &mut Cpu, val: u8) { match *self { MemoryAddressingMode(addr) => cpu.storeb(addr, val) } } +} + +// +// Opcode decoding +// +// This is implemented as a macro so that both the disassembler and the emulator can use it. +// + +macro_rules! decode_op { + ($op:expr, $this:ident) => { + // We try to keep this in the same order as the implementations above. + // TODO: Use arm macros to fix some of this duplication. + match $op { + // Loads + 0xa1 => { let v = $this.indexed_indirect_x(); $this.lda(v) } + 0xa5 => { let v = $this.zero_page(); $this.lda(v) } + 0xa9 => { let v = $this.immediate(); $this.lda(v) } + 0xad => { let v = $this.absolute(); $this.lda(v) } + 0xb1 => { let v = $this.indirect_indexed_y(); $this.lda(v) } + 0xb5 => { let v = $this.zero_page_x(); $this.lda(v) } + 0xb9 => { let v = $this.absolute_y(); $this.lda(v) } + 0xbd => { let v = $this.absolute_x(); $this.lda(v) } + + 0xa2 => { let v = $this.immediate(); $this.ldx(v) } + 0xa6 => { let v = $this.zero_page(); $this.ldx(v) } + 0xb6 => { let v = $this.zero_page_y(); $this.ldx(v) } + 0xae => { let v = $this.absolute(); $this.ldx(v) } + 0xbe => { let v = $this.absolute_y(); $this.ldx(v) } + + 0xa0 => { let v = $this.immediate(); $this.ldy(v) } + 0xa4 => { let v = $this.zero_page(); $this.ldy(v) } + 0xb4 => { let v = $this.zero_page_x(); $this.ldy(v) } + 0xac => { let v = $this.absolute(); $this.ldy(v) } + 0xbc => { let v = $this.absolute_x(); $this.ldy(v) } + + // Stores + 0x85 => { let v = $this.zero_page(); $this.sta(v) } + 0x95 => { let v = $this.zero_page_x(); $this.sta(v) } + 0x8d => { let v = $this.absolute(); $this.sta(v) } + 0x9d => { let v = $this.absolute_x(); $this.sta(v) } + 0x99 => { let v = $this.absolute_y(); $this.sta(v) } + 0x81 => { let v = $this.indexed_indirect_x(); $this.sta(v) } + 0x91 => { let v = $this.indirect_indexed_y(); $this.sta(v) } + + 0x86 => { let v = $this.zero_page(); $this.stx(v) } + 0x96 => { let v = $this.zero_page_y(); $this.stx(v) } + 0x8e => { let v = $this.absolute(); $this.stx(v) } + + 0x84 => { let v = $this.zero_page(); $this.sty(v) } + 0x94 => { let v = $this.zero_page_x(); $this.sty(v) } + 0x8c => { let v = $this.absolute(); $this.sty(v) } + + // Arithmetic + 0x69 => { let v = $this.immediate(); $this.adc(v) } + 0x65 => { let v = $this.zero_page(); $this.adc(v) } + 0x75 => { let v = $this.zero_page_x(); $this.adc(v) } + 0x6d => { let v = $this.absolute(); $this.adc(v) } + 0x7d => { let v = $this.absolute_x(); $this.adc(v) } + 0x79 => { let v = $this.absolute_y(); $this.adc(v) } + 0x61 => { let v = $this.indexed_indirect_x(); $this.adc(v) } + 0x71 => { let v = $this.indirect_indexed_y(); $this.adc(v) } + + 0xe9 => { let v = $this.immediate(); $this.sbc(v) } + 0xe5 => { let v = $this.zero_page(); $this.sbc(v) } + 0xf5 => { let v = $this.zero_page_x(); $this.sbc(v) } + 0xed => { let v = $this.absolute(); $this.sbc(v) } + 0xfd => { let v = $this.absolute_x(); $this.sbc(v) } + 0xf9 => { let v = $this.absolute_y(); $this.sbc(v) } + 0xe1 => { let v = $this.indexed_indirect_x(); $this.sbc(v) } + 0xf1 => { let v = $this.indirect_indexed_y(); $this.sbc(v) } + + // Comparisons + 0xc9 => { let v = $this.immediate(); $this.cmp(v) } + 0xc5 => { let v = $this.zero_page(); $this.cmp(v) } + 0xd5 => { let v = $this.zero_page_x(); $this.cmp(v) } + 0xcd => { let v = $this.absolute(); $this.cmp(v) } + 0xdd => { let v = $this.absolute_x(); $this.cmp(v) } + 0xd9 => { let v = $this.absolute_y(); $this.cmp(v) } + 0xc1 => { let v = $this.indexed_indirect_x(); $this.cmp(v) } + 0xd1 => { let v = $this.indirect_indexed_y(); $this.cmp(v) } + + 0xe0 => { let v = $this.immediate(); $this.cpx(v) } + 0xe4 => { let v = $this.zero_page(); $this.cpx(v) } + 0xec => { let v = $this.absolute(); $this.cpx(v) } + + 0xc0 => { let v = $this.immediate(); $this.cpy(v) } + 0xc4 => { let v = $this.zero_page(); $this.cpy(v) } + 0xcc => { let v = $this.absolute(); $this.cpy(v) } + + // Bitwise operations + 0x29 => { let v = $this.immediate(); $this.and(v) } + 0x25 => { let v = $this.zero_page(); $this.and(v) } + 0x35 => { let v = $this.zero_page_x(); $this.and(v) } + 0x2d => { let v = $this.absolute(); $this.and(v) } + 0x3d => { let v = $this.absolute_x(); $this.and(v) } + 0x39 => { let v = $this.absolute_y(); $this.and(v) } + 0x21 => { let v = $this.indexed_indirect_x(); $this.and(v) } + 0x31 => { let v = $this.indirect_indexed_y(); $this.and(v) } + + 0x09 => { let v = $this.immediate(); $this.ora(v) } + 0x05 => { let v = $this.zero_page(); $this.ora(v) } + 0x15 => { let v = $this.zero_page_x(); $this.ora(v) } + 0x0d => { let v = $this.absolute(); $this.ora(v) } + 0x1d => { let v = $this.absolute_x(); $this.ora(v) } + 0x19 => { let v = $this.absolute_y(); $this.ora(v) } + 0x01 => { let v = $this.indexed_indirect_x(); $this.ora(v) } + 0x11 => { let v = $this.indirect_indexed_y(); $this.ora(v) } + + 0x49 => { let v = $this.immediate(); $this.eor(v) } + 0x45 => { let v = $this.zero_page(); $this.eor(v) } + 0x55 => { let v = $this.zero_page_x(); $this.eor(v) } + 0x4d => { let v = $this.absolute(); $this.eor(v) } + 0x5d => { let v = $this.absolute_x(); $this.eor(v) } + 0x59 => { let v = $this.absolute_y(); $this.eor(v) } + 0x41 => { let v = $this.indexed_indirect_x(); $this.eor(v) } + 0x51 => { let v = $this.indirect_indexed_y(); $this.eor(v) } + + 0x24 => { let v = $this.zero_page(); $this.bit(v) } + 0x2c => { let v = $this.absolute(); $this.bit(v) } + + // Shifts and rotates + 0x2a => { let v = $this.accumulator(); $this.rol(v) } + 0x26 => { let v = $this.zero_page(); $this.rol(v) } + 0x36 => { let v = $this.zero_page_x(); $this.rol(v) } + 0x2e => { let v = $this.absolute(); $this.rol(v) } + 0x3e => { let v = $this.absolute_x(); $this.rol(v) } + + 0x6a => { let v = $this.accumulator(); $this.ror(v) } + 0x66 => { let v = $this.zero_page(); $this.ror(v) } + 0x76 => { let v = $this.zero_page_x(); $this.ror(v) } + 0x6e => { let v = $this.absolute(); $this.ror(v) } + 0x7e => { let v = $this.absolute_x(); $this.ror(v) } + + 0x0a => { let v = $this.accumulator(); $this.asl(v) } + 0x06 => { let v = $this.zero_page(); $this.asl(v) } + 0x16 => { let v = $this.zero_page_x(); $this.asl(v) } + 0x0e => { let v = $this.absolute(); $this.asl(v) } + 0x1e => { let v = $this.absolute_x(); $this.asl(v) } + + 0x4a => { let v = $this.accumulator(); $this.lsr(v) } + 0x46 => { let v = $this.zero_page(); $this.lsr(v) } + 0x56 => { let v = $this.zero_page_x(); $this.lsr(v) } + 0x4e => { let v = $this.absolute(); $this.lsr(v) } + 0x5e => { let v = $this.absolute_x(); $this.lsr(v) } + + // Increments and decrements + 0xe6 => { let v = $this.zero_page(); $this.inc(v) } + 0xf6 => { let v = $this.zero_page_x(); $this.inc(v) } + 0xee => { let v = $this.absolute(); $this.inc(v) } + 0xfe => { let v = $this.absolute_x(); $this.inc(v) } + + 0xc6 => { let v = $this.zero_page(); $this.dec(v) } + 0xd6 => { let v = $this.zero_page_x(); $this.dec(v) } + 0xce => { let v = $this.absolute(); $this.dec(v) } + 0xde => { let v = $this.absolute_x(); $this.dec(v) } + + 0xe8 => $this.inx(), + 0xca => $this.dex(), + 0xc8 => $this.iny(), + 0x88 => $this.dey(), + + // Register moves + 0xaa => $this.tax(), + 0xa8 => $this.tay(), + 0x8a => $this.txa(), + 0x98 => $this.tya(), + 0x9a => $this.txs(), + 0xba => $this.tsx(), + + // Flag operations + 0x18 => $this.clc(), + 0x38 => $this.sec(), + 0x58 => $this.cli(), + 0x78 => $this.sei(), + 0xb8 => $this.clv(), + 0xd8 => $this.cld(), + 0xf8 => $this.sed(), + + // Branches + 0x10 => $this.bpl(), + 0x30 => $this.bmi(), + 0x50 => $this.bvc(), + 0x70 => $this.bvs(), + 0x90 => $this.bcc(), + 0xb0 => $this.bcs(), + 0xd0 => $this.bne(), + 0xf0 => $this.beq(), + + // Jumps + 0x4c => $this.jmp(), + 0x6c => $this.jmpi(), + + // Procedure calls + 0x20 => $this.jsr(), + 0x60 => $this.rts(), + 0x00 => $this.brk(), + 0x40 => $this.rti(), + + // Stack operations + 0x48 => $this.pha(), + 0x68 => $this.pla(), + 0x08 => $this.php(), + 0x28 => $this.plp(), + + // No operation + 0xea => $this.nop(), + + _ => fail!("unimplemented or illegal instruction") + } + } +} + +// +// Main CPU implementation +// + +type Cycles = u64; + +/// The main CPU structure definition. +pub struct Cpu { + cy: Cycles, + regs: Regs, + mem: M, +} + +// The CPU implements Mem so that it can handle writes to the DMA register. +impl Mem for Cpu { + fn loadb(&mut self, addr: u16) -> u8 { self.mem.loadb(addr) } + fn storeb(&mut self, addr: u16, val: u8) { + // Handle OAM_DMA. + if addr == 0x4014 { + self.dma(val) + } else { + self.mem.storeb(addr, val) + } + } +} + +impl Cpu { + // Debugging + #[cfg(cpuspew)] + fn trace(&mut self) { + let mut disassembler = Disassembler { + pc: self.regs.pc, + mem: &mut self.mem + }; + println!( + "%04X %-20s A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%4u", + self.regs.pc as uint, + disassembler.disassemble(), + self.regs.a as uint, + self.regs.x as uint, + self.regs.y as uint, + self.regs.flags as uint, + self.regs.s as uint, + self.cy as uint + ); + } + #[cfg(not(cpuspew))] + fn trace(&mut self) {} + + // Performs DMA to the OAMDATA ($2004) register. + fn dma(&mut self, hi_addr: u8) { + for addr in range(hi_addr as uint << 8, (hi_addr + 1) as uint << 8) { + let val = self.loadb(addr as u16); + self.storeb(0x2004, val); + + // FIXME: The last address sometimes takes 1 cycle, sometimes 2 -- NESdev isn't very + // clear on this. + self.cy += 2; + } + } + + // Memory access helpers + /// Loads the byte at the program counter and increments the program counter. + fn loadb_bump_pc(&mut self) -> u8 { + let val = self.loadb(self.regs.pc); + self.regs.pc += 1; + val + } + /// Loads two bytes (little-endian) at the program counter and bumps the program counter over + /// them. + fn loadw_bump_pc(&mut self) -> u16 { + let val = self.loadw(self.regs.pc); + self.regs.pc += 2; + val + } + + // Stack helpers + fn pushb(&mut self, val: u8) { + self.storeb(0x100 + self.regs.s as u16, val); + self.regs.s -= 1; + } + fn pushw(&mut self, val: u16) { + // FIXME: Is this correct? FCEU has two self.storeb()s here. Might have different + // semantics... + self.storew(0x100 + (self.regs.s - 1) as u16, val); + self.regs.s -= 2; + } + fn popb(&mut self) -> u8 { + let val = self.loadb(0x100 + self.regs.s as u16 + 1); + self.regs.s += 1; + val + } + fn popw(&mut self) -> u16 { + // FIXME: See comment in pushw(). + let val = self.loadw(0x100 + self.regs.s as u16 + 1); + self.regs.s += 2; + val + } + + // Flag helpers + fn get_flag(&self, flag: u8) -> bool { (self.regs.flags & flag) != 0 } + fn set_flag(&mut self, flag: u8, on: bool) { + if on { + self.regs.flags |= flag; + } else { + self.regs.flags &= !flag; + } + } + fn set_flags(&mut self, val: u8) { + // Flags get munged in a strange way relating to the unused bit 5 on the NES. + self.regs.flags = (val | 0x30) - 0x10; + } + fn set_zn(&mut self, val: u8) -> u8 { + self.set_flag(ZERO_FLAG, val == 0); + self.set_flag(NEGATIVE_FLAG, (val & 0x80) != 0); + val + } + + // Addressing modes + fn immediate(&mut self) -> ImmediateAddressingMode { ImmediateAddressingMode } + fn accumulator(&mut self) -> AccumulatorAddressingMode { AccumulatorAddressingMode } + fn zero_page(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode(self.loadb_bump_pc() as u16) + } + fn zero_page_x(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode((self.loadb_bump_pc() + self.regs.x) as u16) + } + fn zero_page_y(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode((self.loadb_bump_pc() + self.regs.y) as u16) + } + fn absolute(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode(self.loadw_bump_pc()) + } + fn absolute_x(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode(self.loadw_bump_pc() + self.regs.x as u16) + } + fn absolute_y(&mut self) -> MemoryAddressingMode { + MemoryAddressingMode(self.loadw_bump_pc() + self.regs.y as u16) + } + fn indexed_indirect_x(&mut self) -> MemoryAddressingMode { + let val = self.loadb_bump_pc(); + let addr = self.loadw_zp(val + self.regs.x); + MemoryAddressingMode(addr) + } + fn indirect_indexed_y(&mut self) -> MemoryAddressingMode { + let val = self.loadb_bump_pc(); + let addr = self.loadw_zp(val) + self.regs.y as u16; + MemoryAddressingMode(addr) + } + + // + // Instructions + // + + // Loads + fn lda>(&mut self, am: AM) { + let val = am.load(self); + self.regs.a = self.set_zn(val) + } + fn ldx>(&mut self, am: AM) { + let val = am.load(self); + self.regs.x = self.set_zn(val) + } + fn ldy>(&mut self, am: AM) { + let val = am.load(self); + self.regs.y = self.set_zn(val) + } + + // Stores + fn sta>(&mut self, am: AM) { am.store(self, self.regs.a) } + fn stx>(&mut self, am: AM) { am.store(self, self.regs.x) } + fn sty>(&mut self, am: AM) { am.store(self, self.regs.y) } + + // Arithmetic + #[inline(always)] + fn adc>(&mut self, am: AM) { + let val = am.load(self); + let mut result = self.regs.a as u32 + val as u32; + if self.get_flag(CARRY_FLAG) { + result += 1; + } + + self.set_flag(CARRY_FLAG, (result & 0x100) != 0); + + let result = result as u8; + self.set_flag(OVERFLOW_FLAG, + (self.regs.a ^ val) & 0x80 == 0 && (self.regs.a ^ result) & 0x80 == 0x80); + self.regs.a = self.set_zn(result); + } + #[inline(always)] + fn sbc>(&mut self, am: AM) { + let val = am.load(self); + let mut result = self.regs.a as u32 - val as u32; + if !self.get_flag(CARRY_FLAG) { + result -= 1; + } + + self.set_flag(CARRY_FLAG, (result & 0x100) == 0); + + let result = result as u8; + self.set_flag(OVERFLOW_FLAG, + (self.regs.a ^ result) & 0x80 != 0 && (self.regs.a ^ val) & 0x80 == 0x80); + self.regs.a = self.set_zn(result); + } + + // Comparisons + fn cmp_base>(&mut self, x: u8, am: AM) { + let y = am.load(self); + let result = x as u32 - y as u32; + self.set_flag(CARRY_FLAG, (result & 0x100) == 0); + let _ = self.set_zn(result as u8); + } + fn cmp>(&mut self, am: AM) { self.cmp_base(self.regs.a, am) } + fn cpx>(&mut self, am: AM) { self.cmp_base(self.regs.x, am) } + fn cpy>(&mut self, am: AM) { self.cmp_base(self.regs.y, am) } + + // Bitwise operations + fn and>(&mut self, am: AM) { + let val = am.load(self) & self.regs.a; + self.regs.a = self.set_zn(val) + } + fn ora>(&mut self, am: AM) { + let val = am.load(self) | self.regs.a; + self.regs.a = self.set_zn(val) + } + fn eor>(&mut self, am: AM) { + let val = am.load(self) ^ self.regs.a; + self.regs.a = self.set_zn(val) + } + fn bit>(&mut self, am: AM) { + let val = am.load(self); + self.set_flag(ZERO_FLAG, (val & self.regs.a) == 0); + self.set_flag(NEGATIVE_FLAG, (val & 0x80) != 0); + self.set_flag(OVERFLOW_FLAG, (val & 0x40) != 0); + } + + // Shifts and rotates + fn shl_base>(&mut self, lsb: bool, am: AM) { + let val = am.load(self); + let new_carry = (val & 0x80) != 0; + let mut result = val << 1; + if lsb { + result |= 1; + } + self.set_flag(CARRY_FLAG, new_carry); + let val = self.set_zn(result as u8); + am.store(self, val) + } + fn shr_base>(&mut self, msb: bool, am: AM) { + let val = am.load(self); + let new_carry = (val & 0x1) != 0; + let mut result = val >> 1; + if msb { + result |= 0x80; + } + self.set_flag(CARRY_FLAG, new_carry); + let val = self.set_zn(result as u8); + am.store(self, val) + } + fn rol>(&mut self, am: AM) { + let val = self.get_flag(CARRY_FLAG); + self.shl_base(val, am) + } + fn ror>(&mut self, am: AM) { + let val = self.get_flag(CARRY_FLAG); + self.shr_base(val, am) + } + fn asl>(&mut self, am: AM) { self.shl_base(false, am) } + fn lsr>(&mut self, am: AM) { self.shr_base(false, am) } + + // Increments and decrements + fn inc>(&mut self, am: AM) { + let val = am.load(self); + let val = self.set_zn(val + 1); + am.store(self, val) + } + fn dec>(&mut self, am: AM) { + let val = am.load(self); + let val = self.set_zn(val - 1); + am.store(self, val) + } + fn inx(&mut self) { self.regs.x = self.set_zn(self.regs.x + 1) } + fn dex(&mut self) { self.regs.x = self.set_zn(self.regs.x - 1) } + fn iny(&mut self) { self.regs.y = self.set_zn(self.regs.y + 1) } + fn dey(&mut self) { self.regs.y = self.set_zn(self.regs.y - 1) } + + // Register moves + fn tax(&mut self) { self.regs.x = self.set_zn(self.regs.a) } + fn tay(&mut self) { self.regs.y = self.set_zn(self.regs.a) } + fn txa(&mut self) { self.regs.a = self.set_zn(self.regs.x) } + fn tya(&mut self) { self.regs.a = self.set_zn(self.regs.y) } + fn txs(&mut self) { self.regs.s = self.regs.x } + fn tsx(&mut self) { self.regs.x = self.set_zn(self.regs.s) } + + // Flag operations + fn clc(&mut self) { self.set_flag(CARRY_FLAG, false) } + fn sec(&mut self) { self.set_flag(CARRY_FLAG, true) } + fn cli(&mut self) { self.set_flag(IRQ_FLAG, false) } + fn sei(&mut self) { self.set_flag(IRQ_FLAG, true) } + fn clv(&mut self) { self.set_flag(OVERFLOW_FLAG, false) } + fn cld(&mut self) { self.set_flag(DECIMAL_FLAG, false) } + fn sed(&mut self) { self.set_flag(DECIMAL_FLAG, true) } + + // Branches + fn bra_base(&mut self, cond: bool) { + let disp = self.loadb_bump_pc() as i8; + if cond { + self.regs.pc = (self.regs.pc as i32 + disp as i32) as u16; + } + } + fn bpl(&mut self) { + let flag = !self.get_flag(NEGATIVE_FLAG); + self.bra_base(flag) + } + fn bmi(&mut self) { + let flag = self.get_flag(NEGATIVE_FLAG); + self.bra_base(flag) + } + fn bvc(&mut self) { + let flag = !self.get_flag(OVERFLOW_FLAG); + self.bra_base(flag) + } + fn bvs(&mut self) { + let flag = self.get_flag(OVERFLOW_FLAG); + self.bra_base(flag) + } + fn bcc(&mut self) { + let flag = !self.get_flag(CARRY_FLAG); + self.bra_base(flag) + } + fn bcs(&mut self) { + let flag = self.get_flag(CARRY_FLAG); + self.bra_base(flag) + } + fn bne(&mut self) { + let flag = !self.get_flag(ZERO_FLAG); + self.bra_base(flag) + } + fn beq(&mut self) { + let flag = self.get_flag(ZERO_FLAG); + self.bra_base(flag) + } + + // Jumps + fn jmp(&mut self) { self.regs.pc = self.loadw_bump_pc() } + fn jmpi(&mut self) { + let addr = self.loadw_bump_pc(); + + // Replicate the famous CPU bug... + let lo = self.loadb(addr); + let hi = self.loadb((addr & 0xff00) | ((addr + 1) & 0x00ff)); + + self.regs.pc = (hi as u16 << 8) | lo as u16; + } + + // Procedure calls + fn jsr(&mut self) { + let addr = self.loadw_bump_pc(); + self.pushw(self.regs.pc - 1); + self.regs.pc = addr; + } + fn rts(&mut self) { self.regs.pc = self.popw() + 1 } + fn brk(&mut self) { + self.pushw(self.regs.pc + 1); + self.pushb(self.regs.flags); // FIXME: FCEU sets BREAK_FLAG and U_FLAG here, why? + self.set_flag(IRQ_FLAG, true); + self.regs.pc = self.loadw(BRK_VECTOR); + } + fn rti(&mut self) { + let flags = self.popb(); + self.set_flags(flags); + self.regs.pc = self.popw(); // NB: no + 1 + } + + // Stack operations + fn pha(&mut self) { self.pushb(self.regs.a) } + fn pla(&mut self) { + let val = self.popb(); + self.regs.a = self.set_zn(val) + } + fn php(&mut self) { self.pushb(self.regs.flags | BREAK_FLAG) } + fn plp(&mut self) { + let val = self.popb(); + self.set_flags(val) + } + + // No operation + fn nop(&mut self) {} + + // The main fetch-and-decode routine + pub fn step(&mut self) { + self.trace(); + + let op = self.loadb_bump_pc(); + decode_op!(op, self); + + self.cy += CYCLE_TABLE[op] as Cycles; + } + + /// External interfaces + pub fn reset(&mut self) { self.regs.pc = self.loadw(RESET_VECTOR); } + + pub fn nmi(&mut self) { + self.pushw(self.regs.pc); + self.pushb(self.regs.flags); + self.regs.pc = self.loadw(NMI_VECTOR); + } + + pub fn irq(&mut self) { + if self.get_flag(IRQ_FLAG) { + return; + } + + self.pushw(self.regs.pc); + self.pushb(self.regs.flags); + self.regs.pc = self.loadw(BRK_VECTOR); + } + + /// The constructor. + pub fn new(mem: M) -> Cpu { Cpu { cy: 0, regs: Regs::new(), mem: mem } } +} + diff --git a/mem.rs b/mem.rs new file mode 100644 index 0000000..7906845 --- /dev/null +++ b/mem.rs @@ -0,0 +1,36 @@ +// +// sprocketnes/mem.rs +// +// Author: Patrick Walton +// + +// +// The memory interface +// + +/// The basic memory interface +pub trait Mem { + fn loadb(&mut self, addr: u16) -> u8; + fn storeb(&mut self, addr: u16, val: u8); +} + +pub trait MemUtil { + fn loadw(&mut self, addr: u16) -> u16; + fn storew(&mut self, addr: u16, val: u16); + fn loadw_zp(&mut self, addr: u8) -> u16; +} + +impl MemUtil for M { + fn loadw(&mut self, addr: u16) -> u16 { + self.loadb(addr) as u16 | (self.loadb(addr + 1) as u16 << 8) + } + fn storew(&mut self, addr: u16, val: u16) { + self.storeb(addr, (val & 0xff) as u8); + self.storeb(addr + 1, ((val >> 8) & 0xff) as u8); + } + // Like loadw, but has wraparound behavior on the zero page for address 0xff. + fn loadw_zp(&mut self, addr: u8) -> u16 { + self.loadb(addr as u16) as u16 | (self.loadb((addr + 1) as u16) as u16 << 8) + } +} + diff --git a/tests.rs b/tests.rs new file mode 100644 index 0000000..5d76818 --- /dev/null +++ b/tests.rs @@ -0,0 +1,46 @@ + +use cpu::Cpu; +use mem::Mem; +use a2::AppleII; + +// + +fn return_two() -> int { + 2 +} + +#[test] +fn return_two_test() { + let x = return_two(); + assert!(x == 2); +} + +pub struct Ram { mem: [u8, ..0x800] } + +impl Mem for Ram { + fn loadb(&mut self, addr: u16) -> u8 { self.mem[addr & 0x7ff] } + fn storeb(&mut self, addr: u16, val: u8) { self.mem[addr & 0x7ff] = val } +} + +#[test] +fn test_cpu() +{ + let ram = Ram { mem: [ 0, ..0x800 ] }; + let mut cpu = Cpu::new(ram); + cpu.reset(); + assert!(cpu.regs.pc == 0); + cpu.step(); +} + +#[test] +fn test_a2() +{ + let mut a2 = AppleII::new(); + a2.read_roms(); + let mut cpu = Cpu::new(a2); + cpu.reset(); + for i in range(0,100) { + cpu.step(); + } +} + diff --git a/util.rs b/util.rs new file mode 100644 index 0000000..a0d80eb --- /dev/null +++ b/util.rs @@ -0,0 +1,169 @@ +// +// sprocketnes/util.rs +// +// Author: Patrick Walton +// + +use std::io::File; +use std::libc::{c_int, c_void, time_t}; +use std::ptr::null; + +// +// A tiny custom serialization infrastructure, used for savestates. +// +// TODO: Use the standard library's ToBytes and add a FromBytes -- or don't; this is such a small +// amount of code it barely seems worth it. +// + +pub trait Save { + fn save(&mut self, fd: &mut File); + fn load(&mut self, fd: &mut File); +} + +impl Save for u8 { + fn save(&mut self, fd: &mut File) { fd.write([ *self ]) } + fn load(&mut self, fd: &mut File) { let mut buf = [ 0 ]; fd.read(buf); *self = buf[0]; } +} + +impl Save for u16 { + fn save(&mut self, fd: &mut File) { fd.write([ *self as u8, (*self >> 8) as u8 ]) } + fn load(&mut self, fd: &mut File) { + let mut buf = [ 0, 0 ]; + fd.read(buf); + *self = (buf[0] as u16) | ((buf[1] as u16) << 8); + } +} + +impl Save for u64 { + fn save(&mut self, fd: &mut File) { + let mut buf = [ 0, ..8 ]; + for i in range(0, 8) { + buf[i] = ((*self) >> (i * 8)) as u8; + } + fd.write(buf); + } + fn load(&mut self, fd: &mut File) { + let mut buf = [ 0, ..8 ]; + fd.read(buf); + *self = 0; + for i in range(0, 8) { + *self = *self | (buf[i] as u64 << (i * 8)); + } + } +} + +impl<'a> Save for &'a mut [u8] { + fn save(&mut self, fd: &mut File) { + fd.write(*self); + } + fn load(&mut self, fd: &mut File) { + fd.read(*self); + } +} + +impl Save for bool { + fn save(&mut self, fd: &mut File) { fd.write([ if *self { 0 } else { 1 } ]) } + fn load(&mut self, fd: &mut File) { + let mut val: [u8, ..1] = [ 0 ]; + fd.read(val); + *self = val[0] != 0 + } +} + +// A convenience macro to save and load entire structs. +macro_rules! save_struct( + ($name:ident { $($field:ident),* }) => ( + impl Save for $name { + fn save(&mut self, fd: &mut File) { + $(self.$field.save(fd);)* + } + fn load(&mut self, fd: &mut File) { + $(self.$field.load(fd);)* + } + } + ) +) + +macro_rules! save_enum( + ($name:ident { $val_0:ident, $val_1:ident }) => ( + impl Save for $name { + fn save(&mut self, fd: &mut File) { + let mut val: u8 = match *self { $val_0 => 0, $val_1 => 1 }; + val.save(fd) + } + fn load(&mut self, fd: &mut File) { + let mut val: u8 = 0; + val.load(fd); + *self = if val == 0 { $val_0 } else { $val_1 }; + } + } + ) +) + +// +// Random number generation +// + +pub struct Xorshift { + x: u32, + y: u32, + z: u32, + w: u32, +} + +impl Xorshift { + pub fn new() -> Xorshift { + Xorshift { x: 123456789, y: 362436069, z: 521288629, w: 88675123 } + } + + pub fn next(&mut self) -> u32 { + let t = self.x ^ (self.x << 11); + self.x = self.y; self.y = self.z; self.z = self.w; + self.w = self.w ^ (self.w >> 19) ^ (t ^ (t >> 8)); + self.w + } +} + +// +// Simple assertions +// + +#[cfg(debug)] +pub fn debug_assert(cond: bool, msg: &str) { + if !cond { + println(msg); + } +} + +#[cfg(not(debug))] +pub fn debug_assert(_: bool, _: &str) {} + +#[cfg(debug)] +pub fn debug_print(msg: &str) { + println(msg); +} + +#[cfg(not(debug))] +pub fn debug_print(_: &str) {} + +// +// Bindings for `gettimeofday(2)` +// + +struct timeval { + tv_sec: time_t, + tv_usec: u32, +} + +extern { + fn gettimeofday(tp: *mut timeval, tzp: *c_void) -> c_int; +} + +pub fn current_time_millis() -> u64 { + unsafe { + let mut tv = timeval { tv_sec: 0, tv_usec: 0 }; + gettimeofday(&mut tv, null()); + (tv.tv_sec as u64) * 1000 + (tv.tv_usec as u64) / 1000 + } +} +