diff --git a/src/cpu.rs b/src/cpu.rs index 90e8dfa..f346200 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -201,6 +201,14 @@ impl CPU { address_from_bytes(slice[0], slice[1]).wrapping_add(y.into()), ) } + AddressingMode::ZeroPageIndirect => { + // Use [u8, ..1] from instruction + // This is where the absolute (16-bit) target address is stored. + // (Output: a 16-bit address) + let start = slice[0]; + let slice = read_address(memory, u16::from(start)); + OpInput::UseAddress(address_from_bytes(slice[0], slice[1])) + } }; // Increment program counter @@ -274,6 +282,16 @@ impl CPU { self.branch_if_not_equal(addr); } + (Instruction::BIT, OpInput::UseImmediate(val)) => { + self.registers.status.set_with_mask( + Status::PS_ZERO, + Status::new(StatusArgs { + zero: 0 == (self.registers.accumulator & val), + ..StatusArgs::none() + }), + ); + } + (Instruction::BIT, OpInput::UseAddress(addr)) => { let a: u8 = self.registers.accumulator; let m: u8 = self.memory.get_byte(addr); @@ -310,6 +328,11 @@ impl CPU { 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); @@ -321,6 +344,18 @@ impl CPU { self.registers.status.or(Status::PS_DISABLE_INTERRUPTS); } + (Instruction::BRKcld, OpInput::UseImplied) => { + for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() { + self.push_on_stack(b); + } + 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.registers.status.or(Status::PS_DISABLE_INTERRUPTS); + self.registers.status.and(!Status::PS_DECIMAL_MODE); + } + (Instruction::BVC, OpInput::UseRelative(rel)) => { let addr = self.registers.program_counter.wrapping_add(rel); self.branch_if_overflow_clear(addr); @@ -466,11 +501,47 @@ impl CPU { let val = self.registers.accumulator; self.push_on_stack(val); } + (Instruction::PHX, OpInput::UseImplied) => { + // Push X + self.push_on_stack(self.registers.index_x); + } + (Instruction::PHY, OpInput::UseImplied) => { + // Push Y + self.push_on_stack(self.registers.index_y); + } (Instruction::PHP, OpInput::UseImplied) => { // Push status let val = self.registers.status.bits() | 0x30; self.push_on_stack(val); } + (Instruction::PLX, OpInput::UseImplied) => { + // Pull accumulator + self.pull_from_stack(); + let val: u8 = self.fetch_from_stack(); + self.registers.index_x = val; + self.registers.status.set_with_mask( + Status::PS_ZERO | Status::PS_NEGATIVE, + Status::new(StatusArgs { + zero: val == 0, + negative: self.registers.accumulator > 127, + ..StatusArgs::none() + }), + ); + } + (Instruction::PLY, OpInput::UseImplied) => { + // Pull accumulator + self.pull_from_stack(); + let val: u8 = self.fetch_from_stack(); + self.registers.index_y = val; + self.registers.status.set_with_mask( + Status::PS_ZERO | Status::PS_NEGATIVE, + Status::new(StatusArgs { + zero: val == 0, + negative: self.registers.accumulator > 127, + ..StatusArgs::none() + }), + ); + } (Instruction::PLA, OpInput::UseImplied) => { // Pull accumulator self.pull_from_stack(); @@ -575,6 +646,9 @@ impl CPU { (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; @@ -584,6 +658,38 @@ impl CPU { let val = self.registers.accumulator; self.load_y_register(val); } + (Instruction::TRB, OpInput::UseAddress(addr)) => { + let val = self.memory.get_byte(addr); + + // The zero flag is set based on the result of the 'and'. + self.registers.status.set_with_mask( + Status::PS_ZERO, + Status::new(StatusArgs { + zero: 0 == (self.registers.accumulator & val), + ..StatusArgs::none() + }), + ); + + // The 1's in the accumulator set the corresponding bits in the operand + let res = self.registers.accumulator | val; + self.memory.set_byte(addr, res); + } + (Instruction::TSB, OpInput::UseAddress(addr)) => { + let val = self.memory.get_byte(addr); + + // The zero flag is set based on the result of the 'and'. + self.registers.status.set_with_mask( + Status::PS_ZERO, + Status::new(StatusArgs { + zero: 0 == (self.registers.accumulator & val), + ..StatusArgs::none() + }), + ); + + // The 1's in the accumulator clear the corresponding bits in the operand + let res = (self.registers.accumulator ^ 0xff) & val; + self.memory.set_byte(addr, res); + } (Instruction::TSX, OpInput::UseImplied) => { let StackPointer(val) = self.registers.stack_pointer; self.load_x_register(val); @@ -1007,6 +1113,10 @@ impl CPU { } } + 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; diff --git a/src/instruction.rs b/src/instruction.rs index f63dd5e..3a3c659 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -25,86 +25,208 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -// Abbreviations -// -// General -// -// M | `Memory location` -// -// Registers -// -// A | accumulator -// X | general purpose register -// Y | general purpose register -// F | processor status flags, collectively -// NV-BDIZC | processor status flags, individually -// S | stack pointer -// PC | program counter -// - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Instruction { - ADC, // ADd with Carry................ | NV ...ZC A = A + M + C - ADCnd, // ADd with Carry................ | NV ...ZC A = A + M + C - AND, // logical AND (bitwise)......... | N. ...Z. A = A && M - ASL, // Arithmetic Shift Left......... | N. ...ZC A = M << 1 - BCC, // Branch if Carry Clear......... | .. ..... PC = !C - BCS, // Branch if Carry Set........... | .. ..... PC = C - BEQ, // Branch if Equal (to zero?).... | .. ..... PC = Z - BIT, // BIT test...................... | NV ...Z. = A & M - BMI, // Branch if Minus............... | .. ..... PC = N - BNE, // Branch if Not Equal........... | .. ..... PC = !Z - BPL, // Branch if Positive............ | .. ..... PC = Z - BRK, // BReaK......................... | .. B.... S PC = - BVC, // Branch if oVerflow Clear...... | .. ..... PC = !V - BVS, // Branch if oVerflow Set........ | .. ..... PC = V - CLC, // CLear Carry flag.............. | .. ....C = 0 - CLD, // Clear Decimal Mode............ | .. .D... = 0 - CLI, // Clear Interrupt Disable....... | .. ..I.. = 0 - CLV, // Clear oVerflow flag........... | .V ..... = 0 - CMP, // Compare....................... | N. ...ZC = A - M - CPX, // Compare X register............ | N. ...ZC = X - M - CPY, // Compare Y register............ | N. ...ZC = Y - M - DEC, // DECrement memory.............. | N. ...Z. M = M - 1 - DEX, // DEcrement X register.......... | N. ...Z. X = X - 1 - DEY, // DEcrement Y register.......... | N. ...Z. Y = Y - 1 - EOR, // Exclusive OR (bitwise)........ | N. ...Z. A = A ^ M - INC, // INCrement memory.............. | N. ...Z. M = M + 1 - INX, // INcrement X register.......... | N. ...Z. X = X + 1 - INY, // INcrement Y register.......... | N. ...Z. Y = Y + 1 - JMP, // JuMP.......................... | .. ..... S PC = - JSR, // Jump to SubRoutine............ | .. ..... S PC = - LDA, // LoaD Accumulator.............. | N. ...Z. A = M - LDX, // LoaD X register............... | N. ...Z. X = M - LDY, // LoaD Y register............... | N. ...Z. Y = M - LSR, // Logical Shift Right........... | N. ...ZC A = A/2 - // or N. ...ZC M = M/2 - NOP, // No OPeration.................. | .. ..... = - ORA, // inclusive OR (bitwise)........ | N. ...Z. A = A | M - PHA, // PusH Accumulator.............. | .. ..... S M = A - PHP, // PusH Processor status......... | .. ..... S M = F - PLA, // PuLl Accumulator.............. | N. ...Z. A S = M (stack) - PLP, // PuLl Processor status......... | NV BDIZC S = M (stack) - ROL, // ROtate Left................... | N. ...ZC A = C A rotated - // or N. ...ZC M = C M rotated - ROR, // ROtate Right.................. | N. ...ZC A = C A rotated - // or N. ...ZC M = C M rotated - RTI, // ReTurn from Interrupt......... | NV BDIZC PC = M (stack) - RTS, // ReTurn from Subroutine........ | .. ..... PC = M (stack) - SBC, // SuBtract with Carry........... | NV ...ZC A = A-M-(1-C) - SBCnd, // SuBtract with Carry........... | NV ...ZC A = A-M-(1-C) - SEC, // SEt Carry flag................ | .. ....C = 1 - SED, // SEt Decimal flag.............. | .. .D... = 1 - SEI, // SEt Interrupt disable......... | .. ..I.. = 1 - STA, // STore Accumulator............. | .. ..... M = A - STX, // STore X register.............. | .. ..... M = X - STY, // STore Y register.............. | .. ..... 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 - TXA, // Transfer X to Accumulator..... | N. ...Z. A = X - TXS, // Transfer X to Stack pointer... | .. ..... S = X - TYA, // Transfer Y to Accumulator..... | N. ...Z. A = Y + // ADd with Carry + ADC, + + // ADd with Carry. This one has now decimal mode. + ADCnd, + + // logical AND (bitwise) + AND, + + // Arithmetic Shift Left + ASL, + + // Branch if Carry Clear + BCC, + + // Branch if Carry Set + BCS, + + // Branch if Equal (to zero?) + BEQ, + + // BIT test + BIT, + + // Branch if Minus + BMI, + + // Branch if Not Equal + BNE, + + // Branch if Positive + BPL, + + // Unconditional BRAnch + BRA, + + // BReaK + BRK, + + // BReaK, clearing decimal flag + BRKcld, + + // Branch if oVerflow Clear + BVC, + + // Branch if oVerflow Set + BVS, + + // CLear Carry flag + CLC, + + // Clear Decimal Mode + CLD, + + // Clear Interrupt Disable + CLI, + + // Clear oVerflow flag + CLV, + + // Compare + CMP, + + // Compare X register + CPX, + + // Compare Y register + CPY, + + // DECrement memory + DEC, + + // DEcrement X register + DEX, + + // DEcrement Y register + DEY, + + // Exclusive OR (bitwise) + EOR, + + // INCrement memory + INC, + + // INcrement X register + INX, + + // INcrement Y register + INY, + + // JuMP + JMP, + + // Jump to SubRoutine + JSR, + + // LoaD Accumulator + LDA, + + // LoaD X register + LDX, + + // LoaD Y register + LDY, + + // Logical Shift Right + LSR, + + // No OPeration + NOP, + + // inclusive OR (bitwise) + ORA, + + // PusH Accumulator + PHA, + + // PusH X + PHX, + + // PusH Y + PHY, + + // PusH Processor status + PHP, + + // PuLl Accumulator + PLA, + + // PuLl X + PLX, + + // PuLl Y + PLY, + + // PuLl Processor status + PLP, + + // ROtate Left + ROL, + + // ROtate Right + ROR, + + // ReTurn from Interrupt + RTI, + + // ReTurn from Subroutine + RTS, + + // SuBtract with Carry + SBC, + + // SuBtract with Carry. This one has now decimal mode. + SBCnd, + + // SEt Carry flag + SEC, + + // SEt Decimal flag + SED, + + // SEt Interrupt disable + SEI, + + // STore Accumulator + STA, + + // STore X register + STX, + + // STore Y register + STY, + + // STore Zero + STZ, + + // Transfer Accumulator to X + TAX, + + // Transfer Accumulator to Y + TAY, + + // Test and Reset Bits + TRB, + + // Test and Set Bits + TSB, + + // Transfer Stack pointer to X + TSX, + + // Transfer X to Accumulator + TXA, + + // Transfer X to Stack pointer + TXS, + + // Transfer Y to Accumulator + TYA, } #[derive(Copy, Clone)] @@ -158,6 +280,9 @@ pub enum AddressingMode { // load from (address stored at constant zero page address) plus Y register, e. g. `lda ($10),Y`. IndirectIndexedY, + + // Address stored at constant zero page address + ZeroPageIndirect, } impl AddressingMode { @@ -177,6 +302,7 @@ impl AddressingMode { AddressingMode::BuggyIndirect => 2, AddressingMode::IndexedIndirectX => 1, AddressingMode::IndirectIndexedY => 1, + AddressingMode::ZeroPageIndirect => 1, } } } @@ -455,24 +581,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, } } } @@ -483,13 +600,10 @@ pub struct RevisionA; impl crate::Variant for RevisionA { fn decode(opcode: u8) -> Option<(Instruction, AddressingMode)> { - match opcode { - 0x66 => None, - 0x6a => None, - 0x6e => None, - 0x76 => None, - 0x7e => None, - _ => Nmos6502::decode(opcode), + // It's the same as on NMOS, but has no ROR instruction. + match Nmos6502::decode(opcode) { + Some((Instruction::ROR, _)) => None, + something_else => something_else, } } } @@ -501,7 +615,32 @@ 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 { + 0x00 => Some((Instruction::BRKcld, AddressingMode::Implied)), + 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)), + 0x7a => Some((Instruction::PLY, AddressingMode::Implied)), + 0xfa => Some((Instruction::PLX, AddressingMode::Implied)), + 0x5a => Some((Instruction::PHY, AddressingMode::Implied)), + 0xda => Some((Instruction::PHX, AddressingMode::Implied)), + 0x04 => Some((Instruction::TSB, AddressingMode::ZeroPage)), + 0x14 => Some((Instruction::TRB, AddressingMode::ZeroPage)), + 0x0c => Some((Instruction::TSB, AddressingMode::Absolute)), + 0x1c => Some((Instruction::TRB, AddressingMode::Absolute)), + 0x12 => Some((Instruction::ORA, AddressingMode::ZeroPageIndirect)), + 0x32 => Some((Instruction::AND, AddressingMode::ZeroPageIndirect)), + 0x52 => Some((Instruction::EOR, AddressingMode::ZeroPageIndirect)), + 0x72 => Some((Instruction::ADC, AddressingMode::ZeroPageIndirect)), + 0x92 => Some((Instruction::STA, AddressingMode::ZeroPageIndirect)), + 0xb2 => Some((Instruction::LDA, AddressingMode::ZeroPageIndirect)), + 0xd2 => Some((Instruction::CMP, AddressingMode::ZeroPageIndirect)), + 0xf2 => Some((Instruction::SBC, AddressingMode::ZeroPageIndirect)), + 0x89 => Some((Instruction::BIT, AddressingMode::Immediate)), _ => Nmos6502::decode(opcode), } }