mirror of
https://github.com/tonk-labs/dappicom.git
synced 2026-01-09 13:48:01 -05:00
setup basic MOS 6502 emulation
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
|
||||
.vscode
|
||||
|
||||
#nargo
|
||||
target
|
||||
proofs
|
||||
|
||||
125
com/Cargo.lock
generated
Normal file
125
com/Cargo.lock
generated
Normal file
@@ -0,0 +1,125 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "com"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.2.1",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"sdl2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
12
com/Cargo.toml
Normal file
12
com/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "com"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.2.1"
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
sdl2 = "0.35.2"
|
||||
914
com/src/cpu.rs
Normal file
914
com/src/cpu.rs
Normal file
@@ -0,0 +1,914 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::opcodes;
|
||||
|
||||
bitflags! {
|
||||
/// # Status Register (P) http://wiki.nesdev.com/w/index.php/Status_flags
|
||||
///
|
||||
/// 7 6 5 4 3 2 1 0
|
||||
/// N V _ B D I Z C
|
||||
/// | | | | | | +--- Carry Flag
|
||||
/// | | | | | +----- Zero Flag
|
||||
/// | | | | +------- Interrupt Disable
|
||||
/// | | | +--------- Decimal Mode (not used on NES)
|
||||
/// | | +----------- Break Command
|
||||
/// | +--------------- Overflow Flag
|
||||
/// +----------------- Negative Flag
|
||||
///
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CpuFlags: u8 {
|
||||
const CARRY = 0b00000001;
|
||||
const ZERO = 0b00000010;
|
||||
const INTERRUPT_DISABLE = 0b00000100;
|
||||
const DECIMAL_MODE = 0b00001000;
|
||||
const BREAK = 0b00010000;
|
||||
const BREAK2 = 0b00100000;
|
||||
const OVERFLOW = 0b01000000;
|
||||
const NEGATIV = 0b10000000;
|
||||
}
|
||||
}
|
||||
|
||||
const STACK: u16 = 0x0100;
|
||||
const STACK_RESET: u8 = 0xfd;
|
||||
|
||||
pub struct CPU {
|
||||
pub register_a: u8,
|
||||
pub register_x: u8,
|
||||
pub register_y: u8,
|
||||
pub status: CpuFlags,
|
||||
pub program_counter: u16,
|
||||
pub stack_pointer: u8,
|
||||
memory: [u8; 0xFFFF],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AddressingMode {
|
||||
Immediate,
|
||||
ZeroPage,
|
||||
ZeroPageX,
|
||||
ZeroPageY,
|
||||
Absolute,
|
||||
AbsoluteX,
|
||||
AbsoluteY,
|
||||
IndirectX,
|
||||
IndirectY,
|
||||
NoneAddressing,
|
||||
}
|
||||
|
||||
/**
|
||||
* Data in memory is addressed using LE
|
||||
*/
|
||||
pub trait Mem {
|
||||
fn mem_read(&self, addr: u16) -> u8;
|
||||
|
||||
fn mem_write(&mut self, addr: u16, data: u8);
|
||||
|
||||
fn mem_read_u16(&self, pos: u16) -> u16 {
|
||||
let lo = self.mem_read(pos) as u16;
|
||||
let hi = self.mem_read(pos + 1) as u16;
|
||||
(hi << 8) | (lo as u16)
|
||||
}
|
||||
|
||||
fn mem_write_u16(&mut self, pos: u16, data: u16) {
|
||||
let hi = (data >> 8) as u8;
|
||||
let lo = (data & 0xff) as u8;
|
||||
self.mem_write(pos, lo);
|
||||
self.mem_write(pos + 1, hi);
|
||||
}
|
||||
}
|
||||
|
||||
impl Mem for CPU {
|
||||
|
||||
fn mem_read(&self, addr: u16) -> u8 {
|
||||
self.memory[addr as usize]
|
||||
}
|
||||
|
||||
fn mem_write(&mut self, addr: u16, data: u8) {
|
||||
self.memory[addr as usize] = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl CPU {
|
||||
pub fn new() -> Self {
|
||||
CPU {
|
||||
register_a: 0,
|
||||
register_x: 0,
|
||||
register_y: 0,
|
||||
program_counter: 0,
|
||||
status: CpuFlags::from_bits_truncate(0b100100),
|
||||
stack_pointer: STACK_RESET,
|
||||
//TODO: in true NES emulation, the memory is initialized to randomized values
|
||||
//some games use this initial randomization to seed rngs
|
||||
memory: [0; 0xFFFF],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_operand_address(&self, mode: &AddressingMode) -> u16 {
|
||||
match mode {
|
||||
AddressingMode::Immediate => self.program_counter,
|
||||
|
||||
AddressingMode::ZeroPage => self.mem_read(self.program_counter) as u16,
|
||||
|
||||
AddressingMode::Absolute => self.mem_read_u16(self.program_counter),
|
||||
|
||||
AddressingMode::ZeroPageX => {
|
||||
let pos = self.mem_read(self.program_counter);
|
||||
let addr = pos.wrapping_add(self.register_x) as u16;
|
||||
addr
|
||||
}
|
||||
|
||||
AddressingMode::ZeroPageY => {
|
||||
let pos = self.mem_read(self.program_counter);
|
||||
let addr = pos.wrapping_add(self.register_y) as u16;
|
||||
addr
|
||||
}
|
||||
|
||||
AddressingMode::AbsoluteX => {
|
||||
let base = self.mem_read_u16(self.program_counter);
|
||||
let addr = base.wrapping_add(self.register_x as u16);
|
||||
addr
|
||||
}
|
||||
|
||||
AddressingMode::AbsoluteY => {
|
||||
let base = self.mem_read_u16(self.program_counter);
|
||||
let addr = base.wrapping_add(self.register_y as u16);
|
||||
addr
|
||||
}
|
||||
|
||||
AddressingMode::IndirectX => {
|
||||
let base = self.mem_read(self.program_counter);
|
||||
|
||||
let ptr: u8 = (base as u8).wrapping_add(self.register_x);
|
||||
let lo = self.mem_read(ptr as u16);
|
||||
let hi = self.mem_read(ptr.wrapping_add(1) as u16);
|
||||
(hi as u16) << 8 | (lo as u16)
|
||||
}
|
||||
|
||||
AddressingMode::IndirectY => {
|
||||
let base = self.mem_read(self.program_counter);
|
||||
|
||||
let lo = self.mem_read(base as u16);
|
||||
let hi = self.mem_read((base as u8).wrapping_add(1) as u16);
|
||||
let deref_base = (hi as u16) << 8 | (lo as u16);
|
||||
let deref = deref_base.wrapping_add(self.register_y as u16);
|
||||
deref
|
||||
}
|
||||
|
||||
AddressingMode::NoneAddressing => {
|
||||
panic!("mode {:?} is not supported", mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.register_a = 0;
|
||||
self.register_x = 0;
|
||||
self.register_y = 0;
|
||||
self.status = CpuFlags::from_bits_truncate(0b100100);
|
||||
self.stack_pointer = STACK_RESET;
|
||||
|
||||
//On loading ROM, NES resets and then loads its first
|
||||
//instruction from this specific memory location 0xFFFC
|
||||
self.program_counter = self.mem_read_u16(0xFFFC);
|
||||
}
|
||||
|
||||
pub fn load_and_run(&mut self, program: Vec<u8>) {
|
||||
self.load(program);
|
||||
self.reset();
|
||||
self.run();
|
||||
}
|
||||
|
||||
pub fn load(&mut self, program: Vec<u8>) {
|
||||
self.memory[0x0600 .. (0x0600 + program.len())].copy_from_slice(&program[..]);
|
||||
//FIXME: This seems like bit of a hack, no? We'll need to change this later.
|
||||
//Later on, we need to just assume the ROM itself will include a value at this address
|
||||
//We can add a special fn for test purposes
|
||||
self.mem_write_u16(0xFFFC, 0x0600);
|
||||
}
|
||||
|
||||
fn ldy(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.register_y = data;
|
||||
self.update_zero_and_negative_flags(self.register_y);
|
||||
}
|
||||
|
||||
fn ldx(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.register_x = data;
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
fn lda(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let value = self.mem_read(addr);
|
||||
|
||||
self.register_a = value;
|
||||
self.update_zero_and_negative_flags(self.register_a);
|
||||
}
|
||||
|
||||
fn sta(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
self.mem_write(addr, self.register_a);
|
||||
}
|
||||
|
||||
fn set_register_a(&mut self, value: u8) {
|
||||
self.register_a = value;
|
||||
self.update_zero_and_negative_flags(self.register_a);
|
||||
}
|
||||
|
||||
fn and(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.set_register_a(data & self.register_a);
|
||||
}
|
||||
|
||||
fn eor(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.set_register_a(data ^ self.register_a);
|
||||
}
|
||||
|
||||
fn ora(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.set_register_a(data | self.register_a);
|
||||
}
|
||||
|
||||
fn tax(&mut self) {
|
||||
self.register_x = self.register_a;
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
fn inx(&mut self) {
|
||||
self.register_x = self.register_x.wrapping_add(1);
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
fn update_zero_and_negative_flags(&mut self, result: u8) {
|
||||
if result == 0 {
|
||||
self.status.insert(CpuFlags::ZERO);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::ZERO);
|
||||
}
|
||||
|
||||
if result & 0b1000_0000 != 0 {
|
||||
self.status.insert(CpuFlags::NEGATIV);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::NEGATIV);
|
||||
}
|
||||
}
|
||||
|
||||
fn iny(&mut self) {
|
||||
self.register_y = self.register_y.wrapping_add(1);
|
||||
self.update_zero_and_negative_flags(self.register_y);
|
||||
}
|
||||
|
||||
fn set_carry_flag(&mut self) {
|
||||
self.status.insert(CpuFlags::CARRY)
|
||||
}
|
||||
|
||||
fn clear_carry_flag(&mut self) {
|
||||
self.status.remove(CpuFlags::CARRY)
|
||||
}
|
||||
|
||||
/// note: ignoring decimal mode
|
||||
/// http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
|
||||
fn add_to_register_a(&mut self, data: u8) {
|
||||
let sum = self.register_a as u16
|
||||
+ data as u16
|
||||
+ (if self.status.contains(CpuFlags::CARRY) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}) as u16;
|
||||
|
||||
let carry = sum > 0xff;
|
||||
|
||||
if carry {
|
||||
self.status.insert(CpuFlags::CARRY);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::CARRY);
|
||||
}
|
||||
|
||||
let result = sum as u8;
|
||||
|
||||
if (data ^ result) & (result ^ self.register_a) & 0x80 != 0 {
|
||||
self.status.insert(CpuFlags::OVERFLOW);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::OVERFLOW)
|
||||
}
|
||||
|
||||
self.set_register_a(result);
|
||||
}
|
||||
|
||||
fn sbc(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(&mode);
|
||||
let data = self.mem_read(addr);
|
||||
self.add_to_register_a(((data as i8).wrapping_neg().wrapping_sub(1)) as u8);
|
||||
}
|
||||
|
||||
fn adc(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let value = self.mem_read(addr);
|
||||
self.add_to_register_a(value);
|
||||
}
|
||||
|
||||
fn stack_pop(&mut self) -> u8 {
|
||||
self.stack_pointer = self.stack_pointer.wrapping_add(1);
|
||||
self.mem_read((STACK as u16) + self.stack_pointer as u16)
|
||||
}
|
||||
|
||||
fn stack_push(&mut self, data: u8) {
|
||||
self.mem_write((STACK as u16) + self.stack_pointer as u16, data);
|
||||
self.stack_pointer = self.stack_pointer.wrapping_sub(1)
|
||||
}
|
||||
|
||||
fn stack_push_u16(&mut self, data: u16) {
|
||||
let hi = (data >> 8) as u8;
|
||||
let lo = (data & 0xff) as u8;
|
||||
self.stack_push(hi);
|
||||
self.stack_push(lo);
|
||||
}
|
||||
|
||||
fn stack_pop_u16(&mut self) -> u16 {
|
||||
let lo = self.stack_pop() as u16;
|
||||
let hi = self.stack_pop() as u16;
|
||||
|
||||
hi << 8 | lo
|
||||
}
|
||||
|
||||
fn asl_accumulator(&mut self) {
|
||||
let mut data = self.register_a;
|
||||
if data >> 7 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data << 1;
|
||||
self.set_register_a(data)
|
||||
}
|
||||
|
||||
fn asl(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
if data >> 7 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data << 1;
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
fn lsr_accumulator(&mut self) {
|
||||
let mut data = self.register_a;
|
||||
if data & 1 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data >> 1;
|
||||
self.set_register_a(data)
|
||||
}
|
||||
|
||||
fn lsr(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
if data & 1 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data >> 1;
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
fn rol(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
let old_carry = self.status.contains(CpuFlags::CARRY);
|
||||
|
||||
if data >> 7 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data << 1;
|
||||
if old_carry {
|
||||
data = data | 1;
|
||||
}
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
|
||||
fn rol_accumulator(&mut self) {
|
||||
let mut data = self.register_a;
|
||||
let old_carry = self.status.contains(CpuFlags::CARRY);
|
||||
|
||||
if data >> 7 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data << 1;
|
||||
if old_carry {
|
||||
data = data | 1;
|
||||
}
|
||||
self.set_register_a(data);
|
||||
}
|
||||
|
||||
|
||||
fn ror(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
let old_carry = self.status.contains(CpuFlags::CARRY);
|
||||
|
||||
if data & 1 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data >> 1;
|
||||
if old_carry {
|
||||
data = data | 0b10000000;
|
||||
}
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
|
||||
fn ror_accumulator(&mut self) {
|
||||
let mut data = self.register_a;
|
||||
let old_carry = self.status.contains(CpuFlags::CARRY);
|
||||
|
||||
if data & 1 == 1 {
|
||||
self.set_carry_flag();
|
||||
} else {
|
||||
self.clear_carry_flag();
|
||||
}
|
||||
data = data >> 1;
|
||||
if old_carry {
|
||||
data = data | 0b10000000;
|
||||
}
|
||||
self.set_register_a(data);
|
||||
}
|
||||
|
||||
fn inc(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
data = data.wrapping_add(1);
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
fn dey(&mut self) {
|
||||
self.register_y = self.register_y.wrapping_sub(1);
|
||||
self.update_zero_and_negative_flags(self.register_y);
|
||||
}
|
||||
|
||||
fn dex(&mut self) {
|
||||
self.register_x = self.register_x.wrapping_sub(1);
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
fn dec(&mut self, mode: &AddressingMode) -> u8 {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let mut data = self.mem_read(addr);
|
||||
data = data.wrapping_sub(1);
|
||||
self.mem_write(addr, data);
|
||||
self.update_zero_and_negative_flags(data);
|
||||
data
|
||||
}
|
||||
|
||||
fn pla(&mut self) {
|
||||
let data = self.stack_pop();
|
||||
self.set_register_a(data);
|
||||
}
|
||||
|
||||
fn plp(&mut self) {
|
||||
self.status = CpuFlags::from_bits_truncate(self.stack_pop());
|
||||
self.status.remove(CpuFlags::BREAK);
|
||||
self.status.insert(CpuFlags::BREAK2);
|
||||
}
|
||||
|
||||
fn php(&mut self) {
|
||||
//http://wiki.nesdev.com/w/index.php/CPU_status_flag_behavior
|
||||
let mut flags = self.status.clone();
|
||||
flags.insert(CpuFlags::BREAK);
|
||||
flags.insert(CpuFlags::BREAK2);
|
||||
self.stack_push(flags.bits());
|
||||
}
|
||||
|
||||
fn bit(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
let and = self.register_a & data;
|
||||
if and == 0 {
|
||||
self.status.insert(CpuFlags::ZERO);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::ZERO);
|
||||
}
|
||||
|
||||
self.status.set(CpuFlags::NEGATIV, data & 0b10000000 > 0);
|
||||
self.status.set(CpuFlags::OVERFLOW, data & 0b01000000 > 0);
|
||||
}
|
||||
|
||||
fn compare(&mut self, mode: &AddressingMode, compare_with: u8) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
let data = self.mem_read(addr);
|
||||
if data <= compare_with {
|
||||
self.status.insert(CpuFlags::CARRY);
|
||||
} else {
|
||||
self.status.remove(CpuFlags::CARRY);
|
||||
}
|
||||
|
||||
self.update_zero_and_negative_flags(compare_with.wrapping_sub(data));
|
||||
}
|
||||
|
||||
fn branch(&mut self, condition: bool) {
|
||||
if condition {
|
||||
let jump: i8 = self.mem_read(self.program_counter) as i8;
|
||||
let jump_addr = self
|
||||
.program_counter
|
||||
.wrapping_add(1)
|
||||
.wrapping_add(jump as u16);
|
||||
|
||||
self.program_counter = jump_addr;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.run_with_callback(|_| {});
|
||||
}
|
||||
|
||||
pub fn run_with_callback<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&mut CPU),
|
||||
{
|
||||
let ref opcodes: HashMap<u8, &'static opcodes::OpCode> = *opcodes::OPCODES_MAP;
|
||||
|
||||
loop {
|
||||
let code = self.mem_read(self.program_counter);
|
||||
self.program_counter += 1;
|
||||
let program_counter_state = self.program_counter;
|
||||
|
||||
let opcode = opcodes.get(&code).unwrap();
|
||||
|
||||
match code {
|
||||
0xa9 | 0xa5 | 0xb5 | 0xad | 0xbd | 0xb9 | 0xa1 | 0xb1 => {
|
||||
self.lda(&opcode.mode);
|
||||
}
|
||||
|
||||
0xAA => self.tax(),
|
||||
0xe8 => self.inx(),
|
||||
0x00 => return,
|
||||
|
||||
/* CLD */ 0xd8 => self.status.remove(CpuFlags::DECIMAL_MODE),
|
||||
|
||||
/* CLI */ 0x58 => self.status.remove(CpuFlags::INTERRUPT_DISABLE),
|
||||
|
||||
/* CLV */ 0xb8 => self.status.remove(CpuFlags::OVERFLOW),
|
||||
|
||||
/* CLC */ 0x18 => self.clear_carry_flag(),
|
||||
|
||||
/* SEC */ 0x38 => self.set_carry_flag(),
|
||||
|
||||
/* SEI */ 0x78 => self.status.insert(CpuFlags::INTERRUPT_DISABLE),
|
||||
|
||||
/* SED */ 0xf8 => self.status.insert(CpuFlags::DECIMAL_MODE),
|
||||
|
||||
/* PHA */ 0x48 => self.stack_push(self.register_a),
|
||||
|
||||
/* PLA */
|
||||
0x68 => {
|
||||
self.pla();
|
||||
}
|
||||
|
||||
/* PHP */
|
||||
0x08 => {
|
||||
self.php();
|
||||
}
|
||||
|
||||
/* PLP */
|
||||
0x28 => {
|
||||
self.plp();
|
||||
}
|
||||
|
||||
/* ADC */
|
||||
0x69 | 0x65 | 0x75 | 0x6d | 0x7d | 0x79 | 0x61 | 0x71 => {
|
||||
self.adc(&opcode.mode);
|
||||
}
|
||||
|
||||
/* SBC */
|
||||
0xe9 | 0xe5 | 0xf5 | 0xed | 0xfd | 0xf9 | 0xe1 | 0xf1 => {
|
||||
self.sbc(&opcode.mode);
|
||||
}
|
||||
|
||||
/* AND */
|
||||
0x29 | 0x25 | 0x35 | 0x2d | 0x3d | 0x39 | 0x21 | 0x31 => {
|
||||
self.and(&opcode.mode);
|
||||
}
|
||||
|
||||
/* EOR */
|
||||
0x49 | 0x45 | 0x55 | 0x4d | 0x5d | 0x59 | 0x41 | 0x51 => {
|
||||
self.eor(&opcode.mode);
|
||||
}
|
||||
|
||||
/* ORA */
|
||||
0x09 | 0x05 | 0x15 | 0x0d | 0x1d | 0x19 | 0x01 | 0x11 => {
|
||||
self.ora(&opcode.mode);
|
||||
}
|
||||
|
||||
/* LSR */ 0x4a => self.lsr_accumulator(),
|
||||
|
||||
/* LSR */
|
||||
0x46 | 0x56 | 0x4e | 0x5e => {
|
||||
self.lsr(&opcode.mode);
|
||||
}
|
||||
|
||||
/*ASL*/ 0x0a => self.asl_accumulator(),
|
||||
|
||||
/* ASL */
|
||||
0x06 | 0x16 | 0x0e | 0x1e => {
|
||||
self.asl(&opcode.mode);
|
||||
}
|
||||
|
||||
/*ROL*/ 0x2a => self.rol_accumulator(),
|
||||
|
||||
/* ROL */
|
||||
0x26 | 0x36 | 0x2e | 0x3e => {
|
||||
self.rol(&opcode.mode);
|
||||
}
|
||||
|
||||
/* ROR */ 0x6a => self.ror_accumulator(),
|
||||
|
||||
/* ROR */
|
||||
0x66 | 0x76 | 0x6e | 0x7e => {
|
||||
self.ror(&opcode.mode);
|
||||
}
|
||||
|
||||
/* INC */
|
||||
0xe6 | 0xf6 | 0xee | 0xfe => {
|
||||
self.inc(&opcode.mode);
|
||||
}
|
||||
|
||||
/* INY */
|
||||
0xc8 => self.iny(),
|
||||
|
||||
/* DEC */
|
||||
0xc6 | 0xd6 | 0xce | 0xde => {
|
||||
self.dec(&opcode.mode);
|
||||
}
|
||||
|
||||
/* DEX */
|
||||
0xca => {
|
||||
self.dex();
|
||||
}
|
||||
|
||||
/* DEY */
|
||||
0x88 => {
|
||||
self.dey();
|
||||
}
|
||||
|
||||
/* CMP */
|
||||
0xc9 | 0xc5 | 0xd5 | 0xcd | 0xdd | 0xd9 | 0xc1 | 0xd1 => {
|
||||
self.compare(&opcode.mode, self.register_a);
|
||||
}
|
||||
|
||||
/* CPY */
|
||||
0xc0 | 0xc4 | 0xcc => {
|
||||
self.compare(&opcode.mode, self.register_y);
|
||||
}
|
||||
|
||||
/* CPX */
|
||||
0xe0 | 0xe4 | 0xec => self.compare(&opcode.mode, self.register_x),
|
||||
|
||||
/* JMP Absolute */
|
||||
0x4c => {
|
||||
let mem_address = self.mem_read_u16(self.program_counter);
|
||||
self.program_counter = mem_address;
|
||||
}
|
||||
|
||||
/* JMP Indirect */
|
||||
0x6c => {
|
||||
let mem_address = self.mem_read_u16(self.program_counter);
|
||||
// let indirect_ref = self.mem_read_u16(mem_address);
|
||||
//6502 bug mode with with page boundary:
|
||||
// if address $3000 contains $40, $30FF contains $80, and $3100 contains $50,
|
||||
// the result of JMP ($30FF) will be a transfer of control to $4080 rather than $5080 as you intended
|
||||
// i.e. the 6502 took the low byte of the address from $30FF and the high byte from $3000
|
||||
|
||||
let indirect_ref = if mem_address & 0x00FF == 0x00FF {
|
||||
let lo = self.mem_read(mem_address);
|
||||
let hi = self.mem_read(mem_address & 0xFF00);
|
||||
(hi as u16) << 8 | (lo as u16)
|
||||
} else {
|
||||
self.mem_read_u16(mem_address)
|
||||
};
|
||||
|
||||
self.program_counter = indirect_ref;
|
||||
}
|
||||
|
||||
/* JSR */
|
||||
0x20 => {
|
||||
self.stack_push_u16(self.program_counter + 2 - 1);
|
||||
let target_address = self.mem_read_u16(self.program_counter);
|
||||
self.program_counter = target_address
|
||||
}
|
||||
|
||||
/* RTS */
|
||||
0x60 => {
|
||||
self.program_counter = self.stack_pop_u16() + 1;
|
||||
}
|
||||
|
||||
/* RTI */
|
||||
0x40 => {
|
||||
self.status = CpuFlags::from_bits_truncate(self.stack_pop());
|
||||
self.status.remove(CpuFlags::BREAK);
|
||||
self.status.insert(CpuFlags::BREAK2);
|
||||
|
||||
self.program_counter = self.stack_pop_u16();
|
||||
}
|
||||
|
||||
/* BNE */
|
||||
0xd0 => {
|
||||
self.branch(!self.status.contains(CpuFlags::ZERO));
|
||||
}
|
||||
|
||||
/* BVS */
|
||||
0x70 => {
|
||||
self.branch(self.status.contains(CpuFlags::OVERFLOW));
|
||||
}
|
||||
|
||||
/* BVC */
|
||||
0x50 => {
|
||||
self.branch(!self.status.contains(CpuFlags::OVERFLOW));
|
||||
}
|
||||
|
||||
/* BPL */
|
||||
0x10 => {
|
||||
self.branch(!self.status.contains(CpuFlags::NEGATIV));
|
||||
}
|
||||
|
||||
/* BMI */
|
||||
0x30 => {
|
||||
self.branch(self.status.contains(CpuFlags::NEGATIV));
|
||||
}
|
||||
|
||||
/* BEQ */
|
||||
0xf0 => {
|
||||
self.branch(self.status.contains(CpuFlags::ZERO));
|
||||
}
|
||||
|
||||
/* BCS */
|
||||
0xb0 => {
|
||||
self.branch(self.status.contains(CpuFlags::CARRY));
|
||||
}
|
||||
|
||||
/* BCC */
|
||||
0x90 => {
|
||||
self.branch(!self.status.contains(CpuFlags::CARRY));
|
||||
}
|
||||
|
||||
/* BIT */
|
||||
0x24 | 0x2c => {
|
||||
self.bit(&opcode.mode);
|
||||
}
|
||||
|
||||
/* STA */
|
||||
0x85 | 0x95 | 0x8d | 0x9d | 0x99 | 0x81 | 0x91 => {
|
||||
self.sta(&opcode.mode);
|
||||
}
|
||||
|
||||
/* STX */
|
||||
0x86 | 0x96 | 0x8e => {
|
||||
let addr = self.get_operand_address(&opcode.mode);
|
||||
self.mem_write(addr, self.register_x);
|
||||
}
|
||||
|
||||
/* STY */
|
||||
0x84 | 0x94 | 0x8c => {
|
||||
let addr = self.get_operand_address(&opcode.mode);
|
||||
self.mem_write(addr, self.register_y);
|
||||
}
|
||||
|
||||
/* LDX */
|
||||
0xa2 | 0xa6 | 0xb6 | 0xae | 0xbe => {
|
||||
self.ldx(&opcode.mode);
|
||||
}
|
||||
|
||||
/* LDY */
|
||||
0xa0 | 0xa4 | 0xb4 | 0xac | 0xbc => {
|
||||
self.ldy(&opcode.mode);
|
||||
}
|
||||
|
||||
/* NOP */
|
||||
0xea => {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
/* TAY */
|
||||
0xa8 => {
|
||||
self.register_y = self.register_a;
|
||||
self.update_zero_and_negative_flags(self.register_y);
|
||||
}
|
||||
|
||||
/* TSX */
|
||||
0xba => {
|
||||
self.register_x = self.stack_pointer;
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
/* TXA */
|
||||
0x8a => {
|
||||
self.register_a = self.register_x;
|
||||
self.update_zero_and_negative_flags(self.register_a);
|
||||
}
|
||||
|
||||
/* TXS */
|
||||
0x9a => {
|
||||
self.stack_pointer = self.register_x;
|
||||
}
|
||||
|
||||
/* TYA */
|
||||
0x98 => {
|
||||
self.register_a = self.register_y;
|
||||
self.update_zero_and_negative_flags(self.register_a);
|
||||
}
|
||||
|
||||
_ => todo!(),
|
||||
}
|
||||
|
||||
if program_counter_state == self.program_counter {
|
||||
self.program_counter += (opcode.len - 1) as u16;
|
||||
}
|
||||
|
||||
callback(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_0xa9_lda_immediate_load_data() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.load_and_run(vec![0xa9, 0x05, 0x00]);
|
||||
assert_eq!(cpu.register_a, 5);
|
||||
assert!(cpu.status.bits() & 0b0000_0010 == 0b00);
|
||||
assert!(cpu.status.bits() & 0b1000_0000 == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_0xaa_tax_move_a_to_x() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.load(vec![0xaa, 0x00]);
|
||||
cpu.reset();
|
||||
cpu.register_a = 10;
|
||||
cpu.run();
|
||||
|
||||
assert_eq!(cpu.register_x, 10)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_5_ops_working_together() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.load_and_run(vec![0xa9, 0xc0, 0xaa, 0xe8, 0x00]);
|
||||
|
||||
assert_eq!(cpu.register_x, 0xc1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inx_overflow() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.load(vec![0xe8, 0xe8, 0x00]);
|
||||
cpu.reset();
|
||||
cpu.register_x = 0xff;
|
||||
cpu.run();
|
||||
|
||||
assert_eq!(cpu.register_x, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lda_from_memory() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.mem_write(0x10, 0x55);
|
||||
|
||||
cpu.load_and_run(vec![0xa5, 0x10, 0x00]);
|
||||
|
||||
assert_eq!(cpu.register_a, 0x55);
|
||||
}
|
||||
}
|
||||
141
com/src/main.rs
Normal file
141
com/src/main.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
pub mod cpu;
|
||||
pub mod opcodes;
|
||||
use cpu::Mem;
|
||||
use cpu::CPU;
|
||||
use rand::Rng;
|
||||
use sdl2::event::Event;
|
||||
use sdl2::EventPump;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::pixels::PixelFormatEnum;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
fn color(byte: u8) -> Color {
|
||||
match byte {
|
||||
0 => sdl2::pixels::Color::BLACK,
|
||||
1 => sdl2::pixels::Color::WHITE,
|
||||
2 | 9 => sdl2::pixels::Color::GREY,
|
||||
3 | 10 => sdl2::pixels::Color::RED,
|
||||
4 | 11 => sdl2::pixels::Color::GREEN,
|
||||
5 | 12 => sdl2::pixels::Color::BLUE,
|
||||
6 | 13 => sdl2::pixels::Color::MAGENTA,
|
||||
7 | 14 => sdl2::pixels::Color::YELLOW,
|
||||
_ => sdl2::pixels::Color::CYAN,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_screen_state(cpu: &CPU, frame: &mut [u8; 32 * 3 * 32]) -> bool {
|
||||
let mut frame_idx = 0;
|
||||
let mut update = false;
|
||||
for i in 0x0200..0x600 {
|
||||
let color_idx = cpu.mem_read(i as u16);
|
||||
let (b1, b2, b3) = color(color_idx).rgb();
|
||||
if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 {
|
||||
frame[frame_idx] = b1;
|
||||
frame[frame_idx + 1] = b2;
|
||||
frame[frame_idx + 2] = b3;
|
||||
update = true;
|
||||
}
|
||||
frame_idx += 3;
|
||||
}
|
||||
update
|
||||
}
|
||||
|
||||
fn handle_user_input(cpu: &mut CPU, event_pump: &mut EventPump) {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
||||
std::process::exit(0)
|
||||
},
|
||||
Event::KeyDown { keycode: Some(Keycode::W), .. } => {
|
||||
cpu.mem_write(0xff, 0x77);
|
||||
},
|
||||
Event::KeyDown { keycode: Some(Keycode::S), .. } => {
|
||||
cpu.mem_write(0xff, 0x73);
|
||||
},
|
||||
Event::KeyDown { keycode: Some(Keycode::A), .. } => {
|
||||
cpu.mem_write(0xff, 0x61);
|
||||
},
|
||||
Event::KeyDown { keycode: Some(Keycode::D), .. } => {
|
||||
cpu.mem_write(0xff, 0x64);
|
||||
}
|
||||
_ => {/* do nothing */}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
// init sdl2
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
let window = video_subsystem
|
||||
.window("Snake game", (32.0 * 10.0) as u32, (32.0 * 10.0) as u32)
|
||||
.position_centered()
|
||||
.build().unwrap();
|
||||
|
||||
let mut canvas = window.into_canvas().present_vsync().build().unwrap();
|
||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
canvas.set_scale(10.0, 10.0).unwrap();
|
||||
|
||||
let creator = canvas.texture_creator();
|
||||
let mut texture = creator
|
||||
.create_texture_target(PixelFormatEnum::RGB24, 32, 32).unwrap();
|
||||
|
||||
|
||||
let game_code = vec![
|
||||
0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02,
|
||||
0x85, 0x02, 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9,
|
||||
0x0f, 0x85, 0x14, 0xa9, 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85,
|
||||
0x00, 0xa5, 0xfe, 0x29, 0x03, 0x18, 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20,
|
||||
0x8d, 0x06, 0x20, 0xc3, 0x06, 0x20, 0x19, 0x07, 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c,
|
||||
0x38, 0x06, 0xa5, 0xff, 0xc9, 0x77, 0xf0, 0x0d, 0xc9, 0x64, 0xf0, 0x14, 0xc9, 0x73, 0xf0,
|
||||
0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26, 0xa9, 0x01, 0x85,
|
||||
0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9, 0x01,
|
||||
0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05,
|
||||
0xa9, 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00,
|
||||
0xc5, 0x10, 0xd0, 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, 0xe6, 0x03, 0xe6, 0x03, 0x20,
|
||||
0x2a, 0x06, 0x60, 0xa2, 0x02, 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, 0xb5, 0x11, 0xc5, 0x11,
|
||||
0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0, 0x06, 0x4c, 0xaa, 0x06, 0x4c, 0x35, 0x07, 0x60,
|
||||
0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca, 0x10, 0xf9, 0xa5, 0x02, 0x4a, 0xb0,
|
||||
0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5, 0x10, 0x38, 0xe9, 0x20,
|
||||
0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28, 0x60, 0xe6,
|
||||
0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10,
|
||||
0xb0, 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5,
|
||||
0x10, 0x29, 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe,
|
||||
0x91, 0x00, 0x60, 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0xa2, 0x00, 0xa9, 0x01, 0x81, 0x10,
|
||||
0x60, 0xa6, 0xff, 0xea, 0xea, 0xca, 0xd0, 0xfb, 0x60,
|
||||
];
|
||||
|
||||
|
||||
//load the game
|
||||
let mut cpu = CPU::new();
|
||||
cpu.load(game_code);
|
||||
cpu.reset();
|
||||
|
||||
let mut screen_state = [0 as u8; 32 * 3 * 32];
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// run the game cycle
|
||||
cpu.run_with_callback(move |cpu| {
|
||||
handle_user_input(cpu, &mut event_pump);
|
||||
|
||||
cpu.mem_write(0xfe, rng.gen_range(1..16));
|
||||
|
||||
if read_screen_state(cpu, &mut screen_state) {
|
||||
texture.update(None, &screen_state, 32 * 3).unwrap();
|
||||
|
||||
canvas.copy(&texture, None, None).unwrap();
|
||||
|
||||
canvas.present();
|
||||
}
|
||||
|
||||
::std::thread::sleep(std::time::Duration::new(0, 70_000));
|
||||
});
|
||||
|
||||
}
|
||||
230
com/src/opcodes.rs
Normal file
230
com/src/opcodes.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use crate::cpu::AddressingMode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct OpCode {
|
||||
pub code: u8,
|
||||
pub mnemonic: &'static str,
|
||||
pub len: u8,
|
||||
pub cycles: u8,
|
||||
pub mode: AddressingMode,
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
fn new(code: u8, mnemonic: &'static str, len: u8, cycles: u8, mode: AddressingMode) -> Self {
|
||||
OpCode {
|
||||
code,
|
||||
mnemonic,
|
||||
len,
|
||||
cycles,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CPU_OPS_CODES: Vec<OpCode> = vec![
|
||||
OpCode::new(0x00, "BRK", 1, 7, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xea, "NOP", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
/* Arithmetic */
|
||||
OpCode::new(0x69, "ADC", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0x65, "ADC", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x75, "ADC", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x6d, "ADC", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x7d, "ADC", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0x79, "ADC", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0x61, "ADC", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0x71, "ADC", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0xe9, "SBC", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xe5, "SBC", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xf5, "SBC", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xed, "SBC", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xfd, "SBC", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0xf9, "SBC", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0xe1, "SBC", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0xf1, "SBC", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0x29, "AND", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0x25, "AND", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x35, "AND", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x2d, "AND", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x3d, "AND", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0x39, "AND", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0x21, "AND", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0x31, "AND", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0x49, "EOR", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0x45, "EOR", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x55, "EOR", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x4d, "EOR", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x5d, "EOR", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0x59, "EOR", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0x41, "EOR", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0x51, "EOR", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0x09, "ORA", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0x05, "ORA", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x15, "ORA", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x0d, "ORA", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x1d, "ORA", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0x19, "ORA", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0x01, "ORA", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0x11, "ORA", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
/* Shifts */
|
||||
OpCode::new(0x0a, "ASL", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x06, "ASL", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x16, "ASL", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x0e, "ASL", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0x1e, "ASL", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0x4a, "LSR", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x46, "LSR", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x56, "LSR", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x4e, "LSR", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0x5e, "LSR", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0x2a, "ROL", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x26, "ROL", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x36, "ROL", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x2e, "ROL", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0x3e, "ROL", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0x6a, "ROR", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x66, "ROR", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x76, "ROR", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x6e, "ROR", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0x7e, "ROR", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0xe6, "INC", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xf6, "INC", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xee, "INC", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0xfe, "INC", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0xe8, "INX", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xc8, "INY", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0xc6, "DEC", 2, 5, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xd6, "DEC", 2, 6, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xce, "DEC", 3, 6, AddressingMode::Absolute),
|
||||
OpCode::new(0xde, "DEC", 3, 7, AddressingMode::AbsoluteX),
|
||||
|
||||
OpCode::new(0xca, "DEX", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x88, "DEY", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0xc9, "CMP", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xc5, "CMP", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xd5, "CMP", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xcd, "CMP", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xdd, "CMP", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0xd9, "CMP", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0xc1, "CMP", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0xd1, "CMP", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0xc0, "CPY", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xc4, "CPY", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xcc, "CPY", 3, 4, AddressingMode::Absolute),
|
||||
|
||||
OpCode::new(0xe0, "CPX", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xe4, "CPX", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xec, "CPX", 3, 4, AddressingMode::Absolute),
|
||||
|
||||
|
||||
/* Branching */
|
||||
|
||||
OpCode::new(0x4c, "JMP", 3, 3, AddressingMode::NoneAddressing), //AddressingMode that acts as Immidiate
|
||||
OpCode::new(0x6c, "JMP", 3, 5, AddressingMode::NoneAddressing), //AddressingMode:Indirect with 6502 bug
|
||||
|
||||
OpCode::new(0x20, "JSR", 3, 6, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x60, "RTS", 1, 6, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0x40, "RTI", 1, 6, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0xd0, "BNE", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x70, "BVS", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x50, "BVC", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x30, "BMI", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xf0, "BEQ", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xb0, "BCS", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x90, "BCC", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x10, "BPL", 2, 2 /*(+1 if branch succeeds +2 if to a new page)*/, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0x24, "BIT", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x2c, "BIT", 3, 4, AddressingMode::Absolute),
|
||||
|
||||
|
||||
/* Stores, Loads */
|
||||
OpCode::new(0xa9, "LDA", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xa5, "LDA", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xb5, "LDA", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xad, "LDA", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xbd, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0xb9, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0xa1, "LDA", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0xb1, "LDA", 2, 5/*+1 if page crossed*/, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0xa2, "LDX", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xa6, "LDX", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xb6, "LDX", 2, 4, AddressingMode::ZeroPageY),
|
||||
OpCode::new(0xae, "LDX", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xbe, "LDX", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteY),
|
||||
|
||||
OpCode::new(0xa0, "LDY", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xa4, "LDY", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xb4, "LDY", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0xac, "LDY", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xbc, "LDY", 3, 4/*+1 if page crossed*/, AddressingMode::AbsoluteX),
|
||||
|
||||
|
||||
OpCode::new(0x85, "STA", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x95, "STA", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x8d, "STA", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x9d, "STA", 3, 5, AddressingMode::AbsoluteX),
|
||||
OpCode::new(0x99, "STA", 3, 5, AddressingMode::AbsoluteY),
|
||||
OpCode::new(0x81, "STA", 2, 6, AddressingMode::IndirectX),
|
||||
OpCode::new(0x91, "STA", 2, 6, AddressingMode::IndirectY),
|
||||
|
||||
OpCode::new(0x86, "STX", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x96, "STX", 2, 4, AddressingMode::ZeroPageY),
|
||||
OpCode::new(0x8e, "STX", 3, 4, AddressingMode::Absolute),
|
||||
|
||||
OpCode::new(0x84, "STY", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x94, "STY", 2, 4, AddressingMode::ZeroPageX),
|
||||
OpCode::new(0x8c, "STY", 3, 4, AddressingMode::Absolute),
|
||||
|
||||
|
||||
/* Flags clear */
|
||||
|
||||
OpCode::new(0xD8, "CLD", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x58, "CLI", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xb8, "CLV", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x18, "CLC", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x38, "SEC", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x78, "SEI", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xf8, "SED", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0xaa, "TAX", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xa8, "TAY", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xba, "TSX", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x8a, "TXA", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x9a, "TXS", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x98, "TYA", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
/* Stack */
|
||||
OpCode::new(0x48, "PHA", 1, 3, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x68, "PLA", 1, 4, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x08, "PHP", 1, 3, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0x28, "PLP", 1, 4, AddressingMode::NoneAddressing),
|
||||
|
||||
];
|
||||
|
||||
|
||||
pub static ref OPCODES_MAP: HashMap<u8, &'static OpCode> = {
|
||||
let mut map = HashMap::new();
|
||||
for cpuop in &*CPU_OPS_CODES {
|
||||
map.insert(cpuop.code, cpuop);
|
||||
}
|
||||
map
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "web-dappicom",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
Reference in New Issue
Block a user