Repository: gianlucag/mos6502
Branch: master
Commit: 5790adce684a
Files: 16
Total size: 95.4 KB
Directory structure:
gitextract_gtzs_4yo/
├── .gitignore
├── LICENSE.txt
├── README.md
├── mos6502.cpp
├── mos6502.h
└── tests/
├── Makefile
├── functional/
│ ├── .gitignore
│ ├── Makefile
│ ├── as65.sh
│ ├── decimal.patch
│ ├── main.cpp
│ ├── notes.txt
│ └── readme.md
└── singlestep/
├── .gitignore
├── Makefile
└── main.cpp
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.o
================================================
FILE: LICENSE.txt
================================================
MIT License
Copyright (c) 2017 Gianluca Ghettini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
## MOS6502 Emulator in C++
This is my C++ implementation of the MOS Technology 6502 CPU. The code is written to be more readable than fast, however some minor tricks have been introduced to greatly reduce the overall execution time.
main features:
- 100% coverage of legal opcodes
- decimal mode implemented
- read/write bus callback
- jump table opcode selection
still to implement
- 100% cycle accuracy
- illegal opcodes
- hardware glitches, the known ones of course :-)
The emulator was extensively tested against this test suite:
https://github.com/Klaus2m5/6502_65C02_functional_tests
and in parallel emulation with Fake6502 http://rubbermallet.org/fake6502.c
so expect nearly 100% compliance with the real deal... at least on the normal behavior: as I said stuff like illegal opcodes or hardware glitches are currently not implemented.
## Why yet another 6502 emulator?
Just for fun :). This CPU (and its many derivatives) powered machines such as:
- Apple II
- Nintendo Entertainment system (NES)
- Atari 2600
- Commodore PET and VIC-20
- Commodore 64 (6510 chip actually, but fully compatible emulator-wise)
- BBC Micro
and many other embedded devices still used today.
You can use this emulator in your machine emulator project. However cycle accuracy is not yet implemented so mid-frame register update tricks cannot be reliably emulated.
## Some things emulators: emulator types
You can group all the CPU emulators out there in 4 main categories:
- switch-case based
- jump-table based
- PLA or microcode emulation based
- graph based
The latter are the most accurate as they emulate the connections between transistors inside the die of the CPU. They emulate even the unwanted glitches, known and still unknown. However, the complexity of such emulators is non-linear with the number of transistors: in other word, _you don't want to emulate a modern Intel quad core using this approach!!!_
for an example check this out: http://visual6502.org/JSSim/index.html
The PLA/microcode based are the best as they offer both speed and limited complexity.
The switch-case based are the simpler ones but also the slowest: the opcode value is thrown inside a huge switch case which selects the code snippet to execute; compilers can optimize switch case to reach near O(log(n)) complexity but they hardly do it when dealing with sparse integers (like most of the CPU opcode tables).
## Emulator features
My project is a simple jump-table based emulator: the actual value of the opcode (let's say 0x80) is used to address a function pointer table, each entry of such table is a C++ function which emulates the behavior of the corresponding real instruction.
All the 13 addressing modes are emulated:
```
// addressing modes
uint16_t Addr_ACC(); // ACCUMULATOR
uint16_t Addr_IMM(); // IMMEDIATE
uint16_t Addr_ABS(); // ABSOLUTE
uint16_t Addr_ZER(); // ZERO PAGE
uint16_t Addr_ZEX(); // INDEXED-X ZERO PAGE
uint16_t Addr_ZEY(); // INDEXED-Y ZERO PAGE
uint16_t Addr_ABX(); // INDEXED-X ABSOLUTE
uint16_t Addr_ABY(); // INDEXED-Y ABSOLUTE
uint16_t Addr_IMP(); // IMPLIED
uint16_t Addr_REL(); // RELATIVE
uint16_t Addr_INX(); // INDEXED-X INDIRECT
uint16_t Addr_INY(); // INDEXED-Y INDIRECT
uint16_t Addr_ABI(); // ABSOLUTE INDIRECT
```
All the 151 opcodes are emulated. Since the 6502 CPU uses 8 bit to encode the opcode value it also has a lot of "illegal opcodes" (i.e. opcode values other than the designed 151). Such opcodes perform weird operations, write multiple registers at the same time, sometimes are the combination of two or more "valid" opcodes. Such illegals were used to enforce software copy protection or to discover the exact CPU type.
The illegals are not supported yet, so instead a simple NOP is executed.
## Inner main loop
It's a classic fetch-decode-execute loop:
```
while(start + n > cycles && !illegalOpcode)
{
// fetch
opcode = Read(pc++);
// decode
instr = InstrTable[opcode];
// execute
Exec(instr);
}
```
The next instruction (the opcode value) is retrieved from memory. Then it's decoded (i.e. the opcode is used to address the instruction table) and the resulting code block is executed.
## Public methods
The emulator comes as a single C++ class with five public methods:
```
mos6502(BusRead r, BusWrite w);
void NMI();
void IRQ();
void Reset();
void Run(uint32_t n);
```
`mos6502(BusRead r, BusWrite w);`
it's the class constructor. It requires you to pass two external functions:
```
uint8_t MemoryRead(uint16_t address);
void MemoryWrite(uint16_t address, uint8_t value);
```
respectively to read/write from/to a memory location (16 bit address, 8 bit value). In such functions you can define your address decoding logic (if any) to address memory mapped I/O, external virtual devices and such.
```
void NMI();
```
triggers a Non-Mascherable Interrupt request, as done by the external pin of the real chip
```
void IRQ();
```
triggers an Interrupt ReQuest, as done by the external pin of the real chip
```
void Reset();
```
performs an hardware reset, as done by the external pin of the real chip
```
void Run(uint32_t n);
```
It runs the CPU for the next 'n' machine instructions.
## Links
Some useful stuff I used...
http://en.wikipedia.org/wiki/MOS_Technology_6502
http://www.6502.org/documents/datasheets/mos/
http://www.mdawson.net/vic20chrome/cpu/mos_6500_mpu_preliminary_may_1976.pdf
http://rubbermallet.org/fake6502.c
================================================
FILE: mos6502.cpp
================================================
#include "mos6502.h"
#define NEGATIVE 0x80
#define OVERFLOW 0x40
#define CONSTANT 0x20
#define BREAK 0x10
#define DECIMAL 0x08
#define INTERRUPT 0x04
#define ZERO 0x02
#define CARRY 0x01
#define SET_NEGATIVE(x) ((x) ? (status |= NEGATIVE) : (status &= (~NEGATIVE)) )
#define SET_OVERFLOW(x) ((x) ? (status |= OVERFLOW) : (status &= (~OVERFLOW)) )
#define SET_CONSTANT(x) ((x) ? (status |= CONSTANT) : (status &= (~CONSTANT)) )
#define SET_BREAK(x) ((x) ? (status |= BREAK) : (status &= (~BREAK)) )
#define SET_DECIMAL(x) ((x) ? (status |= DECIMAL) : (status &= (~DECIMAL)) )
#define SET_INTERRUPT(x) ((x) ? (status |= INTERRUPT) : (status &= (~INTERRUPT)) )
#define SET_ZERO(x) ((x) ? (status |= ZERO) : (status &= (~ZERO)) )
#define SET_CARRY(x) ((x) ? (status |= CARRY) : (status &= (~CARRY)) )
#define IF_NEGATIVE() ((status & NEGATIVE) ? true : false)
#define IF_OVERFLOW() ((status & OVERFLOW) ? true : false)
#define IF_CONSTANT() ((status & CONSTANT) ? true : false)
#define IF_BREAK() ((status & BREAK) ? true : false)
#define IF_DECIMAL() ((status & DECIMAL) ? true : false)
#define IF_INTERRUPT() ((status & INTERRUPT) ? true : false)
#define IF_ZERO() ((status & ZERO) ? true : false)
#define IF_CARRY() ((status & CARRY) ? true : false)
mos6502::Instr mos6502::InstrTable[256];
mos6502::mos6502(BusRead r, BusWrite w, ClockCycle c)
: reset_A(0x00)
, reset_X(0x00)
, reset_Y(0x00)
, reset_sp(0xFD)
, reset_status(CONSTANT)
, irq_line(true)
, nmi_request(false)
, nmi_inhibit(false)
, nmi_line(true)
{
Write = (BusWrite)w;
Read = (BusRead)r;
Cycle = (ClockCycle)c;
static bool initialized = false;
if (initialized) return;
initialized = true;
Instr instr;
// fill jump table with ILLEGALs
instr.addr = &mos6502::Addr_IMP;
instr.saddr = "(null)";
instr.code = &mos6502::Op_ILLEGAL;
instr.scode = "(null)";
instr.penalty = false;
instr.cycles = 0;
for(int i = 0; i < 256; i++)
{
InstrTable[i] = instr;
}
// insert opcodes
#define MAKE_INSTR(HEX, CODE, MODE, CYCLES, PENALTY) \
instr.code = &mos6502::Op_ ## CODE; \
instr.scode = # CODE; \
instr.addr = &mos6502::Addr_ ## MODE; \
instr.saddr = # MODE; \
instr.cycles = CYCLES; \
instr.penalty = PENALTY; \
InstrTable[HEX] = instr;
// ADC
// Add Memory to Accumulator with Carry
//
// A + M + C -> A, C
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// immediate ADC #oper 69 2 2
// zeropage ADC oper 65 2 3
// zeropage,X ADC oper,X 75 2 4
// absolute ADC oper 6D 3 4
// absolute,X ADC oper,X 7D 3 4*
// absolute,Y ADC oper,Y 79 3 4*
// (indirect,X) ADC (oper,X) 61 2 6
// (indirect),Y ADC (oper),Y 71 2 5*
MAKE_INSTR(0x69, ADC, IMM, 2, false);
MAKE_INSTR(0x65, ADC, ZER, 3, false);
MAKE_INSTR(0x75, ADC, ZEX, 4, false);
MAKE_INSTR(0x6D, ADC, ABS, 4, false);
MAKE_INSTR(0x7D, ADC, ABX, 4, true);
MAKE_INSTR(0x79, ADC, ABY, 4, true);
MAKE_INSTR(0x61, ADC, INX, 6, false);
MAKE_INSTR(0x71, ADC, INY, 5, true);
// AND
// AND Memory with Accumulator
//
// A AND M -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate AND #oper 29 2 2
// zeropage AND oper 25 2 3
// zeropage,X AND oper,X 35 2 4
// absolute AND oper 2D 3 4
// absolute,X AND oper,X 3D 3 4*
// absolute,Y AND oper,Y 39 3 4*
// (indirect,X) AND (oper,X) 21 2 6
// (indirect),Y AND (oper),Y 31 2 5*
MAKE_INSTR(0x29, AND, IMM, 2, false);
MAKE_INSTR(0x25, AND, ZER, 3, false);
MAKE_INSTR(0x35, AND, ZEX, 4, false);
MAKE_INSTR(0x2D, AND, ABS, 4, false);
MAKE_INSTR(0x3D, AND, ABX, 4, true);
MAKE_INSTR(0x39, AND, ABY, 4, true);
MAKE_INSTR(0x21, AND, INX, 6, false);
MAKE_INSTR(0x31, AND, INY, 5, true);
// ASL
// Shift Left One Bit (Memory or Accumulator)
//
// C <- [76543210] <- 0
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// accumulator ASL A 0A 1 2
// zeropage ASL oper 06 2 5
// zeropage,X ASL oper,X 16 2 6
// absolute ASL oper 0E 3 6
// absolute,X ASL oper,X 1E 3 7
MAKE_INSTR(0x0A, ASL_ACC, ACC, 2, false);
MAKE_INSTR(0x06, ASL, ZER, 5, false);
MAKE_INSTR(0x16, ASL, ZEX, 6, false);
MAKE_INSTR(0x0E, ASL, ABS, 6, false);
MAKE_INSTR(0x1E, ASL, ABX, 7, false);
// BCC
// Branch on Carry Clear
//
// branch on C = 0
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BCC oper 90 2 2**
MAKE_INSTR(0x90, BCC, REL, 2, true);
// BCS
// Branch on Carry Set
//
// branch on C = 1
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BCS oper B0 2 2**
MAKE_INSTR(0xB0, BCS, REL, 2, true);
// BEQ
// Branch on Result Zero
//
// branch on Z = 1
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BEQ oper F0 2 2**
MAKE_INSTR(0xF0, BEQ, REL, 2, true);
// BIT
// Test Bits in Memory with Accumulator
//
// bits 7 and 6 of operand are transfered to bit 7 and 6 of SR (N,V);
// the zero-flag is set according to the result of the operand AND
// the accumulator (set, if the result is zero, unset otherwise).
// This allows a quick check of a few bits at once without affecting
// any of the registers, other than the status register (SR).
//
// → Further details.
//
// A AND M -> Z, M7 -> N, M6 -> V
// N Z C I D V
// M7 + - - - M6
// addressing assembler opc bytes cycles
// zeropage BIT oper 24 2 3
// absolute BIT oper 2C 3 4
MAKE_INSTR(0x24, BIT, ZER, 3, false);
MAKE_INSTR(0x2C, BIT, ABS, 4, false);
// BMI
// Branch on Result Minus
//
// branch on N = 1
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BMI oper 30 2 2**
MAKE_INSTR(0x30, BMI, REL, 2, true);
// BNE
// Branch on Result not Zero
//
// branch on Z = 0
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BNE oper D0 2 2**
MAKE_INSTR(0xD0, BNE, REL, 2, true);
// BPL
// Branch on Result Plus
//
// branch on N = 0
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BPL oper 10 2 2**
MAKE_INSTR(0x10, BPL, REL, 2, true);
// BRK
// Force Break
//
// BRK initiates a software interrupt similar to a hardware
// interrupt (IRQ). The return address pushed to the stack is
// PC+2, providing an extra byte of spacing for a break mark
// (identifying a reason for the break.)
// The status register will be pushed to the stack with the break
// flag set to 1. However, when retrieved during RTI or by a PLP
// instruction, the break flag will be ignored.
// The interrupt disable flag is not set automatically.
//
// → Further details.
//
// interrupt,
// push PC+2, push SR
// N Z C I D V
// - - - 1 - -
// addressing assembler opc bytes cycles
// implied BRK 00 1 7
MAKE_INSTR(0x00, BRK, IMP, 7, false);
// BVC
// Branch on Overflow Clear
//
// branch on V = 0
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BVC oper 50 2 2**
MAKE_INSTR(0x50, BVC, REL, 2, true);
// BVS
// Branch on Overflow Set
//
// branch on V = 1
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// relative BVS oper 70 2 2**
MAKE_INSTR(0x70, BVS, REL, 2, true);
// CLC
// Clear Carry Flag
//
// 0 -> C
// N Z C I D V
// - - 0 - - -
// addressing assembler opc bytes cycles
// implied CLC 18 1 2
MAKE_INSTR(0x18, CLC, IMP, 2, false);
// CLD
// Clear Decimal Mode
//
// 0 -> D
// N Z C I D V
// - - - - 0 -
// addressing assembler opc bytes cycles
// implied CLD D8 1 2
MAKE_INSTR(0xD8, CLD, IMP, 2, false);
// CLI
// Clear Interrupt Disable Bit
//
// 0 -> I
// N Z C I D V
// - - - 0 - -
// addressing assembler opc bytes cycles
// implied CLI 58 1 2
MAKE_INSTR(0x58, CLI, IMP, 2, false);
// CLV
// Clear Overflow Flag
//
// 0 -> V
// N Z C I D V
// - - - - - 0
// addressing assembler opc bytes cycles
// implied CLV B8 1 2
MAKE_INSTR(0xB8, CLV, IMP, 2, false);
// CMP
// Compare Memory with Accumulator
//
// A - M
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate CMP #oper C9 2 2
// zeropage CMP oper C5 2 3
// zeropage,X CMP oper,X D5 2 4
// absolute CMP oper CD 3 4
// absolute,X CMP oper,X DD 3 4*
// absolute,Y CMP oper,Y D9 3 4*
// (indirect,X) CMP (oper,X) C1 2 6
// (indirect),Y CMP (oper),Y D1 2 5*
MAKE_INSTR(0xC9, CMP, IMM, 2, false);
MAKE_INSTR(0xC5, CMP, ZER, 3, false);
MAKE_INSTR(0xD5, CMP, ZEX, 4, false);
MAKE_INSTR(0xCD, CMP, ABS, 4, false);
MAKE_INSTR(0xDD, CMP, ABX, 4, true);
MAKE_INSTR(0xD9, CMP, ABY, 4, true);
MAKE_INSTR(0xC1, CMP, INX, 6, false);
MAKE_INSTR(0xD1, CMP, INY, 5, true);
// CPX
// Compare Memory and Index X
//
// X - M
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate CPX #oper E0 2 2
// zeropage CPX oper E4 2 3
// absolute CPX oper EC 3 4
MAKE_INSTR(0xE0, CPX, IMM, 2, false);
MAKE_INSTR(0xE4, CPX, ZER, 3, false);
MAKE_INSTR(0xEC, CPX, ABS, 4, false);
// CPY
// Compare Memory and Index Y
//
// Y - M
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate CPY #oper C0 2 2
// zeropage CPY oper C4 2 3
// absolute CPY oper CC 3 4
MAKE_INSTR(0xC0, CPY, IMM, 2, false);
MAKE_INSTR(0xC4, CPY, ZER, 3, false);
MAKE_INSTR(0xCC, CPY, ABS, 4, false);
// DEC
// Decrement Memory by One
//
// M - 1 -> M
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// zeropage DEC oper C6 2 5
// zeropage,X DEC oper,X D6 2 6
// absolute DEC oper CE 3 6
// absolute,X DEC oper,X DE 3 7
MAKE_INSTR(0xC6, DEC, ZER, 5, false);
MAKE_INSTR(0xD6, DEC, ZEX, 6, false);
MAKE_INSTR(0xCE, DEC, ABS, 6, false);
MAKE_INSTR(0xDE, DEC, ABX, 7, false);
// DEX
// Decrement Index X by One
//
// X - 1 -> X
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied DEX CA 1 2
MAKE_INSTR(0xCA, DEX, IMP, 2, false);
// DEY
// Decrement Index Y by One
//
// Y - 1 -> Y
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied DEY 88 1 2
MAKE_INSTR(0x88, DEY, IMP, 2, false);
// EOR
// Exclusive-OR Memory with Accumulator
//
// A EOR M -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate EOR #oper 49 2 2
// zeropage EOR oper 45 2 3
// zeropage,X EOR oper,X 55 2 4
// absolute EOR oper 4D 3 4
// absolute,X EOR oper,X 5D 3 4*
// absolute,Y EOR oper,Y 59 3 4*
// (indirect,X) EOR (oper,X) 41 2 6
// (indirect),Y EOR (oper),Y 51 2 5*
MAKE_INSTR(0x49, EOR, IMM, 2, false);
MAKE_INSTR(0x45, EOR, ZER, 3, false);
MAKE_INSTR(0x55, EOR, ZEX, 4, false);
MAKE_INSTR(0x4D, EOR, ABS, 4, false);
MAKE_INSTR(0x5D, EOR, ABX, 4, true);
MAKE_INSTR(0x59, EOR, ABY, 4, true);
MAKE_INSTR(0x41, EOR, INX, 6, false);
MAKE_INSTR(0x51, EOR, INY, 5, true);
// INC
// Increment Memory by One
//
// M + 1 -> M
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// zeropage INC oper E6 2 5
// zeropage,X INC oper,X F6 2 6
// absolute INC oper EE 3 6
// absolute,X INC oper,X FE 3 7
MAKE_INSTR(0xE6, INC, ZER, 5, false);
MAKE_INSTR(0xF6, INC, ZEX, 6, false);
MAKE_INSTR(0xEE, INC, ABS, 6, false);
MAKE_INSTR(0xFE, INC, ABX, 7, false);
// INX
// Increment Index X by One
//
// X + 1 -> X
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied INX E8 1 2
MAKE_INSTR(0xE8, INX, IMP, 2, false);
// INY
// Increment Index Y by One
//
// Y + 1 -> Y
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied INY C8 1 2
MAKE_INSTR(0xC8, INY, IMP, 2, false);
// JMP
// Jump to New Location
//
// operand 1st byte -> PCL
// operand 2nd byte -> PCH
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute JMP oper 4C 3 3
// indirect JMP (oper) 6C 3 5
MAKE_INSTR(0x4C, JMP, ABS, 3, false);
MAKE_INSTR(0x6C, JMP, ABI, 5, false);
// JSR
// Jump to New Location Saving Return Address
//
// push (PC+2),
// operand 1st byte -> PCL
// operand 2nd byte -> PCH
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute JSR oper 20 3 6
MAKE_INSTR(0x20, JSR, ABS, 6, false);
// LDA
// Load Accumulator with Memory
//
// M -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate LDA #oper A9 2 2
// zeropage LDA oper A5 2 3
// zeropage,X LDA oper,X B5 2 4
// absolute LDA oper AD 3 4
// absolute,X LDA oper,X BD 3 4*
// absolute,Y LDA oper,Y B9 3 4*
// (indirect,X) LDA (oper,X) A1 2 6
// (indirect),Y LDA (oper),Y B1 2 5*
MAKE_INSTR(0xA9, LDA, IMM, 2, false);
MAKE_INSTR(0xA5, LDA, ZER, 3, false);
MAKE_INSTR(0xB5, LDA, ZEX, 4, false);
MAKE_INSTR(0xAD, LDA, ABS, 4, false);
MAKE_INSTR(0xBD, LDA, ABX, 4, true);
MAKE_INSTR(0xB9, LDA, ABY, 4, true);
MAKE_INSTR(0xA1, LDA, INX, 6, false);
MAKE_INSTR(0xB1, LDA, INY, 5, true);
// LDX
// Load Index X with Memory
//
// M -> X
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate LDX #oper A2 2 2
// zeropage LDX oper A6 2 3
// zeropage,Y LDX oper,Y B6 2 4
// absolute LDX oper AE 3 4
// absolute,Y LDX oper,Y BE 3 4*
MAKE_INSTR(0xA2, LDX, IMM, 2, false);
MAKE_INSTR(0xA6, LDX, ZER, 3, false);
MAKE_INSTR(0xB6, LDX, ZEY, 4, false);
MAKE_INSTR(0xAE, LDX, ABS, 4, false);
MAKE_INSTR(0xBE, LDX, ABY, 4, true);
// LDY
// Load Index Y with Memory
//
// M -> Y
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate LDY #oper A0 2 2
// zeropage LDY oper A4 2 3
// zeropage,X LDY oper,X B4 2 4
// absolute LDY oper AC 3 4
// absolute,X LDY oper,X BC 3 4*
MAKE_INSTR(0xA0, LDY, IMM, 2, false);
MAKE_INSTR(0xA4, LDY, ZER, 3, false);
MAKE_INSTR(0xB4, LDY, ZEX, 4, false);
MAKE_INSTR(0xAC, LDY, ABS, 4, false);
MAKE_INSTR(0xBC, LDY, ABX, 4, true);
// LSR
// Shift One Bit Right (Memory or Accumulator)
//
// 0 -> [76543210] -> C
// N Z C I D V
// 0 + + - - -
// addressing assembler opc bytes cycles
// accumulator LSR A 4A 1 2
// zeropage LSR oper 46 2 5
// zeropage,X LSR oper,X 56 2 6
// absolute LSR oper 4E 3 6
// absolute,X LSR oper,X 5E 3 7
MAKE_INSTR(0x4A, LSR_ACC, ACC, 2, false);
MAKE_INSTR(0x46, LSR, ZER, 5, false);
MAKE_INSTR(0x56, LSR, ZEX, 6, false);
MAKE_INSTR(0x4E, LSR, ABS, 6, false);
MAKE_INSTR(0x5E, LSR, ABX, 7, false);
// NOP
// No Operation
//
// ---
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// implied NOP EA 1 2
MAKE_INSTR(0xEA, NOP, IMP, 2, false);
// ORA
// OR Memory with Accumulator
//
// A OR M -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate ORA #oper 09 2 2
// zeropage ORA oper 05 2 3
// zeropage,X ORA oper,X 15 2 4
// absolute ORA oper 0D 3 4
// absolute,X ORA oper,X 1D 3 4*
// absolute,Y ORA oper,Y 19 3 4*
// (indirect,X) ORA (oper,X) 01 2 6
// (indirect),Y ORA (oper),Y 11 2 5*
MAKE_INSTR(0x09, ORA, IMM, 2, false);
MAKE_INSTR(0x05, ORA, ZER, 3, false);
MAKE_INSTR(0x15, ORA, ZEX, 4, false);
MAKE_INSTR(0x0D, ORA, ABS, 4, false);
MAKE_INSTR(0x1D, ORA, ABX, 4, true);
MAKE_INSTR(0x19, ORA, ABY, 4, true);
MAKE_INSTR(0x01, ORA, INX, 6, false);
MAKE_INSTR(0x11, ORA, INY, 5, true);
// PHA
// Push Accumulator on Stack
//
// push A
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// implied PHA 48 1 3
MAKE_INSTR(0x48, PHA, IMP, 3, false);
// PHP
// Push Processor Status on Stack
//
// The status register will be pushed with the break
// flag and bit 5 set to 1.
//
// push SR
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// implied PHP 08 1 3
MAKE_INSTR(0x08, PHP, IMP, 3, false);
// PLA
// Pull Accumulator from Stack
//
// pull A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied PLA 68 1 4
MAKE_INSTR(0x68, PLA, IMP, 4, false);
// PLP
// Pull Processor Status from Stack
//
// The status register will be pulled with the break
// flag and bit 5 ignored.
//
// pull SR
// N Z C I D V
// from stack
// addressing assembler opc bytes cycles
// implied PLP 28 1 4
MAKE_INSTR(0x28, PLP, IMP, 4, false);
// ROL
// Rotate One Bit Left (Memory or Accumulator)
//
// C <- [76543210] <- C
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// accumulator ROL A 2A 1 2
// zeropage ROL oper 26 2 5
// zeropage,X ROL oper,X 36 2 6
// absolute ROL oper 2E 3 6
// absolute,X ROL oper,X 3E 3 7
MAKE_INSTR(0x2A, ROL_ACC, ACC, 2, false);
MAKE_INSTR(0x26, ROL, ZER, 5, false);
MAKE_INSTR(0x36, ROL, ZEX, 6, false);
MAKE_INSTR(0x2E, ROL, ABS, 6, false);
MAKE_INSTR(0x3E, ROL, ABX, 7, false);
// ROR
// Rotate One Bit Right (Memory or Accumulator)
//
// C -> [76543210] -> C
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// accumulator ROR A 6A 1 2
// zeropage ROR oper 66 2 5
// zeropage,X ROR oper,X 76 2 6
// absolute ROR oper 6E 3 6
// absolute,X ROR oper,X 7E 3 7
MAKE_INSTR(0x6A, ROR_ACC, ACC, 2, false);
MAKE_INSTR(0x66, ROR, ZER, 5, false);
MAKE_INSTR(0x76, ROR, ZEX, 6, false);
MAKE_INSTR(0x6E, ROR, ABS, 6, false);
MAKE_INSTR(0x7E, ROR, ABX, 7, false);
// RTI
// Return from Interrupt
//
// The status register is pulled with the break flag
// and bit 5 ignored. Then PC is pulled from the stack.
//
// pull SR, pull PC
// N Z C I D V
// from stack
// addressing assembler opc bytes cycles
// implied RTI 40 1 6
MAKE_INSTR(0x40, RTI, IMP, 6, false);
// RTS
// Return from Subroutine
//
// pull PC, PC+1 -> PC
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// implied RTS 60 1 6
MAKE_INSTR(0x60, RTS, IMP, 6, false);
// SBC
// Subtract Memory from Accumulator with Borrow
//
// A - M - C̅ -> A
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// immediate SBC #oper E9 2 2
// zeropage SBC oper E5 2 3
// zeropage,X SBC oper,X F5 2 4
// absolute SBC oper ED 3 4
// absolute,X SBC oper,X FD 3 4*
// absolute,Y SBC oper,Y F9 3 4*
// (indirect,X) SBC (oper,X) E1 2 6
// (indirect),Y SBC (oper),Y F1 2 5*
MAKE_INSTR(0xE9, SBC, IMM, 2, false);
MAKE_INSTR(0xE5, SBC, ZER, 3, false);
MAKE_INSTR(0xF5, SBC, ZEX, 4, false);
MAKE_INSTR(0xED, SBC, ABS, 4, false);
MAKE_INSTR(0xFD, SBC, ABX, 4, true);
MAKE_INSTR(0xF9, SBC, ABY, 4, true);
MAKE_INSTR(0xE1, SBC, INX, 6, false);
MAKE_INSTR(0xF1, SBC, INY, 5, true);
// SEC
// Set Carry Flag
//
// 1 -> C
// N Z C I D V
// - - 1 - - -
// addressing assembler opc bytes cycles
// implied SEC 38 1 2
MAKE_INSTR(0x38, SEC, IMP, 2, false);
// SED
// Set Decimal Flag
//
// 1 -> D
// N Z C I D V
// - - - - 1 -
// addressing assembler opc bytes cycles
// implied SED F8 1 2
MAKE_INSTR(0xF8, SED, IMP, 2, false);
// SEI
// Set Interrupt Disable Status
//
// 1 -> I
// N Z C I D V
// - - - 1 - -
// addressing assembler opc bytes cycles
// implied SEI 78 1 2
MAKE_INSTR(0x78, SEI, IMP, 2, false);
// STA
// Store Accumulator in Memory
//
// A -> M
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// zeropage STA oper 85 2 3
// zeropage,X STA oper,X 95 2 4
// absolute STA oper 8D 3 4
// absolute,X STA oper,X 9D 3 5
// absolute,Y STA oper,Y 99 3 5
// (indirect,X) STA (oper,X) 81 2 6
// (indirect),Y STA (oper),Y 91 2 6
MAKE_INSTR(0x85, STA, ZER, 3, false);
MAKE_INSTR(0x95, STA, ZEX, 4, false);
MAKE_INSTR(0x8D, STA, ABS, 4, false);
MAKE_INSTR(0x9D, STA, ABX, 5, false);
MAKE_INSTR(0x99, STA, ABY, 5, false);
MAKE_INSTR(0x81, STA, INX, 6, false);
MAKE_INSTR(0x91, STA, INY, 6, false);
// STX
// Store Index X in Memory
//
// X -> M
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// zeropage STX oper 86 2 3
// zeropage,Y STX oper,Y 96 2 4
// absolute STX oper 8E 3 4
MAKE_INSTR(0x86, STX, ZER, 3, false);
MAKE_INSTR(0x96, STX, ZEY, 4, false);
MAKE_INSTR(0x8E, STX, ABS, 4, false);
// STY
// Sore Index Y in Memory
//
// Y -> M
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// zeropage STY oper 84 2 3
// zeropage,X STY oper,X 94 2 4
// absolute STY oper 8C 3 4
MAKE_INSTR(0x84, STY, ZER, 3, false);
MAKE_INSTR(0x94, STY, ZEX, 4, false);
MAKE_INSTR(0x8C, STY, ABS, 4, false);
// TAX
// Transfer Accumulator to Index X
//
// A -> X
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied TAX AA 1 2
MAKE_INSTR(0xAA, TAX, IMP, 2, false);
// TAY
// Transfer Accumulator to Index Y
//
// A -> Y
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied TAY A8 1 2
MAKE_INSTR(0xA8, TAY, IMP, 2, false);
// TSX
// Transfer Stack Pointer to Index X
//
// SP -> X
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied TSX BA 1 2
MAKE_INSTR(0xBA, TSX, IMP, 2, false);
// TXA
// Transfer Index X to Accumulator
//
// X -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied TXA 8A 1 2
MAKE_INSTR(0x8A, TXA, IMP, 2, false);
// TXS
// Transfer Index X to Stack Register
//
// X -> SP
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// implied TXS 9A 1 2
MAKE_INSTR(0x9A, TXS, IMP, 2, false);
// TYA
// Transfer Index Y to Accumulator
//
// Y -> A
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// implied TYA 98 1 2
MAKE_INSTR(0x98, TYA, IMP, 2, false);
#ifdef ILLEGAL_OPCODES
// ALR (ASR)
// AND oper + LSR
//
// A AND oper, 0 -> [76543210] -> C
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate ALR #oper 4B 2 2
MAKE_INSTR(0x4B, ALR, IMM, 2, false);
// ANC
// AND oper + set C as ASL
//
// A AND oper, bit(7) -> C
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate ANC #oper 0B 2 2
MAKE_INSTR(0x0B, ANC, IMM, 2, false);
// ANC (ANC2)
// AND oper + set C as ROL
//
// effectively the same as instr. 0B
//
// A AND oper, bit(7) -> C
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate ANC #oper 2B 2 2
MAKE_INSTR(0x2B, ANC, IMM, 2, false);
// ANE (XAA)
// * OR X + AND oper
//
// Highly unstable, do not use.
//
// A base value in A is determined based on the contets of A and a constant,
// which may be typically $00, $ff, $ee, etc. The value of this constant
// depends on temperature, the chip series, and maybe other factors, as well.
// In order to eliminate these uncertaincies from the equation, use either 0
// as the operand or a value of $FF in the accumulator.
//
// (A OR CONST) AND X AND oper -> A
//
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate ANE #oper 8B 2 2 ††
MAKE_INSTR(0x8B, ANE, IMM, 2, false);
// ARR
// AND oper + ROR
//
// This operation involves the adder:
// V-flag is set according to (A AND oper) + oper
// The carry is not set, but bit 7 (sign) is exchanged with the carry
//
// A AND oper, C -> [76543210] -> C
//
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// immediate ARR #oper 6B 2 2
MAKE_INSTR(0x6B, ARR, IMM, 2, false);
// DCP (DCM)
// DEC oper + CMP oper
//
// M - 1 -> M, A - M
//
// Decrements the operand and then compares the result to the accumulator.
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// zeropage DCP oper C7 2 5
// zeropage,X DCP oper,X D7 2 6
// absolute DCP oper CF 3 6
// absolute,X DCP oper,X DF 3 7
// absolute,Y DCP oper,Y DB 3 7
// (indirect,X) DCP (oper,X) C3 2 8
// (indirect),Y DCP (oper),Y D3 2 8
MAKE_INSTR(0xC7, DCP, ZER, 5, false);
MAKE_INSTR(0xD7, DCP, ZEX, 6, false);
MAKE_INSTR(0xCF, DCP, ABS, 6, false);
MAKE_INSTR(0xDF, DCP, ABX, 7, false);
MAKE_INSTR(0xDB, DCP, ABY, 7, false);
MAKE_INSTR(0xC3, DCP, INX, 8, false);
MAKE_INSTR(0xD3, DCP, INY, 8, false);
// ISC (ISB, INS)
// INC oper + SBC oper
//
// M + 1 -> M, A - M - C̅ -> A
//
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// zeropage ISC oper E7 2 5
// zeropage,X ISC oper,X F7 2 6
// absolute ISC oper EF 3 6
// absolute,X ISC oper,X FF 3 7
// absolute,Y ISC oper,Y FB 3 7
// (indirect,X) ISC (oper,X) E3 2 8
// (indirect),Y ISC (oper),Y F3 2 8
MAKE_INSTR(0xE7, ISC, ZER, 5, false);
MAKE_INSTR(0xF7, ISC, ZEX, 6, false);
MAKE_INSTR(0xEF, ISC, ABS, 6, false);
MAKE_INSTR(0xFF, ISC, ABX, 7, false);
MAKE_INSTR(0xFB, ISC, ABY, 7, false);
MAKE_INSTR(0xE3, ISC, INX, 8, false);
MAKE_INSTR(0xF3, ISC, INY, 8, false);
// LAS (LAR)
// LDA/TSX oper
//
// M AND SP -> A, X, SP
//
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// absolute,Y LAS oper,Y BB 3 4*
MAKE_INSTR(0xBB, LAS, ABY, 4, true);
// LAX
// LDA oper + LDX oper
//
// M -> A -> X
//
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// zeropage LAX oper A7 2 3
// zeropage,Y LAX oper,Y B7 2 4
// absolute LAX oper AF 3 4
// absolute,Y LAX oper,Y BF 3 4*
// (indirect,X) LAX (oper,X) A3 2 6
// (indirect),Y LAX (oper),Y B3 2 5*
MAKE_INSTR(0xA7, LAX, ZER, 3, false);
MAKE_INSTR(0xB7, LAX, ZEY, 4, false);
MAKE_INSTR(0xAF, LAX, ABS, 4, false);
MAKE_INSTR(0xBF, LAX, ABY, 4, true);
MAKE_INSTR(0xA3, LAX, INX, 6, false);
MAKE_INSTR(0xB3, LAX, INY, 5, true);
// LXA (LAX immediate)
// Store * AND oper in A and X
//
// Highly unstable, involves a 'magic' constant, see ANE
//
// (A OR CONST) AND oper -> A -> X
//
// N Z C I D V
// + + - - - -
// addressing assembler opc bytes cycles
// immediate LXA #oper AB 2 2 ††
MAKE_INSTR(0xAB, LXA, IMM, 2, false);
// RLA
// ROL oper + AND oper
//
// M = C <- [76543210] <- C, A AND M -> A
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// zeropage RLA oper 27 2 5
// zeropage,X RLA oper,X 37 2 6
// absolute RLA oper 2F 3 6
// absolute,X RLA oper,X 3F 3 7
// absolute,Y RLA oper,Y 3B 3 7
// (indirect,X) RLA (oper,X) 23 2 8
// (indirect),Y RLA (oper),Y 33 2 8
MAKE_INSTR(0x27, RLA, ZER, 5, false);
MAKE_INSTR(0x37, RLA, ZEX, 6, false);
MAKE_INSTR(0x2F, RLA, ABS, 6, false);
MAKE_INSTR(0x3F, RLA, ABX, 7, false);
MAKE_INSTR(0x3B, RLA, ABY, 7, false);
MAKE_INSTR(0x23, RLA, INX, 8, false);
MAKE_INSTR(0x33, RLA, INY, 8, false);
// RRA
// ROR oper + ADC oper
//
// M = C -> [76543210] -> C, A + M + C -> A, C
//
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// zeropage RRA oper 67 2 5
// zeropage,X RRA oper,X 77 2 6
// absolute RRA oper 6F 3 6
// absolute,X RRA oper,X 7F 3 7
// absolute,Y RRA oper,Y 7B 3 7
// (indirect,X) RRA (oper,X) 63 2 8
// (indirect),Y RRA (oper),Y 73 2 8
MAKE_INSTR(0x67, RRA, ZER, 5, false);
MAKE_INSTR(0x77, RRA, ZEX, 6, false);
MAKE_INSTR(0x6F, RRA, ABS, 6, false);
MAKE_INSTR(0x7F, RRA, ABX, 7, false);
MAKE_INSTR(0x7B, RRA, ABY, 7, false);
MAKE_INSTR(0x63, RRA, INX, 8, false);
MAKE_INSTR(0x73, RRA, INY, 8, false);
// SAX (AXS, AAX)
// A and X are put on the bus at the same time (resulting effectively in an AND operation) and stored in M
//
// A AND X -> M
//
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// zeropage SAX oper 87 2 3
// zeropage,Y SAX oper,Y 97 2 4
// absolute SAX oper 8F 3 4
// (indirect,X) SAX (oper,X) 83 2 6
MAKE_INSTR(0x87, SAX, ZER, 3, false);
MAKE_INSTR(0x97, SAX, ZEY, 4, false);
MAKE_INSTR(0x8F, SAX, ABS, 4, false);
MAKE_INSTR(0x83, SAX, INX, 6, false);
// SBX (AXS, SAX)
// CMP and DEX at once, sets flags like CMP
//
// (A AND X) - oper -> X
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// immediate SBX #oper CB 2 2
MAKE_INSTR(0xCB, SBX, IMM, 2, false);
// SHA (AHX, AXA)
// Stores A AND X AND (high-byte of addr. + 1) at addr.
//
// unstable: sometimes 'AND (H+1)' is dropped, page boundary crossings
// may not work (with the high-byte of the value used as the high-byte of the address)
//
// A AND X AND (H+1) -> M
//
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute,Y SHA oper,Y 9F 3 5 †
// (indirect),Y SHA (oper),Y 93 2 6 †
MAKE_INSTR(0x9F, SHA, ABY, 5, false);
MAKE_INSTR(0x93, SHA, INY, 6, false);
// SHX (A11, SXA, XAS)
// Stores X AND (high-byte of addr. + 1) at addr.
//
// unstable: sometimes 'AND (H+1)' is dropped, page boundary
// crossings may not work (with the high-byte of the value used as the high-byte of the address)
//
// X AND (H+1) -> M
//
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute,Y SHX oper,Y 9E 3 5 †
MAKE_INSTR(0x9E, SHX, ABY, 5, false);
// SHY (A11, SYA, SAY)
// Stores Y AND (high-byte of addr. + 1) at addr.
//
// unstable: sometimes 'AND (H+1)' is dropped, page boundary
// crossings may not work (with the high-byte of the value used as the high-byte of the address)
//
// Y AND (H+1) -> M
//
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute,X SHY oper,X 9C 3 5 †
MAKE_INSTR(0x9C, SHY, ABX, 5, false);
// SLO (ASO)
// ASL oper + ORA oper
//
// M = C <- [76543210] <- 0, A OR M -> A
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// zeropage SLO oper 07 2 5
// zeropage,X SLO oper,X 17 2 6
// absolute SLO oper 0F 3 6
// absolute,X SLO oper,X 1F 3 7
// absolute,Y SLO oper,Y 1B 3 7
// (indirect,X) SLO (oper,X) 03 2 8
// (indirect),Y SLO (oper),Y 13 2 8
MAKE_INSTR(0x07, SLO, ZER, 5, false);
MAKE_INSTR(0x17, SLO, ZEX, 6, false);
MAKE_INSTR(0x0F, SLO, ABS, 6, false);
MAKE_INSTR(0x1F, SLO, ABX, 7, false);
MAKE_INSTR(0x1B, SLO, ABY, 7, false);
MAKE_INSTR(0x03, SLO, INX, 8, false);
MAKE_INSTR(0x13, SLO, INY, 8, false);
// SRE (LSE)
// LSR oper + EOR oper
//
// M = 0 -> [76543210] -> C, A EOR M -> A
//
// N Z C I D V
// + + + - - -
// addressing assembler opc bytes cycles
// zeropage SRE oper 47 2 5
// zeropage,X SRE oper,X 57 2 6
// absolute SRE oper 4F 3 6
// absolute,X SRE oper,X 5F 3 7
// absolute,Y SRE oper,Y 5B 3 7
// (indirect,X) SRE (oper,X) 43 2 8
// (indirect),Y SRE (oper),Y 53 2 8
MAKE_INSTR(0x47, SRE, ZER, 5, false);
MAKE_INSTR(0x57, SRE, ZEX, 6, false);
MAKE_INSTR(0x4F, SRE, ABS, 6, false);
MAKE_INSTR(0x5F, SRE, ABX, 7, false);
MAKE_INSTR(0x5B, SRE, ABY, 7, false);
MAKE_INSTR(0x43, SRE, INX, 8, false);
MAKE_INSTR(0x53, SRE, INY, 8, false);
// TAS (XAS, SHS)
// Puts A AND X in SP and stores A AND X AND (high-byte of addr. + 1) at addr.
//
// unstable: sometimes 'AND (H+1)' is dropped, page boundary
// crossings may not work (with the high-byte of the value used as the high-byte of the address)
//
// A AND X -> SP, A AND X AND (H+1) -> M
//
// N Z C I D V
// - - - - - -
// addressing assembler opc bytes cycles
// absolute,Y TAS oper,Y 9B 3 5 †
MAKE_INSTR(0x9B, TAS, ABY, 5, false);
// USBC (SBC)
// SBC oper + NOP
//
// effectively same as normal SBC immediate, instr. E9.
//
// A - M - C̅ -> A
//
// N Z C I D V
// + + + - - +
// addressing assembler opc bytes cycles
// immediate USBC #oper EB 2 2
MAKE_INSTR(0xEB, SBC, IMM, 2, false);
// NOPs (including DOP, TOP)
// Instructions effecting in 'no operations' in various address modes. Operands are ignored.
//
// N Z C I D V
// - - - - - -
// opc addressing bytes cycles
// 1A implied 1 2
// 3A implied 1 2
// 5A implied 1 2
// 7A implied 1 2
// DA implied 1 2
// FA implied 1 2
MAKE_INSTR(0x1A, NOP, IMP, 2, false);
MAKE_INSTR(0x3A, NOP, IMP, 2, false);
MAKE_INSTR(0x5A, NOP, IMP, 2, false);
MAKE_INSTR(0x7A, NOP, IMP, 2, false);
MAKE_INSTR(0xDA, NOP, IMP, 2, false);
MAKE_INSTR(0xFA, NOP, IMP, 2, false);
// 80 immediate 2 2
// 82 immediate 2 2
// 89 immediate 2 2
// C2 immediate 2 2
// E2 immediate 2 2
MAKE_INSTR(0x80, NOP, IMM, 2, false);
MAKE_INSTR(0x82, NOP, IMM, 2, false);
MAKE_INSTR(0x89, NOP, IMM, 2, false);
MAKE_INSTR(0xC2, NOP, IMM, 2, false);
MAKE_INSTR(0xE2, NOP, IMM, 2, false);
// 04 zeropage 2 3
// 44 zeropage 2 3
// 64 zeropage 2 3
MAKE_INSTR(0x04, NOP, ZER, 3, false);
MAKE_INSTR(0x44, NOP, ZER, 3, false);
MAKE_INSTR(0x64, NOP, ZER, 3, false);
// 14 zeropage,X 2 4
// 34 zeropage,X 2 4
// 54 zeropage,X 2 4
// 74 zeropage,X 2 4
// D4 zeropage,X 2 4
// F4 zeropage,X 2 4
MAKE_INSTR(0x14, NOP, ZEX, 4, false);
MAKE_INSTR(0x34, NOP, ZEX, 4, false);
MAKE_INSTR(0x54, NOP, ZEX, 4, false);
MAKE_INSTR(0x74, NOP, ZEX, 4, false);
MAKE_INSTR(0xD4, NOP, ZEX, 4, false);
MAKE_INSTR(0xF4, NOP, ZEX, 4, false);
// 0C absolute 3 4
MAKE_INSTR(0x0C, NOP, ABS, 4, false);
// 1C absolute,X 3 4*
// 3C absolute,X 3 4*
// 5C absolute,X 3 4*
// 7C absolute,X 3 4*
// DC absolute,X 3 4*
// FC absolute,X 3 4*
MAKE_INSTR(0x1C, NOP, ABX, 4, true);
MAKE_INSTR(0x3C, NOP, ABX, 4, true);
MAKE_INSTR(0x5C, NOP, ABX, 4, true);
MAKE_INSTR(0x7C, NOP, ABX, 4, true);
MAKE_INSTR(0xDC, NOP, ABX, 4, true);
MAKE_INSTR(0xFC, NOP, ABX, 4, true);
// JAM (KIL, HLT)
// These instructions freeze the CPU.
//
// The processor will be trapped infinitely in T1 phase with $FF on the data bus. — Reset required.
//
// Instruction codes: 02, 12, 22, 32, 42, 52, 62, 72, 92, B2, D2, F2
MAKE_INSTR(0x02, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x12, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x22, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x32, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x42, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x52, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x62, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x72, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0x92, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0xB2, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0xD2, ILLEGAL, IMP, 0, false);
MAKE_INSTR(0xF2, ILLEGAL, IMP, 0, false);
#endif
return;
}
uint16_t mos6502::Addr_ACC()
{
return 0; // not used
}
uint16_t mos6502::Addr_IMM()
{
return pc++;
}
uint16_t mos6502::Addr_ABS()
{
uint16_t addrL;
uint16_t addrH;
uint16_t addr;
addrL = Read(pc++);
addrH = Read(pc++);
addr = addrL + (addrH << 8);
return addr;
}
uint16_t mos6502::Addr_ZER()
{
return Read(pc++);
}
uint16_t mos6502::Addr_IMP()
{
return 0; // not used
}
uint16_t mos6502::Addr_REL()
{
uint16_t offset;
uint16_t addr;
offset = (uint16_t)Read(pc++);
if (offset & 0x80) offset |= 0xFF00;
addr = pc + (int16_t)offset;
crossed = (addr & 0xFF00) != (pc & 0xFF00);
return addr;
}
uint16_t mos6502::Addr_ABI()
{
uint16_t addrL;
uint16_t addrH;
uint16_t effL;
uint16_t effH;
uint16_t abs;
uint16_t addr;
addrL = Read(pc++);
addrH = Read(pc++);
abs = (addrH << 8) | addrL;
effL = Read(abs);
#ifndef CMOS_INDIRECT_JMP_FIX
effH = Read((abs & 0xFF00) + ((abs + 1) & 0x00FF) );
#else
effH = Read(abs + 1);
#endif
addr = effL + 0x100 * effH;
return addr;
}
uint16_t mos6502::Addr_ZEX()
{
uint16_t addr = (Read(pc++) + X) & 0xFF;
return addr;
}
uint16_t mos6502::Addr_ZEY()
{
uint16_t addr = (Read(pc++) + Y) & 0xFF;
return addr;
}
uint16_t mos6502::Addr_ABX()
{
uint16_t addr;
uint16_t addrL;
uint16_t addrH;
addrL = Read(pc++);
addrH = Read(pc++);
addr = addrL + (addrH << 8) + X;
crossed = (addrL + X) > 255;
return addr;
}
uint16_t mos6502::Addr_ABY()
{
uint16_t addr;
uint16_t addrL;
uint16_t addrH;
addrL = Read(pc++);
addrH = Read(pc++);
addr = addrL + (addrH << 8) + Y;
crossed = (addrL + Y) > 255;
return addr;
}
uint16_t mos6502::Addr_INX()
{
uint16_t zeroL;
uint16_t zeroH;
uint16_t addr;
zeroL = (Read(pc++) + X) & 0xFF;
zeroH = (zeroL + 1) & 0xFF;
addr = Read(zeroL) + (Read(zeroH) << 8);
return addr;
}
uint16_t mos6502::Addr_INY()
{
uint16_t baseL;
uint16_t zeroL;
uint16_t zeroH;
uint16_t addr;
zeroL = Read(pc++);
zeroH = (zeroL + 1) & 0xFF;
addr = (baseL = /* ASSIGN */ Read(zeroL)) + (Read(zeroH) << 8) + Y;
crossed = (baseL + Y) > 255;
return addr;
}
void mos6502::IRQ(bool line)
{
irq_line = line;
}
void mos6502::NMI(bool line)
{
// falling edge triggered
if (nmi_line == true && line == false) {
if (!nmi_inhibit) {
nmi_request = true;
}
}
nmi_line = line;
}
void mos6502::Reset()
{
// do not set or clear irq_line, that's external to us
// do not set or clear nmi_line, that's external to us
nmi_request = false;
nmi_inhibit = false;
A = reset_A;
Y = reset_Y;
X = reset_X;
// load PC from reset vector
uint8_t pcl = Read(rstVectorL);
uint8_t pch = Read(rstVectorH);
pc = (pch << 8) + pcl;
sp = reset_sp;
status = reset_status | CONSTANT | BREAK;
illegalOpcode = false;
return;
}
void mos6502::StackPush(uint8_t byte)
{
Write(0x0100 + sp, byte);
if(sp == 0x00) sp = 0xFF;
else sp--;
}
uint8_t mos6502::StackPop()
{
if(sp == 0xFF) sp = 0x00;
else sp++;
return Read(0x0100 + sp);
}
void mos6502::Svc_IRQ()
{
//SET_BREAK(0);
StackPush((pc >> 8) & 0xFF);
StackPush(pc & 0xFF);
StackPush((status & ~BREAK) | CONSTANT);
SET_INTERRUPT(1);
// load PC from interrupt request vector
uint8_t pcl = Read(irqVectorL);
uint8_t pch = Read(irqVectorH);
pc = (pch << 8) + pcl;
return;
}
void mos6502::Svc_NMI()
{
//SET_BREAK(0);
StackPush((pc >> 8) & 0xFF);
StackPush(pc & 0xFF);
StackPush((status & ~BREAK) | CONSTANT);
SET_INTERRUPT(1);
// load PC from non-maskable interrupt vector
uint8_t pcl = Read(nmiVectorL);
uint8_t pch = Read(nmiVectorH);
pc = (pch << 8) + pcl;
return;
}
bool mos6502::CheckInterrupts() {
// NMI is edge triggered
if (nmi_request && !nmi_inhibit) {
nmi_request = false;
nmi_inhibit = true;
Svc_NMI();
return true;
}
// check disabled bit
if(!IF_INTERRUPT()) {
// IRQ is level triggered
if (irq_line == false && !nmi_inhibit) {
Svc_IRQ();
return true;
}
}
return false;
}
void mos6502::Run(
int32_t cyclesRemaining,
uint64_t& cycleCount,
CycleMethod cycleMethod)
{
uint8_t opcode;
Instr instr;
while(cyclesRemaining > 0 && !illegalOpcode)
{
if (CheckInterrupts()) {
cycleCount += 6; // TODO FIX verify this is correct
}
// fetch
opcode = Read(pc++);
// decode
instr = InstrTable[opcode];
// execute
Exec(instr);
cycleCount += instr.cycles;
if (branched) {
cycleCount++;
}
if (instr.penalty && crossed) {
cycleCount++;
}
cyclesRemaining -=
cycleMethod == CYCLE_COUNT ? instr.cycles
/* cycleMethod == INST_COUNT */ : 1;
// run clock cycle callback
if (Cycle)
for(int i = 0; i < instr.cycles; i++)
Cycle(this);
}
}
void mos6502::RunEternally()
{
uint8_t opcode;
Instr instr;
while(!illegalOpcode)
{
CheckInterrupts();
// fetch
opcode = Read(pc++);
// decode
instr = InstrTable[opcode];
// execute
Exec(instr);
// run clock cycle callback
if (Cycle)
for(int i = 0; i < instr.cycles; i++)
Cycle(this);
}
}
void mos6502::Exec(Instr i)
{
crossed = false;
branched = false;
uint16_t src = (this->*i.addr)();
(this->*i.code)(src);
}
uint16_t mos6502::GetPC()
{
return pc;
}
uint8_t mos6502::GetS()
{
return sp;
}
uint8_t mos6502::GetP()
{
return status;
}
uint8_t mos6502::GetA()
{
return A;
}
uint8_t mos6502::GetX()
{
return X;
}
uint8_t mos6502::GetY()
{
return Y;
}
void mos6502::SetPC(uint16_t n)
{
pc = n;
}
void mos6502::SetS (uint8_t n)
{
sp = n;
}
void mos6502::SetP (uint8_t n)
{
status = n;
}
void mos6502::SetA (uint8_t n)
{
A = n;
}
void mos6502::SetX (uint8_t n)
{
X = n;
}
void mos6502::SetY (uint8_t n)
{
Y = n;
}
void mos6502::SetResetS(uint8_t value)
{
reset_sp = value;
}
void mos6502::SetResetA(uint8_t value)
{
reset_A = value;
}
void mos6502::SetResetX(uint8_t value)
{
reset_X = value;
}
void mos6502::SetResetY(uint8_t value)
{
reset_Y = value;
}
void mos6502::SetResetP(uint8_t value)
{
reset_status = value | CONSTANT | BREAK;
}
uint8_t mos6502::GetResetS()
{
return reset_sp;
}
uint8_t mos6502::GetResetP()
{
return reset_status;
}
uint8_t mos6502::GetResetA()
{
return reset_A;
}
uint8_t mos6502::GetResetX()
{
return reset_X;
}
uint8_t mos6502::GetResetY()
{
return reset_Y;
}
void mos6502::Op_ILLEGAL(uint16_t src)
{
illegalOpcode = true;
}
void mos6502::Op_ADC(uint16_t src)
{
uint8_t m = Read(src);
unsigned int tmp = m + A + (IF_CARRY() ? 1 : 0);
// N V Z computed *BEFORE* adjustment
SET_OVERFLOW(!((A ^ m) & 0x80) && ((A ^ tmp) & 0x80));
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
if (IF_DECIMAL())
{
// see http://www.6502.org/tutorials/decimal_mode.html
int AL = ((A & 0xF) + (m & 0xF) + (IF_CARRY() ? 1 : 0));
if (AL >= 0xA) {
AL = ((AL + 6) & 0xF) + 0x10;
}
tmp = (m & 0xF0) + (A & 0xF0) + AL;
// N V recomputed
SET_OVERFLOW(!((A ^ m) & 0x80) && ((A ^ tmp) & 0x80));
SET_NEGATIVE(tmp & 0x80);
if (tmp >= 0xA0) tmp += 0x60;
}
// C computed *AFTER* adjustment
SET_CARRY(tmp > 0xFF);
A = tmp & 0xFF;
return;
}
void mos6502::Op_AND(uint16_t src)
{
uint8_t m = Read(src);
uint8_t res = m & A;
SET_NEGATIVE(res & 0x80);
SET_ZERO(!res);
A = res;
return;
}
void mos6502::Op_ASL(uint16_t src)
{
uint8_t m = Read(src);
SET_CARRY(m & 0x80);
m <<= 1;
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Write(src, m);
return;
}
void mos6502::Op_ASL_ACC(uint16_t src)
{
uint8_t m = A;
SET_CARRY(m & 0x80);
m <<= 1;
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
return;
}
void mos6502::Op_BCC(uint16_t src)
{
if (!IF_CARRY())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BCS(uint16_t src)
{
if (IF_CARRY())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BEQ(uint16_t src)
{
if (IF_ZERO())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BIT(uint16_t src)
{
uint8_t m = Read(src);
uint8_t res = m & A;
status = (status & 0x3F) | (uint8_t)(m & 0xC0);
SET_ZERO(!res);
return;
}
void mos6502::Op_BMI(uint16_t src)
{
if (IF_NEGATIVE())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BNE(uint16_t src)
{
if (!IF_ZERO())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BPL(uint16_t src)
{
if (!IF_NEGATIVE())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BRK(uint16_t src)
{
pc++;
StackPush((pc >> 8) & 0xFF);
StackPush(pc & 0xFF);
StackPush(status | CONSTANT | BREAK);
SET_INTERRUPT(1);
pc = (Read(irqVectorH) << 8) + Read(irqVectorL);
return;
}
void mos6502::Op_BVC(uint16_t src)
{
if (!IF_OVERFLOW())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_BVS(uint16_t src)
{
if (IF_OVERFLOW())
{
pc = src;
branched = true; // indicate we did branch
}
else {
crossed = false; // branch not taken does not suffer penalty
}
return;
}
void mos6502::Op_CLC(uint16_t src)
{
SET_CARRY(0);
return;
}
void mos6502::Op_CLD(uint16_t src)
{
SET_DECIMAL(0);
return;
}
void mos6502::Op_CLI(uint16_t src)
{
SET_INTERRUPT(0);
return;
}
void mos6502::Op_CLV(uint16_t src)
{
SET_OVERFLOW(0);
return;
}
void mos6502::Op_CMP(uint16_t src)
{
unsigned int tmp = A - Read(src);
SET_CARRY(tmp < 0x100);
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
return;
}
void mos6502::Op_CPX(uint16_t src)
{
unsigned int tmp = X - Read(src);
SET_CARRY(tmp < 0x100);
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
return;
}
void mos6502::Op_CPY(uint16_t src)
{
unsigned int tmp = Y - Read(src);
SET_CARRY(tmp < 0x100);
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
return;
}
void mos6502::Op_DEC(uint16_t src)
{
uint8_t m = Read(src);
m = (m - 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Write(src, m);
return;
}
void mos6502::Op_DEX(uint16_t src)
{
uint8_t m = X;
m = (m - 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
X = m;
return;
}
void mos6502::Op_DEY(uint16_t src)
{
uint8_t m = Y;
m = (m - 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Y = m;
return;
}
void mos6502::Op_EOR(uint16_t src)
{
uint8_t m = Read(src);
m = A ^ m;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
}
void mos6502::Op_INC(uint16_t src)
{
uint8_t m = Read(src);
m = (m + 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Write(src, m);
}
void mos6502::Op_INX(uint16_t src)
{
uint8_t m = X;
m = (m + 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
X = m;
}
void mos6502::Op_INY(uint16_t src)
{
uint8_t m = Y;
m = (m + 1) & 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Y = m;
}
void mos6502::Op_JMP(uint16_t src)
{
pc = src;
}
void mos6502::Op_JSR(uint16_t src)
{
pc--;
StackPush((pc >> 8) & 0xFF);
StackPush(pc & 0xFF);
// this fixes an obscure problem that only happens when
// the operand and the processor stack are at the same
// place...
src = (src & 0xFF) | (Read(pc) << 8);
pc = src;
}
void mos6502::Op_LDA(uint16_t src)
{
uint8_t m = Read(src);
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
}
void mos6502::Op_LDX(uint16_t src)
{
uint8_t m = Read(src);
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
X = m;
}
void mos6502::Op_LDY(uint16_t src)
{
uint8_t m = Read(src);
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Y = m;
}
void mos6502::Op_LSR(uint16_t src)
{
uint8_t m = Read(src);
SET_CARRY(m & 0x01);
m >>= 1;
SET_NEGATIVE(0);
SET_ZERO(!m);
Write(src, m);
}
void mos6502::Op_LSR_ACC(uint16_t src)
{
uint8_t m = A;
SET_CARRY(m & 0x01);
m >>= 1;
SET_NEGATIVE(0);
SET_ZERO(!m);
A = m;
}
void mos6502::Op_NOP(uint16_t src)
{
return;
}
void mos6502::Op_ORA(uint16_t src)
{
uint8_t m = Read(src);
m = A | m;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
}
void mos6502::Op_PHA(uint16_t src)
{
StackPush(A);
return;
}
void mos6502::Op_PHP(uint16_t src)
{
StackPush(status | CONSTANT | BREAK);
return;
}
void mos6502::Op_PLA(uint16_t src)
{
A = StackPop();
SET_NEGATIVE(A & 0x80);
SET_ZERO(!A);
return;
}
void mos6502::Op_PLP(uint16_t src)
{
status = (status & (CONSTANT | BREAK)) | (StackPop() & ~(CONSTANT | BREAK));
return;
}
void mos6502::Op_ROL(uint16_t src)
{
uint16_t m = Read(src);
m <<= 1;
if (IF_CARRY()) m |= 0x01;
SET_CARRY(m > 0xFF);
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Write(src, m);
return;
}
void mos6502::Op_ROL_ACC(uint16_t src)
{
uint16_t m = A;
m <<= 1;
if (IF_CARRY()) m |= 0x01;
SET_CARRY(m > 0xFF);
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
return;
}
void mos6502::Op_ROR(uint16_t src)
{
uint16_t m = Read(src);
if (IF_CARRY()) m |= 0x100;
SET_CARRY(m & 0x01);
m >>= 1;
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Write(src, m);
return;
}
void mos6502::Op_ROR_ACC(uint16_t src)
{
uint16_t m = A;
if (IF_CARRY()) m |= 0x100;
SET_CARRY(m & 0x01);
m >>= 1;
m &= 0xFF;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
return;
}
void mos6502::Op_RTI(uint16_t src)
{
uint8_t lo, hi;
status = (status & (CONSTANT | BREAK)) | (StackPop() & ~(CONSTANT | BREAK));
lo = StackPop();
hi = StackPop();
pc = (hi << 8) | lo;
nmi_inhibit = false; // always, more efficient than if()
return;
}
void mos6502::Op_RTS(uint16_t src)
{
uint8_t lo, hi;
lo = StackPop();
hi = StackPop();
pc = ((hi << 8) | lo) + 1;
return;
}
void mos6502::Op_SBC(uint16_t src)
{
uint8_t m = Read(src);
int tmp = A - m - (IF_CARRY() ? 0 : 1);
// N V Z computed *BEFORE* adjustment (binary semantics)
SET_OVERFLOW(((A ^ m) & (A ^ tmp) & 0x80) != 0);
SET_NEGATIVE(tmp & 0x80 );
SET_ZERO(!(tmp & 0xFF));
if (IF_DECIMAL())
{
// see http://www.6502.org/tutorials/decimal_mode.html
int AL = (A & 0x0F) - (m & 0x0F) - (IF_CARRY() ? 0 : 1);
if (AL < 0) {
AL = ((AL - 6) & 0x0F) - 0x10;
}
tmp = (A & 0xF0) - (m & 0xF0) + AL;
// N V recompute
SET_OVERFLOW(((A ^ m) & (A ^ tmp) & 0x80) != 0);
SET_NEGATIVE(tmp & 0x80 );
if (tmp < 0) tmp -= 0x60; // ???? huh ????
}
// C computed *AFTER* adjustment
SET_CARRY( tmp >= 0 );
A = tmp & 0xFF;
return;
}
void mos6502::Op_SEC(uint16_t src)
{
SET_CARRY(1);
return;
}
void mos6502::Op_SED(uint16_t src)
{
SET_DECIMAL(1);
return;
}
void mos6502::Op_SEI(uint16_t src)
{
SET_INTERRUPT(1);
return;
}
void mos6502::Op_STA(uint16_t src)
{
Write(src, A);
return;
}
void mos6502::Op_STX(uint16_t src)
{
Write(src, X);
return;
}
void mos6502::Op_STY(uint16_t src)
{
Write(src, Y);
return;
}
void mos6502::Op_TAX(uint16_t src)
{
uint8_t m = A;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
X = m;
return;
}
void mos6502::Op_TAY(uint16_t src)
{
uint8_t m = A;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
Y = m;
return;
}
void mos6502::Op_TSX(uint16_t src)
{
uint8_t m = sp;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
X = m;
return;
}
void mos6502::Op_TXA(uint16_t src)
{
uint8_t m = X;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
return;
}
void mos6502::Op_TXS(uint16_t src)
{
sp = X;
return;
}
void mos6502::Op_TYA(uint16_t src)
{
uint8_t m = Y;
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = m;
return;
}
#ifdef ILLEGAL_OPCODES
void mos6502::Op_ALR(uint16_t src)
{
uint8_t m = Read(src);
uint8_t res = m & A;
SET_CARRY(res & 1);
res >>= 1;
SET_NEGATIVE(res & 0x80);
SET_ZERO(!res);
A = res;
return;
}
void mos6502::Op_ANC(uint16_t src)
{
uint8_t m = Read(src);
uint8_t res = m & A;
SET_CARRY(res & 0x80);
SET_NEGATIVE(res & 0x80);
SET_ZERO(!res);
A = res;
return;
}
void mos6502::Op_ANE(uint16_t src)
{
// A base value in A is determined based on the contets of A and a constant,
// which may be typically $00, $ff, $ee, etc. The value of this constant
// depends on temperature, the chip series, and maybe other factors, as well.
const uint8_t constant = 0xee;
uint8_t m = Read(src);
uint8_t res = ((A | constant) & X & m);
SET_NEGATIVE(res & 0x80);
SET_ZERO(!res);
A = res;
return;
}
void mos6502::Op_ARR(uint16_t src)
{
bool carry = IF_CARRY();
uint8_t m = Read(src);
uint8_t res = m & A;
res >>= 1;
if (carry) {
res |= 0x80;
}
SET_CARRY((res >> 6) & 1);
SET_OVERFLOW(((res >> 6) ^ (res >> 5)) & 1);
SET_NEGATIVE(res & 0x80);
SET_ZERO(!res);
if (IF_DECIMAL())
{
// ARR in decimal mode routes signals through the ALU’s decimal
// adder path, but with no valid carry-in, so the outputs are
// garbage. It’s not emulatable in a meaningful way.
if ((res & 0xF) >= 0xA) {
res += 6;
}
if (res >= 0xA0) {
res += 0x60;
}
}
A = res;
return;
}
void mos6502::Op_DCP(uint16_t src)
{
uint8_t m = Read(src);
m = (m - 1) & 0xFF;
Write(src, m);
unsigned int tmp = A - m;
SET_CARRY(tmp < 0x100);
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
return;
}
void mos6502::Op_ISC(uint16_t src)
{
uint8_t m = Read(src);
// from Op_INC
m = (m + 1) & 0xFF;
Write(src, m);
// from here on is Op_SBC
int tmp = A - m - (IF_CARRY() ? 0 : 1);
// N V Z computed *BEFORE* adjustment (binary semantics)
SET_OVERFLOW(((A ^ m) & (A ^ tmp) & 0x80) != 0);
SET_NEGATIVE(tmp & 0x80 );
SET_ZERO(!(tmp & 0xFF));
if (IF_DECIMAL())
{
// see http://www.6502.org/tutorials/decimal_mode.html
int AL = (A & 0x0F) - (m & 0x0F) - (IF_CARRY() ? 0 : 1);
if (AL < 0) {
AL = ((AL - 6) & 0x0F) - 0x10;
}
tmp = (A & 0xF0) - (m & 0xF0) + AL;
// N V recompute
SET_OVERFLOW(((A ^ m) & (A ^ tmp) & 0x80) != 0);
SET_NEGATIVE(tmp & 0x80 );
if (tmp < 0) tmp -= 0x60; // ???? huh ????
}
// C computed *AFTER* adjustment
SET_CARRY( tmp >= 0 );
A = tmp & 0xFF;
return;
}
void mos6502::Op_LAS(uint16_t src)
{
uint8_t tmp = Read(src);
tmp &= sp;
A = X = sp = tmp;
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(tmp == 0);
return;
}
void mos6502::Op_LAX(uint16_t src)
{
uint8_t m = Read(src);
SET_NEGATIVE(m & 0x80);
SET_ZERO(!m);
A = X = m;
}
void mos6502::Op_LXA(uint16_t src)
{
uint8_t m = Read(src);
A = (A | 0xee) & m; // like ANE, unstable mystery constant
X = A;
SET_NEGATIVE(A & 0x80);
SET_ZERO(!A);
}
void mos6502::Op_RLA(uint16_t src)
{
uint16_t m = Read(src);
m <<= 1;
if (IF_CARRY()) m |= 0x01;
SET_CARRY(m > 0xFF);
m &= 0xFF;
Write(src, m);
A &= m;
SET_NEGATIVE(A & 0x80);
SET_ZERO(!A);
return;
}
void mos6502::Op_RRA(uint16_t src)
{
uint16_t m = Read(src);
if (IF_CARRY()) {
m |= 0x100;
}
SET_CARRY(m & 0x01);
m >>= 1;
m &= 0xFF;
Write(src, m);
// TODO FIX, the rest of this is just Op_ADC
// combine common code into a helper function
unsigned int tmp = m + A + (IF_CARRY() ? 1 : 0);
// N V Z computed *BEFORE* adjustment
SET_OVERFLOW(!((A ^ m) & 0x80) && ((A ^ tmp) & 0x80));
SET_NEGATIVE(tmp & 0x80);
SET_ZERO(!(tmp & 0xFF));
if (IF_DECIMAL())
{
// see http://www.6502.org/tutorials/decimal_mode.html
int AL = ((A & 0xF) + (m & 0xF) + (IF_CARRY() ? 1 : 0));
if (AL >= 0xA) {
AL = ((AL + 6) & 0xF) + 0x10;
}
tmp = (m & 0xF0) + (A & 0xF0) + AL;
// N V recomputed
SET_OVERFLOW(!((A ^ m) & 0x80) && ((A ^ tmp) & 0x80));
SET_NEGATIVE(tmp & 0x80);
if (tmp >= 0xA0) tmp += 0x60;
}
// C computed *AFTER* adjustment
SET_CARRY(tmp > 0xFF);
A = tmp & 0xFF;
return;
}
void mos6502::Op_SAX(uint16_t src)
{
Write(src, A & X); // most simple illegal here
}
void mos6502::Op_SBX(uint16_t src)
{
uint16_t m = Read(src);
uint8_t tmp = A & X;
SET_CARRY(tmp >= m);
SET_ZERO(tmp == m);
X = tmp - m;
SET_NEGATIVE(X & 0x80);
}
void mos6502::Op_SHA(uint16_t src)
{
// unstable, but this is the stable behavior
uint8_t tmp = A & X & ((src >> 8) + 1);
Write(src, tmp);
}
void mos6502::Op_SHX(uint16_t src)
{
// unstable, but this is the stable behavior
uint8_t tmp = X & ((src >> 8) + 1);
Write(src, tmp);
}
void mos6502::Op_SHY(uint16_t src)
{
// unstable, but this is the stable behavior
uint8_t tmp = Y & ((src >> 8) + 1);
Write(src, tmp);
}
void mos6502::Op_SLO(uint16_t src)
{
uint8_t m = Read(src);
SET_CARRY(m & 0x80);
m <<= 1;
m &= 0xFF;
Write(src, m);
A |= m;
SET_NEGATIVE(A & 0x80);
SET_ZERO(!A);
return;
}
void mos6502::Op_SRE(uint16_t src)
{
uint8_t m = Read(src);
SET_CARRY(m & 0x01);
m >>= 1;
Write(src, m);
A ^= m;
SET_NEGATIVE(A & 0x80);
SET_ZERO(!A);
return;
}
void mos6502::Op_TAS(uint16_t src)
{
// unstable, but this is the stable behavior
sp = A & X;
uint8_t tmp = A & X & ((src >> 8) + 1);
Write(src, tmp);
}
#endif
================================================
FILE: mos6502.h
================================================
//============================================================================
// Name : mos6502
// Author : Gianluca Ghettini
// Version : 1.0
// Copyright :
// Description : A MOS 6502 CPU emulator written in C++
//============================================================================
#pragma once
#include <stdint.h>
#include <stdbool.h>
class mos6502
{
private:
// register reset values
uint8_t reset_A;
uint8_t reset_X;
uint8_t reset_Y;
uint8_t reset_sp;
uint8_t reset_status;
// registers
uint8_t A; // accumulator
uint8_t X; // X-index
uint8_t Y; // Y-index
// stack pointer
uint8_t sp;
// program counter
uint16_t pc;
// status register
uint8_t status;
typedef void (mos6502::*CodeExec)(uint16_t);
typedef uint16_t (mos6502::*AddrExec)();
struct Instr
{
AddrExec addr;
const char * saddr;
CodeExec code;
const char * scode;
uint8_t cycles;
bool penalty;
};
static Instr InstrTable[256];
void Exec(Instr i);
bool illegalOpcode;
bool crossed;
bool branched;
bool irq_line; // current state of the line
bool nmi_request; // is there an NMI pending?
bool nmi_inhibit; // are we currently handling an NMI?
bool nmi_line; // current state of the NMI line
bool CheckInterrupts();
// addressing modes
uint16_t Addr_ACC(); // ACCUMULATOR
uint16_t Addr_IMM(); // IMMEDIATE
uint16_t Addr_ABS(); // ABSOLUTE
uint16_t Addr_ZER(); // ZERO PAGE
uint16_t Addr_ZEX(); // INDEXED-X ZERO PAGE
uint16_t Addr_ZEY(); // INDEXED-Y ZERO PAGE
uint16_t Addr_ABX(); // INDEXED-X ABSOLUTE
uint16_t Addr_ABY(); // INDEXED-Y ABSOLUTE
uint16_t Addr_IMP(); // IMPLIED
uint16_t Addr_REL(); // RELATIVE
uint16_t Addr_INX(); // INDEXED-X INDIRECT
uint16_t Addr_INY(); // INDEXED-Y INDIRECT
uint16_t Addr_ABI(); // ABSOLUTE INDIRECT
// opcodes (grouped as per datasheet)
void Op_ADC(uint16_t src);
void Op_AND(uint16_t src);
void Op_ASL(uint16_t src); void Op_ASL_ACC(uint16_t src);
void Op_BCC(uint16_t src);
void Op_BCS(uint16_t src);
void Op_BEQ(uint16_t src);
void Op_BIT(uint16_t src);
void Op_BMI(uint16_t src);
void Op_BNE(uint16_t src);
void Op_BPL(uint16_t src);
void Op_BRK(uint16_t src);
void Op_BVC(uint16_t src);
void Op_BVS(uint16_t src);
void Op_CLC(uint16_t src);
void Op_CLD(uint16_t src);
void Op_CLI(uint16_t src);
void Op_CLV(uint16_t src);
void Op_CMP(uint16_t src);
void Op_CPX(uint16_t src);
void Op_CPY(uint16_t src);
void Op_DEC(uint16_t src);
void Op_DEX(uint16_t src);
void Op_DEY(uint16_t src);
void Op_EOR(uint16_t src);
void Op_INC(uint16_t src);
void Op_INX(uint16_t src);
void Op_INY(uint16_t src);
void Op_JMP(uint16_t src);
void Op_JSR(uint16_t src);
void Op_LDA(uint16_t src);
void Op_LDX(uint16_t src);
void Op_LDY(uint16_t src);
void Op_LSR(uint16_t src); void Op_LSR_ACC(uint16_t src);
void Op_NOP(uint16_t src);
void Op_ORA(uint16_t src);
void Op_PHA(uint16_t src);
void Op_PHP(uint16_t src);
void Op_PLA(uint16_t src);
void Op_PLP(uint16_t src);
void Op_ROL(uint16_t src); void Op_ROL_ACC(uint16_t src);
void Op_ROR(uint16_t src); void Op_ROR_ACC(uint16_t src);
void Op_RTI(uint16_t src);
void Op_RTS(uint16_t src);
void Op_SBC(uint16_t src);
void Op_SEC(uint16_t src);
void Op_SED(uint16_t src);
void Op_SEI(uint16_t src);
void Op_STA(uint16_t src);
void Op_STX(uint16_t src);
void Op_STY(uint16_t src);
void Op_TAX(uint16_t src);
void Op_TAY(uint16_t src);
void Op_TSX(uint16_t src);
void Op_TXA(uint16_t src);
void Op_TXS(uint16_t src);
void Op_TYA(uint16_t src);
#ifdef ILLEGAL_OPCODES
void Op_ALR(uint16_t src);
void Op_ANC(uint16_t src);
void Op_ANE(uint16_t src);
void Op_ARR(uint16_t src);
void Op_DCP(uint16_t src);
void Op_ISC(uint16_t src);
void Op_LAS(uint16_t src);
void Op_LAX(uint16_t src);
void Op_LXA(uint16_t src);
void Op_RLA(uint16_t src);
void Op_RRA(uint16_t src);
void Op_SAX(uint16_t src);
void Op_SBX(uint16_t src);
void Op_SHA(uint16_t src);
void Op_SHX(uint16_t src);
void Op_SHY(uint16_t src);
void Op_SLO(uint16_t src);
void Op_SRE(uint16_t src);
void Op_TAS(uint16_t src);
#endif
void Op_ILLEGAL(uint16_t src);
void Svc_NMI();
void Svc_IRQ();
// IRQ, reset, NMI vectors
static const uint16_t irqVectorH = 0xFFFF;
static const uint16_t irqVectorL = 0xFFFE;
static const uint16_t rstVectorH = 0xFFFD;
static const uint16_t rstVectorL = 0xFFFC;
static const uint16_t nmiVectorH = 0xFFFB;
static const uint16_t nmiVectorL = 0xFFFA;
// read/write/clock-cycle callbacks
typedef void (*BusWrite)(uint16_t, uint8_t);
typedef uint8_t (*BusRead)(uint16_t);
typedef void (*ClockCycle)(mos6502*);
BusRead Read;
BusWrite Write;
ClockCycle Cycle;
// stack operations
inline void StackPush(uint8_t byte);
inline uint8_t StackPop();
public:
enum CycleMethod {
INST_COUNT,
CYCLE_COUNT,
};
mos6502(BusRead r, BusWrite w, ClockCycle c = nullptr);
// set or clear the NMI line. this is an input to the processor.
// a high to low edge transition will trigger an interrupt.
// line state is NOT cleared by Reset()
void NMI(bool line);
// set or clear the IRQ line. this is an input to the processor.
// a low level will trigger an interrupt.
// line state is NOT cleared by Reset()
void IRQ(bool line);
void Reset();
void Run(
int32_t cycles,
uint64_t& cycleCount,
CycleMethod cycleMethod = CYCLE_COUNT);
void RunEternally(); // until it encounters a illegal opcode
// useful when running e.g. WOZ Monitor
// no need to worry about cycle exhaus-
// tion
// Various getter/setters
uint16_t GetPC();
uint8_t GetS();
uint8_t GetP();
uint8_t GetA();
uint8_t GetX();
uint8_t GetY();
void SetPC(uint16_t n);
void SetS(uint8_t n);
void SetP(uint8_t n);
void SetA(uint8_t n);
void SetX(uint8_t n);
void SetY(uint8_t n);
void SetResetS(uint8_t value);
void SetResetP(uint8_t value);
void SetResetA(uint8_t value);
void SetResetX(uint8_t value);
void SetResetY(uint8_t value);
uint8_t GetResetS();
uint8_t GetResetP();
uint8_t GetResetA();
uint8_t GetResetX();
uint8_t GetResetY();
};
================================================
FILE: tests/Makefile
================================================
all:
( cd functional && make )
( cd singlestep && make )
@echo ===============================
@echo === ALL TESTS COMPLETE: success
@echo ===============================
================================================
FILE: tests/functional/.gitignore
================================================
*.hex
*.lst
*.a65
main
6502_65C02_functional_tests
================================================
FILE: tests/functional/Makefile
================================================
# Makefile to run unit tests
# Fail early if required tools are missing
ifeq (, $(shell which git 2>/dev/null))
$(error "Error: git not found in PATH")
endif
ifeq (, $(shell which dosbox 2>/dev/null))
$(error "Error: dosbox not found in PATH")
endif
SHELL := /bin/bash
.SHELLFLAGS := -e -o pipefail -c
BASE := 6502_65C02_functional_tests
all: $(BASE) $(BASE)/as65_142 main tests tests
@echo ======================================
@echo === FUNCTIONAL TESTS COMPLETE: success
@echo ======================================
clean:
rm -rf $(BASE) ft.* dt.* it.*
$(BASE):
@test ! -e "$@" || { echo "do NOT use 'make -B', use 'make clean ; make' instead"; exit 1; }
@echo "Fetching functional tests from GitHub..."
git clone https://github.com/Klaus2m5/6502_65C02_functional_tests
$(BASE)/as65_142:
echo "Unpacking assembler..."
mkdir -p $(BASE)/as65_142
( cd $(BASE)/as65_142 && unzip ../as65_142.zip )
main: main.cpp ../../mos6502.cpp ../../mos6502.h
g++ -Wall -O3 -o main ../../mos6502.cpp main.cpp
tests: 6502_functional_test 6502_decimal_test 6502_interrupt_test
ft.hex:
cp $(BASE)/6502_functional_test.a65 ft.a65
./as65.sh ft.a65
6502_functional_test: main ft.hex
@echo "================ Running $@"
./main ft.hex 0x400 0x3469 quiet # magic numbers come from comments and examination of *.lst
dt.hex:
cp $(BASE)/6502_decimal_test.a65 dt.a65
patch -p0 < decimal.patch
./as65.sh dt.a65
6502_decimal_test: main dt.hex
@echo "================ Running $@"
./main dt.hex 0x200 ?0x000b quiet # magic numbers come from comments and examination of *.lst
it.hex:
cp $(BASE)/6502_interrupt_test.a65 it.a65
./as65.sh it.a65
6502_interrupt_test: main it.hex
@echo "================ Running $@"
./main it.hex 0x400 0x06f5 quiet # magic numbers come from comments and examination of *.lst
================================================
FILE: tests/functional/as65.sh
================================================
#!/bin/bash
set -euo pipefail
src_file="$1"
src_dir=$(dirname "$(realpath "$src_file")")
base_name=$(basename "${src_file%.*}")
dest_dir="6502_65C02_functional_tests/as65_142"
# Sanity checks
[[ -f "$src_file" ]] || { echo "Error: $src_file not found"; exit 1; }
[[ -d "$dest_dir" ]] || { echo "Error: $dest_dir not found"; exit 1; }
# ---- Collision check (case-insensitive) ----
shopt -s nullglob
declare -a collisions=()
check_fn_lc="$(basename "$src_file")"
check_fn_lc="${check_fn_lc,,}" # full filename (with extension), lowercased
check_base_lc="${base_name,,}" # basename (no extension), lowercased
for f in "$dest_dir"/*; do
[[ -f "$f" ]] || continue
bn="$(basename "$f")"
bn_lc="${bn,,}"
name="${bn%.*}"
name_lc="${name,,}"
if [[ "$bn_lc" == "$check_fn_lc" || "$name_lc" == "$check_base_lc" ]]; then
collisions+=("$bn")
fi
done
shopt -u nullglob
if (( ${#collisions[@]} > 0 )); then
echo "Error: collision(s) in '$dest_dir' (case-insensitive): ${collisions[*]}" >&2
echo "Refusing to copy to avoid overwriting existing files." >&2
exit 1
fi
echo "Copying $src_file to $dest_dir"
cp "$src_file" "$dest_dir/" || { echo "Copy failed"; exit 1; }
(
cd "$(dirname "$dest_dir")"
echo "Running assembler under DOSBox..."
dosbox \
-c "config -set core=dynamic" \
-c "config -set cycles=max" \
-c "config -set cycleup=100000" \
-c "config -set cycledown=100000" \
-c "config -set frameskip=5" \
-c "config -set output=surface" \
-c "mount c $(pwd)" \
-c "c:" \
-c "cd as65_142" \
-c "AS65-DOS.EXE -x1 -o${base_name}.hex -l -m -s2 -w -h0 -c -i -t -u -z $(basename "$src_file")" \
-c "exit" # change that "exit" to a "pause" if you need to debug
) || { echo "Assembler failed"; exit 1; }
# ---- Artifact move block ----
echo "Moving artifacts back to $src_dir with normalized names..."
shopt -s nullglob
found=0
src_base_lc="${base_name,,}"
for f in "$dest_dir"/*; do
[[ -f "$f" ]] || continue
fn="$(basename "$f")"
name="${fn%.*}" # basename without extension
ext="${fn##*.}" # extension
name_lc="${name,,}" # lowercased basename
if [[ "$name_lc" == "$src_base_lc" ]]; then
echo "Moving $f → $src_dir/${base_name}.${ext,,}"
mv "$f" "$src_dir/${base_name}.${ext,,}" || { echo "Failed to move $fn"; exit 1; }
((found++)) || true # prevent 'set -e' from exiting since found++ returns old value (non-zero on later iterations)
fi
done
shopt -u nullglob
(( found > 0 )) || { echo "No artifacts for '$base_name' found in $dest_dir" >&2; exit 1; }
echo "Success: moved $found artifact(s) to $src_dir"
================================================
FILE: tests/functional/decimal.patch
================================================
--- dt.a65 2025-10-12 17:08:27.391206657 -0700
+++ dt.a65 2025-10-12 17:10:37.155949816 -0700
@@ -34,7 +34,7 @@ chk_z = 0 ; check zero flag
chk_c = 1 ; check carry flag
end_of_test macro
- db $db ;execute 65C02 stop instruction
+ jmp * ; 6502 has no stop instruction
endm
bss
================================================
FILE: tests/functional/main.cpp
================================================
// compile with "g++ main.cpp ../../mos6502.cpp -o main"
#include "../../mos6502.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
bool quiet = false;
uint8_t ram[65536] = {0};
mos6502 *cpu = NULL;
int start = -1;
int success = -1;
int retaddr = -1;
void writeRam(uint16_t addr, uint8_t val)
{
ram[addr] = val;
// feedback
if (addr == 0xbffc) {
// things are inverted here
cpu->IRQ((val & 1) ? false : true);
cpu->NMI((val & 2) ? false : true);
}
}
uint8_t readRam(uint16_t addr)
{
return ram[addr];
}
void tick(mos6502*)
{
static uint16_t lastpc = 0xFFFF;
static int count = 0;
uint16_t pc = cpu->GetPC();
if (pc != lastpc) {
if (!quiet) {
printf("PC=%04x\r", pc);
}
}
if (pc == success) {
printf("\nsuccess\n");
exit(0);
}
if (pc == lastpc) {
count++;
if (count > 100) {
if (retaddr != -1) {
if (ram[retaddr]) {
printf("\ncode %02X\n", ram[retaddr]);
printf("Y=%02x\n", cpu->GetY());
printf("N1=%02x N2=%02x\n", ram[0], ram[1]);
printf("HA=%02x HNVZC=%02x\n", ram[2], ram[3]);
printf("DA=%02x DNVZC=%02x\n", ram[4], ram[5]);
printf("AR=%02x NF=%02x VF=%02x ZF=%02x CF=%02x\n", ram[6], ram[7], ram[8], ram[9],
ram[10]);
printf("FAIL\n");
exit(-1);
}
else {
printf("\nsuccess\n");
exit(0);
}
}
else {
printf("\nFAIL\n");
exit(-1);
}
}
}
else {
count = 0;
}
lastpc = pc;
}
void bail(const char *s)
{
fprintf(stderr, "%s\n", s);
exit(-1);
}
uint32_t fetch(const char *s, uint16_t offset, uint8_t count)
{
uint32_t ret = 0;
uint32_t val = 0;
for (int i = 0; i < count; i++) {
ret <<= 4;
if (s[offset + i] <= '9') {
val = s[offset + i] - '0';
}
else if (s[offset + i] <= 'F') {
val = s[offset + i] - 'A' + 10;
}
else if (s[offset + i] <= 'f') {
val = s[offset + i] - 'a' + 10;
}
ret |= val;
}
return ret;
}
void handle_hex(const char *fname)
{
char buf[1024];
FILE *f = fopen(fname, "r");
if (!f) {
bail("could not open hex file");
}
while (NULL != fgets(buf, sizeof(buf), f)) {
if (buf[0] != ':') {
bail("unexpected start code in hex file");
}
// TODO FIX verify checksum for line here!
int length = fetch(buf, 1, 2);
int address = fetch(buf, 3, 4);
int record = fetch(buf, 7, 2);
if (record == 0x00) {
for (int i = 0; i < length; i++) {
ram[address + i] = fetch(buf, 9 + 2 * i, 2);
}
}
else if (record == 0x01) {
// do nothing
}
else {
bail("unexpected record type in hex file");
}
}
fclose(f);
}
int main(int argc, char **argv) {
if (argc != 4 && argc != 5) {
fprintf(stderr, "Usage: %s <file>.hex <start> <success> [quiet]\n", argv[0]);
return -1;
}
if (argc == 5 && !strcmp(argv[4], "quiet")) {
quiet = true;
}
handle_hex(argv[1]);
start = strtoul(argv[2], NULL, 0);
if (argv[3][0] == '?') {
retaddr = strtoul(argv[3]+1, NULL, 0);
}
else {
success = strtoul(argv[3], NULL, 0);
}
printf("start=%04X\n", start);
ram[0xFFFC] = start & 0xFF;
ram[0xFFFD] = start >> 8;
cpu = new mos6502(readRam, writeRam, tick);
cpu->Reset();
cpu->RunEternally();
return 0;
}
================================================
FILE: tests/functional/notes.txt
================================================
assembler originally came from https://www.kingswood-consulting.co.uk/assemblers/
author does NOT distribute source code.
================================================
FILE: tests/functional/readme.md
================================================
just type "make"
================================================
FILE: tests/singlestep/.gitignore
================================================
65x02
main
================================================
FILE: tests/singlestep/Makefile
================================================
# Makefile to run unit tests
# Fail early if required tools are missing
ifeq (, $(shell which git 2>/dev/null))
$(error "Error: git not found in PATH")
endif
SHELL := /bin/bash
.SHELLFLAGS := -e -o pipefail -c
BASE := 65x02
all: $(BASE) main tests
@echo TEST COMPLETE: success
clean:
rm -rf $(BASE) ft.* dt.* it.*
$(BASE):
@test ! -e "$@" || { echo "do NOT use 'make -B', use 'make clean ; make' instead"; exit 1; }
@echo "Fetching functional tests from GitHub..."
git clone https://github.com/SingleStepTests/65x02.git
main: main.cpp ../../mos6502.cpp ../../mos6502.h
g++ -O3 -Wall -o main -DILLEGAL_OPCODES ../../mos6502.cpp main.cpp
tests: main
for f in 65x02/6502/v1/*.json; do echo == "$$f" ; ./main "$$f"; done
@echo ======================================
@echo === SINGLESTEP TESTS COMPLETE: success
@echo ======================================
================================================
FILE: tests/singlestep/main.cpp
================================================
// compile with "g++ main.cpp ../../mos6502.cpp -o main"
#include "../../mos6502.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#define NAME "\"name\": \""
#define CYCLES "\"cycles\": ["
#define INITIAL "\"initial\": {"
#define FINAL "\"final\": {"
#define PC "\"pc\": "
#define S "\"s\": "
#define A "\"a\": "
#define X "\"x\": "
#define Y "\"y\": "
#define P "\"p\": "
#define RAM "\"ram\": ["
#define PMASK (~0x00)
static const char *unstable = "\x6b\x93\x9b\x9c\x9e\x9f";
bool quiet = false;
uint8_t ram[65536] = {0};
mos6502 *cpu = NULL;
int linenum;
uint64_t cycles;
uint64_t actual_cycles;
int failures = 0;
bool is_unstable = false;
const char *name = NULL;
char linebuf[4096];
char hexbuf[4096];
void writeRam(uint16_t addr, uint8_t val)
{
ram[addr] = val;
}
uint8_t readRam(uint16_t addr)
{
return ram[addr];
}
void tick(mos6502*)
{
}
void translate(void)
{
char *p = linebuf;
char *q = hexbuf;
if (!strchr(p, 'n')) {
strcpy(q, p);
return;
}
while (*p != ',') {
*q++ = *p++;
}
bool valid = false;
int n = 0;
while (*p) {
if (*p == ' ' && !strncmp(p+1, INITIAL, strlen(INITIAL))) {
*q++ = '\n';
p++;
}
else if (*p == ' ' && !strncmp(p+1, FINAL, strlen(FINAL))) {
*q++ = '\n';
p++;
}
else if (*p == ' ' && !strncmp(p+1, CYCLES, strlen(CYCLES))) {
*q++ = '\n';
p++;
}
else if (*p == ' ' && !strncmp(p+1, RAM, strlen(RAM))) {
*q++ = '\n';
p++;
}
else if (*p >= '0' && *p <= '9') {
n = n * 10 + (*p - '0');
valid = true;
p++;
}
else {
if (valid) {
if (n < 256) {
sprintf(q, "%02x", n);
q += 2;
}
else {
sprintf(q, "%04x", n);
q += 4;
}
n = 0;
valid = false;
}
*q++ = *p++;
}
}
*q = 0;
}
bool failed = false;
void reset_fail(void) {
failed = false;
}
void fail(const char *s)
{
if (!failed) {
failed = true;
fprintf(stderr, "NAME: %s\n", name);
fprintf(stderr, "%s\n", hexbuf);
}
fprintf(stderr, "%s\n", s);
failures++;
}
void bail(const char *s)
{
fprintf(stderr, "%s\n", s);
exit(-1);
}
const char *locate(const char *haystack, const char *needle, const char *name)
{
const char *ret = strstr(haystack, needle);
if (ret) {
ret += strlen(needle);
return ret;
}
else {
char buf[1024];
sprintf(buf, "cannot find %s at line %d", name, linenum);
bail(buf);
}
return NULL;
}
void handle_name(char *copy)
{
char *p = (char *) locate(copy, NAME, "NAME");
char *q = strchr(p, '\"');
if (q) {
*q = 0;
//printf("NAME: %s\n", p);
if (name) {
free((void *)name);
}
name = strdup(p);
}
else {
char buf[1024];
sprintf(buf, "cannot parse NAME at line %d", linenum);
bail(buf);
}
free(copy);
}
void handle_cycles(char *copy)
{
char *p = (char *) locate(copy, CYCLES, "CYCLES");
cycles = 0;
while (*p) {
if (*p == '[') {
cycles++;
}
p++;
}
if (cycles < 2) {
char buf[1024];
sprintf(buf, "cannot parse CYCLES at line %d", linenum);
bail(buf);
}
// printf("CYCLES: %d\n", (int)cycles);
free(copy);
}
void handle_initial(char *copy)
{
char *p = (char *) locate(copy, INITIAL, "INITIAL");
cpu->SetPC(atoi(locate(p, PC, "INITIAL_PC")));
cpu->SetS(atoi(locate(p, S, "INITIAL_S")));
cpu->SetA(atoi(locate(p, A, "INITIAL_A")));
cpu->SetX(atoi(locate(p, X, "INITIAL_X")));
cpu->SetY(atoi(locate(p, Y, "INITIAL_Y")));
cpu->SetP(atoi(locate(p, P, "INITIAL_P")));
bzero(ram, sizeof(ram));
const char *q = locate(p, RAM, "INITIAL_RAM");
while (*q != '}') {
int addr, val;
if (*q == '[') {
sscanf(q+1, "%d, %d", &addr, &val);
ram[addr] = val;
}
q++;
}
free(copy);
}
bool is_jam(uint8_t val) {
// Instruction codes: 02, 12, 22, 32, 42, 52, 62, 72, 92, B2, D2, F2
if ((val & 0x0F) == 0x02) {
if ((val & 0x80) == 0) {
return true;
}
if ((val & 0x10) == 0x10) {
return true;
}
}
return false;
}
void handle_final(char *copy, bool jammed)
{
char buf[1024];
char *p = (char *) locate(copy, FINAL, "FINAL");
uint8_t val;
uint16_t pcval;
reset_fail();
if (!jammed && cpu->GetPC() != (pcval = /* ASSIGN */ atoi(locate(p, PC, "FINAL_PC")))) {
sprintf(buf, "FAIL: PC %04x != %04x at line %d", cpu->GetPC(), pcval, linenum);
fail(buf);
}
if (cpu->GetS() != (val = /* ASSIGN */ atoi(locate(p, S, "FINAL_S")))) {
sprintf(buf, "FAIL: S %02x != %02x at line %d", cpu->GetS(), val, linenum);
fail(buf);
}
if (cpu->GetA() != (val = /* ASSIGN */ atoi(locate(p, A, "FINAL_A")))) {
sprintf(buf, "FAIL: A %02x != %02x at line %d", cpu->GetA(), val, linenum);
fail(buf);
}
if (cpu->GetX() != (val = /* ASSIGN */ atoi(locate(p, X, "FINAL_X")))) {
sprintf(buf, "FAIL: X %02x != %02x at line %d", cpu->GetX(), val, linenum);
fail(buf);
}
if (cpu->GetY() != (val = /* ASSIGN */ atoi(locate(p, Y, "FINAL_Y")))) {
sprintf(buf, "FAIL: Y %02x != %02x at line %d", cpu->GetY(), val, linenum);
fail(buf);
}
if ((cpu->GetP() & PMASK) != (val = /* ASSIGN */ (atoi(locate(p, P, "FINAL_P")) & PMASK))) {
sprintf(buf, "FAIL: P %02x != %02x at line %d", cpu->GetP(), val, linenum);
fail(buf);
}
const char *q = locate(p, RAM, "FINAL_RAM");
while (*q != '}') {
int addr, val;
if (*q == '[') {
sscanf(q+1, "%d, %d", &addr, &val);
if (ram[addr] != val) {
sprintf(buf, "FAIL: RAM[%04x] %02x != %02x at line %d", addr, ram[addr], val, linenum);
fail(buf);
}
}
q++;
}
free(copy);
//printf("pass\n");
}
void handle_line(const char *line)
{
handle_name(strdup(line));
handle_cycles(strdup(line));
handle_initial(strdup(line));
actual_cycles = 0;
bool jammed = false;
if (!is_jam(ram[cpu->GetPC()])) {
cpu->Run(1, actual_cycles, mos6502::INST_COUNT);
if (cycles != actual_cycles) {
char buf[1024];
sprintf(buf, "FAIL: actual %d != %d cycles at %d", (int) actual_cycles, (int) cycles, linenum);
fail(buf);
}
}
else {
jammed = true;
}
handle_final(strdup(line), jammed);
}
void handle_json(const char *fname)
{
const char *p = strstr(fname, ".json");
if (p) {
p -= 2;
unsigned char x = strtoul(p, NULL, 16);
if (strchr(unstable, x)) {
is_unstable = true;
}
}
linenum = 1;
FILE *f = fopen(fname, "r");
if (!f) {
bail("could not open json file");
}
while (NULL != fgets(linebuf, sizeof(linebuf), f)) {
translate();
// we assume a lot here...
p = linebuf;
if (p[0] == '[') {
p++;
}
switch(p[0]) {
case 0:
case 0x0A:
case 0x0D:
case '[':
case ']':
break;
case '{':
handle_line(p);
break;
default:
{
char buf[1024];
sprintf(buf, "parse error at line %d, %02x", linenum, p[0]);
bail(buf);
}
break;
}
linenum++;
}
fclose(f);
}
int main(int argc, char **argv) {
if (argc != 2 && argc != 3) {
fprintf(stderr, "Usage: %s <file>.json [quiet]\n", argv[0]);
return -1;
}
if (argc == 3 && !strcmp(argv[2], "quiet")) {
quiet = true;
}
cpu = new mos6502(readRam, writeRam, tick);
cpu->Reset();
handle_json(argv[1]);
if (failures) {
printf("%d %sfailure%s\n", failures, is_unstable ? "unstable " : "", failures > 1 ? "s" : "");
}
return (failures && !is_unstable) ? -1 : 0;
}
gitextract_gtzs_4yo/
├── .gitignore
├── LICENSE.txt
├── README.md
├── mos6502.cpp
├── mos6502.h
└── tests/
├── Makefile
├── functional/
│ ├── .gitignore
│ ├── Makefile
│ ├── as65.sh
│ ├── decimal.patch
│ ├── main.cpp
│ ├── notes.txt
│ └── readme.md
└── singlestep/
├── .gitignore
├── Makefile
└── main.cpp
SYMBOL INDEX (23 symbols across 3 files)
FILE: mos6502.h
function class (line 13) | class mos6502
FILE: tests/functional/main.cpp
function writeRam (line 22) | void writeRam(uint16_t addr, uint8_t val)
function readRam (line 34) | uint8_t readRam(uint16_t addr)
function tick (line 39) | void tick(mos6502*)
function bail (line 85) | void bail(const char *s)
function fetch (line 91) | uint32_t fetch(const char *s, uint16_t offset, uint8_t count)
function handle_hex (line 111) | void handle_hex(const char *fname)
function main (line 143) | int main(int argc, char **argv) {
FILE: tests/singlestep/main.cpp
function writeRam (line 42) | void writeRam(uint16_t addr, uint8_t val)
function readRam (line 47) | uint8_t readRam(uint16_t addr)
function tick (line 52) | void tick(mos6502*)
function translate (line 56) | void translate(void)
function reset_fail (line 117) | void reset_fail(void) {
function fail (line 121) | void fail(const char *s)
function bail (line 132) | void bail(const char *s)
function handle_name (line 153) | void handle_name(char *copy)
function handle_cycles (line 173) | void handle_cycles(char *copy)
function handle_initial (line 196) | void handle_initial(char *copy)
function is_jam (line 222) | bool is_jam(uint8_t val) {
function handle_final (line 235) | void handle_final(char *copy, bool jammed)
function handle_line (line 288) | void handle_line(const char *line)
function handle_json (line 314) | void handle_json(const char *fname)
function main (line 363) | int main(int argc, char **argv) {
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (103K chars).
[
{
"path": ".gitignore",
"chars": 3,
"preview": "*.o"
},
{
"path": "LICENSE.txt",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2017 Gianluca Ghettini\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 5418,
"preview": "## MOS6502 Emulator in C++\n\nThis is my C++ implementation of the MOS Technology 6502 CPU. The code is written to be more"
},
{
"path": "mos6502.cpp",
"chars": 66040,
"preview": "#include \"mos6502.h\"\n\n#define NEGATIVE 0x80\n#define OVERFLOW 0x40\n#define CONSTANT 0x20\n#define BREAK 0x10\n#defin"
},
{
"path": "mos6502.h",
"chars": 7153,
"preview": "//============================================================================\n// Name : mos6502\n// Author :"
},
{
"path": "tests/Makefile",
"chars": 176,
"preview": "all:\n\t( cd functional && make )\n\t( cd singlestep && make )\n\t@echo ===============================\n\t@echo === ALL TESTS C"
},
{
"path": "tests/functional/.gitignore",
"chars": 51,
"preview": "*.hex\n*.lst\n*.a65\nmain\n6502_65C02_functional_tests\n"
},
{
"path": "tests/functional/Makefile",
"chars": 1813,
"preview": "# Makefile to run unit tests\n\n# Fail early if required tools are missing\nifeq (, $(shell which git 2>/dev/null))\n$(error"
},
{
"path": "tests/functional/as65.sh",
"chars": 2754,
"preview": "#!/bin/bash\nset -euo pipefail\n\nsrc_file=\"$1\"\nsrc_dir=$(dirname \"$(realpath \"$src_file\")\")\nbase_name=$(basename \"${src_fi"
},
{
"path": "tests/functional/decimal.patch",
"chars": 365,
"preview": "--- dt.a65\t2025-10-12 17:08:27.391206657 -0700\n+++ dt.a65\t2025-10-12 17:10:37.155949816 -0700\n@@ -34,7 +34,7 @@ chk_z "
},
{
"path": "tests/functional/main.cpp",
"chars": 3690,
"preview": "// compile with \"g++ main.cpp ../../mos6502.cpp -o main\"\n\n#include \"../../mos6502.h\"\n\n#include <stdlib.h>\n#include <stri"
},
{
"path": "tests/functional/notes.txt",
"chars": 122,
"preview": "assembler originally came from https://www.kingswood-consulting.co.uk/assemblers/\nauthor does NOT distribute source code"
},
{
"path": "tests/functional/readme.md",
"chars": 17,
"preview": "just type \"make\"\n"
},
{
"path": "tests/singlestep/.gitignore",
"chars": 11,
"preview": "65x02\nmain\n"
},
{
"path": "tests/singlestep/Makefile",
"chars": 874,
"preview": "# Makefile to run unit tests\n\n# Fail early if required tools are missing\nifeq (, $(shell which git 2>/dev/null))\n$(error"
},
{
"path": "tests/singlestep/main.cpp",
"chars": 8133,
"preview": "// compile with \"g++ main.cpp ../../mos6502.cpp -o main\"\n\n#include \"../../mos6502.h\"\n\n#include <stdlib.h>\n#include <stri"
}
]
About this extraction
This page contains the full source code of the gianlucag/mos6502 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (95.4 KB), approximately 32.8k tokens, and a symbol index with 23 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.