mirror of
https://github.com/makarcz/vm6502.git
synced 2025-01-14 12:33:39 +00:00
f4526b73c0
Graphics device text/character mode. Linux port. Documentation updates.
1932 lines
52 KiB
C++
1932 lines
52 KiB
C++
/*
|
|
*--------------------------------------------------------------------
|
|
* Project: VM65 - Virtual Machine/CPU emulator programming
|
|
* framework.
|
|
*
|
|
* File: VMachine.cpp
|
|
*
|
|
* Purpose: Implementation of VMachine class.
|
|
* The VMachine class implements the Virtual Machine
|
|
* in its entirety. It creates all the objects that
|
|
* emulate the component's of the whole system and
|
|
* implements the methods to execute the code on the
|
|
* emulated platform.
|
|
*
|
|
* Date: 8/25/2016
|
|
*
|
|
* Copyright: (C) by Marek Karcz 2016. All rights reserved.
|
|
*
|
|
* Contact: makarcz@yahoo.com
|
|
*
|
|
* License Agreement and Warranty:
|
|
|
|
This software is provided with No Warranty.
|
|
I (Marek Karcz) will not be held responsible for any damage to
|
|
computer systems, data or user's health resulting from use.
|
|
Please proceed responsibly and apply common sense.
|
|
This software is provided in hope that it will be useful.
|
|
It is free of charge for non-commercial and educational use.
|
|
Distribution of this software in non-commercial and educational
|
|
derivative work is permitted under condition that original
|
|
copyright notices and comments are preserved. Some 3-rd party work
|
|
included with this project may require separate application for
|
|
permission from their respective authors/copyright owners.
|
|
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
#include <stdio.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string.h>
|
|
#include "system.h"
|
|
#include "VMachine.h"
|
|
#include "MKGenException.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace MKBasic {
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: VMachine()
|
|
* Purpose: Default class constructor.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
VMachine::VMachine()
|
|
{
|
|
InitVM();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: VMachine()
|
|
* Purpose: Custom class constructor.
|
|
* Arguments: romfname - name of the ROM definition file
|
|
* ramfname - name of the RAM definition file
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
VMachine::VMachine(string romfname, string ramfname)
|
|
{
|
|
InitVM();
|
|
LoadROM(romfname);
|
|
LoadRAM(ramfname);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ~VMachine()
|
|
* Purpose: Class destructor.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
VMachine::~VMachine()
|
|
{
|
|
delete mpCPU;
|
|
delete mpROM;
|
|
delete mpRAM;
|
|
delete mpConIO;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: InitVM()
|
|
* Purpose: Initialize class.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
f *--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::InitVM()
|
|
{
|
|
mOpInterrupt = false;
|
|
mpRAM = new Memory();
|
|
|
|
mPerfStats.cycles = 0;
|
|
mPerfStats.perf_onemhz = 0;
|
|
mPerfStats.prev_cycles = 0;
|
|
mPerfStats.prev_usec = 0;
|
|
mOldStyleHeader = false;
|
|
mError = VMERR_OK;
|
|
mAutoExec = false;
|
|
mAutoReset = false;
|
|
mCharIOAddr = CHARIO_ADDR;
|
|
mCharIOActive = mCharIO = false;
|
|
mGraphDispActive = false;
|
|
mPerfStatsActive = false;
|
|
mDebugTraceActive = false;
|
|
if (NULL == mpRAM) {
|
|
throw MKGenException("Unable to initialize VM (RAM).");
|
|
}
|
|
mRunAddr = mpRAM->Peek16bit(0xFFFC); // address under RESET vector
|
|
mpROM = new Memory();
|
|
if (NULL == mpROM) {
|
|
throw MKGenException("Unable to initialize VM (ROM).");
|
|
}
|
|
mpCPU = new MKCpu(mpRAM);
|
|
if (NULL == mpCPU) {
|
|
throw MKGenException("Unable to initialize VM (CPU).");
|
|
}
|
|
mpConIO = new ConsoleIO();
|
|
if (NULL == mpConIO) {
|
|
throw MKGenException("Unable to initialize VM (ConsoleIO)");
|
|
}
|
|
mBeginTime = high_resolution_clock::now();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ClearScreen()
|
|
* Purpose: Clear the working are of the VM - DOS.
|
|
* This is not a part of virtual display emulation.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ClearScreen()
|
|
{
|
|
mpConIO->ClearScreen();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ScrHome()
|
|
* Purpose: Bring the console cursor to home position - DOS.
|
|
* This is not a part of virtual display emulation.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ScrHome()
|
|
{
|
|
mpConIO->ScrHome();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ShowDisp()
|
|
* Purpose: Show the emulated virtual text display device contents.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ShowDisp()
|
|
{
|
|
if (mCharIOActive && NULL != mpDisp) {
|
|
ScrHome();
|
|
mpDisp->ShowScr();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: CalcCurrPerf()
|
|
* Purpose: Calculate CPU emulation performance based on 1 MHz model
|
|
* CPU.
|
|
* Arguments: n/a
|
|
* Returns: Integer, the % of speed as compared to 1 MHz CPU.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::CalcCurrPerf()
|
|
{
|
|
if (!mPerfStatsActive) return 0;
|
|
|
|
auto lap = high_resolution_clock::now();
|
|
long usec = duration_cast<microseconds>(lap-mPerfStats.begin_time).count();
|
|
|
|
if (usec > 0) {
|
|
int currperf = (int)(((double)mPerfStats.cycles / (double)usec) * 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 = usec;
|
|
mPerfStats.cycles = 0;
|
|
mPerfStats.begin_time = lap;
|
|
if (mDebugTraceActive) { // prepare and log some debug traces
|
|
stringstream sscp, ssap;
|
|
string msg, avgprf, curprf;
|
|
ssap << mPerfStats.perf_onemhz;
|
|
ssap >> avgprf;
|
|
sscp << currperf;
|
|
sscp >> curprf;
|
|
msg = "Perf. measured. Curr.: " + curprf + " %, Avg.: " + avgprf + " %";
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
return mPerfStats.perf_onemhz;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: PERFSTAT_LAP (macro)
|
|
* Purpose: Calculate emulation performace at pre-defined interval
|
|
* of real time and clock ticks.
|
|
* Arguments: cycles - long : number of clock ticks executed so far
|
|
* begin - time_point<high_resolution_clock> : the moment
|
|
* when time measurement started
|
|
* Returns: n/a
|
|
* Remarks: Call inside emulation execute loop.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
#define PERFSTAT_LAP(cycles,begin) \
|
|
{ \
|
|
if (mPerfStatsActive && cycles%PERFSTAT_CYCLES == 0) { \
|
|
long usec = duration_cast<microseconds> \
|
|
(high_resolution_clock::now()-begin).count(); \
|
|
if (usec >= PERFSTAT_INTERVAL) CalcCurrPerf(); \
|
|
} \
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Run()
|
|
* Purpose: Run VM until software break instruction.
|
|
* Arguments: n/a
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Run()
|
|
{
|
|
Regs *cpureg = NULL;
|
|
|
|
AddDebugTrace("Running code at: $" + Addr2HexStr(mRunAddr));
|
|
mOpInterrupt = false;
|
|
mpConIO->InitCursesScr();
|
|
ClearScreen();
|
|
ShowDisp();
|
|
mPerfStats.cycles = 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;
|
|
PERFSTAT_LAP(mPerfStats.cycles,mPerfStats.begin_time);
|
|
}
|
|
CalcCurrPerf();
|
|
|
|
ShowDisp();
|
|
mpConIO->CloseCursesScr();
|
|
|
|
return cpureg;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Run()
|
|
* Purpose: Run VM from specified address until software break
|
|
* instruction.
|
|
* Arguments: addr - start execution address
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Run(unsigned short addr)
|
|
{
|
|
mRunAddr = addr;
|
|
return Run();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Exec()
|
|
* Purpose: Run VM from current address until last RTS (if enabled).
|
|
* NOTE: Stack must be empty for last RTS to be trapped.
|
|
* Arguments: n/a
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Exec()
|
|
{
|
|
Regs *cpureg = NULL;
|
|
|
|
AddDebugTrace("Executing code at: $" + Addr2HexStr(mRunAddr));
|
|
mOpInterrupt = false;
|
|
mpConIO->InitCursesScr();
|
|
ClearScreen();
|
|
ShowDisp();
|
|
mPerfStats.cycles = 0;
|
|
mPerfStats.begin_time = high_resolution_clock::now();
|
|
while (true) {
|
|
mPerfStats.cycles++;
|
|
cpureg = Step();
|
|
if (cpureg->LastRTS || mOpInterrupt) break;
|
|
PERFSTAT_LAP(mPerfStats.cycles,mPerfStats.begin_time);
|
|
}
|
|
CalcCurrPerf();
|
|
|
|
ShowDisp();
|
|
mpConIO->CloseCursesScr();
|
|
|
|
return cpureg;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetPerfStats()
|
|
* Purpose: Get performance stats data.
|
|
* Arguments:
|
|
* Returns: struct PerfStats
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
PerfStats VMachine::GetPerfStats()
|
|
{
|
|
return mPerfStats;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Exec()
|
|
* Purpose: Run VM from specified address until RTS.
|
|
* Arguments: addr - start execution address
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Exec(unsigned short addr)
|
|
{
|
|
mRunAddr = addr;
|
|
return Exec();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Step()
|
|
* Purpose: Execute single opcode.
|
|
* Arguments: n/a
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Step()
|
|
{
|
|
Regs *cpureg = NULL;
|
|
|
|
cpureg = mpCPU->ExecOpcode(mRunAddr);
|
|
if (mGraphDispActive && cpureg->CyclesLeft == 0) {
|
|
mpRAM->GraphDisp_ReadEvents();
|
|
}
|
|
mRunAddr = cpureg->PtrAddr;
|
|
|
|
return cpureg;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Step()
|
|
* Purpose: Execute single opcode.
|
|
* Arguments: addr (unsigned short) - opcode address
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Step(unsigned short addr)
|
|
{
|
|
mRunAddr = addr;
|
|
return Step();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadROM()
|
|
* Purpose: Load data from memory definition file to the memory.
|
|
* Arguments: romfname - name of the ROM file definition
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::LoadROM(string romfname)
|
|
{
|
|
LoadMEM(romfname, mpROM);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadRAM()
|
|
* Purpose: Load data from memory definition file to the memory.
|
|
* Arguments: ramfname - name of the RAM file definition
|
|
* Returns: int - error code
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::LoadRAM(string ramfname)
|
|
{
|
|
int err = 0;
|
|
eMemoryImageTypes memimg_type = GetMemoryImageType(ramfname);
|
|
switch (memimg_type) {
|
|
case MEMIMG_VM65DEF: err = LoadMEM(ramfname, mpRAM); break;
|
|
case MEMIMG_INTELHEX: err = LoadRAMHex(ramfname); break;
|
|
case MEMIMG_BIN:
|
|
default: // unknown type, try to read as binary
|
|
// and hope for the best
|
|
err = LoadRAMBin(ramfname);
|
|
break;
|
|
}
|
|
mError = err;
|
|
if (mDebugTraceActive && err) {
|
|
stringstream sserr;
|
|
string msg, strerr;
|
|
sserr << err;
|
|
sserr >> strerr;
|
|
msg = "ERROR: LoadRAM, error code: " + strerr;
|
|
AddDebugTrace(msg);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetMemoryImageType()
|
|
* Purpose: Detect format of memory image file.
|
|
* Arguments: ramfname - name of the RAM file definition
|
|
* Returns: eMemoryImageTypes - code of the memory image format
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
eMemoryImageTypes VMachine::GetMemoryImageType(string ramfname)
|
|
{
|
|
eMemoryImageTypes ret = MEMIMG_UNKNOWN;
|
|
char buf[256] = {0};
|
|
FILE *fp = NULL;
|
|
int n = 0;
|
|
|
|
if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) {
|
|
memset(buf, 0, 256);
|
|
while (0 == feof(fp) && 0 == ferror(fp)) {
|
|
unsigned char val = fgetc(fp);
|
|
buf[n++] = val;
|
|
if (n >= 256) break;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
bool possibly_intelhex = true;
|
|
for (int i=0; i<256; i++) {
|
|
char *pc = buf+i;
|
|
if (isspace(buf[i])) continue;
|
|
if (i < 256-9 // 256 less the length of the longest expected keyword
|
|
&&
|
|
(!strncmp(pc, "ADDR", 4)
|
|
|| !strncmp(pc, "ORG", 3)
|
|
|| !strncmp(pc, "IOADDR", 6)
|
|
|| !strncmp(pc, "ROMBEGIN", 8)
|
|
|| !strncmp(pc, "ROMEND", 6)
|
|
|| !strncmp(pc, "ENROM", 5)
|
|
|| !strncmp(pc, "ENIO", 4)
|
|
|| !strncmp(pc, "EXEC", 4)
|
|
|| !strncmp(pc, "RESET", 5)
|
|
|| !strncmp(pc, "ENGRAPH", 7)
|
|
|| !strncmp(pc, "GRAPHADDR", 9))
|
|
)
|
|
{
|
|
ret = MEMIMG_VM65DEF;
|
|
break;
|
|
}
|
|
if (buf[i] != ':'
|
|
&& buf[i] != '0'
|
|
&& buf[i] != '1'
|
|
&& buf[i] != '2'
|
|
&& buf[i] != '3'
|
|
&& buf[i] != '4'
|
|
&& buf[i] != '5'
|
|
&& buf[i] != '6'
|
|
&& buf[i] != '7'
|
|
&& buf[i] != '8'
|
|
&& buf[i] != '9'
|
|
&& tolower(buf[i]) != 'a'
|
|
&& tolower(buf[i]) != 'b'
|
|
&& tolower(buf[i]) != 'c'
|
|
&& tolower(buf[i]) != 'd'
|
|
&& tolower(buf[i]) != 'e'
|
|
&& tolower(buf[i]) != 'f')
|
|
{
|
|
possibly_intelhex = false;
|
|
}
|
|
}
|
|
if (ret == MEMIMG_UNKNOWN && possibly_intelhex)
|
|
ret = MEMIMG_INTELHEX;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: HasHdrData()
|
|
* Purpose: Check for header in the binary memory image.
|
|
* Arguments: File pointer.
|
|
* Returns: true if magic keyword found at the beginning of the
|
|
* memory image file, false otherwise
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::HasHdrData(FILE *fp)
|
|
{
|
|
bool ret = false;
|
|
int n = 0, l = strlen(HDRMAGICKEY);
|
|
char buf[20];
|
|
|
|
memset(buf, 0, 20);
|
|
|
|
rewind(fp);
|
|
while (0 == feof(fp) && 0 == ferror(fp)) {
|
|
unsigned char val = fgetc(fp);
|
|
buf[n] = val;
|
|
n++;
|
|
if (n >= l) break;
|
|
}
|
|
ret = (0 == strncmp(buf, HDRMAGICKEY, l));
|
|
|
|
AddDebugTrace(((ret) ? "HasHdrData: YES" : "HasHdrData: NO"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: HasOldHdrData()
|
|
* Purpose: Check for previous version header in the binary memory
|
|
* image.
|
|
* Arguments: File pointer.
|
|
* Returns: true if magic keyword found at the beginning of the
|
|
* memory image file, false otherwise
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::HasOldHdrData(FILE *fp)
|
|
{
|
|
bool ret = false;
|
|
int n = 0, l = strlen(HDRMAGICKEY_OLD);
|
|
char buf[20];
|
|
|
|
memset(buf, 0, 20);
|
|
|
|
rewind(fp);
|
|
while (0 == feof(fp) && 0 == ferror(fp)) {
|
|
unsigned char val = fgetc(fp);
|
|
buf[n] = val;
|
|
n++;
|
|
if (n >= l) break;
|
|
}
|
|
ret = (0 == strncmp(buf, HDRMAGICKEY_OLD, l));
|
|
|
|
AddDebugTrace(((ret) ? "HasOldHdrData: YES" : "HasOldHdrData: NO"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadHdrData()
|
|
* Purpose: Load data from binary image header.
|
|
* Arguments: File pointer.
|
|
* Returns: bool, true if success, false if error
|
|
*
|
|
* Details:
|
|
* Header of the binary memory image consists of magic keyword
|
|
* string followed by the 128 bytes of data (unsigned char values).
|
|
* It has following format:
|
|
*
|
|
* MAGIC_KEYWORD
|
|
* aabbccddefghijklmm[remaining unused bytes]
|
|
*
|
|
* Where:
|
|
* MAGIC_KEYWORD - text string indicating header, may vary between
|
|
* versions thus rendering headers from previous
|
|
* versions incompatible - currently: "SNAPSHOT2"
|
|
* NOTE: Previous version of header is currently
|
|
* recognized and can be read, the magic
|
|
* keyword of previous version: "SNAPSHOT".
|
|
* Old header had only 15 bytes of data.
|
|
* This backwards compatibility will be
|
|
* removed in next version as the new
|
|
* format of header with 128 bytes for
|
|
* data leaves space for expansion without
|
|
* the need of changing file format.
|
|
* aa - low and hi bytes of execute address (PC)
|
|
* bb - low and hi bytes of char IO address
|
|
* cc - low and hi bytes of ROM begin address
|
|
* dd - low and hi bytes of ROM end address
|
|
* e - 0 if char IO is disabled, 1 if enabled
|
|
* f - 0 if ROM is disabled, 1 if enabled
|
|
* g - value in CPU Acc (accumulator) register
|
|
* h - value in CPU X (X index) register
|
|
* i - value in CPU Y (Y index) register
|
|
* j - value in CPU PS (processor status/flags)
|
|
* k - value in CPU SP (stack pointer) register
|
|
* l - 0 if generic graphics display device is disabled,
|
|
* 1 if graphics display is enabled
|
|
* mm - low and hi bytes of graphics display base address
|
|
*
|
|
* NOTE:
|
|
* If magic keyword was detected, this part is already read and file
|
|
* pointer position is at the 1-st byte of data. Therefore this
|
|
* method does not have to read and skip the magic keyword.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::LoadHdrData(FILE *fp)
|
|
{
|
|
int n = 0, l = 0, hdrdtlen = HDRDATALEN;
|
|
unsigned short rb = 0, re = 0;
|
|
Regs r;
|
|
bool ret = false;
|
|
|
|
if (mOldStyleHeader) hdrdtlen = HDRDATALEN_OLD;
|
|
while (0 == feof(fp) && 0 == ferror(fp) && n < hdrdtlen) {
|
|
unsigned char val = fgetc(fp);
|
|
switch (n)
|
|
{
|
|
case 1: mRunAddr = l + 256 * val;
|
|
ADD_DBG_LDMEMPARHEX("LoadHdrData : mRunAddr",mRunAddr);
|
|
break;
|
|
case 3: mCharIOAddr = l + 256 * val;
|
|
ADD_DBG_LDMEMPARHEX("LoadHdrData : mCharIOAddr",mCharIOAddr);
|
|
break;
|
|
case 5: rb = l + 256 * val;
|
|
break;
|
|
case 7: re = l + 256 * val;
|
|
break;
|
|
case 8: mCharIOActive = (val != 0);
|
|
ADD_DBG_LDMEMPARVAL("LoadHdrData : mCharIOActive",mCharIOActive);
|
|
break;
|
|
case 9: if (val != 0) {
|
|
mpRAM->EnableROM(rb, re);
|
|
} else {
|
|
mpRAM->SetROM(rb, re);
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("LoadHdrData : ROM begin",rb);
|
|
ADD_DBG_LDMEMPARHEX("LoadHdrData : ROM end",re);
|
|
ADD_DBG_LDMEMPARVAL("LoadHdrData : ROM enable",((val!=0)?1:0));
|
|
break;
|
|
case 10: r.Acc = val;
|
|
break;
|
|
case 11: r.IndX = val;
|
|
break;
|
|
case 12: r.IndY = val;
|
|
break;
|
|
case 13: r.Flags = val;
|
|
break;
|
|
case 14: r.PtrStack = val;
|
|
ret = true;
|
|
break;
|
|
case 15: mGraphDispActive = (val != 0);
|
|
ADD_DBG_LDMEMPARVAL("LoadHdrData : mGraphDispActive",mGraphDispActive);
|
|
break;
|
|
case 17: if (mGraphDispActive) SetGraphDisp(l + 256 * val);
|
|
else DisableGraphDisp();
|
|
ADD_DBG_LDMEMPARHEX("LoadHdrData : Graph. Disp. addr",(l + 256 * val));
|
|
break;
|
|
default: break;
|
|
}
|
|
l = val;
|
|
n++;
|
|
}
|
|
if (ret) {
|
|
r.PtrAddr = mRunAddr;
|
|
mpCPU->SetRegs(r);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SaveHdrData()
|
|
* Purpose: Save header data to binary file (memory snapshot).
|
|
* Arguments: File pointer, must be opened for writing in binary mode.
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::SaveHdrData(FILE *fp)
|
|
{
|
|
char buf[20] = {0};
|
|
int n = HDRDATALEN;
|
|
|
|
strcpy(buf, HDRMAGICKEY);
|
|
for (unsigned int i = 0; i < strlen(HDRMAGICKEY); i++) {
|
|
fputc(buf[i], fp);
|
|
}
|
|
Regs *reg = mpCPU->GetRegs();
|
|
unsigned char lo = 0, hi = 0;
|
|
lo = (unsigned char) (reg->PtrAddr & 0x00FF);
|
|
hi = (unsigned char) ((reg->PtrAddr & 0xFF00) >> 8);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
SAVE_HDR_DATA(hi,fp,n);
|
|
lo = (unsigned char) (mCharIOAddr & 0x00FF);
|
|
hi = (unsigned char) ((mCharIOAddr & 0xFF00) >> 8);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
SAVE_HDR_DATA(hi,fp,n);
|
|
lo = (unsigned char) (GetROMBegin() & 0x00FF);
|
|
hi = (unsigned char) ((GetROMBegin() & 0xFF00) >> 8);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
SAVE_HDR_DATA(hi,fp,n);
|
|
lo = (unsigned char) (GetROMEnd() & 0x00FF);
|
|
hi = (unsigned char) ((GetROMEnd() & 0xFF00) >> 8);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
SAVE_HDR_DATA(hi,fp,n);
|
|
lo = (mCharIOActive ? 1 : 0);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
lo = (IsROMEnabled() ? 1 : 0);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
Regs *pregs = mpCPU->GetRegs();
|
|
if (pregs != NULL) {
|
|
SAVE_HDR_DATA(pregs->Acc,fp,n);
|
|
SAVE_HDR_DATA(pregs->IndX,fp,n);
|
|
SAVE_HDR_DATA(pregs->IndY,fp,n);
|
|
SAVE_HDR_DATA(pregs->Flags,fp,n);
|
|
SAVE_HDR_DATA(pregs->PtrStack,fp,n);
|
|
}
|
|
lo = (mGraphDispActive ? 1 : 0);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
lo = (unsigned char) (GetGraphDispAddr() & 0x00FF);
|
|
hi = (unsigned char) ((GetGraphDispAddr() & 0xFF00) >> 8);
|
|
SAVE_HDR_DATA(lo,fp,n);
|
|
SAVE_HDR_DATA(hi,fp,n);
|
|
// fill up the unused slots of header data with 0-s
|
|
for (int i = n; i > 0; i--) fputc(0, fp);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SaveSnapshot()
|
|
* Purpose: Save current state of the VM and memory image.
|
|
* Arguments: String - file name.
|
|
* Returns: int, 0 if successful, greater then 0 if not (# of bytes
|
|
* not written).
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::SaveSnapshot(string fname)
|
|
{
|
|
FILE *fp = NULL;
|
|
int ret = MAX_8BIT_ADDR+1;
|
|
|
|
if ((fp = fopen(fname.c_str(), "wb")) != NULL) {
|
|
SaveHdrData(fp);
|
|
for (int addr = 0; addr < MAX_8BIT_ADDR+1; addr++) {
|
|
if (addr != mCharIOAddr && addr != mCharIOAddr+1) {
|
|
unsigned char b = mpRAM->Peek8bitImg((unsigned short)addr);
|
|
if (EOF != fputc(b, fp)) ret--;
|
|
else break;
|
|
} else {
|
|
if (EOF != fputc(0, fp)) ret--;
|
|
else break;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
if (0 != ret) mError = VMERR_SAVE_SNAPSHOT;
|
|
if (mDebugTraceActive && ret) {
|
|
stringstream sserr;
|
|
string msg, strerr;
|
|
sserr << ret;
|
|
sserr >> strerr;
|
|
msg = "ERROR: SaveSnapshot, error code: " + strerr;
|
|
AddDebugTrace(msg);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadRAMBin()
|
|
* Purpose: Load data from binary image file to the memory.
|
|
* Arguments: ramfname - name of the RAM file definition
|
|
* Returns: int - error code
|
|
* MEMIMGERR_OK - OK
|
|
* MEMIMGERR_RAMBIN_EOF
|
|
* - WARNING: Unexpected EOF (image shorter than 64kB).
|
|
* MEMIMGERR_RAMBIN_OPEN
|
|
* - WARNING: Unable to open memory image file.
|
|
* MEMIMGERR_RAMBIN_HDR
|
|
* - WARNING: Problem with binary image header.
|
|
* MEMIMGERR_RAMBIN_NOHDR
|
|
* - WARNING: No header found in binary image.
|
|
* MEMIMGERR_RAMBIN_HDRANDEOF
|
|
* - WARNING: Problem with binary image header and
|
|
* Unexpected EOF (image shorter than 64kB).
|
|
* MEMIMGERR_RAMBIN_NOHDRANDEOF
|
|
* - WARNING: No header found in binary image and
|
|
* Unexpected EOF (image shorter than 64kB).
|
|
* TO DO:
|
|
* - Add fixed size header to binary image with emulator
|
|
* configuration data. Presence of the header will be detected
|
|
* by magic key at the beginning. Header should also include
|
|
* snapshot info, so the program can continue from the place
|
|
* where it was frozen/saved.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::LoadRAMBin(string ramfname)
|
|
{
|
|
FILE *fp = NULL;
|
|
unsigned short addr = 0x0000;
|
|
int n = 0;
|
|
Memory *pm = mpRAM;
|
|
int ret = MEMIMGERR_RAMBIN_OPEN;
|
|
|
|
AddDebugTrace("LoadRAMBin : " + ramfname);
|
|
mOldStyleHeader = false;
|
|
if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) {
|
|
if (HasHdrData(fp) || (mOldStyleHeader = HasOldHdrData(fp))) {
|
|
ret = (LoadHdrData(fp) ? MEMIMGERR_OK : MEMIMGERR_RAMBIN_HDR);
|
|
} else {
|
|
ret = MEMIMGERR_RAMBIN_NOHDR;
|
|
rewind(fp);
|
|
}
|
|
// temporarily disable emulation facilities to allow
|
|
// proper memory image initialization
|
|
bool tmp1 = mCharIOActive, tmp2 = mpRAM->IsROMEnabled();
|
|
DisableCharIO();
|
|
DisableROM();
|
|
while (0 == feof(fp) && 0 == ferror(fp)) {
|
|
unsigned char val = fgetc(fp);
|
|
pm->Poke8bitImg(addr, val);
|
|
addr++; n++;
|
|
}
|
|
fclose(fp);
|
|
// restore emulation facilities status
|
|
if (tmp1) SetCharIO(mCharIOAddr, false);
|
|
if (tmp2) EnableROM();
|
|
if (n <= 0xFFFF) {
|
|
switch (ret) {
|
|
|
|
case MEMIMGERR_OK:
|
|
ret = MEMIMGERR_RAMBIN_EOF;
|
|
break;
|
|
|
|
case MEMIMGERR_RAMBIN_HDR:
|
|
ret = MEMIMGERR_RAMBIN_HDRANDEOF;
|
|
break;
|
|
|
|
case MEMIMGERR_RAMBIN_NOHDR:
|
|
ret = MEMIMGERR_RAMBIN_NOHDRANDEOF;
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
mError = ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadRAMHex()
|
|
* Purpose: Load data from Intel HEX file format to memory.
|
|
* Arguments: hexfname - name of the HEX file
|
|
* Returns: int, MEMIMGERR_OK if OK, otherwise error code:
|
|
* MEMIMGERR_INTELH_OPEN - unable to open file
|
|
* MEMIMGERR_INTELH_SYNTAX - syntax error
|
|
* MEMIMGERR_INTELH_FMT - hex format error
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::LoadRAMHex(string hexfname)
|
|
{
|
|
char line[256] = {0};
|
|
FILE *fp = NULL;
|
|
int ret = 0;
|
|
unsigned int addr = 0;
|
|
|
|
bool tmp1 = mCharIOActive, tmp2 = mpRAM->IsROMEnabled();
|
|
DisableCharIO();
|
|
DisableROM();
|
|
if ((fp = fopen(hexfname.c_str(), "r")) != NULL) {
|
|
while (0 == feof(fp) && 0 == ferror(fp)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
if (line[0] == ':') {
|
|
if (0 == strcmp(line, HEXEOF)) {
|
|
break; // EOF, we are done here.
|
|
}
|
|
char blen[3] = {0,0,0};
|
|
char baddr[5] = {0,0,0,0,0};
|
|
char brectype[3] = {0,0,0};
|
|
blen[0] = line[1];
|
|
blen[1] = line[2];
|
|
blen[2] = 0;
|
|
baddr[0] = line[3];
|
|
baddr[1] = line[4];
|
|
baddr[2] = line[5];
|
|
baddr[3] = line[6];
|
|
baddr[4] = 0;
|
|
brectype[0] = line[7];
|
|
brectype[1] = line[8];
|
|
brectype[2] = 0;
|
|
unsigned int reclen = 0, rectype = 0;
|
|
sscanf(blen, "%02x", &reclen);
|
|
sscanf(baddr, "%04x", &addr);
|
|
sscanf(brectype, "%02x", &rectype);
|
|
if (reclen == 0 && rectype == 1) break; // EOF, we are done here.
|
|
if (rectype != 0) continue; // not a data record, next!
|
|
for (unsigned int i=9; i<reclen*2+9; i+=2,addr++) {
|
|
if (i>=strlen(line)-3) {
|
|
ret = MEMIMGERR_INTELH_FMT; // hex format error
|
|
break;
|
|
}
|
|
char dbuf[3] = {0,0,0};
|
|
unsigned int byteval = 0;
|
|
Memory *pm = mpRAM;
|
|
dbuf[0] = line[i];
|
|
dbuf[1] = line[i+1];
|
|
dbuf[2] = 0;
|
|
sscanf(dbuf, "%02x", &byteval);
|
|
pm->Poke8bitImg(addr, (unsigned char)byteval&0x00FF);
|
|
}
|
|
} else {
|
|
ret = MEMIMGERR_INTELH_SYNTAX; // syntax error
|
|
break;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
} else {
|
|
ret = MEMIMGERR_INTELH_OPEN; // unable to open file
|
|
}
|
|
if (tmp1) SetCharIO(mCharIOAddr, false);
|
|
if (tmp2) EnableROM();
|
|
|
|
mError = ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadRAMDef()
|
|
* Purpose: Load RAM from VM65 memory definition file.
|
|
* Arguments: memfname - file name
|
|
* Returns: int - error code.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::LoadRAMDef(string memfname)
|
|
{
|
|
return LoadMEM(memfname, mpRAM);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadMEM()
|
|
* Purpose: Load data from VM65 memory definition file to the
|
|
* provided memory device.
|
|
* Arguments: memfname - name of memory definition file
|
|
* pmem - pointer to memory object
|
|
* Returns: int - error code
|
|
* Details:
|
|
* Format of the memory definition file:
|
|
* [; comment]
|
|
* [ADDR
|
|
* address]
|
|
* [data]
|
|
* [ORG
|
|
* address]
|
|
* [data]
|
|
* [IOADDR
|
|
* address]
|
|
* [ROMBEGIN
|
|
* address]
|
|
* [ROMEND
|
|
* address]
|
|
* [ENIO]
|
|
* [ENROM]
|
|
* [EXEC
|
|
* addrress]
|
|
* [ENGRAPH]
|
|
* [GRAPHADDR
|
|
* address]
|
|
* [RESET]
|
|
*
|
|
* Where:
|
|
* [] - optional token
|
|
* ADDR - label indicating that starting address will follow in next
|
|
* line, it also defines run address
|
|
* ORG - label indicating that the address counter will change to the
|
|
* value provided in next line
|
|
* IOADDR - label indicating that char IO trap address will be defined
|
|
* in the next line
|
|
* ROMBEGIN - label indicating that ROM begin address will be defined
|
|
* in the next line
|
|
* ROMEND - label indicating that ROM end address will be defined
|
|
* in the next line
|
|
* ENIO - label enabling char IO emulation
|
|
* ENROM - label enabling ROM emulation
|
|
* EXEC - label enabling auto-execute of code, address follows in the
|
|
* next line
|
|
* ENGRAPH - enable generic graphics display device emulation with
|
|
* default base address
|
|
* GRAPHADDR - label indicating that base address for generic graphics
|
|
* display device will follow in next line,
|
|
* also enables generic graphics device emulation, but
|
|
* with the customized base address
|
|
* RESET - initiate CPU reset sequence after loading memory definition file
|
|
* address - decimal or hexadecimal (prefix $) address in memory
|
|
* E.g:
|
|
* ADDR
|
|
* $200
|
|
*
|
|
* or
|
|
*
|
|
* ADDR
|
|
* 512
|
|
*
|
|
* changes the default start address (256) to 512.
|
|
*
|
|
* ORG
|
|
* 49152
|
|
*
|
|
* moves address counter to address 49152, following data will be
|
|
* loaded from that address forward
|
|
*
|
|
* data - the multi-line stream of decimal of hexadecimal ($xx) values
|
|
* of size unsigned char (byte: 0-255) separated with spaces
|
|
* or commas.
|
|
* E.g.:
|
|
* $00 $00 $00 $00
|
|
* $00 $00 $00 $00
|
|
*
|
|
* or
|
|
*
|
|
* $00,$00,$00,$00
|
|
*
|
|
* or
|
|
*
|
|
* 0 0 0 0
|
|
*
|
|
* or
|
|
*
|
|
* 0,0,0,0
|
|
* 0 0 0 0
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::LoadMEM(string memfname, Memory *pmem)
|
|
{
|
|
FILE *fp = NULL;
|
|
char line[256] = "\0";
|
|
int lc = 0, errc = 0;
|
|
unsigned short addr = 0, rombegin = 0, romend = 0;
|
|
unsigned int nAddr, graphaddr = GRDISP_ADDR;
|
|
bool enrom = false, enio = false, runset = false;
|
|
bool ioset = false, execset = false, rombegset = false;
|
|
bool romendset = false, engraph = false, graphset = false;
|
|
Memory *pm = pmem;
|
|
int err = MEMIMGERR_OK;
|
|
|
|
if (mDebugTraceActive) {
|
|
string msg = "LoadMEM: " + memfname;
|
|
AddDebugTrace(msg);
|
|
}
|
|
if ((fp = fopen(memfname.c_str(), "r")) != NULL) {
|
|
DisableROM();
|
|
DisableCharIO();
|
|
while (0 == feof(fp) && 0 == ferror(fp))
|
|
{
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
// change run address (can be done only once)
|
|
if (0 == strncmp(line, "ADDR", 4)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!runset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
addr = nAddr;
|
|
} else {
|
|
addr = (unsigned short) atoi(line);
|
|
}
|
|
mRunAddr = addr;
|
|
runset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: Run address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: Run address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("ADDR",addr);
|
|
continue;
|
|
}
|
|
// change address counter
|
|
if (0 == strncmp(line, "ORG", 3)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
addr = nAddr;
|
|
} else {
|
|
addr = (unsigned short) atoi(line);
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("ORG",addr);
|
|
continue;
|
|
}
|
|
// define I/O emulation address (once)
|
|
if (0 == strncmp(line, "IOADDR", 6)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!ioset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
mCharIOAddr = nAddr;
|
|
} else {
|
|
mCharIOAddr = (unsigned short) atoi(line);
|
|
}
|
|
ioset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: I/O address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: I/O address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("IOADDR",mCharIOAddr);
|
|
continue;
|
|
}
|
|
// define generic graphics display device base address (once)
|
|
if (0 == strncmp(line, "GRAPHADDR", 9)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!graphset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
graphaddr = nAddr;
|
|
} else {
|
|
graphaddr = (unsigned short) atoi(line);
|
|
}
|
|
graphset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: graphics device base address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: graphics device base address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("GRAPHADDR",graphaddr);
|
|
continue;
|
|
}
|
|
// enable character I/O emulation
|
|
if (0 == strncmp(line, "ENIO", 4)) {
|
|
enio = true;
|
|
ADD_DBG_LDMEMPARVAL("ENIO",enio);
|
|
continue;
|
|
}
|
|
// enable generic graphics display emulation
|
|
if (0 == strncmp(line, "ENGRAPH", 7)) {
|
|
engraph = true;
|
|
ADD_DBG_LDMEMPARVAL("ENIO",engraph);
|
|
continue;
|
|
}
|
|
// enable ROM emulation
|
|
if (0 == strncmp(line, "ENROM", 5)) {
|
|
enrom = true;
|
|
ADD_DBG_LDMEMPARVAL("ENROM",enrom);
|
|
continue;
|
|
}
|
|
// auto execute from address
|
|
if (0 == strncmp(line, "EXEC", 4)) {
|
|
mAutoExec = true;
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!execset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
mRunAddr = nAddr;
|
|
} else {
|
|
mRunAddr = (unsigned short) atoi(line);
|
|
}
|
|
execset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: auto-exec address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: auto-exec address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("EXEC",mRunAddr);
|
|
continue;
|
|
}
|
|
// auto reset
|
|
if (0 == strncmp(line, "RESET", 5)) {
|
|
mAutoReset = true;
|
|
ADD_DBG_LDMEMPARVAL("RESET",mAutoReset);
|
|
continue;
|
|
}
|
|
// define ROM begin address
|
|
if (0 == strncmp(line, "ROMBEGIN", 8)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!rombegset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
rombegin = nAddr;
|
|
} else {
|
|
rombegin = (unsigned short) atoi(line);
|
|
}
|
|
rombegset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: ROM-begin address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: ROM-begin address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("ROMBEGIN",rombegin);
|
|
continue;
|
|
}
|
|
// define ROM end address
|
|
if (0 == strncmp(line, "ROMEND", 6)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
lc++;
|
|
if (!romendset) {
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
romend = nAddr;
|
|
} else {
|
|
romend = (unsigned short) atoi(line);
|
|
}
|
|
romendset = true;
|
|
} else {
|
|
err = MEMIMGERR_VM65_IGNPROCWRN;
|
|
errc++;
|
|
ADD_DBG_LOADMEM(lc," WARNING: ROM-end address was already set. Ignoring...");
|
|
//cout << "LINE #" << dec << lc << " WARNING: ROM-end address was already set. Ignoring..." << endl;
|
|
}
|
|
ADD_DBG_LDMEMPARHEX("ROMEND",romend);
|
|
continue;
|
|
}
|
|
if (';' == *line) continue; // skip comment lines
|
|
char *s = strtok (line, " ,");
|
|
while (NULL != s) {
|
|
unsigned int nVal;
|
|
if (*s == '$') {
|
|
sscanf(s+1, "%02x", &nVal);
|
|
pm->Poke8bitImg(addr++, (unsigned short)nVal);
|
|
} else {
|
|
pm->Poke8bitImg(addr++, (unsigned short)atoi(s));
|
|
}
|
|
s = strtok(NULL, " ,");
|
|
}
|
|
}
|
|
fclose(fp);
|
|
if (rombegin > MIN_ROM_BEGIN && romend > rombegin) {
|
|
if (enrom)
|
|
pm->EnableROM(rombegin, romend);
|
|
else
|
|
pm->SetROM(rombegin, romend);
|
|
} else {
|
|
if (enrom) EnableROM();
|
|
}
|
|
if (enio) {
|
|
SetCharIO(mCharIOAddr, false);
|
|
}
|
|
if (engraph || graphset) {
|
|
SetGraphDisp(graphaddr);
|
|
}
|
|
}
|
|
else {
|
|
err = MEMIMGERR_VM65_OPEN;
|
|
cout << "WARNING: Unable to open memory definition file: ";
|
|
cout << memfname << endl;
|
|
errc++;
|
|
}
|
|
if (errc) {
|
|
cout << "Found " << dec << errc << ((errc > 1) ? " problems." : " problem.") << endl;
|
|
cout << "Press [ENTER] to continue...";
|
|
getchar();
|
|
}
|
|
|
|
mError = err;
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: MemPeek8bit()
|
|
* Purpose: Read value from specified RAM address.
|
|
* Arguments: addr - RAM address (0..0xFFFF)
|
|
* Returns: unsigned short - value read from specified RAM address
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::MemPeek8bit(unsigned short addr)
|
|
{
|
|
unsigned short ret = 0;
|
|
|
|
ret = (unsigned short)mpRAM->Peek8bit(addr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: MemPoke8bit()
|
|
* Purpose: Write value to specified RAM address.
|
|
* Arguments: addr - RAM address (0..0xFFFF)
|
|
* v - 8-bit byte value
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::MemPoke8bit(unsigned short addr, unsigned char v)
|
|
{
|
|
mpRAM->Poke8bit(addr, v);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetRegs()
|
|
* Purpose: Return pointer to CPU status register.
|
|
* Arguments: n/a
|
|
* Returns: pointer to status register
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::GetRegs()
|
|
{
|
|
return mpCPU->GetRegs();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetCharIO()
|
|
* Purpose: Activates and sets an address of basic character I/O
|
|
* emulation (console).
|
|
* Arguments: addr - address of I/O area (0x0000..0xFFFF)
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::SetCharIO(unsigned short addr, bool echo)
|
|
{
|
|
mCharIOAddr = addr;
|
|
mCharIOActive = true;
|
|
mpRAM->SetCharIO(addr, echo);
|
|
mpDisp = mpRAM->GetMemMapDevPtr()->GetDispPtr();
|
|
if (mDebugTraceActive) {
|
|
string msg;
|
|
msg = "Char I/O activated at: $" + Addr2HexStr(addr) + ", echo: "
|
|
+ ((echo) ? "ON" : "OFF");
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DisableCharIO()
|
|
* Purpose: Deactivates basic character I/O emulation (console).
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisableCharIO()
|
|
{
|
|
mCharIOActive = false;
|
|
mpRAM->DisableCharIO();
|
|
AddDebugTrace("Char I/O DISABLED");
|
|
//mpRAM->GetMemMapDevPtr()->DeactivateCharIO();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetCharIOAddr()
|
|
* Purpose: Returns current address of basic character I/O area.
|
|
* Arguments: n/a
|
|
* Returns: address of I/O area
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::GetCharIOAddr()
|
|
{
|
|
return mCharIOAddr;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetCharIOActive()
|
|
* Purpose: Returns status of character I/O emulation.
|
|
* Arguments: n/a
|
|
* Returns: true if I/O emulation active
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::GetCharIOActive()
|
|
{
|
|
return mCharIOActive;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetGraphDisp()
|
|
* Purpose: Set graphics device address and enable.
|
|
* Arguments: addr - unsigned short : device base address.
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::SetGraphDisp(unsigned short addr)
|
|
{
|
|
mGraphDispActive = true;
|
|
mpRAM->SetGraphDisp(addr);
|
|
if (mDebugTraceActive) {
|
|
string msg;
|
|
msg = "Graphics Device set at: $" + Addr2HexStr(addr) + ".";
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method:
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisableGraphDisp()
|
|
{
|
|
mGraphDispActive = false;
|
|
mpRAM->DisableGraphDisp();
|
|
AddDebugTrace("Graphics Device DISABLED.");
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetGraphDispAddr()
|
|
* Purpose: Return base address of graphics display.
|
|
* Arguments: n/a
|
|
* Returns: unsigned short - address ($0000 - $FFFF)
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::GetGraphDispAddr()
|
|
{
|
|
return mpRAM->GetGraphDispAddr();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetGraphDispActive()
|
|
* Purpose: Returns status of graphics display emulation.
|
|
* Arguments: n/a
|
|
* Returns: true if graphics display emulation is active
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::GetGraphDispActive()
|
|
{
|
|
return mGraphDispActive;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ShowIO()
|
|
* Purpose: Show contents of emulated char I/O.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ShowIO()
|
|
{
|
|
if (mCharIOActive && NULL != mpDisp)
|
|
mpDisp->ShowScr();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsAutoExec()
|
|
* Purpose: Return status of auto-execute flag.
|
|
* Arguments: n/a
|
|
* Returns: bool - true if auto-exec flag is enabled.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsAutoExec()
|
|
{
|
|
return mAutoExec;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsAutoReset()
|
|
* Purpose: Return status of auto-reset flag.
|
|
* Arguments: n/a
|
|
* Returns: bool - true if auto-exec flag is enabled.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsAutoReset()
|
|
{
|
|
return mAutoReset;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EnableROM()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::EnableROM()
|
|
{
|
|
mpRAM->EnableROM();
|
|
AddDebugTrace("ROM ENABLED.");
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DisableROM()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisableROM()
|
|
{
|
|
mpRAM->DisableROM();
|
|
AddDebugTrace("ROM DISABLED.");
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetROM()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::SetROM(unsigned short start, unsigned short end)
|
|
{
|
|
mpRAM->SetROM(start, end);
|
|
if (mDebugTraceActive) {
|
|
string msg;
|
|
msg = "ROM SET, Start: $" + Addr2HexStr(start) + ", End: $";
|
|
msg += Addr2HexStr(end) + ".";
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EnableROM()
|
|
* Purpose: Sets and enables Read Only Memory range.
|
|
* Arguments: start, end - unsigned short : start and end addresses
|
|
* of the ROM.
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::EnableROM(unsigned short start, unsigned short end)
|
|
{
|
|
mpRAM->EnableROM(start, end);
|
|
if (mDebugTraceActive) {
|
|
string msg;
|
|
msg = "ROM ENABLED, Start: $" + Addr2HexStr(start) + ", End: $";
|
|
msg += Addr2HexStr(end) + ".";
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetROMBegin()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::GetROMBegin()
|
|
{
|
|
return mpRAM->GetROMBegin();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetROMEnd()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::GetROMEnd()
|
|
{
|
|
return mpRAM->GetROMEnd();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsROMEnabled()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsROMEnabled()
|
|
{
|
|
return mpRAM->IsROMEnabled();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetRunAddr()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::GetRunAddr()
|
|
{
|
|
return mRunAddr;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetOpInterrupt()
|
|
* Purpose: Set the flag indicating operator interrupt.
|
|
* Arguments: bool - new value of the flag
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::SetOpInterrupt(bool opint)
|
|
{
|
|
mOpInterrupt = opint;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsOpInterrupt()
|
|
* Purpose: Return the flag indicating operator interrupt status.
|
|
* Arguments: n/a
|
|
* Returns: bool - true if operator interrupt flag was set
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsOpInterrupt()
|
|
{
|
|
return mOpInterrupt;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetExecHistory()
|
|
* Purpose: Return history of executed opcodes (last 20).
|
|
* Arguments: n/a
|
|
* Returns: queue<string>
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
queue<string> VMachine::GetExecHistory()
|
|
{
|
|
return mpCPU->GetExecHistory();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Disassemble()
|
|
* Purpose: Disassemble code in memory. Return next instruction
|
|
* address.
|
|
* Arguments: addr - address in memory
|
|
* buf - character buffer for disassembled instruction
|
|
* Returns: unsigned short - address of next instruction
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
unsigned short VMachine::Disassemble(unsigned short addr, char *buf)
|
|
{
|
|
return mpCPU->Disassemble(addr, buf);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Reset()
|
|
* Purpose: Reset VM and CPU (should never return except operator
|
|
* induced interrupt).
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::Reset()
|
|
{
|
|
mpCPU->Reset();
|
|
Exec(mpCPU->GetRegs()->PtrAddr);
|
|
mpCPU->mExitAtLastRTS = true;
|
|
AddDebugTrace("*** CPU RESET ***");
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Interrupt()
|
|
* Purpose: Send Interrupt ReQuest to CPU (set the IRQ line LOW).
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::Interrupt()
|
|
{
|
|
mpCPU->Interrupt();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetLastError()
|
|
* Purpose: Return error code set by last operation. Reset error
|
|
* code to OK.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
int VMachine::GetLastError()
|
|
{
|
|
int ret = mError;
|
|
mError = MEMIMGERR_OK;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EnableExecHistory()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::EnableExecHistory(bool enexehist)
|
|
{
|
|
mpCPU->EnableExecHistory(enexehist);
|
|
if (mDebugTraceActive) {
|
|
string msg;
|
|
msg = "The op-code execute history "
|
|
+ (string)((enexehist) ? "ENABLED" : "DISABLED");
|
|
msg += ".";
|
|
AddDebugTrace(msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsExecHistoryActive()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsExecHistoryActive()
|
|
{
|
|
return mpCPU->IsExecHistoryEnabled();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EnableDebugTrace()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::EnableDebugTrace()
|
|
{
|
|
if (!mDebugTraceActive) {
|
|
mDebugTraceActive = true;
|
|
while (!mDebugTraces.empty()) mDebugTraces.pop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DisableDebugTrace()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisableDebugTrace()
|
|
{
|
|
mDebugTraceActive = false;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsDebugTraceActive()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsDebugTraceActive()
|
|
{
|
|
return mDebugTraceActive;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: GetDebugTraces()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
queue<string> VMachine::GetDebugTraces()
|
|
{
|
|
return mDebugTraces;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EnablePerfStats()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::EnablePerfStats()
|
|
{
|
|
if (!mPerfStatsActive) {
|
|
mPerfStatsActive = true;
|
|
mPerfStats.cycles = 0;
|
|
mPerfStats.prev_cycles = 0;
|
|
mPerfStats.prev_usec = 0;
|
|
mPerfStats.perf_onemhz = 0;
|
|
AddDebugTrace("Performance stats ENABLED.");
|
|
}
|
|
}
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DisablePerfStats()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisablePerfStats()
|
|
{
|
|
mPerfStatsActive = false;
|
|
AddDebugTrace("Performance stats DISABLED.");
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: IsPerfStatsActive()
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
bool VMachine::IsPerfStatsActive()
|
|
{
|
|
return mPerfStatsActive;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: AddDebugTrace()
|
|
* Purpose: Add string to the debug messages queue. String is
|
|
* prefixed with time stamp (number of microseconds since
|
|
* start of program. Queue is maintained to not exceed
|
|
* DBG_TRACE_SIZE. If the size is exceeded with the next
|
|
* added debug message, the first one in the queue is
|
|
* deleted (FIFO).
|
|
* Arguments: msg - string : debug message.
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::AddDebugTrace(string msg)
|
|
{
|
|
if (mDebugTraceActive) {
|
|
stringstream ss;
|
|
string mmsg;
|
|
|
|
// add timestamp in front (micro seconds)
|
|
auto lap = high_resolution_clock::now();
|
|
unsigned long usec = duration_cast<microseconds>(lap-mBeginTime).count();
|
|
ss << usec;
|
|
ss >> mmsg;
|
|
mmsg += " : " + msg;
|
|
mDebugTraces.push(mmsg);
|
|
while (mDebugTraces.size() > DBG_TRACE_SIZE) mDebugTraces.pop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Addr2HexStr()
|
|
* Purpose: Convert 16-bit address to a hex notation string.
|
|
* Arguments: addr - 16-bit unsigned
|
|
* Returns: string
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
string VMachine::Addr2HexStr(unsigned short addr)
|
|
{
|
|
stringstream ss;
|
|
string ret;
|
|
|
|
ss << hex << addr;
|
|
ss >> ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: Addr2DecStr()
|
|
* Purpose: Convert 16-bit address to a decimal notation string.
|
|
* Arguments: addr - 16-bit unsigned
|
|
* Returns: string
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
string VMachine::Addr2DecStr(unsigned short addr)
|
|
{
|
|
stringstream ss;
|
|
string ret;
|
|
|
|
ss << addr;
|
|
ss >> ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
} // namespace MKBasic
|