New project, some CPU operations emulated

This commit is contained in:
william 2025-01-11 22:02:13 -05:00
commit d606446c12
23 changed files with 799 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/nesrust.iml" filepath="$PROJECT_DIR$/.idea/nesrust.iml" />
</modules>
</component>
</project>

15
.idea/nesrust.iml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/core/core/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/core/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/core/core/target" />
<excludeFolder url="file://$MODULE_DIR$/core/target" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

16
Cargo.lock generated Normal file
View File

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]]
name = "core"
version = "0.1.0"
dependencies = [
"bitflags",
]

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
#[package]
#name = "nesrust"
#version = "0.1.0"
#edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["core"]

7
core/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "core"
version = "0.1.0"

9
core/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "core"
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.7.0"

211
core/src/cpu/mod.rs Normal file
View File

@ -0,0 +1,211 @@
mod op;
mod operations;
use crate::memory::MemoryBus;
use bitflags::bitflags;
const STACK_ADDR: u16 = 0x0100;
/// Represents a 6502 CPU
struct Cpu<'a> {
/// The registers of the CPU
registers: CpuRegisters,
/// The memory bus accessible by the CPU
memory_bus: &'a MemoryBus,
/// The amount of cycles the CPU will be busy for (won't execute any instruction)
busy_cycle_count: u16,
/// Whether an OAM DMA was triggered (data transfer to the PPU OAM memory)
oam_dma_triggered: bool,
/// Whether an interrupt was requested
nmi_requested: bool,
}
/// Represents the registers of the 6502 CPU
struct CpuRegisters {
/// The program counter
pc: u16,
/// The stack pointer
sp: u8,
/// The accumulator
a: u8,
/// The X general purpose register
x: u8,
/// The Y general purpose register
y: u8,
/// The status flags
status: u8,
}
bitflags! {
pub struct CpuStatus: u8 {
const Carry = 0b00000001;
const Zero = 0b00000010;
const InterruptDisable = 0b00000100;
const Decimal = 0b00001000;
const B = 0b00010000;
const Overflow = 0b01000000;
const Negative = 0b10000000;
}
}
pub trait CpuInternals {
/// Gets a status flag
///
/// # Arguments
/// * `flag` - The status flag to get
fn get_status_flag(&self, flag: CpuStatus) -> bool;
/// Gets a status flag as a byte
///
/// # Arguments
/// * `flag` - The status flag to get
///
/// # Returns
/// `1` if the flag is `true`, `0` if `false`
fn get_status_flag_u8(&self, flag: CpuStatus) -> u8;
/// Gets the next byte in the program
fn program_get_next_byte(&mut self) -> u8;
/// Gets the next word in the program
fn program_get_next_word(&mut self) -> u16;
/// Sets a status flag
///
/// # Arguments
/// * `flag` - The status flag to set
/// * `value` - Whether the flag is set
fn set_status_flag(&mut self, flag: CpuStatus, set: bool);
/// Sets a status flag from a byte.
/// The value `0` will become `false`, and any other value will be `true`.
///
/// # Arguments
/// * `flag` - The status flag to set
/// * `value` - The value of the flag
fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8);
/// Pushes a value to the top of the stack
///
/// # Arguments
/// * `value` - The value to push
fn stack_push(&mut self, value: u8);
/// Pops the value on the top of the stack
///
/// # Returns
/// The byte on the top of the stack
fn stack_pop(&mut self) -> u8;
/// Pushes the context to the top of the stack
/// The context consists of the program counter and the status register.
fn stack_push_context(&mut self);
/// Pops the context from the top of the stack
fn stack_pop_context(&mut self);
}
impl Cpu {
pub fn new<'a>(memory_bus: &mut MemoryBus) -> Self {
Cpu {
registers: CpuRegisters {
pc: 0x8000,
sp: 0xFD,
a: 0,
x: 0,
y: 0,
status: 0x04,
},
memory_bus,
busy_cycle_count: 0,
oam_dma_triggered: false,
nmi_requested: false,
}
}
}
impl CpuInternals for Cpu {
fn get_status_flag(&self, flag: CpuStatus) -> bool {
*self.registers.status & flag
}
fn get_status_flag_u8(&self, flag: CpuStatus) -> u8 {
let status = self.get_status_flag(flag);
if status {
1
} else {
0
}
}
fn program_get_next_byte(&mut self) -> u8 {
let byte = self.memory_bus.get_byte(self.registers.pc);
self.registers.pc += 1;
byte
}
fn program_get_next_word(&mut self) -> u16 {
let word = self.memory_bus.get_word(self.registers.pc);
self.registers.pc += 2;
word
}
fn set_status_flag(&mut self, flag: CpuStatus, set: bool) {
if set {
*self.registers.status |= flag;
} else {
*self.registers.status &= !flag;
}
}
fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8) {
let set = if value == 0 { false } else { true };
self.set_status_flag(flag, set);
}
fn stack_push(&mut self, value: u8) {
assert!(self.registers.sp > 0);
let addr = STACK_ADDR | self.registers.sp as u16;
self.memory_bus.set_byte(addr, value);
self.registers.sp -= 1;
}
fn stack_pop(&mut self) -> u8 {
assert!(self.registers.sp < 0xff);
self.registers.sp += 1;
let addr = STACK_ADDR | self.registers.sp as u16;
self.memory_bus.get_byte(addr)
}
fn stack_push_context(&mut self) {
self.stack_push((self.registers.pc >> 8) as u8);
self.stack_push((self.registers.pc & 0xff) as u8);
self.stack_push(self.registers.status);
}
fn stack_pop_context(&mut self) {
let mut status = self.stack_pop();
status &= 0xEF;
status |= 0x20;
let mut pc = self.stack_pop() as u16;
pc += (self.stack_pop() as u16) << 8;
self.registers.status = status;
self.registers.pc = pc;
}
}

273
core/src/cpu/op.rs Normal file
View File

@ -0,0 +1,273 @@
// From https://github.com/lukexor/tetanes/blob/main/tetanes-core/src/cpu/instr.rs
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[rustfmt::skip]
pub enum OperationType {
ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX,
CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA,
PLP, ROL, ROR, RTI, RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA,
// "Unofficial" opcodes
SKB, IGN, ISB, DCP, AXS, LAS, LAX, AHX, SAX, XAA, SXA, RRA, TAS, SYA, ARR, SRE, ALR, RLA, ANC,
SLO, #[default] XXX
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[rustfmt::skip]
pub enum AddressingMode {
#[default]
IMM,
ZP0, ZPX, ZPY,
ABS, ABX, ABY,
IND, IDX, IDY,
REL, ACC, IMP,
}
use crate::cpu::{Cpu, CpuInternals};
use AddressingMode::{ABS, ABX, ABY, ACC, IDX, IDY, IMM, IMP, IND, REL, ZP0, ZPX, ZPY};
use OperationType::{
ADC, AHX, ALR, ANC, AND, ARR, ASL, AXS, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC,
CLD, CLI, CLV, CMP, CPX, CPY, DCP, DEC, DEX, DEY, EOR, IGN, INC, INX, INY, ISB, JMP, JSR, LAS,
LAX, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC,
SEC, SED, SEI, SKB, SLO, SRE, STA, STX, STY, SXA, SYA, TAS, TAX, TAY, TSX, TXA, TXS, TYA, XAA,
XXX,
};
/// CPU Instruction.
pub struct Instruction(u8, AddressingMode, OperationType, usize);
impl Instruction {
pub const fn opcode(&self) -> u8 {
self.0
}
pub const fn addr_mode(&self) -> AddressingMode {
self.1
}
pub const fn op(&self) -> OperationType {
self.2
}
pub const fn cycles(&self) -> usize {
self.3
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum OperandType {
Accumulator,
Address,
Immediate,
}
pub struct Operand {
operand_type: OperandType,
pub value: u16,
is_page_crossing: bool,
}
impl Operand {
pub const fn value_u8(&self) -> u8 {
self.value as u8
}
pub const fn is_address(&self) -> bool {
self.operand_type == OperandType::Address
}
pub fn read(&self, cpu: &Cpu) -> u8 {
match self.operand_type {
OperandType::Accumulator => cpu.registers.a,
OperandType::Immediate => self.value_u8(),
OperandType::Address => {
let addr = self.value;
cpu.memory_bus.get_byte(addr)
}
}
}
pub fn write(&self, value: u8, cpu: &mut Cpu) {
match self.operand_type {
OperandType::Accumulator => cpu.registers.a = value,
OperandType::Address => {
let addr = self.value;
cpu.memory_bus.set_byte(addr, value);
}
OperandType::Immediate => unreachable!("Cannot write to immediate operand"),
}
}
}
// Operands
impl Cpu {
/// 16x16 grid of 6502 opcodes. Matches datasheet matrix for easy lookup
#[rustfmt::skip]
pub const INSTRUCTIONS: [Instruction; 256] = [
Instruction(0x00, IMM, BRK, 7), Instruction(0x01, IDX, ORA, 6), Instruction(0x02, IMP, XXX, 2), Instruction(0x03, IDX, SLO, 8), Instruction(0x04, ZP0, NOP, 3), Instruction(0x05, ZP0, ORA, 3), Instruction(0x06, ZP0, ASL, 5), Instruction(0x07, ZP0, SLO, 5), Instruction(0x08, IMP, PHP, 3), Instruction(0x09, IMM, ORA, 2), Instruction(0x0A, ACC, ASL, 2), Instruction(0x0B, IMM, ANC, 2), Instruction(0x0C, ABS, NOP, 4), Instruction(0x0D, ABS, ORA, 4), Instruction(0x0E, ABS, ASL, 6), Instruction(0x0F, ABS, SLO, 6),
Instruction(0x10, REL, BPL, 2), Instruction(0x11, IDY, ORA, 5), Instruction(0x12, IMP, XXX, 2), Instruction(0x13, IDY, SLO, 8), Instruction(0x14, ZPX, NOP, 4), Instruction(0x15, ZPX, ORA, 4), Instruction(0x16, ZPX, ASL, 6), Instruction(0x17, ZPX, SLO, 6), Instruction(0x18, IMP, CLC, 2), Instruction(0x19, ABY, ORA, 4), Instruction(0x1A, IMP, NOP, 2), Instruction(0x1B, ABY, SLO, 7), Instruction(0x1C, ABX, IGN, 4), Instruction(0x1D, ABX, ORA, 4), Instruction(0x1E, ABX, ASL, 7), Instruction(0x1F, ABX, SLO, 7),
Instruction(0x20, ABS, JSR, 6), Instruction(0x21, IDX, AND, 6), Instruction(0x22, IMP, XXX, 2), Instruction(0x23, IDX, RLA, 8), Instruction(0x24, ZP0, BIT, 3), Instruction(0x25, ZP0, AND, 3), Instruction(0x26, ZP0, ROL, 5), Instruction(0x27, ZP0, RLA, 5), Instruction(0x28, IMP, PLP, 4), Instruction(0x29, IMM, AND, 2), Instruction(0x2A, ACC, ROL, 2), Instruction(0x2B, IMM, ANC, 2), Instruction(0x2C, ABS, BIT, 4), Instruction(0x2D, ABS, AND, 4), Instruction(0x2E, ABS, ROL, 6), Instruction(0x2F, ABS, RLA, 6),
Instruction(0x30, REL, BMI, 2), Instruction(0x31, IDY, AND, 5), Instruction(0x32, IMP, XXX, 2), Instruction(0x33, IDY, RLA, 8), Instruction(0x34, ZPX, NOP, 4), Instruction(0x35, ZPX, AND, 4), Instruction(0x36, ZPX, ROL, 6), Instruction(0x37, ZPX, RLA, 6), Instruction(0x38, IMP, SEC, 2), Instruction(0x39, ABY, AND, 4), Instruction(0x3A, IMP, NOP, 2), Instruction(0x3B, ABY, RLA, 7), Instruction(0x3C, ABX, IGN, 4), Instruction(0x3D, ABX, AND, 4), Instruction(0x3E, ABX, ROL, 7), Instruction(0x3F, ABX, RLA, 7),
Instruction(0x40, IMP, RTI, 6), Instruction(0x41, IDX, EOR, 6), Instruction(0x42, IMP, XXX, 2), Instruction(0x43, IDX, SRE, 8), Instruction(0x44, ZP0, NOP, 3), Instruction(0x45, ZP0, EOR, 3), Instruction(0x46, ZP0, LSR, 5), Instruction(0x47, ZP0, SRE, 5), Instruction(0x48, IMP, PHA, 3), Instruction(0x49, IMM, EOR, 2), Instruction(0x4A, ACC, LSR, 2), Instruction(0x4B, IMM, ALR, 2), Instruction(0x4C, ABS, JMP, 3), Instruction(0x4D, ABS, EOR, 4), Instruction(0x4E, ABS, LSR, 6), Instruction(0x4F, ABS, SRE, 6),
Instruction(0x50, REL, BVC, 2), Instruction(0x51, IDY, EOR, 5), Instruction(0x52, IMP, XXX, 2), Instruction(0x53, IDY, SRE, 8), Instruction(0x54, ZPX, NOP, 4), Instruction(0x55, ZPX, EOR, 4), Instruction(0x56, ZPX, LSR, 6), Instruction(0x57, ZPX, SRE, 6), Instruction(0x58, IMP, CLI, 2), Instruction(0x59, ABY, EOR, 4), Instruction(0x5A, IMP, NOP, 2), Instruction(0x5B, ABY, SRE, 7), Instruction(0x5C, ABX, IGN, 4), Instruction(0x5D, ABX, EOR, 4), Instruction(0x5E, ABX, LSR, 7), Instruction(0x5F, ABX, SRE, 7),
Instruction(0x60, IMP, RTS, 6), Instruction(0x61, IDX, ADC, 6), Instruction(0x62, IMP, XXX, 2), Instruction(0x63, IDX, RRA, 8), Instruction(0x64, ZP0, NOP, 3), Instruction(0x65, ZP0, ADC, 3), Instruction(0x66, ZP0, ROR, 5), Instruction(0x67, ZP0, RRA, 5), Instruction(0x68, IMP, PLA, 4), Instruction(0x69, IMM, ADC, 2), Instruction(0x6A, ACC, ROR, 2), Instruction(0x6B, IMM, ARR, 2), Instruction(0x6C, IND, JMP, 5), Instruction(0x6D, ABS, ADC, 4), Instruction(0x6E, ABS, ROR, 6), Instruction(0x6F, ABS, RRA, 6),
Instruction(0x70, REL, BVS, 2), Instruction(0x71, IDY, ADC, 5), Instruction(0x72, IMP, XXX, 2), Instruction(0x73, IDY, RRA, 8), Instruction(0x74, ZPX, NOP, 4), Instruction(0x75, ZPX, ADC, 4), Instruction(0x76, ZPX, ROR, 6), Instruction(0x77, ZPX, RRA, 6), Instruction(0x78, IMP, SEI, 2), Instruction(0x79, ABY, ADC, 4), Instruction(0x7A, IMP, NOP, 2), Instruction(0x7B, ABY, RRA, 7), Instruction(0x7C, ABX, IGN, 4), Instruction(0x7D, ABX, ADC, 4), Instruction(0x7E, ABX, ROR, 7), Instruction(0x7F, ABX, RRA, 7),
Instruction(0x80, IMM, SKB, 2), Instruction(0x81, IDX, STA, 6), Instruction(0x82, IMM, SKB, 2), Instruction(0x83, IDX, SAX, 6), Instruction(0x84, ZP0, STY, 3), Instruction(0x85, ZP0, STA, 3), Instruction(0x86, ZP0, STX, 3), Instruction(0x87, ZP0, SAX, 3), Instruction(0x88, IMP, DEY, 2), Instruction(0x89, IMM, SKB, 2), Instruction(0x8A, IMP, TXA, 2), Instruction(0x8B, IMM, XAA, 2), Instruction(0x8C, ABS, STY, 4), Instruction(0x8D, ABS, STA, 4), Instruction(0x8E, ABS, STX, 4), Instruction(0x8F, ABS, SAX, 4),
Instruction(0x90, REL, BCC, 2), Instruction(0x91, IDY, STA, 6), Instruction(0x92, IMP, XXX, 2), Instruction(0x93, IDY, AHX, 6), Instruction(0x94, ZPX, STY, 4), Instruction(0x95, ZPX, STA, 4), Instruction(0x96, ZPY, STX, 4), Instruction(0x97, ZPY, SAX, 4), Instruction(0x98, IMP, TYA, 2), Instruction(0x99, ABY, STA, 5), Instruction(0x9A, IMP, TXS, 2), Instruction(0x9B, ABY, TAS, 5), Instruction(0x9C, ABX, SYA, 5), Instruction(0x9D, ABX, STA, 5), Instruction(0x9E, ABY, SXA, 5), Instruction(0x9F, ABY, AHX, 5),
Instruction(0xA0, IMM, LDY, 2), Instruction(0xA1, IDX, LDA, 6), Instruction(0xA2, IMM, LDX, 2), Instruction(0xA3, IDX, LAX, 6), Instruction(0xA4, ZP0, LDY, 3), Instruction(0xA5, ZP0, LDA, 3), Instruction(0xA6, ZP0, LDX, 3), Instruction(0xA7, ZP0, LAX, 3), Instruction(0xA8, IMP, TAY, 2), Instruction(0xA9, IMM, LDA, 2), Instruction(0xAA, IMP, TAX, 2), Instruction(0xAB, IMM, LAX, 2), Instruction(0xAC, ABS, LDY, 4), Instruction(0xAD, ABS, LDA, 4), Instruction(0xAE, ABS, LDX, 4), Instruction(0xAF, ABS, LAX, 4),
Instruction(0xB0, REL, BCS, 2), Instruction(0xB1, IDY, LDA, 5), Instruction(0xB2, IMP, XXX, 2), Instruction(0xB3, IDY, LAX, 5), Instruction(0xB4, ZPX, LDY, 4), Instruction(0xB5, ZPX, LDA, 4), Instruction(0xB6, ZPY, LDX, 4), Instruction(0xB7, ZPY, LAX, 4), Instruction(0xB8, IMP, CLV, 2), Instruction(0xB9, ABY, LDA, 4), Instruction(0xBA, IMP, TSX, 2), Instruction(0xBB, ABY, LAS, 4), Instruction(0xBC, ABX, LDY, 4), Instruction(0xBD, ABX, LDA, 4), Instruction(0xBE, ABY, LDX, 4), Instruction(0xBF, ABY, LAX, 4),
Instruction(0xC0, IMM, CPY, 2), Instruction(0xC1, IDX, CMP, 6), Instruction(0xC2, IMM, SKB, 2), Instruction(0xC3, IDX, DCP, 8), Instruction(0xC4, ZP0, CPY, 3), Instruction(0xC5, ZP0, CMP, 3), Instruction(0xC6, ZP0, DEC, 5), Instruction(0xC7, ZP0, DCP, 5), Instruction(0xC8, IMP, INY, 2), Instruction(0xC9, IMM, CMP, 2), Instruction(0xCA, IMP, DEX, 2), Instruction(0xCB, IMM, AXS, 2), Instruction(0xCC, ABS, CPY, 4), Instruction(0xCD, ABS, CMP, 4), Instruction(0xCE, ABS, DEC, 6), Instruction(0xCF, ABS, DCP, 6),
Instruction(0xD0, REL, BNE, 2), Instruction(0xD1, IDY, CMP, 5), Instruction(0xD2, IMP, XXX, 2), Instruction(0xD3, IDY, DCP, 8), Instruction(0xD4, ZPX, NOP, 4), Instruction(0xD5, ZPX, CMP, 4), Instruction(0xD6, ZPX, DEC, 6), Instruction(0xD7, ZPX, DCP, 6), Instruction(0xD8, IMP, CLD, 2), Instruction(0xD9, ABY, CMP, 4), Instruction(0xDA, IMP, NOP, 2), Instruction(0xDB, ABY, DCP, 7), Instruction(0xDC, ABX, IGN, 4), Instruction(0xDD, ABX, CMP, 4), Instruction(0xDE, ABX, DEC, 7), Instruction(0xDF, ABX, DCP, 7),
Instruction(0xE0, IMM, CPX, 2), Instruction(0xE1, IDX, SBC, 6), Instruction(0xE2, IMM, SKB, 2), Instruction(0xE3, IDX, ISB, 8), Instruction(0xE4, ZP0, CPX, 3), Instruction(0xE5, ZP0, SBC, 3), Instruction(0xE6, ZP0, INC, 5), Instruction(0xE7, ZP0, ISB, 5), Instruction(0xE8, IMP, INX, 2), Instruction(0xE9, IMM, SBC, 2), Instruction(0xEA, IMP, NOP, 2), Instruction(0xEB, IMM, SBC, 2), Instruction(0xEC, ABS, CPX, 4), Instruction(0xED, ABS, SBC, 4), Instruction(0xEE, ABS, INC, 6), Instruction(0xEF, ABS, ISB, 6),
Instruction(0xF0, REL, BEQ, 2), Instruction(0xF1, IDY, SBC, 5), Instruction(0xF2, IMP, XXX, 2), Instruction(0xF3, IDY, ISB, 8), Instruction(0xF4, ZPX, NOP, 4), Instruction(0xF5, ZPX, SBC, 4), Instruction(0xF6, ZPX, INC, 6), Instruction(0xF7, ZPX, ISB, 6), Instruction(0xF8, IMP, SED, 2), Instruction(0xF9, ABY, SBC, 4), Instruction(0xFA, IMP, NOP, 2), Instruction(0xFB, ABY, ISB, 7), Instruction(0xFC, ABX, IGN, 4), Instruction(0xFD, ABX, SBC, 4), Instruction(0xFE, ABX, INC, 7), Instruction(0xFF, ABX, ISB, 7),
];
pub fn read_next_instruction(&mut self) -> Instruction {
let op_code = self.program_get_next_byte();
let instruction = Instruction[op_code];
instruction
}
/// Decodes the next operand in memory
///
/// # Arguments
/// * `addr_mode` - The addressing mode of the instruction
fn operand_decode(&mut self, addr_mode: AddressingMode) -> Operand {
if addr_mode == ACC {
Self::operand_decode_acc()
} else if addr_mode == IMM {
self.operand_decode_immediate()
} else {
// Other modes are addresses
let mut page_crossed = false;
let addr = match addr_mode {
ZP0 => self.operand_addr_zp(),
ZPX => self.operand_addr_zpx(),
ZPY => self.operand_addr_zpy(),
ABS => self.operand_addr_abs(),
ABX => self.operand_addr_absx(&mut page_crossed),
ABY => self.operand_addr_absy(&mut page_crossed),
IND => self.operand_addr_indj(),
IDX => self.operand_addr_indx(),
IDY => self.operand_addr_indy(&mut page_crossed),
REL => self.operand_addr_rel(),
_ => unreachable!("Already handled"),
};
Operand {
operand_type: OperandType::Address,
value: addr,
is_page_crossing: page_crossed,
}
}
}
/// A - A
fn operand_decode_acc() -> Operand {
Operand {
operand_type: OperandType::Accumulator,
value: 0,
is_page_crossing: false,
}
}
/// #v - byte
fn operand_decode_immediate(&mut self) -> Operand {
Operand {
operand_type: OperandType::Immediate,
value: self.program_get_next_byte() as u16,
is_page_crossing: false,
}
}
/// d - byte % 256
fn operand_addr_zp(&mut self) -> u16 {
self.program_get_next_byte() as u16
}
/// d,x - (byte + X) % 256
fn operand_addr_zpx(&mut self) -> u16 {
let addr = self.program_get_next_byte() as u16;
(addr + self.registers.x as u16) & 0xFF
}
/// d,y - (byte + Y) % 256
fn operand_addr_zpy(&mut self) -> u16 {
let addr = self.program_get_next_byte() as u16;
(addr + self.registers.y as u16) & 0xFF
}
/// a - word
fn operand_addr_abs(&mut self) -> u16 {
self.program_get_next_word()
}
/// a,x - word + X
fn operand_addr_absx(&mut self, page_crossed: &mut bool) -> u16 {
let addr = self.program_get_next_word();
let indexed_addr = addr + self.registers.x as u16;
*page_crossed = is_page_crossed(addr, indexed_addr);
indexed_addr
}
/// a,y - word + Y
fn operand_addr_absy(&mut self, page_crossed: &mut bool) -> u16 {
let addr = self.program_get_next_word();
let indexed_addr = addr + self.registers.y as u16;
*page_crossed = is_page_crossed(addr, indexed_addr);
indexed_addr
}
/// (a) - PEEK(word)
fn operand_addr_indj(&mut self) -> u16 {
let ref_addr = self.program_get_next_word();
if ref_addr & 0xFF == 0xFF {
// Error in NES CPU for JMP op
let low = self.memory_bus.get_byte(ref_addr) as u16;
let high = self.memory_bus.get_byte(ref_addr & 0xFF00) as u16;
low + (high << 8)
} else {
self.memory_bus.get_word(ref_addr)
}
}
/// (d,x) - PEEK((byte + X) % 256)
fn operand_addr_indx(&mut self) -> u16 {
let mut ref_addr = self.program_get_next_byte() as u16;
ref_addr += self.registers.x as u16;
let low = self.memory_bus.get_byte(ref_addr & 0xFF) as u16;
let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16;
low + (high << 8)
}
/// (d),y - PEEK(byte) + Y
fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 {
let mut ref_addr = self.program_get_next_byte() as u16;
let low = self.memory_bus.get_byte(ref_addr) as u16;
let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16;
let addr = low + (high << 8);
let incr_addr = addr + self.registers.y as u16;
*page_crossed = is_page_crossed(addr, incr_addr);
incr_addr
}
/// label - PC + byte
fn operand_addr_rel(&mut self) -> u16 {
let base_addr = self.registers.pc;
let offset = self.program_get_next_byte() as u16;
base_addr + offset
}
}
fn is_page_crossed(a: u16, b: u16) -> bool {
(a & 0xFF00) != (b & 0xFF00)
}

181
core/src/cpu/operations.rs Normal file
View File

@ -0,0 +1,181 @@
use crate::cpu::op::Operand;
use crate::cpu::{Cpu, CpuInternals, CpuStatus};
fn is_sign_overflow(val1: u8, val2: u8, result: u8) -> bool {
(val1 & 0x80 == val2 & 0x80) && (val1 & 0x80 != result & 0x80)
}
impl Cpu {
fn add_with_carry(&mut self, value: u8) {
let a = self.registers.a;
let addition = a.wrapping_add(value);
let mut overflow = value < a;
let result = addition.wrapping_add(self.get_status_flag_u8(CpuStatus::Carry));
if result < addition {
// The addition resulted in a smaller number, there was overflow
overflow = true;
}
self.registers.a = result;
self.set_status_flag(CpuStatus::Carry, overflow);
self.set_status_flag(CpuStatus::Overflow, is_sign_overflow(a, value, result));
}
fn set_common_flags(&mut self, result: u8) {
self.set_status_flag(CpuStatus::Zero, result == 0);
self.set_status_flag(CpuStatus::Negative, result & 0x80 != 0);
}
// ADC operations
/// Add with carry to accumulator
fn op_adc(&mut self, operand: Operand) {
let value = operand.read(self);
self.add_with_carry(value);
}
/// Unofficial, stores A, X and (high byte of address + 1)
/// Unstable in the hardware
fn op_ahx(&mut self, operand: Operand) {
assert!(operand.is_address());
let addr = operand.value;
let a = self.registers.a;
let x = self.registers.x;
let h = (addr >> 8) as u8;
let result = a & x & h;
operand.write(result, self);
}
/// Unofficial, AND + LSR
fn op_alr(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a & value;
// Sets the dropped bit in the carry register
self.set_status_flag_u8(CpuStatus::Carry, result & 0x01);
let result_shift = result >> 1;
operand.write(result_shift, self);
self.set_common_flags(result_shift);
}
/// Unofficial, bitwise AND and set carry from shift left
fn op_anc(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a & value;
self.registers.a = result;
self.set_common_flags(result);
self.set_status_flag_u8(CpuStatus::Carry, result & 0x80);
}
/// Bitwise AND with accumulator
fn op_and(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a & value;
self.registers.a = result;
self.set_common_flags(result);
}
/// Unofficial, depends on analog effects and cannot be emulated easily.
fn op_ane(&self, _: Operand) {
assert!(false);
}
/// Unofficial, AND + ROR
fn op_arr(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let carry = self.get_status_flag_u8(CpuStatus::Carry);
let result = a & value;
let result_shift = result >> 1 | (carry << 7);
self.registers.a = result_shift;
let result_added = result.wrapping_add(value);
self.set_common_flags(result_shift);
self.set_status_flag_u8(CpuStatus::Carry, result & 0x01);
self.set_status_flag(
CpuStatus::Overflow,
is_sign_overflow(result, value, result_added),
);
}
/// Arithmetic shift left
fn op_asl(&mut self, operand: Operand) {
let value = operand.read(self);
let result = value << 1;
operand.write(result, self);
self.set_common_flags(result);
self.set_status_flag_u8(CpuStatus::Carry, value & 0x80);
}
/// Compare to accumulator
fn op_cmp(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a.wrapping_sub(value);
self.set_status_flag(CpuStatus::Carry, a >= value);
self.set_common_flags(result);
}
/// Bitwise exclusive OR with accumulator
fn op_eor(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a ^ value;
self.registers.a = result;
self.set_common_flags(result);
}
/// Load to accumulator
fn op_lda(&mut self, operand: Operand) {
let value = operand.read(self);
self.registers.a = value;
self.set_common_flags(value);
}
/// Bitwise OR with accumulator
fn op_ora(&mut self, operand: Operand) {
let value = operand.read(self);
let a = self.registers.a;
let result = a | value;
self.registers.a = result;
}
/// Subtract with carry from accumulator
fn op_sbc(&mut self, operand: Operand) {
let value = operand.read(self);
self.add_with_carry(!value);
}
/// Store accumulator
fn op_sta(&mut self, operand: Operand) {
assert!(operand.is_address());
let a = self.registers.a;
operand.write(a, self);
// TODO: C code enabled page crossing in the operand, but I didn't find it in the docs
}
}

13
core/src/lib.rs Normal file
View File

@ -0,0 +1,13 @@
mod cpu;
mod memory;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

32
core/src/memory.rs Normal file
View File

@ -0,0 +1,32 @@
const MEMORY_SIZE: usize = 0x0800;
pub struct MemoryBus {
ram: [u8; MEMORY_SIZE],
}
impl MemoryBus {
/// Gets a byte from memory.
///
/// # Arguments
/// * `addr` - The address of the byte
pub fn get_byte(&self, addr: u16) -> u8 {
unimplemented!("MemoryBus::get_byte");
}
/// Gets a word from memory.
///
/// # Arguments
/// * `addr` - The address of the word
pub fn get_word(&self, addr: u16) -> u16 {
unimplemented!("MemoryBus::get_word");
}
/// Sets a byte in memory
///
/// # Arguments
/// * `addr` - The address of the byte
/// * `value` - The value to set
pub fn set_byte(&mut self, addr: u16, value: u8) {
unimplemented!("MemoryBus::set_byte");
}
}

View File

@ -0,0 +1 @@
{"rustc_fingerprint":52135951790503613,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.74.0-nightly (62ebe3a2b 2023-09-08)\nbinary: rustc\ncommit-hash: 62ebe3a2b177d50ec664798d731b8a8d1a9120d1\ncommit-date: 2023-09-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.74.0-nightly\nLLVM version: 17.0.0\n","stderr":""},"14371922958718593042":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/william/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}

3
core/target/CACHEDIR.TAG Normal file
View File

@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View File

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
8876810ef228e07e

View File

@ -0,0 +1 @@
{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":13126374248311259211,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-0d1f6aefe433fb28/dep-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
d2244b0cb93f6bbb

View File

@ -0,0 +1 @@
{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":18326522262828315194,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-9be0639a870abcef/dep-test-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}