mirror of https://github.com/makarcz/vm6502.git
580 lines
14 KiB
C++
580 lines
14 KiB
C++
#include <stdio.h>
|
|
#include <iostream>
|
|
#include <conio.h>
|
|
#include <string.h>
|
|
#include "VMachine.h"
|
|
#include "MKGenException.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace MKBasic {
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method:
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* 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 mpDisp;
|
|
delete mpCPU;
|
|
delete mpROM;
|
|
delete mpRAM;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: InitVM()
|
|
* Purpose: Initialize class.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::InitVM()
|
|
{
|
|
mpRAM = new Memory();
|
|
mRunAddr = 0x200;
|
|
mCharIOAddr = CHARIO_ADDR;
|
|
mCharIOActive = mCharIO = false;
|
|
if (NULL == mpRAM) {
|
|
throw MKGenException("Unable to initialize VM (RAM).");
|
|
}
|
|
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).");
|
|
}
|
|
mpDisp = new Display();
|
|
if (NULL == mpDisp) {
|
|
throw MKGenException("Unable to initialize VM (Display).");
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method:
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ClearScreen()
|
|
{
|
|
HANDLE hStdOut;
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
DWORD count;
|
|
DWORD cellCount;
|
|
COORD homeCoords = { 0, 0 };
|
|
|
|
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
|
|
if (hStdOut == INVALID_HANDLE_VALUE) return;
|
|
|
|
/* Get the number of cells in the current buffer */
|
|
if (!GetConsoleScreenBufferInfo( hStdOut, &csbi )) return;
|
|
cellCount = csbi.dwSize.X *csbi.dwSize.Y;
|
|
|
|
/* Fill the entire buffer with spaces */
|
|
if (!FillConsoleOutputCharacter(
|
|
hStdOut,
|
|
(TCHAR) ' ',
|
|
cellCount,
|
|
homeCoords,
|
|
&count
|
|
)) return;
|
|
|
|
/* Fill the entire buffer with the current colors and attributes */
|
|
if (!FillConsoleOutputAttribute(
|
|
hStdOut,
|
|
csbi.wAttributes,
|
|
cellCount,
|
|
homeCoords,
|
|
&count
|
|
)) return;
|
|
|
|
/* Move the cursor home */
|
|
SetConsoleCursorPosition( hStdOut, homeCoords );
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method:
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ScrHome()
|
|
{
|
|
HANDLE hStdOut;
|
|
COORD homeCoords = { 0, 0 };
|
|
|
|
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
|
|
if (hStdOut == INVALID_HANDLE_VALUE) return;
|
|
|
|
/* Move the cursor home */
|
|
SetConsoleCursorPosition( hStdOut, homeCoords );
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method:
|
|
* Purpose:
|
|
* Arguments:
|
|
* Returns:
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ShowDisp()
|
|
{
|
|
if (mCharIOActive) {
|
|
#if defined (WINDOWS)
|
|
//ClearScreen();
|
|
ScrHome();
|
|
#elif defined (LINUX)
|
|
system("clear");
|
|
#endif
|
|
mpDisp->ShowScr();
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* 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;
|
|
|
|
#if defined (WINDOWS)
|
|
ClearScreen();
|
|
#elif defined (LINUX)
|
|
system("clear");
|
|
#endif
|
|
ShowDisp();
|
|
while (true) {
|
|
cpureg = Step();
|
|
if (mCharIO) {
|
|
ShowDisp();
|
|
}
|
|
if (cpureg->SoftIrq)
|
|
break;
|
|
}
|
|
|
|
ShowDisp();
|
|
|
|
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.
|
|
* NOTE: Stack must be empty!
|
|
* Arguments: n/a
|
|
* Returns: Pointer to CPU registers and flags.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
Regs *VMachine::Exec()
|
|
{
|
|
Regs *cpureg = NULL;
|
|
|
|
#if defined (WINDOWS)
|
|
ClearScreen();
|
|
#elif defined (LINUX)
|
|
system("clear");
|
|
#endif
|
|
ShowDisp();
|
|
while (true) {
|
|
cpureg = Step();
|
|
if (mCharIO) {
|
|
ShowDisp();
|
|
}
|
|
if (cpureg->LastRTS) break;
|
|
}
|
|
|
|
ShowDisp();
|
|
|
|
return cpureg;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* 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()
|
|
{
|
|
unsigned short addr = mRunAddr;
|
|
Regs *cpureg = NULL;
|
|
|
|
cpureg = mpCPU->ExecOpcode(addr);
|
|
addr = cpureg->PtrAddr;
|
|
mRunAddr = addr;
|
|
|
|
if (mCharIOActive) {
|
|
char c = -1;
|
|
mCharIO = false;
|
|
while ((c = mpRAM->GetCharOut()) != -1) {
|
|
mpDisp->PutChar(c);
|
|
mCharIO = true;
|
|
}
|
|
}
|
|
|
|
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: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::LoadRAM(string ramfname)
|
|
{
|
|
LoadMEM(ramfname, mpRAM);
|
|
mpRAM->EnableROM();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: LoadMEM()
|
|
* Purpose: Load data from memory definition file to the memory.
|
|
* Arguments: memfname - name of memory definition file
|
|
* pmem - pointer to memory object
|
|
* Returns: n/a
|
|
* Details:
|
|
* Format of the memory definition file:
|
|
* ; comment
|
|
* ADDR
|
|
* address
|
|
* data
|
|
* ORG
|
|
* address
|
|
*
|
|
* Where:
|
|
* ADDR - label indicating that starting address will follow in next
|
|
* line
|
|
* ORG - label indicating that the address counter will change to the
|
|
* value provided in next line
|
|
* 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
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::LoadMEM(string memfname, Memory *pmem)
|
|
{
|
|
FILE *fp = NULL;
|
|
char line[256] = "\0";
|
|
unsigned short addr = 0x200;
|
|
unsigned int nAddr;
|
|
Memory *pm = pmem;
|
|
|
|
if ((fp = fopen(memfname.c_str(), "r")) != NULL) {
|
|
fgets(line, 256, fp);
|
|
if (0 == strcmp(line, "ADDR")) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
addr = nAddr;
|
|
} else {
|
|
addr = (unsigned short) atoi(line);
|
|
}
|
|
mRunAddr = addr;
|
|
}
|
|
while (0 == feof(fp) && 0 == ferror(fp))
|
|
{
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
if (0 == strncmp(line, "ORG", 3)) {
|
|
line[0] = '\0';
|
|
fgets(line, 256, fp);
|
|
if (*line == '$') {
|
|
sscanf(line+1, "%04x", &nAddr);
|
|
addr = nAddr;
|
|
} else {
|
|
addr = (unsigned short) atoi(line);
|
|
}
|
|
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->Poke8bit(addr++, (unsigned short)nVal);
|
|
} else {
|
|
pm->Poke8bit(addr++, (unsigned short)atoi(s));
|
|
}
|
|
s = strtok(NULL, " ,");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
cout << "WARNING: Unable to open memory definition file: " << memfname << endl;
|
|
cout << "Press [ENTER]...";
|
|
getchar();
|
|
//throw MKGenException("Unable to open memory definition file: " + memfname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* 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->ClrScr();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DisableCharIO()
|
|
* Purpose: Deactivates basic character I/O emulation (console).
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::DisableCharIO()
|
|
{
|
|
mCharIOActive = false;
|
|
mpRAM->DisableCharIO();
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* 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: ShowIO()
|
|
* Purpose: Show contents of emulated char I/O.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void VMachine::ShowIO()
|
|
{
|
|
if (mCharIOActive)
|
|
mpDisp->ShowScr();
|
|
}
|
|
|
|
} // namespace MKBasic
|