Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Akhmetov committed Apr 23, 2019
0 parents commit 8affede
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
**/*.rs.bk
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "mos6502"
version = "0.1.0"
authors = ["Alexander Akhmetov <[email protected]>"]
edition = "2018"

[dependencies]
Empty file added src/asm.rs
Empty file.
248 changes: 248 additions & 0 deletions src/cpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use crate::operation;
use crate::utils;

pub struct CPU {
a: u8,
x: u8,
y: u8,
s: u8,
p: u8,
pc: u16,
memory: [u8; 64 * 1024],
}

impl CPU {
pub fn new() -> CPU {
CPU {
a: 0,
x: 0,
y: 0,
s: 0,
p: 0,
pc: 0,
memory: [0; 64 * 1024],
}
}

pub fn load(&mut self, buf: &[u8], addr: u16) {
let program_end_addr = buf.len() + addr as usize;
if program_end_addr > 65536 {
panic!("Wrong address to load the program");
}

self.memory[(addr as usize)..program_end_addr].clone_from_slice(buf);
self.pc = addr;
}

pub fn run(&mut self) {
loop {
let operation = operation::by_code(self.memory[self.pc as usize]);

match operation.name {
"BRK" => return,
"LDA" => self.operation_lda(operation),
"ADC" => self.operation_adc(operation),
"STA" => self.operation_sta(operation),
_ => panic!("unknown operation"),
};

self.pc += operation.length as u16;
}
}

fn operation_lda(&mut self, operation: &operation::Operation) {
self.a = self.get_operand(operation) as u8;
}

fn operation_adc(&mut self, operation: &operation::Operation) {
self.a += self.get_operand(operation) as u8;
}

fn operation_sta(&mut self, operation: &operation::Operation) {
let addr = self.next_two_byte_number();
self.memory[addr as usize] = self.a;
}

fn get_operand(&self, operation: &operation::Operation) -> usize {
match operation.addressing {
operation::AddressingMode::Immediate => self.memory[self.pc as usize + 1] as usize,
operation::AddressingMode::Absolute => {
self.memory[self.next_two_byte_number() as usize] as usize
}
_ => panic!("unknown addressing mode"),
}
}

fn next_two_byte_number(&self) -> u16 {
// returns next two bytes (self.pc + 1, self.pc + 2) as one u16
// (converts from little endian)
utils::little_endian_to_u16(
self.memory[self.pc as usize + 1],
self.memory[self.pc as usize + 2],
)
}
}

#[cfg(test)]
mod tests {
use crate::cpu::CPU;
use crate::operation;

#[test]
fn test_new() {
let c = CPU::new();

assert_eq!(c.a, 0);
assert_eq!(c.x, 0);
assert_eq!(c.y, 0);
assert_eq!(c.s, 0);
assert_eq!(c.p, 0);
assert_eq!(c.pc, 0);
assert_eq!(&c.memory[..], &[0; 64 * 1024][..]);
}

#[test]
fn test_load() {
// test when it loads a three bytes dump at a specific location
let mut cpu = CPU::new();

let addr = 32768;
let bytes = [1; 3];

assert_eq!(&cpu.memory[..], &[0; 64 * 1024][..]);
cpu.load(&bytes, addr);

let exp_memory = [0, 1, 1, 1, 0];
assert_eq!(&cpu.memory[32767..32772], exp_memory);

assert_eq!(cpu.pc, addr);
}

#[test]
#[should_panic]
fn test_load_data_too_big() {
let mut cpu = CPU::new();
cpu.load(&[1, 2, 3], 65535);
}

#[test]
fn test_operation_lda() {
let mut cpu = CPU::new();

let program: [u8; 3] = [
operation::by_name("LDA", operation::AddressingMode::Immediate).code,
10,
operation::by_name("BRK", operation::AddressingMode::Immediate).code,
];
cpu.load(&program, 0);

assert_eq!(cpu.a, 0);

cpu.run();

assert_eq!(cpu.a, 10);
}

#[test]
fn test_operation_adc_immediate() {
let mut cpu = CPU::new();
cpu.a = 10;

let program: [u8; 3] = [
operation::by_name("ADC", operation::AddressingMode::Immediate).code,
15,
operation::by_name("BRK", operation::AddressingMode::Immediate).code,
];
cpu.load(&program, 0);

assert_eq!(cpu.a, 10);

cpu.run();

assert_eq!(cpu.a, 25);
}

#[test]
fn test_operation_adc_absolute() {
let mut cpu = CPU::new();
cpu.a = 10;

let program: [u8; 4] = [
operation::by_name("ADC", operation::AddressingMode::Absolute).code,
0xff,
0xaa,
operation::by_name("BRK", operation::AddressingMode::Immediate).code,
];
cpu.load(&program, 0);
cpu.memory[43775] = 15; // 0xaaff

assert_eq!(cpu.a, 10);

cpu.run();

assert_eq!(cpu.a, 25);
}

#[test]
fn test_operation_sta() {
let mut cpu = CPU::new();
cpu.a = 10;

let program: [u8; 4] = [
operation::by_name("STA", operation::AddressingMode::Absolute).code,
50,
0,
operation::by_name("BRK", operation::AddressingMode::Immediate).code,
];
cpu.load(&program, 0);

assert_eq!(cpu.memory[50], 0);

cpu.run();

assert_eq!(cpu.memory[50], cpu.a);
}

#[test]
fn test_next_two_bytes() {
let mut cpu = CPU::new();
let bytes = [0, 0x0a, 0x0b];
cpu.load(&bytes, 0);
assert_eq!(cpu.next_two_byte_number(), 2826);
}

#[test]
fn test_next_two_bytes_with_only_lower_byte() {
let mut cpu = CPU::new();
let bytes = [0, 10, 0];
cpu.load(&bytes, 0);
assert_eq!(cpu.next_two_byte_number(), 10);
}

#[test]
fn test_simple_program() {
// loads 100 from the memory
// adds 7 to the a register
// stores a register at address 0x000f
let mut cpu = CPU::new();
cpu.a = 10;

let program: [u8; 8] = [
operation::by_name("LDA", operation::AddressingMode::Immediate).code,
100,
operation::by_name("ADC", operation::AddressingMode::Immediate).code,
7,
operation::by_name("STA", operation::AddressingMode::Absolute).code,
15,
0,
operation::by_name("BRK", operation::AddressingMode::Immediate).code,
];
cpu.load(&program, 0);

cpu.run();

assert_eq!(cpu.a, 107);
assert_eq!(cpu.memory[15], 107);
}

}
9 changes: 9 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod cpu;
mod operation;
mod utils;
use cpu::CPU;

fn main() {
println!("Hello, world!");
let cpu = CPU::new();
}
96 changes: 96 additions & 0 deletions src/operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#[derive(PartialEq, Eq)]
pub enum AddressingMode {
Immediate = 1,
Absolute,
ZeroPage,
Implied,
IndirectAbsolute,
AbsoluteIndexed,
ZeroPageIndexed,
IndexedIndirect,
IndirectIndexed,
RelativeAddressing,
AccumulatorAddressing,
}

pub struct Operation {
pub name: &'static str,
pub code: u8,
pub length: u8,
pub addressing: AddressingMode,
}

pub static OPERATIONS: &'static [Operation] = &[
Operation {
name: "BRK",
code: 0x00,
length: 1,
addressing: AddressingMode::Immediate,
},
Operation {
name: "LDA",
code: 0xa9,
length: 2,
addressing: AddressingMode::Immediate,
},
Operation {
name: "ADC",
code: 0x69,
length: 2,
addressing: AddressingMode::Immediate,
},
Operation {
name: "ADC",
code: 0x60,
length: 3,
addressing: AddressingMode::Absolute,
},
Operation {
name: "STA",
code: 0x85,
length: 2,
addressing: AddressingMode::Absolute,
},
];

pub fn by_code(code: u8) -> &'static Operation {
let opcode = OPERATIONS.iter().find(|o| o.code == code);

if let Some(opcode) = opcode {
return opcode;
} else {
panic!("unknown operation code");
}
}

pub fn by_name(name: &str, addressing: AddressingMode) -> &Operation {
let opcode = OPERATIONS
.iter()
.find(|o| o.name == name && o.addressing == addressing);

if let Some(opcode) = opcode {
return opcode;
} else {
panic!("unknown operation name");
}
}

#[cfg(test)]
mod tests {
use crate::operation::{by_name, AddressingMode};

macro_rules! test_opcode {
($test_name:ident, $name:expr, $mode:expr, $expected:expr) => {
#[test]
fn $test_name() {
assert_eq!(by_name($name, $mode).code, $expected);
}
};
}

test_opcode!(brk_immediate, "BRK", AddressingMode::Immediate, 0x00);
test_opcode!(lda_immediate, "LDA", AddressingMode::Immediate, 0xa9);
test_opcode!(adc_immediate, "ADC", AddressingMode::Immediate, 0x69);
test_opcode!(adc_absolute, "ADC", AddressingMode::Absolute, 0x60);
test_opcode!(sta_absolute, "STA", AddressingMode::Absolute, 0x85);
}
3 changes: 3 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn little_endian_to_u16(big: u8, little: u8) -> u16 {
(little as u16) << 8 | (big as u16)
}

0 comments on commit 8affede

Please sign in to comment.