mirror of
https://github.com/mre/mos6502.git
synced 2024-11-28 22:51:26 +00:00
Merge pull request #20 from typelist/master
Implement SBC (subtract-with-carry) and flag instructions CLC, CLD, CLI, CLV, SEC, SED, SEI
This commit is contained in:
commit
fe9610e3d8
243
src/machine.rs
243
src/machine.rs
@ -32,7 +32,8 @@ use instruction;
|
|||||||
use instruction::{DecodedInstr};
|
use instruction::{DecodedInstr};
|
||||||
use memory::Memory;
|
use memory::Memory;
|
||||||
use registers::{ Registers, StackPointer, Status, StatusArgs };
|
use registers::{ Registers, StackPointer, Status, StatusArgs };
|
||||||
use registers::{ PS_NEGATIVE, PS_OVERFLOW, PS_ZERO, PS_CARRY };
|
use registers::{ PS_NEGATIVE, PS_DECIMAL_MODE, PS_OVERFLOW, PS_ZERO, PS_CARRY,
|
||||||
|
PS_DISABLE_INTERRUPTS };
|
||||||
|
|
||||||
pub struct Machine {
|
pub struct Machine {
|
||||||
pub registers: Registers,
|
pub registers: Registers,
|
||||||
@ -80,12 +81,12 @@ impl Machine {
|
|||||||
(instruction::ADC, instruction::UseImmediate(val)) => {
|
(instruction::ADC, instruction::UseImmediate(val)) => {
|
||||||
debug!("add with carry immediate: {}", val);
|
debug!("add with carry immediate: {}", val);
|
||||||
self.add_with_carry(val as i8);
|
self.add_with_carry(val as i8);
|
||||||
},
|
}
|
||||||
(instruction::ADC, instruction::UseAddress(addr)) => {
|
(instruction::ADC, instruction::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr) as i8;
|
let val = self.memory.get_byte(addr) as i8;
|
||||||
debug!("add with carry. address: {}. value: {}", addr, val);
|
debug!("add with carry. address: {}. value: {}", addr, val);
|
||||||
self.add_with_carry(val);
|
self.add_with_carry(val);
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::BIT, instruction::UseAddress(addr)) => {
|
(instruction::BIT, instruction::UseAddress(addr)) => {
|
||||||
let a: u8 = self.registers.accumulator as u8;
|
let a: u8 = self.registers.accumulator as u8;
|
||||||
@ -114,7 +115,20 @@ impl Machine {
|
|||||||
+ AddressDiff(rel as i32);
|
+ AddressDiff(rel as i32);
|
||||||
debug!("branch if minus relative. address: {}", addr);
|
debug!("branch if minus relative. address: {}", addr);
|
||||||
self.branch_if_minus(addr);
|
self.branch_if_minus(addr);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
(instruction::CLC, instruction::UseImplied) => {
|
||||||
|
self.registers.status.and(!PS_CARRY);
|
||||||
|
}
|
||||||
|
(instruction::CLD, instruction::UseImplied) => {
|
||||||
|
self.registers.status.and(!PS_DECIMAL_MODE);
|
||||||
|
}
|
||||||
|
(instruction::CLI, instruction::UseImplied) => {
|
||||||
|
self.registers.status.and(!PS_DISABLE_INTERRUPTS);
|
||||||
|
}
|
||||||
|
(instruction::CLV, instruction::UseImplied) => {
|
||||||
|
self.registers.status.and(!PS_OVERFLOW);
|
||||||
|
}
|
||||||
|
|
||||||
(instruction::DEC, instruction::UseAddress(addr)) => {
|
(instruction::DEC, instruction::UseAddress(addr)) => {
|
||||||
self.decrement_memory(addr)
|
self.decrement_memory(addr)
|
||||||
@ -122,87 +136,109 @@ impl Machine {
|
|||||||
|
|
||||||
(instruction::DEX, instruction::UseImplied) => {
|
(instruction::DEX, instruction::UseImplied) => {
|
||||||
self.dec_x();
|
self.dec_x();
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::JMP, instruction::UseAddress(addr)) => {
|
(instruction::JMP, instruction::UseAddress(addr)) => {
|
||||||
self.jump(addr)
|
self.jump(addr)
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::LDA, instruction::UseImmediate(val)) => {
|
(instruction::LDA, instruction::UseImmediate(val)) => {
|
||||||
debug!("load A immediate: {}", val);
|
debug!("load A immediate: {}", val);
|
||||||
self.load_accumulator(val as i8);
|
self.load_accumulator(val as i8);
|
||||||
},
|
}
|
||||||
(instruction::LDA, instruction::UseAddress(addr)) => {
|
(instruction::LDA, instruction::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load A. address: {}. value: {}", addr, val);
|
debug!("load A. address: {}. value: {}", addr, val);
|
||||||
self.load_accumulator(val as i8);
|
self.load_accumulator(val as i8);
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::LDX, instruction::UseImmediate(val)) => {
|
(instruction::LDX, instruction::UseImmediate(val)) => {
|
||||||
debug!("load X immediate: {}", val);
|
debug!("load X immediate: {}", val);
|
||||||
self.load_x_register(val as i8);
|
self.load_x_register(val as i8);
|
||||||
},
|
}
|
||||||
(instruction::LDX, instruction::UseAddress(addr)) => {
|
(instruction::LDX, instruction::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load X. address: {}. value: {}", addr, val);
|
debug!("load X. address: {}. value: {}", addr, val);
|
||||||
self.load_x_register(val as i8);
|
self.load_x_register(val as i8);
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::LDY, instruction::UseImmediate(val)) => {
|
(instruction::LDY, instruction::UseImmediate(val)) => {
|
||||||
debug!("load Y immediate: {}", val);
|
debug!("load Y immediate: {}", val);
|
||||||
self.load_y_register(val as i8);
|
self.load_y_register(val as i8);
|
||||||
},
|
}
|
||||||
(instruction::LDY, instruction::UseAddress(addr)) => {
|
(instruction::LDY, instruction::UseAddress(addr)) => {
|
||||||
let val = self.memory.get_byte(addr);
|
let val = self.memory.get_byte(addr);
|
||||||
debug!("load Y. address: {}. value: {}", addr, val);
|
debug!("load Y. address: {}. value: {}", addr, val);
|
||||||
self.load_y_register(val as i8);
|
self.load_y_register(val as i8);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
(instruction::SBC, instruction::UseImmediate(val)) => {
|
||||||
|
debug!("subtract with carry immediate: {}", val);
|
||||||
|
self.subtract_with_carry(val as i8);
|
||||||
|
}
|
||||||
|
(instruction::SBC, instruction::UseAddress(addr)) => {
|
||||||
|
let val = self.memory.get_byte(addr) as i8;
|
||||||
|
debug!("subtract with carry. address: {}. value: {}",
|
||||||
|
addr, val);
|
||||||
|
self.subtract_with_carry(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
(instruction::SEC, instruction::UseImplied) => {
|
||||||
|
self.registers.status.or(PS_CARRY);
|
||||||
|
}
|
||||||
|
(instruction::SED, instruction::UseImplied) => {
|
||||||
|
self.registers.status.or(PS_DECIMAL_MODE);
|
||||||
|
}
|
||||||
|
(instruction::SEI, instruction::UseImplied) => {
|
||||||
|
self.registers.status.or(PS_DISABLE_INTERRUPTS);
|
||||||
|
}
|
||||||
|
|
||||||
(instruction::STA, instruction::UseAddress(addr)) => {
|
(instruction::STA, instruction::UseAddress(addr)) => {
|
||||||
self.memory.set_byte(addr, self.registers.accumulator as u8);
|
self.memory.set_byte(addr, self.registers.accumulator as u8);
|
||||||
},
|
}
|
||||||
(instruction::STX, instruction::UseAddress(addr)) => {
|
(instruction::STX, instruction::UseAddress(addr)) => {
|
||||||
self.memory.set_byte(addr, self.registers.index_x as u8);
|
self.memory.set_byte(addr, self.registers.index_x as u8);
|
||||||
},
|
}
|
||||||
(instruction::STY, instruction::UseAddress(addr)) => {
|
(instruction::STY, instruction::UseAddress(addr)) => {
|
||||||
self.memory.set_byte(addr, self.registers.index_y as u8);
|
self.memory.set_byte(addr, self.registers.index_y as u8);
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::TAX, instruction::UseImplied) => {
|
(instruction::TAX, instruction::UseImplied) => {
|
||||||
let val = self.registers.accumulator;
|
let val = self.registers.accumulator;
|
||||||
self.load_x_register(val);
|
self.load_x_register(val);
|
||||||
},
|
}
|
||||||
(instruction::TAY, instruction::UseImplied) => {
|
(instruction::TAY, instruction::UseImplied) => {
|
||||||
let val = self.registers.accumulator;
|
let val = self.registers.accumulator;
|
||||||
self.load_y_register(val);
|
self.load_y_register(val);
|
||||||
},
|
}
|
||||||
(instruction::TSX, instruction::UseImplied) => {
|
(instruction::TSX, instruction::UseImplied) => {
|
||||||
let StackPointer(val) = self.registers.stack_pointer;
|
let StackPointer(val) = self.registers.stack_pointer;
|
||||||
let val = val as i8;
|
let val = val as i8;
|
||||||
self.load_x_register(val);
|
self.load_x_register(val);
|
||||||
},
|
}
|
||||||
(instruction::TXA, instruction::UseImplied) => {
|
(instruction::TXA, instruction::UseImplied) => {
|
||||||
let val = self.registers.index_x;
|
let val = self.registers.index_x;
|
||||||
self.load_accumulator(val);
|
self.load_accumulator(val);
|
||||||
},
|
}
|
||||||
(instruction::TXS, instruction::UseImplied) => {
|
(instruction::TXS, instruction::UseImplied) => {
|
||||||
// Note that this is the only 'transfer' instruction that does
|
// Note that this is the only 'transfer' instruction that does
|
||||||
// NOT set the zero and negative flags. (Because the target
|
// NOT set the zero and negative flags. (Because the target
|
||||||
// is the stack pointer)
|
// is the stack pointer)
|
||||||
let val = self.registers.index_x;
|
let val = self.registers.index_x;
|
||||||
self.registers.stack_pointer = StackPointer(val as u8);
|
self.registers.stack_pointer = StackPointer(val as u8);
|
||||||
},
|
}
|
||||||
(instruction::TYA, instruction::UseImplied) => {
|
(instruction::TYA, instruction::UseImplied) => {
|
||||||
let val = self.registers.index_y;
|
let val = self.registers.index_y;
|
||||||
self.load_accumulator(val);
|
self.load_accumulator(val);
|
||||||
},
|
}
|
||||||
|
|
||||||
(instruction::NOP, _) => {
|
(instruction::NOP, instruction::UseImplied) => {
|
||||||
debug!("nop instr");
|
debug!("NOP instruction");
|
||||||
},
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
debug!("attempting to execute unimplemented instruction");
|
debug!("attempting to execute unimplemented or invalid \
|
||||||
},
|
instruction");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,31 +285,81 @@ impl Machine {
|
|||||||
value);
|
value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO akeeton: Implement binary-coded decimal.
|
|
||||||
fn add_with_carry(&mut self, value: i8) {
|
fn add_with_carry(&mut self, value: i8) {
|
||||||
let a_before: i8 = self.registers.accumulator;
|
if self.registers.status.contains(PS_DECIMAL_MODE) {
|
||||||
let c_before: i8 = self.registers.status.get_carry();
|
// TODO akeeton: Implement binary-coded decimal.
|
||||||
let a_after: i8 = a_before + c_before + value;
|
debug!("binary-coded decimal not implemented for add_with_carry");
|
||||||
|
} else {
|
||||||
|
let a_before: i8 = self.registers.accumulator;
|
||||||
|
let c_before: i8 = if self.registers.status.contains(PS_CARRY)
|
||||||
|
{ 1 } else { 0 };
|
||||||
|
let a_after: i8 = a_before + c_before + value;
|
||||||
|
|
||||||
debug_assert_eq!(a_after as u8, a_before as u8 + c_before as u8
|
debug_assert_eq!(a_after as u8, a_before as u8 + c_before as u8
|
||||||
+ value as u8);
|
+ value as u8);
|
||||||
|
|
||||||
let did_carry = (a_after as u8) < (a_before as u8);
|
let did_carry = (a_after as u8) < (a_before as u8);
|
||||||
|
|
||||||
let did_overflow =
|
let did_overflow =
|
||||||
(a_before < 0 && value < 0 && a_after >= 0)
|
(a_before < 0 && value < 0 && a_after >= 0)
|
||||||
|| (a_before > 0 && value > 0 && a_after <= 0);
|
|| (a_before > 0 && value > 0 && a_after <= 0);
|
||||||
|
|
||||||
let mask = PS_CARRY | PS_OVERFLOW;
|
let mask = PS_CARRY | PS_OVERFLOW;
|
||||||
|
|
||||||
self.registers.status.set_with_mask(mask,
|
self.registers.status.set_with_mask(mask,
|
||||||
Status::new(StatusArgs { carry: did_carry,
|
Status::new(StatusArgs { carry: did_carry,
|
||||||
overflow: did_overflow,
|
overflow: did_overflow,
|
||||||
..StatusArgs::none() } ));
|
..StatusArgs::none() } ));
|
||||||
|
|
||||||
self.load_accumulator(a_after);
|
self.load_accumulator(a_after);
|
||||||
|
|
||||||
debug!("accumulator: {}", self.registers.accumulator);
|
debug!("accumulator: {}", self.registers.accumulator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement binary-coded decimal
|
||||||
|
fn subtract_with_carry(&mut self, value: i8) {
|
||||||
|
if self.registers.status.contains(PS_DECIMAL_MODE) {
|
||||||
|
debug!("binary-coded decimal not implemented for \
|
||||||
|
subtract_with_carry");
|
||||||
|
} else {
|
||||||
|
// A - M - (1 - C)
|
||||||
|
|
||||||
|
// nc -- 'not carry'
|
||||||
|
let nc: i8 = if self.registers.status.contains(PS_CARRY)
|
||||||
|
{ 0 } else { 1 };
|
||||||
|
|
||||||
|
let a_before: i8 = self.registers.accumulator;
|
||||||
|
|
||||||
|
let a_after = a_before - value - nc;
|
||||||
|
|
||||||
|
// The carry flag is set on unsigned overflow.
|
||||||
|
let did_carry = (a_after as u8) > (a_before as u8);
|
||||||
|
|
||||||
|
// The overflow flag is set on two's-complement overflow.
|
||||||
|
//
|
||||||
|
// range of A is -128 to 127
|
||||||
|
// range of - M - (1 - C) is -128 to 128
|
||||||
|
// -(127 + 1) to -(-128 + 0)
|
||||||
|
//
|
||||||
|
let over = ((nc == 0 && value < 0) || (nc == 1 && value < -1))
|
||||||
|
&& a_before >= 0
|
||||||
|
&& a_after < 0;
|
||||||
|
|
||||||
|
let under = (a_before < 0) && (-value - nc < 0)
|
||||||
|
&& a_after >= 0;
|
||||||
|
|
||||||
|
let did_overflow = over || under;
|
||||||
|
|
||||||
|
let mask = PS_CARRY | PS_OVERFLOW;
|
||||||
|
|
||||||
|
self.registers.status.set_with_mask(mask,
|
||||||
|
Status::new(StatusArgs { carry: did_carry,
|
||||||
|
overflow: did_overflow,
|
||||||
|
..StatusArgs::none() } ));
|
||||||
|
|
||||||
|
self.load_accumulator(a_after);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrement_memory(&mut self, addr: Address) {
|
fn decrement_memory(&mut self, addr: Address) {
|
||||||
@ -286,12 +372,9 @@ impl Machine {
|
|||||||
|
|
||||||
self.registers.status.set_with_mask(
|
self.registers.status.set_with_mask(
|
||||||
PS_NEGATIVE | PS_ZERO,
|
PS_NEGATIVE | PS_ZERO,
|
||||||
Status::new(StatusArgs {
|
Status::new(StatusArgs { negative: is_negative,
|
||||||
negative: is_negative,
|
zero: is_zero,
|
||||||
zero: is_zero,
|
..StatusArgs::none() } ));
|
||||||
..StatusArgs::none()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dec_x(&mut self) {
|
fn dec_x(&mut self) {
|
||||||
@ -390,6 +473,66 @@ fn add_with_carry_test() {
|
|||||||
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subtract_with_carry_test() {
|
||||||
|
let mut machine = Machine::new();
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::SEC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = 0;
|
||||||
|
|
||||||
|
machine.subtract_with_carry(1);
|
||||||
|
assert_eq!(machine.registers.accumulator, -1);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), false);
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::SEC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = -128;
|
||||||
|
machine.subtract_with_carry(1);
|
||||||
|
assert_eq!(machine.registers.accumulator, 127);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::SEC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = 127;
|
||||||
|
machine.subtract_with_carry(-1);
|
||||||
|
assert_eq!(machine.registers.accumulator, -128);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::CLC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = -64;
|
||||||
|
machine.subtract_with_carry(64);
|
||||||
|
assert_eq!(machine.registers.accumulator, 127);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::SEC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = 0;
|
||||||
|
machine.subtract_with_carry(-128);
|
||||||
|
assert_eq!(machine.registers.accumulator, -128);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), true);
|
||||||
|
|
||||||
|
machine.execute_instruction((instruction::CLC, instruction::UseImplied));
|
||||||
|
machine.registers.accumulator = 0;
|
||||||
|
machine.subtract_with_carry(127);
|
||||||
|
assert_eq!(machine.registers.accumulator, -128);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_CARRY), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_ZERO), false);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_NEGATIVE), true);
|
||||||
|
assert_eq!(machine.registers.status.contains(PS_OVERFLOW), false);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrement_memory_test() {
|
fn decrement_memory_test() {
|
||||||
let mut machine = Machine::new();
|
let mut machine = Machine::new();
|
||||||
@ -488,7 +631,7 @@ fn branch_if_minus_test() {
|
|||||||
{
|
{
|
||||||
let mut machine = Machine::new();
|
let mut machine = Machine::new();
|
||||||
|
|
||||||
machine.registers.status.set_with_mask(PS_NEGATIVE, PS_NEGATIVE);
|
machine.registers.status.or(PS_NEGATIVE);
|
||||||
let registers_before = machine.registers;
|
let registers_before = machine.registers;
|
||||||
|
|
||||||
machine.branch_if_minus(Address(0xABCD));
|
machine.branch_if_minus(Address(0xABCD));
|
||||||
|
@ -104,12 +104,16 @@ impl Status {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_with_mask(&mut self, mask: Status, rhs: Status) {
|
pub fn and(&mut self, rhs: Status) {
|
||||||
*self = (*self & !mask) | rhs;
|
*self = *self & rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_carry(self) -> i8 {
|
pub fn or(&mut self, rhs: Status) {
|
||||||
if self.contains(PS_CARRY) { 1 } else { 0 }
|
*self = *self | rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_with_mask(&mut self, mask: Status, rhs: Status) {
|
||||||
|
*self = (*self & !mask) | rhs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user