Completed official instruction set
This commit is contained in:
parent
5ee985acce
commit
0ac1511078
@ -51,7 +51,7 @@ bitflags! {
|
||||
const Zero = 0b00000010;
|
||||
const InterruptDisable = 0b00000100;
|
||||
const Decimal = 0b00001000;
|
||||
const B = 0b00010000;
|
||||
const Break = 0b00010000;
|
||||
const Overflow = 0b01000000;
|
||||
const Negative = 0b10000000;
|
||||
}
|
||||
@ -100,12 +100,23 @@ pub trait CpuInternals {
|
||||
/// * `value` - The value to push
|
||||
fn stack_push(&mut self, value: u8);
|
||||
|
||||
/// Pops the value on the top of the stack
|
||||
/// Pushes a word value to the top of the stack
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - The word to push
|
||||
fn stack_push_word(&mut self, value: u16);
|
||||
|
||||
/// Pops the byte from the top of the stack
|
||||
///
|
||||
/// # Returns
|
||||
/// The byte on the top of the stack
|
||||
fn stack_pop(&mut self) -> u8;
|
||||
|
||||
/// Pops a word from the top of the stack
|
||||
///
|
||||
/// # Returns
|
||||
/// The word on the top of the stack
|
||||
fn stack_pop_word(&mut self) -> u16;
|
||||
/// 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);
|
||||
@ -182,6 +193,11 @@ impl CpuInternals for Cpu {
|
||||
self.registers.sp -= 1;
|
||||
}
|
||||
|
||||
fn stack_push_word(&mut self, value: u16) {
|
||||
self.stack_push((value >> 8) as u8);
|
||||
self.stack_push((value & 0xFF) as u8);
|
||||
}
|
||||
|
||||
fn stack_pop(&mut self) -> u8 {
|
||||
assert!(self.registers.sp < 0xff);
|
||||
|
||||
@ -191,6 +207,13 @@ impl CpuInternals for Cpu {
|
||||
self.memory_bus.get_byte(addr)
|
||||
}
|
||||
|
||||
fn stack_pop_word(&mut self) -> u16 {
|
||||
let low = self.stack_pop() as u16;
|
||||
let high = self.stack_pop() as u16;
|
||||
|
||||
low | (high << 8)
|
||||
}
|
||||
|
||||
fn stack_push_context(&mut self) {
|
||||
self.stack_push((self.registers.pc >> 8) as u8);
|
||||
self.stack_push((self.registers.pc & 0xff) as u8);
|
||||
|
@ -264,7 +264,13 @@ impl Cpu {
|
||||
let base_addr = self.registers.pc;
|
||||
let offset = self.program_get_next_byte() as u16;
|
||||
|
||||
base_addr + offset
|
||||
// The offset is signed
|
||||
let positive_offset = offset & 0x7F;
|
||||
if offset & 0x80 {
|
||||
base_addr.wrapping_sub(positive_offset)
|
||||
} else {
|
||||
base_addr.wrapping_add(positive_offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::cpu::op::Operand;
|
||||
use crate::cpu::op::{AddressingMode, Operand};
|
||||
use crate::cpu::{Cpu, CpuInternals, CpuStatus};
|
||||
|
||||
// Unofficial ops documentation: https://www.masswerk.at/6502/6502_instruction_set.html#SAX
|
||||
@ -25,6 +25,25 @@ impl Cpu {
|
||||
self.set_status_flag(CpuStatus::Overflow, is_sign_overflow(a, value, result));
|
||||
}
|
||||
|
||||
fn branch(&mut self, operand: Operand, condition: bool) {
|
||||
let mut cycle_count = 2;
|
||||
|
||||
if condition {
|
||||
cycle_count += 1;
|
||||
|
||||
let source_addr = self.registers.pc;
|
||||
let target_addr = operand.value;
|
||||
self.registers.pc = target_addr;
|
||||
|
||||
// More cycles are used if the target address is in a new page
|
||||
if target_addr & 0xFF00 != source_addr & 0xFF00 {
|
||||
cycle_count += 2;
|
||||
}
|
||||
}
|
||||
|
||||
self.busy_cycle_count = cycle_count;
|
||||
}
|
||||
|
||||
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);
|
||||
@ -126,6 +145,91 @@ impl Cpu {
|
||||
self.set_status_flag_u8(CpuStatus::Carry, value & 0x80);
|
||||
}
|
||||
|
||||
/// Branch if carry clear
|
||||
fn op_bcc(&mut self, operand: Operand) {
|
||||
let condition = !self.get_status_flag(CpuStatus::Carry);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Branch if carry set
|
||||
fn op_bcs(&mut self, operand: Operand) {
|
||||
let condition = self.get_status_flag(CpuStatus::Carry);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Branch if equal (zero is set)
|
||||
fn op_beq(&mut self, operand: Operand) {
|
||||
let condition = self.get_status_flag(CpuStatus::Zero);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Bit test, only changes flags from bitwise AND with accumulator
|
||||
fn op_bit(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a & value;
|
||||
|
||||
self.set_status_flag(CpuStatus::Zero, result == 0);
|
||||
self.set_status_flag_u8(CpuStatus::Overflow, value & 0b01000000);
|
||||
self.set_status_flag_u8(CpuStatus::Negative, value & 0b10000000);
|
||||
}
|
||||
|
||||
/// Branch if minus (negative is set)
|
||||
fn op_bmi(&mut self, operand: Operand) {
|
||||
let condition = self.get_status_flag(CpuStatus::Negative);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Branch if not equal (zero is not set)
|
||||
fn op_bne(&mut self, operand: Operand) {
|
||||
let condition = !self.get_status_flag(CpuStatus::Zero);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Branch if plus (negative not set)
|
||||
fn op_bpl(&mut self, operand: Operand) {
|
||||
let condition = !self.get_status_flag(CpuStatus::Negative);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Break, software interrupt
|
||||
fn op_brk(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::Break, true);
|
||||
self.stack_push_context();
|
||||
|
||||
// TODO: If an NMI is triggered at the same time, this interrupt is skipped but the break flag is still set
|
||||
// Load IRQ interrupt vector in PC at $FFFE/F
|
||||
// Cycle count: 7
|
||||
}
|
||||
|
||||
/// Branch if overflow clear
|
||||
fn op_bvc(&mut self, operand: Operand) {
|
||||
let condition = !self.get_status_flag(CpuStatus::Overflow);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Branch if overflow set
|
||||
fn op_bvs(&mut self, operand: Operand) {
|
||||
let condition = self.get_status_flag(CpuStatus::Overflow);
|
||||
self.branch(operand, condition);
|
||||
}
|
||||
|
||||
/// Clear carry flag
|
||||
fn op_clc(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::Carry, false);
|
||||
}
|
||||
|
||||
/// Clear interrupt disable flag
|
||||
fn op_cli(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::InterruptDisable, false);
|
||||
}
|
||||
|
||||
/// Clear overflow flag
|
||||
fn op_clv(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::Overflow, false);
|
||||
}
|
||||
|
||||
/// Compare to accumulator
|
||||
fn op_cmp(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
@ -137,6 +241,58 @@ impl Cpu {
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Compare to X
|
||||
fn op_cpx(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let x = self.registers.x;
|
||||
|
||||
let result = x.wrapping_sub(value);
|
||||
|
||||
self.set_status_flag(CpuStatus::Carry, x >= value);
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Compare to Y
|
||||
fn op_cpy(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let y = self.registers.y;
|
||||
|
||||
let result = y.wrapping_sub(value);
|
||||
|
||||
self.set_status_flag(CpuStatus::Carry, y >= value);
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Decrement memory
|
||||
fn op_dec(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
let result = value.wrapping_sub(1);
|
||||
operand.write(result, self);
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Decrement X
|
||||
fn op_dex(&mut self, operand: Operand) {
|
||||
let x = self.registers.x;
|
||||
|
||||
let result = x.wrapping_sub(1);
|
||||
self.registers.x = result;
|
||||
|
||||
self.set_common_flags(result)
|
||||
}
|
||||
|
||||
/// Decrement Y
|
||||
fn op_dey(&mut self, operand: Operand) {
|
||||
let y = self.registers.y;
|
||||
|
||||
let result = y.wrapping_sub(1);
|
||||
self.registers.y = result;
|
||||
|
||||
self.set_common_flags(result)
|
||||
}
|
||||
|
||||
/// Bitwise exclusive OR with accumulator
|
||||
fn op_eor(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
@ -148,6 +304,67 @@ impl Cpu {
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Increment memory
|
||||
fn op_inc(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
let result = value.wrapping_add(1);
|
||||
operand.write(result, self);
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Increment X
|
||||
fn op_inx(&mut self, operand: Operand) {
|
||||
let x = self.registers.x;
|
||||
|
||||
let result = x.wrapping_add(1);
|
||||
self.registers.x = result;
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Increment Y
|
||||
fn op_iny(&mut self, operand: Operand) {
|
||||
let y = self.registers.y;
|
||||
|
||||
let result = y.wrapping_add(1);
|
||||
self.registers.y = result;
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Jump
|
||||
fn op_jmp(&mut self, operand: Operand, addr_mode: AddressingMode) {
|
||||
let value = operand.value;
|
||||
|
||||
let target_addr = if addr_mode == AddressingMode::ABS {
|
||||
value
|
||||
} else {
|
||||
assert_eq!(addr_mode, AddressingMode::IND);
|
||||
self.memory_bus.get_word(value)
|
||||
};
|
||||
|
||||
self.registers.pc = target_addr;
|
||||
|
||||
// TODO
|
||||
// int cycle_count = 3;
|
||||
// if (addr_mode == ADDR_MODE_INDIRECT_JUMP) {
|
||||
// cycle_count = 5;
|
||||
// }
|
||||
}
|
||||
|
||||
/// Jump to subroutine
|
||||
fn op_jsr(&mut self, operand: Operand) {
|
||||
let target_addr = operand.value;
|
||||
let pc = self.registers.pc;
|
||||
|
||||
self.stack_push_word(pc);
|
||||
self.registers.pc = target_addr;
|
||||
|
||||
// TODO: Cycle count
|
||||
}
|
||||
|
||||
/// Load to accumulator
|
||||
fn op_lda(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
@ -157,6 +374,40 @@ impl Cpu {
|
||||
self.set_common_flags(value);
|
||||
}
|
||||
|
||||
/// Load to X
|
||||
fn op_ldx(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
self.registers.x = value;
|
||||
|
||||
self.set_common_flags(value);
|
||||
}
|
||||
|
||||
/// Load to Y
|
||||
fn op_ldy(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
self.registers.y = value;
|
||||
|
||||
self.set_common_flags(value);
|
||||
}
|
||||
|
||||
/// Logical shift right
|
||||
fn op_lsr(&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 & 0x1);
|
||||
}
|
||||
|
||||
/// No operation
|
||||
fn op_nop(&mut self, operand: Operand) {
|
||||
// TODO: Cycle count (2)
|
||||
}
|
||||
|
||||
/// Bitwise OR with accumulator
|
||||
fn op_ora(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
@ -166,12 +417,121 @@ impl Cpu {
|
||||
self.registers.a = result;
|
||||
}
|
||||
|
||||
/// Push A to stack
|
||||
fn op_pha(&mut self, operand: Operand) {
|
||||
let a = self.registers.a;
|
||||
|
||||
self.stack_push(a);
|
||||
|
||||
// TODO: Cycle count (3)
|
||||
}
|
||||
|
||||
/// Push status to stack
|
||||
fn op_php(&mut self, operand: Operand) {
|
||||
let mut status = self.registers.status;
|
||||
status |= 0b00110000;
|
||||
|
||||
self.stack_push(status);
|
||||
|
||||
// TODO: Cycle count (3)
|
||||
}
|
||||
|
||||
/// Pull A from stack
|
||||
fn op_pla(&mut self, operand: Operand) {
|
||||
let a = self.stack_pop();
|
||||
|
||||
self.registers.a = a;
|
||||
|
||||
self.set_common_flags(a);
|
||||
// TODO: Cycle count (4)
|
||||
}
|
||||
|
||||
/// Pull status from stack
|
||||
fn op_plp(&mut self, operand: Operand) {
|
||||
let current_status = self.registers.status & 0b00110000;
|
||||
let status = self.stack_pop() & 0b11001111; // bits 4 and 5 are ignored
|
||||
|
||||
let result = status | current_status;
|
||||
|
||||
self.registers.status = result;
|
||||
// TODO: Cycle count (4)
|
||||
}
|
||||
|
||||
/// Rotate left
|
||||
fn op_rol(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let carry = self.get_status_flag_u8(CpuStatus::Carry);
|
||||
|
||||
let result = (value << 1) | carry;
|
||||
operand.write(result, self);
|
||||
|
||||
self.set_common_flags(result);
|
||||
self.set_status_flag_u8(CpuStatus::Carry, value & 0x80);
|
||||
}
|
||||
|
||||
/// Rotate right
|
||||
fn op_ror(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let carry = self.get_status_flag_u8(CpuStatus::Carry);
|
||||
|
||||
let result = (value >> 1) | (carry << 7);
|
||||
operand.write(result, self);
|
||||
|
||||
self.set_common_flags(result);
|
||||
self.set_status_flag_u8(CpuStatus::Carry, value & 0x01);
|
||||
}
|
||||
|
||||
/// Return from interrupt
|
||||
fn op_rti(&mut self, operand: Operand) {
|
||||
let current_status = self.registers.status & 0b00110000;
|
||||
let status = self.stack_pop() & 0b11001111; // bits 4 and 5 are ignored
|
||||
|
||||
let result_status = status | current_status;
|
||||
let pc = self.stack_pop_word();
|
||||
|
||||
self.registers.status = result_status;
|
||||
self.registers.pc = pc;
|
||||
|
||||
// TODO: Cycle count (6)
|
||||
}
|
||||
|
||||
/// Return from subroutine
|
||||
fn op_rts(&mut self, operand: Operand) {
|
||||
let pc = self.stack_pop_word();
|
||||
|
||||
let target_pc = pc.wrapping_add(1);
|
||||
self.registers.pc = target_pc;
|
||||
|
||||
// TODO: Cycle count (6)
|
||||
}
|
||||
|
||||
/// Subtract with carry from accumulator
|
||||
fn op_sbc(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
self.add_with_carry(!value);
|
||||
}
|
||||
|
||||
/// Set carry flag
|
||||
fn op_sec(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::Carry, true);
|
||||
|
||||
// TODO: Cycle count (2)
|
||||
}
|
||||
|
||||
/// Set decimal
|
||||
fn op_sed(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::Decimal, true);
|
||||
|
||||
// TODO: Cycle count (2)
|
||||
}
|
||||
|
||||
/// Set interrupt disable
|
||||
fn op_sei(&mut self, operand: Operand) {
|
||||
self.set_status_flag(CpuStatus::InterruptDisable, true);
|
||||
|
||||
// TODO: Cycle count (2)
|
||||
}
|
||||
|
||||
/// Store accumulator
|
||||
fn op_sta(&mut self, operand: Operand) {
|
||||
assert!(operand.is_address());
|
||||
@ -180,4 +540,65 @@ impl Cpu {
|
||||
operand.write(a, self);
|
||||
// TODO: C code enabled page crossing in the operand, but I didn't find it in the docs
|
||||
}
|
||||
|
||||
/// Store X
|
||||
fn op_stx(&mut self, operand: Operand) {
|
||||
assert!(operand.is_address());
|
||||
|
||||
let x = self.registers.x;
|
||||
operand.write(x, self);
|
||||
}
|
||||
|
||||
/// Store Y
|
||||
fn op_sty(&mut self, operand: Operand) {
|
||||
assert!(operand.is_address());
|
||||
|
||||
let y = self.registers.y;
|
||||
operand.write(y, self);
|
||||
}
|
||||
|
||||
/// Transfer A to X
|
||||
fn op_tax(&mut self, operand: Operand) {
|
||||
let a = self.registers.a;
|
||||
|
||||
self.registers.x = a;
|
||||
|
||||
self.set_common_flags(a);
|
||||
}
|
||||
|
||||
/// Transfer A to Y
|
||||
fn op_tay(&mut self, operand: Operand) {
|
||||
let a = self.registers.a;
|
||||
|
||||
self.registers.y = a;
|
||||
|
||||
self.set_common_flags(a);
|
||||
}
|
||||
|
||||
/// Transfer stack pointer to X
|
||||
fn op_tsx(&mut self, operand: Operand) {
|
||||
let sp = self.registers.sp;
|
||||
|
||||
self.registers.x = sp;
|
||||
|
||||
self.set_common_flags(sp);
|
||||
}
|
||||
|
||||
/// Transfer X to A
|
||||
fn op_txa(&mut self, operand: Operand) {
|
||||
let x = self.registers.x;
|
||||
|
||||
self.registers.a = x;
|
||||
|
||||
self.set_common_flags(x);
|
||||
}
|
||||
|
||||
/// Transfer Y to A
|
||||
fn op_tya(&mut self, operand: Operand) {
|
||||
let y = self.registers.y;
|
||||
|
||||
self.registers.a = y;
|
||||
|
||||
self.set_common_flags(y);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user