// PORTL = Data
// PORTA = Address LSB
// PORTC = Address MSB
// 65C02 control pins needed:
#define PHI2 2 // 65C02 clock
#define RW 3
#define SYNC 4 // indicates first byte of new opcode
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
DDRL = 0x00; // Data = INPUT
DDRA = 0x00; // Address = INPUT
DDRC = 0x00; // Address = INPUT
pinMode(PHI2, INPUT);
pinMode(RW, INPUT);
pinMode(SYNC, INPUT);
attachInterrupt(digitalPinToInterrupt(PHI2), OnClock, FALLING); // Data and Address are stable when PHI2 is falling
Serial.begin(115200);
}
enum AddressingMode {Undefined, Immediate, Accumulator, Relative, Absolute, AbsoluteIndirect, AbsoluteXIndexedIndirect, XIndexedAbsolute, YIndexedAbsolute, ZeroPage,
XIndexedZeroPage, YIndexedZeroPage, ZeroPageIndirect, XIndexedZeroPageIndirect, ZeroPageIndirectYIndexed};
unsigned int previousAddress = 0x0000;
unsigned int operands = 0;
unsigned int previousOperand = 0;
AddressingMode addressingMode = Undefined;
unsigned int currentAddress = 0x0000;
unsigned char currentData = 0;
bool currentRead = 0;
unsigned char currentOpCode = 0;
bool opCodeMustFollow = false;
void OnClock()
{
char output[64];
unsigned int currentAddress = PINA + (PINC << 8);
bool currentRead = (PINE & (1<<PE5)) != 0; // RW: PIN 3 is PE5
bool currentSync = (PINE & (1<<PE3)) != 0; // RW: PIN 5 is PE3
unsigned char currentData = PINL;
unsigned int absoluteAddress = 0;
if (currentAddress == previousAddress)
{
// false clock
}
else if ((currentAddress >= 0x0100) && (currentAddress <= 0x01FF))
{
// stack (needs to be before operands below in case operation accesses stack first like JSR does)
sprintf(output, "\t\t\t\t\t0x%04X\t0x%02X\t\t%c STACK", currentAddress, currentData, (char)(currentRead? 'R' : 'W'));
Serial.println(output);
}
else if (operands > 0)
{
if (operands == 1)
{
switch (addressingMode)
{
case XIndexedZeroPageIndirect: // (zeropage,X)
sprintf(output, "\t(0x%02X, X)", currentData);
break;
case ZeroPage: // zeropage
sprintf(output, "\t0x%02X", currentData);
break;
case ZeroPageIndirect: // (zeropage)
sprintf(output, "\t(0x%02X)", currentData);
break;
case Immediate: // #immediate
sprintf(output, "\t%%0x%02X", currentData);
break;
case Absolute: // absolute
absoluteAddress = (currentData << 8) + previousOperand;
if (currentOpCode == 0x20) // JSR is special
{
sprintf(output, "\t\t\t0x%04X", absoluteAddress);
}
else
{
sprintf(output, "\t0x%04X", absoluteAddress);
}
break;
case AbsoluteIndirect: // JMP (absolute)
absoluteAddress = (currentData << 8) + previousOperand;
sprintf(output, "\t(0x%04X)", absoluteAddress);
break;
case AbsoluteXIndexedIndirect: // JMP (absolute,X)
absoluteAddress = (currentData << 8) + previousOperand;
sprintf(output, "\t(0x%04X, X)", absoluteAddress);
break;
case ZeroPageIndirectYIndexed: // (zeropage),Y
sprintf(output, "\t(0x%02X), Y", currentData);
break;
case XIndexedZeroPage: // zeropage, X
sprintf(output, "\t0x%02X, X", currentData);
break;
case YIndexedZeroPage: // zeropage, Y
sprintf(output, "\t0x%02X, Y", currentData);
break;
case XIndexedAbsolute: // absolute, X
absoluteAddress = (currentData << 8) + previousOperand;
sprintf(output, "\t0x%04X, X", absoluteAddress);
break;
case YIndexedAbsolute: // absolute, Y
absoluteAddress = (currentData << 8) + previousOperand;
sprintf(output, "\t0x%04X, Y", absoluteAddress);
break;
case Relative: // relative (for branches)
// The range of the offset is 128 to +127 bytes from the next instruction.
absoluteAddress = currentAddress + currentData - 255;
sprintf(output, "\t0x%04X", absoluteAddress);
break;
case Undefined: // what's this?
sprintf(output, "\t0x%02X ???", currentData);
break;
}
Serial.println(output);
}
else if (operands == 2)
{
previousOperand = currentData;
}
else
{
Serial.println("BAD operands");
}
operands--;
}
else if ((currentAddress >= 0x00F0) && (currentAddress <= 0x00FF)) // implementation specific: what are your PORT addresses?
{
// port IO
sprintf(output, "\t\t\t\t\t0x%04X\t0x%02X\t\t%c PORT", currentAddress, currentData, (char)(currentRead? 'R' : 'W'));
Serial.println(output);
}
else if ((currentAddress >= 0x0000) && (currentAddress <= 0x7FFF)) // implementation specific: what is your data RAM?
{
// RAM
sprintf(output, "\t\t\t\t\t0x%04X\t0x%02X\t\t%c RAM", currentAddress, currentData, (char)(currentRead? 'R' : 'W'));
Serial.println(output);
}
else if ((currentAddress >= 0xFFFA) && (currentAddress <= 0xFFFF))
{
// vectors
sprintf(output, "\t\t\t\t\t0x%04X\t0x%02X\t\t%c VECTORS", currentAddress, currentData, (char)(currentRead? 'R' : 'W'));
Serial.println(output);
}
else if ((currentSync || opCodeMustFollow) && currentRead) // first byte of instruction according to SYNC
{
// https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2 - good opcode resource
// https://llx.com/Neil/a2/opcodes.html#insc02 - good instruction decoding resource
char opName[5];
operands = 0;
addressingMode = Undefined;
strcpy(opName, "???"); // what's this?!
currentOpCode = currentData;
opCodeMustFollow = false;
// other instructions (not neatly in the groups which follow)
switch (currentData)
{
case 0x00:
strcpy(opName, "BRK");
break;
case 0x20:
strcpy(opName, "JSR");
operands = 2;
addressingMode = Absolute;
break;
case 0x4C:
strcpy(opName, "JMP");
operands = 2;
addressingMode = Absolute;
break;
case 0x6C:
strcpy(opName, "JMP");
operands = 2;
addressingMode = AbsoluteIndirect;
break;
case 0x7C:
strcpy(opName, "JMP");
operands = 2;
addressingMode = AbsoluteXIndexedIndirect;
break;
case 0x40:
strcpy(opName, "RTI");
break;
case 0x60:
strcpy(opName, "RTS");
break;
case 0x08:
strcpy(opName, "PHP");
break;
case 0x28:
strcpy(opName, "PLP");
break;
case 0x48:
strcpy(opName, "PHA");
break;
case 0x68:
strcpy(opName, "PLA");
break;
case 0x88:
strcpy(opName, "DEY");
break;
case 0xA8:
strcpy(opName, "TAY");
break;
case 0xC8:
strcpy(opName, "INY");
break;
case 0xE8:
strcpy(opName, "INX");
break;
case 0x18:
strcpy(opName, "CLC");
break;
case 0x38:
strcpy(opName, "SEC");
break;
case 0x58:
strcpy(opName, "CLI");
break;
case 0x78:
strcpy(opName, "SEI");
break;
case 0x98:
strcpy(opName, "TYA");
break;
case 0xB8:
strcpy(opName, "CLV");
break;
case 0xD8:
strcpy(opName, "CLD");
break;
case 0xF8:
strcpy(opName, "SED");
break;
case 0x8A:
strcpy(opName, "TXA");
break;
case 0x9A:
strcpy(opName, "TXS");
break;
case 0xAA:
strcpy(opName, "TAX");
break;
case 0xBA:
strcpy(opName, "TSX");
break;
case 0xCA:
strcpy(opName, "DEX");
break;
case 0xEA:
strcpy(opName, "NOP");
break;
// 65C02
case 0x1A:
strcpy(opName, "INC");
break;
case 0x3A:
strcpy(opName, "DEC");
break;
case 0xDA:
strcpy(opName, "PHX");
break;
case 0x5A:
strcpy(opName, "PHY");
break;
case 0xFA:
strcpy(opName, "PLX");
break;
case 0x7A:
strcpy(opName, "PLY");
break;
case 0x89:
strcpy(opName, "BIT");
addressingMode = Immediate;
operands = 1;
break;
case 0x14:
strcpy(opName, "TRB");
addressingMode = ZeroPage;
operands = 1;
break;
case 0x64:
strcpy(opName, "STZ");
addressingMode = ZeroPage;
operands = 1;
break;
case 0x1C:
strcpy(opName, "TRB");
addressingMode = Absolute;
operands = 2;
break;
case 0x9C:
strcpy(opName, "STZ");
addressingMode = Absolute;
operands = 2;
break;
case 0x74:
strcpy(opName, "STZ");
addressingMode = XIndexedZeroPage;
operands = 1;
break;
case 0x9E:
strcpy(opName, "STZ");
addressingMode = XIndexedAbsolute;
operands = 2;
break;
case 0x10:
strcpy(opName, "BPL");
addressingMode = Relative;
operands = 1;
break;
case 0x30:
strcpy(opName, "BMI");
addressingMode = Relative;
operands = 1;
break;
case 0x50:
strcpy(opName, "BVC");
addressingMode = Relative;
operands = 1;
break;
case 0x70:
strcpy(opName, "BVS");
addressingMode = Relative;
operands = 1;
break;
case 0x80:
strcpy(opName, "BRA");
addressingMode = Relative;
operands = 1;
break;
case 0x90:
strcpy(opName, "BCC");
addressingMode = Relative;
operands = 1;
break;
case 0xB0:
strcpy(opName, "BCS");
addressingMode = Relative;
operands = 1;
break;
case 0xD0:
strcpy(opName, "BNE");
addressingMode = Relative;
operands = 1;
break;
case 0xF0:
strcpy(opName, "BEQ");
addressingMode = Relative;
operands = 1;
break;
default:
switch (currentData & 0b00000011)
{
case 0b00000001: // 'Group One' instructions
addressingMode = Undefined;
switch (currentData & 0b00011100)
{
case 0b00000000: // (zeropage,X)
addressingMode = XIndexedZeroPageIndirect;
operands = 1;
break;
case 0b00000100: // zeropage
addressingMode = ZeroPage;
operands = 1;
break;
case 0b00001000: // #immediate
addressingMode = Immediate;
operands = 1;
break;
case 0b00010000: // (zeropage),Y
addressingMode = ZeroPageIndirectYIndexed;
operands = 1;
break;
case 0b00010100: // zeropage,X
addressingMode = XIndexedZeroPage;
operands = 1;
break;
case 0b00001100: // absolute
addressingMode = Absolute;
operands = 2;
break;
case 0b00011000: // absolute, X
addressingMode = XIndexedAbsolute;
operands = 2;
break;
case 0b00011100: // absolute, Y
addressingMode = YIndexedAbsolute;
operands = 2;
break;
}
switch (currentData & 0b11100000)
{
case 0b00000000:
strcpy(opName, "ORA");
break;
case 0b00100000:
strcpy(opName, "AND");
break;
case 0b01000000:
strcpy(opName, "EOR");
break;
case 0b01100000:
strcpy(opName, "ADC");
break;
case 0b10000000:
strcpy(opName, "STA");
break;
case 0b10100000:
strcpy(opName, "LDA");
break;
case 0b11000000:
strcpy(opName, "CMP");
break;
case 0b11100000:
strcpy(opName, "SBC");
break;
}
break;
case 0b00000010: // 'Group Two' instructions
addressingMode = Undefined;
switch (currentData & 0b00011100)
{
case 0b00000000: // #immediate
addressingMode = Immediate;
operands = 1;
break;
case 0b00000100: // zeropage
addressingMode = ZeroPage;
operands = 1;
break;
case 0b00001000: // A
addressingMode = Accumulator;
operands = 0;
break;
case 0b00001100: // absolute
addressingMode = Absolute;
operands = 2;
break;
case 0b0010000: // (zeropage)
addressingMode = ZeroPageIndirect;
operands = 2;
break;
case 0b00010100: // zeropage,X
addressingMode = XIndexedZeroPage;
operands = 1;
break;
case 0b00011100: // absolute, X
addressingMode = XIndexedAbsolute;
operands = 2;
break;
}
if (addressingMode == ZeroPageIndirect) // 65C02 exception
{
// 'Group One' opcodes
switch (currentData & 0b11100000)
{
case 0b00000000:
strcpy(opName, "ORA");
break;
case 0b00100000:
strcpy(opName, "AND");
break;
case 0b01000000:
strcpy(opName, "EOR");
break;
case 0b01100000:
strcpy(opName, "ADC");
break;
case 0b10000000:
strcpy(opName, "STA");
break;
case 0b10100000:
strcpy(opName, "LDA");
break;
case 0b11000000:
strcpy(opName, "CMP");
break;
case 0b11100000:
strcpy(opName, "SBC");
break;
}
}
else
{
switch (currentData & 0b11100000)
{
case 0b00000000:
strcpy(opName, "ASL");
break;
case 0b00100000:
strcpy(opName, "ROL");
break;
case 0b01000000:
strcpy(opName, "LSR");
break;
case 0b01100000:
strcpy(opName, "ROR");
break;
case 0b10000000:
switch (addressingMode)
{
case XIndexedZeroPage:
addressingMode = YIndexedZeroPage;
break;
}
strcpy(opName, "STX");
break;
case 0b10100000:
switch (addressingMode)
{
case XIndexedZeroPage:
addressingMode = YIndexedZeroPage;
break;
case XIndexedAbsolute:
addressingMode = YIndexedAbsolute;
break;
}
strcpy(opName, "LDX");
break;
case 0b11000000:
strcpy(opName, "DEC");
break;
case 0b11100000:
strcpy(opName, "INC");
break;
}
}
break;
case 0b00000000: // 'Group Three' instructions
addressingMode = Undefined;
switch (currentData & 0b00011100)
{
case 0b00000000: // #immediate
addressingMode = Immediate;
operands = 1;
break;
case 0b00000100: // zeropage
addressingMode = ZeroPage;
operands = 1;
break;
case 0b00001100: // absolute
addressingMode = Absolute;
operands = 2;
break;
case 0b00010100: // zeropage,X
addressingMode = XIndexedZeroPage;
operands = 1;
break;
case 0b00011100: // absolute, X
addressingMode = XIndexedAbsolute;
operands = 2;
break;
}
switch (currentData & 0b11100000)
{
case 0b00000000:
strcpy(opName, "TSB");
break;
case 0b00100000:
strcpy(opName, "BIT");
break;
case 0b10000000:
strcpy(opName, "STY");
break;
case 0b10100000:
strcpy(opName, "LDY");
break;
case 0b11000000:
strcpy(opName, "CPY");
break;
case 0b11100000:
strcpy(opName, "CPX");
break;
}
break;
case 0b00000011: // special Rockwell and WDC instructions
addressingMode = Undefined;
unsigned char bit = ((currentData & 0b01110000) >> 4) + '0';
switch (currentData & 0b00001100)
{
case 0b00000100:
sprintf(opName, "RMB%c", bit);
break;
case 0b10000100:
sprintf(opName, "SMB%c", bit);
break;
case 0b00001100:
sprintf(opName, "BBR%c", bit);
break;
case 0b10001100:
sprintf(opName, "BBS%c", bit);
break;
}
break;
}
break;
}
if ( (operands > 0)
&& (currentOpCode != 0x20) // JSR is special: stack bytes are seen before operand bytes
)
{
sprintf(output, "0x%04X\t0x%02X\t%s", currentAddress, currentData, opName);
}
else
{
sprintf(output, "0x%04X\t0x%02X\t%s\n", currentAddress, currentData, opName);
}
Serial.print(output);
// For some reason the SYNC signal is not always correct following a single byte opcode.
// This is a workaround but it cannot be used if the single byte opcode causes stack read/write
opCodeMustFollow = false;
if (operands == 0)
{
switch (currentData)
{
case 0x48: // PHA
case 0x08: // PHP
case 0xDA: // PHX
case 0x5A: // PHY
case 0x68: // PLA
case 0x28: // PLP
case 0xFA: // PLX
case 0x7A: // PLY
case 0x20: // JSR
case 0x40: // RTI
case 0x60: // RTS
break;
default:
opCodeMustFollow = true;
break;
}
}
}
else
{
// everything else
sprintf(output, "\t\t\t\t\t0x%04X\t0x%02X\t\t%c\t%c ?", currentAddress, currentData, (char)(currentRead? 'R' : 'W'), (char)(currentSync? 'S' : ' '));
Serial.println(output);
}
previousAddress = currentAddress;
}
void loop()
{
// Toggle the Mega's built-in LED as a heartbeat indicator:
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
digitalWrite(LED_BUILTIN, HIGH);
}
Comments
Please log in or sign up to comment.