Adds BIT and JMP instructions

joeyrp 2 years ago
parent 400e378492
commit d65c7cfd88

@ -1,45 +1,45 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 're6502'",
"cargo": {
"args": [
"build",
"--bin=re6502",
"--package=re6502"
],
"filter": {
"name": "re6502",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 're6502'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=re6502",
"--package=re6502"
],
"filter": {
"name": "re6502",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 're6502'",
"cargo": {
"args": [
"build",
"--bin=re6502",
"--package=re6502"
],
"filter": {
"name": "re6502",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 're6502'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=re6502",
"--package=re6502"
],
"filter": {
"name": "re6502",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

@ -1,37 +1,37 @@
The code in the methods r6502::instructions::ADC and r6502::instructions::SDC
is adapted from code written by javidx9 (David Barr) and is subject to the following license:
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018-2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
The code in the methods r6502::instructions::ADC and r6502::instructions::SDC
is adapted from code written by javidx9 (David Barr) and is subject to the following license:
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018-2019 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,259 +1,310 @@
#![allow(unused_variables, dead_code, non_snake_case)]
use super::{R6502, Bus};
#[derive(Clone, Copy, PartialEq)]
pub enum ModeID
{
IMP, // Implied
ACM, // Accumulator
IMM, // Immediate
ZP0, // Zero Page
ZPX, // Zero Page, X
ZPY, // Zero Page, Y
REL, // Relative
ABS, // Absolute
ABX, // Absolute, X
ABY, // Aboslute, Y
IND, // Indirect
IZX, // Indirect, X
IZY, // Indirect, Y
ERR, // Error mode - this is an invalid mode
}
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
// GROUP ONE ADDRESS MODES
// 000 (zero page,X) IZX
// 001 zero page ZP0
// 010 #immediate IMM
// 011 absolute ABS
// 100 (zero page),Y IZY
// 101 zero page,X ZPX
// 110 absolute,Y ABY
// 111 absolute,X ABX
// GROUP TWO ADDRESS MODES
// 000 #immediate IMM
// 001 zero page ZP0
// 010 accumulator ACM
// 011 absolute ABS
// 100 NONE ERR
// 101 zero page,X ZPX
// 110 NONE ERR
// 111 absolute,X ABX
pub struct AddressingModes;
impl AddressingModes
{
pub const GROUP_ONE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
AddressingModes::IZX,
AddressingModes::ZP0,
AddressingModes::IMM,
AddressingModes::ABS,
AddressingModes::IZY,
AddressingModes::ZPX,
AddressingModes::ABY,
AddressingModes::ABX,
];
pub const GROUP_TWO_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
AddressingModes::IMM,
AddressingModes::ZP0,
AddressingModes::ACM,
AddressingModes::ABS,
AddressingModes::ERR,
AddressingModes::ZPX,
AddressingModes::ERR,
AddressingModes::ABX,
];
}
impl AddressingModes
{
pub fn ERR(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
ModeID::ERR
}
pub fn IMP(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
ModeID::IMP
}
pub fn ACM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_data = cpu.a as u16;
ModeID::ACM
}
pub fn IMM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_data = bus.read(cpu.pc) as u16;
cpu.pc += 1;
ModeID::IMM
}
pub fn ZP0(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZP0
}
pub fn ZPX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.working_addr += cpu.x as u16;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZPX
}
pub fn ZPY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.working_addr += cpu.y as u16;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZPY
}
pub fn REL(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// NOTE: Not sure if we can use the working_data variable for this.
// if any instruction using this address mode needs extra data read
// then we need another variable to store this address
//
// Use working_addr to just like the other modes
cpu.working_data = bus.read(cpu.pc) as u16;
cpu.pc += 1;
ModeID::REL
}
pub fn ABS(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABS
}
pub fn ABX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_addr += cpu.x as u16;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABX
}
pub fn ABY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_addr += cpu.y as u16;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABY
}
pub fn IND(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
ModeID::IND
}
// Indexed Indirect Addressing (IND, X)
// In indexed indirect addressing (referred to as (Indirect, X)), the second byte of the
// instruction is added to the contents of the X register, discarding the carry.
// The result of the addition points to a memory location on the Zero Page which contains
// the low order byte of the effective address. The next memory location in page zero,
// contains the high order byte of the effective address. Both memory locations specifying
// the effective address must be in the Zero Page.
//
// Info from:
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
pub fn IZX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
let offset = bus.read(cpu.pc) as u16;
cpu.pc += 1;
let mut pointer = cpu.x as u16 + offset;
// discard the carry and wrap
// If the addition goes beyond the Zero Page
// it should wrap around back to the beginning
pointer = pointer & 0x00FF;
let lo_byte = bus.read(pointer) as u16;
let hi_byte = bus.read(pointer + 1) as u16;
cpu.working_addr = (hi_byte << 0x08) | lo_byte;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::IZX
}
// Indirect Indexed Addressing (IND), Y
// In indirect indexed addressing, the second byte of the instruction points to
// a memory location in page zero. The contents of this memory location are added to
// the contents of the Y register. The result is the low order byte of the effective address.
// The carry from this addition is added to the contents of the next page zero memory
// location, to form the high order byte of the effective address.
//
// Info from:
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
pub fn IZY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// zp_pointer points to a location in zero page
let zp_pointer = bus.read(cpu.pc) as u16;
cpu.pc += 1;
// The value at zp_pointer is added to the Y register
let zp_value = bus.read(zp_pointer) as u16;
let sum = zp_value + cpu.y as u16;
// The sum with the carry discarded is the lo byte
let lo_byte = sum & 0x00FF;
// The carry plus the value at the next zero page address is the hi byte
let zp_next = bus.read(zp_pointer + 1) as u16;
let temp = (sum & 0xFF00) >> 0x08;
let temp2 = temp + zp_next;
let hi_byte: u8 = (((sum & 0xFF00) >> 0x08) + zp_next) as u8;
// Store the final address and read the data
cpu.working_addr = ((hi_byte as u16) << 0x08) | lo_byte;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::IZY
}
#![allow(unused_variables, dead_code, non_snake_case)]
use super::{R6502, Bus};
#[derive(Clone, Copy, PartialEq)]
pub enum ModeID
{
IMP, // Implied
ACM, // Accumulator
IMM, // Immediate
ZP0, // Zero Page
ZPX, // Zero Page, X
ZPY, // Zero Page, Y
REL, // Relative
ABS, // Absolute
ABX, // Absolute, X
ABY, // Aboslute, Y
IND, // Indirect
IZX, // Indirect, X
IZY, // Indirect, Y
ERR, // Error mode - this is an invalid mode
}
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
// GROUP ONE ADDRESS MODES
// 000 (zero page,X) IZX
// 001 zero page ZP0
// 010 #immediate IMM
// 011 absolute ABS
// 100 (zero page),Y IZY
// 101 zero page,X ZPX
// 110 absolute,Y ABY
// 111 absolute,X ABX
// GROUP TWO ADDRESS MODES
// 000 #immediate IMM
// 001 zero page ZP0
// 010 accumulator ACM
// 011 absolute ABS
// 100 NONE ERR
// 101 zero page,X ZPX
// 110 NONE ERR
// 111 absolute,X ABX
// GROUP THREE ADDRES MODES
// 000 #immediate IMM
// 001 zero page ZP0
// 011 absolute ABS
// 101 zero page,X ZPX
// 111 absolute,X ABX
pub struct AddressingModes;
impl AddressingModes
{
pub const GROUP_ONE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
AddressingModes::IZX,
AddressingModes::ZP0,
AddressingModes::IMM,
AddressingModes::ABS,
AddressingModes::IZY,
AddressingModes::ZPX,
AddressingModes::ABY,
AddressingModes::ABX,
];
pub const GROUP_TWO_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
AddressingModes::IMM,
AddressingModes::ZP0,
AddressingModes::ACM,
AddressingModes::ABS,
AddressingModes::ERR,
AddressingModes::ZPX,
AddressingModes::ERR,
AddressingModes::ABX,
];
pub const GROUP_THREE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
AddressingModes::IMM,
AddressingModes::ZP0,
AddressingModes::ERR,
AddressingModes::ABS,
AddressingModes::ERR,
AddressingModes::ZPX,
AddressingModes::ERR,
AddressingModes::ABX,
];
}
impl AddressingModes
{
pub fn ERR(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// TODO: Better error handling
ModeID::ERR
}
pub fn IMP(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
ModeID::IMP
}
pub fn ACM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_data = cpu.a as u16;
ModeID::ACM
}
pub fn IMM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_data = bus.read(cpu.pc) as u16;
cpu.pc += 1;
ModeID::IMM
}
pub fn ZP0(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZP0
}
pub fn ZPX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.working_addr += cpu.x as u16;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZPX
}
pub fn ZPY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
cpu.working_addr += cpu.y as u16;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::ZPY
}
pub fn REL(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// NOTE: Not sure if we can use the working_data variable for this.
// if any instruction using this address mode needs extra data read
// then we need another variable to store this address
//
// Use working_addr to just like the other modes
cpu.working_data = bus.read(cpu.pc) as u16;
cpu.pc += 1;
ModeID::REL
}
pub fn ABS(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABS
}
pub fn ABX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_addr += cpu.x as u16;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABX
}
pub fn ABY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
cpu.working_addr = bus.read(cpu.pc) as u16;
cpu.pc += 1;
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
cpu.pc += 1;
cpu.working_addr += cpu.y as u16;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::ABY
}
// https://www.nesdev.org/obelisk-6502-guide/addressing.html#IND
// JMP is the only 6502 instruction to support indirection.
// The instruction contains a 16 bit address which identifies the location of
// the least significant byte of another 16 bit memory address which is the real
// target of the instruction.
// For example if location $0120 contains $FC and location $0121 contains $BA then
// the instruction JMP ($0120) will cause the next instruction execution to occur at
// $BAFC (e.g. the contents of $0120 and $0121).
pub fn IND(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// https://www.nesdev.org/obelisk-6502-guide/reference.html#JMP
// NOTE: An original 6502 does not correctly fetch the target address
// if the indirect vector falls on a page boundary (e.g. $xxFF where xx is any value from $00 to $FF).
// In this case it fetches the LSB from $xxFF as expected but takes the MSB from $xx00.
let ptr_lo = bus.read(cpu.pc) as u16;
cpu.pc += 1;
let ptr_hi = bus.read(cpu.pc) as u16;
cpu.pc += 1;
let ptr = (ptr_hi << 8) | ptr_lo;
let addr_lo = bus.read(ptr) as u16;
let mut addr_hi = bus.read(ptr + 1) as u16;
// Emulate the bug
if ptr_lo == 0xFF
{
addr_hi = bus.read(ptr & 0xFF00) as u16;
}
cpu.working_addr = (addr_hi << 8) | addr_lo;
ModeID::IND
}
// Indexed Indirect Addressing (IND, X)
// In indexed indirect addressing (referred to as (Indirect, X)), the second byte of the
// instruction is added to the contents of the X register, discarding the carry.
// The result of the addition points to a memory location on the Zero Page which contains
// the low order byte of the effective address. The next memory location in page zero,
// contains the high order byte of the effective address. Both memory locations specifying
// the effective address must be in the Zero Page.
//
// Info from:
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
pub fn IZX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
let offset = bus.read(cpu.pc) as u16;
cpu.pc += 1;
let mut pointer = cpu.x as u16 + offset;
// discard the carry and wrap
// If the addition goes beyond the Zero Page
// it should wrap around back to the beginning
pointer = pointer & 0x00FF;
let lo_byte = bus.read(pointer) as u16;
let hi_byte = bus.read(pointer + 1) as u16;
cpu.working_addr = (hi_byte << 0x08) | lo_byte;
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
ModeID::IZX
}
// Indirect Indexed Addressing (IND), Y
// In indirect indexed addressing, the second byte of the instruction points to
// a memory location in page zero. The contents of this memory location are added to
// the contents of the Y register. The result is the low order byte of the effective address.
// The carry from this addition is added to the contents of the next page zero memory
// location, to form the high order byte of the effective address.
//
// Info from:
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
pub fn IZY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
{
// zp_pointer points to a location in zero page
let zp_pointer = bus.read(cpu.pc) as u16;
cpu.pc += 1;
// The value at zp_pointer is added to the Y register
let zp_value = bus.read(zp_pointer) as u16;
let sum = zp_value + cpu.y as u16;
// The sum with the carry discarded is the lo byte
let lo_byte = sum & 0x00FF;
// The carry plus the value at the next zero page address is the hi byte
let zp_next = bus.read(zp_pointer + 1) as u16;
let temp = (sum & 0xFF00) >> 0x08;
let temp2 = temp + zp_next;
let hi_byte: u8 = (((sum & 0xFF00) >> 0x08) + zp_next) as u8;
// Store the final address and read the data
cpu.working_addr = ((hi_byte as u16) << 0x08) | lo_byte;
cpu.working_data = bus.read(cpu.working_addr) as u16;
ModeID::IZY
}
}

@ -1,424 +1,503 @@
#![allow(dead_code, non_snake_case)]
use super::{R6502, Bus, Flags, addressing_modes::{AddressingModes, ModeID}};
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
// GROUP ONE
// 000 ORA
// 001 AND
// 010 EOR
// 011 ADC
// 100 STA
// 101 LDA
// 110 CMP
// 111 SBC
pub struct Instructions;
impl Instructions
{
pub const GROUP_ONE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
Instructions::ORA,
Instructions::AND,
Instructions::EOR,
Instructions::ADC,
Instructions::STA,
Instructions::LDA,
Instructions::CMP,
Instructions::SBC,
];
pub const GROUP_TWO_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
Instructions::ASL, // 000
Instructions::ROL, // 001
Instructions::LSR, // 010
Instructions::ROR, // 011
Instructions::STX, // 100
Instructions::LDX, // 101
Instructions::DEC, // 110
Instructions::INC, // 111
];
}
impl Instructions
{
///////////////////////////////////////////////////////////
// GROUP ONE
pub fn ORA(cpu: &mut R6502, _bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a | data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn AND(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a & data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn EOR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a ^ data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
// Using a technique written javidx9
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L659
pub fn ADC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let carry = cpu.check_flag(Flags::C) as u16;
// 16 bit addition to capture the carry easier
let temp: u16 = cpu.a as u16 + cpu.working_data + carry;
if temp > 255
{
cpu.set_flag(Flags::C);
}
if temp == 0
{
cpu.set_flag(Flags::Z);
}
let did_overflow = (!((cpu.a as u16) ^ (cpu.working_data as u16)) & ((cpu.a as u16) ^ temp)) & 0x0080;
cpu.clear_flag(Flags::V);
if did_overflow > 0
{
cpu.set_flag(Flags::V);
}
cpu.clear_flag(Flags::N);
if temp & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.a = (temp & 0x00FF) as u8;
}
pub fn STA(cpu: &mut R6502, bus: &mut dyn Bus)
{
bus.write(cpu.working_addr, cpu.a);
}
pub fn LDA(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn CMP(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
if cpu.a >= data
{
cpu.set_flag(Flags::C);
}
else
{
cpu.clear_flag(Flags::C);
}
if cpu.a == data
{
cpu.set_flag(Flags::Z);
}
else
{
cpu.clear_flag(Flags::Z);
}
if cpu.a < data
{
cpu.set_flag(Flags::N);
}
else
{
cpu.clear_flag(Flags::N);
}
}
// Using a technique written javidx9
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L714
//
// More info about the carry bit:
// http://forum.6502.org/viewtopic.php?t=18
pub fn SBC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let value = cpu.working_data ^ 0x00FF;
let carry = cpu.check_flag(Flags::C) as u16;
let temp: u16 = cpu.a as u16 + value + carry;
cpu.clear_flag(Flags::C);
if temp > 255
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::Z);
if temp == 0
{
cpu.set_flag(Flags::Z);
}
let did_overflow = (!((cpu.a as u16) ^ (value)) & ((cpu.a as u16) ^ temp)) & 0x0080;
cpu.clear_flag(Flags::V);
if did_overflow > 0
{
cpu.set_flag(Flags::V);
}
cpu.clear_flag(Flags::N);
if temp & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.a = (temp & 0x00FF) as u8;
}
///////////////////////////////////////////////////////////
// GROUP TWO
pub fn ASL(cpu: &mut R6502, bus: &mut dyn Bus)
{
cpu.clear_flag(Flags::C);
if cpu.working_data as u8 & 0x80 > 0
{
cpu.set_flag(Flags::C);
}
let result = cpu.working_data << 1;
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if result as u8 & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn ROL(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_7 = (cpu.working_data & 0x80) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let result = (cpu.working_data << 1) ^ carry;
cpu.clear_flag(Flags::C);
if old_bit_7
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn LSR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_0 = (cpu.working_data & 0x01) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let result = cpu.working_data >> 1;
cpu.clear_flag(Flags::C);
if old_bit_0
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn ROR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_0 = (cpu.working_data & 0x01) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let temp = carry << 7;
let result = (cpu.working_data >> 1) ^ (carry << 7);
cpu.clear_flag(Flags::C);
if old_bit_0
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn STX(cpu: &mut R6502, bus: &mut dyn Bus)
{
bus.write(cpu.working_addr, cpu.x);
}
pub fn LDX(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.x = data;
if cpu.x == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.x & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn DEC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let dec_val = bus.read(cpu.working_addr) - 1;
bus.write(cpu.working_addr, dec_val);
cpu.clear_flag(Flags::Z);
if dec_val == 0
{
cpu.set_flag(Flags::Z);
}
cpu.clear_flag(Flags::N);
if dec_val & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
}
pub fn INC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let dec_val = bus.read(cpu.working_addr) + 1;
bus.write(cpu.working_addr, dec_val);
cpu.clear_flag(Flags::Z);
if dec_val == 0
{
cpu.set_flag(Flags::Z);
}
cpu.clear_flag(Flags::N);
if dec_val & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
}
///////////////////////////////////////////////////////////
// GROUP THREE
#![allow(dead_code, non_snake_case)]
use super::{R6502, Bus, Flags, addressing_modes::{AddressingModes, ModeID}};
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
// GROUP ONE
// 000 ORA
// 001 AND
// 010 EOR
// 011 ADC
// 100 STA
// 101 LDA
// 110 CMP
// 111 SBC
pub struct Instructions;
impl Instructions
{
pub const GROUP_ONE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
Instructions::ORA,
Instructions::AND,
Instructions::EOR,
Instructions::ADC,
Instructions::STA,
Instructions::LDA,
Instructions::CMP,
Instructions::SBC,
];
pub const GROUP_TWO_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
Instructions::ASL, // 000
Instructions::ROL, // 001
Instructions::LSR, // 010
Instructions::ROR, // 011
Instructions::STX, // 100
Instructions::LDX, // 101
Instructions::DEC, // 110
Instructions::INC, // 111
];
pub const GROUP_THREE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
Instructions::ERR,
Instructions::BIT, // 001 BIT
Instructions::JMP, // 010 JMP
Instructions::JMP, // 011 JMP (abs)
Instructions::STY, // 100 STY
Instructions::LDY, // 101 LDY
Instructions::CPY, // 110 CPY
Instructions::CPX, // 111 CPX
];
}
impl Instructions
{
pub fn ERR(_cpu: &mut R6502, _bus: &mut dyn Bus)
{
// TODO: Better error handling
println!("ERROR: Invalid Instruction");
}
///////////////////////////////////////////////////////////
// GROUP ONE
pub fn ORA(cpu: &mut R6502, _bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a | data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn AND(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a & data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn EOR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = cpu.a ^ data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
// Using a technique written javidx9
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L659
pub fn ADC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let carry = cpu.check_flag(Flags::C) as u16;
// 16 bit addition to capture the carry easier
let temp: u16 = cpu.a as u16 + cpu.working_data + carry;
if temp > 255
{
cpu.set_flag(Flags::C);
}
if temp == 0
{
cpu.set_flag(Flags::Z);
}
let did_overflow = (!((cpu.a as u16) ^ (cpu.working_data as u16)) & ((cpu.a as u16) ^ temp)) & 0x0080;
cpu.clear_flag(Flags::V);
if did_overflow > 0
{
cpu.set_flag(Flags::V);
}
cpu.clear_flag(Flags::N);
if temp & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.a = (temp & 0x00FF) as u8;
}
pub fn STA(cpu: &mut R6502, bus: &mut dyn Bus)
{
bus.write(cpu.working_addr, cpu.a);
}
pub fn LDA(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.a = data;
if cpu.a == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.a & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn CMP(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
if cpu.a >= data
{
cpu.set_flag(Flags::C);
}
else
{
cpu.clear_flag(Flags::C);
}
if cpu.a == data
{
cpu.set_flag(Flags::Z);
}
else
{
cpu.clear_flag(Flags::Z);
}
if cpu.a < data
{
cpu.set_flag(Flags::N);
}
else
{
cpu.clear_flag(Flags::N);
}
}
// Using a technique written javidx9
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L714
//
// More info about the carry bit:
// http://forum.6502.org/viewtopic.php?t=18
pub fn SBC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let value = cpu.working_data ^ 0x00FF;
let carry = cpu.check_flag(Flags::C) as u16;
let temp: u16 = cpu.a as u16 + value + carry;
cpu.clear_flag(Flags::C);
if temp > 255
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::Z);
if temp == 0
{
cpu.set_flag(Flags::Z);
}
let did_overflow = (!((cpu.a as u16) ^ (value)) & ((cpu.a as u16) ^ temp)) & 0x0080;
cpu.clear_flag(Flags::V);
if did_overflow > 0
{
cpu.set_flag(Flags::V);
}
cpu.clear_flag(Flags::N);
if temp & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.a = (temp & 0x00FF) as u8;
}
///////////////////////////////////////////////////////////
// GROUP TWO
pub fn ASL(cpu: &mut R6502, bus: &mut dyn Bus)
{
cpu.clear_flag(Flags::C);
if cpu.working_data as u8 & 0x80 > 0
{
cpu.set_flag(Flags::C);
}
let result = cpu.working_data << 1;
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if result as u8 & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn ROL(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_7 = (cpu.working_data & 0x80) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let result = (cpu.working_data << 1) ^ carry;
cpu.clear_flag(Flags::C);
if old_bit_7
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn LSR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_0 = (cpu.working_data & 0x01) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let result = cpu.working_data >> 1;
cpu.clear_flag(Flags::C);
if old_bit_0
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn ROR(cpu: &mut R6502, bus: &mut dyn Bus)
{
let old_bit_0 = (cpu.working_data & 0x01) > 0;
let carry = cpu.check_flag(Flags::C) as u16;
let temp = carry << 7;
let result = (cpu.working_data >> 1) ^ (carry << 7);
cpu.clear_flag(Flags::C);
if old_bit_0
{
cpu.set_flag(Flags::C);
}
cpu.clear_flag(Flags::N);
if result & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
cpu.clear_flag(Flags::Z);
if result == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.addr_mode == ModeID::ACM
{
cpu.a = result as u8;
}
else
{
bus.write(cpu.working_addr, result as u8);
}
}
pub fn STX(cpu: &mut R6502, bus: &mut dyn Bus)
{
bus.write(cpu.working_addr, cpu.x);
}
pub fn LDX(cpu: &mut R6502, bus: &mut dyn Bus)
{
let data = cpu.working_data as u8;
cpu.x = data;
if cpu.x == 0
{
cpu.set_flag(Flags::Z);
}
if cpu.x & 0x80 != 0
{
cpu.set_flag(Flags::N);
}
}
pub fn DEC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let dec_val = bus.read(cpu.working_addr) - 1;
bus.write(cpu.working_addr, dec_val);
cpu.clear_flag(Flags::Z);
if dec_val == 0
{
cpu.set_flag(Flags::Z);
}
cpu.clear_flag(Flags::N);
if dec_val & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
}
pub fn INC(cpu: &mut R6502, bus: &mut dyn Bus)
{
let dec_val = bus.read(cpu.working_addr) + 1;
bus.write(cpu.working_addr, dec_val);
cpu.clear_flag(Flags::Z);
if dec_val == 0
{
cpu.set_flag(Flags::Z);
}
cpu.clear_flag(Flags::N);
if dec_val & 0x80 > 0
{
cpu.set_flag(Flags::N);
}
}
///////////////////////////////////////////////////////////
// GROUP THREE
pub fn BIT(cpu: &mut R6502, bus: &mut dyn Bus)
{
cpu.set_flag(Flags::Z);
if cpu.a & (cpu.working_data as u8) > 0
{
cpu.clear_flag(Flags::Z);
}
cpu.clear_flag(Flags::V);
if cpu.working_data & 0x0040 > 0
{
cpu.set_flag(Flags::V);
}
cpu.clear_flag(Flags::N);
if cpu.working_data & 0x0080 > 0
{
cpu.set_flag(Flags::N);
}
}
pub fn JMP(cpu: &mut R6502, bus: &mut dyn Bus)
{
cpu.pc = cpu.working_addr;
}
// JMP (abs)
// pub fn JPA(cpu: &mut R6502, bus: &mut dyn Bus)
// {
// cpu.pc = cpu.working_addr;
// }
pub fn STY(cpu: &mut R6502, bus: &mut dyn Bus)
{
}
pub fn LDY(cpu: &mut R6502, bus: &mut dyn Bus)
{
}
pub fn CPY(cpu: &mut R6502, bus: &mut dyn Bus)
{
}
pub fn CPX(cpu: &mut R6502, bus: &mut dyn Bus)
{
}
///////////////////////////////////////////////////////////
// BRANCHING
///////////////////////////////////////////////////////////
// INTERRUPT AND SUBROUTINE
}

@ -1,239 +1,258 @@
#![allow(unused_variables, dead_code, non_snake_case)]
mod addressing_modes;
mod instructions;
use addressing_modes::{AddressingModes, ModeID};
use instructions::Instructions;
pub trait Bus
{
fn read(&self, addr: u16) -> u8;
fn write(&mut self, addr: u16, value: u8);
}
// impl Sized for Bus
// {
// }
#[derive(Copy, Clone, Debug)]
pub enum Flags
{
C = (1 << 0), // Carry Flag
Z = (1 << 1), // Zero Flag
I = (1 << 2), // Interrupt Disable
D = (1 << 3), // Decimal Mode Flag
B = (1 << 4), // Break Command
U = (1 << 5), // Unused
V = (1 << 6), // Overflow Flag
N = (1 << 7), // Negative Flag
}
pub enum Registers
{
A,
X,
Y,
PC,
SP,
STATUS,
}
#[derive(Clone, Copy, PartialEq)]
pub struct R6502
{
a: u8, // Accumulator
x: u8, // X Register
y: u8, // Y Register
pc: u16, // Program Counter
sp: u8, // Stack Pointer
status: u8, // Status Flags
cycles: u32, // Track cycles
// Helper Vars
addr_mode: ModeID,
working_data: u16, // value fetched for the ALU
working_addr: u16,
}
impl R6502
{
// constructor
pub fn new() -> R6502
{
R6502 { a: 0, x: 0, y: 0, pc: 0, sp: 0, status: 0, cycles: 0, addr_mode: ModeID::IMP, working_data: 0, working_addr: 0 }
}
// Debug Access
pub fn debug_get_reg(&self, reg: Registers) -> u16
{
match reg
{
Registers::A => self.a as u16,
Registers::X => self.x as u16,
Registers::Y => self.y as u16,
Registers::PC => self.pc,
Registers::SP => self.sp as u16,
Registers::STATUS => self.status as u16,
}
}
pub fn debug_set_reg(&mut self, reg: Registers, value: u16)
{
match reg
{
Registers::A => self.a = value as u8,
Registers::X => self.x = value as u8,
Registers::Y => self.y = value as u8,
Registers::PC => self.pc = value,
Registers::SP => self.sp = value as u8,
Registers::STATUS => self.status = value as u8,
}
}
// signals
pub fn clock(&mut self, bus: &mut dyn Bus)
{
// TODO: Track instructions cycles
// if self.cycles == 0
//{
let opcode = bus.read(self.pc);
self.pc += 1;
execute(opcode, self, bus);
//self.pc += 1;
//}
self.cycles -= 1;
}
pub fn reset(&mut self, bus: &mut dyn Bus)
{
self.a = 0;
self.x = 0;
self.y = 0;
self.sp = 0xFF; // stack actually starts at 0x01FF but we only need the low byte since the end of the stack is at 0x0100
self.status = 0;
self.set_flag(Flags::U);
let lo: u16 = bus.read(0xFFFC) as u16;
let hi: u16 = bus.read(0xFFFD) as u16;
self.pc = (hi << 8) | lo;
// internal helper variables
self.working_data = 0;
// self.working_addr = 0;
self.cycles = 8;
}
pub fn irq(&mut self, bus: &mut impl Bus)
{
}
pub fn nmi(&mut self, bus: &mut impl Bus)
{
}
// helpers
pub fn set_flag(&mut self, bit: Flags)
{
self.status |= bit as u8;
}
pub fn clear_flag(&mut self, bit: Flags)
{
self.status &= !(bit as u8);
}
pub fn check_flag(&mut self, bit: Flags) -> u8
{
if self.status & (bit as u8) > 0
{
return 1;
}
return 0;
}
}
fn execute(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
let group_code = instruction & 0x03; // group one has a bit pattern of xxxxxx01
match group_code
{
0x01 => exe_group_one(instruction, cpu, bus),
0x02 => exe_group_two(instruction, cpu, bus),
// TODO: Conditionals and specially formatted instructions
_ => panic!("UNKNOWN INSTRUCTION ADDRESS MODE: {}", group_code)
}
}
fn exe_group_one(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
let addr_mask = (instruction & 0x1C) >> 2;
let op_mask = (instruction & 0xE0) >> 5;
cpu.addr_mode = AddressingModes::GROUP_ONE_ADDRS[addr_mask as usize](cpu, bus);
Instructions::GROUP_ONE_OPS[op_mask as usize](cpu, bus);
}
fn exe_group_two(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
let addr_mask = (instruction & 0x1C) >> 2;
let op_mask = (instruction & 0xE0) >> 5;
// With STX and LDX, "zero page,X" addressing becomes "zero page,Y", and with LDX, "absolute,X" becomes "absolute,Y".
const STX_ZPX: u8 = 0x96;
const LDX_ZPX: u8 = 0xB6;
const LDX_ABX: u8 = 0xBE;
match instruction
{
STX_ZPX =>
{
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
Instructions::STX(cpu, bus);
},
LDX_ZPX =>
{
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
Instructions::LDX(cpu, bus);
}
LDX_ABX =>
{
cpu.addr_mode = AddressingModes::ABY(cpu, bus);
Instructions::LDX(cpu, bus);
}
_ =>
{
cpu.addr_mode = AddressingModes::GROUP_TWO_ADDRS[addr_mask as usize](cpu, bus);
Instructions::GROUP_TWO_OPS[op_mask as usize](cpu, bus);
}
}
}
#![allow(unused_variables, dead_code, non_snake_case)]
mod addressing_modes;
mod instructions;
use addressing_modes::{AddressingModes, ModeID};
use instructions::Instructions;
pub trait Bus
{
fn read(&self, addr: u16) -> u8;
fn write(&mut self, addr: u16, value: u8);
}
// impl Sized for Bus
// {
// }
#[derive(Copy, Clone, Debug)]
pub enum Flags
{
C = (1 << 0), // Carry Flag
Z = (1 << 1), // Zero Flag
I = (1 << 2), // Interrupt Disable
D = (1 << 3), // Decimal Mode Flag
B = (1 << 4), // Break Command
U = (1 << 5), // Unused
V = (1 << 6), // Overflow Flag
N = (1 << 7), // Negative Flag
}
pub enum Registers
{
A,
X,
Y,
PC,
SP,
STATUS,
}
#[derive(Clone, Copy, PartialEq)]
pub struct R6502
{
a: u8, // Accumulator
x: u8, // X Register
y: u8, // Y Register
pc: u16, // Program Counter
sp: u8, // Stack Pointer
status: u8, // Status Flags
cycles: u32, // Track cycles
// Helper Vars
addr_mode: ModeID,
working_data: u16, // value fetched for the ALU
working_addr: u16,
}
impl R6502
{
// constructor
pub fn new() -> R6502
{
R6502 { a: 0, x: 0, y: 0, pc: 0, sp: 0, status: 0, cycles: 0, addr_mode: ModeID::IMP, working_data: 0, working_addr: 0 }
}
// Debug Access
pub fn debug_get_reg(&self, reg: Registers) -> u16
{
match reg
{
Registers::A => self.a as u16,
Registers::X => self.x as u16,
Registers::Y => self.y as u16,
Registers::PC => self.pc,
Registers::SP => self.sp as u16,
Registers::STATUS => self.status as u16,
}
}
pub fn debug_set_reg(&mut self, reg: Registers, value: u16)
{
match reg
{
Registers::A => self.a = value as u8,
Registers::X => self.x = value as u8,
Registers::Y => self.y = value as u8,
Registers::PC => self.pc = value,
Registers::SP => self.sp = value as u8,
Registers::STATUS => self.status = value as u8,
}
}
// signals
pub fn clock(&mut self, bus: &mut dyn Bus)
{
// TODO: Track instructions cycles
// if self.cycles == 0
//{
let opcode = bus.read(self.pc);
self.pc += 1;
execute(opcode, self, bus);
//self.pc += 1;
//}
self.cycles -= 1;
}
pub fn reset(&mut self, bus: &mut dyn Bus)
{
self.a = 0;
self.x = 0;
self.y = 0;
self.sp = 0xFF; // stack actually starts at 0x01FF but we only need the low byte since the end of the stack is at 0x0100
self.status = 0;
self.set_flag(Flags::U);
let lo: u16 = bus.read(0xFFFC) as u16;
let hi: u16 = bus.read(0xFFFD) as u16;
self.pc = (hi << 8) | lo;
// internal helper variables
self.working_data = 0;
// self.working_addr = 0;
self.cycles = 8;
}
pub fn irq(&mut self, bus: &mut impl Bus)
{
}
pub fn nmi(&mut self, bus: &mut impl Bus)
{
}
// helpers
pub fn set_flag(&mut self, bit: Flags)
{
self.status |= bit as u8;
}
pub fn clear_flag(&mut self, bit: Flags)
{
self.status &= !(bit as u8);
}
pub fn check_flag(&mut self, bit: Flags) -> u8
{
if self.status & (bit as u8) > 0
{
return 1;
}
return 0;
}
}
fn execute(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
// Instruction decoding:
// https://llx.com/Neil/a2/opcodes.html
let group_code = instruction & 0x03; // group one has a bit pattern of xxxxxx01
match group_code
{
0x01 => exe_group_one(instruction, cpu, bus),
0x02 => exe_group_two(instruction, cpu, bus),
0x00 => exe_group_three(instruction, cpu, bus),
// TODO: Conditionals and specially formatted instructions
_ => panic!("UNKNOWN INSTRUCTION ADDRESS MODE: {}", group_code)
}
}
fn exe_group_one(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
let addr_mask = (instruction & 0x1C) >> 2;
let op_mask = (instruction & 0xE0) >> 5;
cpu.addr_mode = AddressingModes::GROUP_ONE_ADDRS[addr_mask as usize](cpu, bus);
Instructions::GROUP_ONE_OPS[op_mask as usize](cpu, bus);
}
fn exe_group_two(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
let addr_mask = (instruction & 0x1C) >> 2;
let op_mask = (instruction & 0xE0) >> 5;
// With STX and LDX, "zero page,X" addressing becomes "zero page,Y", and with LDX, "absolute,X" becomes "absolute,Y".
const STX_ZPX: u8 = 0x96;
const LDX_ZPX: u8 = 0xB6;
const LDX_ABX: u8 = 0xBE;
match instruction
{
STX_ZPX =>
{
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
Instructions::STX(cpu, bus);
},
LDX_ZPX =>
{
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
Instructions::LDX(cpu, bus);
}
LDX_ABX =>
{
cpu.addr_mode = AddressingModes::ABY(cpu, bus);
Instructions::LDX(cpu, bus);
}
_ =>
{
cpu.addr_mode = AddressingModes::GROUP_TWO_ADDRS[addr_mask as usize](cpu, bus);
Instructions::GROUP_TWO_OPS[op_mask as usize](cpu, bus);
}
}
}
fn exe_group_three(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
{
let addr_mask = (instruction & 0x1C) >> 2;
let op_mask = (instruction & 0xE0) >> 5;
// SPECIAL CASE FOR JMP (abs)
const JMP_IND: u8 = 0x6C;
if instruction == JMP_IND
{
cpu.addr_mode = AddressingModes::IND(cpu, bus);
}
else
{
cpu.addr_mode = AddressingModes::GROUP_THREE_ADDRS[addr_mask as usize](cpu, bus);
}
Instructions::GROUP_THREE_OPS[op_mask as usize](cpu, bus);
}

@ -1,300 +1,300 @@
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers};
#[test]
fn IMP()
{
}
#[test]
fn ACM()
{
}
#[test]
fn IMM()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA9); // LDA - Immediate mode
bus.write(addr + 1, 0x08); // Argument
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZP0()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x000A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA5); // LDA - Zero Page mode
bus.write(addr + 1, 0x0A); // Argument
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZPX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x000A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB5); // LDA - Zero Page, X mode
bus.write(addr + 1, 0x04); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x06);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZPY()
{
}
#[test]
fn REL()
{
}
#[test]
fn ABS()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xAD); // LDA - Absolute mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ABX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010B, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xBD); // LDA - Absolute, X mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ABY()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010B, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB9); // LDA - Absolute, X mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::Y, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn IND()
{
}
#[test]
fn IZX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory
bus.write(0x010B, 0x08);
// Manuall put 0x010B into the Zero page at 0x000A
bus.write(0x000A, 0x0B); // Pointer lo byte
bus.write(0x000B, 0x01); // Pointer hi byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA1); // LDA - Indirect, X mode
bus.write(addr + 1, 0x09); // Argument - gets added to X reg
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn IZY()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory
bus.write(0x020B, 0x08);
// Manuall put 0x01FC into the Zero page at 0x000A
// This will be added to the Y register (which will store 0x0F)
bus.write(0x000A, 0xFC); // Pointer lo byte
bus.write(0x000B, 0x01); // Pointer hi byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB1); // LDA - Indirect, Y mode
bus.write(addr + 1, 0x0A); // Argument - Pointer into the Zero Page
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::Y, 0x0F); // Offset of the value at the zero page address
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers};
#[test]
fn IMP()
{
}
#[test]
fn ACM()
{
}
#[test]
fn IMM()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA9); // LDA - Immediate mode
bus.write(addr + 1, 0x08); // Argument
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZP0()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x000A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA5); // LDA - Zero Page mode
bus.write(addr + 1, 0x0A); // Argument
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZPX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x000A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB5); // LDA - Zero Page, X mode
bus.write(addr + 1, 0x04); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x06);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ZPY()
{
}
#[test]
fn REL()
{
}
#[test]
fn ABS()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010A, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xAD); // LDA - Absolute mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ABX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010B, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xBD); // LDA - Absolute, X mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn ABY()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory in the zero page
bus.write(0x010B, 0x08);
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB9); // LDA - Absolute, X mode
bus.write(addr + 1, 0x0A); // Argument lo word
bus.write(addr + 2, 0x01); // Argument hi word
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::Y, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn IND()
{
}
#[test]
fn IZX()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory
bus.write(0x010B, 0x08);
// Manuall put 0x010B into the Zero page at 0x000A
bus.write(0x000A, 0x0B); // Pointer lo byte
bus.write(0x000B, 0x01); // Pointer hi byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xA1); // LDA - Indirect, X mode
bus.write(addr + 1, 0x09); // Argument - gets added to X reg
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::X, 0x01);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}
#[test]
fn IZY()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Manually put 0x08 into memory
bus.write(0x020B, 0x08);
// Manuall put 0x01FC into the Zero page at 0x000A
// This will be added to the Y register (which will store 0x0F)
bus.write(0x000A, 0xFC); // Pointer lo byte
bus.write(0x000B, 0x01); // Pointer hi byte
// Program to load 0x08 into the accumulator
bus.write(addr, 0xB1); // LDA - Indirect, Y mode
bus.write(addr + 1, 0x0A); // Argument - Pointer into the Zero Page
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::Y, 0x0F); // Offset of the value at the zero page address
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x08 in the A register?
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
}

@ -1,109 +1,109 @@
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn basic()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Write the program to memory
// ADC 6
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x04);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x0A in the A register?
assert_eq!(0x0A, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
}
#[test]
fn with_carry()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Add with carry in
// Write the program to memory
// ADC 6
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu internals
cpu.debug_set_reg(Registers::A, 0x04);
cpu.set_flag(Flags::C);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x0B in the A register?
assert_eq!(0x0B, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_overflow()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Add with overflow
// Write the program to memory
// ADC 126
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 126); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu internals
cpu.debug_set_reg(Registers::A, 0x04);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 130 in the A register?
assert_eq!(130, cpu.debug_get_reg(Registers::A));
// Is the overflow bit set?
assert_eq!(1, cpu.check_flag(Flags::V), "Failed addition with overflow");
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn basic()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Write the program to memory
// ADC 6
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x04);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x0A in the A register?
assert_eq!(0x0A, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
}
#[test]
fn with_carry()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Add with carry in
// Write the program to memory
// ADC 6
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu internals
cpu.debug_set_reg(Registers::A, 0x04);
cpu.set_flag(Flags::C);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x0B in the A register?
assert_eq!(0x0B, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_overflow()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Add with overflow
// Write the program to memory
// ADC 126
bus.write(addr, 0x69); // ADC - Immediate mode
bus.write(addr + 1, 126); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu internals
cpu.debug_set_reg(Registers::A, 0x04);
// Clock the cpu twice (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 130 in the A register?
assert_eq!(130, cpu.debug_get_reg(Registers::A));
// Is the overflow bit set?
assert_eq!(1, cpu.check_flag(Flags::V), "Failed addition with overflow");
}

@ -1,110 +1,110 @@
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn less_than()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is less than A reg
// Program to compare 0x10 with 0x05 (0x05 will be the argument)
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x05); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x10);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Flag should be 1, Z N Flags should be 0
assert_eq!(1, cpu.check_flag(Flags::C));
assert_eq!(0, cpu.check_flag(Flags::Z));
assert_eq!(0, cpu.check_flag(Flags::N));
}
#[test]
fn equal_to()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is equal to the A reg
// Program to compare 0x10 with 0x10
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x10); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x10);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Z Flags should be 1, N Flag should be 0
assert_eq!(1, cpu.check_flag(Flags::C));
assert_eq!(1, cpu.check_flag(Flags::Z));
assert_eq!(0, cpu.check_flag(Flags::N));
}
#[test]
fn greater_than()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is greater than A reg
// Program to compare 0x05 with 0x10
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x10); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x05);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Z Flags should be 0, N Flag should be 1
assert_eq!(0, cpu.check_flag(Flags::C));
assert_eq!(0, cpu.check_flag(Flags::Z));
assert_eq!(1, cpu.check_flag(Flags::N));
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn less_than()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is less than A reg
// Program to compare 0x10 with 0x05 (0x05 will be the argument)
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x05); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x10);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Flag should be 1, Z N Flags should be 0
assert_eq!(1, cpu.check_flag(Flags::C));
assert_eq!(0, cpu.check_flag(Flags::Z));
assert_eq!(0, cpu.check_flag(Flags::N));
}
#[test]
fn equal_to()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is equal to the A reg
// Program to compare 0x10 with 0x10
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x10); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x10);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Z Flags should be 1, N Flag should be 0
assert_eq!(1, cpu.check_flag(Flags::C));
assert_eq!(1, cpu.check_flag(Flags::Z));
assert_eq!(0, cpu.check_flag(Flags::N));
}
#[test]
fn greater_than()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
///////////////////////
// Parameter is greater than A reg
// Program to compare 0x05 with 0x10
bus.write(addr, 0xC9); // CMP - Immediate mode
bus.write(addr + 1, 0x10); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu registers
cpu.debug_set_reg(Registers::A, 0x05);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// C Z Flags should be 0, N Flag should be 1
assert_eq!(0, cpu.check_flag(Flags::C));
assert_eq!(0, cpu.check_flag(Flags::Z));
assert_eq!(1, cpu.check_flag(Flags::N));
}

@ -1,105 +1,105 @@
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn basic()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x06 from 0x0A
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0x0A);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x04 in the A register?
assert_eq!(0x04, cpu.debug_get_reg(Registers::A), "wrong answer");
assert_eq!(1, cpu.check_flag(Flags::C), "Carry bit should be set");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_carry()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x09 from 0x08
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x09); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0x08);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is -1 in the A register?
assert_eq!(0xFF as u16, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_overflow()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x7E from 0xFB (-5 - 126)
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x7E); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0xFB);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
assert_eq!(0x7D, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(1, cpu.check_flag(Flags::C), "Carry bit should be set");
assert_eq!(1, cpu.check_flag(Flags::V), "Overflow bit should be set");
}
#![allow(dead_code, non_snake_case)]
use crate::tests::test_bus::RAMBus;
use crate::r6502::{R6502, Bus, Registers, Flags};
#[test]
fn basic()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x06 from 0x0A
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x06); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0x0A);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is 0x04 in the A register?
assert_eq!(0x04, cpu.debug_get_reg(Registers::A), "wrong answer");
assert_eq!(1, cpu.check_flag(Flags::C), "Carry bit should be set");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_carry()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x09 from 0x08
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x09); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0x08);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is -1 in the A register?
assert_eq!(0xFF as u16, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
}
#[test]
fn with_overflow()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Program to subtract 0x7E from 0xFB (-5 - 126)
bus.write(addr, 0xE9); // SBC - Immediate mode
bus.write(addr + 1, 0x7E); // Argument
// Restart cpu
cpu.reset(&mut bus);
// manually setup the cpu state
cpu.set_flag(Flags::C);
cpu.debug_set_reg(Registers::A, 0xFB);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
assert_eq!(0x7D, cpu.debug_get_reg(Registers::A), "Wrong answer");
assert_eq!(1, cpu.check_flag(Flags::C), "Carry bit should be set");
assert_eq!(1, cpu.check_flag(Flags::V), "Overflow bit should be set");
}

@ -457,4 +457,81 @@ fn INC()
// Is 0x11 in memory at 0x08?
assert_eq!(0x11, bus.read(0x08));
}
/////////////////////////////////////////////////////////////////////
// GROUP THREE
/////////////////////////////////////////////////////////////////////
#[test]
fn BIT()
{
// TODO: Could add more tests for this instruction
// Maybe move into it's own file and test
// every result pattern.
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Put value to test into memory
bus.write(0x08, 0x10);
// BIT test program
bus.write(addr, 0x24); // BIT - Zero Page
bus.write(addr + 1, 0x08); // Argument
// Restart cpu
cpu.reset(&mut bus);
// Preload A register with bit mask
cpu.debug_set_reg(Registers::A, 0x05);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is the Z flag set?
assert_eq!(1, cpu.check_flag(Flags::Z));
// Is the N flag and V flag not set?
assert_eq!(0, cpu.check_flag(Flags::V));
assert_eq!(0, cpu.check_flag(Flags::N));
}
#[test]
fn JMP()
{
let mut cpu = R6502::new();
let mut bus = RAMBus::new();
// program address
let addr = 0x0020 as u16;
// Set the program counter address
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
// Put jump location into memory
bus.write(0x08, 0x34);
bus.write(0x09, 0x12);
// JMP test program
bus.write(addr, 0x6C); // JMP - IND
bus.write(addr + 1, 0x08); // LO Argument
bus.write(addr + 2, 0x00); // HI Argument
// Restart cpu
cpu.reset(&mut bus);
// Clock the cpu to run the program (Clock essentially runs one full instruction)
cpu.clock(&mut bus);
// Is the program counter now 0x1234?
assert_eq!(0x1234, cpu.debug_get_reg(Registers::PC));
}

@ -1,28 +1,28 @@
use crate::r6502::Bus;
// All-RAM bus for testing
pub struct RAMBus
{
ram: [u8; 64 * 1024]
}
impl RAMBus
{
pub fn new() -> RAMBus
{
RAMBus { ram: [0; 64 * 1024] }
}
}
impl Bus for RAMBus
{
fn read(&self, addr: u16) -> u8
{
self.ram[addr as usize]
}
fn write(&mut self, addr: u16, value: u8)
{
self.ram[addr as usize] = value;
}
use crate::r6502::Bus;
// All-RAM bus for testing
pub struct RAMBus
{
ram: [u8; 64 * 1024]
}
impl RAMBus
{
pub fn new() -> RAMBus
{
RAMBus { ram: [0; 64 * 1024] }
}
}
impl Bus for RAMBus
{
fn read(&self, addr: u16) -> u8
{
self.ram[addr as usize]
}
fn write(&mut self, addr: u16, value: u8)
{
self.ram[addr as usize] = value;
}
}
Loading…
Cancel
Save