mirror of
https://github.com/makarcz/vm6502.git
synced 2024-12-26 22:31:18 +00:00
Performance optimizations.
Performance optimizations. Emulation speed measurement. Bug fixes.
This commit is contained in:
parent
20d12b5eae
commit
3cd0bc3f20
140
MKCpu.cpp
140
MKCpu.cpp
@ -351,6 +351,7 @@ void MKCpu::InitCpu()
|
||||
mReg.PageBoundary = false;
|
||||
mLocalMem = false;
|
||||
mExitAtLastRTS = true;
|
||||
mEnableHistory = false; // performance decrease when enabled
|
||||
if (NULL == mpMem) {
|
||||
mpMem = new Memory();
|
||||
if (NULL == mpMem) {
|
||||
@ -359,15 +360,15 @@ void MKCpu::InitCpu()
|
||||
mLocalMem = true;
|
||||
}
|
||||
// Set default BRK vector ($FFFE -> $FFF0)
|
||||
mpMem->Poke8bit(0xFFFE,0xF0); // LSB
|
||||
mpMem->Poke8bit(0xFFFF,0xFF); // MSB
|
||||
mpMem->Poke8bitImg(0xFFFE,0xF0); // LSB
|
||||
mpMem->Poke8bitImg(0xFFFF,0xFF); // MSB
|
||||
// Put RTI opcode at BRK procedure address.
|
||||
mpMem->Poke8bit(0xFFF0, OPCODE_RTI);
|
||||
mpMem->Poke8bitImg(0xFFF0, OPCODE_RTI);
|
||||
// Set default RESET vector ($FFFC -> $0200)
|
||||
mpMem->Poke8bit(0xFFFC,0x00); // LSB
|
||||
mpMem->Poke8bit(0xFFFD,0x02); // MSB
|
||||
mpMem->Poke8bitImg(0xFFFC,0x00); // LSB
|
||||
mpMem->Poke8bitImg(0xFFFD,0x02); // MSB
|
||||
// Set BRK code at the RESET procedure address.
|
||||
mpMem->Poke8bit(0x0200,OPCODE_BRK);
|
||||
mpMem->Poke8bitImg(0x0200,OPCODE_BRK);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -922,24 +923,24 @@ unsigned short MKCpu::GetArgWithMode(unsigned short addr, int mode)
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: Disassemble()
|
||||
* Purpose: Disassemble instruction and argument per addressing mode
|
||||
* Arguments: n/a - internal
|
||||
* Purpose: Disassemble op-code exec. history item.
|
||||
* Arguments: histit - pointer to OpCodeHistItem type
|
||||
* Returns: 0
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
unsigned short MKCpu::Disassemble()
|
||||
unsigned short MKCpu::Disassemble(OpCodeHistItem *histit)
|
||||
{
|
||||
char sArg[40];
|
||||
char sFmt[20];
|
||||
|
||||
strcpy(sFmt, "%s ");
|
||||
strcat(sFmt, mArgFmtTbl[mReg.LastAddrMode].c_str());
|
||||
strcat(sFmt, mArgFmtTbl[histit->LastAddrMode].c_str());
|
||||
sprintf(sArg, sFmt,
|
||||
((mOpCodesMap[(eOpCodes)mReg.LastOpCode]).amf.length() > 0
|
||||
? (mOpCodesMap[(eOpCodes)mReg.LastOpCode]).amf.c_str() : "???"),
|
||||
mReg.LastArg);
|
||||
((mOpCodesMap[(eOpCodes)histit->LastOpCode]).amf.length() > 0
|
||||
? (mOpCodesMap[(eOpCodes)histit->LastOpCode]).amf.c_str() : "???"),
|
||||
histit->LastArg);
|
||||
for (unsigned int i=0; i<strlen(sArg); i++) sArg[i] = toupper(sArg[i]);
|
||||
mReg.LastInstr = sArg;
|
||||
histit->LastInstr = sArg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -963,7 +964,7 @@ unsigned short MKCpu::Disassemble(unsigned short opcaddr, char *instrbuf)
|
||||
int opcode = -1;
|
||||
int addrmode = -1;
|
||||
|
||||
opcode = mpMem->Peek8bit(addr++);
|
||||
opcode = mpMem->Peek8bitImg(addr++);
|
||||
addrmode = (mOpCodesMap[(eOpCodes)opcode]).amf.length() > 0
|
||||
? (mOpCodesMap[(eOpCodes)opcode]).addrmode : -1;
|
||||
|
||||
@ -971,12 +972,12 @@ unsigned short MKCpu::Disassemble(unsigned short opcaddr, char *instrbuf)
|
||||
switch (mAddrModesLen[addrmode])
|
||||
{
|
||||
case 2:
|
||||
sprintf(sBuf, "$%02x ", mpMem->Peek8bit(addr));
|
||||
sprintf(sBuf, "$%02x ", mpMem->Peek8bitImg(addr));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
sprintf(sBuf, "$%02x $%02x", mpMem->Peek8bit(addr),
|
||||
mpMem->Peek8bit(addr+1));
|
||||
sprintf(sBuf, "$%02x $%02x", mpMem->Peek8bitImg(addr),
|
||||
mpMem->Peek8bitImg(addr+1));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -985,7 +986,7 @@ unsigned short MKCpu::Disassemble(unsigned short opcaddr, char *instrbuf)
|
||||
}
|
||||
strcpy(sFmt, "$%04x: $%02x %s %s ");
|
||||
strcat(sFmt, mArgFmtTbl[addrmode].c_str());
|
||||
sprintf(sArg, sFmt, opcaddr, mpMem->Peek8bit(opcaddr), sBuf,
|
||||
sprintf(sArg, sFmt, opcaddr, mpMem->Peek8bitImg(opcaddr), sBuf,
|
||||
((mOpCodesMap[(eOpCodes)opcode]).amf.length() > 0
|
||||
? (mOpCodesMap[(eOpCodes)opcode]).amf.c_str() : "???"),
|
||||
GetArgWithMode(addr,addrmode));
|
||||
@ -3821,19 +3822,22 @@ Regs *MKCpu::ExecOpcode(unsigned short memaddr)
|
||||
mReg.LastAddr = memaddr;
|
||||
unsigned char opcode = OPCODE_BRK;
|
||||
|
||||
// skip until all the cycles were completed
|
||||
// The op-code action was executed already once.
|
||||
// Now skip if the clock cycles for this op-code are not yet completed.
|
||||
if (mReg.CyclesLeft > 0) {
|
||||
mReg.CyclesLeft--;
|
||||
return &mReg;
|
||||
}
|
||||
|
||||
// if no IRQ waiting, get the next opcode and advance PC
|
||||
// If no IRQ waiting, get the next opcode and advance PC.
|
||||
// Otherwise the opcode is OPCODE_BRK and with IrqPending
|
||||
// flag set the IRQ sequence will be executed.
|
||||
if (!mReg.IrqPending) {
|
||||
opcode = mpMem->Peek8bit(mReg.PtrAddr++);
|
||||
}
|
||||
|
||||
// load CPU instruction details from map
|
||||
OpCode instrdet = mOpCodesMap[(eOpCodes)opcode];
|
||||
OpCode *instrdet = &mOpCodesMap[(eOpCodes)opcode];
|
||||
|
||||
SetFlag(false, FLAGS_BRK); // reset BRK flag - we want to detect
|
||||
mReg.SoftIrq = false; // software interrupt each time it
|
||||
@ -3843,27 +3847,34 @@ Regs *MKCpu::ExecOpcode(unsigned short memaddr)
|
||||
mReg.LastAddrMode = ADDRMODE_UND;
|
||||
mReg.LastArg = 0;
|
||||
|
||||
string s = (instrdet.amf.length() > 0
|
||||
? instrdet.amf.c_str() : "???");
|
||||
string s = (instrdet->amf.length() > 0
|
||||
? instrdet->amf.c_str() : "???");
|
||||
|
||||
if (s.compare("ILL") == 0) {
|
||||
// trap any illegal opcode
|
||||
mReg.SoftIrq = true;
|
||||
} else {
|
||||
// execute legal opcode
|
||||
mReg.CyclesLeft = instrdet.time - 1;
|
||||
OpCodeHdlrFn pfun = instrdet.pfun;
|
||||
// reset remaining cycles counter and execute legal opcode
|
||||
mReg.CyclesLeft = instrdet->time - 1;
|
||||
OpCodeHdlrFn pfun = instrdet->pfun;
|
||||
if (NULL != pfun) (this->*pfun)();
|
||||
}
|
||||
|
||||
|
||||
Disassemble();
|
||||
char histentry[80];
|
||||
sprintf(histentry,
|
||||
"$%04x: %-16s \t$%02x | $%02x | $%02x | $%02x | $%02x",
|
||||
mReg.LastAddr, mReg.LastInstr.c_str(), mReg.Acc, mReg.IndX,
|
||||
mReg.IndY, mReg.Flags, mReg.PtrStack);
|
||||
Add2History(histentry);
|
||||
// Update history/log of recently executed op-codes/instructions.
|
||||
if (mEnableHistory) {
|
||||
|
||||
OpCodeHistItem histentry;
|
||||
histentry.LastAddr = mReg.LastAddr;
|
||||
histentry.Acc = mReg.Acc;
|
||||
histentry.IndX = mReg.IndX;
|
||||
histentry.IndY = mReg.IndY;
|
||||
histentry.Flags = mReg.Flags;
|
||||
histentry.PtrStack = mReg.PtrStack;
|
||||
histentry.LastOpCode = mReg.LastOpCode;
|
||||
histentry.LastAddrMode = mReg.LastAddrMode;
|
||||
histentry.LastArg = mReg.LastArg;
|
||||
Add2History(histentry);
|
||||
}
|
||||
|
||||
return &mReg;
|
||||
}
|
||||
@ -3884,28 +3895,75 @@ Regs *MKCpu::GetRegs()
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: Add2History()
|
||||
* Purpose: Add entry to execute history.
|
||||
* Purpose: Add entry with last executed op-code, arguments and
|
||||
* CPU status to execute history.
|
||||
* Arguments: s - string (entry)
|
||||
* Returns: n/a
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void MKCpu::Add2History(string s)
|
||||
void MKCpu::Add2History(OpCodeHistItem histitem)
|
||||
{
|
||||
mExecHistory.push(s);
|
||||
while (mExecHistory.size() > 20) mExecHistory.pop();
|
||||
mExecHistory.push(histitem);
|
||||
while (mExecHistory.size() > OPCO_HIS_SIZE) mExecHistory.pop();
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: GetExecHistory()
|
||||
* Purpose: Return queue with execute history.
|
||||
* Purpose: Disassemble op-codes execute history stored in
|
||||
* mExecHistory and create/return queue of strings with
|
||||
* execute history in symbolic form (assembly mnemonics,
|
||||
* properly converted arguments in corresponding addressing
|
||||
* mode notation that adheres to MOS-6502 industry
|
||||
* standard.)
|
||||
* Arguments: n/a
|
||||
* Returns: queue<string>
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
queue<string> MKCpu::GetExecHistory()
|
||||
{
|
||||
return mExecHistory;
|
||||
queue<string> ret;
|
||||
queue<OpCodeHistItem> exechist(mExecHistory);
|
||||
|
||||
while (exechist.size()) {
|
||||
OpCodeHistItem item = exechist.front();
|
||||
Disassemble(&item);
|
||||
char histentry[80];
|
||||
sprintf(histentry,
|
||||
"$%04x: %-16s \t$%02x | $%02x | $%02x | $%02x | $%02x",
|
||||
item.LastAddr, item.LastInstr.c_str(), item.Acc, item.IndX,
|
||||
item.IndY, item.Flags, item.PtrStack);
|
||||
ret.push(histentry);
|
||||
exechist.pop();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: EnableExecHistory()
|
||||
* Purpose: Enable/disable recording of op-codes execute history.
|
||||
* Arguments: bool - true = enable / false = disable
|
||||
* Returns: n/a
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void MKCpu::EnableExecHistory(bool enexehist)
|
||||
{
|
||||
mEnableHistory = enexehist;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: IsExecHistoryEnabled()
|
||||
* Purpose: Check if op-code execute history is enabled.
|
||||
* Arguments: n/a
|
||||
* Returns: bool - true = enabled / false = disabled
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
bool MKCpu::IsExecHistoryEnabled()
|
||||
{
|
||||
return mEnableHistory;
|
||||
}
|
||||
|
||||
/*
|
||||
|
25
MKCpu.h
25
MKCpu.h
@ -12,6 +12,7 @@ using namespace std;
|
||||
namespace MKBasic {
|
||||
|
||||
#define DISS_BUF_SIZE 60 // disassembled instruction buffer size
|
||||
#define OPCO_HIS_SIZE 20 // size of op-code execute history queue
|
||||
|
||||
struct Regs {
|
||||
unsigned char Acc; // 8-bit accumulator
|
||||
@ -427,6 +428,8 @@ class MKCpu
|
||||
Regs *GetRegs();
|
||||
void SetRegs(Regs r);
|
||||
queue<string> GetExecHistory();
|
||||
void EnableExecHistory(bool enexehist);
|
||||
bool IsExecHistoryEnabled();
|
||||
unsigned short Disassemble(unsigned short addr,
|
||||
char *instrbuf); // Disassemble instruction in memory, return next instruction addr.
|
||||
void Reset(); // reset CPU
|
||||
@ -435,6 +438,20 @@ class MKCpu
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
// keeps all needed data to disassemble op-codes in execute history queue
|
||||
struct OpCodeHistItem {
|
||||
unsigned char Acc; // 8-bit accumulator
|
||||
unsigned char IndX; // 8-bit index register X
|
||||
unsigned char IndY; // 8-bit index register Y
|
||||
unsigned char Flags; // CPU flags
|
||||
unsigned char PtrStack; // 8-bit stack pointer (0-255).
|
||||
unsigned short LastAddr; // PC at the time of previous op-code
|
||||
string LastInstr; // instruction and argument executed in previous step
|
||||
int LastOpCode; // op-code of last instruction
|
||||
unsigned short LastArg; // argument to the last instruction
|
||||
int LastAddrMode; // addressing mode of last instruction
|
||||
};
|
||||
|
||||
struct Regs mReg; // CPU registers
|
||||
Memory *mpMem; // pointer to memory object
|
||||
@ -442,7 +459,8 @@ class MKCpu
|
||||
OpCodesMap mOpCodesMap; // hash table of all opcodes
|
||||
int mAddrModesLen[ADDRMODE_LENGTH]; // array of instructions lengths per addressing mode
|
||||
string mArgFmtTbl[ADDRMODE_LENGTH]; // array of instructions assembly formats per addressing mode
|
||||
queue<string> mExecHistory; // history of last 20 op-codes with arguments and registers statuses
|
||||
queue<OpCodeHistItem> mExecHistory; // keep the op-codes execute history
|
||||
bool mEnableHistory; // enable/disable execute history
|
||||
|
||||
|
||||
void InitCpu();
|
||||
@ -466,8 +484,9 @@ class MKCpu
|
||||
unsigned short GetAddrWithMode(int mode); // Get address of the byte argument with specified addr. mode
|
||||
unsigned short GetArgWithMode(unsigned short opcaddr,
|
||||
int mode); // Get argument from address with specified addr. mode
|
||||
unsigned short Disassemble(); // Disassemble instruction and argument per addressing mode
|
||||
void Add2History(string s); // add entry to op-codes execute history
|
||||
unsigned short Disassemble(OpCodeHistItem *histit); // Disassemble op-code exec history item
|
||||
//void Add2History(string s); // add entry to op-codes execute history
|
||||
void Add2History(OpCodeHistItem histitem); // add entry to op-codes execute history
|
||||
bool PageBoundary(unsigned short startaddr,
|
||||
unsigned short endaddr); // detect if page boundary was crossed
|
||||
|
||||
|
@ -130,9 +130,8 @@ Device MemMapDev::GetDevice(int devnum)
|
||||
++devit
|
||||
) {
|
||||
|
||||
Device dev = *devit;
|
||||
if (dev.num == devnum) {
|
||||
ret = dev;
|
||||
if (devit->num == devnum) {
|
||||
ret = *devit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -287,6 +286,7 @@ int MemMapDev::getch()
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -307,9 +307,17 @@ unsigned char MemMapDev::ReadCharKb(bool nonblock)
|
||||
static int c = ' ';
|
||||
if (mIOEcho && isprint(c)) putchar(c);
|
||||
fflush(stdout);
|
||||
if (!nonblock) while(!kbhit());
|
||||
else c = 0;
|
||||
c = getch();
|
||||
|
||||
if (nonblock) {
|
||||
// get a keystroke only if character is already in buffer
|
||||
if (kbhit()) c = getch();
|
||||
else c = 0;
|
||||
|
||||
} else {
|
||||
// wait for a keystroke, then get the character from buffer
|
||||
while(!kbhit());
|
||||
c = getch();
|
||||
}
|
||||
#if defined(LINUX)
|
||||
if (c == 3) { // capture CTRL-C in CONIO mode
|
||||
reset_terminal_mode();
|
||||
|
@ -225,7 +225,7 @@ class MemMapDev {
|
||||
|
||||
void set_conio_terminal_mode();
|
||||
int kbhit();
|
||||
int getch();
|
||||
int getch();
|
||||
|
||||
#endif
|
||||
|
||||
|
259
Memory.cpp
259
Memory.cpp
@ -1,5 +1,6 @@
|
||||
|
||||
#include "Memory.h"
|
||||
#include "MKGenException.h"
|
||||
|
||||
//#define DBG 1
|
||||
#if defined (DBG)
|
||||
@ -58,6 +59,9 @@ void Memory::Initialize()
|
||||
for (int i=0; i < 0xFFFF; i++) {
|
||||
m8bitMem[addr++] = 0;
|
||||
}
|
||||
for (int i=0; i < MEM_PAGE_SIZE; i++) {
|
||||
mMemPageDev[i] = -1;
|
||||
}
|
||||
mCharIOAddr = CHARIO_ADDR;
|
||||
mCharIOActive = false;
|
||||
mIOEcho = false;
|
||||
@ -138,37 +142,32 @@ unsigned char Memory::Peek8bit(unsigned short addr)
|
||||
{
|
||||
// if memory address is in range of any active memory mapped
|
||||
// devices, call corresponding device handling function
|
||||
bool cont_loop = true;
|
||||
for (vector<int>::iterator it = mActiveDevNumVec.begin();
|
||||
it != mActiveDevNumVec.end() && cont_loop;
|
||||
++it
|
||||
) {
|
||||
int mempg = addr / MEM_PAGE_SIZE;
|
||||
if (mMemPageDev[mempg] >= 0) {
|
||||
bool cont_loop = true;
|
||||
for (vector<Device>::iterator devit = mActiveDeviceVec.begin();
|
||||
devit != mActiveDeviceVec.end() && cont_loop;
|
||||
++devit
|
||||
) {
|
||||
|
||||
Device dev = mpMemMapDev->GetDevice(*it);
|
||||
if (dev.num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = dev.addr_ranges.begin();
|
||||
memrangeit != dev.addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
AddrRange addr_range = *memrangeit;
|
||||
if (addr >= addr_range.start_addr && addr <= addr_range.end_addr) {
|
||||
ReadFunPtr pfun = dev.read_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr);
|
||||
break;
|
||||
if (devit->num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = devit->addr_ranges.begin();
|
||||
memrangeit != devit->addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
if (addr >= memrangeit->start_addr && addr <= memrangeit->end_addr) {
|
||||
ReadFunPtr pfun = devit->read_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == devit->num);
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == dev.num);
|
||||
}
|
||||
/*
|
||||
if (mCharIOActive && addr == mCharIOAddr) {
|
||||
m8bitMem[addr] = ReadCharKb(false); // blocking mode input
|
||||
} else if (mCharIOActive && addr == mCharIOAddr+1) {
|
||||
m8bitMem[addr] = ReadCharKb(true); // non-blocking mode input
|
||||
}*/
|
||||
|
||||
return m8bitMem[addr];
|
||||
}
|
||||
@ -204,38 +203,32 @@ unsigned short Memory::Peek16bit(unsigned short addr)
|
||||
|
||||
// if memory address is in range of any active memory mapped
|
||||
// devices, call corresponding device handling function
|
||||
bool cont_loop = true;
|
||||
for (vector<int>::iterator it = mActiveDevNumVec.begin();
|
||||
it != mActiveDevNumVec.end() && cont_loop;
|
||||
++it
|
||||
) {
|
||||
Device dev = mpMemMapDev->GetDevice(*it);
|
||||
if (dev.num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = dev.addr_ranges.begin();
|
||||
memrangeit != dev.addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
AddrRange addr_range = *memrangeit;
|
||||
if (addr >= addr_range.start_addr && addr <= addr_range.end_addr) {
|
||||
ReadFunPtr pfun = dev.read_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr);
|
||||
break;
|
||||
int mempg = addr / MEM_PAGE_SIZE;
|
||||
if (mMemPageDev[mempg] >= 0) {
|
||||
bool cont_loop = true;
|
||||
for (vector<Device>::iterator devit = mActiveDeviceVec.begin();
|
||||
devit != mActiveDeviceVec.end() && cont_loop;
|
||||
++devit
|
||||
) {
|
||||
if (devit->num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = devit->addr_ranges.begin();
|
||||
memrangeit != devit->addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
if (addr >= memrangeit->start_addr && addr <= memrangeit->end_addr) {
|
||||
ReadFunPtr pfun = devit->read_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == devit->num);
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == dev.num);
|
||||
}
|
||||
|
||||
/*
|
||||
if (mCharIOActive && addr == mCharIOAddr) {
|
||||
m8bitMem[addr] = ReadCharKb(false); // blocking mode input
|
||||
} else if (mCharIOActive && addr == mCharIOAddr+1) {
|
||||
m8bitMem[addr] = ReadCharKb(true); // non-blocking mode input
|
||||
}*/
|
||||
|
||||
ret = m8bitMem[addr++];
|
||||
ret += m8bitMem[addr] * 256;
|
||||
|
||||
@ -259,35 +252,32 @@ void Memory::Poke8bit(unsigned short addr, unsigned char val)
|
||||
{
|
||||
// if memory address is in range of any active memory mapped
|
||||
// devices, call corresponding device handling function
|
||||
bool cont_loop = true;
|
||||
for (vector<int>::iterator it = mActiveDevNumVec.begin();
|
||||
it != mActiveDevNumVec.end() && cont_loop;
|
||||
++it
|
||||
) {
|
||||
Device dev = mpMemMapDev->GetDevice(*it);
|
||||
if (dev.num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = dev.addr_ranges.begin();
|
||||
memrangeit != dev.addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
AddrRange addr_range = *memrangeit;
|
||||
if (addr >= addr_range.start_addr && addr <= addr_range.end_addr) {
|
||||
WriteFunPtr pfun = dev.write_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr,(int)val);
|
||||
m8bitMem[addr] = val;
|
||||
break;
|
||||
int mempg = addr / MEM_PAGE_SIZE;
|
||||
if (mMemPageDev[mempg] >= 0) {
|
||||
bool cont_loop = true;
|
||||
for (vector<Device>::iterator devit = mActiveDeviceVec.begin();
|
||||
devit != mActiveDeviceVec.end() && cont_loop;
|
||||
++devit
|
||||
) {
|
||||
if (devit->num >= 0) {
|
||||
for (MemAddrRanges::iterator memrangeit = devit->addr_ranges.begin();
|
||||
memrangeit != devit->addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
if (addr >= memrangeit->start_addr && addr <= memrangeit->end_addr) {
|
||||
WriteFunPtr pfun = devit->write_fun_ptr;
|
||||
if (pfun != NULL) {
|
||||
cont_loop = false;
|
||||
(mpMemMapDev->*pfun)((int)addr,(int)val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == devit->num);
|
||||
}
|
||||
}
|
||||
mDispOp = (DEVNUM_GRDISP == dev.num);
|
||||
}
|
||||
/*
|
||||
if (mCharIOActive && addr == mCharIOAddr)
|
||||
PutCharIO(val);
|
||||
*/
|
||||
|
||||
if (!mROMActive || (addr < mROMBegin || addr > mROMEnd)) {
|
||||
m8bitMem[addr] = val;
|
||||
}
|
||||
@ -319,7 +309,6 @@ void Memory::Poke8bitImg(unsigned short addr, unsigned char val)
|
||||
void Memory::SetCharIO(unsigned short addr, bool echo)
|
||||
{
|
||||
mCharIOAddr = addr;
|
||||
//mCharIOActive = true;
|
||||
mIOEcho = echo;
|
||||
|
||||
AddrRange addr_range(addr, addr+1);
|
||||
@ -330,9 +319,9 @@ void Memory::SetCharIO(unsigned short addr, bool echo)
|
||||
dev_params.push_back(dev_par);
|
||||
memaddr_ranges.push_back(addr_range);
|
||||
|
||||
if (false == mCharIOActive) AddDevice(DEVNUM_CHARIO);
|
||||
mCharIOActive = true;
|
||||
SetupDevice(DEVNUM_CHARIO, memaddr_ranges, dev_params);
|
||||
if (false == mCharIOActive) AddDevice(DEVNUM_CHARIO);
|
||||
mCharIOActive = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -380,9 +369,9 @@ void Memory::SetGraphDisp(unsigned short addr)
|
||||
dev_params.push_back(dev_par);
|
||||
memaddr_ranges.push_back(addr_range);
|
||||
|
||||
if (false == mGraphDispActive) AddDevice(DEVNUM_GRDISP);
|
||||
mGraphDispActive = true;
|
||||
SetupDevice(DEVNUM_GRDISP, memaddr_ranges, dev_params);
|
||||
if (false == mGraphDispActive) AddDevice(DEVNUM_GRDISP);
|
||||
mGraphDispActive = true;
|
||||
mpMemMapDev->ActivateGraphDisp();
|
||||
}
|
||||
|
||||
@ -458,14 +447,49 @@ bool Memory::IsROMEnabled()
|
||||
* Method: AddDevice()
|
||||
* Purpose: Add device number to active devices list.
|
||||
* Arguments: devnum - device number
|
||||
* Returns: 0
|
||||
* Returns: -1 if device is not supported OR already cached
|
||||
* devnum - device number if it was found
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
int Memory::AddDevice(int devnum)
|
||||
{
|
||||
mActiveDevNumVec.push_back(devnum);
|
||||
int ret = -1;
|
||||
bool found = false;
|
||||
Device dev = mpMemMapDev->GetDevice(devnum);
|
||||
if (dev.num >= 0) {
|
||||
for (vector<Device>::iterator devit = mActiveDeviceVec.begin();
|
||||
devit != mActiveDeviceVec.end();
|
||||
++devit
|
||||
) {
|
||||
|
||||
return 0;
|
||||
if (devit->num == devnum) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if device not found in local cache, add it
|
||||
if (!found) {
|
||||
mActiveDeviceVec.push_back(dev);
|
||||
ret = devnum;
|
||||
|
||||
// update the device usage flag in memory pages devices array mMemPageDev
|
||||
for (MemAddrRanges::iterator memrangeit = dev.addr_ranges.begin();
|
||||
memrangeit != dev.addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
|
||||
int pgnum = memrangeit->start_addr / MEM_PAGE_SIZE;
|
||||
while (pgnum < MEM_PAGE_SIZE) {
|
||||
mMemPageDev[pgnum] = devnum;
|
||||
pgnum++;
|
||||
if (pgnum * MEM_PAGE_SIZE > memrangeit->end_addr) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // END if (dev.num >= 0)
|
||||
// else device with wuch number is not supported
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -473,25 +497,56 @@ int Memory::AddDevice(int devnum)
|
||||
* Method: DeleteDevice()
|
||||
* Purpose: Delete device number from active devices list.
|
||||
* Arguments: devnum - device number
|
||||
* Returns: 0
|
||||
* Returns: >=0 if device was found in local cache and deleted
|
||||
* -1 if device was not found
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
int Memory::DeleteDevice(int devnum)
|
||||
{
|
||||
vector<int> active_new;
|
||||
vector<Device> actdev_new;
|
||||
int ret = -1;
|
||||
|
||||
for (vector<int>::iterator it = mActiveDevNumVec.begin();
|
||||
it != mActiveDevNumVec.end();
|
||||
++it
|
||||
) {
|
||||
if (*it != devnum) {
|
||||
active_new.push_back(*it);
|
||||
}
|
||||
for (int i=0; i < MEM_PAGE_SIZE; i++) {
|
||||
mMemPageDev[i] = -1;
|
||||
}
|
||||
mActiveDevNumVec.clear();
|
||||
mActiveDevNumVec = active_new;
|
||||
// device is deleted by refreshing local active devices cache
|
||||
// the device to be deleted is skipped and not re-added to refreshed
|
||||
// cache vector
|
||||
for (vector<Device>::iterator devit = mActiveDeviceVec.begin();
|
||||
devit != mActiveDeviceVec.end();
|
||||
++devit
|
||||
) {
|
||||
|
||||
return 0;
|
||||
if (devit->num != devnum) {
|
||||
|
||||
Device dev = mpMemMapDev->GetDevice(devit->num);
|
||||
|
||||
if (dev.num < 0)
|
||||
throw MKGenException("Unsupported device in local cache");
|
||||
|
||||
actdev_new.push_back(mpMemMapDev->GetDevice(devit->num));
|
||||
|
||||
// update the device number in memory pages devices array mMemPageDev
|
||||
for (MemAddrRanges::iterator memrangeit = devit->addr_ranges.begin();
|
||||
memrangeit != devit->addr_ranges.end();
|
||||
++memrangeit
|
||||
) {
|
||||
|
||||
int pgnum = memrangeit->start_addr / MEM_PAGE_SIZE;
|
||||
while (pgnum < MEM_PAGE_SIZE) {
|
||||
mMemPageDev[pgnum] = devit->num;
|
||||
pgnum++;
|
||||
if (pgnum * MEM_PAGE_SIZE > memrangeit->end_addr) break;
|
||||
}
|
||||
}
|
||||
|
||||
} else ret++; // indicating that the device was found in cache
|
||||
}
|
||||
// refresh local active devices cache
|
||||
mActiveDeviceVec.clear();
|
||||
mActiveDeviceVec = actdev_new;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -508,14 +563,12 @@ void Memory::SetupDevice(int devnum,
|
||||
MemAddrRanges memranges,
|
||||
DevParams params)
|
||||
{
|
||||
for (vector<int>::iterator it = mActiveDevNumVec.begin();
|
||||
it != mActiveDevNumVec.end();
|
||||
++it
|
||||
) {
|
||||
if (devnum == *it) {
|
||||
mpMemMapDev->SetupDevice(devnum, memranges, params);
|
||||
break;
|
||||
}
|
||||
Device dev = mpMemMapDev->GetDevice(devnum);
|
||||
if (dev.num >= 0) {
|
||||
mpMemMapDev->SetupDevice(devnum, memranges, params);
|
||||
// reload device to local vector because its setup has changed
|
||||
// but only if device was cached locally already (active)
|
||||
if (0 <= DeleteDevice(devnum)) AddDevice(devnum);
|
||||
}
|
||||
if (DEVNUM_CHARIO == devnum) {
|
||||
mCharIOAddr = mpMemMapDev->GetCharIOAddr();
|
||||
|
10
Memory.h
10
Memory.h
@ -5,6 +5,7 @@
|
||||
#include "MemMapDev.h"
|
||||
|
||||
#define MAX_8BIT_ADDR 0xFFFF
|
||||
#define MEM_PAGE_SIZE 0x100
|
||||
#define ROM_BEGIN 0xD000
|
||||
#define ROM_END 0xDFFF
|
||||
#define MIN_ROM_BEGIN 0x0200
|
||||
@ -54,14 +55,19 @@ class Memory
|
||||
private:
|
||||
|
||||
unsigned char m8bitMem[MAX_8BIT_ADDR+1];
|
||||
// array of device usage for each memory page
|
||||
// this is performance optimization array that keeps values >= 0 under the
|
||||
// indexes of memory pages where the memory mapped device is active or -1
|
||||
// if there is no active device on given memory page
|
||||
int mMemPageDev[MEM_PAGE_SIZE];
|
||||
unsigned short mCharIOAddr;
|
||||
bool mCharIOActive;
|
||||
bool mIOEcho;
|
||||
unsigned short mROMBegin;
|
||||
unsigned short mROMEnd;
|
||||
bool mROMActive;
|
||||
vector<int> mActiveDevNumVec; // active devices numbers
|
||||
MemMapDev *mpMemMapDev; // pointer to MemMapDev object
|
||||
vector<Device> mActiveDeviceVec; // active devices
|
||||
MemMapDev *mpMemMapDev; // pointer to MemMapDev object
|
||||
bool mGraphDispActive;
|
||||
bool mDispOp;
|
||||
|
||||
|
156
ReadMe.txt
156
ReadMe.txt
@ -281,10 +281,23 @@ code and also emulated CPU Acc register contains the same code.
|
||||
|
||||
Reading from IOADDR+1 inside 6502 code invokes a non-blocking character
|
||||
input function from user's DOS/shell session.
|
||||
This function is different than blocking one in one respect.
|
||||
This function will return value 0 in the memory location and Acc register
|
||||
if there was no key pressed by the user (no character waiting in buffer).
|
||||
If there was a key typed, the function will act as the blocking counterpart.
|
||||
|
||||
The difference between blocking and non-blocking character input from 6502
|
||||
code perspective:
|
||||
|
||||
- blocking
|
||||
|
||||
Just read from the address. Emulated device will wait for the character
|
||||
input. The 6502 code does not need to be polling the address for
|
||||
a keystroke.
|
||||
|
||||
- non-blocking
|
||||
|
||||
If there is no character/keystroke in the keyboard buffer, the function
|
||||
will move on returning the value of 0. If implementing waiting character
|
||||
input in 6502 code with this function, the 6502 code needs to be polling
|
||||
the value returned from this address in a loop until it is different
|
||||
than 0.
|
||||
|
||||
Note that there is no clearly distinguished prompt generated by emulator
|
||||
when there is character input operation performed. It is designed like that
|
||||
@ -308,7 +321,7 @@ It is only written to the emulated display's text memory.
|
||||
|
||||
When VM is running in non-debug code execution mode (X - execute from new
|
||||
address), the character is also output to the native DOS/shell console
|
||||
(user's screen).
|
||||
(user's screen, a.k.a. standard output).
|
||||
The character output history is therefore always kept in the memory of the
|
||||
emulated text display device and can be recalled to the screen in debug
|
||||
console with command 'T'.
|
||||
@ -316,6 +329,7 @@ console with command 'T'.
|
||||
There are 2 reasons for this:
|
||||
|
||||
* Performance.
|
||||
|
||||
In previous version only the emulated text display device approach was used.
|
||||
That meant that each time there was a new character in the emulated display
|
||||
buffer, the entire emulated text output device screen had to be refreshed on
|
||||
@ -323,12 +337,16 @@ the DOS/shell console. That was slow and caused screen flicker when characters
|
||||
were output at high rate of speed.
|
||||
|
||||
* Record of character I/O operation.
|
||||
|
||||
During step-by-step debugging or multiple-step animated registers mode, any
|
||||
characters output is immediately replaced by the registers and stack status
|
||||
on the screen and is not visible on the screen. However user must be able to
|
||||
debug applications that perform character I/O operations and recall the
|
||||
history of the characters output to the emulated text display device. This is
|
||||
when shadow copy of character I/O comes handy.
|
||||
on the screen and is not visible on the screen. However user, while debugging
|
||||
must be able to recall the history of the characters output to the emulated
|
||||
text display device if user's program uses character I/O, for normal operation
|
||||
or diagnostic purposes. This is when shadow copy of character I/O comes handy.
|
||||
In real life, text output devices like text terminals, serial terminals,
|
||||
especially the software emulated ones also offer the facilities to buffer the
|
||||
output to memory for later review (history).
|
||||
|
||||
4. ROM (read-only memory) emulation.
|
||||
|
||||
@ -356,6 +374,13 @@ ROMEND
|
||||
address
|
||||
ENROM
|
||||
|
||||
For more sophisticated memory mapping schemes including multiple ROM ranges
|
||||
and/or MMU-s that allow to read from ROM, write to RAM in the same range etc.
|
||||
I suggest to write your ROM device emulating code and use it to expand the
|
||||
functionality of this emulator using memory Mapped Devices framework,
|
||||
explained later in this document and in the programmer's reference manual
|
||||
document included with this project.
|
||||
|
||||
5. Additional comments and remarks.
|
||||
|
||||
IOADDR is permitted to be located in the emulated ROM memory range.
|
||||
@ -380,7 +405,8 @@ The VMachine class calls the ExecOpcode() method as fast as possible, so it is
|
||||
not real-time accurate, as already mentioned. To implement real-time accurate
|
||||
emulation, the MKCpu::ExecOpcode() method calls would have to be synchronized
|
||||
to some fairly accurate time scale (some kind of timer thread or a different
|
||||
solution).
|
||||
solution) to emulate the timing on the bus signals level. This emulator does
|
||||
not implement such level of accuracy.
|
||||
|
||||
6. Debugger Console Command Reference.
|
||||
|
||||
@ -432,7 +458,7 @@ V - toggle graphics display (video) emulation
|
||||
When enabled, window with graphics screen will open and several
|
||||
registers are available to control the device starting at provided
|
||||
base address. Read programmers reference for detailed documentation
|
||||
regarding the available registers and their functions.
|
||||
regarding the available registers and their functions.
|
||||
R - show registers
|
||||
Displays CPU registers, flags and stack.
|
||||
Y - snapshot
|
||||
@ -468,7 +494,7 @@ K - toggle ROM emulation
|
||||
(read-only memory) will be mapped. Default range: $D000-$DFFF.
|
||||
L - load memory image
|
||||
Usage: L [image_type] [image_name]
|
||||
Where:
|
||||
Where:
|
||||
image_type - A - (auto), B (binary), H (Intel HEX) OR D (definition),
|
||||
image_name - name of the image file.
|
||||
This function allows to load new memory image from either binary
|
||||
@ -478,7 +504,7 @@ L - load memory image
|
||||
The binary image is always loaded from address 0x0000 and can be up to
|
||||
64kB long. The definition file format is a plain text file that can
|
||||
contain following keywords and data:
|
||||
|
||||
|
||||
ADDR This keyword defines the run address of the executable code.
|
||||
It is optional, but if exists, it must be the 1-st keyword
|
||||
in the definition file.
|
||||
@ -523,11 +549,32 @@ L - load memory image
|
||||
in decimal or hexadecimal format.
|
||||
|
||||
RESET Enables auto-reset of the CPU. After loading the memory
|
||||
definition file, the CPU reset sequence will be initiated.
|
||||
definition file, the CPU reset sequence will be initiated.
|
||||
|
||||
ENGRAPH Enables raster graphics device emulation.
|
||||
|
||||
GRAPHADDR Defines the base address of raster graphics device. The next
|
||||
line that follows sets the address in decimal or hexadecimal
|
||||
format.
|
||||
|
||||
NOTE: The binary image file can contain a header which contains
|
||||
definitions corresponding to the above parameters at fixed
|
||||
positions. This header is created when user saves the snapshot of
|
||||
current emulator memory image and status. Example use scenario:
|
||||
* User loads the image definition file.
|
||||
* User adjusts various parameters of the emulator
|
||||
(enables/disables devices, sets addresses, changes memory
|
||||
contents).
|
||||
* User saves the snapshot with 'Y' command.
|
||||
* Next time user loads the snapshot image, all the parameters
|
||||
and memory contents stick. This way game status can be saved
|
||||
or a BASIC interpreter with BASIC program in it.
|
||||
See command 'Y' for details.
|
||||
|
||||
O - display op-codes history
|
||||
Show the history of last executed op-codes/instructions, full with
|
||||
disassembled mnemonic and argument.
|
||||
disassembled mnemonic, argument and CPU registers and status.
|
||||
NOTE: op-codes execute history must be enabled, see command 'U'.
|
||||
D - diassemble code in memory
|
||||
Usage: D [startaddr] [endaddr]
|
||||
Where: startaddr,endaddr - hexadecimal address [0000..FFFF].
|
||||
@ -543,6 +590,11 @@ D - diassemble code in memory
|
||||
jumping to main loop. The reset routine disables trapping last RTS
|
||||
opcode if stack is empty, so the VM will never return from opcodes
|
||||
execution loop unless user interrupts with CTRL-C or CTRL-Break.
|
||||
? - display commands menu
|
||||
Display the menu of all available in Debug Console commands.
|
||||
U - enable/disable exec. history
|
||||
Toggle enable/disable of op-codes execute history.
|
||||
Disabling this feature improves performance.
|
||||
|
||||
NOTE:
|
||||
1. If no arguments provided, each command will prompt user to enter
|
||||
@ -551,7 +603,7 @@ NOTE:
|
||||
by pressing CTRL-C or CTRL-Pause/Break, which will generate
|
||||
a "Operator Interrupt". However in the character input mode
|
||||
use CTRL-Y combination or CTRL-Break (DOS), CTRL-C (Linux).
|
||||
You may need to press ENTER after that in input mode (DOS)
|
||||
You may need to press ENTER after that in input mode (DOS).
|
||||
|
||||
7. Command line usage.
|
||||
|
||||
@ -628,8 +680,8 @@ Programming API responsible for modeling this functionality is implemented
|
||||
in Memory and MemMapDev classes. The Memory class implements access to
|
||||
specific memory locations and maintains the memory image.
|
||||
The MemMapDev class implements specific device address spaces and handling
|
||||
methods that are triggered when addresses of the device are accessed by the
|
||||
microprocessor.
|
||||
methods that are triggered when addresses that belong to the device are
|
||||
accessed by the microprocessor.
|
||||
Programmers can expand the functionality of this emulator by adding necessary
|
||||
code emulating specific devices in MemMapDev and Memory classes implementation
|
||||
and header files. In current version, two basic devices are implemented:
|
||||
@ -640,7 +692,7 @@ E.g.:
|
||||
Character I/O device uses just 2 memory locations, one for non-blocking I/O
|
||||
and one for blocking I/O. Writing to location causes character output, while
|
||||
reading from location waits for character input (blocking mode) or reads the
|
||||
character from keyboard buffer if available (non-blocking mode).
|
||||
character from keyboard buffer if available (non-blocking mode) or returns 0.
|
||||
The graphics display can be accessed by writing to multiple memory locations.
|
||||
|
||||
If we assume that GRDEVBASE is the base address of the Graphics Device, there
|
||||
@ -685,7 +737,7 @@ Reading from registers has no effect (returns 0).
|
||||
|
||||
Above method of interfacing GD requires no dedicated graphics memory space
|
||||
in VM's RAM. It is also simple to implement.
|
||||
The downside - slow performance (multiple memory writes to select/unselect
|
||||
The downside - slower performance (multiple memory writes to select/unselect
|
||||
a pixel or set color).
|
||||
I plan to add graphics frame buffer in the VM's RAM address space in future
|
||||
release.
|
||||
@ -693,7 +745,67 @@ release.
|
||||
Simple demo program written in EhBasic that shows how to drive the graphics
|
||||
screen is included: grdevdemo.bas.
|
||||
|
||||
10. Problems, issues, bugs.
|
||||
10. Performance considerations.
|
||||
|
||||
Program measures the emulation performance and displays it in the Debug
|
||||
Console. It uses a 1 MHz CPU as a reference to return % of speed compared to
|
||||
assumed 1,000,000 CPU cycles or clock ticks per second - which is considered
|
||||
a 100 % speed.
|
||||
Performance is measured during the whole execution cycle and calculated at the
|
||||
end of the run. Captured speed is summed with previous result and divided by 2
|
||||
to produce average emulation speed during single session.
|
||||
|
||||
This emulator has been optimized for performance. I had issues with
|
||||
emulation speed in previous version, but I took a good look at all the
|
||||
critical parts of code and fixed the problems. I am sure there is still
|
||||
space for improvement, but now the emulation speed leaves good margin
|
||||
for expansion with new emulated peripherals and still should compare
|
||||
well to the model 1 MHz CPU.
|
||||
Emulating of pure 6502 machine code with all peripherals (memory mapped
|
||||
devices, I/O etc.) emulation disabled and time critical debugging facility,
|
||||
the op-codes execute history also disabled, returns performance in range of
|
||||
646 % (PC1) and 468 % (PC2) (* see annotation below).
|
||||
Enabling the op-code execute history drops the performance.
|
||||
With all peripherals disabled and op-code history enabled we are down to
|
||||
411 % on PC1 and 312 % on PC2.
|
||||
|
||||
Enabling and adding the emulated memory mapped devices to the pool may cause
|
||||
the emulation speed to drop as well. Hovever even with currently implemented
|
||||
peripherals (char I/O, graphics raster device) enabled and actively used and
|
||||
op-codes execute history enabled the performance is still well above 300 %
|
||||
on both PC1 and on PC2 (* see annotations for PC configurations/specs).
|
||||
The same case but with op-code execute history disabled - performance exceeds
|
||||
400 % on both PC configurations.
|
||||
|
||||
Currently the main emulation loop is not synchronized to an accurate
|
||||
clock tick or raster synchronization signal but just runs as fast as it can.
|
||||
Therefore emulation speed may vary per PC and per current load on the system.
|
||||
|
||||
If this code is to be used as a template to implement emulator of a real-world
|
||||
machine, like C-64 or Apple I, it may need some work to improve performance,
|
||||
but I think is leaves a pretty good margin for expansion as it is.
|
||||
On a fast PC (* see annotation) the emulation speed above 600 % with
|
||||
basically nothing but CPU emulated and op-codes execute history disabled
|
||||
(which should be disabled by default as it is needed for debugging purposes
|
||||
only) is IMO decent if we don't want to emulate MOS 6502 machine with clock
|
||||
much faster than 1 MHz.
|
||||
|
||||
Annotations to 'Performance considerations':
|
||||
*)
|
||||
|
||||
PC1 stats:
|
||||
Type: Desktop
|
||||
CPU: 2.49 GHz (64-bit Quad-core Q8300)
|
||||
RAM: 4,060 MB
|
||||
OS: Windows 10 Pro (no SP) [6.2.9200]
|
||||
|
||||
PC2 stats:
|
||||
Type: Laptop
|
||||
CPU: 2.3 GHz (64-bit Quad-core i5-6300HQ)
|
||||
RAM: 15.9 GB
|
||||
OS: Win 10 Home.
|
||||
|
||||
11. Problems, issues, bugs.
|
||||
|
||||
* Regaining focus of the graphics window when it is not being written to by the
|
||||
6502 code is somewhat flakey. Since the window has no title bar, user can
|
||||
@ -702,7 +814,7 @@ screen is included: grdevdemo.bas.
|
||||
of emulator while in emulation mode should bring the graphics window back
|
||||
to front.
|
||||
|
||||
11. Warranty and License Agreement.
|
||||
12. Warranty and License Agreement.
|
||||
|
||||
This software is provided with No Warranty.
|
||||
I (The Author) will not be held responsible for any damage to computer
|
||||
|
100
VMachine.cpp
100
VMachine.cpp
@ -81,6 +81,11 @@ void VMachine::InitVM()
|
||||
mOpInterrupt = false;
|
||||
mpRAM = new Memory();
|
||||
|
||||
mPerfStats.cycles = 0;
|
||||
mPerfStats.micro_secs = 0;
|
||||
mPerfStats.perf_onemhz = 0;
|
||||
mPerfStats.prev_cycles = 0;
|
||||
mPerfStats.prev_usec = 0;
|
||||
mOldStyleHeader = false;
|
||||
mError = VMERR_OK;
|
||||
mAutoExec = false;
|
||||
@ -225,6 +230,39 @@ void VMachine::ShowDisp()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method:
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
int VMachine::CalcCurrPerf()
|
||||
{
|
||||
auto lap = high_resolution_clock::now();
|
||||
auto beg = mPerfStats.begin_time;
|
||||
mPerfStats.micro_secs = duration_cast<microseconds>(lap-beg).count();
|
||||
|
||||
if (mPerfStats.micro_secs > 0) {
|
||||
int currperf = (int)
|
||||
(((double)mPerfStats.cycles / (double)mPerfStats.micro_secs) * 100.0);
|
||||
if (mPerfStats.perf_onemhz == 0)
|
||||
mPerfStats.perf_onemhz = currperf;
|
||||
else
|
||||
mPerfStats.perf_onemhz = (mPerfStats.perf_onemhz + currperf) / 2;
|
||||
|
||||
mPerfStats.prev_cycles = mPerfStats.cycles;
|
||||
mPerfStats.prev_usec = mPerfStats.micro_secs;
|
||||
mPerfStats.cycles = 0;
|
||||
mPerfStats.micro_secs = 0;
|
||||
mPerfStats.begin_time = high_resolution_clock::now();
|
||||
}
|
||||
|
||||
return mPerfStats.perf_onemhz;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: Run()
|
||||
@ -240,14 +278,17 @@ Regs *VMachine::Run()
|
||||
mOpInterrupt = false;
|
||||
ClearScreen();
|
||||
ShowDisp();
|
||||
mPerfStats.cycles = 0;
|
||||
mPerfStats.micro_secs = 0;
|
||||
mPerfStats.begin_time = high_resolution_clock::now();
|
||||
while (true) {
|
||||
mPerfStats.cycles++;
|
||||
cpureg = Step();
|
||||
if (cpureg->CyclesLeft == 0 && mCharIO) {
|
||||
ShowDisp();
|
||||
}
|
||||
if (cpureg->SoftIrq || mOpInterrupt)
|
||||
break;
|
||||
if (cpureg->CyclesLeft == 0 && mCharIO) ShowDisp();
|
||||
if (cpureg->SoftIrq || mOpInterrupt) break;
|
||||
//if (mPerfStats.cycles == PERFSTAT_INTERVAL) CalcCurrPerf();
|
||||
}
|
||||
CalcCurrPerf();
|
||||
|
||||
ShowDisp();
|
||||
|
||||
@ -285,20 +326,39 @@ Regs *VMachine::Exec()
|
||||
mOpInterrupt = false;
|
||||
ClearScreen();
|
||||
ShowDisp();
|
||||
mPerfStats.cycles = 0;
|
||||
mPerfStats.micro_secs = 0;
|
||||
mPerfStats.begin_time = high_resolution_clock::now();
|
||||
while (true) {
|
||||
mPerfStats.cycles++;
|
||||
cpureg = Step();
|
||||
if (cpureg->CyclesLeft == 0 && mCharIO) {
|
||||
cout << mpDisp->GetLastChar();
|
||||
cout << flush;
|
||||
}
|
||||
if (cpureg->LastRTS || mOpInterrupt) break;
|
||||
//if (mPerfStats.cycles == PERFSTAT_INTERVAL) CalcCurrPerf();
|
||||
}
|
||||
CalcCurrPerf();
|
||||
|
||||
ShowDisp();
|
||||
|
||||
return cpureg;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method:
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
PerfStats VMachine::GetPerfStats()
|
||||
{
|
||||
return mPerfStats;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: Exec()
|
||||
@ -327,7 +387,9 @@ Regs *VMachine::Step()
|
||||
Regs *cpureg = NULL;
|
||||
|
||||
cpureg = mpCPU->ExecOpcode(addr);
|
||||
if (mGraphDispActive) mpRAM->GraphDisp_ReadEvents();
|
||||
if (mGraphDispActive && cpureg->CyclesLeft == 0) {
|
||||
mpRAM->GraphDisp_ReadEvents();
|
||||
}
|
||||
addr = cpureg->PtrAddr;
|
||||
mRunAddr = addr;
|
||||
|
||||
@ -1619,4 +1681,30 @@ int VMachine::GetLastError()
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method:
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void VMachine::EnableExecHistory(bool enexehist)
|
||||
{
|
||||
mpCPU->EnableExecHistory(enexehist);
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method:
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
bool VMachine::IsExecHistoryActive()
|
||||
{
|
||||
return mpCPU->IsExecHistoryEnabled();
|
||||
}
|
||||
|
||||
} // namespace MKBasic
|
||||
|
22
VMachine.h
22
VMachine.h
@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <chrono>
|
||||
#include "system.h"
|
||||
#include "MKCpu.h"
|
||||
#include "Memory.h"
|
||||
@ -20,8 +21,10 @@
|
||||
#define HDRDATALEN 128
|
||||
#define HDRDATALEN_OLD 15
|
||||
#define HEXEOF ":00000001FF"
|
||||
//#define PERFSTAT_INTERVAL 30000000
|
||||
|
||||
using namespace std;
|
||||
using namespace chrono;
|
||||
|
||||
namespace MKBasic {
|
||||
|
||||
@ -63,6 +66,16 @@ enum eVMErrors {
|
||||
VMERR_UNKNOWN // unknown error
|
||||
};
|
||||
|
||||
struct PerfStats {
|
||||
time_point<high_resolution_clock>
|
||||
begin_time; // the moment of time count start
|
||||
long cycles; // performance stats
|
||||
long micro_secs; // performance stats
|
||||
long prev_cycles; // previously measured stats
|
||||
long prev_usec; // previously measured stats
|
||||
int perf_onemhz; // avg. % perf. based on 1MHz CPU.
|
||||
};
|
||||
|
||||
class VMachine
|
||||
{
|
||||
public:
|
||||
@ -113,7 +126,12 @@ class VMachine
|
||||
int GetLastError();
|
||||
void SetGraphDisp(unsigned short addr);
|
||||
void DisableGraphDisp();
|
||||
unsigned short GetGraphDispAddr();
|
||||
unsigned short GetGraphDispAddr();
|
||||
PerfStats GetPerfStats(); // returns performance stats based on 1 million
|
||||
// cycles per second (1 MHz CPU).
|
||||
void EnableExecHistory(bool enexehist);
|
||||
bool IsExecHistoryActive();
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
@ -133,6 +151,7 @@ class VMachine
|
||||
int mError; // last error code
|
||||
bool mGraphDispActive;
|
||||
bool mOldStyleHeader;
|
||||
PerfStats mPerfStats;
|
||||
|
||||
int LoadMEM(string memfname, Memory *pmem);
|
||||
void ShowDisp();
|
||||
@ -141,6 +160,7 @@ class VMachine
|
||||
bool LoadHdrData(FILE *fp);
|
||||
void SaveHdrData(FILE *fp);
|
||||
eMemoryImageTypes GetMemoryImageType(string ramfname);
|
||||
int CalcCurrPerf();
|
||||
};
|
||||
|
||||
} // namespace MKBasic
|
||||
|
BIN
ehbas_grdemo_loop.snap
Normal file
BIN
ehbas_grdemo_loop.snap
Normal file
Binary file not shown.
2074
ehbas_nogrdev.dat
Normal file
2074
ehbas_nogrdev.dat
Normal file
File diff suppressed because it is too large
Load Diff
407
main.cpp
407
main.cpp
@ -317,6 +317,8 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat)
|
||||
cout << " at: $" << hex << pvm->GetGraphDispAddr() << endl;
|
||||
cout << "ROM: " << ((pvm->IsROMEnabled()) ? "enabled." : "disabled.") << " ";
|
||||
cout << "Range: $" << hex << pvm->GetROMBegin() << " - $" << hex << pvm->GetROMEnd() << "." << endl;
|
||||
cout << "Op-code execute history: " << (pvm->IsExecHistoryActive() ? "enabled" : "disabled");
|
||||
cout << "." << endl;
|
||||
}
|
||||
cout << " \r";
|
||||
|
||||
@ -341,9 +343,9 @@ void ShowMenu()
|
||||
cout << " E - toggle I/O local echo | F - toggle registers animation" << endl;
|
||||
cout << " J - set animation delay | M - dump memory, W - write memory" << endl;
|
||||
cout << " K - toggle ROM emulation | R - show registers, Y - snapshot" << endl;
|
||||
cout << " L - load memory image | O - display op-codes history" << endl;
|
||||
cout << " L - load memory image | O - display op-code exec. history" << endl;
|
||||
cout << " D - disassemble code in memory | Q - quit, 0 - reset, H - help" << endl;
|
||||
cout << " V - toggle graphics emulation |" << endl;
|
||||
cout << " V - toggle graphics emulation | U - enable/disable exec. history" << endl;
|
||||
cout << "------------------------------------+----------------------------------------" << endl;
|
||||
}
|
||||
|
||||
@ -392,6 +394,227 @@ void ShowMenu()
|
||||
} \
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: ShowSpeedStats()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void ShowSpeedStats()
|
||||
{
|
||||
cout << endl;
|
||||
cout << dec;
|
||||
cout << "CPU emulation speed stats: " << endl;
|
||||
cout << "|-> Average speed based on 1MHz CPU: " << pvm->GetPerfStats().perf_onemhz << " %" << endl;
|
||||
cout << "|-> Last measured # of cycles exec.: " << pvm->GetPerfStats().prev_cycles << endl;
|
||||
cout << "|-> Last measured time of execution: " << pvm->GetPerfStats().prev_usec << " usec" << endl;
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: ExecHistory()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void ExecHistory()
|
||||
{
|
||||
if (pvm->IsExecHistoryActive()) {
|
||||
queue<string> exechist(pvm->GetExecHistory());
|
||||
cout << "PC : INSTR ACC | X | Y | PS | SP" << endl;
|
||||
cout << "------------------------------------+-----+-----+-----+-----" << endl;
|
||||
while (exechist.size()) {
|
||||
cout << exechist.front() << endl;
|
||||
exechist.pop();
|
||||
}
|
||||
} else {
|
||||
cout << "Sorry. Op-code execute history is currently disabled." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: LoadImage()
|
||||
* Purpose: Load memory image from file. Set new execute address.
|
||||
* Arguments: newaddr - current execute address
|
||||
* Returns: unsigned int - new execute address
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
unsigned int LoadImage(unsigned int newaddr)
|
||||
{
|
||||
char typ = 0;
|
||||
for (char c = tolower(typ);
|
||||
c != 'a' && c != 'b' && c != 'h' && c != 'd';
|
||||
c = tolower(typ)) {
|
||||
cout << "Type (A - auto/B - binary/H - Intel HEX/D - definition): ";
|
||||
cin >> typ;
|
||||
}
|
||||
cout << " [";
|
||||
switch (tolower(typ)) {
|
||||
case 'a': cout << "auto"; break;
|
||||
case 'b': cout << "binary"; break;
|
||||
case 'h': cout << "Intel HEX"; break;
|
||||
case 'd': cout << "definition"; break;
|
||||
default: break; // should never happen
|
||||
}
|
||||
cout << "]" << endl;
|
||||
string name;
|
||||
cout << "Memory Image File Name: ";
|
||||
cin >> name;
|
||||
cout << " [" << name << "]" << endl;
|
||||
if (typ == 'b') PrintVMErr (pvm->LoadRAMBin(name));
|
||||
else if (typ == 'h') PrintVMErr (pvm->LoadRAMHex(name));
|
||||
else if (typ == 'd') {
|
||||
PrintVMErr (pvm->LoadRAMDef(name));
|
||||
if (pvm->IsAutoExec()) execvm = true;
|
||||
if (newaddr == 0) newaddr = 0x10000;
|
||||
}
|
||||
else { // automatic file format detection
|
||||
pvm->LoadRAM(name);
|
||||
if (pvm->IsAutoExec()) execvm = true;
|
||||
if (newaddr == 0) newaddr = 0x10000;
|
||||
}
|
||||
|
||||
return newaddr;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: ToggleIO()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
unsigned int ToggleIO(unsigned int ioaddr)
|
||||
{
|
||||
if (pvm->GetCharIOActive()) {
|
||||
pvm->DisableCharIO();
|
||||
cout << "I/O deactivated." << endl;
|
||||
} else {
|
||||
ioaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << ioaddr << "]" << endl;
|
||||
pvm->SetCharIO(ioaddr, ioecho);
|
||||
cout << "I/O activated." << endl;
|
||||
}
|
||||
|
||||
return ioaddr;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: ToggleGrDisp()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
unsigned int ToggleGrDisp(unsigned int graddr)
|
||||
{
|
||||
if (pvm->GetGraphDispActive()) {
|
||||
pvm->DisableGraphDisp();
|
||||
cout << "Graphics display deactivated." << endl;
|
||||
} else {
|
||||
graddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << graddr << "]" << endl;
|
||||
pvm->SetGraphDisp(graddr);
|
||||
cout << "Graphics display activated." << endl;
|
||||
}
|
||||
|
||||
return graddr;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: WriteToMemory()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void WriteToMemory()
|
||||
{
|
||||
unsigned int tmpaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << tmpaddr << "]" << endl;
|
||||
cout << "Enter hex bytes [00..FF] values separated with NL or spaces, end with [100]:" << endl;
|
||||
unsigned short v = 0;
|
||||
while (true) {
|
||||
cin >> hex >> v;
|
||||
cout << " " << hex << v;
|
||||
if (v > 0xFF) break;
|
||||
pvm->MemPoke8bit(tmpaddr++, v & 0xFF);
|
||||
};
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: DisassembleMemory()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void DisassembleMemory()
|
||||
{
|
||||
unsigned int addrbeg = 0x10000, addrend = 0x10000;
|
||||
cout << "Enter address range (0..0xFFFF)..." << endl;
|
||||
addrbeg = PromptNewAddress("Start address (0..FFFF): ");
|
||||
cout << " [" << hex << addrbeg << "]" << endl;
|
||||
addrend = PromptNewAddress("End address (0..FFFF): ");
|
||||
cout << " [" << hex << addrend << "]" << endl;
|
||||
cout << endl;
|
||||
for (unsigned int addr = addrbeg; addr <= addrend;) {
|
||||
char instrbuf[DISS_BUF_SIZE];
|
||||
addr = pvm->Disassemble((unsigned short)addr, instrbuf);
|
||||
cout << instrbuf << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: DumpMemory()
|
||||
* Purpose:
|
||||
* Arguments:
|
||||
* Returns:
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
void DumpMemory()
|
||||
{
|
||||
unsigned int addrbeg = 0x10000, addrend = 0x10000;
|
||||
cout << "Enter address range (0..0xFFFF)..." << endl;
|
||||
addrbeg = PromptNewAddress("Start address (0..FFFF): ");
|
||||
cout << " [" << hex << addrbeg << "]" << endl;
|
||||
addrend = PromptNewAddress("End address (0..FFFF): ");
|
||||
cout << " [" << hex << addrend << "]" << endl;
|
||||
cout << endl;
|
||||
for (unsigned int addr = addrbeg; addr <= addrend; addr+=16) {
|
||||
cout << "\t|";
|
||||
for (unsigned int j=0; j < 16; j++) {
|
||||
unsigned int hv = (unsigned int)pvm->MemPeek8bit(addr+j);
|
||||
if (hv < 16) {
|
||||
cout << 0;
|
||||
}
|
||||
cout << hex << hv << " ";
|
||||
}
|
||||
cout << "|";
|
||||
for (int j=0; j < 16; j++) {
|
||||
char cc = (char)pvm->MemPeek8bit(addr+j);
|
||||
if (isprint(cc))
|
||||
cout << cc;
|
||||
else
|
||||
cout << "?";
|
||||
}
|
||||
cout << '\r';
|
||||
cout << hex << addr;
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------
|
||||
* Method: LoadArgs()
|
||||
@ -478,8 +701,8 @@ int main(int argc, char *argv[]) {
|
||||
CopyrightBanner();
|
||||
string cmd;
|
||||
bool runvm = false, step = false, brk = false, execaddr = false, stop = true;
|
||||
bool lrts = false, anim = false, enrom = pvm->IsROMEnabled();
|
||||
unsigned int newaddr = pvm->GetRunAddr(), ioaddr = pvm->GetCharIOAddr(), tmpaddr = 0x0000;
|
||||
bool lrts = false, anim = false, enrom = pvm->IsROMEnabled(), show_menu = true;
|
||||
unsigned int newaddr = pvm->GetRunAddr(), ioaddr = pvm->GetCharIOAddr();
|
||||
unsigned int graddr = pvm->GetGraphDispAddr();
|
||||
unsigned int rombegin = pvm->GetROMBegin(), romend = pvm->GetROMEnd(), delay = ANIM_DELAY;
|
||||
int nsteps = 0;
|
||||
@ -532,19 +755,36 @@ int main(int argc, char *argv[]) {
|
||||
} else if (stop) {
|
||||
cout << "STOPPED at " << hex << ((newaddr > 0xFFFF) ? preg->PtrAddr : newaddr) << endl;
|
||||
}
|
||||
ShowSpeedStats();
|
||||
opbrk = brk = stop = lrts = false;
|
||||
pvm->SetOpInterrupt(false);
|
||||
ShowRegs(preg,pvm,ioecho,true);
|
||||
show_menu = true;
|
||||
}
|
||||
if (show_menu) {
|
||||
|
||||
ShowMenu();
|
||||
show_menu = false;
|
||||
|
||||
} else {
|
||||
|
||||
cout << endl;
|
||||
cout << "Type '?' and press ENTER for Menu ..." << endl;
|
||||
cout << endl;
|
||||
|
||||
}
|
||||
ShowMenu();
|
||||
cout << "> ";
|
||||
cin >> cmd;
|
||||
char c = tolower(cmd.c_str()[0]);
|
||||
if (c == 'h') { // display help
|
||||
if (c == '?') {
|
||||
show_menu = true;
|
||||
}
|
||||
else if (c == 'h') { // display help
|
||||
ShowHelp();
|
||||
} else if (c == 'p') { // Interrupt ReQuest
|
||||
pvm->Interrupt();
|
||||
cout << "OK" << endl;
|
||||
show_menu = true;
|
||||
} else if (c == 'y') { // save snapshot of current CPU and memory in binary image
|
||||
string name;
|
||||
cout << "Enter file name: ";
|
||||
@ -560,47 +800,11 @@ int main(int argc, char *argv[]) {
|
||||
reset = true;
|
||||
execvm = true;
|
||||
runvm = false;
|
||||
show_menu = true;
|
||||
} else if (c == 'o') {
|
||||
queue<string> exechist(pvm->GetExecHistory());
|
||||
cout << "PC : INSTR ACC | X | Y | PS | SP" << endl;
|
||||
cout << "------------------------------------+-----+-----+-----+-----" << endl;
|
||||
while (exechist.size()) {
|
||||
cout << exechist.front() << endl;
|
||||
exechist.pop();
|
||||
}
|
||||
ExecHistory();
|
||||
} else if (c == 'l') { // load memory image
|
||||
char typ = 0;
|
||||
for (char c = tolower(typ);
|
||||
c != 'a' && c != 'b' && c != 'h' && c != 'd';
|
||||
c = tolower(typ)) {
|
||||
cout << "Type (A - auto/B - binary/H - Intel HEX/D - definition): ";
|
||||
cin >> typ;
|
||||
}
|
||||
cout << " [";
|
||||
switch (tolower(typ)) {
|
||||
case 'a': cout << "auto"; break;
|
||||
case 'b': cout << "binary"; break;
|
||||
case 'h': cout << "Intel HEX"; break;
|
||||
case 'd': cout << "definition"; break;
|
||||
default: break; // should never happen
|
||||
}
|
||||
cout << "]" << endl;
|
||||
string name;
|
||||
cout << "Memory Image File Name: ";
|
||||
cin >> name;
|
||||
cout << " [" << name << "]" << endl;
|
||||
if (typ == 'b') PrintVMErr (pvm->LoadRAMBin(name));
|
||||
else if (typ == 'h') PrintVMErr (pvm->LoadRAMHex(name));
|
||||
else if (typ == 'd') {
|
||||
PrintVMErr (pvm->LoadRAMDef(name));
|
||||
if (pvm->IsAutoExec()) execvm = true;
|
||||
if (newaddr == 0) newaddr = 0x10000;
|
||||
}
|
||||
else { // automatic file format detection
|
||||
pvm->LoadRAM(name);
|
||||
if (pvm->IsAutoExec()) execvm = true;
|
||||
if (newaddr == 0) newaddr = 0x10000;
|
||||
}
|
||||
newaddr = LoadImage(newaddr);
|
||||
} else if (c == 'k') { // toggle ROM emulation
|
||||
if (!enrom) {
|
||||
enrom = true;
|
||||
@ -645,37 +849,11 @@ int main(int argc, char *argv[]) {
|
||||
cout << "ERROR: I/O is deactivated." << endl;
|
||||
}
|
||||
} else if (c == 'i') { // toggle I/O
|
||||
if (pvm->GetCharIOActive()) {
|
||||
pvm->DisableCharIO();
|
||||
cout << "I/O deactivated." << endl;
|
||||
} else {
|
||||
ioaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << ioaddr << "]" << endl;
|
||||
pvm->SetCharIO(ioaddr, ioecho);
|
||||
cout << "I/O activated." << endl;
|
||||
}
|
||||
ioaddr = ToggleIO(ioaddr);
|
||||
} else if (c == 'v') { // toggle graphics display
|
||||
if (pvm->GetGraphDispActive()) {
|
||||
pvm->DisableGraphDisp();
|
||||
cout << "Graphics display deactivated." << endl;
|
||||
} else {
|
||||
graddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << graddr << "]" << endl;
|
||||
pvm->SetGraphDisp(graddr);
|
||||
cout << "Graphics display activated." << endl;
|
||||
}
|
||||
graddr = ToggleGrDisp(graddr);
|
||||
} else if (c == 'w') { // write to memory
|
||||
tmpaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << tmpaddr << "]" << endl;
|
||||
cout << "Enter hex bytes [00..FF] values separated with NL or spaces, end with [100]:" << endl;
|
||||
unsigned short v = 0;
|
||||
while (true) {
|
||||
cin >> hex >> v;
|
||||
cout << " " << hex << v;
|
||||
if (v > 0xFF) break;
|
||||
pvm->MemPoke8bit(tmpaddr++, v & 0xFF);
|
||||
};
|
||||
cout << endl;
|
||||
WriteToMemory();
|
||||
} else if (c == 'a') { // change run address
|
||||
execaddr = stop = true;
|
||||
newaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
@ -690,62 +868,33 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
cout << " [" << dec << nsteps << "]" << endl;
|
||||
runvm = step = stop = true;
|
||||
show_menu = true;
|
||||
} else if (c == 'c') { // continue running code
|
||||
runvm = true;
|
||||
show_menu = true;
|
||||
} else if (c == 'g') { // run from new address until BRK
|
||||
runvm = true;
|
||||
execaddr = true;
|
||||
newaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << newaddr << "]" << endl;
|
||||
show_menu = true;
|
||||
} else if (c == 'x') { // execute code at address
|
||||
execvm = true;
|
||||
execaddr = true;
|
||||
newaddr = PromptNewAddress("Address (0..FFFF): ");
|
||||
cout << " [" << hex << newaddr << "]" << endl;
|
||||
cout << " [" << hex << newaddr << "]" << endl;
|
||||
show_menu = true;
|
||||
} else if (c == 'q') { // quit
|
||||
break;
|
||||
} else if (c == 'd') { // disassemble code in memory
|
||||
unsigned int addrbeg = 0x10000, addrend = 0x10000;
|
||||
cout << "Enter address range (0..0xFFFF)..." << endl;
|
||||
addrbeg = PromptNewAddress("Start address (0..FFFF): ");
|
||||
cout << " [" << hex << addrbeg << "]" << endl;
|
||||
addrend = PromptNewAddress("End address (0..FFFF): ");
|
||||
cout << " [" << hex << addrend << "]" << endl;
|
||||
cout << endl;
|
||||
for (unsigned int addr = addrbeg; addr <= addrend;) {
|
||||
char instrbuf[DISS_BUF_SIZE];
|
||||
addr = pvm->Disassemble((unsigned short)addr, instrbuf);
|
||||
cout << instrbuf << endl;
|
||||
}
|
||||
DisassembleMemory();
|
||||
} else if (c == 'm') { // dump memory
|
||||
unsigned int addrbeg = 0x10000, addrend = 0x10000;
|
||||
cout << "Enter address range (0..0xFFFF)..." << endl;
|
||||
addrbeg = PromptNewAddress("Start address (0..FFFF): ");
|
||||
cout << " [" << hex << addrbeg << "]" << endl;
|
||||
addrend = PromptNewAddress("End address (0..FFFF): ");
|
||||
cout << " [" << hex << addrend << "]" << endl;
|
||||
DumpMemory();
|
||||
} else if (c == 'u') { // toggle enable/disable op-code exec. history
|
||||
pvm->EnableExecHistory(!pvm->IsExecHistoryActive());
|
||||
cout << "Op-code execute history has been ";
|
||||
cout << (pvm->IsExecHistoryActive() ? "enabled" : "disabled") << ".";
|
||||
cout << endl;
|
||||
for (unsigned int addr = addrbeg; addr <= addrend; addr+=16) {
|
||||
cout << "\t|";
|
||||
for (unsigned int j=0; j < 16; j++) {
|
||||
unsigned int hv = (unsigned int)pvm->MemPeek8bit(addr+j);
|
||||
if (hv < 16) {
|
||||
cout << 0;
|
||||
}
|
||||
cout << hex << hv << " ";
|
||||
}
|
||||
cout << "|";
|
||||
for (int j=0; j < 16; j++) {
|
||||
char cc = (char)pvm->MemPeek8bit(addr+j);
|
||||
if (isprint(cc))
|
||||
cout << cc;
|
||||
else
|
||||
cout << "?";
|
||||
}
|
||||
cout << '\r';
|
||||
cout << hex << addr;
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -963,11 +1112,32 @@ L - load memory image
|
||||
in decimal or hexadecimal format.
|
||||
|
||||
RESET Enables auto-reset of the CPU. After loading the memory
|
||||
definition file, the CPU reset sequence will be initiated.
|
||||
definition file, the CPU reset sequence will be initiated.
|
||||
|
||||
ENGRAPH Enables raster graphics device emulation.
|
||||
|
||||
GRAPHADDR Defines the base address of raster graphics device. The next
|
||||
line that follows sets the address in decimal or hexadecimal
|
||||
format.
|
||||
|
||||
NOTE: The binary image file can contain a header which contains
|
||||
definitions corresponding to the above parameters at fixed
|
||||
positions. This header is created when user saves the snapshot of
|
||||
current emulator memory image and status. Example use scenario:
|
||||
* User loads the image definition file.
|
||||
* User adjusts various parameters of the emulator
|
||||
(enables/disables devices, sets addresses, changes memory
|
||||
contents).
|
||||
* User saves the snapshot with 'Y' command.
|
||||
* Next time user loads the snapshot image, all the parameters
|
||||
and memory contents stick. This way game status can be saved
|
||||
or a BASIC interpreter with BASIC program in it.
|
||||
See command 'Y' for details.
|
||||
|
||||
O - display op-codes history
|
||||
Show the history of last executed op-codes/instructions, full with
|
||||
disassembled mnemonic and argument.
|
||||
disassembled mnemonic, argument and CPU registers and status.
|
||||
NOTE: op-codes execute history must be enabled, see command 'U'.
|
||||
D - diassemble code in memory
|
||||
Usage: D [startaddr] [endaddr]
|
||||
Where: startaddr,endaddr - hexadecimal address [0000..FFFF].
|
||||
@ -983,6 +1153,11 @@ D - diassemble code in memory
|
||||
jumping to main loop. The reset routine disables trapping last RTS
|
||||
opcode if stack is empty, so the VM will never return from opcodes
|
||||
execution loop unless user interrupts with CTRL-C or CTRL-Break.
|
||||
? - display commands menu
|
||||
Display the menu of all available in Debug Console commands.
|
||||
U - enable/disable exec. history
|
||||
Toggle enable/disable of op-codes execute history.
|
||||
Disabling this feature improves performance.
|
||||
|
||||
NOTE:
|
||||
1. If no arguments provided, each command will prompt user to enter
|
||||
|
Loading…
Reference in New Issue
Block a user