mirror of
https://github.com/sehugg/rustyapple.git
synced 2025-02-19 21:30:50 +00:00
first cut of cpu + apple i/o
This commit is contained in:
commit
9768a71474
6
Makefile
Normal file
6
Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
test:
|
||||
rustc -o appletest --test apple.rs
|
||||
./appletest
|
||||
|
||||
|
239
a2.rs
Normal file
239
a2.rs
Normal 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
31
apple.rs
Normal 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
726
cpu.rs
Normal 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
36
mem.rs
Normal 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
46
tests.rs
Normal 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
169
util.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user