Adds BIT and JMP instructions
parent
400e378492
commit
d65c7cfd88
@ -1,45 +1,45 @@
|
|||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
// Use IntelliSense to learn about possible attributes.
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug executable 're6502'",
|
"name": "Debug executable 're6502'",
|
||||||
"cargo": {
|
"cargo": {
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--bin=re6502",
|
"--bin=re6502",
|
||||||
"--package=re6502"
|
"--package=re6502"
|
||||||
],
|
],
|
||||||
"filter": {
|
"filter": {
|
||||||
"name": "re6502",
|
"name": "re6502",
|
||||||
"kind": "bin"
|
"kind": "bin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug unit tests in executable 're6502'",
|
"name": "Debug unit tests in executable 're6502'",
|
||||||
"cargo": {
|
"cargo": {
|
||||||
"args": [
|
"args": [
|
||||||
"test",
|
"test",
|
||||||
"--no-run",
|
"--no-run",
|
||||||
"--bin=re6502",
|
"--bin=re6502",
|
||||||
"--package=re6502"
|
"--package=re6502"
|
||||||
],
|
],
|
||||||
"filter": {
|
"filter": {
|
||||||
"name": "re6502",
|
"name": "re6502",
|
||||||
"kind": "bin"
|
"kind": "bin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,37 +1,37 @@
|
|||||||
|
|
||||||
|
|
||||||
The code in the methods r6502::instructions::ADC and r6502::instructions::SDC
|
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:
|
is adapted from code written by javidx9 (David Barr) and is subject to the following license:
|
||||||
|
|
||||||
License (OLC-3)
|
License (OLC-3)
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Copyright 2018-2019 OneLoneCoder.com
|
Copyright 2018-2019 OneLoneCoder.com
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions
|
modification, are permitted provided that the following conditions
|
||||||
are met:
|
are met:
|
||||||
|
|
||||||
1. Redistributions or derivations of source code must retain the above
|
1. Redistributions or derivations of source code must retain the above
|
||||||
copyright notice, this list of conditions and the following disclaimer.
|
copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
2. Redistributions or derivative works in binary form must reproduce
|
2. Redistributions or derivative works in binary form must reproduce
|
||||||
the above copyright notice. This list of conditions and the following
|
the above copyright notice. This list of conditions and the following
|
||||||
disclaimer must be reproduced in the documentation and/or other
|
disclaimer must be reproduced in the documentation and/or other
|
||||||
materials provided with the distribution.
|
materials provided with the distribution.
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
contributors may be used to endorse or promote products derived
|
contributors may be used to endorse or promote products derived
|
||||||
from this software without specific prior written permission.
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@ -1,259 +1,310 @@
|
|||||||
|
|
||||||
#![allow(unused_variables, dead_code, non_snake_case)]
|
#![allow(unused_variables, dead_code, non_snake_case)]
|
||||||
|
|
||||||
use super::{R6502, Bus};
|
use super::{R6502, Bus};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum ModeID
|
pub enum ModeID
|
||||||
{
|
{
|
||||||
IMP, // Implied
|
IMP, // Implied
|
||||||
ACM, // Accumulator
|
ACM, // Accumulator
|
||||||
IMM, // Immediate
|
IMM, // Immediate
|
||||||
ZP0, // Zero Page
|
ZP0, // Zero Page
|
||||||
ZPX, // Zero Page, X
|
ZPX, // Zero Page, X
|
||||||
ZPY, // Zero Page, Y
|
ZPY, // Zero Page, Y
|
||||||
REL, // Relative
|
REL, // Relative
|
||||||
ABS, // Absolute
|
ABS, // Absolute
|
||||||
ABX, // Absolute, X
|
ABX, // Absolute, X
|
||||||
ABY, // Aboslute, Y
|
ABY, // Aboslute, Y
|
||||||
IND, // Indirect
|
IND, // Indirect
|
||||||
IZX, // Indirect, X
|
IZX, // Indirect, X
|
||||||
IZY, // Indirect, Y
|
IZY, // Indirect, Y
|
||||||
ERR, // Error mode - this is an invalid mode
|
ERR, // Error mode - this is an invalid mode
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instruction decoding:
|
// Instruction decoding:
|
||||||
// https://llx.com/Neil/a2/opcodes.html
|
// https://llx.com/Neil/a2/opcodes.html
|
||||||
|
|
||||||
// GROUP ONE ADDRESS MODES
|
// GROUP ONE ADDRESS MODES
|
||||||
// 000 (zero page,X) IZX
|
// 000 (zero page,X) IZX
|
||||||
// 001 zero page ZP0
|
// 001 zero page ZP0
|
||||||
// 010 #immediate IMM
|
// 010 #immediate IMM
|
||||||
// 011 absolute ABS
|
// 011 absolute ABS
|
||||||
// 100 (zero page),Y IZY
|
// 100 (zero page),Y IZY
|
||||||
// 101 zero page,X ZPX
|
// 101 zero page,X ZPX
|
||||||
// 110 absolute,Y ABY
|
// 110 absolute,Y ABY
|
||||||
// 111 absolute,X ABX
|
// 111 absolute,X ABX
|
||||||
|
|
||||||
// GROUP TWO ADDRESS MODES
|
// GROUP TWO ADDRESS MODES
|
||||||
// 000 #immediate IMM
|
// 000 #immediate IMM
|
||||||
// 001 zero page ZP0
|
// 001 zero page ZP0
|
||||||
// 010 accumulator ACM
|
// 010 accumulator ACM
|
||||||
// 011 absolute ABS
|
// 011 absolute ABS
|
||||||
// 100 NONE ERR
|
// 100 NONE ERR
|
||||||
// 101 zero page,X ZPX
|
// 101 zero page,X ZPX
|
||||||
// 110 NONE ERR
|
// 110 NONE ERR
|
||||||
// 111 absolute,X ABX
|
// 111 absolute,X ABX
|
||||||
|
|
||||||
pub struct AddressingModes;
|
// GROUP THREE ADDRES MODES
|
||||||
impl AddressingModes
|
// 000 #immediate IMM
|
||||||
{
|
// 001 zero page ZP0
|
||||||
pub const GROUP_ONE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
|
// 011 absolute ABS
|
||||||
AddressingModes::IZX,
|
// 101 zero page,X ZPX
|
||||||
AddressingModes::ZP0,
|
// 111 absolute,X ABX
|
||||||
AddressingModes::IMM,
|
|
||||||
AddressingModes::ABS,
|
pub struct AddressingModes;
|
||||||
AddressingModes::IZY,
|
impl AddressingModes
|
||||||
AddressingModes::ZPX,
|
{
|
||||||
AddressingModes::ABY,
|
pub const GROUP_ONE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
|
||||||
AddressingModes::ABX,
|
AddressingModes::IZX,
|
||||||
];
|
AddressingModes::ZP0,
|
||||||
|
AddressingModes::IMM,
|
||||||
pub const GROUP_TWO_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
|
AddressingModes::ABS,
|
||||||
AddressingModes::IMM,
|
AddressingModes::IZY,
|
||||||
AddressingModes::ZP0,
|
AddressingModes::ZPX,
|
||||||
AddressingModes::ACM,
|
AddressingModes::ABY,
|
||||||
AddressingModes::ABS,
|
AddressingModes::ABX,
|
||||||
AddressingModes::ERR,
|
];
|
||||||
AddressingModes::ZPX,
|
|
||||||
AddressingModes::ERR,
|
pub const GROUP_TWO_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
|
||||||
AddressingModes::ABX,
|
AddressingModes::IMM,
|
||||||
];
|
AddressingModes::ZP0,
|
||||||
}
|
AddressingModes::ACM,
|
||||||
|
AddressingModes::ABS,
|
||||||
impl AddressingModes
|
AddressingModes::ERR,
|
||||||
{
|
AddressingModes::ZPX,
|
||||||
|
AddressingModes::ERR,
|
||||||
pub fn ERR(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
AddressingModes::ABX,
|
||||||
{
|
];
|
||||||
ModeID::ERR
|
|
||||||
}
|
pub const GROUP_THREE_ADDRS: [fn(&mut R6502, &mut dyn Bus) -> ModeID; 8] = [
|
||||||
|
AddressingModes::IMM,
|
||||||
pub fn IMP(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
AddressingModes::ZP0,
|
||||||
{
|
AddressingModes::ERR,
|
||||||
ModeID::IMP
|
AddressingModes::ABS,
|
||||||
}
|
AddressingModes::ERR,
|
||||||
|
AddressingModes::ZPX,
|
||||||
pub fn ACM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
AddressingModes::ERR,
|
||||||
{
|
AddressingModes::ABX,
|
||||||
cpu.working_data = cpu.a as u16;
|
];
|
||||||
ModeID::ACM
|
}
|
||||||
}
|
|
||||||
|
impl AddressingModes
|
||||||
pub fn IMM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
{
|
||||||
{
|
|
||||||
cpu.working_data = bus.read(cpu.pc) as u16;
|
pub fn ERR(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
cpu.pc += 1;
|
{
|
||||||
|
// TODO: Better error handling
|
||||||
ModeID::IMM
|
ModeID::ERR
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ZP0(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
pub fn IMP(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
{
|
{
|
||||||
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
|
ModeID::IMP
|
||||||
cpu.pc += 1;
|
}
|
||||||
|
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
pub fn ACM(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
ModeID::ZP0
|
cpu.working_data = cpu.a as u16;
|
||||||
}
|
ModeID::ACM
|
||||||
|
}
|
||||||
pub fn ZPX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
|
||||||
{
|
pub fn IMM(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.working_data = bus.read(cpu.pc) as u16;
|
||||||
cpu.pc += 1;
|
cpu.pc += 1;
|
||||||
|
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
ModeID::IMM
|
||||||
|
}
|
||||||
ModeID::ZPX
|
|
||||||
}
|
pub fn ZP0(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
pub fn ZPY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
|
||||||
{
|
cpu.pc += 1;
|
||||||
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
|
|
||||||
cpu.working_addr += cpu.y as u16;
|
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
||||||
cpu.pc += 1;
|
|
||||||
|
ModeID::ZP0
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
}
|
||||||
|
|
||||||
ModeID::ZPY
|
pub fn ZPX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
}
|
{
|
||||||
|
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
|
||||||
pub fn REL(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
cpu.working_addr += cpu.x as u16;
|
||||||
{
|
cpu.pc += 1;
|
||||||
// NOTE: Not sure if we can use the working_data variable for this.
|
|
||||||
// if any instruction using this address mode needs extra data read
|
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
||||||
// then we need another variable to store this address
|
|
||||||
//
|
ModeID::ZPX
|
||||||
// Use working_addr to just like the other modes
|
}
|
||||||
cpu.working_data = bus.read(cpu.pc) as u16;
|
|
||||||
cpu.pc += 1;
|
pub fn ZPY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
ModeID::REL
|
cpu.working_addr = bus.read(cpu.pc) as u16 & 0x00FF;
|
||||||
}
|
cpu.working_addr += cpu.y as u16;
|
||||||
|
cpu.pc += 1;
|
||||||
pub fn ABS(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
|
||||||
{
|
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
||||||
cpu.working_addr = bus.read(cpu.pc) as u16;
|
|
||||||
cpu.pc += 1;
|
ModeID::ZPY
|
||||||
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
}
|
||||||
cpu.pc += 1;
|
|
||||||
|
pub fn REL(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
{
|
||||||
|
// NOTE: Not sure if we can use the working_data variable for this.
|
||||||
ModeID::ABS
|
// if any instruction using this address mode needs extra data read
|
||||||
}
|
// then we need another variable to store this address
|
||||||
|
//
|
||||||
pub fn ABX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
// Use working_addr to just like the other modes
|
||||||
{
|
cpu.working_data = bus.read(cpu.pc) as u16;
|
||||||
cpu.working_addr = bus.read(cpu.pc) as u16;
|
cpu.pc += 1;
|
||||||
cpu.pc += 1;
|
|
||||||
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
ModeID::REL
|
||||||
cpu.pc += 1;
|
}
|
||||||
|
|
||||||
cpu.working_addr += cpu.x as u16;
|
pub fn ABS(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
cpu.working_addr = bus.read(cpu.pc) as u16;
|
||||||
|
cpu.pc += 1;
|
||||||
ModeID::ABX
|
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
||||||
}
|
cpu.pc += 1;
|
||||||
|
|
||||||
pub fn ABY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
||||||
{
|
|
||||||
cpu.working_addr = bus.read(cpu.pc) as u16;
|
ModeID::ABS
|
||||||
cpu.pc += 1;
|
}
|
||||||
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
|
||||||
cpu.pc += 1;
|
pub fn ABX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
cpu.working_addr += cpu.y as u16;
|
cpu.working_addr = bus.read(cpu.pc) as u16;
|
||||||
|
cpu.pc += 1;
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
||||||
|
cpu.pc += 1;
|
||||||
ModeID::ABY
|
|
||||||
}
|
cpu.working_addr += cpu.x as u16;
|
||||||
|
|
||||||
pub fn IND(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
||||||
{
|
|
||||||
|
ModeID::ABX
|
||||||
ModeID::IND
|
}
|
||||||
}
|
|
||||||
|
pub fn ABY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
// Indexed Indirect Addressing (IND, X)
|
cpu.working_addr = bus.read(cpu.pc) as u16;
|
||||||
// In indexed indirect addressing (referred to as (Indirect, X)), the second byte of the
|
cpu.pc += 1;
|
||||||
// instruction is added to the contents of the X register, discarding the carry.
|
cpu.working_addr |= (bus.read(cpu.pc) as u16) << 8;
|
||||||
// The result of the addition points to a memory location on the Zero Page which contains
|
cpu.pc += 1;
|
||||||
// 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
|
cpu.working_addr += cpu.y as u16;
|
||||||
// the effective address must be in the Zero Page.
|
|
||||||
//
|
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
||||||
// Info from:
|
|
||||||
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
|
ModeID::ABY
|
||||||
pub fn IZX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
}
|
||||||
{
|
|
||||||
let offset = bus.read(cpu.pc) as u16;
|
// https://www.nesdev.org/obelisk-6502-guide/addressing.html#IND
|
||||||
cpu.pc += 1;
|
// JMP is the only 6502 instruction to support indirection.
|
||||||
let mut pointer = cpu.x as u16 + offset;
|
// 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
|
||||||
// discard the carry and wrap
|
// target of the instruction.
|
||||||
// If the addition goes beyond the Zero Page
|
|
||||||
// it should wrap around back to the beginning
|
// For example if location $0120 contains $FC and location $0121 contains $BA then
|
||||||
pointer = pointer & 0x00FF;
|
// the instruction JMP ($0120) will cause the next instruction execution to occur at
|
||||||
|
// $BAFC (e.g. the contents of $0120 and $0121).
|
||||||
let lo_byte = bus.read(pointer) as u16;
|
|
||||||
let hi_byte = bus.read(pointer + 1) as u16;
|
pub fn IND(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
cpu.working_addr = (hi_byte << 0x08) | lo_byte;
|
{
|
||||||
|
// https://www.nesdev.org/obelisk-6502-guide/reference.html#JMP
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16 & 0x00FF;
|
// 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).
|
||||||
ModeID::IZX
|
// 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;
|
||||||
// Indirect Indexed Addressing (IND), Y
|
let ptr_hi = bus.read(cpu.pc) as u16;
|
||||||
// In indirect indexed addressing, the second byte of the instruction points to
|
cpu.pc += 1;
|
||||||
// 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.
|
let ptr = (ptr_hi << 8) | ptr_lo;
|
||||||
// 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.
|
let addr_lo = bus.read(ptr) as u16;
|
||||||
//
|
let mut addr_hi = bus.read(ptr + 1) as u16;
|
||||||
// Info from:
|
|
||||||
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
|
// Emulate the bug
|
||||||
pub fn IZY(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
if ptr_lo == 0xFF
|
||||||
{
|
{
|
||||||
// zp_pointer points to a location in zero page
|
addr_hi = bus.read(ptr & 0xFF00) as u16;
|
||||||
let zp_pointer = bus.read(cpu.pc) as u16;
|
}
|
||||||
cpu.pc += 1;
|
|
||||||
|
cpu.working_addr = (addr_hi << 8) | addr_lo;
|
||||||
// The value at zp_pointer is added to the Y register
|
|
||||||
let zp_value = bus.read(zp_pointer) as u16;
|
ModeID::IND
|
||||||
let sum = zp_value + cpu.y as u16;
|
}
|
||||||
|
|
||||||
// The sum with the carry discarded is the lo byte
|
|
||||||
let lo_byte = sum & 0x00FF;
|
// Indexed Indirect Addressing (IND, X)
|
||||||
|
// In indexed indirect addressing (referred to as (Indirect, X)), the second byte of the
|
||||||
// The carry plus the value at the next zero page address is the hi byte
|
// instruction is added to the contents of the X register, discarding the carry.
|
||||||
let zp_next = bus.read(zp_pointer + 1) as u16;
|
// The result of the addition points to a memory location on the Zero Page which contains
|
||||||
let temp = (sum & 0xFF00) >> 0x08;
|
// the low order byte of the effective address. The next memory location in page zero,
|
||||||
let temp2 = temp + zp_next;
|
// contains the high order byte of the effective address. Both memory locations specifying
|
||||||
let hi_byte: u8 = (((sum & 0xFF00) >> 0x08) + zp_next) as u8;
|
// the effective address must be in the Zero Page.
|
||||||
|
//
|
||||||
// Store the final address and read the data
|
// Info from:
|
||||||
cpu.working_addr = ((hi_byte as u16) << 0x08) | lo_byte;
|
// https://web.archive.org/web/20221112231348if_/http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
|
||||||
cpu.working_data = bus.read(cpu.working_addr) as u16;
|
pub fn IZX(cpu: &mut R6502, bus: &mut dyn Bus) -> ModeID
|
||||||
|
{
|
||||||
ModeID::IZY
|
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)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
|
|
||||||
use super::{R6502, Bus, Flags, addressing_modes::{AddressingModes, ModeID}};
|
use super::{R6502, Bus, Flags, addressing_modes::{AddressingModes, ModeID}};
|
||||||
|
|
||||||
// Instruction decoding:
|
// Instruction decoding:
|
||||||
// https://llx.com/Neil/a2/opcodes.html
|
// https://llx.com/Neil/a2/opcodes.html
|
||||||
|
|
||||||
// GROUP ONE
|
// GROUP ONE
|
||||||
// 000 ORA
|
// 000 ORA
|
||||||
// 001 AND
|
// 001 AND
|
||||||
// 010 EOR
|
// 010 EOR
|
||||||
// 011 ADC
|
// 011 ADC
|
||||||
// 100 STA
|
// 100 STA
|
||||||
// 101 LDA
|
// 101 LDA
|
||||||
// 110 CMP
|
// 110 CMP
|
||||||
// 111 SBC
|
// 111 SBC
|
||||||
|
|
||||||
pub struct Instructions;
|
pub struct Instructions;
|
||||||
|
|
||||||
impl Instructions
|
impl Instructions
|
||||||
{
|
{
|
||||||
pub const GROUP_ONE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
|
pub const GROUP_ONE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
|
||||||
Instructions::ORA,
|
Instructions::ORA,
|
||||||
Instructions::AND,
|
Instructions::AND,
|
||||||
Instructions::EOR,
|
Instructions::EOR,
|
||||||
Instructions::ADC,
|
Instructions::ADC,
|
||||||
Instructions::STA,
|
Instructions::STA,
|
||||||
Instructions::LDA,
|
Instructions::LDA,
|
||||||
Instructions::CMP,
|
Instructions::CMP,
|
||||||
Instructions::SBC,
|
Instructions::SBC,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const GROUP_TWO_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
|
pub const GROUP_TWO_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
|
||||||
Instructions::ASL, // 000
|
Instructions::ASL, // 000
|
||||||
Instructions::ROL, // 001
|
Instructions::ROL, // 001
|
||||||
Instructions::LSR, // 010
|
Instructions::LSR, // 010
|
||||||
Instructions::ROR, // 011
|
Instructions::ROR, // 011
|
||||||
Instructions::STX, // 100
|
Instructions::STX, // 100
|
||||||
Instructions::LDX, // 101
|
Instructions::LDX, // 101
|
||||||
Instructions::DEC, // 110
|
Instructions::DEC, // 110
|
||||||
Instructions::INC, // 111
|
Instructions::INC, // 111
|
||||||
];
|
];
|
||||||
}
|
|
||||||
|
pub const GROUP_THREE_OPS: [fn(&mut R6502, &mut dyn Bus); 8] = [
|
||||||
impl Instructions
|
Instructions::ERR,
|
||||||
{
|
Instructions::BIT, // 001 BIT
|
||||||
///////////////////////////////////////////////////////////
|
Instructions::JMP, // 010 JMP
|
||||||
// GROUP ONE
|
Instructions::JMP, // 011 JMP (abs)
|
||||||
pub fn ORA(cpu: &mut R6502, _bus: &mut dyn Bus)
|
Instructions::STY, // 100 STY
|
||||||
{
|
Instructions::LDY, // 101 LDY
|
||||||
let data = cpu.working_data as u8;
|
Instructions::CPY, // 110 CPY
|
||||||
cpu.a = cpu.a | data;
|
Instructions::CPX, // 111 CPX
|
||||||
if cpu.a == 0
|
];
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::Z);
|
|
||||||
}
|
|
||||||
|
}
|
||||||
if cpu.a & 0x80 != 0
|
|
||||||
{
|
impl Instructions
|
||||||
cpu.set_flag(Flags::N);
|
{
|
||||||
}
|
pub fn ERR(_cpu: &mut R6502, _bus: &mut dyn Bus)
|
||||||
}
|
{
|
||||||
|
// TODO: Better error handling
|
||||||
pub fn AND(cpu: &mut R6502, bus: &mut dyn Bus)
|
println!("ERROR: Invalid Instruction");
|
||||||
{
|
}
|
||||||
let data = cpu.working_data as u8;
|
|
||||||
cpu.a = cpu.a & data;
|
///////////////////////////////////////////////////////////
|
||||||
if cpu.a == 0
|
// GROUP ONE
|
||||||
{
|
pub fn ORA(cpu: &mut R6502, _bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::Z);
|
{
|
||||||
}
|
let data = cpu.working_data as u8;
|
||||||
|
cpu.a = cpu.a | data;
|
||||||
if cpu.a & 0x80 != 0
|
if cpu.a == 0
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::N);
|
cpu.set_flag(Flags::Z);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if cpu.a & 0x80 != 0
|
||||||
pub fn EOR(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
cpu.set_flag(Flags::N);
|
||||||
let data = cpu.working_data as u8;
|
}
|
||||||
cpu.a = cpu.a ^ data;
|
}
|
||||||
if cpu.a == 0
|
|
||||||
{
|
pub fn AND(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::Z);
|
{
|
||||||
}
|
let data = cpu.working_data as u8;
|
||||||
|
cpu.a = cpu.a & data;
|
||||||
if cpu.a & 0x80 != 0
|
if cpu.a == 0
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::N);
|
cpu.set_flag(Flags::Z);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if cpu.a & 0x80 != 0
|
||||||
// Using a technique written javidx9
|
{
|
||||||
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
|
cpu.set_flag(Flags::N);
|
||||||
// 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;
|
pub fn EOR(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
|
{
|
||||||
// 16 bit addition to capture the carry easier
|
let data = cpu.working_data as u8;
|
||||||
let temp: u16 = cpu.a as u16 + cpu.working_data + carry;
|
cpu.a = cpu.a ^ data;
|
||||||
|
if cpu.a == 0
|
||||||
if temp > 255
|
{
|
||||||
{
|
cpu.set_flag(Flags::Z);
|
||||||
cpu.set_flag(Flags::C);
|
}
|
||||||
}
|
|
||||||
|
if cpu.a & 0x80 != 0
|
||||||
if temp == 0
|
{
|
||||||
{
|
cpu.set_flag(Flags::N);
|
||||||
cpu.set_flag(Flags::Z);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let did_overflow = (!((cpu.a as u16) ^ (cpu.working_data as u16)) & ((cpu.a as u16) ^ temp)) & 0x0080;
|
// Using a technique written javidx9
|
||||||
cpu.clear_flag(Flags::V);
|
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
|
||||||
if did_overflow > 0
|
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L659
|
||||||
{
|
pub fn ADC(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::V);
|
{
|
||||||
}
|
let carry = cpu.check_flag(Flags::C) as u16;
|
||||||
|
|
||||||
cpu.clear_flag(Flags::N);
|
// 16 bit addition to capture the carry easier
|
||||||
if temp & 0x80 > 0
|
let temp: u16 = cpu.a as u16 + cpu.working_data + carry;
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::N);
|
if temp > 255
|
||||||
}
|
{
|
||||||
|
cpu.set_flag(Flags::C);
|
||||||
cpu.a = (temp & 0x00FF) as u8;
|
}
|
||||||
}
|
|
||||||
|
if temp == 0
|
||||||
pub fn STA(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
cpu.set_flag(Flags::Z);
|
||||||
bus.write(cpu.working_addr, cpu.a);
|
}
|
||||||
}
|
|
||||||
|
let did_overflow = (!((cpu.a as u16) ^ (cpu.working_data as u16)) & ((cpu.a as u16) ^ temp)) & 0x0080;
|
||||||
pub fn LDA(cpu: &mut R6502, bus: &mut dyn Bus)
|
cpu.clear_flag(Flags::V);
|
||||||
{
|
if did_overflow > 0
|
||||||
let data = cpu.working_data as u8;
|
{
|
||||||
cpu.a = data;
|
cpu.set_flag(Flags::V);
|
||||||
|
}
|
||||||
if cpu.a == 0
|
|
||||||
{
|
cpu.clear_flag(Flags::N);
|
||||||
cpu.set_flag(Flags::Z);
|
if temp & 0x80 > 0
|
||||||
}
|
{
|
||||||
|
cpu.set_flag(Flags::N);
|
||||||
if cpu.a & 0x80 != 0
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::N);
|
cpu.a = (temp & 0x00FF) as u8;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn STA(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
pub fn CMP(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
bus.write(cpu.working_addr, cpu.a);
|
||||||
let data = cpu.working_data as u8;
|
}
|
||||||
if cpu.a >= data
|
|
||||||
{
|
pub fn LDA(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::C);
|
{
|
||||||
}
|
let data = cpu.working_data as u8;
|
||||||
else
|
cpu.a = data;
|
||||||
{
|
|
||||||
cpu.clear_flag(Flags::C);
|
if cpu.a == 0
|
||||||
}
|
{
|
||||||
|
cpu.set_flag(Flags::Z);
|
||||||
if cpu.a == data
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::Z);
|
if cpu.a & 0x80 != 0
|
||||||
}
|
{
|
||||||
else
|
cpu.set_flag(Flags::N);
|
||||||
{
|
}
|
||||||
cpu.clear_flag(Flags::Z);
|
}
|
||||||
}
|
|
||||||
|
pub fn CMP(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
if cpu.a < data
|
{
|
||||||
{
|
let data = cpu.working_data as u8;
|
||||||
cpu.set_flag(Flags::N);
|
if cpu.a >= data
|
||||||
}
|
{
|
||||||
else
|
cpu.set_flag(Flags::C);
|
||||||
{
|
}
|
||||||
cpu.clear_flag(Flags::N);
|
else
|
||||||
}
|
{
|
||||||
}
|
cpu.clear_flag(Flags::C);
|
||||||
|
}
|
||||||
// Using a technique written javidx9
|
|
||||||
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
|
if cpu.a == data
|
||||||
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L714
|
{
|
||||||
//
|
cpu.set_flag(Flags::Z);
|
||||||
// More info about the carry bit:
|
}
|
||||||
// http://forum.6502.org/viewtopic.php?t=18
|
else
|
||||||
pub fn SBC(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
cpu.clear_flag(Flags::Z);
|
||||||
let value = cpu.working_data ^ 0x00FF;
|
}
|
||||||
let carry = cpu.check_flag(Flags::C) as u16;
|
|
||||||
|
if cpu.a < data
|
||||||
let temp: u16 = cpu.a as u16 + value + carry;
|
{
|
||||||
|
cpu.set_flag(Flags::N);
|
||||||
cpu.clear_flag(Flags::C);
|
}
|
||||||
if temp > 255
|
else
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::C);
|
cpu.clear_flag(Flags::N);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cpu.clear_flag(Flags::Z);
|
|
||||||
if temp == 0
|
// Using a technique written javidx9
|
||||||
{
|
// The code in this function falls under the License (OLC-3) SEE LICENSE FILE
|
||||||
cpu.set_flag(Flags::Z);
|
// https://github.com/OneLoneCoder/olcNES/blob/master/Part%232%20-%20CPU/olc6502.cpp#L714
|
||||||
}
|
//
|
||||||
|
// More info about the carry bit:
|
||||||
let did_overflow = (!((cpu.a as u16) ^ (value)) & ((cpu.a as u16) ^ temp)) & 0x0080;
|
// http://forum.6502.org/viewtopic.php?t=18
|
||||||
cpu.clear_flag(Flags::V);
|
pub fn SBC(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
if did_overflow > 0
|
{
|
||||||
{
|
let value = cpu.working_data ^ 0x00FF;
|
||||||
cpu.set_flag(Flags::V);
|
let carry = cpu.check_flag(Flags::C) as u16;
|
||||||
}
|
|
||||||
|
let temp: u16 = cpu.a as u16 + value + carry;
|
||||||
cpu.clear_flag(Flags::N);
|
|
||||||
if temp & 0x80 > 0
|
cpu.clear_flag(Flags::C);
|
||||||
{
|
if temp > 255
|
||||||
cpu.set_flag(Flags::N);
|
{
|
||||||
}
|
cpu.set_flag(Flags::C);
|
||||||
|
}
|
||||||
cpu.a = (temp & 0x00FF) as u8;
|
|
||||||
}
|
cpu.clear_flag(Flags::Z);
|
||||||
|
if temp == 0
|
||||||
///////////////////////////////////////////////////////////
|
{
|
||||||
// GROUP TWO
|
cpu.set_flag(Flags::Z);
|
||||||
pub fn ASL(cpu: &mut R6502, bus: &mut dyn Bus)
|
}
|
||||||
{
|
|
||||||
cpu.clear_flag(Flags::C);
|
let did_overflow = (!((cpu.a as u16) ^ (value)) & ((cpu.a as u16) ^ temp)) & 0x0080;
|
||||||
if cpu.working_data as u8 & 0x80 > 0
|
cpu.clear_flag(Flags::V);
|
||||||
{
|
if did_overflow > 0
|
||||||
cpu.set_flag(Flags::C);
|
{
|
||||||
}
|
cpu.set_flag(Flags::V);
|
||||||
|
}
|
||||||
let result = cpu.working_data << 1;
|
|
||||||
|
cpu.clear_flag(Flags::N);
|
||||||
cpu.clear_flag(Flags::Z);
|
if temp & 0x80 > 0
|
||||||
if result == 0
|
{
|
||||||
{
|
cpu.set_flag(Flags::N);
|
||||||
cpu.set_flag(Flags::Z);
|
}
|
||||||
}
|
|
||||||
|
cpu.a = (temp & 0x00FF) as u8;
|
||||||
if result as u8 & 0x80 > 0
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::N);
|
///////////////////////////////////////////////////////////
|
||||||
}
|
// GROUP TWO
|
||||||
|
pub fn ASL(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
if cpu.addr_mode == ModeID::ACM
|
{
|
||||||
{
|
cpu.clear_flag(Flags::C);
|
||||||
cpu.a = result as u8;
|
if cpu.working_data as u8 & 0x80 > 0
|
||||||
}
|
{
|
||||||
else
|
cpu.set_flag(Flags::C);
|
||||||
{
|
}
|
||||||
bus.write(cpu.working_addr, result as u8);
|
|
||||||
}
|
let result = cpu.working_data << 1;
|
||||||
}
|
|
||||||
|
cpu.clear_flag(Flags::Z);
|
||||||
pub fn ROL(cpu: &mut R6502, bus: &mut dyn Bus)
|
if result == 0
|
||||||
{
|
{
|
||||||
let old_bit_7 = (cpu.working_data & 0x80) > 0;
|
cpu.set_flag(Flags::Z);
|
||||||
let carry = cpu.check_flag(Flags::C) as u16;
|
}
|
||||||
let result = (cpu.working_data << 1) ^ carry;
|
|
||||||
|
if result as u8 & 0x80 > 0
|
||||||
cpu.clear_flag(Flags::C);
|
{
|
||||||
if old_bit_7
|
cpu.set_flag(Flags::N);
|
||||||
{
|
}
|
||||||
cpu.set_flag(Flags::C);
|
|
||||||
}
|
if cpu.addr_mode == ModeID::ACM
|
||||||
|
{
|
||||||
cpu.clear_flag(Flags::N);
|
cpu.a = result as u8;
|
||||||
if result & 0x80 > 0
|
}
|
||||||
{
|
else
|
||||||
cpu.set_flag(Flags::N);
|
{
|
||||||
}
|
bus.write(cpu.working_addr, result as u8);
|
||||||
|
}
|
||||||
cpu.clear_flag(Flags::Z);
|
}
|
||||||
if result == 0
|
|
||||||
{
|
pub fn ROL(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::Z);
|
{
|
||||||
}
|
let old_bit_7 = (cpu.working_data & 0x80) > 0;
|
||||||
|
let carry = cpu.check_flag(Flags::C) as u16;
|
||||||
if cpu.addr_mode == ModeID::ACM
|
let result = (cpu.working_data << 1) ^ carry;
|
||||||
{
|
|
||||||
cpu.a = result as u8;
|
cpu.clear_flag(Flags::C);
|
||||||
}
|
if old_bit_7
|
||||||
else
|
{
|
||||||
{
|
cpu.set_flag(Flags::C);
|
||||||
bus.write(cpu.working_addr, result as u8);
|
}
|
||||||
}
|
|
||||||
|
cpu.clear_flag(Flags::N);
|
||||||
}
|
if result & 0x80 > 0
|
||||||
|
{
|
||||||
pub fn LSR(cpu: &mut R6502, bus: &mut dyn Bus)
|
cpu.set_flag(Flags::N);
|
||||||
{
|
}
|
||||||
let old_bit_0 = (cpu.working_data & 0x01) > 0;
|
|
||||||
let carry = cpu.check_flag(Flags::C) as u16;
|
cpu.clear_flag(Flags::Z);
|
||||||
let result = cpu.working_data >> 1;
|
if result == 0
|
||||||
|
{
|
||||||
cpu.clear_flag(Flags::C);
|
cpu.set_flag(Flags::Z);
|
||||||
if old_bit_0
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::C);
|
if cpu.addr_mode == ModeID::ACM
|
||||||
}
|
{
|
||||||
|
cpu.a = result as u8;
|
||||||
cpu.clear_flag(Flags::N);
|
}
|
||||||
if result & 0x80 > 0
|
else
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::N);
|
bus.write(cpu.working_addr, result as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.clear_flag(Flags::Z);
|
}
|
||||||
if result == 0
|
|
||||||
{
|
pub fn LSR(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.set_flag(Flags::Z);
|
{
|
||||||
}
|
let old_bit_0 = (cpu.working_data & 0x01) > 0;
|
||||||
|
let carry = cpu.check_flag(Flags::C) as u16;
|
||||||
if cpu.addr_mode == ModeID::ACM
|
let result = cpu.working_data >> 1;
|
||||||
{
|
|
||||||
cpu.a = result as u8;
|
cpu.clear_flag(Flags::C);
|
||||||
}
|
if old_bit_0
|
||||||
else
|
{
|
||||||
{
|
cpu.set_flag(Flags::C);
|
||||||
bus.write(cpu.working_addr, result as u8);
|
}
|
||||||
}
|
|
||||||
}
|
cpu.clear_flag(Flags::N);
|
||||||
|
if result & 0x80 > 0
|
||||||
pub fn ROR(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
cpu.set_flag(Flags::N);
|
||||||
let old_bit_0 = (cpu.working_data & 0x01) > 0;
|
}
|
||||||
let carry = cpu.check_flag(Flags::C) as u16;
|
|
||||||
let temp = carry << 7;
|
cpu.clear_flag(Flags::Z);
|
||||||
let result = (cpu.working_data >> 1) ^ (carry << 7);
|
if result == 0
|
||||||
|
{
|
||||||
cpu.clear_flag(Flags::C);
|
cpu.set_flag(Flags::Z);
|
||||||
if old_bit_0
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::C);
|
if cpu.addr_mode == ModeID::ACM
|
||||||
}
|
{
|
||||||
|
cpu.a = result as u8;
|
||||||
cpu.clear_flag(Flags::N);
|
}
|
||||||
if result & 0x80 > 0
|
else
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::N);
|
bus.write(cpu.working_addr, result as u8);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cpu.clear_flag(Flags::Z);
|
|
||||||
if result == 0
|
pub fn ROR(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::Z);
|
let old_bit_0 = (cpu.working_data & 0x01) > 0;
|
||||||
}
|
let carry = cpu.check_flag(Flags::C) as u16;
|
||||||
|
let temp = carry << 7;
|
||||||
if cpu.addr_mode == ModeID::ACM
|
let result = (cpu.working_data >> 1) ^ (carry << 7);
|
||||||
{
|
|
||||||
cpu.a = result as u8;
|
cpu.clear_flag(Flags::C);
|
||||||
}
|
if old_bit_0
|
||||||
else
|
{
|
||||||
{
|
cpu.set_flag(Flags::C);
|
||||||
bus.write(cpu.working_addr, result as u8);
|
}
|
||||||
}
|
|
||||||
}
|
cpu.clear_flag(Flags::N);
|
||||||
|
if result & 0x80 > 0
|
||||||
pub fn STX(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
cpu.set_flag(Flags::N);
|
||||||
bus.write(cpu.working_addr, cpu.x);
|
}
|
||||||
}
|
|
||||||
|
cpu.clear_flag(Flags::Z);
|
||||||
pub fn LDX(cpu: &mut R6502, bus: &mut dyn Bus)
|
if result == 0
|
||||||
{
|
{
|
||||||
let data = cpu.working_data as u8;
|
cpu.set_flag(Flags::Z);
|
||||||
cpu.x = data;
|
}
|
||||||
|
|
||||||
if cpu.x == 0
|
if cpu.addr_mode == ModeID::ACM
|
||||||
{
|
{
|
||||||
cpu.set_flag(Flags::Z);
|
cpu.a = result as u8;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if cpu.x & 0x80 != 0
|
{
|
||||||
{
|
bus.write(cpu.working_addr, result as u8);
|
||||||
cpu.set_flag(Flags::N);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn STX(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
pub fn DEC(cpu: &mut R6502, bus: &mut dyn Bus)
|
{
|
||||||
{
|
bus.write(cpu.working_addr, cpu.x);
|
||||||
let dec_val = bus.read(cpu.working_addr) - 1;
|
}
|
||||||
bus.write(cpu.working_addr, dec_val);
|
|
||||||
|
pub fn LDX(cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
cpu.clear_flag(Flags::Z);
|
{
|
||||||
if dec_val == 0
|
let data = cpu.working_data as u8;
|
||||||
{
|
cpu.x = data;
|
||||||
cpu.set_flag(Flags::Z);
|
|
||||||
}
|
if cpu.x == 0
|
||||||
|
{
|
||||||
cpu.clear_flag(Flags::N);
|
cpu.set_flag(Flags::Z);
|
||||||
if dec_val & 0x80 > 0
|
}
|
||||||
{
|
|
||||||
cpu.set_flag(Flags::N);
|
if cpu.x & 0x80 != 0
|
||||||
}
|
{
|
||||||
|
cpu.set_flag(Flags::N);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn INC(cpu: &mut R6502, bus: &mut dyn Bus)
|
|
||||||
{
|
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);
|
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.clear_flag(Flags::Z);
|
||||||
{
|
if dec_val == 0
|
||||||
cpu.set_flag(Flags::Z);
|
{
|
||||||
}
|
cpu.set_flag(Flags::Z);
|
||||||
|
}
|
||||||
cpu.clear_flag(Flags::N);
|
|
||||||
if dec_val & 0x80 > 0
|
cpu.clear_flag(Flags::N);
|
||||||
{
|
if dec_val & 0x80 > 0
|
||||||
cpu.set_flag(Flags::N);
|
{
|
||||||
}
|
cpu.set_flag(Flags::N);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
}
|
||||||
// GROUP THREE
|
|
||||||
|
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)]
|
#![allow(unused_variables, dead_code, non_snake_case)]
|
||||||
|
|
||||||
mod addressing_modes;
|
mod addressing_modes;
|
||||||
mod instructions;
|
mod instructions;
|
||||||
|
|
||||||
use addressing_modes::{AddressingModes, ModeID};
|
use addressing_modes::{AddressingModes, ModeID};
|
||||||
use instructions::Instructions;
|
use instructions::Instructions;
|
||||||
|
|
||||||
pub trait Bus
|
pub trait Bus
|
||||||
{
|
{
|
||||||
fn read(&self, addr: u16) -> u8;
|
fn read(&self, addr: u16) -> u8;
|
||||||
fn write(&mut self, addr: u16, value: u8);
|
fn write(&mut self, addr: u16, value: u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Sized for Bus
|
// impl Sized for Bus
|
||||||
// {
|
// {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Flags
|
pub enum Flags
|
||||||
{
|
{
|
||||||
C = (1 << 0), // Carry Flag
|
C = (1 << 0), // Carry Flag
|
||||||
Z = (1 << 1), // Zero Flag
|
Z = (1 << 1), // Zero Flag
|
||||||
I = (1 << 2), // Interrupt Disable
|
I = (1 << 2), // Interrupt Disable
|
||||||
D = (1 << 3), // Decimal Mode Flag
|
D = (1 << 3), // Decimal Mode Flag
|
||||||
B = (1 << 4), // Break Command
|
B = (1 << 4), // Break Command
|
||||||
U = (1 << 5), // Unused
|
U = (1 << 5), // Unused
|
||||||
V = (1 << 6), // Overflow Flag
|
V = (1 << 6), // Overflow Flag
|
||||||
N = (1 << 7), // Negative Flag
|
N = (1 << 7), // Negative Flag
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Registers
|
pub enum Registers
|
||||||
{
|
{
|
||||||
A,
|
A,
|
||||||
X,
|
X,
|
||||||
Y,
|
Y,
|
||||||
PC,
|
PC,
|
||||||
SP,
|
SP,
|
||||||
STATUS,
|
STATUS,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct R6502
|
pub struct R6502
|
||||||
{
|
{
|
||||||
a: u8, // Accumulator
|
a: u8, // Accumulator
|
||||||
x: u8, // X Register
|
x: u8, // X Register
|
||||||
y: u8, // Y Register
|
y: u8, // Y Register
|
||||||
|
|
||||||
pc: u16, // Program Counter
|
pc: u16, // Program Counter
|
||||||
sp: u8, // Stack Pointer
|
sp: u8, // Stack Pointer
|
||||||
status: u8, // Status Flags
|
status: u8, // Status Flags
|
||||||
|
|
||||||
cycles: u32, // Track cycles
|
cycles: u32, // Track cycles
|
||||||
|
|
||||||
// Helper Vars
|
// Helper Vars
|
||||||
addr_mode: ModeID,
|
addr_mode: ModeID,
|
||||||
working_data: u16, // value fetched for the ALU
|
working_data: u16, // value fetched for the ALU
|
||||||
working_addr: u16,
|
working_addr: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl R6502
|
impl R6502
|
||||||
{
|
{
|
||||||
// constructor
|
// constructor
|
||||||
pub fn new() -> R6502
|
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 }
|
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
|
// Debug Access
|
||||||
pub fn debug_get_reg(&self, reg: Registers) -> u16
|
pub fn debug_get_reg(&self, reg: Registers) -> u16
|
||||||
{
|
{
|
||||||
match reg
|
match reg
|
||||||
{
|
{
|
||||||
Registers::A => self.a as u16,
|
Registers::A => self.a as u16,
|
||||||
Registers::X => self.x as u16,
|
Registers::X => self.x as u16,
|
||||||
Registers::Y => self.y as u16,
|
Registers::Y => self.y as u16,
|
||||||
|
|
||||||
Registers::PC => self.pc,
|
Registers::PC => self.pc,
|
||||||
Registers::SP => self.sp as u16,
|
Registers::SP => self.sp as u16,
|
||||||
|
|
||||||
Registers::STATUS => self.status as u16,
|
Registers::STATUS => self.status as u16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_set_reg(&mut self, reg: Registers, value: u16)
|
pub fn debug_set_reg(&mut self, reg: Registers, value: u16)
|
||||||
{
|
{
|
||||||
match reg
|
match reg
|
||||||
{
|
{
|
||||||
Registers::A => self.a = value as u8,
|
Registers::A => self.a = value as u8,
|
||||||
Registers::X => self.x = value as u8,
|
Registers::X => self.x = value as u8,
|
||||||
Registers::Y => self.y = value as u8,
|
Registers::Y => self.y = value as u8,
|
||||||
|
|
||||||
Registers::PC => self.pc = value,
|
Registers::PC => self.pc = value,
|
||||||
Registers::SP => self.sp = value as u8,
|
Registers::SP => self.sp = value as u8,
|
||||||
|
|
||||||
Registers::STATUS => self.status = value as u8,
|
Registers::STATUS => self.status = value as u8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// signals
|
// signals
|
||||||
pub fn clock(&mut self, bus: &mut dyn Bus)
|
pub fn clock(&mut self, bus: &mut dyn Bus)
|
||||||
{
|
{
|
||||||
// TODO: Track instructions cycles
|
// TODO: Track instructions cycles
|
||||||
// if self.cycles == 0
|
// if self.cycles == 0
|
||||||
//{
|
//{
|
||||||
let opcode = bus.read(self.pc);
|
let opcode = bus.read(self.pc);
|
||||||
self.pc += 1;
|
self.pc += 1;
|
||||||
|
|
||||||
execute(opcode, self, bus);
|
execute(opcode, self, bus);
|
||||||
//self.pc += 1;
|
//self.pc += 1;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
self.cycles -= 1;
|
self.cycles -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self, bus: &mut dyn Bus)
|
pub fn reset(&mut self, bus: &mut dyn Bus)
|
||||||
{
|
{
|
||||||
self.a = 0;
|
self.a = 0;
|
||||||
self.x = 0;
|
self.x = 0;
|
||||||
self.y = 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.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.status = 0;
|
||||||
self.set_flag(Flags::U);
|
self.set_flag(Flags::U);
|
||||||
|
|
||||||
let lo: u16 = bus.read(0xFFFC) as u16;
|
let lo: u16 = bus.read(0xFFFC) as u16;
|
||||||
let hi: u16 = bus.read(0xFFFD) as u16;
|
let hi: u16 = bus.read(0xFFFD) as u16;
|
||||||
|
|
||||||
self.pc = (hi << 8) | lo;
|
self.pc = (hi << 8) | lo;
|
||||||
|
|
||||||
// internal helper variables
|
// internal helper variables
|
||||||
self.working_data = 0;
|
self.working_data = 0;
|
||||||
// self.working_addr = 0;
|
// self.working_addr = 0;
|
||||||
|
|
||||||
self.cycles = 8;
|
self.cycles = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn irq(&mut self, bus: &mut impl Bus)
|
pub fn irq(&mut self, bus: &mut impl Bus)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nmi(&mut self, bus: &mut impl Bus)
|
pub fn nmi(&mut self, bus: &mut impl Bus)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
pub fn set_flag(&mut self, bit: Flags)
|
pub fn set_flag(&mut self, bit: Flags)
|
||||||
{
|
{
|
||||||
self.status |= bit as u8;
|
self.status |= bit as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_flag(&mut self, bit: Flags)
|
pub fn clear_flag(&mut self, bit: Flags)
|
||||||
{
|
{
|
||||||
self.status &= !(bit as u8);
|
self.status &= !(bit as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_flag(&mut self, bit: Flags) -> u8
|
pub fn check_flag(&mut self, bit: Flags) -> u8
|
||||||
{
|
{
|
||||||
if self.status & (bit as u8) > 0
|
if self.status & (bit as u8) > 0
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn execute(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
|
fn execute(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
|
||||||
{
|
{
|
||||||
// Instruction decoding:
|
// Instruction decoding:
|
||||||
// https://llx.com/Neil/a2/opcodes.html
|
// https://llx.com/Neil/a2/opcodes.html
|
||||||
|
|
||||||
let group_code = instruction & 0x03; // group one has a bit pattern of xxxxxx01
|
let group_code = instruction & 0x03; // group one has a bit pattern of xxxxxx01
|
||||||
match group_code
|
match group_code
|
||||||
{
|
{
|
||||||
0x01 => exe_group_one(instruction, cpu, bus),
|
0x01 => exe_group_one(instruction, cpu, bus),
|
||||||
0x02 => exe_group_two(instruction, cpu, bus),
|
0x02 => exe_group_two(instruction, cpu, bus),
|
||||||
|
0x00 => exe_group_three(instruction, cpu, bus),
|
||||||
// TODO: Conditionals and specially formatted instructions
|
|
||||||
|
// TODO: Conditionals and specially formatted instructions
|
||||||
_ => panic!("UNKNOWN INSTRUCTION ADDRESS MODE: {}", group_code)
|
|
||||||
}
|
_ => panic!("UNKNOWN INSTRUCTION ADDRESS MODE: {}", group_code)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn exe_group_one(instruction: u8, cpu: &mut R6502, bus: &mut dyn Bus)
|
|
||||||
{
|
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;
|
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);
|
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)
|
|
||||||
{
|
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;
|
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;
|
// With STX and LDX, "zero page,X" addressing becomes "zero page,Y", and with LDX, "absolute,X" becomes "absolute,Y".
|
||||||
const LDX_ZPX: u8 = 0xB6;
|
const STX_ZPX: u8 = 0x96;
|
||||||
const LDX_ABX: u8 = 0xBE;
|
const LDX_ZPX: u8 = 0xB6;
|
||||||
|
const LDX_ABX: u8 = 0xBE;
|
||||||
|
|
||||||
match instruction
|
|
||||||
{
|
match instruction
|
||||||
STX_ZPX =>
|
{
|
||||||
{
|
STX_ZPX =>
|
||||||
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
|
{
|
||||||
Instructions::STX(cpu, bus);
|
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
|
||||||
},
|
Instructions::STX(cpu, bus);
|
||||||
|
},
|
||||||
LDX_ZPX =>
|
|
||||||
{
|
LDX_ZPX =>
|
||||||
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
|
{
|
||||||
Instructions::LDX(cpu, bus);
|
cpu.addr_mode = AddressingModes::ZPY(cpu, bus);
|
||||||
}
|
Instructions::LDX(cpu, bus);
|
||||||
|
}
|
||||||
LDX_ABX =>
|
|
||||||
{
|
LDX_ABX =>
|
||||||
|
{
|
||||||
cpu.addr_mode = AddressingModes::ABY(cpu, bus);
|
|
||||||
Instructions::LDX(cpu, bus);
|
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);
|
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)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
|
|
||||||
use crate::tests::test_bus::RAMBus;
|
use crate::tests::test_bus::RAMBus;
|
||||||
use crate::r6502::{R6502, Bus, Registers};
|
use crate::r6502::{R6502, Bus, Registers};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn IMP()
|
fn IMP()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ACM()
|
fn ACM()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn IMM()
|
fn IMM()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xA9); // LDA - Immediate mode
|
bus.write(addr, 0xA9); // LDA - Immediate mode
|
||||||
bus.write(addr + 1, 0x08); // Argument
|
bus.write(addr + 1, 0x08); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ZP0()
|
fn ZP0()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory in the zero page
|
// Manually put 0x08 into memory in the zero page
|
||||||
bus.write(0x000A, 0x08);
|
bus.write(0x000A, 0x08);
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xA5); // LDA - Zero Page mode
|
bus.write(addr, 0xA5); // LDA - Zero Page mode
|
||||||
bus.write(addr + 1, 0x0A); // Argument
|
bus.write(addr + 1, 0x0A); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ZPX()
|
fn ZPX()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory in the zero page
|
// Manually put 0x08 into memory in the zero page
|
||||||
bus.write(0x000A, 0x08);
|
bus.write(0x000A, 0x08);
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xB5); // LDA - Zero Page, X mode
|
bus.write(addr, 0xB5); // LDA - Zero Page, X mode
|
||||||
bus.write(addr + 1, 0x04); // Argument
|
bus.write(addr + 1, 0x04); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::X, 0x06);
|
cpu.debug_set_reg(Registers::X, 0x06);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ZPY()
|
fn ZPY()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn REL()
|
fn REL()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ABS()
|
fn ABS()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory in the zero page
|
// Manually put 0x08 into memory in the zero page
|
||||||
bus.write(0x010A, 0x08);
|
bus.write(0x010A, 0x08);
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xAD); // LDA - Absolute mode
|
bus.write(addr, 0xAD); // LDA - Absolute mode
|
||||||
bus.write(addr + 1, 0x0A); // Argument lo word
|
bus.write(addr + 1, 0x0A); // Argument lo word
|
||||||
bus.write(addr + 2, 0x01); // Argument hi word
|
bus.write(addr + 2, 0x01); // Argument hi word
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ABX()
|
fn ABX()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory in the zero page
|
// Manually put 0x08 into memory in the zero page
|
||||||
bus.write(0x010B, 0x08);
|
bus.write(0x010B, 0x08);
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xBD); // LDA - Absolute, X mode
|
bus.write(addr, 0xBD); // LDA - Absolute, X mode
|
||||||
bus.write(addr + 1, 0x0A); // Argument lo word
|
bus.write(addr + 1, 0x0A); // Argument lo word
|
||||||
bus.write(addr + 2, 0x01); // Argument hi word
|
bus.write(addr + 2, 0x01); // Argument hi word
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::X, 0x01);
|
cpu.debug_set_reg(Registers::X, 0x01);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ABY()
|
fn ABY()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory in the zero page
|
// Manually put 0x08 into memory in the zero page
|
||||||
bus.write(0x010B, 0x08);
|
bus.write(0x010B, 0x08);
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xB9); // LDA - Absolute, X mode
|
bus.write(addr, 0xB9); // LDA - Absolute, X mode
|
||||||
bus.write(addr + 1, 0x0A); // Argument lo word
|
bus.write(addr + 1, 0x0A); // Argument lo word
|
||||||
bus.write(addr + 2, 0x01); // Argument hi word
|
bus.write(addr + 2, 0x01); // Argument hi word
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::Y, 0x01);
|
cpu.debug_set_reg(Registers::Y, 0x01);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn IND()
|
fn IND()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn IZX()
|
fn IZX()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory
|
// Manually put 0x08 into memory
|
||||||
bus.write(0x010B, 0x08);
|
bus.write(0x010B, 0x08);
|
||||||
|
|
||||||
// Manuall put 0x010B into the Zero page at 0x000A
|
// Manuall put 0x010B into the Zero page at 0x000A
|
||||||
bus.write(0x000A, 0x0B); // Pointer lo byte
|
bus.write(0x000A, 0x0B); // Pointer lo byte
|
||||||
bus.write(0x000B, 0x01); // Pointer hi byte
|
bus.write(0x000B, 0x01); // Pointer hi byte
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xA1); // LDA - Indirect, X mode
|
bus.write(addr, 0xA1); // LDA - Indirect, X mode
|
||||||
bus.write(addr + 1, 0x09); // Argument - gets added to X reg
|
bus.write(addr + 1, 0x09); // Argument - gets added to X reg
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::X, 0x01);
|
cpu.debug_set_reg(Registers::X, 0x01);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn IZY()
|
fn IZY()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Manually put 0x08 into memory
|
// Manually put 0x08 into memory
|
||||||
bus.write(0x020B, 0x08);
|
bus.write(0x020B, 0x08);
|
||||||
|
|
||||||
// Manuall put 0x01FC into the Zero page at 0x000A
|
// Manuall put 0x01FC into the Zero page at 0x000A
|
||||||
// This will be added to the Y register (which will store 0x0F)
|
// This will be added to the Y register (which will store 0x0F)
|
||||||
bus.write(0x000A, 0xFC); // Pointer lo byte
|
bus.write(0x000A, 0xFC); // Pointer lo byte
|
||||||
bus.write(0x000B, 0x01); // Pointer hi byte
|
bus.write(0x000B, 0x01); // Pointer hi byte
|
||||||
|
|
||||||
// Program to load 0x08 into the accumulator
|
// Program to load 0x08 into the accumulator
|
||||||
bus.write(addr, 0xB1); // LDA - Indirect, Y mode
|
bus.write(addr, 0xB1); // LDA - Indirect, Y mode
|
||||||
bus.write(addr + 1, 0x0A); // Argument - Pointer into the Zero Page
|
bus.write(addr + 1, 0x0A); // Argument - Pointer into the Zero Page
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::Y, 0x0F); // Offset of the value at the zero page address
|
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)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x08 in the A register?
|
// Is 0x08 in the A register?
|
||||||
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
assert_eq!(0x08, cpu.debug_get_reg(Registers::A));
|
||||||
}
|
}
|
||||||
@ -1,109 +1,109 @@
|
|||||||
|
|
||||||
#![allow(dead_code, non_snake_case)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
|
|
||||||
use crate::tests::test_bus::RAMBus;
|
use crate::tests::test_bus::RAMBus;
|
||||||
use crate::r6502::{R6502, Bus, Registers, Flags};
|
use crate::r6502::{R6502, Bus, Registers, Flags};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic()
|
fn basic()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Write the program to memory
|
// Write the program to memory
|
||||||
// ADC 6
|
// ADC 6
|
||||||
bus.write(addr, 0x69); // ADC - Immediate mode
|
bus.write(addr, 0x69); // ADC - Immediate mode
|
||||||
bus.write(addr + 1, 0x06); // Argument
|
bus.write(addr + 1, 0x06); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::A, 0x04);
|
cpu.debug_set_reg(Registers::A, 0x04);
|
||||||
|
|
||||||
// Clock the cpu twice (Clock essentially runs one full instruction)
|
// Clock the cpu twice (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x0A in the A register?
|
// Is 0x0A in the A register?
|
||||||
assert_eq!(0x0A, cpu.debug_get_reg(Registers::A), "Wrong answer");
|
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::V), "Overflow bit should not be set");
|
||||||
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
|
assert_eq!(0, cpu.check_flag(Flags::C), "Carry bit should not be set");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_carry()
|
fn with_carry()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Add with carry in
|
// Add with carry in
|
||||||
// Write the program to memory
|
// Write the program to memory
|
||||||
// ADC 6
|
// ADC 6
|
||||||
bus.write(addr, 0x69); // ADC - Immediate mode
|
bus.write(addr, 0x69); // ADC - Immediate mode
|
||||||
bus.write(addr + 1, 0x06); // Argument
|
bus.write(addr + 1, 0x06); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu internals
|
// manually setup the cpu internals
|
||||||
cpu.debug_set_reg(Registers::A, 0x04);
|
cpu.debug_set_reg(Registers::A, 0x04);
|
||||||
cpu.set_flag(Flags::C);
|
cpu.set_flag(Flags::C);
|
||||||
|
|
||||||
// Clock the cpu twice (Clock essentially runs one full instruction)
|
// Clock the cpu twice (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x0B in the A register?
|
// Is 0x0B in the A register?
|
||||||
assert_eq!(0x0B, cpu.debug_get_reg(Registers::A), "Wrong answer");
|
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");
|
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_overflow()
|
fn with_overflow()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Add with overflow
|
// Add with overflow
|
||||||
// Write the program to memory
|
// Write the program to memory
|
||||||
// ADC 126
|
// ADC 126
|
||||||
bus.write(addr, 0x69); // ADC - Immediate mode
|
bus.write(addr, 0x69); // ADC - Immediate mode
|
||||||
bus.write(addr + 1, 126); // Argument
|
bus.write(addr + 1, 126); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu internals
|
// manually setup the cpu internals
|
||||||
cpu.debug_set_reg(Registers::A, 0x04);
|
cpu.debug_set_reg(Registers::A, 0x04);
|
||||||
|
|
||||||
// Clock the cpu twice (Clock essentially runs one full instruction)
|
// Clock the cpu twice (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 130 in the A register?
|
// Is 130 in the A register?
|
||||||
assert_eq!(130, cpu.debug_get_reg(Registers::A));
|
assert_eq!(130, cpu.debug_get_reg(Registers::A));
|
||||||
|
|
||||||
// Is the overflow bit set?
|
// Is the overflow bit set?
|
||||||
assert_eq!(1, cpu.check_flag(Flags::V), "Failed addition with overflow");
|
assert_eq!(1, cpu.check_flag(Flags::V), "Failed addition with overflow");
|
||||||
}
|
}
|
||||||
@ -1,110 +1,110 @@
|
|||||||
|
|
||||||
#![allow(dead_code, non_snake_case)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
|
|
||||||
use crate::tests::test_bus::RAMBus;
|
use crate::tests::test_bus::RAMBus;
|
||||||
use crate::r6502::{R6502, Bus, Registers, Flags};
|
use crate::r6502::{R6502, Bus, Registers, Flags};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn less_than()
|
fn less_than()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
///////////////////////
|
///////////////////////
|
||||||
// Parameter is less than A reg
|
// Parameter is less than A reg
|
||||||
|
|
||||||
// Program to compare 0x10 with 0x05 (0x05 will be the argument)
|
// Program to compare 0x10 with 0x05 (0x05 will be the argument)
|
||||||
bus.write(addr, 0xC9); // CMP - Immediate mode
|
bus.write(addr, 0xC9); // CMP - Immediate mode
|
||||||
bus.write(addr + 1, 0x05); // Argument
|
bus.write(addr + 1, 0x05); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::A, 0x10);
|
cpu.debug_set_reg(Registers::A, 0x10);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// C Flag should be 1, Z N Flags should be 0
|
// C Flag should be 1, Z N Flags should be 0
|
||||||
assert_eq!(1, cpu.check_flag(Flags::C));
|
assert_eq!(1, cpu.check_flag(Flags::C));
|
||||||
assert_eq!(0, cpu.check_flag(Flags::Z));
|
assert_eq!(0, cpu.check_flag(Flags::Z));
|
||||||
assert_eq!(0, cpu.check_flag(Flags::N));
|
assert_eq!(0, cpu.check_flag(Flags::N));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn equal_to()
|
fn equal_to()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
///////////////////////
|
///////////////////////
|
||||||
// Parameter is equal to the A reg
|
// Parameter is equal to the A reg
|
||||||
|
|
||||||
// Program to compare 0x10 with 0x10
|
// Program to compare 0x10 with 0x10
|
||||||
bus.write(addr, 0xC9); // CMP - Immediate mode
|
bus.write(addr, 0xC9); // CMP - Immediate mode
|
||||||
bus.write(addr + 1, 0x10); // Argument
|
bus.write(addr + 1, 0x10); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::A, 0x10);
|
cpu.debug_set_reg(Registers::A, 0x10);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// C Z Flags should be 1, N Flag should be 0
|
// 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::C));
|
||||||
assert_eq!(1, cpu.check_flag(Flags::Z));
|
assert_eq!(1, cpu.check_flag(Flags::Z));
|
||||||
assert_eq!(0, cpu.check_flag(Flags::N));
|
assert_eq!(0, cpu.check_flag(Flags::N));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn greater_than()
|
fn greater_than()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
///////////////////////
|
///////////////////////
|
||||||
// Parameter is greater than A reg
|
// Parameter is greater than A reg
|
||||||
|
|
||||||
// Program to compare 0x05 with 0x10
|
// Program to compare 0x05 with 0x10
|
||||||
bus.write(addr, 0xC9); // CMP - Immediate mode
|
bus.write(addr, 0xC9); // CMP - Immediate mode
|
||||||
bus.write(addr + 1, 0x10); // Argument
|
bus.write(addr + 1, 0x10); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu registers
|
// manually setup the cpu registers
|
||||||
cpu.debug_set_reg(Registers::A, 0x05);
|
cpu.debug_set_reg(Registers::A, 0x05);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// C Z Flags should be 0, N Flag should be 1
|
// 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::C));
|
||||||
assert_eq!(0, cpu.check_flag(Flags::Z));
|
assert_eq!(0, cpu.check_flag(Flags::Z));
|
||||||
assert_eq!(1, cpu.check_flag(Flags::N));
|
assert_eq!(1, cpu.check_flag(Flags::N));
|
||||||
}
|
}
|
||||||
@ -1,105 +1,105 @@
|
|||||||
|
|
||||||
#![allow(dead_code, non_snake_case)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
|
|
||||||
use crate::tests::test_bus::RAMBus;
|
use crate::tests::test_bus::RAMBus;
|
||||||
use crate::r6502::{R6502, Bus, Registers, Flags};
|
use crate::r6502::{R6502, Bus, Registers, Flags};
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic()
|
fn basic()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Program to subtract 0x06 from 0x0A
|
// Program to subtract 0x06 from 0x0A
|
||||||
bus.write(addr, 0xE9); // SBC - Immediate mode
|
bus.write(addr, 0xE9); // SBC - Immediate mode
|
||||||
bus.write(addr + 1, 0x06); // Argument
|
bus.write(addr + 1, 0x06); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu state
|
// manually setup the cpu state
|
||||||
cpu.set_flag(Flags::C);
|
cpu.set_flag(Flags::C);
|
||||||
cpu.debug_set_reg(Registers::A, 0x0A);
|
cpu.debug_set_reg(Registers::A, 0x0A);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is 0x04 in the A register?
|
// Is 0x04 in the A register?
|
||||||
assert_eq!(0x04, cpu.debug_get_reg(Registers::A), "wrong answer");
|
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!(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");
|
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_carry()
|
fn with_carry()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Program to subtract 0x09 from 0x08
|
// Program to subtract 0x09 from 0x08
|
||||||
bus.write(addr, 0xE9); // SBC - Immediate mode
|
bus.write(addr, 0xE9); // SBC - Immediate mode
|
||||||
bus.write(addr + 1, 0x09); // Argument
|
bus.write(addr + 1, 0x09); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu state
|
// manually setup the cpu state
|
||||||
cpu.set_flag(Flags::C);
|
cpu.set_flag(Flags::C);
|
||||||
cpu.debug_set_reg(Registers::A, 0x08);
|
cpu.debug_set_reg(Registers::A, 0x08);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
// Is -1 in the A register?
|
// Is -1 in the A register?
|
||||||
assert_eq!(0xFF as u16, cpu.debug_get_reg(Registers::A), "Wrong answer");
|
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::C), "Carry bit should not be set");
|
||||||
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
|
assert_eq!(0, cpu.check_flag(Flags::V), "Overflow bit should not be set");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_overflow()
|
fn with_overflow()
|
||||||
{
|
{
|
||||||
let mut cpu = R6502::new();
|
let mut cpu = R6502::new();
|
||||||
let mut bus = RAMBus::new();
|
let mut bus = RAMBus::new();
|
||||||
|
|
||||||
// program address
|
// program address
|
||||||
let addr = 0x0020 as u16;
|
let addr = 0x0020 as u16;
|
||||||
|
|
||||||
// Set the program counter address
|
// Set the program counter address
|
||||||
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
bus.write(0xFFFC, (addr & 0x00FF) as u8); // low byte
|
||||||
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
bus.write(0xFFFD, ((addr & 0xFF00) >> 8) as u8); // high byte
|
||||||
|
|
||||||
// Program to subtract 0x7E from 0xFB (-5 - 126)
|
// Program to subtract 0x7E from 0xFB (-5 - 126)
|
||||||
bus.write(addr, 0xE9); // SBC - Immediate mode
|
bus.write(addr, 0xE9); // SBC - Immediate mode
|
||||||
bus.write(addr + 1, 0x7E); // Argument
|
bus.write(addr + 1, 0x7E); // Argument
|
||||||
|
|
||||||
// Restart cpu
|
// Restart cpu
|
||||||
cpu.reset(&mut bus);
|
cpu.reset(&mut bus);
|
||||||
|
|
||||||
// manually setup the cpu state
|
// manually setup the cpu state
|
||||||
cpu.set_flag(Flags::C);
|
cpu.set_flag(Flags::C);
|
||||||
cpu.debug_set_reg(Registers::A, 0xFB);
|
cpu.debug_set_reg(Registers::A, 0xFB);
|
||||||
|
|
||||||
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
// Clock the cpu to run the program (Clock essentially runs one full instruction)
|
||||||
cpu.clock(&mut bus);
|
cpu.clock(&mut bus);
|
||||||
|
|
||||||
|
|
||||||
assert_eq!(0x7D, cpu.debug_get_reg(Registers::A), "Wrong answer");
|
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::C), "Carry bit should be set");
|
||||||
assert_eq!(1, cpu.check_flag(Flags::V), "Overflow 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;
|
use crate::r6502::Bus;
|
||||||
|
|
||||||
// All-RAM bus for testing
|
// All-RAM bus for testing
|
||||||
pub struct RAMBus
|
pub struct RAMBus
|
||||||
{
|
{
|
||||||
ram: [u8; 64 * 1024]
|
ram: [u8; 64 * 1024]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RAMBus
|
impl RAMBus
|
||||||
{
|
{
|
||||||
pub fn new() -> RAMBus
|
pub fn new() -> RAMBus
|
||||||
{
|
{
|
||||||
RAMBus { ram: [0; 64 * 1024] }
|
RAMBus { ram: [0; 64 * 1024] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus for RAMBus
|
impl Bus for RAMBus
|
||||||
{
|
{
|
||||||
fn read(&self, addr: u16) -> u8
|
fn read(&self, addr: u16) -> u8
|
||||||
{
|
{
|
||||||
self.ram[addr as usize]
|
self.ram[addr as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, addr: u16, value: u8)
|
fn write(&mut self, addr: u16, value: u8)
|
||||||
{
|
{
|
||||||
self.ram[addr as usize] = value;
|
self.ram[addr as usize] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue