Performance optimizations.

Performance optimizations. Emulation speed measurement. Bug fixes.
This commit is contained in:
Marek Karcz 2016-08-21 01:41:38 -04:00
parent 20d12b5eae
commit 3cd0bc3f20
12 changed files with 2914 additions and 301 deletions

140
MKCpu.cpp
View File

@ -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
View File

@ -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

View File

@ -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();

View File

@ -225,7 +225,7 @@ class MemMapDev {
void set_conio_terminal_mode();
int kbhit();
int getch();
int getch();
#endif

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

2074
ehbas_nogrdev.dat Normal file

File diff suppressed because it is too large Load Diff

407
main.cpp
View File

@ -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