406 lines
10 KiB
C++
Executable File
406 lines
10 KiB
C++
Executable File
/*
|
|
* (C) 2006 Dirk W. Hoffmann. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "C64.h"
|
|
|
|
CPU::CPU()
|
|
{
|
|
name = "CPU";
|
|
debug(2, " Creating CPU at address %p...\n", this);
|
|
|
|
// Chip model
|
|
chipModel = MOS6510;
|
|
|
|
// Establish callback for each instruction
|
|
registerInstructions();
|
|
|
|
// Clear all breakpoint tags
|
|
for (int i = 0; i < 65536; i++) {
|
|
breakpoint[i] = NO_BREAKPOINT;
|
|
}
|
|
|
|
// Register snapshot items
|
|
SnapshotItem items[] = {
|
|
|
|
// Lifetime items
|
|
{ &chipModel, sizeof(chipModel), KEEP_ON_RESET },
|
|
|
|
// Internal state
|
|
{ &A, sizeof(A), CLEAR_ON_RESET },
|
|
{ &X, sizeof(X), CLEAR_ON_RESET },
|
|
{ &Y, sizeof(Y), CLEAR_ON_RESET },
|
|
{ &PC, sizeof(PC), CLEAR_ON_RESET },
|
|
{ &PC_at_cycle_0, sizeof(PC_at_cycle_0), CLEAR_ON_RESET },
|
|
{ &SP, sizeof(SP), CLEAR_ON_RESET },
|
|
{ &N, sizeof(N), CLEAR_ON_RESET },
|
|
{ &V, sizeof(V), CLEAR_ON_RESET },
|
|
{ &B, sizeof(B), CLEAR_ON_RESET },
|
|
{ &D, sizeof(D), CLEAR_ON_RESET },
|
|
{ &I, sizeof(I), CLEAR_ON_RESET },
|
|
{ &Z, sizeof(Z), CLEAR_ON_RESET },
|
|
{ &C, sizeof(C), CLEAR_ON_RESET },
|
|
{ &opcode, sizeof(opcode), CLEAR_ON_RESET },
|
|
{ &addr_lo, sizeof(addr_lo), CLEAR_ON_RESET },
|
|
{ &addr_hi, sizeof(addr_hi), CLEAR_ON_RESET },
|
|
{ &ptr, sizeof(ptr), CLEAR_ON_RESET },
|
|
{ &pc_lo, sizeof(pc_lo), CLEAR_ON_RESET },
|
|
{ &pc_hi, sizeof(pc_hi), CLEAR_ON_RESET },
|
|
{ &overflow, sizeof(overflow), CLEAR_ON_RESET },
|
|
{ &data, sizeof(data), CLEAR_ON_RESET },
|
|
{ &port, sizeof(port), KEEP_ON_RESET }, // Reset in C64Memory::reset
|
|
{ &port_direction, sizeof(port_direction), KEEP_ON_RESET }, // Reset in C64Memory::reset
|
|
{ &external_port_bits, sizeof(external_port_bits), CLEAR_ON_RESET },
|
|
{ &rdyLine, sizeof(rdyLine), CLEAR_ON_RESET },
|
|
{ &irqLine, sizeof(irqLine), CLEAR_ON_RESET },
|
|
{ &nmiLine, sizeof(nmiLine), CLEAR_ON_RESET },
|
|
{ &nmiEdge, sizeof(nmiEdge), CLEAR_ON_RESET },
|
|
{ &interruptsPending, sizeof(interruptsPending), CLEAR_ON_RESET },
|
|
{ &nextPossibleIrqCycle, sizeof(nextPossibleIrqCycle), CLEAR_ON_RESET },
|
|
{ &nextPossibleNmiCycle, sizeof(nextPossibleNmiCycle), CLEAR_ON_RESET },
|
|
{ &errorState, sizeof(errorState), CLEAR_ON_RESET },
|
|
{ &callStack, sizeof(callStack), CLEAR_ON_RESET | WORD_FORMAT },
|
|
{ &callStackPointer, sizeof(callStackPointer), CLEAR_ON_RESET },
|
|
{ &oldI, sizeof(oldI), CLEAR_ON_RESET },
|
|
{ NULL, 0, 0 }};
|
|
|
|
registerSnapshotItems(items, sizeof(items));
|
|
}
|
|
|
|
CPU::~CPU()
|
|
{
|
|
debug(2, " Releasing CPU...\n");
|
|
}
|
|
|
|
void
|
|
CPU::reset()
|
|
{
|
|
VirtualComponent::reset();
|
|
|
|
external_port_bits = 0x1F;
|
|
rdyLine = true;
|
|
next = &CPU::fetch;
|
|
}
|
|
|
|
uint32_t
|
|
CPU::stateSize()
|
|
{
|
|
return VirtualComponent::stateSize() + 2;
|
|
}
|
|
|
|
void
|
|
CPU::loadFromBuffer(uint8_t **buffer)
|
|
{
|
|
VirtualComponent::loadFromBuffer(buffer);
|
|
next = CPU::callbacks[read16(buffer)];
|
|
}
|
|
|
|
void
|
|
CPU::saveToBuffer(uint8_t **buffer)
|
|
{
|
|
VirtualComponent::saveToBuffer(buffer);
|
|
for (uint16_t i = 0;; i++) {
|
|
if (callbacks[i] == NULL) {
|
|
panic("ERROR while saving state: Callback pointer not found!\n");
|
|
}
|
|
if (callbacks[i] == next) {
|
|
write16(buffer, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CPU::dumpState()
|
|
{
|
|
msg("CPU:\n");
|
|
msg("----\n\n");
|
|
msg("%s\n", disassemble());
|
|
msg("Processor port : %02X\n", port);
|
|
msg("Port direction : %02X\n", port_direction);
|
|
msg(" Rdy line : %s\n", rdyLine ? "high" : "low");
|
|
msg(" Irq line : %02X\n", irqLine);
|
|
msg(" Nmi line : %02X %s\n", nmiLine, nmiEdge ? "(negative edge)" : "");
|
|
msg(" no IRQ before : %ull\n", nextPossibleIrqCycle);
|
|
msg(" no NMI before : %ull\n", nextPossibleNmiCycle);
|
|
msg(" IRQ routine : %02X%02X\n", mem->peek(0xFFFF), mem->peek(0xFFFE));
|
|
msg(" NMI routine : %02X%02X\n", mem->peek(0xFFFB), mem->peek(0xFFFA));
|
|
msg("\n");
|
|
}
|
|
|
|
void
|
|
CPU::setPortDirection(uint8_t value)
|
|
{
|
|
|
|
port_direction = value;
|
|
|
|
// TODO: A VIC byte will show up in ram[0x0000];
|
|
// "ram[0] = TheVIC->LastVICByte;" [Frodo]
|
|
|
|
// Store value of Bit 7, Bit 6 and Bit 3 if the corresponding bit lines are configured as outputs
|
|
uint8_t mask = 0xC8 & port_direction;
|
|
external_port_bits &= ~mask;
|
|
external_port_bits |= mask & port;
|
|
}
|
|
|
|
void
|
|
CPU::setPort(uint8_t value)
|
|
{
|
|
port = value;
|
|
|
|
// TODO: A VIC byte will show up in ram[0x0001];
|
|
// "ram[1] = TheVIC->LastVICByte;" [Frodo]
|
|
|
|
// Store value of Bit 7, Bit 6 and Bit 3 if the corresponding bit lines are configured as outputs
|
|
uint8_t mask = 0xC8 & port_direction;
|
|
external_port_bits &= ~mask;
|
|
external_port_bits |= mask & port;
|
|
|
|
// Datasette
|
|
if (port_direction & 0x20) {
|
|
c64->datasette.setMotor((value & 0x20) == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
CPU::setIRQLine(uint8_t bit)
|
|
{
|
|
assert(bit != 0);
|
|
|
|
if (irqLine == 0) {
|
|
nextPossibleIrqCycle = c64->getCycles() + 2;
|
|
}
|
|
irqLine |= bit;
|
|
interruptsPending = true;
|
|
}
|
|
|
|
bool
|
|
CPU::IRQLineRaisedLongEnough()
|
|
{
|
|
return c64->getCycles() >= nextPossibleIrqCycle;
|
|
}
|
|
|
|
bool
|
|
CPU::IRQsAreBlocked() {
|
|
bool result;
|
|
if (opcode == 0x78 /* SEI */ || opcode == 0x58 /* CLI */)
|
|
result = oldI;
|
|
else
|
|
result = I;
|
|
|
|
oldI = I;
|
|
return result;
|
|
}
|
|
|
|
void
|
|
CPU::setNMILine(uint8_t bit) // TODO: RENAME TO RAISE NMIline
|
|
{
|
|
assert(bit != 0);
|
|
|
|
if (!nmiLine) setNMIEdge();
|
|
nmiLine |= bit;
|
|
}
|
|
|
|
void
|
|
CPU::setNMIEdge()
|
|
{
|
|
nmiEdge = true;
|
|
interruptsPending = true;
|
|
nextPossibleNmiCycle = c64->getCycles() + 2;
|
|
}
|
|
|
|
void
|
|
CPU::clearNMIEdge()
|
|
{
|
|
nmiEdge = false;
|
|
interruptsPending = irqLine;
|
|
}
|
|
|
|
bool
|
|
CPU::NMILineRaisedLongEnough()
|
|
{
|
|
return c64->getCycles() >= nextPossibleNmiCycle;
|
|
}
|
|
|
|
|
|
// Instruction set
|
|
const char
|
|
*CPU::getMnemonic(uint8_t opcode)
|
|
{
|
|
return mnemonic[opcode];
|
|
}
|
|
|
|
CPU::AddressingMode
|
|
CPU::getAddressingMode(uint8_t opcode)
|
|
{
|
|
return addressingMode[opcode];
|
|
}
|
|
|
|
int
|
|
CPU::getLengthOfInstruction(uint8_t opcode)
|
|
{
|
|
switch(addressingMode[opcode]) {
|
|
case ADDR_IMPLIED:
|
|
case ADDR_ACCUMULATOR:
|
|
return 1;
|
|
case ADDR_IMMEDIATE:
|
|
case ADDR_ZERO_PAGE:
|
|
case ADDR_ZERO_PAGE_X:
|
|
case ADDR_ZERO_PAGE_Y:
|
|
case ADDR_INDIRECT_X:
|
|
case ADDR_INDIRECT_Y:
|
|
case ADDR_RELATIVE:
|
|
return 2;
|
|
case ADDR_ABSOLUTE:
|
|
case ADDR_ABSOLUTE_X:
|
|
case ADDR_ABSOLUTE_Y:
|
|
case ADDR_DIRECT:
|
|
case ADDR_INDIRECT:
|
|
return 3;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
char *
|
|
CPU::disassemble()
|
|
{
|
|
char buf[64], msg[128];
|
|
int i, op;
|
|
|
|
uint16_t pc = PC_at_cycle_0;
|
|
uint8_t opcode = mem->peek(pc);
|
|
|
|
strcpy(msg, "");
|
|
|
|
// Program counter
|
|
sprintf(buf, "%04X: ", pc);
|
|
strcat(msg, buf);
|
|
|
|
// Hex dump
|
|
for (i = 0; i < 3; i++) {
|
|
if (i < getLengthOfInstruction(opcode)) {
|
|
sprintf(buf, "%02X ", mem->peek(pc+i));
|
|
strcat(msg, buf);
|
|
} else {
|
|
sprintf(buf, " ");
|
|
strcat(msg, buf);
|
|
}
|
|
}
|
|
|
|
// Register
|
|
sprintf(buf, " %02X %02X %02X %02X ", A, X, Y, SP);
|
|
strcat(msg, buf);
|
|
|
|
// Flags
|
|
sprintf(buf, "%c%c%c%c%c%c%c%c ",
|
|
N ? 'N' : 'n',
|
|
V ? 'V' : 'v',
|
|
'-',
|
|
B ? 'B' : 'b',
|
|
D ? 'D' : 'd',
|
|
I ? 'I' : 'i',
|
|
Z ? 'Z' : 'z',
|
|
C ? 'C' : 'c');
|
|
strcat(msg, buf);
|
|
|
|
// Mnemonic
|
|
sprintf(buf, "%s ", getMnemonic(opcode));
|
|
strcat(msg, buf);
|
|
|
|
// Get operand as number
|
|
switch (addressingMode[opcode]) {
|
|
case CPU::ADDR_IMMEDIATE:
|
|
case CPU::ADDR_ZERO_PAGE:
|
|
case CPU::ADDR_ZERO_PAGE_X:
|
|
case CPU::ADDR_ZERO_PAGE_Y:
|
|
case CPU::ADDR_INDIRECT_X:
|
|
case CPU::ADDR_INDIRECT_Y:
|
|
op = mem->peek(pc+1);
|
|
break;
|
|
case CPU::ADDR_DIRECT:
|
|
case CPU::ADDR_INDIRECT:
|
|
case CPU::ADDR_ABSOLUTE:
|
|
case CPU::ADDR_ABSOLUTE_X:
|
|
case CPU::ADDR_ABSOLUTE_Y:
|
|
op = mem->peekWord(pc+1);
|
|
break;
|
|
case CPU::ADDR_RELATIVE:
|
|
op = pc + 2 + (int8_t)mem->peek(pc+1);
|
|
break;
|
|
default:
|
|
op = -1;
|
|
}
|
|
|
|
// Format operand
|
|
switch (addressingMode[opcode]) {
|
|
case CPU::ADDR_IMPLIED:
|
|
case CPU::ADDR_ACCUMULATOR:
|
|
sprintf(buf, " ");
|
|
break;
|
|
case CPU::ADDR_IMMEDIATE:
|
|
sprintf(buf, "#%02X", op);
|
|
break;
|
|
case CPU::ADDR_ZERO_PAGE:
|
|
sprintf(buf, "%02X", op);
|
|
break;
|
|
case CPU::ADDR_ZERO_PAGE_X:
|
|
sprintf(buf, "%02X,X", op);
|
|
break;
|
|
case CPU::ADDR_ZERO_PAGE_Y:
|
|
sprintf(buf, "%02X,Y", op);
|
|
break;
|
|
case CPU::ADDR_ABSOLUTE:
|
|
case CPU::ADDR_DIRECT:
|
|
sprintf(buf, "%04X", op);
|
|
break;
|
|
case CPU::ADDR_ABSOLUTE_X:
|
|
sprintf(buf, "%04X,X", op);
|
|
break;
|
|
case CPU::ADDR_ABSOLUTE_Y:
|
|
sprintf(buf, "%04X,Y", op);
|
|
break;
|
|
case CPU::ADDR_INDIRECT:
|
|
sprintf(buf, "(%04X)", op);
|
|
break;
|
|
case CPU::ADDR_INDIRECT_X:
|
|
sprintf(buf, "(%04X,X)", op);
|
|
break;
|
|
case CPU::ADDR_INDIRECT_Y:
|
|
sprintf(buf, "(%04X),Y", op);
|
|
break;
|
|
case CPU::ADDR_RELATIVE:
|
|
sprintf(buf, "%04X", op);
|
|
break;
|
|
default:
|
|
sprintf(buf, "???");
|
|
}
|
|
strcat(msg, buf);
|
|
return strdup(msg);
|
|
}
|
|
|
|
void
|
|
CPU::setErrorState(ErrorState state)
|
|
{
|
|
if (errorState == state)
|
|
return;
|
|
|
|
errorState = state;
|
|
c64->putMessage(MSG_CPU, state);
|
|
}
|
|
|
|
|