finish ch3.2
This commit is contained in:
parent
a2cbfbf82d
commit
c8d9a02de1
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "nes_emulator_rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.5.0"
|
283
src/cpu.rs
Normal file
283
src/cpu.rs
Normal file
@ -0,0 +1,283 @@
|
||||
use crate::opcodes;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The `CPU` struct represents the central processing unit of the NES emulator.
|
||||
/// It contains the following fields:
|
||||
/// * `register_a`: The accumulator register, used for arithmetic operations.
|
||||
/// * `register_x`: The index register.
|
||||
/// * `status`: The status register, used to store flags that represent the result of operations.
|
||||
/// * `program_counter`: The program counter register, used to keep track of the current execution point in the program.
|
||||
pub struct CPU {
|
||||
pub register_a: u8,
|
||||
pub register_x: u8,
|
||||
pub register_y: u8,
|
||||
pub status: u8,
|
||||
pub program_counter: u16,
|
||||
memory: [u8; 0xFFFF],
|
||||
}
|
||||
|
||||
impl Default for CPU {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum AddressingMode {
|
||||
Immediate,
|
||||
ZeroPage,
|
||||
ZeroPage_X,
|
||||
ZeroPage_Y,
|
||||
Absolute,
|
||||
Absolute_X,
|
||||
Absolute_Y,
|
||||
Indirect_X,
|
||||
Indirect_Y,
|
||||
NoneAddressing,
|
||||
}
|
||||
|
||||
impl CPU {
|
||||
pub fn new() -> Self {
|
||||
CPU {
|
||||
register_a: 0,
|
||||
register_x: 0,
|
||||
register_y: 0,
|
||||
status: 0,
|
||||
program_counter: 0,
|
||||
memory: [0; 0xFFFF],
|
||||
}
|
||||
}
|
||||
|
||||
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[0x8000..(0x8000 + program.len())].copy_from_slice(&program[..]);
|
||||
self.mem_write_u16(0xFFFC, 0x8000);
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let 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_or_else(|| panic!("opcode {:x} is not supported", code));
|
||||
match code {
|
||||
0xa9 | 0xa5 | 0xb5 | 0xad | 0xbd | 0xb9 | 0xa1 | 0xb1 => {
|
||||
self.lda(&opcode.mode);
|
||||
}
|
||||
|
||||
0x85 | 0x95 | 0x8d | 0x9d | 0x99 | 0x81 | 0x91 => {
|
||||
self.sta(&opcode.mode);
|
||||
}
|
||||
|
||||
0xAA => self.tax(),
|
||||
0xe8 => self.inx(),
|
||||
0x00 => return,
|
||||
|
||||
_ => todo!(),
|
||||
}
|
||||
|
||||
if program_counter_state == self.program_counter {
|
||||
self.program_counter += (opcode.len - 1) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.register_a = 0;
|
||||
self.register_x = 0;
|
||||
self.register_y = 0;
|
||||
self.status = 0;
|
||||
self.program_counter = self.mem_read_u16(0xFFFC);
|
||||
}
|
||||
|
||||
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::ZeroPage_X => {
|
||||
let pos = self.mem_read(self.program_counter);
|
||||
let addr = pos.wrapping_add(self.register_x);
|
||||
addr as u16
|
||||
}
|
||||
AddressingMode::ZeroPage_Y => {
|
||||
let pos = self.mem_read(self.program_counter);
|
||||
let addr = pos.wrapping_add(self.register_y);
|
||||
addr as u16
|
||||
}
|
||||
AddressingMode::Absolute_X => {
|
||||
let base = self.mem_read_u16(self.program_counter);
|
||||
base.wrapping_add(self.register_x as u16)
|
||||
}
|
||||
AddressingMode::Absolute_Y => {
|
||||
let base = self.mem_read_u16(self.program_counter);
|
||||
base.wrapping_add(self.register_y as u16)
|
||||
}
|
||||
AddressingMode::Indirect_X => {
|
||||
let base = self.mem_read(self.program_counter);
|
||||
|
||||
let ptr: u8 = base.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::Indirect_Y => {
|
||||
let base = self.mem_read(self.program_counter);
|
||||
|
||||
let lo = self.mem_read(base as u16);
|
||||
let hi = self.mem_read(base.wrapping_add(1) as u16);
|
||||
let deref_base = (hi as u16) << 8 | (lo as u16);
|
||||
deref_base.wrapping_add(self.register_y as u16)
|
||||
}
|
||||
AddressingMode::NoneAddressing => {
|
||||
panic!("mode {:?} is not supported", mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// LDA - Load accumulator
|
||||
/// Loads a byte of memory into the accumulator setting the zero and negative flags as appropriate.
|
||||
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);
|
||||
}
|
||||
|
||||
/// STA - Store Accumulator
|
||||
/// Stores the contents of the accumulator into memory.
|
||||
fn sta(&mut self, mode: &AddressingMode) {
|
||||
let addr = self.get_operand_address(mode);
|
||||
self.mem_write(addr, self.register_a);
|
||||
}
|
||||
|
||||
/// TAX - Transfer Accumulator to X
|
||||
/// Copies the current contents of the accumulator into the X register and sets the zero and negative flags as appropriate.
|
||||
fn tax(&mut self) {
|
||||
self.register_x = self.register_a;
|
||||
self.update_zero_and_negative_flags(self.register_x);
|
||||
}
|
||||
|
||||
/// INX - Increment X Register
|
||||
/// Adds one to the X register setting the zero and negative flags as appropriate.
|
||||
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 |= 0b0000_0010;
|
||||
} else {
|
||||
self.status &= 0b1111_1101;
|
||||
}
|
||||
|
||||
if result & 0b1000_0000 != 0 {
|
||||
self.status |= 0b1000_0000;
|
||||
} else {
|
||||
self.status &= 0b0111_1111;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_0xa9_lda_immediate_load_data() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.reset();
|
||||
cpu.load_and_run(vec![0xa9, 0x05, 0x00]);
|
||||
assert_eq!(cpu.register_a, 5);
|
||||
assert_eq!(cpu.status & 0b0000_0010, 0);
|
||||
assert_eq!(cpu.status & 0b1000_0000, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_0xa9_lda_zero_flag() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.reset();
|
||||
cpu.load_and_run(vec![0xa9, 0x00, 0x00]);
|
||||
assert_eq!(cpu.status & 0b0000_0010, 0b10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_0xaa_tax_move_a_to_x() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.reset();
|
||||
cpu.load_and_run(vec![0xa9, 0x0A, 0xaa, 0x00]);
|
||||
|
||||
assert_eq!(cpu.register_x, 10)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_5_ops_working_together() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.reset();
|
||||
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.reset();
|
||||
cpu.load_and_run(vec![0xa9, 0xff, 0xaa, 0xe8, 0xe8, 0x00]);
|
||||
|
||||
assert_eq!(cpu.register_x, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lda_from_memory() {
|
||||
let mut cpu = CPU::new();
|
||||
cpu.reset();
|
||||
cpu.mem_write(0x10, 0x55);
|
||||
cpu.load_and_run(vec![0xa5, 0x10, 0x00]);
|
||||
|
||||
assert_eq!(cpu.register_a, 0x55);
|
||||
}
|
||||
}
|
9
src/main.rs
Normal file
9
src/main.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub mod cpu;
|
||||
pub mod opcodes;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
57
src/opcodes.rs
Normal file
57
src/opcodes.rs
Normal file
@ -0,0 +1,57 @@
|
||||
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(0xaa, "TAX", 1, 2, AddressingMode::NoneAddressing),
|
||||
OpCode::new(0xe8, "INX", 1, 2, AddressingMode::NoneAddressing),
|
||||
|
||||
OpCode::new(0xa9, "LDA", 2, 2, AddressingMode::Immediate),
|
||||
OpCode::new(0xa5, "LDA", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0xb5, "LDA", 2, 4, AddressingMode::ZeroPage_X),
|
||||
OpCode::new(0xad, "LDA", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0xbd, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::Absolute_X),
|
||||
OpCode::new(0xb9, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::Absolute_Y),
|
||||
OpCode::new(0xa1, "LDA", 2, 6, AddressingMode::Indirect_X),
|
||||
OpCode::new(0xb1, "LDA", 2, 5/*+1 if page crossed*/, AddressingMode::Indirect_Y),
|
||||
|
||||
OpCode::new(0x85, "STA", 2, 3, AddressingMode::ZeroPage),
|
||||
OpCode::new(0x95, "STA", 2, 4, AddressingMode::ZeroPage_X),
|
||||
OpCode::new(0x8d, "STA", 3, 4, AddressingMode::Absolute),
|
||||
OpCode::new(0x9d, "STA", 3, 5, AddressingMode::Absolute_X),
|
||||
OpCode::new(0x99, "STA", 3, 5, AddressingMode::Absolute_Y),
|
||||
OpCode::new(0x81, "STA", 2, 6, AddressingMode::Indirect_X),
|
||||
OpCode::new(0x91, "STA", 2, 6, AddressingMode::Indirect_Y),
|
||||
|
||||
];
|
||||
|
||||
|
||||
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
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user