Compare commits

...

13 Commits

Author SHA1 Message Date
omarandlorraine 54196929d8
Merge 0f1c01ce60 into 4847744518 2024-04-24 14:57:31 +01:00
Sam M W 0f1c01ce60 implement stz 2024-04-24 14:51:43 +01:00
Sam M W 2c26ebb00a decode inc a and dec a on CMOS 2024-04-24 14:51:43 +01:00
Sam M W df51b077e7 add/implement BRA instruction for CMOS 2024-04-24 14:51:43 +01:00
Sam M W 309ad50374 simpler/more obvious way to select for different implementations on derivatives 2024-04-24 14:51:43 +01:00
Sam M W 4847744518 correction to comment 2024-04-24 14:51:08 +01:00
Matthias Endler 11d9540729 fix typo 2024-04-24 15:41:25 +02:00
Sam M W bf06ad8924 better commenting inside of AddressingMode enum 2024-04-24 15:41:25 +02:00
Sam M W 54dd0cd536 rename IndirectWithFix to Indirect 2024-04-24 15:41:25 +02:00
Sam M W 2444ef52d1 fix typo in comment 2024-04-24 15:41:25 +02:00
Sam M W ad622bc930 formatting 2024-04-24 15:41:25 +02:00
Sam M W 97d6b3fd89 split the Indirect addressing mode into BuggyIndirect and IndirectWithFix 2024-04-24 15:41:25 +02:00
Sam M W da30c8c67d change arr_to_addr to address_from_bytes 2024-04-24 15:41:25 +02:00
2 changed files with 127 additions and 50 deletions

View File

@ -31,10 +31,8 @@ use crate::Variant;
use crate::registers::{Registers, StackPointer, Status, StatusArgs};
fn arr_to_addr(arr: &[u8]) -> u16 {
debug_assert!(arr.len() == 2);
u16::from(arr[0]) + (u16::from(arr[1]) << 8usize)
fn address_from_bytes(lo: u8, hi: u8) -> u16 {
u16::from(lo) + (u16::from(hi) << 8usize)
}
#[derive(Clone)]
@ -136,24 +134,52 @@ impl<M: Bus, V: Variant> CPU<M, V> {
AddressingMode::Absolute => {
// Use [u8, ..2] from instruction as address
// (Output: a 16-bit address)
OpInput::UseAddress(arr_to_addr(&slice))
OpInput::UseAddress(address_from_bytes(slice[0], slice[1]))
}
AddressingMode::AbsoluteX => {
// Use [u8, ..2] from instruction as address, add X
// (Output: a 16-bit address)
OpInput::UseAddress(arr_to_addr(&slice).wrapping_add(x.into()))
OpInput::UseAddress(
address_from_bytes(slice[0], slice[1]).wrapping_add(x.into()),
)
}
AddressingMode::AbsoluteY => {
// Use [u8, ..2] from instruction as address, add Y
// (Output: a 16-bit address)
OpInput::UseAddress(arr_to_addr(&slice).wrapping_add(y.into()))
OpInput::UseAddress(
address_from_bytes(slice[0], slice[1]).wrapping_add(y.into()),
)
}
AddressingMode::Indirect => {
// Use [u8, ..2] from instruction as an address. Interpret the
// two bytes starting at that address as an address.
// (Output: a 16-bit address)
let slice = read_address(memory, arr_to_addr(&slice));
OpInput::UseAddress(arr_to_addr(&slice))
// TODO: If the pointer ends in 0xff, then incrementing it would propagate
// the carry to the high byte of the pointer. This incurs a cost of one
// machine cycle on the real 65C02, which is not implemented here.
let slice = read_address(memory, address_from_bytes(slice[0], slice[1]));
OpInput::UseAddress(address_from_bytes(slice[0], slice[1]))
}
AddressingMode::BuggyIndirect => {
// Use [u8, ..2] from instruction as an address. Interpret the
// two bytes starting at that address as an address.
// (Output: a 16-bit address)
let pointer = address_from_bytes(slice[0], slice[1]);
let low_byte_of_target = memory.get_byte(pointer);
let low_byte_of_incremented_pointer =
pointer.to_le_bytes()[0].wrapping_add(1);
let incremented_pointer = u16::from_le_bytes([
low_byte_of_incremented_pointer,
pointer.to_le_bytes()[1],
]);
let high_byte_of_target = memory.get_byte(incremented_pointer);
OpInput::UseAddress(address_from_bytes(
low_byte_of_target,
high_byte_of_target,
))
}
AddressingMode::IndexedIndirectX => {
// Use [u8, ..1] from instruction
@ -162,7 +188,7 @@ impl<M: Bus, V: Variant> CPU<M, V> {
// (Output: a 16-bit address)
let start = slice[0].wrapping_add(x);
let slice = read_address(memory, u16::from(start));
OpInput::UseAddress(arr_to_addr(&slice))
OpInput::UseAddress(address_from_bytes(slice[0], slice[1]))
}
AddressingMode::IndirectIndexedY => {
// Use [u8, ..1] from instruction
@ -171,7 +197,9 @@ impl<M: Bus, V: Variant> CPU<M, V> {
// (Output: a 16-bit address)
let start = slice[0];
let slice = read_address(memory, u16::from(start));
OpInput::UseAddress(arr_to_addr(&slice).wrapping_add(y.into()))
OpInput::UseAddress(
address_from_bytes(slice[0], slice[1]).wrapping_add(y.into()),
)
}
};
@ -282,6 +310,11 @@ impl<M: Bus, V: Variant> CPU<M, V> {
self.branch_if_positive(addr);
}
(Instruction::BRA, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch(addr);
}
(Instruction::BRK, OpInput::UseImplied) => {
for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() {
self.push_on_stack(b);
@ -547,6 +580,9 @@ impl<M: Bus, V: Variant> CPU<M, V> {
(Instruction::STY, OpInput::UseAddress(addr)) => {
self.memory.set_byte(addr, self.registers.index_y);
}
(Instruction::STZ, OpInput::UseAddress(addr)) => {
self.memory.set_byte(addr, 0);
}
(Instruction::TAX, OpInput::UseImplied) => {
let val = self.registers.accumulator;
@ -979,6 +1015,10 @@ impl<M: Bus, V: Variant> CPU<M, V> {
}
}
fn branch(&mut self, addr: u16) {
self.registers.program_counter = addr;
}
fn branch_if_positive(&mut self, addr: u16) {
if !self.registers.status.contains(Status::PS_NEGATIVE) {
self.registers.program_counter = addr;

View File

@ -55,6 +55,7 @@ pub enum Instruction {
BMI, // Branch if Minus............... | .. ..... PC = N
BNE, // Branch if Not Equal........... | .. ..... PC = !Z
BPL, // Branch if Positive............ | .. ..... PC = Z
BRA, // Unconditional BRAnch.......... | .. B.... S PC =
BRK, // BReaK......................... | .. B.... S PC =
BVC, // Branch if oVerflow Clear...... | .. ..... PC = !V
BVS, // Branch if oVerflow Set........ | .. ..... PC = V
@ -99,6 +100,7 @@ pub enum Instruction {
STA, // STore Accumulator............. | .. ..... M = A
STX, // STore X register.............. | .. ..... M = X
STY, // STore Y register.............. | .. ..... M = Y
STZ, // STore Zero.................... | .. ..... M = Y
TAX, // Transfer Accumulator to X..... | N. ...Z. X = A
TAY, // Transfer Accumulator to Y..... | N. ...Z. Y = A
TSX, // Transfer Stack pointer to X... | N. ...Z. X = S
@ -117,21 +119,47 @@ pub enum OpInput {
#[derive(Copy, Clone)]
pub enum AddressingMode {
Accumulator, // 1 LSR A work directly on accumulator
Implied, // 1 BRK
Immediate, // 2 LDA #10 8-bit constant in instruction
ZeroPage, // 2 LDA $00 zero-page address
ZeroPageX, // 2 LDA $80,X address is X register + 8-bit constant
ZeroPageY, // 2 LDX $10,Y address is Y register + 8-bit constant
Relative, // 2 BNE LABEL branch target as signed relative offset
Absolute, // 3 JMP $1000 full 16-bit address
AbsoluteX, // 3 STA $1000,X full 16-bit address plus X register
AbsoluteY, // 3 STA $1000,Y full 16-bit address plus Y register
Indirect, // 3 JMP ($1000) jump to address stored at address
IndexedIndirectX, // 2 LDA ($10,X) load from address stored at (constant
// zero page address plus X register)
IndirectIndexedY, // 2 LDA ($10),Y load from (address stored at constant
// zero page address) plus Y register
// work directly on accumulator, e. g. `lsr a`.
Accumulator,
// BRK
Implied,
// 8-bit constant in instruction, e. g. `lda #10`.
Immediate,
// zero-page address, e. g. `lda $00`.
ZeroPage,
// address is X register + 8-bit constant, e. g. `lda $80,x`.
ZeroPageX,
// address is Y register + 8-bit constant, e. g. `ldx $10,y`.
ZeroPageY,
// branch target as signed relative offset, e. g. `bne label`.
Relative,
// full 16-bit address, e. g. `jmp $1000`.
Absolute,
// full 16-bit address plus X register, e. g. `sta $1000,X`.
AbsoluteX,
// full 16-bit address plus Y register, e. g. `sta $1000,Y`.
AbsoluteY,
// jump to address stored at address, with the page-crossing bug found in NMOS chips, e. g. `jmp ($1000)`.
BuggyIndirect,
// jump to address stored at address, e. g. `jmp ($1000)`.
Indirect,
// load from address stored at (constant zero page address plus X register), e. g. `lda ($10,X)`.
IndexedIndirectX,
// load from (address stored at constant zero page address) plus Y register, e. g. `lda ($10),Y`.
IndirectIndexedY,
}
impl AddressingMode {
@ -148,6 +176,7 @@ impl AddressingMode {
AddressingMode::AbsoluteX => 2,
AddressingMode::AbsoluteY => 2,
AddressingMode::Indirect => 2,
AddressingMode::BuggyIndirect => 2,
AddressingMode::IndexedIndirectX => 1,
AddressingMode::IndirectIndexedY => 1,
}
@ -270,7 +299,7 @@ impl crate::Variant for Nmos6502 {
0x69 => Some((Instruction::ADC, AddressingMode::Immediate)),
0x6a => Some((Instruction::ROR, AddressingMode::Accumulator)),
0x6b => None,
0x6c => Some((Instruction::JMP, AddressingMode::Indirect)),
0x6c => Some((Instruction::JMP, AddressingMode::BuggyIndirect)),
0x6d => Some((Instruction::ADC, AddressingMode::Absolute)),
0x6e => Some((Instruction::ROR, AddressingMode::Absolute)),
0x6f => None,
@ -428,24 +457,15 @@ pub struct Ricoh2a03;
impl crate::Variant for Ricoh2a03 {
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
match opcode {
0x61 => Some((Instruction::ADCnd, AddressingMode::IndexedIndirectX)),
0x65 => Some((Instruction::ADCnd, AddressingMode::ZeroPage)),
0x69 => Some((Instruction::ADCnd, AddressingMode::Immediate)),
0x6d => Some((Instruction::ADCnd, AddressingMode::Absolute)),
0x71 => Some((Instruction::ADCnd, AddressingMode::IndirectIndexedY)),
0x75 => Some((Instruction::ADCnd, AddressingMode::ZeroPageX)),
0x79 => Some((Instruction::ADCnd, AddressingMode::AbsoluteY)),
0x7d => Some((Instruction::ADCnd, AddressingMode::AbsoluteX)),
0xe1 => Some((Instruction::SBCnd, AddressingMode::IndexedIndirectX)),
0xe5 => Some((Instruction::SBCnd, AddressingMode::ZeroPage)),
0xe9 => Some((Instruction::SBCnd, AddressingMode::Immediate)),
0xed => Some((Instruction::SBCnd, AddressingMode::Absolute)),
0xf1 => Some((Instruction::SBCnd, AddressingMode::IndirectIndexedY)),
0xf5 => Some((Instruction::SBCnd, AddressingMode::ZeroPageX)),
0xf9 => Some((Instruction::SBCnd, AddressingMode::AbsoluteY)),
0xfd => Some((Instruction::SBCnd, AddressingMode::AbsoluteX)),
_ => Nmos6502::decode(opcode),
// It's the same as on NMOS, but doesn't support decimal mode.
match Nmos6502::decode(opcode) {
Some((Instruction::ADC, addressing_mode)) => {
Some((Instruction::ADCnd, addressing_mode))
}
Some((Instruction::SBC, addressing_mode)) => {
Some((Instruction::SBCnd, addressing_mode))
}
something_else => something_else,
}
}
}
@ -456,12 +476,29 @@ pub struct RevisionA;
impl crate::Variant for RevisionA {
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
// It's the same as on NMOS, but has no ROR instruction.
match Nmos6502::decode(opcode) {
Some((Instruction::ROR, _)) => None,
something_else => something_else,
}
}
}
/// Emulates the 65C02, which has a few bugfixes, and another addressing mode
pub struct Cmos6502;
impl crate::Variant for Cmos6502 {
fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> {
// TODO: We obviously need to add the other CMOS instructions here.
match opcode {
0x66 => None,
0x6a => None,
0x6e => None,
0x76 => None,
0x7e => None,
0x1a => Some((Instruction::INC, AddressingMode::Accumulator)),
0x3a => Some((Instruction::DEC, AddressingMode::Accumulator)),
0x6c => Some((Instruction::JMP, AddressingMode::Indirect)),
0x80 => Some((Instruction::BRA, AddressingMode::Relative)),
0x64 => Some((Instruction::STZ, AddressingMode::ZeroPage)),
0x74 => Some((Instruction::STZ, AddressingMode::ZeroPageX)),
0x9c => Some((Instruction::STZ, AddressingMode::Absolute)),
0x9e => Some((Instruction::STZ, AddressingMode::AbsoluteX)),
_ => Nmos6502::decode(opcode),
}
}