mirror of
https://github.com/mre/mos6502.git
synced 2024-11-23 04:33:25 +00:00
Code Cleanup (#97)
This adds some new directives for clippy and fixes all warnings. Also updated the dependencies to their latest versions.
This commit is contained in:
parent
4847744518
commit
467b3ff436
1
.gitignore
vendored
1
.gitignore
vendored
@ -66,3 +66,4 @@ TAGS.vi
|
|||||||
src/.DS_Store
|
src/.DS_Store
|
||||||
tmp.*.rs
|
tmp.*.rs
|
||||||
.vscode
|
.vscode
|
||||||
|
Cargo.lock
|
@ -40,8 +40,8 @@ edition = "2021"
|
|||||||
name = "mos6502"
|
name = "mos6502"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.3.3"
|
bitflags = "2.5.0"
|
||||||
log = "0.4.19"
|
log = "0.4.21"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
decimal_mode = []
|
decimal_mode = []
|
||||||
|
143
src/cpu.rs
143
src/cpu.rs
@ -47,6 +47,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Bus, V: Variant> CPU<M, V> {
|
impl<M: Bus, V: Variant> CPU<M, V> {
|
||||||
|
// Allowing `needless_pass_by_value` to simplify construction. Passing by
|
||||||
|
// value avoids the borrow and improves readability when constructing the
|
||||||
|
// CPU.
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn new(memory: M, _variant: V) -> CPU<M, V> {
|
pub fn new(memory: M, _variant: V) -> CPU<M, V> {
|
||||||
CPU {
|
CPU {
|
||||||
registers: Registers::new(),
|
registers: Registers::new(),
|
||||||
@ -56,10 +60,23 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
//TODO: // should read some bytes from the stack and also get the PC from the reset vector
|
//TODO: should read some bytes from the stack and also get the PC from the reset vector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next byte from memory and decode it into an instruction and addressing mode.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if the instruction is not recognized
|
||||||
|
/// (i.e. the opcode is invalid or has not been implemented).
|
||||||
pub fn fetch_next_and_decode(&mut self) -> Option<DecodedInstr> {
|
pub fn fetch_next_and_decode(&mut self) -> Option<DecodedInstr> {
|
||||||
|
// Helper function to read a 16-bit address from memory
|
||||||
|
fn read_address<M: Bus>(mem: &mut M, addr: u16) -> [u8; 2] {
|
||||||
|
let lo = mem.get_byte(addr);
|
||||||
|
let hi = mem.get_byte(addr.wrapping_add(1));
|
||||||
|
[lo, hi]
|
||||||
|
}
|
||||||
|
|
||||||
let x: u8 = self.memory.get_byte(self.registers.program_counter);
|
let x: u8 = self.memory.get_byte(self.registers.program_counter);
|
||||||
|
|
||||||
match V::decode(x) {
|
match V::decode(x) {
|
||||||
@ -87,12 +104,6 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
|
|
||||||
let memory = &mut self.memory;
|
let memory = &mut self.memory;
|
||||||
|
|
||||||
fn read_address<M: Bus>(mem: &mut M, addr: u16) -> [u8; 2] {
|
|
||||||
let lo = mem.get_byte(addr);
|
|
||||||
let hi = mem.get_byte(addr.wrapping_add(1));
|
|
||||||
[lo, hi]
|
|
||||||
}
|
|
||||||
|
|
||||||
let am_out = match am {
|
let am_out = match am {
|
||||||
AddressingMode::Accumulator | AddressingMode::Implied => {
|
AddressingMode::Accumulator | AddressingMode::Implied => {
|
||||||
// Always the same -- no input
|
// Always the same -- no input
|
||||||
@ -213,24 +224,25 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) {
|
pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) {
|
||||||
match decoded_instr {
|
match decoded_instr {
|
||||||
(Instruction::ADC, OpInput::UseImmediate(val)) => {
|
(Instruction::ADC, OpInput::UseImmediate(val)) => {
|
||||||
debug!("add with carry immediate: {}", val);
|
log::debug!("add with carry immediate: {}", val);
|
||||||
self.add_with_carry(val);
|
self.add_with_carry(val);
|
||||||
}
|
}
|
||||||
(Instruction::ADC, OpInput::UseAddress(addr)) => {
|
(Instruction::ADC, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("add with carry. address: {:?}. value: {}", addr, val);
|
log::debug!("add with carry. address: {:?}. value: {}", addr, val);
|
||||||
self.add_with_carry(val);
|
self.add_with_carry(val);
|
||||||
}
|
}
|
||||||
(Instruction::ADCnd, OpInput::UseImmediate(val)) => {
|
(Instruction::ADCnd, OpInput::UseImmediate(val)) => {
|
||||||
debug!("add with carry immediate: {}", val);
|
log::debug!("add with carry immediate: {}", val);
|
||||||
self.add_with_no_decimal(val);
|
self.add_with_no_decimal(val);
|
||||||
}
|
}
|
||||||
(Instruction::ADCnd, OpInput::UseAddress(addr)) => {
|
(Instruction::ADCnd, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("add with carry. address: {:?}. value: {}", addr, val);
|
log::debug!("add with carry. address: {:?}. value: {}", addr, val);
|
||||||
self.add_with_no_decimal(val);
|
self.add_with_no_decimal(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +313,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
|
|
||||||
(Instruction::BMI, OpInput::UseRelative(rel)) => {
|
(Instruction::BMI, OpInput::UseRelative(rel)) => {
|
||||||
let addr = self.registers.program_counter.wrapping_add(rel);
|
let addr = self.registers.program_counter.wrapping_add(rel);
|
||||||
debug!("branch if minus relative. address: {:?}", addr);
|
log::debug!("branch if minus relative. address: {:?}", addr);
|
||||||
self.branch_if_minus(addr);
|
self.branch_if_minus(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +329,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
self.push_on_stack(self.registers.status.bits());
|
self.push_on_stack(self.registers.status.bits());
|
||||||
let pcl = self.memory.get_byte(0xfffe);
|
let pcl = self.memory.get_byte(0xfffe);
|
||||||
let pch = self.memory.get_byte(0xffff);
|
let pch = self.memory.get_byte(0xffff);
|
||||||
self.jump(((pch as u16) << 8) | pcl as u16);
|
self.jump((u16::from(pch) << 8) | u16::from(pcl));
|
||||||
self.registers.status.or(Status::PS_DISABLE_INTERRUPTS);
|
self.registers.status.or(Status::PS_DISABLE_INTERRUPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,32 +424,32 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::LDA, OpInput::UseImmediate(val)) => {
|
(Instruction::LDA, OpInput::UseImmediate(val)) => {
|
||||||
debug!("load A immediate: {}", val);
|
log::debug!("load A immediate: {}", val);
|
||||||
self.load_accumulator(val);
|
self.load_accumulator(val);
|
||||||
}
|
}
|
||||||
(Instruction::LDA, OpInput::UseAddress(addr)) => {
|
(Instruction::LDA, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load A. address: {:?}. value: {}", addr, val);
|
log::debug!("load A. address: {:?}. value: {}", addr, val);
|
||||||
self.load_accumulator(val);
|
self.load_accumulator(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::LDX, OpInput::UseImmediate(val)) => {
|
(Instruction::LDX, OpInput::UseImmediate(val)) => {
|
||||||
debug!("load X immediate: {}", val);
|
log::debug!("load X immediate: {}", val);
|
||||||
self.load_x_register(val);
|
self.load_x_register(val);
|
||||||
}
|
}
|
||||||
(Instruction::LDX, OpInput::UseAddress(addr)) => {
|
(Instruction::LDX, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load X. address: {:?}. value: {}", addr, val);
|
log::debug!("load X. address: {:?}. value: {}", addr, val);
|
||||||
self.load_x_register(val);
|
self.load_x_register(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::LDY, OpInput::UseImmediate(val)) => {
|
(Instruction::LDY, OpInput::UseImmediate(val)) => {
|
||||||
debug!("load Y immediate: {}", val);
|
log::debug!("load Y immediate: {}", val);
|
||||||
self.load_y_register(val);
|
self.load_y_register(val);
|
||||||
}
|
}
|
||||||
(Instruction::LDY, OpInput::UseAddress(addr)) => {
|
(Instruction::LDY, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load Y. address: {:?}. value: {}", addr, val);
|
log::debug!("load Y. address: {:?}. value: {}", addr, val);
|
||||||
self.load_y_register(val);
|
self.load_y_register(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,32 +539,33 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
self.registers.status = Status::from_bits_truncate(val);
|
self.registers.status = Status::from_bits_truncate(val);
|
||||||
let pcl: u8 = self.pull_from_stack();
|
let pcl: u8 = self.pull_from_stack();
|
||||||
let pch: u8 = self.fetch_from_stack();
|
let pch: u8 = self.fetch_from_stack();
|
||||||
self.registers.program_counter = ((pch as u16) << 8) | pcl as u16;
|
self.registers.program_counter = (u16::from(pch) << 8) | u16::from(pcl);
|
||||||
}
|
}
|
||||||
(Instruction::RTS, OpInput::UseImplied) => {
|
(Instruction::RTS, OpInput::UseImplied) => {
|
||||||
self.pull_from_stack();
|
self.pull_from_stack();
|
||||||
let pcl: u8 = self.pull_from_stack();
|
let pcl: u8 = self.pull_from_stack();
|
||||||
let pch: u8 = self.fetch_from_stack();
|
let pch: u8 = self.fetch_from_stack();
|
||||||
self.registers.program_counter = (((pch as u16) << 8) | pcl as u16).wrapping_add(1);
|
self.registers.program_counter =
|
||||||
|
((u16::from(pch) << 8) | u16::from(pcl)).wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::SBC, OpInput::UseImmediate(val)) => {
|
(Instruction::SBC, OpInput::UseImmediate(val)) => {
|
||||||
debug!("subtract with carry immediate: {}", val);
|
log::debug!("subtract with carry immediate: {}", val);
|
||||||
self.subtract_with_carry(val);
|
self.subtract_with_carry(val);
|
||||||
}
|
}
|
||||||
(Instruction::SBC, OpInput::UseAddress(addr)) => {
|
(Instruction::SBC, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("subtract with carry. address: {:?}. value: {}", addr, val);
|
log::debug!("subtract with carry. address: {:?}. value: {}", addr, val);
|
||||||
self.subtract_with_carry(val);
|
self.subtract_with_carry(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::SBCnd, OpInput::UseImmediate(val)) => {
|
(Instruction::SBCnd, OpInput::UseImmediate(val)) => {
|
||||||
debug!("subtract with carry immediate: {}", val);
|
log::debug!("subtract with carry immediate: {}", val);
|
||||||
self.subtract_with_no_decimal(val);
|
self.subtract_with_no_decimal(val);
|
||||||
}
|
}
|
||||||
(Instruction::SBCnd, OpInput::UseAddress(addr)) => {
|
(Instruction::SBCnd, OpInput::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("subtract with carry. address: {:?}. value: {}", addr, val);
|
log::debug!("subtract with carry. address: {:?}. value: {}", addr, val);
|
||||||
self.subtract_with_no_decimal(val);
|
self.subtract_with_no_decimal(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,10 +618,10 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(Instruction::NOP, OpInput::UseImplied) => {
|
(Instruction::NOP, OpInput::UseImplied) => {
|
||||||
debug!("NOP instruction");
|
log::debug!("NOP instruction");
|
||||||
}
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
debug!(
|
log::debug!(
|
||||||
"attempting to execute unimplemented or invalid \
|
"attempting to execute unimplemented or invalid \
|
||||||
instruction"
|
instruction"
|
||||||
);
|
);
|
||||||
@ -628,23 +641,23 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_flags_from_i8(status: &mut Status, value: i8) {
|
/// Checks if a given `u8` value should be interpreted as negative when
|
||||||
let is_zero = value == 0;
|
/// considered as `i8`.
|
||||||
let is_negative = value < 0;
|
///
|
||||||
|
/// In an 8-bit unsigned integer (`u8`), values range from 0 to 255. When
|
||||||
status.set_with_mask(
|
/// these values are interpreted as signed integers (`i8`), values from 128
|
||||||
Status::PS_ZERO | Status::PS_NEGATIVE,
|
/// to 255 are considered negative, corresponding to the signed range -128
|
||||||
Status::new(StatusArgs {
|
/// to -1. This function checks if the provided `u8` value falls within that
|
||||||
zero: is_zero,
|
/// range, effectively determining if the most significant bit is set, which
|
||||||
negative: is_negative,
|
/// indicates a negative number in two's complement form.
|
||||||
..StatusArgs::none()
|
/// ```
|
||||||
}),
|
const fn value_is_negative(value: u8) -> bool {
|
||||||
);
|
value > 127
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_flags_from_u8(status: &mut Status, value: u8) {
|
fn set_flags_from_u8(status: &mut Status, value: u8) {
|
||||||
let is_zero = value == 0;
|
let is_zero = value == 0;
|
||||||
let is_negative = value > 127;
|
let is_negative = Self::value_is_negative(value);
|
||||||
|
|
||||||
status.set_with_mask(
|
status.set_with_mask(
|
||||||
Status::PS_ZERO | Status::PS_NEGATIVE,
|
Status::PS_ZERO | Status::PS_NEGATIVE,
|
||||||
@ -668,7 +681,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
CPU::<M, V>::set_flags_from_i8(status, *p_val as i8);
|
CPU::<M, V>::set_flags_from_u8(status, *p_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shift_right_with_flags(p_val: &mut u8, status: &mut Status) {
|
fn shift_right_with_flags(p_val: &mut u8, status: &mut Status) {
|
||||||
@ -682,7 +695,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
CPU::<M, V>::set_flags_from_i8(status, *p_val as i8);
|
CPU::<M, V>::set_flags_from_u8(status, *p_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_left_with_flags(p_val: &mut u8, status: &mut Status) {
|
fn rotate_left_with_flags(p_val: &mut u8, status: &mut Status) {
|
||||||
@ -698,7 +711,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
CPU::<M, V>::set_flags_from_i8(status, *p_val as i8);
|
CPU::<M, V>::set_flags_from_u8(status, *p_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_right_with_flags(p_val: &mut u8, status: &mut Status) {
|
fn rotate_right_with_flags(p_val: &mut u8, status: &mut Status) {
|
||||||
@ -714,7 +727,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
CPU::<M, V>::set_flags_from_i8(status, *p_val as i8);
|
CPU::<M, V>::set_flags_from_u8(status, *p_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_u8_with_flags(mem: &mut u8, status: &mut Status, value: u8) {
|
fn set_u8_with_flags(mem: &mut u8, status: &mut Status, value: u8) {
|
||||||
@ -747,7 +760,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_with_carry(&mut self, value: u8) {
|
fn add_with_carry(&mut self, value: u8) {
|
||||||
fn decimal_adjust(result: u8) -> u8 {
|
const fn decimal_adjust(result: u8) -> u8 {
|
||||||
let bcd1: u8 = if (result & 0x0f) > 0x09 { 0x06 } else { 0x00 };
|
let bcd1: u8 = if (result & 0x0f) > 0x09 { 0x06 } else { 0x00 };
|
||||||
|
|
||||||
let bcd2: u8 = if (result.wrapping_add(bcd1) & 0xf0) > 0x90 {
|
let bcd2: u8 = if (result.wrapping_add(bcd1) & 0xf0) > 0x90 {
|
||||||
@ -791,7 +804,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
|
|
||||||
self.load_accumulator(result);
|
self.load_accumulator(result);
|
||||||
|
|
||||||
debug!("accumulator: {}", self.registers.accumulator);
|
log::debug!("accumulator: {}", self.registers.accumulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_with_no_decimal(&mut self, value: u8) {
|
fn add_with_no_decimal(&mut self, value: u8) {
|
||||||
@ -823,7 +836,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
|
|
||||||
self.load_accumulator(result);
|
self.load_accumulator(result);
|
||||||
|
|
||||||
debug!("accumulator: {}", self.registers.accumulator);
|
log::debug!("accumulator: {}", self.registers.accumulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn and(&mut self, value: u8) {
|
fn and(&mut self, value: u8) {
|
||||||
@ -835,11 +848,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
// A - M - (1 - C)
|
// A - M - (1 - C)
|
||||||
|
|
||||||
// nc -- 'not carry'
|
// nc -- 'not carry'
|
||||||
let nc: u8 = if self.registers.status.contains(Status::PS_CARRY) {
|
let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY));
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
let a_before = self.registers.accumulator;
|
let a_before = self.registers.accumulator;
|
||||||
|
|
||||||
@ -881,11 +890,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
// A - M - (1 - C)
|
// A - M - (1 - C)
|
||||||
|
|
||||||
// nc -- 'not carry'
|
// nc -- 'not carry'
|
||||||
let nc: u8 = if self.registers.status.contains(Status::PS_CARRY) {
|
let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY));
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
let a_before = self.registers.accumulator;
|
let a_before = self.registers.accumulator;
|
||||||
|
|
||||||
@ -943,13 +948,12 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
let value_new = val.wrapping_add(1);
|
let value_new = val.wrapping_add(1);
|
||||||
*val = value_new;
|
*val = value_new;
|
||||||
|
|
||||||
let is_negative = (value_new as i8) < 0;
|
|
||||||
let is_zero = value_new == 0;
|
let is_zero = value_new == 0;
|
||||||
|
|
||||||
flags.set_with_mask(
|
flags.set_with_mask(
|
||||||
Status::PS_NEGATIVE | Status::PS_ZERO,
|
Status::PS_NEGATIVE | Status::PS_ZERO,
|
||||||
Status::new(StatusArgs {
|
Status::new(StatusArgs {
|
||||||
negative: is_negative,
|
negative: Self::value_is_negative(value_new),
|
||||||
zero: is_zero,
|
zero: is_zero,
|
||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
@ -960,14 +964,14 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
let value_new = val.wrapping_sub(1);
|
let value_new = val.wrapping_sub(1);
|
||||||
*val = value_new;
|
*val = value_new;
|
||||||
|
|
||||||
let is_negative = (value_new as i8) < 0;
|
|
||||||
let is_zero = value_new == 0;
|
let is_zero = value_new == 0;
|
||||||
|
let is_negative = Self::value_is_negative(value_new);
|
||||||
|
|
||||||
flags.set_with_mask(
|
flags.set_with_mask(
|
||||||
Status::PS_NEGATIVE | Status::PS_ZERO,
|
Status::PS_NEGATIVE | Status::PS_ZERO,
|
||||||
Status::new(StatusArgs {
|
Status::new(StatusArgs {
|
||||||
negative: is_negative,
|
|
||||||
zero: is_zero,
|
zero: is_zero,
|
||||||
|
negative: is_negative,
|
||||||
..StatusArgs::none()
|
..StatusArgs::none()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -1033,20 +1037,24 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
// ...
|
// ...
|
||||||
// The N flag contains most significant bit of the subtraction result.
|
// The N flag contains most significant bit of the subtraction result.
|
||||||
fn compare(&mut self, r: u8, val: u8) {
|
fn compare(&mut self, r: u8, val: u8) {
|
||||||
|
// Setting the CARRY flag: A (unsigned) >= NUM (unsigned)
|
||||||
if r >= val {
|
if r >= val {
|
||||||
self.registers.status.insert(Status::PS_CARRY);
|
self.registers.status.insert(Status::PS_CARRY);
|
||||||
} else {
|
} else {
|
||||||
self.registers.status.remove(Status::PS_CARRY);
|
self.registers.status.remove(Status::PS_CARRY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setting the ZERO flag: A = NUM
|
||||||
if r == val {
|
if r == val {
|
||||||
self.registers.status.insert(Status::PS_ZERO);
|
self.registers.status.insert(Status::PS_ZERO);
|
||||||
} else {
|
} else {
|
||||||
self.registers.status.remove(Status::PS_ZERO);
|
self.registers.status.remove(Status::PS_ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
let diff: i8 = (r as i8).wrapping_sub(val as i8);
|
// Set the NEGATIVE flag based on the MSB of the result of subtraction
|
||||||
if diff < 0 {
|
// This checks if the 8th bit is set (0x80 in hex is 128 in decimal, which is the 8th bit in a byte)
|
||||||
|
let diff = r.wrapping_sub(val);
|
||||||
|
if Self::value_is_negative(diff) {
|
||||||
self.registers.status.insert(Status::PS_NEGATIVE);
|
self.registers.status.insert(Status::PS_NEGATIVE);
|
||||||
} else {
|
} else {
|
||||||
self.registers.status.remove(Status::PS_NEGATIVE);
|
self.registers.status.remove(Status::PS_NEGATIVE);
|
||||||
@ -1059,7 +1067,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compare_with_x_register(&mut self, val: u8) {
|
fn compare_with_x_register(&mut self, val: u8) {
|
||||||
debug!("compare_with_x_register");
|
log::debug!("compare_with_x_register");
|
||||||
|
|
||||||
let x = self.registers.index_x;
|
let x = self.registers.index_x;
|
||||||
self.compare(x, val);
|
self.compare(x, val);
|
||||||
@ -1112,6 +1120,11 @@ impl<M: Bus, V: Variant> core::fmt::Debug for CPU<M, V> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
// Casting from signed to unsigned integers is intentional in these tests
|
||||||
|
#![allow(clippy::cast_sign_loss)]
|
||||||
|
// Operations may intentionally wrap due to emulation of 8-bit unsigned
|
||||||
|
// integer arithmetic. We do this to test wrap-around conditions.
|
||||||
|
#![allow(clippy::cast_possible_wrap)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::instruction::Nmos6502;
|
use crate::instruction::Nmos6502;
|
||||||
@ -1215,6 +1228,8 @@ mod tests {
|
|||||||
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
|
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
|
||||||
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
|
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
|
||||||
|
|
||||||
|
// Allow casting from i8 to u8; -127i8 wraps to 129u8, as intended for
|
||||||
|
// two's complement arithmetic.
|
||||||
cpu.add_with_carry(-127i8 as u8);
|
cpu.add_with_carry(-127i8 as u8);
|
||||||
assert_eq!(cpu.registers.accumulator, 0);
|
assert_eq!(cpu.registers.accumulator, 0);
|
||||||
assert!(cpu.registers.status.contains(Status::PS_CARRY));
|
assert!(cpu.registers.status.contains(Status::PS_CARRY));
|
||||||
|
@ -107,7 +107,7 @@ pub enum Instruction {
|
|||||||
TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y
|
TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum OpInput {
|
pub enum OpInput {
|
||||||
UseImplied,
|
UseImplied,
|
||||||
UseImmediate(u8),
|
UseImmediate(u8),
|
||||||
@ -115,7 +115,7 @@ pub enum OpInput {
|
|||||||
UseAddress(u16),
|
UseAddress(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum AddressingMode {
|
pub enum AddressingMode {
|
||||||
// work directly on accumulator, e. g. `lsr a`.
|
// work directly on accumulator, e. g. `lsr a`.
|
||||||
Accumulator,
|
Accumulator,
|
||||||
@ -161,7 +161,8 @@ pub enum AddressingMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AddressingMode {
|
impl AddressingMode {
|
||||||
pub fn extra_bytes(self) -> u16 {
|
#[must_use]
|
||||||
|
pub const fn extra_bytes(self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
AddressingMode::Accumulator => 0,
|
AddressingMode::Accumulator => 0,
|
||||||
AddressingMode::Implied => 0,
|
AddressingMode::Implied => 0,
|
||||||
@ -184,6 +185,7 @@ impl AddressingMode {
|
|||||||
pub type DecodedInstr = (Instruction, OpInput);
|
pub type DecodedInstr = (Instruction, OpInput);
|
||||||
|
|
||||||
/// The NMOS 6502 variant. This one is present in the Commodore 64, early Apple IIs, etc.
|
/// The NMOS 6502 variant. This one is present in the Commodore 64, early Apple IIs, etc.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Nmos6502;
|
pub struct Nmos6502;
|
||||||
|
|
||||||
impl crate::Variant for Nmos6502 {
|
impl crate::Variant for Nmos6502 {
|
||||||
@ -449,8 +451,9 @@ impl crate::Variant for Nmos6502 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Ricoh variant which has no decimal mode. This is what to use if you want to emulate the
|
/// The Ricoh variant which has no decimal mode. This is what to use if you want
|
||||||
/// NES.
|
/// to emulate the NES.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Ricoh2a03;
|
pub struct Ricoh2a03;
|
||||||
|
|
||||||
impl crate::Variant for Ricoh2a03 {
|
impl crate::Variant for Ricoh2a03 {
|
||||||
@ -479,10 +482,12 @@ impl crate::Variant for Ricoh2a03 {
|
|||||||
|
|
||||||
/// Emulates some very early 6502s which have no ROR instruction. This one is used in very early
|
/// Emulates some very early 6502s which have no ROR instruction. This one is used in very early
|
||||||
/// KIM-1s.
|
/// KIM-1s.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct RevisionA;
|
pub struct RevisionA;
|
||||||
|
|
||||||
impl crate::Variant for RevisionA {
|
impl crate::Variant for RevisionA {
|
||||||
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
|
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
|
||||||
|
#[allow(clippy::match_same_arms)]
|
||||||
match opcode {
|
match opcode {
|
||||||
0x66 => None,
|
0x66 => None,
|
||||||
0x6a => None,
|
0x6a => None,
|
||||||
@ -495,6 +500,7 @@ impl crate::Variant for RevisionA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emulates the 65C02, which has a few bugfixes, and another addressing mode
|
/// Emulates the 65C02, which has a few bugfixes, and another addressing mode
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Cmos6502;
|
pub struct Cmos6502;
|
||||||
|
|
||||||
impl crate::Variant for Cmos6502 {
|
impl crate::Variant for Cmos6502 {
|
||||||
|
26
src/lib.rs
26
src/lib.rs
@ -25,15 +25,29 @@
|
|||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
|
#![warn(
|
||||||
|
absolute_paths_not_starting_with_crate,
|
||||||
|
rustdoc::invalid_html_tags,
|
||||||
|
missing_copy_implementations,
|
||||||
|
missing_debug_implementations,
|
||||||
|
semicolon_in_expressions_from_macros,
|
||||||
|
unreachable_pub,
|
||||||
|
unused_crate_dependencies,
|
||||||
|
unused_extern_crates,
|
||||||
|
variant_size_differences,
|
||||||
|
clippy::missing_const_for_fn
|
||||||
|
)]
|
||||||
|
#![deny(anonymous_parameters, macro_use_extern_crate, pointer_structural_match)]
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
// Registers and ops follow the 6502 naming convention and have similar names at
|
||||||
|
// times
|
||||||
|
#![allow(clippy::similar_names)]
|
||||||
|
#![allow(clippy::match_same_arms)]
|
||||||
|
#![allow(clippy::too_many_lines)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
#[doc = include_str!("../README.md")]
|
#[doc = include_str!("../README.md")]
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
|
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
|
@ -47,7 +47,7 @@ pub const IRQ_INTERRUPT_VECTOR_HI: u16 = 0xFFFF;
|
|||||||
const MEMORY_SIZE: usize = (ADDR_HI_BARE - ADDR_LO_BARE) as usize + 1usize;
|
const MEMORY_SIZE: usize = (ADDR_HI_BARE - ADDR_LO_BARE) as usize + 1usize;
|
||||||
|
|
||||||
// FIXME: Should this use indirection for `bytes`?
|
// FIXME: Should this use indirection for `bytes`?
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
bytes: [u8; MEMORY_SIZE],
|
bytes: [u8; MEMORY_SIZE],
|
||||||
}
|
}
|
||||||
@ -58,10 +58,37 @@ impl Default for Memory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for a bus that can read and write bytes.
|
||||||
|
///
|
||||||
|
/// This is used to abstract the memory and I/O operations of the CPU.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use mos6502::memory::{Bus, Memory};
|
||||||
|
///
|
||||||
|
/// let mut memory = Memory::new();
|
||||||
|
/// memory.set_byte(0x0000, 0x12);
|
||||||
|
/// assert_eq!(memory.get_byte(0x0000), 0x12);
|
||||||
|
/// ```
|
||||||
pub trait Bus {
|
pub trait Bus {
|
||||||
|
/// Returns the byte at the given address.
|
||||||
fn get_byte(&mut self, address: u16) -> u8;
|
fn get_byte(&mut self, address: u16) -> u8;
|
||||||
|
|
||||||
|
/// Sets the byte at the given address to the given value.
|
||||||
fn set_byte(&mut self, address: u16, value: u8);
|
fn set_byte(&mut self, address: u16, value: u8);
|
||||||
|
|
||||||
|
/// Sets the bytes starting at the given address to the given values.
|
||||||
|
///
|
||||||
|
/// This is a default implementation that calls `set_byte` for each byte.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This assumes that the length of `values` is less than or equal to
|
||||||
|
/// [`u16::MAX`] (65535). If the length of `values` is greater than `u16::MAX`,
|
||||||
|
/// this will truncate the length. This assumption is made because the
|
||||||
|
/// maximum addressable memory for the 6502 is 64KB.
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn set_bytes(&mut self, start: u16, values: &[u8]) {
|
fn set_bytes(&mut self, start: u16, values: &[u8]) {
|
||||||
for i in 0..values.len() as u16 {
|
for i in 0..values.len() as u16 {
|
||||||
self.set_byte(start + i, values[i as usize]);
|
self.set_byte(start + i, values[i as usize]);
|
||||||
@ -70,7 +97,8 @@ pub trait Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub fn new() -> Memory {
|
#[must_use]
|
||||||
|
pub const fn new() -> Memory {
|
||||||
Memory {
|
Memory {
|
||||||
bytes: [0; MEMORY_SIZE],
|
bytes: [0; MEMORY_SIZE],
|
||||||
}
|
}
|
||||||
@ -82,12 +110,14 @@ impl Bus for Memory {
|
|||||||
self.bytes[address as usize]
|
self.bytes[address as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the byte at the given address to the given value and returns the
|
/// Sets the byte at the given address to the given value and returns the
|
||||||
// previous value at the address.
|
/// previous value at the address.
|
||||||
fn set_byte(&mut self, address: u16, value: u8) {
|
fn set_byte(&mut self, address: u16, value: u8) {
|
||||||
self.bytes[address as usize] = value;
|
self.bytes[address as usize] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fast way to set multiple bytes in memory when the underlying memory is a
|
||||||
|
/// consecutive block of bytes.
|
||||||
fn set_bytes(&mut self, start: u16, values: &[u8]) {
|
fn set_bytes(&mut self, start: u16, values: &[u8]) {
|
||||||
let start = start as usize;
|
let start = start as usize;
|
||||||
|
|
||||||
@ -103,7 +133,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic(expected = "range end index 65537 out of range for slice of length 65536")]
|
||||||
fn test_memory_overflow_panic() {
|
fn test_memory_overflow_panic() {
|
||||||
let mut memory = Memory::new();
|
let mut memory = Memory::new();
|
||||||
memory.set_bytes(0xFFFE, &[1, 2, 3]);
|
memory.set_bytes(0xFFFE, &[1, 2, 3]);
|
||||||
|
@ -25,8 +25,11 @@
|
|||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
// Useful for constructing Status instances
|
// Useful for constructing Status instances
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct StatusArgs {
|
pub struct StatusArgs {
|
||||||
pub negative: bool,
|
pub negative: bool,
|
||||||
pub overflow: bool,
|
pub overflow: bool,
|
||||||
@ -39,7 +42,8 @@ pub struct StatusArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StatusArgs {
|
impl StatusArgs {
|
||||||
pub fn none() -> StatusArgs {
|
#[must_use]
|
||||||
|
pub const fn none() -> StatusArgs {
|
||||||
StatusArgs {
|
StatusArgs {
|
||||||
negative: false,
|
negative: false,
|
||||||
overflow: false,
|
overflow: false,
|
||||||
@ -71,6 +75,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
impl Status {
|
||||||
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
StatusArgs {
|
StatusArgs {
|
||||||
negative,
|
negative,
|
||||||
@ -86,28 +91,28 @@ impl Status {
|
|||||||
let mut out = Status::empty();
|
let mut out = Status::empty();
|
||||||
|
|
||||||
if negative {
|
if negative {
|
||||||
out |= Status::PS_NEGATIVE
|
out |= Status::PS_NEGATIVE;
|
||||||
}
|
}
|
||||||
if overflow {
|
if overflow {
|
||||||
out |= Status::PS_OVERFLOW
|
out |= Status::PS_OVERFLOW;
|
||||||
}
|
}
|
||||||
if unused {
|
if unused {
|
||||||
out |= Status::PS_UNUSED
|
out |= Status::PS_UNUSED;
|
||||||
}
|
}
|
||||||
if brk {
|
if brk {
|
||||||
out |= Status::PS_BRK
|
out |= Status::PS_BRK;
|
||||||
}
|
}
|
||||||
if decimal_mode {
|
if decimal_mode {
|
||||||
out |= Status::PS_DECIMAL_MODE
|
out |= Status::PS_DECIMAL_MODE;
|
||||||
}
|
}
|
||||||
if disable_interrupts {
|
if disable_interrupts {
|
||||||
out |= Status::PS_DISABLE_INTERRUPTS
|
out |= Status::PS_DISABLE_INTERRUPTS;
|
||||||
}
|
}
|
||||||
if zero {
|
if zero {
|
||||||
out |= Status::PS_ZERO
|
out |= Status::PS_ZERO;
|
||||||
}
|
}
|
||||||
if carry {
|
if carry {
|
||||||
out |= Status::PS_CARRY
|
out |= Status::PS_CARRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
out
|
out
|
||||||
@ -146,7 +151,8 @@ impl Default for Status {
|
|||||||
pub struct StackPointer(pub u8);
|
pub struct StackPointer(pub u8);
|
||||||
|
|
||||||
impl StackPointer {
|
impl StackPointer {
|
||||||
pub fn to_u16(self) -> u16 {
|
#[must_use]
|
||||||
|
pub const fn to_u16(self) -> u16 {
|
||||||
let StackPointer(val) = self;
|
let StackPointer(val) = self;
|
||||||
u16::from_le_bytes([val, 0x01])
|
u16::from_le_bytes([val, 0x01])
|
||||||
}
|
}
|
||||||
@ -177,6 +183,7 @@ impl Default for Registers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
|
#[must_use]
|
||||||
pub fn new() -> Registers {
|
pub fn new() -> Registers {
|
||||||
// TODO akeeton: Revisit these defaults.
|
// TODO akeeton: Revisit these defaults.
|
||||||
Registers {
|
Registers {
|
||||||
|
Loading…
Reference in New Issue
Block a user