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