first cut of cpu + apple i/o

This commit is contained in:
Steven Hugg 2014-01-15 00:05:42 -05:00
commit 9768a71474
7 changed files with 1253 additions and 0 deletions

6
Makefile Normal file
View File

@ -0,0 +1,6 @@
test:
rustc -o appletest --test apple.rs
./appletest

239
a2.rs Normal file
View File

@ -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");
}
}

31
apple.rs Normal file
View File

@ -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
}
*/

726
cpu.rs Normal file
View File

@ -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<M> {
fn load(&self, cpu: &mut Cpu<M>) -> u8;
fn store(&self, cpu: &mut Cpu<M>, val: u8);
}
struct AccumulatorAddressingMode;
impl<M:Mem> AddressingMode<M> for AccumulatorAddressingMode {
fn load(&self, cpu: &mut Cpu<M>) -> u8 { cpu.regs.a }
fn store(&self, cpu: &mut Cpu<M>, val: u8) { cpu.regs.a = val }
}
struct ImmediateAddressingMode;
impl<M:Mem> AddressingMode<M> for ImmediateAddressingMode {
fn load(&self, cpu: &mut Cpu<M>) -> u8 { cpu.loadb_bump_pc() }
fn store(&self, _: &mut Cpu<M>, _: u8) {
// Not particularly type-safe, but probably not worth using trait inheritance for this.
fail!("can't store to immediate")
}
}
struct MemoryAddressingMode(u16);
impl<M:Mem> AddressingMode<M> for MemoryAddressingMode {
fn load(&self, cpu: &mut Cpu<M>) -> u8 { match *self { MemoryAddressingMode(addr) => cpu.loadb(addr) } }
fn store(&self, cpu: &mut Cpu<M>, 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<M> {
cy: Cycles,
regs: Regs,
mem: M,
}
// The CPU implements Mem so that it can handle writes to the DMA register.
impl<M:Mem> Mem for Cpu<M> {
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<M:Mem> Cpu<M> {
// 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<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self);
self.regs.a = self.set_zn(val)
}
fn ldx<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self);
self.regs.x = self.set_zn(val)
}
fn ldy<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self);
self.regs.y = self.set_zn(val)
}
// Stores
fn sta<AM:AddressingMode<M>>(&mut self, am: AM) { am.store(self, self.regs.a) }
fn stx<AM:AddressingMode<M>>(&mut self, am: AM) { am.store(self, self.regs.x) }
fn sty<AM:AddressingMode<M>>(&mut self, am: AM) { am.store(self, self.regs.y) }
// Arithmetic
#[inline(always)]
fn adc<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&mut self, am: AM) { self.cmp_base(self.regs.a, am) }
fn cpx<AM:AddressingMode<M>>(&mut self, am: AM) { self.cmp_base(self.regs.x, am) }
fn cpy<AM:AddressingMode<M>>(&mut self, am: AM) { self.cmp_base(self.regs.y, am) }
// Bitwise operations
fn and<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self) & self.regs.a;
self.regs.a = self.set_zn(val)
}
fn ora<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self) | self.regs.a;
self.regs.a = self.set_zn(val)
}
fn eor<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self) ^ self.regs.a;
self.regs.a = self.set_zn(val)
}
fn bit<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&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<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = self.get_flag(CARRY_FLAG);
self.shl_base(val, am)
}
fn ror<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = self.get_flag(CARRY_FLAG);
self.shr_base(val, am)
}
fn asl<AM:AddressingMode<M>>(&mut self, am: AM) { self.shl_base(false, am) }
fn lsr<AM:AddressingMode<M>>(&mut self, am: AM) { self.shr_base(false, am) }
// Increments and decrements
fn inc<AM:AddressingMode<M>>(&mut self, am: AM) {
let val = am.load(self);
let val = self.set_zn(val + 1);
am.store(self, val)
}
fn dec<AM:AddressingMode<M>>(&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<M> { Cpu { cy: 0, regs: Regs::new(), mem: mem } }
}

36
mem.rs Normal file
View File

@ -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<M:Mem> 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)
}
}

46
tests.rs Normal file
View File

@ -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();
}
}

169
util.rs Normal file
View File

@ -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
}
}