Adds BIT and JMP instructions
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");
|
||||
}
|
||||
|
||||
@ -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…
Reference in New Issue