stella/src/emucore/M6502.hxx

470 lines
14 KiB
C++

//============================================================================
//
// MM MM 6666 555555 0000 2222
// MMMM MMMM 66 66 55 00 00 22 22
// MM MMM MM 66 55 00 00 22
// MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator"
// MM MM 66 66 55 00 00 22
// MM MM 66 66 55 55 00 00 22
// MM MM 6666 5555 0000 222222
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef M6502_HXX
#define M6502_HXX
#include <functional>
class Settings;
class System;
class DispatchResult;
#ifdef DEBUGGER_SUPPORT
class Debugger;
class CpuDebug;
#include "Expression.hxx"
#include "PackedBitArray.hxx"
#include "TrapArray.hxx"
#endif
#include "bspf.hxx"
#include "Serializable.hxx"
/**
The 6502 is an 8-bit microprocessor that has a 64K addressing space.
This class provides a high compatibility 6502 microprocessor emulator.
The memory accesses and cycle counts it generates are valid at the
sub-instruction level and "false" reads are generated (such as the ones
produced by the Indirect,X addressing when it crosses a page boundary).
This provides provides better compatibility for hardware that has side
effects and for games which are very time sensitive.
@author Bradford W. Mott
*/
class M6502 : public Serializable
{
// The 6502 and Cart debugger classes are friends who need special access
friend class CartDebug;
friend class CpuDebug;
public:
using onHaltCallback = std::function<void()>;
public:
/**
Create a new 6502 microprocessor.
*/
explicit M6502(const Settings& settings);
virtual ~M6502() = default;
public:
/**
Install the processor in the specified system. Invoked by the
system when the processor is attached to it.
@param system The system the processor should install itself in
*/
void install(System& system);
/**
Reset the processor to its power-on state. This method should not
be invoked until the entire 6502 system is constructed and installed
since it involves reading the reset vector from memory.
*/
void reset();
/**
Request a maskable interrupt
*/
void irq() { myExecutionStatus |= MaskableInterruptBit; }
/**
Request a non-maskable interrupt
*/
void nmi() { myExecutionStatus |= NonmaskableInterruptBit; }
/**
Set the callback for handling a halt condition
*/
void setOnHaltCallback(onHaltCallback callback) { myOnHaltCallback = callback; }
/**
RDY pulled low --- halt on next read.
*/
void requestHalt();
/**
Pull RDY high again before the callback was triggered.
*/
void clearHaltRequest() { myHaltRequested = false; }
/**
Execute instructions until the specified number of instructions
is executed, someone stops execution, or an error occurs. Answers
true iff execution stops normally.
@param cycles Indicates the number of cycles to execute. Not that the actual
granularity of the CPU is instructions, so this is only accurate up to
a couple of cycles
@param result A DispatchResult object that will transport the result
*/
void execute(uInt64 cycles, DispatchResult& result);
bool execute(uInt64 cycles);
/**
Tell the processor to stop executing instructions. Invoking this
method while the processor is executing instructions will stop
execution as soon as possible.
*/
void stop() { myExecutionStatus |= StopExecutionBit; }
/**
Answer true iff a fatal error has occured from which the processor
cannot recover (i.e. illegal instruction, etc.)
@return true iff a fatal error has occured
*/
bool fatalError() const { return myExecutionStatus & FatalErrorBit; }
/**
Get the 16-bit value of the Program Counter register.
@return The program counter register
*/
uInt16 getPC() const { return PC; }
/**
Check the type of the last peek().
@return true, if the last peek() was a ghost read.
*/
bool lastWasGhostPeek() const { return myFlags == 0; } // DISASM_NONE
/**
Return the last address that was part of a read/peek.
@return The address of the last read
*/
uInt16 lastReadBaseAddress() const { return myLastPeekBaseAddress; }
/**
Return the last address that was part of a write/poke.
@return The address of the last write
*/
uInt16 lastWriteBaseAddress() const { return myLastPokeBaseAddress; }
/**
Return the source of the address that was used for a write/poke.
Note that this isn't the same as the address that is poked, but
is instead the address of the *data* that is poked (if any).
@return The address of the data used in the last poke, else 0
*/
uInt16 lastDataAddressForPoke() const { return myDataAddressForPoke; }
/**
Return the last data address used as part of a peek operation for
the S/A/X/Y registers. Note that if an address wasn't used (as in
immediate mode), then the address is -1.
@return The address of the data used in the last peek, else -1
*/
Int32 lastSrcAddressS() const { return myLastSrcAddressS; }
Int32 lastSrcAddressA() const { return myLastSrcAddressA; }
Int32 lastSrcAddressX() const { return myLastSrcAddressX; }
Int32 lastSrcAddressY() const { return myLastSrcAddressY; }
/**
Get the number of memory accesses to distinct memory locations
@return The number of memory accesses to distinct memory locations
*/
uInt32 distinctAccesses() const { return myNumberOfDistinctAccesses; }
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
bool save(Serializer& out) const override;
/**
Loads the current state of this device from the given Serializer.
@param in The Serializer device to load from.
@return The result of the load. True on success, false on failure.
*/
bool load(Serializer& in) override;
#ifdef DEBUGGER_SUPPORT
public:
// Attach the specified debugger.
void attach(Debugger& debugger);
PackedBitArray& breakPoints() { return myBreakPoints; }
TrapArray& readTraps() { return myReadTraps; }
TrapArray& writeTraps() { return myWriteTraps; }
// methods for 'breakif' handling
uInt32 addCondBreak(Expression* e, const string& name);
bool delCondBreak(uInt32 idx);
void clearCondBreaks();
const StringList& getCondBreakNames() const;
// methods for 'savestateif' handling
uInt32 addCondSaveState(Expression* e, const string& name);
bool delCondSaveState(uInt32 idx);
void clearCondSaveStates();
const StringList& getCondSaveStateNames() const;
// methods for 'trapif' handling
uInt32 addCondTrap(Expression* e, const string& name);
bool delCondTrap(uInt32 brk);
void clearCondTraps();
const StringList& getCondTrapNames() const;
void setGhostReadsTrap(bool enable) { myGhostReadsTrap = enable; }
void setReadFromWritePortBreak(bool enable) { myReadFromWritePortBreak = enable; }
#endif // DEBUGGER_SUPPORT
private:
/**
Get the byte at the specified address and update the cycle count.
Addresses marked as code are hints to the debugger/disassembler to
conclusively determine code sections, even if the disassembler cannot
find them itself.
@param address The address from which the value should be loaded
@param flags Indicates that this address has the given flags
for type of access (CODE, DATA, GFX, etc)
@return The byte at the specified address
*/
uInt8 peek(uInt16 address, uInt8 flags);
/**
Change the byte at the specified address to the given value and
update the cycle count.
@param address The address where the value should be stored
@param value The value to be stored at the address
*/
void poke(uInt16 address, uInt8 value, uInt8 flags = 0);
/**
Get the 8-bit value of the Processor Status register.
@return The processor status register
*/
uInt8 PS() const {
uInt8 ps = 0x20;
if(N) ps |= 0x80;
if(V) ps |= 0x40;
if(B) ps |= 0x10;
if(D) ps |= 0x08;
if(I) ps |= 0x04;
if(!notZ) ps |= 0x02;
if(C) ps |= 0x01;
return ps;
}
/**
Change the Processor Status register to correspond to the given value.
@param ps The value to set the processor status register to
*/
void PS(uInt8 ps) {
N = ps & 0x80;
V = ps & 0x40;
B = true; // B = ps & 0x10; The 6507's B flag always true
D = ps & 0x08;
I = ps & 0x04;
notZ = !(ps & 0x02);
C = ps & 0x01;
}
/**
Called after an interrupt has be requested using irq() or nmi()
*/
void interruptHandler();
/**
Check whether halt was requested (RDY low) and notify
*/
void handleHalt();
/**
This is the actual dispatch function that does the grunt work. M6502::execute
wraps it and makes sure that any pending halt is processed before returning.
*/
void _execute(uInt64 cycles, DispatchResult& result);
#ifdef DEBUGGER_SUPPORT
/**
Check whether we are required to update hardware (TIA + RIOT) in lockstep
with the CPU and update the flag accordingly.
*/
void updateStepStateByInstruction();
#endif // DEBUGGER_SUPPORT
private:
/**
Bit fields used to indicate that certain conditions need to be
handled such as stopping execution, fatal errors, maskable interrupts
and non-maskable interrupts (in myExecutionStatus)
*/
enum
{
StopExecutionBit = 0x01,
FatalErrorBit = 0x02,
MaskableInterruptBit = 0x04,
NonmaskableInterruptBit = 0x08
};
uInt8 myExecutionStatus;
/// Pointer to the system the processor is installed in or the null pointer
System* mySystem;
/// Reference to the settings
const Settings& mySettings;
uInt8 A; // Accumulator
uInt8 X; // X index register
uInt8 Y; // Y index register
uInt8 SP; // Stack Pointer
uInt8 IR; // Instruction register
uInt16 PC; // Program Counter
bool N; // N flag for processor status register
bool V; // V flag for processor status register
bool B; // B flag for processor status register
bool D; // D flag for processor status register
bool I; // I flag for processor status register
bool notZ; // Z flag complement for processor status register
bool C; // C flag for processor status register
uInt8 icycles; // cycles of last instruction
/// Indicates the numer of distinct memory accesses
uInt32 myNumberOfDistinctAccesses;
/// Indicates the last address which was accessed
uInt16 myLastAddress;
/// Last cycle that triggered a breakpoint
uInt64 myLastBreakCycle;
/// Indicates the last address which was accessed specifically
/// by a peek or poke command
uInt16 myLastPeekAddress, myLastPokeAddress;
/// Indicates the last base (= non-mirrored) address which was
/// accessed specifically by a peek or poke command
uInt16 myLastPeekBaseAddress, myLastPokeBaseAddress;
// Indicates the type of the last access
uInt8 myFlags;
/// Indicates the last address used to access data by a peek command
/// for the CPU registers (S/A/X/Y)
Int32 myLastSrcAddressS, myLastSrcAddressA,
myLastSrcAddressX, myLastSrcAddressY;
/// Indicates the data address used by the last command that performed
/// a poke (currently, the last address used by STx)
/// If an address wasn't used (ie, as in immediate mode), the address
/// is set to zero
uInt16 myDataAddressForPoke;
/// Indicates the number of system cycles per processor cycle
static constexpr uInt32 SYSTEM_CYCLES_PER_CPU = 1;
/// Called when the processor enters halt state
onHaltCallback myOnHaltCallback;
/// Indicates whether RDY was pulled low
bool myHaltRequested;
#ifdef DEBUGGER_SUPPORT
enum CondAction
{
breakAction,
saveStateAction
};
Int32 evalCondBreaks() {
for(uInt32 i = 0; i < myCondBreaks.size(); i++)
if(myCondBreaks[i]->evaluate())
return i;
return -1; // no break hit
}
Int32 evalCondSaveStates()
{
for(uInt32 i = 0; i < myCondSaveStates.size(); i++)
if(myCondSaveStates[i]->evaluate())
return i;
return -1; // no save state point hit
}
Int32 evalCondTraps()
{
for(uInt32 i = 0; i < myTrapConds.size(); i++)
if(myTrapConds[i]->evaluate())
return i;
return -1; // no trapif hit
}
/// Pointer to the debugger for this processor or the null pointer
Debugger* myDebugger;
// Addresses for which the specified action should occur
PackedBitArray myBreakPoints;// , myReadTraps, myWriteTraps, myReadTrapIfs, myWriteTrapIfs;
TrapArray myReadTraps, myWriteTraps;
// Did we just now hit a trap?
bool myJustHitReadTrapFlag;
bool myJustHitWriteTrapFlag;
struct HitTrapInfo {
string message;
int address;
};
HitTrapInfo myHitTrapInfo;
vector<unique_ptr<Expression>> myCondBreaks;
StringList myCondBreakNames;
vector<unique_ptr<Expression>> myCondSaveStates;
StringList myCondSaveStateNames;
vector<unique_ptr<Expression>> myTrapConds;
StringList myTrapCondNames;
#endif // DEBUGGER_SUPPORT
bool myGhostReadsTrap; // trap on ghost reads
bool myReadFromWritePortBreak; // trap on reads from write ports
bool myStepStateByInstruction;
private:
// Following constructors and assignment operators not supported
M6502() = delete;
M6502(const M6502&) = delete;
M6502(M6502&&) = delete;
M6502& operator=(const M6502&) = delete;
M6502& operator=(M6502&&) = delete;
};
#endif