Compare commits

...

4 Commits

Author SHA1 Message Date
Sam M W fe4422359d refactor away some casts 2024-04-23 17:42:25 +01:00
Matthias 7579cd5d24 fix some lints 2024-04-22 16:21:17 +02:00
Matthias bdad29af7d Make sure `Cargo.lock` is not comitted 2024-04-22 16:02:27 +02:00
Matthias 2cb1f1ae47 Be more restrictive about code lints
I've added a bunch of new directives for clippy and fixed the highlighted warnings,
which got triggered in the process.
Also updated the dependencies to their latest versions.
Furthermore, I've added the `log::` prefix to debug output to make the use of logging more explicit.
2024-04-21 13:21:00 +02:00
7 changed files with 126 additions and 90 deletions

1
.gitignore vendored
View File

@ -66,3 +66,4 @@ TAGS.vi
src/.DS_Store
tmp.*.rs
.vscode
Cargo.lock

View File

@ -40,8 +40,8 @@ edition = "2021"
name = "mos6502"
[dependencies]
bitflags = "2.3.3"
log = "0.4.19"
bitflags = "2.5.0"
log = "0.4.21"
[features]
decimal_mode = []

View File

@ -49,6 +49,7 @@ where
}
impl<M: Bus, V: Variant> CPU<M, V> {
#[allow(clippy::needless_pass_by_value)]
pub fn new(memory: M, _variant: V) -> CPU<M, V> {
CPU {
registers: Registers::new(),
@ -58,10 +59,23 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
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> {
// 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);
match V::decode(x) {
@ -89,12 +103,6 @@ impl<M: Bus, V: Variant> CPU<M, V> {
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 {
AddressingMode::Accumulator | AddressingMode::Implied => {
// Always the same -- no input
@ -185,24 +193,25 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
}
#[allow(clippy::too_many_lines)]
pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) {
match decoded_instr {
(Instruction::ADC, OpInput::UseImmediate(val)) => {
debug!("add with carry immediate: {}", val);
log::debug!("add with carry immediate: {}", val);
self.add_with_carry(val);
}
(Instruction::ADC, OpInput::UseAddress(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);
}
(Instruction::ADCnd, OpInput::UseImmediate(val)) => {
debug!("add with carry immediate: {}", val);
log::debug!("add with carry immediate: {}", val);
self.add_with_no_decimal(val);
}
(Instruction::ADCnd, OpInput::UseAddress(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);
}
@ -273,7 +282,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
(Instruction::BMI, OpInput::UseRelative(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);
}
@ -289,7 +298,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
self.push_on_stack(self.registers.status.bits());
let pcl = self.memory.get_byte(0xfffe);
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);
}
@ -384,32 +393,32 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
(Instruction::LDA, OpInput::UseImmediate(val)) => {
debug!("load A immediate: {}", val);
log::debug!("load A immediate: {}", val);
self.load_accumulator(val);
}
(Instruction::LDA, OpInput::UseAddress(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);
}
(Instruction::LDX, OpInput::UseImmediate(val)) => {
debug!("load X immediate: {}", val);
log::debug!("load X immediate: {}", val);
self.load_x_register(val);
}
(Instruction::LDX, OpInput::UseAddress(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);
}
(Instruction::LDY, OpInput::UseImmediate(val)) => {
debug!("load Y immediate: {}", val);
log::debug!("load Y immediate: {}", val);
self.load_y_register(val);
}
(Instruction::LDY, OpInput::UseAddress(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);
}
@ -499,32 +508,33 @@ impl<M: Bus, V: Variant> CPU<M, V> {
self.registers.status = Status::from_bits_truncate(val);
let pcl: u8 = self.pull_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) => {
self.pull_from_stack();
let pcl: u8 = self.pull_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)) => {
debug!("subtract with carry immediate: {}", val);
log::debug!("subtract with carry immediate: {}", val);
self.subtract_with_carry(val);
}
(Instruction::SBC, OpInput::UseAddress(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);
}
(Instruction::SBCnd, OpInput::UseImmediate(val)) => {
debug!("subtract with carry immediate: {}", val);
log::debug!("subtract with carry immediate: {}", val);
self.subtract_with_no_decimal(val);
}
(Instruction::SBCnd, OpInput::UseAddress(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);
}
@ -577,10 +587,10 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
(Instruction::NOP, OpInput::UseImplied) => {
debug!("NOP instruction");
log::debug!("NOP instruction");
}
(_, _) => {
debug!(
log::debug!(
"attempting to execute unimplemented or invalid \
instruction"
);
@ -600,29 +610,18 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
}
fn set_flags_from_i8(status: &mut Status, value: i8) {
let is_zero = value == 0;
let is_negative = value < 0;
status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: is_zero,
negative: is_negative,
..StatusArgs::none()
}),
);
const fn value_is_negative(value: u8) -> bool {
value > 127
}
fn set_flags_from_u8(status: &mut Status, value: u8) {
let is_zero = value == 0;
let is_negative = value > 127;
status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: is_zero,
negative: is_negative,
negative: Self::value_is_negative(value),
..StatusArgs::none()
}),
);
@ -640,7 +639,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
..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) {
@ -654,7 +653,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
..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) {
@ -670,7 +669,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
..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) {
@ -686,7 +685,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
..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) {
@ -719,7 +718,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
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 bcd2: u8 = if (result.wrapping_add(bcd1) & 0xf0) > 0x90 {
@ -763,7 +762,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
self.load_accumulator(result);
debug!("accumulator: {}", self.registers.accumulator);
log::debug!("accumulator: {}", self.registers.accumulator);
}
fn add_with_no_decimal(&mut self, value: u8) {
@ -795,7 +794,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
self.load_accumulator(result);
debug!("accumulator: {}", self.registers.accumulator);
log::debug!("accumulator: {}", self.registers.accumulator);
}
fn and(&mut self, value: u8) {
@ -807,11 +806,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
// A - M - (1 - C)
// nc -- 'not carry'
let nc: u8 = if self.registers.status.contains(Status::PS_CARRY) {
0
} else {
1
};
let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY));
let a_before = self.registers.accumulator;
@ -853,11 +848,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
// A - M - (1 - C)
// nc -- 'not carry'
let nc: u8 = if self.registers.status.contains(Status::PS_CARRY) {
0
} else {
1
};
let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY));
let a_before = self.registers.accumulator;
@ -915,13 +906,12 @@ impl<M: Bus, V: Variant> CPU<M, V> {
let value_new = val.wrapping_add(1);
*val = value_new;
let is_negative = (value_new as i8) < 0;
let is_zero = value_new == 0;
flags.set_with_mask(
Status::PS_NEGATIVE | Status::PS_ZERO,
Status::new(StatusArgs {
negative: is_negative,
negative: Self::value_is_negative(value_new),
zero: is_zero,
..StatusArgs::none()
}),
@ -932,13 +922,12 @@ impl<M: Bus, V: Variant> CPU<M, V> {
let value_new = val.wrapping_sub(1);
*val = value_new;
let is_negative = (value_new as i8) < 0;
let is_zero = value_new == 0;
flags.set_with_mask(
Status::PS_NEGATIVE | Status::PS_ZERO,
Status::new(StatusArgs {
negative: is_negative,
negative: Self::value_is_negative(value_new),
zero: is_zero,
..StatusArgs::none()
}),
@ -1031,7 +1020,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
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;
self.compare(x, val);

View File

@ -107,7 +107,7 @@ pub enum Instruction {
TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum OpInput {
UseImplied,
UseImmediate(u8),
@ -115,7 +115,7 @@ pub enum OpInput {
UseAddress(u16),
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum AddressingMode {
Accumulator, // 1 LSR A work directly on accumulator
Implied, // 1 BRK
@ -135,7 +135,8 @@ pub enum AddressingMode {
}
impl AddressingMode {
pub fn extra_bytes(self) -> u16 {
#[must_use]
pub const fn extra_bytes(self) -> u16 {
match self {
AddressingMode::Accumulator => 0,
AddressingMode::Implied => 0,
@ -157,6 +158,7 @@ impl AddressingMode {
pub type DecodedInstr = (Instruction, OpInput);
/// The NMOS 6502 variant. This one is present in the Commodore 64, early Apple IIs, etc.
#[derive(Copy, Clone, Debug)]
pub struct Nmos6502;
impl crate::Variant for Nmos6502 {
@ -422,8 +424,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
/// NES.
/// The Ricoh variant which has no decimal mode. This is what to use if you want
/// to emulate the NES.
#[derive(Copy, Clone, Debug)]
pub struct Ricoh2a03;
impl crate::Variant for Ricoh2a03 {
@ -452,10 +455,12 @@ impl crate::Variant for Ricoh2a03 {
/// Emulates some very early 6502s which have no ROR instruction. This one is used in very early
/// KIM-1s.
#[derive(Copy, Clone, Debug)]
pub struct RevisionA;
impl crate::Variant for RevisionA {
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
#[allow(clippy::match_same_arms)]
match opcode {
0x66 => None,
0x6a => None,

View File

@ -25,15 +25,29 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// 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]
#[doc = include_str!("../README.md")]
#[macro_use]
extern crate log;
#[macro_use]
extern crate bitflags;
pub mod cpu;
pub mod instruction;
pub mod memory;

View File

@ -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;
// FIXME: Should this use indirection for `bytes`?
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub struct Memory {
bytes: [u8; MEMORY_SIZE],
}
@ -58,10 +58,29 @@ 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 {
/// Returns the byte at the given address.
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);
/// Sets the bytes starting at the given address to the given values.
///
/// This is a default implementation that calls `set_byte` for each byte.
fn set_bytes(&mut self, start: u16, values: &[u8]) {
for i in 0..values.len() as u16 {
self.set_byte(start + i, values[i as usize]);
@ -70,7 +89,8 @@ pub trait Bus {
}
impl Memory {
pub fn new() -> Memory {
#[must_use]
pub const fn new() -> Memory {
Memory {
bytes: [0; MEMORY_SIZE],
}
@ -103,7 +123,7 @@ mod tests {
use super::*;
#[test]
#[should_panic]
#[should_panic(expected = "range end index 65537 out of range for slice of length 65536")]
fn test_memory_overflow_panic() {
let mut memory = Memory::new();
memory.set_bytes(0xFFFE, &[1, 2, 3]);

View File

@ -25,8 +25,11 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
use bitflags::bitflags;
// Useful for constructing Status instances
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct StatusArgs {
pub negative: bool,
pub overflow: bool,
@ -39,7 +42,8 @@ pub struct StatusArgs {
}
impl StatusArgs {
pub fn none() -> StatusArgs {
#[must_use]
pub const fn none() -> StatusArgs {
StatusArgs {
negative: false,
overflow: false,
@ -71,6 +75,7 @@ bitflags! {
}
impl Status {
#[must_use]
pub fn new(
StatusArgs {
negative,
@ -86,28 +91,28 @@ impl Status {
let mut out = Status::empty();
if negative {
out |= Status::PS_NEGATIVE
out |= Status::PS_NEGATIVE;
}
if overflow {
out |= Status::PS_OVERFLOW
out |= Status::PS_OVERFLOW;
}
if unused {
out |= Status::PS_UNUSED
out |= Status::PS_UNUSED;
}
if brk {
out |= Status::PS_BRK
out |= Status::PS_BRK;
}
if decimal_mode {
out |= Status::PS_DECIMAL_MODE
out |= Status::PS_DECIMAL_MODE;
}
if disable_interrupts {
out |= Status::PS_DISABLE_INTERRUPTS
out |= Status::PS_DISABLE_INTERRUPTS;
}
if zero {
out |= Status::PS_ZERO
out |= Status::PS_ZERO;
}
if carry {
out |= Status::PS_CARRY
out |= Status::PS_CARRY;
}
out
@ -146,7 +151,8 @@ impl Default for Status {
pub struct StackPointer(pub u8);
impl StackPointer {
pub fn to_u16(self) -> u16 {
#[must_use]
pub const fn to_u16(self) -> u16 {
let StackPointer(val) = self;
u16::from_le_bytes([val, 0x01])
}
@ -177,6 +183,7 @@ impl Default for Registers {
}
impl Registers {
#[must_use]
pub fn new() -> Registers {
// TODO akeeton: Revisit these defaults.
Registers {