Memory Mapped Device Layer

Implemented MemMapDev layer, added User Manual and Programmers
Reference.
This commit is contained in:
Marek Karcz 2016-08-09 23:39:52 -04:00
parent 0d47565b9d
commit 20d12b5eae
17 changed files with 3838 additions and 1123 deletions

464
GraphDisp.cpp Normal file
View File

@ -0,0 +1,464 @@
#include "GraphDisp.h"
#include "MKGenException.h"
#include "system.h"
#if defined(WINDOWS)
#include "wtypes.h"
#endif
namespace MKBasic {
/*
*--------------------------------------------------------------------
* Method: GraphDisp()
* Purpose: Class default constructor.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
GraphDisp::GraphDisp()
{
Initialize();
}
/*
*--------------------------------------------------------------------
* Method: GraphDisp()
* Purpose: Class custom constructor.
* Arguments: width, height - integer, graphical display dimensions
* (virtual dimensions as opposed to
* to actual dimensions GRAPHDISP_MAXW,
* GRAPHDISP_MAXH)
* Returns: n/a
*--------------------------------------------------------------------
*/
GraphDisp::GraphDisp(int width, int height)
{
mWidth = width;
mHeight = height;
Initialize();
}
/*
*--------------------------------------------------------------------
* Method: ~GraphDisp()
* Purpose: Class destructor.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
GraphDisp::~GraphDisp()
{
mContLoop = false;
while (mMainLoopActive);
SDL_DestroyRenderer(mpRenderer);
SDL_DestroyWindow(mpWindow);
SDL_Quit();
}
/*
*--------------------------------------------------------------------
* Method: Initialize()
* Purpose: Initialize class members, objects.
* Arguments: n/a
* Returns: n/a
* Problems:
* SDL_CreateWindow with flags:
* SDL_WINDOW_SHOWN|SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE
* creates window with no title/icon/system buttons, but with
* a frame, which can be used to resize it. The problem is, when
* it is resized, it stops updating correctly.
*--------------------------------------------------------------------
*/
void GraphDisp::Initialize()
{
int desk_w, desk_h, winbd_top = 5, winbd_right = 5;
GetDesktopResolution(desk_w, desk_h);
// Available in version > 2.0.4
//SDL_GetWindowBordersSize(mpWindow, &winbd_top, NULL, NULL, &winbd_right);
mWinPosX = desk_w - GRAPHDISP_MAXW - winbd_right;
mWinPosY = winbd_top;
SDL_Init(SDL_INIT_VIDEO);
mpWindow = SDL_CreateWindow(
"GraphDisp",
mWinPosX,
mWinPosY,
GRAPHDISP_MAXW,
GRAPHDISP_MAXH,
SDL_WINDOW_SHOWN|SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE
);
if (NULL == mpWindow) {
throw MKGenException(SDL_GetError());
}
// Get window surface
mpSurface = SDL_GetWindowSurface(mpWindow);
// Create renderer for window
mpRenderer = SDL_CreateRenderer(mpWindow, -1, SDL_RENDERER_SOFTWARE);
//mpRenderer = SDL_GetRenderer(mpWindow);
if (NULL == mpRenderer) {
throw MKGenException(SDL_GetError());
}
Clear();
}
/*
*--------------------------------------------------------------------
* Method: ClearScreen()
* Purpose: Clear the surface. Update screen.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::ClearScreen()
{
Clear();
UpdateSurface();
}
/*
*--------------------------------------------------------------------
* Method: Clear()
* Purpose: Clear the surface.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::Clear()
{
//Fill the surface with background color
SDL_FillRect(mpSurface, NULL, SDL_MapRGB(mpSurface->format,
mBgRgbR,
mBgRgbG,
mBgRgbB));
}
/*
*--------------------------------------------------------------------
* Method: UpdateSurface()
* Purpose: Update window surface.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::UpdateSurface()
{
//Update the surface
SDL_UpdateWindowSurface(mpWindow);
}
/*
*--------------------------------------------------------------------
* Method: Update()
* Purpose: Update window surface (public).
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::Update()
{
//Update the surface
UpdateSurface();
}
/*
*--------------------------------------------------------------------
* Method: RenderPixel()
* Purpose: Set or unset pixel (scaled) on graphics display
* surface.
* Arguments: x, y - integer, virtual pixel coordinates
* set - boolean, set or unset pixel
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::RenderPixel(int x, int y, bool set)
{
SDL_Rect rtf;
rtf.x = x * mPixelSizeX; rtf.y = y * mPixelSizeY;
rtf.w = mPixelSizeX;
rtf.h = mPixelSizeY;
int rgb_r = 0, rgb_g = 0, rgb_b = 0;
if (set) {
rgb_r = mFgRgbR;
rgb_g = mFgRgbG;
rgb_b = mFgRgbB;
} else {
rgb_r = mBgRgbR;
rgb_g = mBgRgbG;
rgb_b = mBgRgbB;
}
// set or unset pixel
SDL_FillRect(mpSurface, &rtf, SDL_MapRGB(mpSurface->format, rgb_r, rgb_g, rgb_b));
//Update the surface
SDL_UpdateWindowSurface(mpWindow);
}
/*
*--------------------------------------------------------------------
* Method: SetPixel()
* Purpose: Set pixel (scaled) on graphics display surface.
* Arguments: x, y - pixel coordinates
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::SetPixel(int x, int y)
{
RenderPixel(x, y, true);
}
/*
*--------------------------------------------------------------------
* Method: ErasePixel()
* Purpose: Unset (erase) pixel (paint with BG color) on graphics
* display surface.
* Arguments: x, y - pixel coordinates
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::ErasePixel(int x, int y)
{
RenderPixel(x, y, false);
}
/*
*--------------------------------------------------------------------
* Method: DrawLine()
* Purpose: Draw or erase line between specified points.
* Arguments: x1, y1 - coordinates of first point
* x2, y2 - coordinates of second point
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::DrawLine(int x1, int y1, int x2, int y2, bool draworerase)
{
int colR = mFgRgbR, colG = mFgRgbG, colB = mFgRgbB;
if (false == draworerase) {
colR = mBgRgbR;
colG = mBgRgbG;
colB = mBgRgbB;
}
SDL_SetRenderDrawColor(mpRenderer, colR, colG, colB, SDL_ALPHA_OPAQUE);
SDL_RenderSetLogicalSize(mpRenderer, mWidth, mHeight);
SDL_RenderSetScale(mpRenderer, mPixelSizeX, mPixelSizeY);
SDL_RenderDrawLine(mpRenderer, x1, y1, x2, y2);
SDL_RenderPresent(mpRenderer);
}
/*
*--------------------------------------------------------------------
* Method: DrawLine()
* Purpose: Draw line between specified points.
* Arguments: x1, y1 - coordinates of first point
* x2, y2 - coordinates of second point
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::DrawLine(int x1, int y1, int x2, int y2)
{
DrawLine(x1, y1, x2, y2, true);
}
/*
*--------------------------------------------------------------------
* Method: EraseLine()
* Purpose: Erase line between specified points (draw with BG color)
* Arguments: x1, y1 - coordinates of first point
* x2, y2 - coordinates of second point
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::EraseLine(int x1, int y1, int x2, int y2)
{
DrawLine(x1, y1, x2, y2, false);
}
/*
*--------------------------------------------------------------------
* Method: SetBgColor()
* Purpose: Set background color.
* Arguments: r, g, b - integer, red, green and blue intensities
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::SetBgColor(int r, int g, int b)
{
mBgRgbR = r;
mBgRgbG = g;
mBgRgbB = b;
}
/*
*--------------------------------------------------------------------
* Method: SetFgColor()
* Purpose: Set foreground color.
* Arguments: r, g, b - integer, red, green and blue intensities
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::SetFgColor(int r, int g, int b)
{
mFgRgbR = r;
mFgRgbG = g;
mFgRgbB = b;
}
/*
*--------------------------------------------------------------------
* Method: MainLoop()
* Purpose: The main loop to process SDL events and update window.
* This is a global function meant to run in a thread.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void MainLoop(GraphDisp *pgd)
{
pgd->mMainLoopActive = true;
while (pgd->mContLoop) {
pgd->ReadEvents();
pgd->Update();
}
pgd->mMainLoopActive = false;
}
/*
*--------------------------------------------------------------------
* Method: Start()
* Purpose: Starts MainLoop in a thread.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::Start(GraphDisp *pgd)
{
mMainLoopThread = thread(MainLoop,pgd);
}
/*
*--------------------------------------------------------------------
* Method: Stop()
* Purpose: Stop MainLoop thread.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void GraphDisp::Stop()
{
mContLoop = false;
mMainLoopThread.join();
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
bool GraphDisp::IsMainLoopActive()
{
return mMainLoopActive;
}
/*
*--------------------------------------------------------------------
* Method: ReadEvents()
* Purpose: Read events and perform actions when needed.
* Arguments: n/a
* Returns: n/a
* Problems:
* By blitting (copying) surface I wanted to avoid loosing already
* drawn pixels during windows resize, but this doesn't work.
*--------------------------------------------------------------------
*/
void GraphDisp::ReadEvents()
{
SDL_Event e;
SDL_Surface *pTmpSurface;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
mContLoop = false;
break;
case SDL_WINDOWEVENT:
if (SDL_WINDOWEVENT_RESIZED == e.window.event) {
pTmpSurface = SDL_CreateRGBSurface(0,
mpSurface->w,
mpSurface->h,
mpSurface->format->BitsPerPixel,
mpSurface->format->Rmask,
mpSurface->format->Gmask,
mpSurface->format->Bmask,
mpSurface->format->Amask);
SDL_SetWindowSize(mpWindow, GRAPHDISP_MAXW, GRAPHDISP_MAXH);
mpSurface = SDL_GetWindowSurface(mpWindow);
SDL_SetWindowPosition(mpWindow, mWinPosX, mWinPosY);
SDL_SetSurfaceAlphaMod(pTmpSurface, 0);
SDL_BlitSurface(pTmpSurface, 0, mpSurface, 0);
UpdateSurface();
SDL_FreeSurface(pTmpSurface);
} else if (SDL_WINDOWEVENT_FOCUS_GAINED == e.window.event) {
SDL_RaiseWindow(mpWindow);
}
break;
default:
break;
}
}
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void GraphDisp::GetDesktopResolution(int& horizontal, int& vertical)
{
#if defined(WINDOWS)
RECT desktop;
// Get a handle to the desktop window
const HWND hDesktop = GetDesktopWindow();
// Get the size of screen to the variable desktop
GetWindowRect(hDesktop, &desktop);
// The top left corner will have coordinates (0,0)
// and the bottom right corner will have coordinates
// (horizontal, vertical)
horizontal = desktop.right;
vertical = desktop.bottom;
#else
horizontal = GRAPHDISP_MAXW;
vertical = GRAPHDISP_MAXH;
#endif
}
} // namespace MKBasic

72
GraphDisp.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef GRAPHDISP_H
#define GRAPHDISP_H
/*
* Rudimentary emulation of generic raster graphics RGB display device.
*/
#include <thread>
#include <SDL.h>
using namespace std;
namespace MKBasic {
const int GRAPHDISP_MAXW = 640; // "real" display width or maximum virtual width
const int GRAPHDISP_MAXH = 400; // "real" display width or maximum virtual height
class GraphDisp {
public:
bool mContLoop = true;
bool mMainLoopActive = false;
GraphDisp();
GraphDisp(int width, int height);
~GraphDisp();
void Start(GraphDisp *pgd);
void Stop();
void SetPixel(int x, int y);
void ErasePixel(int x, int y);
void DrawLine(int x1, int y1, int x2, int y2);
void EraseLine(int x1, int y1, int x2, int y2);
void SetBgColor(int r, int g, int b);
void SetFgColor(int r, int g, int b);
void Update();
void ReadEvents();
void ClearScreen();
//void MainLoop();
bool IsMainLoopActive();
private:
int mWidth = 320; // virtual display width
int mHeight = 200; // virtual display height
int mPixelSizeX = 2; // virtual pixel width
int mPixelSizeY = 2; // virtual pixel height
int mWinPosX = 0; // SDL window position coordinate X
int mWinPosY = 0; // SDL window position coordinate Y
int mBgRgbR = 0; // bg color, RGB red intensity
int mBgRgbG = 0; // bg color, RGB green intensity
int mBgRgbB = 0; // bg color, RGB blue intensity
int mFgRgbR = 0xFF; // fg color, RGB red intensity
int mFgRgbG = 0xFF; // fg color, RGB green intensity
int mFgRgbB = 0xFF; // fg color, RGB blue intensity
SDL_Window *mpWindow = NULL;
SDL_Surface *mpSurface = NULL;
SDL_Renderer *mpRenderer = NULL;
thread mMainLoopThread;
void Initialize();
void UpdateSurface();
void Clear();
void GetDesktopResolution(int& horizontal, int& vertical);
void DrawLine(int x1, int y1, int x2, int y2, bool draworerase);
void RenderPixel(int x, int y, bool set);
}; // class GraphDisp
} // namespace MKBasic
#endif

663
MemMapDev.cpp Normal file
View File

@ -0,0 +1,663 @@
#include "MemMapDev.h"
#include "Memory.h"
#include "MKGenException.h"
#include <stdio.h>
#include <ctype.h>
#if defined(WINDOWS)
#include <conio.h>
#endif
//#define DBG 1
#if defined (DBG)
#include <iostream>
using namespace std;
#endif
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
namespace MKBasic {
/*
*--------------------------------------------------------------------
* Method: MemMapDev()
* Purpose: Class constructor.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
MemMapDev::MemMapDev()
{
mpMem = NULL;
Initialize();
}
/*
*--------------------------------------------------------------------
* Method: MemMapDev()
* Purpose: Custom class constructor.
* Arguments: pmem - Pointer to Memory, pointer to Memory object.
* Returns: n/a
*--------------------------------------------------------------------
*/
MemMapDev::MemMapDev(Memory *pmem)
{
mpMem = pmem;
Initialize();
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
MemMapDev::~MemMapDev()
{
}
/*
*--------------------------------------------------------------------
* Method: Initialize()
* Purpose: Initialize class and all supported devices.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::Initialize()
{
mInBufDataBegin = mInBufDataEnd = 0;
mOutBufDataBegin = mOutBufDataEnd = 0;
mIOEcho = false;
mCharIOAddr = CHARIO_ADDR;
mGraphDispAddr = GRDISP_ADDR;
mpGraphDisp = NULL;
AddrRange addr_range(CHARIO_ADDR, CHARIO_ADDR+1);
DevPar dev_par("echo", "false");
MemAddrRanges addr_ranges_chario;
DevParams dev_params_chario;
addr_ranges_chario.push_back(addr_range);
dev_params_chario.push_back(dev_par);
Device dev_chario(DEVNUM_CHARIO,
"Character I/O",
addr_ranges_chario,
&MemMapDev::CharIODevice_Read,
&MemMapDev::CharIODevice_Write,
dev_params_chario);
mDevices.push_back(dev_chario);
addr_range.start_addr = GRDISP_ADDR;
addr_range.end_addr = GRDISP_ADDR + GRAPHDEVREG_END - 1;
dev_par.name = "nil";
dev_par.value = "nil";
MemAddrRanges addr_ranges_grdisp;
DevParams dev_params_grdisp;
addr_ranges_grdisp.push_back(addr_range);
dev_params_grdisp.push_back(dev_par);
Device dev_grdisp(DEVNUM_GRDISP,
"Graphics Display",
addr_ranges_grdisp,
&MemMapDev::GraphDispDevice_Read,
&MemMapDev::GraphDispDevice_Write,
dev_params_grdisp);
mDevices.push_back(dev_grdisp);
}
/*
*--------------------------------------------------------------------
* Method: GetDevice()
* Purpose: Get device by specified number.
* Arguments: devnum - device number
* Returns: Device structure value.
*--------------------------------------------------------------------
*/
Device MemMapDev::GetDevice(int devnum)
{
Device ret;
for (MemMappedDevices::iterator devit = mDevices.begin();
devit != mDevices.end();
++devit
) {
Device dev = *devit;
if (dev.num == devnum) {
ret = dev;
break;
}
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: SetupDevice()
* Purpose: Configure device memory ranges and parameters.
* Arguments: devnum - device number
* memranges - memory address ranges vector
* params - parameters vector
* Returns: integer, 0 if OK, error number otherwise
*--------------------------------------------------------------------
*/
int MemMapDev::SetupDevice(int devnum,
MemAddrRanges memranges,
DevParams params)
{
#if defined(DBG)
cout << "DBG: MemMapDev::SetupDevice()" << endl;
#endif
int ret = 0;
Device dev = GetDevice(devnum);
if (dev.num >= 0) {
dev.addr_ranges = memranges;
dev.params = params;
MemMappedDevices devices_new;
for (MemMappedDevices::iterator it = mDevices.begin();
it != mDevices.end();
++it
) {
if ((*it).num != devnum) devices_new.push_back(*it);
}
devices_new.push_back(dev);
mDevices = devices_new;
// device specific post-processing
if (DEVNUM_CHARIO == devnum) {
MemAddrRanges::iterator it = memranges.begin();
mCharIOAddr = (*it).start_addr;
for (DevParams::iterator it = params.begin();
it != params.end();
++it
) {
string par_name = (*it).name;
string par_val = (*it).value;
if (0 == par_name.compare("echo")) {
if (0 == par_val.compare("true")) mIOEcho = true;
else mIOEcho = false;
}
}
#if defined(DBG)
cout << "DBG: MemMapDev::SetupDevice() mCharIOAddr = $" << hex << mCharIOAddr << endl;
cout << "DBG: MemMapDev::SetupDevice() mIOEcho = " << (mIOEcho?"true":"false") << endl;
#endif
} else if (DEVNUM_GRDISP == devnum) {
MemAddrRanges::iterator it = memranges.begin();
mGraphDispAddr = (*it).start_addr;
}
// finished device specific post-processing
} else {
ret++; // error
#if defined(DBG)
cout << "DBG: MemMapDev::SetupDevice() ERROR!" << endl;
#endif
}
#if defined(DBG)
while (!getchar());
cout << "Press [ENTER]...";
getchar();
#endif
return ret;
}
#if defined(LINUX)
#include <stdlib.h>
#include <string.h>
#include <signal.h>
struct termios orig_termios;
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void reset_terminal_mode()
{
tcsetattr(0, TCSANOW, &orig_termios);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void MemMapDev::set_conio_terminal_mode()
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(reset_terminal_mode);
cfmakeraw(&new_termios);
tcsetattr(0, TCSANOW, &new_termios);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
int MemMapDev::kbhit()
{
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
int MemMapDev::getch()
{
int r;
unsigned char c;
if ((r = read(0, &c, sizeof(c))) < 0) {
return r;
} else {
return c;
}
}
#endif
/*
*--------------------------------------------------------------------
* Method: ReadCharKb()
* Purpose: If char I/O active, read character from console
* (non-blocking) and put in an input FIFO buffer.
* Arguments: nonblock - if true, works in non-blocking mode
* Returns: n/a
*--------------------------------------------------------------------
*/
unsigned char MemMapDev::ReadCharKb(bool nonblock)
{
unsigned char ret = 0;
#if defined(LINUX)
set_conio_terminal_mode();
#endif
static int c = ' ';
if (mIOEcho && isprint(c)) putchar(c);
fflush(stdout);
if (!nonblock) while(!kbhit());
else c = 0;
c = getch();
#if defined(LINUX)
if (c == 3) { // capture CTRL-C in CONIO mode
reset_terminal_mode();
kill(getpid(),SIGINT);
}
#endif
mCharIOBufIn[mInBufDataEnd] = c;
mInBufDataEnd++;
if (mInBufDataEnd >= CHARIO_BUF_SIZE) mInBufDataEnd = 0;
ret = c;
#if defined(LINUX)
reset_terminal_mode();
#endif
return ret;
}
/*
*--------------------------------------------------------------------
* Method: GetCharIn()
* Purpose: Return character from the emulated character I/O FIFO
* input buffer or -1 if FIFO empty or char I/O not active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char MemMapDev::GetCharIn()
{
char ret = -1;
if (mInBufDataEnd != mInBufDataBegin) {
ret = mCharIOBufIn[mInBufDataBegin];
mInBufDataBegin++;
if (mInBufDataBegin >= CHARIO_BUF_SIZE) mInBufDataBegin = 0;
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: GetCharOut()
* Purpose: Return character from the emulated character I/O FIFO
* output buffer or -1 if FIFO empty or char I/O not
* active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char MemMapDev::GetCharOut()
{
char ret = -1;
if (mOutBufDataEnd != mOutBufDataBegin) {
ret = mCharIOBufOut[mOutBufDataBegin];
mOutBufDataBegin++;
if (mOutBufDataBegin >= CHARIO_BUF_SIZE) mOutBufDataBegin = 0;
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: PutCharIO()
* Purpose: Put character in the output char I/O FIFO buffer.
* Arguments: c - character
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::PutCharIO(char c)
{
mCharIOBufOut[mOutBufDataEnd] = c;
mOutBufDataEnd++;
if (mOutBufDataEnd >= CHARIO_BUF_SIZE) mOutBufDataEnd = 0;
}
/*
*--------------------------------------------------------------------
* Method: CharIODevice_Write()
* Purpose: Write value to Char I/O device register.
* Arguments: addr - address, val - value (character)
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::CharIODevice_Write(int addr, int val)
{
if ((unsigned int)addr == mCharIOAddr) {
PutCharIO ((char) val);
}
}
/*
*--------------------------------------------------------------------
* Method: CharIODevice_Read()
* Purpose: Read value from Char I/O device register.
* Arguments: addr - address
* Returns: value
*--------------------------------------------------------------------
*/
int MemMapDev::CharIODevice_Read(int addr)
{
int ret = 0;
if ((unsigned int)addr == mCharIOAddr) {
ret = ReadCharKb(false); // blocking mode input
} else if ((unsigned int)addr == mCharIOAddr+1) {
ret = ReadCharKb(true); // non-blocking mode input
}
mpMem->Poke8bitImg((unsigned short)addr, (unsigned char)ret);
return ret;
}
/*
*--------------------------------------------------------------------
* Method: GetCharIOAddr()
* Purpose: Returns current address of basic character I/O area.
* Arguments: n/a
* Returns: address of I/O area
*--------------------------------------------------------------------
*/
unsigned short MemMapDev::GetCharIOAddr()
{
return mCharIOAddr;
}
/*
*--------------------------------------------------------------------
* Method: GetCharIOEchoOn()
* Purpose: Returns current status of char I/O local echo flag.
* Arguments: n/a
* Returns: true if local echo is enabled, false if disabled
*--------------------------------------------------------------------
*/
bool MemMapDev::GetCharIOEchoOn()
{
return mIOEcho;
}
/*
*--------------------------------------------------------------------
* Method: GetGraphDispAddrBase()
* Purpose: Return base address of graphics display device.
* Arguments: n/a
* Returns: unsigned short - address ($0000 - $FFFF)
*--------------------------------------------------------------------
*/
unsigned short MemMapDev::GetGraphDispAddrBase()
{
return mGraphDispAddr;
}
/*
*--------------------------------------------------------------------
* Method: GraphDispDevice_Read()
* Purpose: Read from specified graphics display device register.
* Arguments: int - address of the register in memory.
* Returns: int - read value.
*--------------------------------------------------------------------
*/
int MemMapDev::GraphDispDevice_Read(int addr)
{
// this device has no read registers, return 0
GraphDisp_ReadEvents();
GraphDisp_Update();
return 0;
}
/*
*--------------------------------------------------------------------
* Method: GraphDispDevice_Write()
* Purpose: Write a value to specified graphics display device
* register.
* Arguments: addr - address of the register in memory
* val - value to write
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::GraphDispDevice_Write(int addr, int val)
{
if (NULL != mpGraphDisp) { // only if device is active
if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_X) {
// setup X coordinate of the pixel or beginning of line,
// less significant part
mGrDevRegs.mGraphDispLoX = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_X + 1) {
// setup X coordinate of the pixel, more significant part
mGrDevRegs.mGraphDispHiX = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_Y) {
// setup Y coordinate of the pixel
mGrDevRegs.mGraphDispY = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_PXCOL_R) {
// setup pixel RGB color Red component
mGrDevRegs.mGraphDispPixColR = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_PXCOL_G) {
// setup pixel RGB color Green component
mGrDevRegs.mGraphDispPixColG = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_PXCOL_B) {
// setup pixel RGB color Blue component
mGrDevRegs.mGraphDispPixColB = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_BGCOL_R) {
// setup background RGB color Red component
mGrDevRegs.mGraphDispBgColR = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_BGCOL_G) {
// setup background RGB color Green component
mGrDevRegs.mGraphDispBgColG = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_BGCOL_B) {
// setup background RGB color Blue component
mGrDevRegs.mGraphDispBgColB = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_X2) {
// setup X coordinate of the end of line, less significant part
mGrDevRegs.mGraphDispLoX2 = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_X2 + 1) {
// setup X coordinate of the end of line, more significant part
mGrDevRegs.mGraphDispHiX2 = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_Y2) {
// setup Y coordinate of the end of line
mGrDevRegs.mGraphDispY2 = (unsigned char)val;
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_CMD) {
// execute command
switch (val) {
case GRAPHDEVCMD_CLRSCR:
mpGraphDisp->ClearScreen();
break;
case GRAPHDEVCMD_SETPXL:
mpGraphDisp->SetPixel(mGrDevRegs.mGraphDispLoX
+ 256 * mGrDevRegs.mGraphDispHiX,
mGrDevRegs.mGraphDispY);
break;
case GRAPHDEVCMD_CLRPXL:
mpGraphDisp->ErasePixel(mGrDevRegs.mGraphDispLoX
+ 256 * mGrDevRegs.mGraphDispHiX,
mGrDevRegs.mGraphDispY);
break;
case GRAPHDEVCMD_SETBGC:
mpGraphDisp->SetBgColor(mGrDevRegs.mGraphDispBgColR,
mGrDevRegs.mGraphDispBgColG,
mGrDevRegs.mGraphDispBgColB);
break;
case GRAPHDEVCMD_SETFGC:
mpGraphDisp->SetFgColor(mGrDevRegs.mGraphDispPixColR,
mGrDevRegs.mGraphDispPixColG,
mGrDevRegs.mGraphDispPixColB);
break;
case GRAPHDEVCMD_DRAWLN:
mpGraphDisp->DrawLine(mGrDevRegs.mGraphDispLoX
+ 256 * mGrDevRegs.mGraphDispHiX,
mGrDevRegs.mGraphDispY,
mGrDevRegs.mGraphDispLoX2
+ 256 * mGrDevRegs.mGraphDispHiX2,
mGrDevRegs.mGraphDispY2);
break;
case GRAPHDEVCMD_ERASLN:
mpGraphDisp->EraseLine(mGrDevRegs.mGraphDispLoX
+ 256 * mGrDevRegs.mGraphDispHiX,
mGrDevRegs.mGraphDispY,
mGrDevRegs.mGraphDispLoX2
+ 256 * mGrDevRegs.mGraphDispHiX2,
mGrDevRegs.mGraphDispY2);
break;
default:
break;
}
}
GraphDisp_ReadEvents();
GraphDisp_Update();
//mpGraphDisp->Update();
} // if (NULL != mpGraphDisp)
}
/*
*--------------------------------------------------------------------
* Method: ActivateGraphDisp()
* Purpose: Activate graphics display.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::ActivateGraphDisp()
{
if (NULL == mpGraphDisp) {
mpGraphDisp = new GraphDisp();
if (NULL == mpGraphDisp)
throw MKGenException("Out of memory while initializing Graphics Display Device");
mGrDevRegs.mGraphDispBgColR = 0;
mGrDevRegs.mGraphDispBgColG = 0;
mGrDevRegs.mGraphDispBgColB = 0;
mGrDevRegs.mGraphDispPixColR = 0xFF;
mGrDevRegs.mGraphDispPixColG = 0xFF;
mGrDevRegs.mGraphDispPixColB = 0xFF;
mpGraphDisp->SetBgColor(mGrDevRegs.mGraphDispBgColR,
mGrDevRegs.mGraphDispBgColG,
mGrDevRegs.mGraphDispBgColB);
mpGraphDisp->SetFgColor(mGrDevRegs.mGraphDispPixColR,
mGrDevRegs.mGraphDispPixColG,
mGrDevRegs.mGraphDispPixColB);
GraphDisp_Update();
//mpGraphDisp->Start(mpGraphDisp);
}
}
/*
*--------------------------------------------------------------------
* Method: DeactivateGraphDisp()
* Purpose: Inactivate graphics display.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void MemMapDev::DeactivateGraphDisp()
{
#if defined(DBG)
if (false == mpGraphDisp->IsMainLoopActive()) {
cout << "DBG: ERROR: Main Loop is already inactive in Graphics Display." << endl;
}
#endif
//mpGraphDisp->Stop();
if (NULL != mpGraphDisp) delete mpGraphDisp;
mpGraphDisp = NULL;
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void MemMapDev::GraphDisp_ReadEvents()
{
if (NULL != mpGraphDisp) mpGraphDisp->ReadEvents();
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void MemMapDev::GraphDisp_Update()
{
if (NULL != mpGraphDisp) mpGraphDisp->Update();
}
} // namespace MKBasic

236
MemMapDev.h Normal file
View File

@ -0,0 +1,236 @@
#ifndef MEMMAPDEV_H
#define MEMMAPDEV_H
#include <vector>
#include <string>
#include "system.h"
//#include "Memory.h"
#include "GraphDisp.h"
#if defined(LINUX)
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>
#endif
// some default definitions
#define CHARIO_ADDR 0xE000
#define GRDISP_ADDR 0xE002
#define CHARIO_BUF_SIZE 256
using namespace std;
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
namespace MKBasic {
class Memory;
// Definitions for memory mapped devices handling.
// memory address ranges
struct AddrRange {
unsigned short start_addr, end_addr; // inclusive address range
AddrRange(unsigned short staddr, unsigned short endaddr)
{
start_addr = staddr;
end_addr = endaddr;
}
};
// device parameters
struct DevPar {
string name, value;
DevPar(string n, string v)
{
name = n;
value = v;
}
};
typedef vector<AddrRange> MemAddrRanges;
typedef vector<DevPar> DevParams;
class MemMapDev;
// Class MemMapDev implements functionality of all
// memory mapped devices.
// device register read, arguments: address
typedef int (MemMapDev::*ReadFunPtr)(int);
// device register write, arguments: address, value
typedef void (MemMapDev::*WriteFunPtr)(int,int);
// Definition of device.
struct Device {
int num; // device number
string name; // device name
MemAddrRanges addr_ranges; // vector of memory address ranges for this device
ReadFunPtr read_fun_ptr; // pointer to memory register read function
WriteFunPtr write_fun_ptr; // pointer to memory register write function
DevParams params; // list of device parameters
Device()
{
num = -1;
}
Device(int dnum,
string dname,
MemAddrRanges addrranges,
ReadFunPtr prdfun,
WriteFunPtr pwrfun,
DevParams parms)
{
num = dnum;
name = dname;
addr_ranges = addrranges;
read_fun_ptr = prdfun;
write_fun_ptr = pwrfun;
params = parms;
}
};
typedef vector<Device> MemMappedDevices;
enum DevNums {
DEVNUM_CHARIO = 0, // character I/O device
DEVNUM_GRDISP = 1, // raster graphics display device
};
/*
* NOTE regarding device address ranges.
*
* The emulated device is a model of a electronic device that is connected
* to the CPU-s bus (address, memory, control signals).
* Depending on the device, the memory address range maybe as simple as
* a single memory address or it may contain multiple memory addresses
* or ranges of addresses positioned at various non-contiguous places.
* The functions of these addresses are handled internally by the MemMapDev
* class. The client (user) initializing the device, if providing custom
* memory ranges must know more details about device in order to provide
* accurate data. E.g.: if device requires a single address as an entry
* point, just one memory range is needed with start_addr == end_addr.
* If device is more complicated and requires range of addresses to access
* its control registers, another range of addresses to map memory access
* etc., device should be setup accordingly by calling SetupDevice method
* with proper arguments. Device setup is not mandatory, each device that
* is mainained is initialized wit default parameters.
*/
// offsets of raster graphics device registers
enum GraphDevRegs {
GRAPHDEVREG_X = 0,
GRAPHDEVREG_Y = 2,
GRAPHDEVREG_PXCOL_R = 3,
GRAPHDEVREG_PXCOL_G = 4,
GRAPHDEVREG_PXCOL_B = 5,
GRAPHDEVREG_BGCOL_R = 6,
GRAPHDEVREG_BGCOL_G = 7,
GRAPHDEVREG_BGCOL_B = 8,
GRAPHDEVREG_CMD = 9,
GRAPHDEVREG_X2 = 10,
GRAPHDEVREG_Y2 = 12,
//---------------------------
GRAPHDEVREG_END
};
// graphics display commands
enum GraphDevCmds {
GRAPHDEVCMD_CLRSCR = 0,
GRAPHDEVCMD_SETPXL = 1,
GRAPHDEVCMD_CLRPXL = 2,
GRAPHDEVCMD_SETBGC = 3,
GRAPHDEVCMD_SETFGC = 4,
GRAPHDEVCMD_DRAWLN = 5,
GRAPHDEVCMD_ERASLN = 6
};
struct GraphDeviceRegs {
unsigned char mGraphDispLoX;
unsigned char mGraphDispHiX;
unsigned char mGraphDispY;
unsigned char mGraphDispLoX2;
unsigned char mGraphDispHiX2;
unsigned char mGraphDispY2;
unsigned char mGraphDispPixColR;
unsigned char mGraphDispPixColG;
unsigned char mGraphDispPixColB;
unsigned char mGraphDispBgColR;
unsigned char mGraphDispBgColG;
unsigned char mGraphDispBgColB;
};
// Functionality of memory mapped devices
class MemMapDev {
public:
MemMapDev();
MemMapDev(Memory *pmem);
~MemMapDev();
Device GetDevice(int devnum);
int SetupDevice(int devnum, MemAddrRanges memranges, DevParams params);
char GetCharIn();
char GetCharOut();
unsigned short GetCharIOAddr();
bool GetCharIOEchoOn();
int CharIODevice_Read(int addr);
void CharIODevice_Write(int addr, int val);
unsigned short GetGraphDispAddrBase();
void ActivateGraphDisp();
void DeactivateGraphDisp();
int GraphDispDevice_Read(int addr);
void GraphDispDevice_Write(int addr, int val);
void GraphDisp_ReadEvents();
void GraphDisp_Update();
private:
Memory *mpMem; // pointer to memory object
MemMappedDevices mDevices;
char mCharIOBufIn[CHARIO_BUF_SIZE];
char mCharIOBufOut[CHARIO_BUF_SIZE];
unsigned int mInBufDataBegin;
unsigned int mInBufDataEnd;
unsigned int mOutBufDataBegin;
unsigned int mOutBufDataEnd;
unsigned int mCharIOAddr;
bool mIOEcho;
unsigned int mGraphDispAddr;
GraphDisp *mpGraphDisp; // pointer to Graphics Device object
GraphDeviceRegs mGrDevRegs; // graphics display device registers
void Initialize();
unsigned char ReadCharKb(bool nonblock);
void PutCharIO(char c);
#if defined(LINUX)
void set_conio_terminal_mode();
int kbhit();
int getch();
#endif
};
} // namespace MKBasic
#endif // MEMMAPDEV_H

View File

@ -1,12 +1,6 @@
#include "Memory.h"
#include <stdio.h>
#include <ctype.h>
#if defined(WINDOWS)
#include <conio.h>
#endif
//#define DBG 1
#if defined (DBG)
#include <iostream>
@ -47,6 +41,7 @@ Memory::Memory()
*/
Memory::~Memory()
{
if (NULL != mpMemMapDev) delete mpMemMapDev;
}
/*
@ -66,92 +61,13 @@ void Memory::Initialize()
mCharIOAddr = CHARIO_ADDR;
mCharIOActive = false;
mIOEcho = false;
mInBufDataBegin = mInBufDataEnd = 0;
mOutBufDataBegin = mOutBufDataEnd = 0;
mROMBegin = ROM_BEGIN;
mROMEnd = ROM_END;
mROMActive = false;
mpMemMapDev = new MemMapDev(this);
mGraphDispActive = false;
}
#if defined(LINUX)
#include <stdlib.h>
#include <string.h>
#include <signal.h>
struct termios orig_termios;
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void reset_terminal_mode()
{
tcsetattr(0, TCSANOW, &orig_termios);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void Memory::set_conio_terminal_mode()
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(reset_terminal_mode);
cfmakeraw(&new_termios);
tcsetattr(0, TCSANOW, &new_termios);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
int Memory::kbhit()
{
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
int Memory::getch()
{
int r;
unsigned char c;
if ((r = read(0, &c, sizeof(c))) < 0) {
return r;
} else {
return c;
}
}
#endif
/*
*--------------------------------------------------------------------
* Method:
@ -210,141 +126,115 @@ void Memory::EnableROM(unsigned short start, unsigned short end)
/*
*--------------------------------------------------------------------
* Method: ReadCharKb()
* Purpose: If char I/O active, read character from console
* (non-blocking) and put in an input FIFO buffer.
* Arguments: nonblock - if true, works in non-blocking mode
* Returns: n/a
*--------------------------------------------------------------------
*/
unsigned char Memory::ReadCharKb(bool nonblock)
{
unsigned char ret = 0;
if (mCharIOActive) {
#if defined(LINUX)
set_conio_terminal_mode();
#endif
static int c = ' ';
if (mIOEcho && isprint(c)) putchar(c);
fflush(stdout);
if (!nonblock) while(!kbhit());
else c = 0;
c = getch();
#if defined(LINUX)
if (c == 3) { // capture CTRL-C in CONIO mode
reset_terminal_mode();
kill(getpid(),SIGINT);
}
#endif
mCharIOBufIn[mInBufDataEnd] = c;
mInBufDataEnd++;
if (mInBufDataEnd >= CHARIO_BUF_SIZE) mInBufDataEnd = 0;
ret = c;
#if defined(LINUX)
reset_terminal_mode();
#endif
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: GetCharIn()
* Purpose: Return character from the emulated character I/O FIFO
* input buffer or -1 if FIFO empty or char I/O not active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char Memory::GetCharIn()
{
char ret = -1;
if (mCharIOActive) {
if (mInBufDataEnd != mInBufDataBegin) {
ret = mCharIOBufIn[mInBufDataBegin];
mInBufDataBegin++;
if (mInBufDataBegin >= CHARIO_BUF_SIZE) mInBufDataBegin = 0;
}
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: GetCharOut()
* Purpose: Return character from the emulated character I/O FIFO
* output buffer or -1 if FIFO empty or char I/O not
* active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char Memory::GetCharOut()
{
char ret = -1;
if (mCharIOActive) {
if (mOutBufDataEnd != mOutBufDataBegin) {
ret = mCharIOBufOut[mOutBufDataBegin];
mOutBufDataBegin++;
if (mOutBufDataBegin >= CHARIO_BUF_SIZE) mOutBufDataBegin = 0;
}
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: PutCharIO()
* Purpose: Put character in the output char I/O FIFO buffer.
* Arguments: c - character
* Returns: n/a
*--------------------------------------------------------------------
*/
void Memory::PutCharIO(char c)
{
if (mCharIOActive) {
mCharIOBufOut[mOutBufDataEnd] = c;
mOutBufDataEnd++;
if (mOutBufDataEnd >= CHARIO_BUF_SIZE) mOutBufDataEnd = 0;
}
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* Method: Peek8bit()
* Purpose: Get/read 8-bit value from memory. If the memory address
* is within the range of any active devices, call
* handling method for this device.
* Arguments: addr - memory address
* Returns: unsigned char value read from specified memory address
*--------------------------------------------------------------------
*/
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
) {
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;
}
}
}
}
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];
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* Method: Peek8bitImg()
* Purpose: Get/read 8-bit value from memory image only.
* Memory mapped devices are not affected.
* Arguments: addr - memory address
* Returns: unsigned char value read from specified memory address
*--------------------------------------------------------------------
*/
unsigned char Memory::Peek8bitImg(unsigned short addr)
{
return m8bitMem[addr];
}
/*
*--------------------------------------------------------------------
* Method: Peek16bit()
* Purpose: Get/read 16-bit value from memory. If the memory address
* is within the range of any active devices, call
* handling method for this device.
* Arguments: addr - memory address
* Returns: unsigned short value read secified from memory address
* and next memory address (16-bit, little endian)
*--------------------------------------------------------------------
*/
unsigned short Memory::Peek16bit(unsigned short addr)
{
unsigned short ret = 0;
// 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;
}
}
}
}
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;
@ -356,8 +246,8 @@ unsigned short Memory::Peek16bit(unsigned short addr)
*--------------------------------------------------------------------
* Method: Poke8bit()
* Purpose: Write byte to specified memory location.
* If the memory location is mapped to character I/O,
* write value to output buffer and memory location.
* If the memory location is mapped to an active device,
* call corresponding handling function.
* If the memory location is protected (ROM), do not
* write the value.
* Arguments: addr - (0x0000..0xffff) memory address,
@ -367,13 +257,56 @@ unsigned short Memory::Peek16bit(unsigned short addr)
*/
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;
}
}
}
}
mDispOp = (DEVNUM_GRDISP == dev.num);
}
/*
if (mCharIOActive && addr == mCharIOAddr)
PutCharIO(val);
*/
if (!mROMActive || (addr < mROMBegin || addr > mROMEnd)) {
m8bitMem[addr] = val;
}
}
/*
*--------------------------------------------------------------------
* Method: Poke8bitImg()
* Purpose: Write byte to specified memory location.
* Arguments: addr - (0x0000..0xffff) memory address,
* val - value (0x00..0xff)
* Returns: n/a
*--------------------------------------------------------------------
*/
void Memory::Poke8bitImg(unsigned short addr, unsigned char val)
{
m8bitMem[addr] = val;
}
/*
*--------------------------------------------------------------------
* Method: SetCharIO()
@ -386,8 +319,20 @@ void Memory::Poke8bit(unsigned short addr, unsigned char val)
void Memory::SetCharIO(unsigned short addr, bool echo)
{
mCharIOAddr = addr;
mCharIOActive = true;
//mCharIOActive = true;
mIOEcho = echo;
AddrRange addr_range(addr, addr+1);
MemAddrRanges memaddr_ranges;
DevPar dev_par("echo",(echo?"true":"false"));
DevParams dev_params;
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);
}
/*
@ -401,6 +346,7 @@ void Memory::SetCharIO(unsigned short addr, bool echo)
void Memory::DisableCharIO()
{
mCharIOActive = false;
DeleteDevice(DEVNUM_CHARIO);
}
/*
@ -416,6 +362,58 @@ unsigned short Memory::GetCharIOAddr()
return mCharIOAddr;
}
/*
*--------------------------------------------------------------------
* Method: SetGraphDisp()
* Purpose: Setup and activate graphics display device.
* Arguments: addr - base address of display device
* Returns: n/a
*--------------------------------------------------------------------
*/
void Memory::SetGraphDisp(unsigned short addr)
{
AddrRange addr_range(addr, addr + GRAPHDEVREG_END - 1);
MemAddrRanges memaddr_ranges;
DevPar dev_par("nil","nil");
DevParams dev_params;
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);
mpMemMapDev->ActivateGraphDisp();
}
/*
*--------------------------------------------------------------------
* Method: DisableGraphDisp()
* Purpose: Inactivate graphics display device.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void Memory::DisableGraphDisp()
{
mGraphDispActive = false;
mpMemMapDev->DeactivateGraphDisp();
DeleteDevice(DEVNUM_GRDISP);
}
/*
*--------------------------------------------------------------------
* Method: GetGraphDispAddr()
* Purpose: Return base address of graphics display device.
* Arguments: n/a
* Returns: unsigned short - address ($0000 - $FFFF)
*--------------------------------------------------------------------
*/
unsigned short Memory::GetGraphDispAddr()
{
return mpMemMapDev->GetGraphDispAddrBase();
}
/*
*--------------------------------------------------------------------
* Method:
@ -455,4 +453,144 @@ bool Memory::IsROMEnabled()
return mROMActive;
}
/*
*--------------------------------------------------------------------
* Method: AddDevice()
* Purpose: Add device number to active devices list.
* Arguments: devnum - device number
* Returns: 0
*--------------------------------------------------------------------
*/
int Memory::AddDevice(int devnum)
{
mActiveDevNumVec.push_back(devnum);
return 0;
}
/*
*--------------------------------------------------------------------
* Method: DeleteDevice()
* Purpose: Delete device number from active devices list.
* Arguments: devnum - device number
* Returns: 0
*--------------------------------------------------------------------
*/
int Memory::DeleteDevice(int devnum)
{
vector<int> active_new;
for (vector<int>::iterator it = mActiveDevNumVec.begin();
it != mActiveDevNumVec.end();
++it
) {
if (*it != devnum) {
active_new.push_back(*it);
}
}
mActiveDevNumVec.clear();
mActiveDevNumVec = active_new;
return 0;
}
/*
*--------------------------------------------------------------------
* Method: SetupDevice()
* Purpose: Configure device address ranges and parameters.
* Arguments: devnum - device number, must be on active dev. list
* memranges - memory address ranges vector
* params - parameters vector
* Returns: n/a
*--------------------------------------------------------------------
*/
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;
}
}
if (DEVNUM_CHARIO == devnum) {
mCharIOAddr = mpMemMapDev->GetCharIOAddr();
mIOEcho = mpMemMapDev->GetCharIOEchoOn();
}
}
/*
*--------------------------------------------------------------------
* Method: GetCharIn()
* Purpose: Return character from the emulated character I/O FIFO
* input buffer or -1 if FIFO empty or char I/O not active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char Memory::GetCharIn()
{
return mpMemMapDev->GetCharIn();
}
/*
*--------------------------------------------------------------------
* Method: GetCharOut()
* Purpose: Return character from the emulated character I/O FIFO
* output buffer or -1 if FIFO empty or char I/O not
* active.
* Arguments: n/a
* Returns: character
*--------------------------------------------------------------------
*/
char Memory::GetCharOut()
{
return mpMemMapDev->GetCharOut();
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void Memory::GraphDisp_ReadEvents()
{
mpMemMapDev->GraphDisp_ReadEvents();
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void Memory::GraphDisp_Update()
{
mpMemMapDev->GraphDisp_Update();
}
/*
*--------------------------------------------------------------------
* Method: GraphDispOp()
* Purpose: Status of last operation being perf. on Graphics Display
* or not.
* Arguments: n/a
* Returns: bool, true if last operation was performed on Graphics
* Display device.
*--------------------------------------------------------------------
*/
bool Memory::GraphDispOp()
{
return mDispOp;
}
} // namespace MKBasic

151
Memory.h
View File

@ -1,77 +1,74 @@
#ifndef MEMORY_H
#define MEMORY_H
#include "system.h"
#if defined(LINUX)
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>
#endif
#define MAX_8BIT_ADDR 0xFFFF
#define CHARIO_ADDR 0xE000
#define CHARIO_BUF_SIZE 256
#define ROM_BEGIN 0xD000
#define ROM_END 0xDFFF
#define MIN_ROM_BEGIN 0x0200
namespace MKBasic {
class Memory
{
public:
Memory();
~Memory();
void Initialize();
unsigned char Peek8bit(unsigned short addr);
unsigned short Peek16bit(unsigned short addr);
void Poke8bit(unsigned short addr, unsigned char val);
void SetCharIO(unsigned short addr, bool echo);
void DisableCharIO();
unsigned short GetCharIOAddr();
char GetCharIn();
char GetCharOut();
void EnableROM();
void DisableROM();
void SetROM(unsigned short start, unsigned short end);
void EnableROM(unsigned short start, unsigned short end);
unsigned short GetROMBegin();
unsigned short GetROMEnd();
bool IsROMEnabled();
protected:
private:
unsigned char m8bitMem[MAX_8BIT_ADDR+1];
char mCharIOBufIn[CHARIO_BUF_SIZE];
char mCharIOBufOut[CHARIO_BUF_SIZE];
unsigned int mInBufDataBegin;
unsigned int mInBufDataEnd;
unsigned int mOutBufDataBegin;
unsigned int mOutBufDataEnd;
unsigned short mCharIOAddr;
bool mCharIOActive;
bool mIOEcho;
unsigned short mROMBegin;
unsigned short mROMEnd;
bool mROMActive;
unsigned char ReadCharKb(bool nonblock);
void PutCharIO(char c);
#if defined(LINUX)
void set_conio_terminal_mode();
int kbhit();
int getch();
#endif
};
} // namespace MKBasic
#endif
#ifndef MEMORY_H
#define MEMORY_H
#include "system.h"
#include "MemMapDev.h"
#define MAX_8BIT_ADDR 0xFFFF
#define ROM_BEGIN 0xD000
#define ROM_END 0xDFFF
#define MIN_ROM_BEGIN 0x0200
using namespace std;
namespace MKBasic {
class Memory
{
public:
Memory();
~Memory();
void Initialize();
unsigned char Peek8bit(unsigned short addr);
unsigned char Peek8bitImg(unsigned short addr);
unsigned short Peek16bit(unsigned short addr);
void Poke8bit(unsigned short addr, unsigned char val); // write to memory and call memory mapped device handle
void Poke8bitImg(unsigned short addr, unsigned char val); // write to memory image only
void SetCharIO(unsigned short addr, bool echo);
void DisableCharIO();
unsigned short GetCharIOAddr();
char GetCharIn();
char GetCharOut();
void EnableROM();
void DisableROM();
void SetROM(unsigned short start, unsigned short end);
void EnableROM(unsigned short start, unsigned short end);
unsigned short GetROMBegin();
unsigned short GetROMEnd();
bool IsROMEnabled();
int AddDevice(int devnum);
int DeleteDevice(int devnum);
void SetupDevice(int devnum, MemAddrRanges memranges, DevParams params);
void SetGraphDisp(unsigned short addr);
void DisableGraphDisp();
unsigned short GetGraphDispAddr();
void GraphDisp_ReadEvents();
void GraphDisp_Update();
bool GraphDispOp();
protected:
private:
unsigned char m8bitMem[MAX_8BIT_ADDR+1];
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
bool mGraphDispActive;
bool mDispOp;
unsigned char ReadCharKb(bool nonblock);
void PutCharIO(char c);
};
} // namespace MKBasic
#endif

View File

@ -0,0 +1,567 @@
User Manual and Programmers Reference Manual for VM65.
by
Marek Karcz (C) 2016.
1. Introduction.
VM65 is a simulator which can be expanded to become an emulator of more
complex computer system either a real one or completely abstract virtual
machine.
VM65 acronym stands for Virtual Machine 65 and is designed for MOS-6502
simulation. In current form it allows to simulate a MOS-6502 CPU in
MS Windows environment using MS-DOS command line UI.
Software emulates all original MOS-6502 legal opcodes. The illegal opcodes
are not emulated and are opened for expansion. It is up to programmer to
implement illegal opcodes accurate emulation or replace them with extensions
or traps. In current version they do nothing except to consume CPU cycles.
The 6502 emulation is not purely abstract. The basic components of a real
hardware CPU system are emulated: microprocessor, memory and memory mapped
devices abstract layer - about which more in section 4.
Using this software user will be able to load plain text memory definition
file, Intel HEX format file, raw binary image file or binary snapshot image
created with this software and execute or debug code within it in
a continuous or in step-by-step mode.
The file will be loaded and translated to the emulated memory space and
optional emulation parameters included in the image will be interpreted and
adequately translated into values of the corresponding internal flags.
It is possible to automatically run program starting at specified address,
perform automatic reset of the CPU after memory image upload or perform
these actions from command line prompt of the debug console.
2. Command Line Interface and Debug Console.
Usage:
vm65 [-h] | [ramdeffile] [-b | -x] [-r]
Where:
ramdeffile - RAM definition file name
-b - specify input format as binary
-x - specify input format as Intel HEX
-r - after loading, perform CPU RESET
-h - print this help screen
When ran with no arguments, program will load default memory
definition files: default.rom, default.ram and will enter the debug
console menu.
When ramdeffile argument is provided with no input format specified,
program will attempt to automatically detect input format and load the
memory definition from the file, set the flags and parameters depending
on the contents of the memory definition file and enter the corresponding
mode of operation as defined in that file.
If input format is specified (-b|-x), program will load memory from the
provided image file and enter the debug console menu.
To start program, navigate to the program directory in MS-DOS prompt console
and type:
vm65
Above will run the emulator. Since no memory image is specified, program
will attempt to load dummy.rom and dummy.ram files, which supposed to be
plain text memory image definition files. The simplest you can create can
look like this:
ORG
$0000
$00
NOTE: To see the full usage help for vm65, use flag -h:
vm65 -h
Program loaded with no arguments will present user with following UI
which will be referred to as Debug Console:
STOPPED at 0
*-------------*-----------------------*----------*----------*
| PC: $0000 | Acc: $00 (00000000) | X: $00 | Y: $00 |
*-------------*-----------------------*----------*----------*
| NV-BDIZC |
| 00100000 | Last instr.:
*-------------*
Stack: $ff
[]
I/O status: disabled, at: $e000, local echo: OFF.
Graphics status: disabled, at: $e002
ROM: disabled. Range: $d000 - $dfff.
------------------------------------+----------------------------------------
C - continue, S - step | A - set address for next step
G - go/cont. from new address | N - go number of steps, P - IRQ
I - toggle char I/O emulation | X - execute from new address
T - show I/O console | B - blank (clear) screen
E - toggle I/O local echo | F - toggle registers animation
J - set animation delay | M - dump memory, W - write memory
K - toggle ROM emulation | R - show registers, Y - snapshot
L - load memory image | O - display op-codes history
D - disassemble code in memory | Q - quit, 0 - reset, H - help
V - toggle graphics emulation |
------------------------------------+----------------------------------------
> _
Each of the commands in Debug Console is interactive and can be used in two
ways - user can issue command in the prompt, press enter and follow the
instructions/prompts to enter remaining parameters OR if user already knows
the format of the command, all parameters can be entered in a single line.
To see full help for above commands, issue command H in the prompt and press
ENTER.
A quick overview of the some commands available in Debug Console:
* Code execution and debugging commands.
C - Continue
Execute code from current address after it was interrupted.
S - Step
Executes single opcode at current address.
A - Set address for next step
Sets current address to one provided is argument (or prompts for one).
G - Execute (continue) from address.
Asks for (if not provided as argument) address and then executes code.
N - Execute # of steps.
X - Execute from address.
F - Toggle registers animation.
When in multi-step debug mode (command: N), displaying registers
can be suppressed or, when animation mode is enabled - they will
be continuously displayed after each executed step.
J - Set registers animation delay.
Sets the time added at the end of each execution step in multi
step mode (command: N). The default value is 250 ms. The change
of this parameter will basically set the speed of multi-step code
execution. Greater delay will slow down the execution but improve
readability of the registers in Debug Console as they will not change
as often as if this delay was smaller.
R - Show registers.
Displays current status of CPU registers, flags and stack.
O - Display op-codes history.
Show the history of last executed op-codes/instructions, full with
disassembled mnemonic and argument.
D - Disassemble code in memory.
Attempt to disassemble code in specified address range and display
the results (print) on the screen in symbolic form.
0 - Reset.
Run the processor initialization sequence, just like the real CPU
when its RTS signal is set to LOW and HIGH again. CPU will disable
interrupts, copy address from vector $FFFC to processors PC and will
start executing code. Programmer must put initialization routine
under address pointed by $FFFC vector, which will set the arithmetic
mode, initialize stack, I/O devices and enable IRQ if needed before
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.
* Memory access commands.
M - Dump memory.
Dumps contents of memory, hexadecimal and ASCII formats.
W - Write memory.
Writes provided values to memory starting at specified address.
* I/O devices commands.
I - Toggle char I/O emulation.
Enables/disables basic character I/O emulation. When enabled, all writes
to the specified memory address also writes a character code to
to a virtual console. All reads from specified memory address
are interpreted as console character input.
The base address corresponds to blocking mode character I/O device,
while base address + 1 corresponds to non-blocking character I/O device.
The blocking device waits for the input character - code execution
stops until user enters the keystroke, while in non-blocking mode
the emulator takes the recently entered keystroke from the system
keyboard buffer. That means that implementing the character input
in 6502 assembly/machine code requires a loop reading from non-blocking
address until the character code is different than 0, while in blocking
mode it is enough to just read from I/O address. The emulator will only
proceed when the key was pressed by user.
Examples of 6502 code:
- a 'waiting' character input implemented using non-blocking char I/O
readchar: LDA $E001
BEQ readchar ; A = NULL, keep reading char I/O
RTS ; A = char code
- a 'waiting' character inpute implemented using blocking char I/O
readchar: LDA $E000
RTS ; A = char code
T - Show I/O console.
Displays/prints the contents of the virtual console screen.
Note that in run mode (commands X, G or C), virtual screen is
displayed automatically in real-time if I/O emulation is enabled.
V - Toggle graphics emulation.
Enables/disables basic raster (pixel) based RGB graphics display
emulation.
When enabled, window with graphics screen will open and several
registers are available to control the device starting at provided
base address. Detailed description of the device registers os provided
later in this document.
The difference between 'G' and 'X' is that code executed with command 'G'
will return to Debug Console when BRK or last RTS (empty stack) opcode is
encountered, while code executed with 'X' goes to full CPU emulation mode
where BRK will invoke proper interrupt routine as setup by IRQ vector in
memory. The last RTS opcode (empty stack) will return to Debug
Console in both cases unless such behavior is disabled (and this is only
true in case of Reset function, see command '0').
Entering keyboard interrupt codes (CTRL-C, CTRL-BREAK) will return to Debug
Console.
NOTE: All addresses and memory values are to be entered in hexadecimal
format.
3. Implementing the Virtual Machine.
The Virtual Machine (or Emulator) is implemented by the means of multiple
layers of abstraction. The higher the layer, the less code it contains
that is virtual hardware dependent.
On the highest level we have main.cpp which implements the UI and Debug
Console. Programmer can replace this code with custom designed UI or GUI.
The definition of the emulated system layer begins in VMachine class
(VMachine.h and VMachine.cpp) which provides the implementation of the
entire emulated system. It is a template upon which programmer can expand.
Several important methods are defined here that allow to execute the 6502
code in many different modes:
Regs *Run();
Regs *Run(unsigned short addr);
Regs *Exec();
Regs *Exec(unsigned short addr);
Regs *Step();
Regs *Step(unsigned short addr);
void Reset();
Programmer should use this class to implement all the pieces of the
emulated virtual computer system on the highest abstraction level.
In current version the VMachine class initializes the basic system with CPU
and memory at its core and character and raster graphics display devices.
Going one step down the abstraction layer are MKCPu and Memory classes.
The MKCpu class is the most important part of the emulator. It defines
the whole system's architecture. Based on the type of the CPU device we
know how to implement the rest of the core components by knowing the
maximum addressable memory space, size of the data bus (8-bit, 16-bit) etc.
The MKCpu class defines all the internal registers of the virtual CPU,
the op-codes interpreter and its interface to outside components.
The most important API methods are:
Regs *ExecOpcode(unsigned short memaddr);
Regs *GetRegs();
void SetRegs(Regs r);
void Reset(); // reset CPU
void Interrupt(); // Interrupt ReQuest (IRQ)
The Memory class (Memory.h and Memory.cpp) implements essential concept
in any microprocessor based system, which is the memory. The assumption is
that such a system requires some sort of program storage to be able to
execute code. This is not entirely true in the real world, where we can
make the CPU 'think' that it executes the real code with a tricky circuit
and get away without any memory to make the CPU run. This however has
limited usefulness (diagnostics, testing the basic operation of CPU) and
has no use in the emulation domain where we have no need to test the CPU
chip for basic operation on electrical level. The Memory class is also an
important entry level for another concept of microprocessor based system:
a memory mapped device. This brings us to the lower level of abstraction
which is the memory mapped device class: MemMapDev.
Class MemMapDev is a core or hub for implementing all the devices that are
connected to the virtual CPU via its memory address space. This is
explained in more detail in chapter 4.
All remaining classes are the implementation of the actual devices on the
lowest level of abstraction (from the emulator point of view) and other
helper classes and methods.
Examples of virtual devices emulation implementation are: Display and
GraphDisp.
Programmer can add more code and more classes and integrate them into the
emulator using this architecture. This is the starting point of the
emulator of a real microprocessor based system or completely abstract
virtual machine that has no hardware counterpart.
4. Memory Mapped Device Abstraction Layer/API.
In microprocessor based systems in majority of cases communication with
peripheral devices is done via registers which in turn are located under
specific memory addresses.
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.
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:
- character I/O and
- raster (pixel based) graphics display.
Character I/O device uses 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).
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
are following registers:
Offset Register Description
----------------------------------------------------------------------------
0 GRAPHDEVREG_X_LO Least significant part of pixel's X (column)
coordinate or begin of line coord. (0-255)
1 GRAPHDEVREG_X_HI Most significant part of pixel's X (column)
coordinate or begin of line coord. (0-1)
2 GRAPHDEVREG_Y Pixel's Y (row) coordinate (0-199)
3 GRAPHDEVREG_PXCOL_R Pixel's RGB color component - Red (0-255)
4 GRAPHDEVREG_PXCOL_G Pixel's RGB color component - Green (0-255)
5 GRAPHDEVREG_PXCOL_B Pixel's RGB color component - Blue (0-255)
6 GRAPHDEVREG_BGCOL_R Backgr. RGB color component - Red (0-255)
7 GRAPHDEVREG_BGCOL_G Backgr. RGB color component - Green (0-255)
8 GRAPHDEVREG_BGCOL_B Backgr. RGB color component - Blue (0-255)
9 GRAPHDEVREG_CMD Command code
10 GRAPHDEVREG_X2_LO Least significant part of end of line's X
coordinate
11 GRAPHDEVREG_X2_HI Most significant part of end of line's X
coordinate
12 GRAPHDEVREG_Y2 End of line's Y (row) coordinate (0-199)
Writing values to above memory locations when Graphics Device is enabled
allows to set the corresponding parameters of the device, while writing to
command register executes corresponding command (performs action) per codes
listed below:
Command code Command description
----------------------------------------------------------------------------
GRAPHDEVCMD_CLRSCR = 0 Clear screen
GRAPHDEVCMD_SETPXL = 1 Set the pixel location to pixel color
GRAPHDEVCMD_CLRPXL = 2 Clear the pixel location (set to bg color)
GRAPHDEVCMD_SETBGC = 3 Set the background color
GRAPHDEVCMD_SETFGC = 4 Set the foreground (pixel) color
GRAPHDEVCMD_DRAWLN = 5 Draw line
GRAPHDEVCMD_ERASLN = 6 Erase line
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
a pixel or set color).
I plan to add graphics frame buffer in the VM's RAM address space in future
release.
Simple demo program written in EhBasic that shows how to drive the graphics
screen:
1 REM GRAPHICS DISPLAY DEVICE DEMO
2 REM BASE ADDRESS $FFE2
3 REM DRAW HORIZONTAL AND VERTICAL LINES
4 REM DRAW SINUSOID
10 GB=65506:REM SET BASE ADDRESS
12 REM INITIALIZE, SET COLORS
15 POKE GB+1,0:POKE GB+11,0
16 POKE GB+3,0:POKE GB+4,255:POKE GB+5,0
17 POKE GB+6,0:POKE GB+7,0:POKE GB+8,0
18 POKE GB+9,3:POKE GB+9,4:POKE GB+9,0
19 GOSUB 1120:REM DRAW SINUSOID
20 Y=100:REM X-AXIS
30 GOSUB 1000
50 X=100:REM Y-AXIS
60 GOSUB 1060
70 REM SOME EXTRA DOTTED LINES
80 Y=50:GOSUB 1200
90 Y=150:GOSUB 1200
100 X=50:GOSUB 1260
110 X=150:GOSUB 1260
990 PRINT "DONE"
998 END
999 REM ------- SUBROUTINES SECTION -------
1000 REM DRAW HORIZONTAL LINE AT Y
1005 POKE GB+2,Y
1006 POKE GB+12,Y
1020 POKE GB,0
1025 POKE GB+10,199:POKE GB+9,5
1050 RETURN
1060 REM DRAW VERTICAL LINE AT X
1070 POKE GB,X
1075 POKE GB+10,X
1090 POKE GB+2,0
1095 POKE GB+12,199:POKE GB+9,5
1110 RETURN
1120 REM SINUSOID
1130 FOR X=0 TO 199-4 STEP 5
1140 XX=X*(6.28/200)
1145 XE=(X+5)*(6.28/200)
1150 YY=SIN(XX):YE=SIN(XE)
1160 Y=199-INT((YY+1)*100)
1165 Y2=199-INT((YE+1)*100)
1170 POKE GB,X:POKE GB+2,Y
1175 POKE GB+10,X+5:POKE GB+12,Y2:POKE GB+9,5
1180 NEXT X
1190 RETURN
1200 REM DRAW DOTTED HORIZONTAL LINE AT Y
1205 POKE GB+2,Y
1210 FOR X=0 TO 199 STEP 4
1220 POKE GB,X
1230 POKE GB+9,1
1240 NEXT X
1250 RETURN
1260 REM DRAW DOTTED VERTICAL LINE AT X
1270 POKE GB,X
1280 FOR Y=0 TO 199 STEP 4
1290 POKE GB+2,Y
1300 POKE GB+9,1
1310 NEXT Y
1320 RETURN
4.1. Adding new device implementation.
MemMapDev.h and MemMapDev.cpp define the higher abstraction layer for memory
mapped devices. Memory address range class: AddrRange and device class:
Device are implemented to provide core facilities and class: MemMapDev for
future expansion of the emulated system.
To add a new device to the memory mapped devices, programmer must:
* Implement the device behavior in a separate class.
See GraphDisp.h, GraphDisp.cpp as example.
This is the actual device driver, code that defines how the device works.
Device driver class may define the raster graphics display functions,
a real time clock chip functions, sound chip functions, Disk Drive
functions etc.
This code does not contain definitions related to how the device is mapped
to the memory space of emulated computer system. That would be the
responsibility of MemMapDev class, providing the middle layer of
abstraction for connecting the device to the memory space of the CPU.
* Add necessary definitions, enumerators and methods to the MemMapDev.h
and MemMapDev.cpp.
The minimal set should include methods to initialize the device and
methods executed when memory mapped device registers are read from and
written to.
Other methods may be needed to perform device implementation specific
functions.
See example set of methods for simple raster graphics device:
unsigned short GetGraphDispAddrBase();
void ActivateGraphDisp();
void DeactivateGraphDisp();
int GraphDispDevice_Read(int addr);
void GraphDispDevice_Write(int addr, int val);
void GraphDisp_ReadEvents();
void GraphDisp_Update();
Important concept at the core of the memory mapped device is the method
handling the action to be performed when certain memory register is being
read from or written to. These are defined as function pointers in Device
class: read_fun_ptr and write_fun_ptr.
struct Device {
int num; // device number
string name; // device name
MemAddrRanges addr_ranges; // vector of memory address ranges for
// this device
ReadFunPtr read_fun_ptr; // pointer to memory register read
// function
WriteFunPtr write_fun_ptr; // pointer to memory register write
// function
DevParams params; // list of device parameters
[...]
Programmer implements these handler methods inside MemMapDev class.
Programmer should not forget to add the new device to the pool in the
MemMapDev::Initialize() method which adds all implemented devices to the
devices list with their corresponding default memory bases/ranges and
parameters. These devices can be later re-defined with
MemMapDev::SetupDevice() method.
typedef vector<Device> MemMappedDevices;
[...]
MemMappedDevices mDevices;
[...]
void MemMapDev::Initialize()
{
[...]
Device dev_grdisp(DEVNUM_GRDISP,
"Graphics Display",
addr_ranges_grdisp,
&MemMapDev::GraphDispDevice_Read,
&MemMapDev::GraphDispDevice_Write,
dev_params_grdisp);
mDevices.push_back(dev_grdisp);
[...]
* Add necessary ports in Memory.h and Memory.cpp.
Memory class already implements the code handling memory mapped devices
in all read/write memory methods. Some local methods and flags specific
to the devices may be needed for implementation convenience.
Important relevant methods in Memory class:
int AddDevice(int devnum);
int DeleteDevice(int devnum);
void SetupDevice(int devnum, MemAddrRanges memranges, DevParams params);
are the API to be called from higher level abstraction layer. Some proxy
methods can be implemented in Memory class for this purpose.
See Memory::SetGraphDisp() and VMachine::SetGraphDisp() methods for
an example. The SetGraphDisp() method in Memory class defines the memory
mapped device on a lower level and calls SetupDevice().

View File

@ -1,5 +1,6 @@
Project: MKBasic (VM6502).
Project: MKBasic (a.k.a. VM6502, a.k.a. VM65, I just can't decide
how to name it :-)).
Author: Copyright (C) Marek Karcz 2016. All rights reserved.
Free for personal and non-commercial use.
@ -15,21 +16,25 @@ MOS 6502 emulator, Virtual CPU/Machine and potentially retro-style 8-bit
computer emulator.
MOS-6502-compatible virtual computer featuring BASIC interpreter, machine code
monitor, input/output device emulation etc.
Program works in DOS/shell console (text mode) only.
Main UI of the program works in DOS/shell console.
Graphics display emulation requires SDL2.
Makefile are included to build under Windows 32/64 (mingw compiler required)
and under Linux Ubuntu or Ubuntu based.
and under Linux Ubuntu or Ubuntu based distro.
SDL2 library must be on your execution path in order to run program.
To build under Windows 32/64:
* Install MINGW64 under C:\mingw-w64\x86_64-5.3.0 folder.
* Run mingw terminal.
* Change current directory to that of this project.
* Set environment variable SDLDIR.
* Run: makeming.bat
To build under Linux:
* Make sure C++11 compliant version of GCC compiler is installed.
* Change current directory to that of this project.
* Set environment variable SDLDIR.
* Run: make clean all
Program passed following tests:
@ -63,12 +68,44 @@ Binary image is always loaded from address $0000 and can be up to 64 kB long,
so the code must be properly located inside that image. Image can be shorter
than 64 kB, user will receive warning in such case, but it will be loaded.
Binary image may have header attached at the top.
It consists of magic keyword 'SNAPSHOT' followed by 15 bytes of data
(subject to change in future versions).
Older version of header consists of magic keyword 'SNAPSHOT' followed by 15
bytes of data - this format had no space for expansion and will be removed
in future version. All new snapshots are saved in newest format.
Current version of header consists of magic keyword 'SNAPSHOT2' followed by
128 bytes of data. Not all of the 128 bytes are used, so there is a space
for expansion without the need of changing the file format.
The header data saves the status of CPU and emulation facilities like
character I/O address and enable flag, ROM boundaries and enable flag etc.
This header is added when user saves snapshot of the VM from debug console
menu with command: Y [file_name].
Below is the full detailed description of the header 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"
*
* Data:
*
* 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
* [remaining unused bytes are filled with 0-s]
Header is not mandatory, so the binary image created outside application can
also be used. User will receive warning at startup during image load if
header is missing, but image will be loaded. In this case, user may need
@ -131,24 +168,32 @@ ENROM
ENIO
EXEC
address
ENGRAPH
GRAPHADDR
address
RESET
Where:
ADDR - label indicating that starting and run address will follow in
the next line
the next line
ORG - label indicating that the address counter will change to the
value provided in next line
value provided in next line
IOADDR - label indicating that character I/O emulation trap address will
follow in the next line
follow in the next line
ROMBEGIN - label indicating that the emulated read-only memory start
address will follow in the next line
address will follow in the next line
ROMEND - label indicating that the emulated read-only memory end address
will follow in the next line
will follow in the next line
ENROM - enable read-only memory emulation
ENIO - enable character I/O emulation
EXEC - label indicating that the auto-execute address will follow
in the next line, 6502 program will auto-execute from that
address after memory definition file is done loading
in the next line, 6502 program will auto-execute from that
address after memory definition file is done loading
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
@ -327,9 +372,9 @@ Emulator is "cycle accurate" but not time or speed accurate.
This means that each call to MKCpu::ExecOpcode() method is considered a single
CPU cycle, so depending on the executed opcode, multiple calls (# varies per
opcode and other conditions) are needed to complete the opcode execution and
proceed to the next one. Method returns pointer to the the virtual CPU
registers. One of the members of this structure is named CyclesLeft. When this
variable reaches 0, the opcode execution is considered complete.
proceed to the next one. Method returns pointer to the virtual CPU registers.
One of the members of this structure is named CyclesLeft. When this variable
reaches 0, the opcode execution is considered complete.
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
@ -380,6 +425,14 @@ I - toggle char I/O emulation
to the specified memory address also writes a character code to
to a virtual console. All reads from specified memory address
are interpreted as console character input.
V - toggle graphics display (video) emulation
Usage: V [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF],
Toggles basic raster (pixel) based RGB graphics display 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.
R - show registers
Displays CPU registers, flags and stack.
Y - snapshot
@ -415,11 +468,13 @@ 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:
image_type - B (binary), H (Intel HEX) OR D (definition),
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
image file, Intel HEX format file or the ASCII definition file.
With option 'A' selected, automatic input format detection will be
attempted.
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:
@ -500,35 +555,35 @@ NOTE:
7. Command line usage.
D:\src\wrk\mkbasic>mkbasic -h
C:\src\devcppprj\mkbasic>mkbasic -h
Virtual Machine/CPU Emulator (MOS 6502) and Debugger.
Copyright (C) by Marek Karcz 2016. All rights reserved.
Usage:
mkbasic [-h] | [ramdeffile] | [-b ramimage] | [-r ramimage]
OR
mkbasic [-x intelheximage]
mkbasic [-h] | [ramdeffile] [-b | -x] [-r]
Where:
ramdeffile - RAM definition file name
intelheximage - Intel HEX format file
ramimage - RAM binary image file name
-b - specify input format as binary
-x - specify input format as Intel HEX
-r - after loading, perform CPU RESET
-h - print this help screen
When ran with no arguments, program will load default memory
definition files: default.rom, default.ram and will enter the debug
console menu.
When ramdeffile argument is provided, program will load the memory
definition from the file, set the flags and parameters depending on the
contents of the memory definition file and enter the corresponding mode
of operation as defined in that file.
If used with flag -b or -x, program will load memory from the provided image
file and enter the debug console menu.
If used with flag -r, program will load memory from the provided image
file and execute CPU reset sequence.
When ramdeffile argument is provided with no input format specified,
program will attempt to automatically detect input format and load the
memory definition from the file, set the flags and parameters depending
on the contents of the memory definition file and enter the corresponding
mode of operation as defined in that file.
If input format is specified (-b|-x), program will load memory from the
provided image file and enter the debug console menu.
8. Utilities.
@ -564,7 +619,90 @@ Where:
addr = 0, exec is not set and data blocks with 0-s only
are always suppressed.
9. Warranty and License Agreement.
9. Memory Mapped Device abstraction layer.
In microprocessor based systems in majority of cases communication with
peripheral devices is done via registers which in turn are located under
specific memory addresses.
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.
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:
character I/O and raster (pixel based) graphics display. Both can be activated
or inactivated at will and provide simple register based interface that
requires no extra memory space use for data.
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).
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
are following registers:
Offset Register Description
------------------------------------------------------------------------------
0 GRAPHDEVREG_X_LO Least significant part of pixel's X (column)
coordinate or begin of line coord. (0-255)
1 GRAPHDEVREG_X_HI Most significant part of pixel's X (column)
coordinate or begin of line coord. (0-1)
2 GRAPHDEVREG_Y Pixel's Y (row) coordinate (0-199)
3 GRAPHDEVREG_PXCOL_R Pixel's RGB color component - Red (0-255)
4 GRAPHDEVREG_PXCOL_G Pixel's RGB color component - Green (0-255)
5 GRAPHDEVREG_PXCOL_B Pixel's RGB color component - Blue (0-255)
6 GRAPHDEVREG_BGCOL_R Background RGB color component - Red (0-255)
7 GRAPHDEVREG_BGCOL_G Background RGB color component - Green (0-255)
8 GRAPHDEVREG_BGCOL_B Background RGB color component - Blue (0-255)
9 GRAPHDEVREG_CMD Command code
10 GRAPHDEVREG_X2_LO Least significant part of end of line's X
coordinate
11 GRAPHDEVREG_X2_HI Most significant part of end of line's X
coordinate
12 GRAPHDEVREG_Y2 End of line's Y (row) coordinate (0-199)
Writing values to above memory locations when Graphics Device is enabled
allows to set the corresponding parameters of the device, while writing to
command register executes corresponding command (performs action) per codes
listed below:
Command code Command description
------------------------------------------------------------------------------
GRAPHDEVCMD_CLRSCR = 0 Clear screen
GRAPHDEVCMD_SETPXL = 1 Set the pixel location to pixel color
GRAPHDEVCMD_CLRPXL = 2 Clear the pixel location (set to bg color)
GRAPHDEVCMD_SETBGC = 3 Set the background color
GRAPHDEVCMD_SETFGC = 4 Set the foreground (pixel) color
GRAPHDEVCMD_DRAWLN = 5 Draw line
GRAPHDEVCMD_ERASLN = 6 Erase line
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
a pixel or set color).
I plan to add graphics frame buffer in the VM's RAM address space in future
release.
Simple demo program written in EhBasic that shows how to drive the graphics
screen is included: grdevdemo.bas.
10. 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
only switch to it by ALT-TAB (windows) or clicking on the corresponding icon
on the task bar. However it doesn't always work. Switching to the DOS console
of emulator while in emulation mode should bring the graphics window back
to front.
11. 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,10 +81,13 @@ void VMachine::InitVM()
mOpInterrupt = false;
mpRAM = new Memory();
mOldStyleHeader = false;
mError = VMERR_OK;
mAutoExec = false;
mAutoReset = false;
mCharIOAddr = CHARIO_ADDR;
mCharIOActive = mCharIO = false;
mGraphDispActive = false;
if (NULL == mpRAM) {
throw MKGenException("Unable to initialize VM (RAM).");
}
@ -324,12 +327,14 @@ Regs *VMachine::Step()
Regs *cpureg = NULL;
cpureg = mpCPU->ExecOpcode(addr);
if (mGraphDispActive) mpRAM->GraphDisp_ReadEvents();
addr = cpureg->PtrAddr;
mRunAddr = addr;
if (cpureg->CyclesLeft == 0 && mCharIOActive && !mOpInterrupt) {
char c = -1;
mCharIO = false;
while ((c = mpRAM->GetCharOut()) != -1) {
mOpInterrupt = mOpInterrupt || (c == OPINTERRUPT);
if (!mOpInterrupt) {
@ -374,13 +379,97 @@ void VMachine::LoadROM(string romfname)
* Method: LoadRAM()
* Purpose: Load data from memory definition file to the memory.
* Arguments: ramfname - name of the RAM file definition
* Returns: n/a
* Returns: int - error code
*--------------------------------------------------------------------
*/
void VMachine::LoadRAM(string ramfname)
int VMachine::LoadRAM(string ramfname)
{
LoadMEM(ramfname, mpRAM);
//mpRAM->EnableROM();
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;
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;
}
/*
@ -400,6 +489,7 @@ bool VMachine::HasHdrData(FILE *fp)
memset(buf, 0, 20);
rewind(fp);
while (0 == feof(fp) && 0 == ferror(fp)) {
unsigned char val = fgetc(fp);
buf[n] = val;
@ -411,6 +501,36 @@ bool VMachine::HasHdrData(FILE *fp)
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));
return ret;
}
/*
*--------------------------------------------------------------------
* Method: LoadHdrData()
@ -420,16 +540,25 @@ bool VMachine::HasHdrData(FILE *fp)
*
* Details:
* Header of the binary memory image consists of magic keyword
* string followed by the data (unsigned char values).
* string followed by the 128 bytes of data (unsigned char values).
* It has following format:
*
* MAGIC_KEYWORD
* aabbccddefghijk
* aabbccddefghijklmm[remaining unused bytes]
*
* Where:
* MAGIC_KEYWORD - text string indicating header, may vary between
* versions thus rendering headers from previous
* versions incompatible - currently: "SNAPSHOT"
* 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
@ -441,6 +570,9 @@ bool VMachine::HasHdrData(FILE *fp)
* 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
@ -450,12 +582,13 @@ bool VMachine::HasHdrData(FILE *fp)
*/
bool VMachine::LoadHdrData(FILE *fp)
{
int n = 0, l = 0;
int n = 0, l = 0, hdrdtlen = HDRDATALEN;
unsigned short rb = 0, re = 0;
Regs r;
bool ret = false;
while (0 == feof(fp) && 0 == ferror(fp) && n < HDRDATALEN) {
if (mOldStyleHeader) hdrdtlen = HDRDATALEN_OLD;
while (0 == feof(fp) && 0 == ferror(fp) && n < hdrdtlen) {
unsigned char val = fgetc(fp);
switch (n)
{
@ -486,6 +619,11 @@ bool VMachine::LoadHdrData(FILE *fp)
case 14: r.PtrStack = val;
ret = true;
break;
case 15: mGraphDispActive = (val != 0);
break;
case 17: if (mGraphDispActive) SetGraphDisp(l + 256 * val);
else DisableGraphDisp();
break;
default: break;
}
l = val;
@ -499,6 +637,9 @@ bool VMachine::LoadHdrData(FILE *fp)
return ret;
}
// Macro to save header data: v - value, fp - file pointer, n - data counter (dec)
#define SAVE_HDR_DATA(v,fp,n) {fputc(v, fp); n--;}
/*
*--------------------------------------------------------------------
* Method: SaveHdrData()
@ -510,6 +651,8 @@ bool VMachine::LoadHdrData(FILE *fp)
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);
@ -518,32 +661,40 @@ void VMachine::SaveHdrData(FILE *fp)
unsigned char lo = 0, hi = 0;
lo = (unsigned char) (reg->PtrAddr & 0x00FF);
hi = (unsigned char) ((reg->PtrAddr & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
SAVE_HDR_DATA(lo,fp,n);
SAVE_HDR_DATA(hi,fp,n);
lo = (unsigned char) (mCharIOAddr & 0x00FF);
hi = (unsigned char) ((mCharIOAddr & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
SAVE_HDR_DATA(lo,fp,n);
SAVE_HDR_DATA(hi,fp,n);
lo = (unsigned char) (GetROMBegin() & 0x00FF);
hi = (unsigned char) ((GetROMBegin() & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
SAVE_HDR_DATA(lo,fp,n);
SAVE_HDR_DATA(hi,fp,n);
lo = (unsigned char) (GetROMEnd() & 0x00FF);
hi = (unsigned char) ((GetROMEnd() & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
SAVE_HDR_DATA(lo,fp,n);
SAVE_HDR_DATA(hi,fp,n);
lo = (mCharIOActive ? 1 : 0);
fputc(lo, fp);
SAVE_HDR_DATA(lo,fp,n);
lo = (IsROMEnabled() ? 1 : 0);
fputc(lo, fp);
SAVE_HDR_DATA(lo,fp,n);
Regs *pregs = mpCPU->GetRegs();
if (pregs != NULL) {
fputc(pregs->Acc, fp);
fputc(pregs->IndX, fp);
fputc(pregs->IndY, fp);
fputc(pregs->Flags, fp);
fputc(pregs->PtrStack, fp);
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);
}
/*
@ -564,7 +715,7 @@ int VMachine::SaveSnapshot(string fname)
SaveHdrData(fp);
for (int addr = 0; addr < MAX_8BIT_ADDR+1; addr++) {
if (addr != mCharIOAddr && addr != mCharIOAddr+1) {
unsigned char b = mpRAM->Peek8bit((unsigned short)addr);
unsigned char b = mpRAM->Peek8bitImg((unsigned short)addr);
if (EOF != fputc(b, fp)) ret--;
else break;
} else {
@ -574,6 +725,7 @@ int VMachine::SaveSnapshot(string fname)
}
fclose(fp);
}
if (0 != ret) mError = VMERR_SAVE_SNAPSHOT;
return ret;
}
@ -584,15 +736,21 @@ int VMachine::SaveSnapshot(string fname)
* Purpose: Load data from binary image file to the memory.
* Arguments: ramfname - name of the RAM file definition
* Returns: int - error code
* 0 - OK
* 1 - WARNING: Unexpected EOF (image shorter than 64kB).
* 2 - WARNING: Unable to open memory image file.
* 3 - WARNING: Problem with binary image header.
* 4 - WARNING: No header found in binary image.
* 5 - WARNING: Problem with binary image header and
* Unexpected EOF (image shorter than 64kB).
* 6 - WARNING: No header found in binary image and
* Unexpected EOF (image shorter than 64kB).
* 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
@ -607,13 +765,14 @@ int VMachine::LoadRAMBin(string ramfname)
unsigned short addr = 0x0000;
int n = 0;
Memory *pm = mpRAM;
int ret = 2;
int ret = MEMIMGERR_RAMBIN_OPEN;
mOldStyleHeader = false;
if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) {
if (HasHdrData(fp)) {
ret = (LoadHdrData(fp) ? 0 : 3);
if (HasHdrData(fp) || (mOldStyleHeader = HasOldHdrData(fp))) {
ret = (LoadHdrData(fp) ? MEMIMGERR_OK : MEMIMGERR_RAMBIN_HDR);
} else {
ret = 4;
ret = MEMIMGERR_RAMBIN_NOHDR;
rewind(fp);
}
// temporarily disable emulation facilities to allow
@ -623,7 +782,7 @@ int VMachine::LoadRAMBin(string ramfname)
DisableROM();
while (0 == feof(fp) && 0 == ferror(fp)) {
unsigned char val = fgetc(fp);
pm->Poke8bit(addr, val);
pm->Poke8bitImg(addr, val);
addr++; n++;
}
fclose(fp);
@ -632,13 +791,24 @@ int VMachine::LoadRAMBin(string ramfname)
if (tmp2) EnableROM();
if (n <= 0xFFFF) {
switch (ret) {
case 0: ret = 1; break;
case 3: ret = 5; break;
case 4: ret = 6; break;
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;
}
@ -648,10 +818,10 @@ int VMachine::LoadRAMBin(string ramfname)
* Method: LoadRAMHex()
* Purpose: Load data from Intel HEX file format to memory.
* Arguments: hexfname - name of the HEX file
* Returns: int, 0 if OK, >0 - error code:
* 1 - unable to open file
* 2 - syntax error
* 3 - hex format error
* 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)
@ -694,7 +864,7 @@ int VMachine::LoadRAMHex(string hexfname)
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 = 3; // hex format error
ret = MEMIMGERR_INTELH_FMT; // hex format error
break;
}
char dbuf[3] = {0,0,0};
@ -704,30 +874,46 @@ int VMachine::LoadRAMHex(string hexfname)
dbuf[1] = line[i+1];
dbuf[2] = 0;
sscanf(dbuf, "%02x", &byteval);
pm->Poke8bit(addr, (unsigned char)byteval&0x00FF);
pm->Poke8bitImg(addr, (unsigned char)byteval&0x00FF);
}
} else {
ret = 2; // syntax error
ret = MEMIMGERR_INTELH_SYNTAX; // syntax error
break;
}
}
fclose(fp);
} else {
ret = 1; // unable to open file
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 memory definition file to the memory.
* 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: n/a
* Returns: int - error code
* Details:
* Format of the memory definition file:
* [; comment]
@ -747,6 +933,10 @@ int VMachine::LoadRAMHex(string hexfname)
* [ENROM]
* [EXEC
* addrress]
* [ENGRAPH]
* [GRAPHADDR
* address]
* [RESET]
*
* Where:
* [] - optional token
@ -764,6 +954,13 @@ int VMachine::LoadRAMHex(string hexfname)
* 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
@ -803,21 +1000,20 @@ int VMachine::LoadRAMHex(string hexfname)
* 0 0 0 0
*--------------------------------------------------------------------
*/
void VMachine::LoadMEM(string memfname, Memory *pmem)
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;
unsigned int nAddr, graphaddr = GRDISP_ADDR;
bool enrom = false, enio = false, runset = false;
bool ioset = false, execset = false, rombegset = false;
bool romendset = false;
bool romendset = false, engraph = false, graphset = false;
Memory *pm = pmem;
int err = MEMIMGERR_OK;
if ((fp = fopen(memfname.c_str(), "r")) != NULL) {
// to ensure proper memory initialization, disable emulation
// of char I/O and ROM
DisableROM();
DisableCharIO();
while (0 == feof(fp) && 0 == ferror(fp))
@ -840,6 +1036,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
mRunAddr = addr;
runset = true;
} else {
err = MEMIMGERR_VM65_IGNPROCWRN;
errc++;
cout << "LINE #" << dec << lc << " WARNING: Run address was already set. Ignoring..." << endl;
}
@ -872,16 +1069,42 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
}
ioset = true;
} else {
err = MEMIMGERR_VM65_IGNPROCWRN;
errc++;
cout << "LINE #" << dec << lc << " WARNING: I/O address was already set. Ignoring..." << endl;
}
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++;
cout << "LINE #" << dec << lc << " WARNING: graphics device base address was already set. Ignoring..." << endl;
}
continue;
}
// enable character I/O emulation
if (0 == strncmp(line, "ENIO", 4)) {
enio = true;
continue;
}
// enable generic graphics display emulation
if (0 == strncmp(line, "ENGRAPH", 7)) {
engraph = true;
continue;
}
// enable ROM emulation
if (0 == strncmp(line, "ENROM", 5)) {
enrom = true;
@ -902,6 +1125,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
}
execset = true;
} else {
err = MEMIMGERR_VM65_IGNPROCWRN;
errc++;
cout << "LINE #" << dec << lc << " WARNING: auto-exec address was already set. Ignoring..." << endl;
}
@ -926,6 +1150,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
}
rombegset = true;
} else {
err = MEMIMGERR_VM65_IGNPROCWRN;
errc++;
cout << "LINE #" << dec << lc << " WARNING: ROM-begin address was already set. Ignoring..." << endl;
}
@ -945,6 +1170,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
}
romendset = true;
} else {
err = MEMIMGERR_VM65_IGNPROCWRN;
errc++;
cout << "LINE #" << dec << lc << " WARNING: ROM-end address was already set. Ignoring..." << endl;
}
@ -956,9 +1182,9 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
unsigned int nVal;
if (*s == '$') {
sscanf(s+1, "%02x", &nVal);
pm->Poke8bit(addr++, (unsigned short)nVal);
pm->Poke8bitImg(addr++, (unsigned short)nVal);
} else {
pm->Poke8bit(addr++, (unsigned short)atoi(s));
pm->Poke8bitImg(addr++, (unsigned short)atoi(s));
}
s = strtok(NULL, " ,");
}
@ -975,8 +1201,12 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
if (enio) {
SetCharIO(mCharIOAddr, false);
}
if (engraph || graphset) {
SetGraphDisp(graphaddr);
}
}
else {
err = MEMIMGERR_VM65_OPEN;
cout << "WARNING: Unable to open memory definition file: " << memfname << endl;
errc++;
}
@ -985,6 +1215,10 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
cout << "Press [ENTER] to continue...";
getchar();
}
mError = err;
return err;
}
/*
@ -1088,6 +1322,60 @@ bool VMachine::GetCharIOActive()
return mCharIOActive;
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void VMachine::SetGraphDisp(unsigned short addr)
{
mGraphDispActive = true;
mpRAM->SetGraphDisp(addr);
}
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
void VMachine::DisableGraphDisp()
{
mGraphDispActive = false;
mpRAM->DisableGraphDisp();
}
/*
*--------------------------------------------------------------------
* 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()
@ -1315,4 +1603,20 @@ 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;
}
} // namespace MKBasic

View File

@ -15,14 +15,54 @@
#define IOREFRESH 32
#define OPINTERRUPT 25 // operator interrupt code (CTRL-Y)
#define HDRMAGICKEY "SNAPSHOT"
#define HDRDATALEN 15
#define HDRMAGICKEY "SNAPSHOT2"
#define HDRMAGICKEY_OLD "SNAPSHOT"
#define HDRDATALEN 128
#define HDRDATALEN_OLD 15
#define HEXEOF ":00000001FF"
using namespace std;
namespace MKBasic {
// Types of memory image definition file.
enum eMemoryImageTypes {
MEMIMG_UNKNOWN = 0,
MEMIMG_VM65DEF,
MEMIMG_INTELHEX,
MEMIMG_BIN
};
// Types of memory image load errors
enum eMemImgLoadErrors {
MEMIMGERR_OK = 0, // all is good
// binary format
MEMIMGERR_RAMBIN_OPEN, // unable to open file
MEMIMGERR_RAMBIN_EOF, // unexpected EOF (image shorter then 64 kB)
MEMIMGERR_RAMBIN_HDR, // header problem
MEMIMGERR_RAMBIN_NOHDR, // no header found
MEMIMGERR_RAMBIN_HDRANDEOF, // header problem and unexpected EOF
MEMIMGERR_RAMBIN_NOHDRANDEOF, // header not found and unexoected EOF
// Intel HEX format
MEMIMGERR_INTELH_OPEN, // unable to open file
MEMIMGERR_INTELH_SYNTAX, // syntax error
MEMIMGERR_INTELH_FMT, // format error
// VM65 memory definition
MEMIMGERR_VM65_OPEN, // unable to open file
MEMIMGERR_VM65_IGNPROCWRN, // processing warnings (ignored, not critical)
//-------------------------------------------------------------------------
MEMIMGERR_UNKNOWN
};
// Types of other errors
enum eVMErrors {
VMERR_OK = 0, // all is good
VMERR_SAVE_SNAPSHOT = MEMIMGERR_UNKNOWN+1, // problem saving memory image
// snapshot
//-------------------------------------------------------------------------
VMERR_UNKNOWN // unknown error
};
class VMachine
{
public:
@ -38,9 +78,10 @@ class VMachine
Regs *Step();
Regs *Step(unsigned short addr);
void LoadROM(string romfname);
void LoadRAM(string ramfname);
int LoadRAM(string ramfname);
int LoadRAMBin(string ramfname);
int LoadRAMHex(string hexfname);
int LoadRAMDef(string memfname);
unsigned short MemPeek8bit(unsigned short addr);
void MemPoke8bit(unsigned short addr, unsigned char v);
Regs *GetRegs();
@ -48,6 +89,7 @@ class VMachine
void DisableCharIO();
unsigned short GetCharIOAddr();
bool GetCharIOActive();
bool GetGraphDispActive();
void ShowIO();
void ClearScreen();
void ScrHome();
@ -68,6 +110,10 @@ class VMachine
void Reset();
void Interrupt();
int SaveSnapshot(string fname);
int GetLastError();
void SetGraphDisp(unsigned short addr);
void DisableGraphDisp();
unsigned short GetGraphDispAddr();
protected:
@ -84,12 +130,17 @@ class VMachine
bool mOpInterrupt; // operator interrupt from console
bool mAutoExec;
bool mAutoReset;
int mError; // last error code
bool mGraphDispActive;
bool mOldStyleHeader;
void LoadMEM(string memfname, Memory *pmem);
int LoadMEM(string memfname, Memory *pmem);
void ShowDisp();
bool HasHdrData(FILE *fp);
bool HasOldHdrData(FILE *fp);
bool LoadHdrData(FILE *fp);
void SaveHdrData(FILE *fp);
eMemoryImageTypes GetMemoryImageType(string ramfname);
};
} // namespace MKBasic

630
dummy.ram
View File

@ -1,315 +1,317 @@
;
; test program #1
; address: $0200
; load Acc with value 12
; write Acc to address $c000 (49152)
;
; nop
; nop
; lda #$0c
; sta $c000
; brk
;
$EA $EA $A9 $0c $8D $00 $c0 $00 $00
;
; test program #2
; address: $0400
; copy 0-terminated string from
; address $d000 to $0200
; "Hello World!"
;
; ORG=$0400
; hello:
; ldx #0
; loop:
; lda $d000,x
; beq $06 ;branch to end (+6) if A=0
; sta $0200,x
; inx
; bne $f5 ; branch to loop (-11) if X<>0
; end:
; brk
ORG
$0400
$A2 $00
$BD $00 $d0
$F0 $06
$9D $00 $02
$E8
$D0 $F5
$00 $00
; data
; address: $d000
ORG
$D000
;DEC: 53248
; "Hello World!"
72 101 108 108 111 32 87 111 114 108 100 33 0
;
; test program #3 - copy Hello World! string to $0300
; using different assembly instructions
; address: $0500
;
; ORG=$0500 ;dec: 1280
; hello:
; lda #0
; sta $05
; ldx $05
; loop:
; lda $d000,x
; sta $0300,x
; beq end ;(+6)
; inx
; beq end ;(+3)
; jmp loop
; end:
; brk
ORG
$0500
;DEC: 1280
$A9 $00
$85 $05
$A6 $05
$BD $00 $d0
$9D $00 $03
$F0 $06
$E8
$F0 $03
$4C $06 $05
$00 $00
;
; test program #4
; left-shift memory location $05 at zero page,
; then location $06 using zero page indexed addressing,
; then memory location $c001 (outside zero page) using absolute addressing
; then location $c002 using indexed absolute addressing
; and finally left-shift Acc.
; stop after each step for debugging
; exit loop when Acc=0
;
; start:
; lda #$ff
; ldx #$01
; sta $05
; sta $05,x
; sta $c000,x
; inx
; sta $c000,x
; ldx #$01
; loop2:
; brk
; asl $05
; asl $05,x
; asl $c001
; asl $c001,x
; asl
; bne loop2 ;(-15 or $f1)
; brk
ORG
$0600
$A9 $FF
$A2 $01
$85 $05
$95 $05
$9D $00 $C0
$E8
$9D $00 $C0
$A2 $01
$00 $00
$06 $05
$16 $05
$0E $01 $C0
$1E $01 $C0
$0A
$D0 $F1
$00 $00
;
; test program #5
; Test ORA opcode with various arguments and addressing modes.
; At each break, the contents of Acc should equal $AA.
;
; start:
; lda #$aa ;%10101010
; sta $05
; sta $aa
; lda #$00
; tax
; ora ($05,x)
; brk
; lda #$00
; ora $05
; brk
; lda #$00
; ora #$aa
; brk
; lda #$00
; ora $0005
; brk
; lda #$05
; sta $06
; lda #$00
; sta $07
; tay
; ora ($06),y
; brk
; lda #$00
; tax
; ora $05,x
; brk
; lda #$00
; tay
; ora $0005,y
; brk
; lda #$00
; tax
; ora $0005,x
; brk
ORG
$0700
$A9 $AA
$85 $05
$85 $AA
$A9 $00
$AA
$01 $05
$00 $00
$A9 $00
$05 $05
$00 $00
$A9 $00
$09 $AA
$00 $00
$A9 $00
$0D $05 $00
$00 $00
$A9 $05
$85 $06
$A9 $00
$85 $07
$A8
$11 $06
$00 $00
$A9 $00
$AA
$15 $05
$00 $00
$A9 $00
$A8
$19 $05 $00
$00 $00
$A9 $00
$AA
$1D $05 $00
$00 $00
;
; test program #6
; Test JSR opcode.
; After each break examine memory at $c000 and $c001.
; After 1-st break, $c000 should equal $dd.
; Return address-1 ($0802) should be on stack.
; After 2-nd break, PC counter should be at $0805.
; After 3-rd break, $c000 should equal $ee.
; Return address-1 ($0807) should be on stack.
; After 4-th break, PC counter should be at $080a.
;
; start:
; jsr sub1
; brk
; jsr sub2
; brk
; brk
; brk
; sub1:
; lda #$dd
; sta $c000
; brk
; rts
; sub2:
; lda #$ee
; sta $c000
; brk
; rts
;
ORG
$0800
$20 $0B $08
$00 $00
$20 $13 $08
$00
$00
$00
$A9 $DD
$8D $00 $C0
$00 $00
$60
$A9 $EE
$8D $00 $C0
$00 $00
$60
;
; test program #7
; Test ADC opcode.
; Expected results:
; First break: Acc=$01, Carry=1
; 2-nd break: Acc=$02, Carry=1
; 3-rd break: Acc=$22, Carry=0
; 4-th break: Acc=$23, Carry=0
;
; start:
; clc
; lda #$ff
; adc #$02
; brk
; sec
; lda #$ff
; adc #$02
; brk
; clc
; lda #$20
; adc #$02
; brk
; sec
; lda #$20
; adc #$02
; brk
;
ORG
$0900
$18
$A9 $FF
$69 $02
$00 $00
$38
$A9 $FF
$69 $02
$00 $00
$18
$A9 $20
$69 $02
$00 $00
$38
$A9 $20
$69 $02
$00 $00
;
; test program #8
; Test ROR opcode.
;
; start:
; sec
; lda #$00
; loop:
; ror
; brk
; bcc loop ;(-5 -> $FB)
; brk
;
ORG
$0920
$38
$A9 $00
$6A
$00 $00
$90 $FB
$00 $00
ORG
$0200
;
; test program #1
; address: $0200
; load Acc with value 12
; write Acc to address $c000 (49152)
;
; nop
; nop
; lda #$0c
; sta $c000
; brk
;
$EA $EA $A9 $0c $8D $00 $c0 $00 $00
;
; test program #2
; address: $0400
; copy 0-terminated string from
; address $d000 to $0200
; "Hello World!"
;
; ORG=$0400
; hello:
; ldx #0
; loop:
; lda $d000,x
; beq $06 ;branch to end (+6) if A=0
; sta $0200,x
; inx
; bne $f5 ; branch to loop (-11) if X<>0
; end:
; brk
ORG
$0400
$A2 $00
$BD $00 $d0
$F0 $06
$9D $00 $02
$E8
$D0 $F5
$00 $00
; data
; address: $d000
ORG
$D000
;DEC: 53248
; "Hello World!"
72 101 108 108 111 32 87 111 114 108 100 33 0
;
; test program #3 - copy Hello World! string to $0300
; using different assembly instructions
; address: $0500
;
; ORG=$0500 ;dec: 1280
; hello:
; lda #0
; sta $05
; ldx $05
; loop:
; lda $d000,x
; sta $0300,x
; beq end ;(+6)
; inx
; beq end ;(+3)
; jmp loop
; end:
; brk
ORG
$0500
;DEC: 1280
$A9 $00
$85 $05
$A6 $05
$BD $00 $d0
$9D $00 $03
$F0 $06
$E8
$F0 $03
$4C $06 $05
$00 $00
;
; test program #4
; left-shift memory location $05 at zero page,
; then location $06 using zero page indexed addressing,
; then memory location $c001 (outside zero page) using absolute addressing
; then location $c002 using indexed absolute addressing
; and finally left-shift Acc.
; stop after each step for debugging
; exit loop when Acc=0
;
; start:
; lda #$ff
; ldx #$01
; sta $05
; sta $05,x
; sta $c000,x
; inx
; sta $c000,x
; ldx #$01
; loop2:
; brk
; asl $05
; asl $05,x
; asl $c001
; asl $c001,x
; asl
; bne loop2 ;(-15 or $f1)
; brk
ORG
$0600
$A9 $FF
$A2 $01
$85 $05
$95 $05
$9D $00 $C0
$E8
$9D $00 $C0
$A2 $01
$00 $00
$06 $05
$16 $05
$0E $01 $C0
$1E $01 $C0
$0A
$D0 $F1
$00 $00
;
; test program #5
; Test ORA opcode with various arguments and addressing modes.
; At each break, the contents of Acc should equal $AA.
;
; start:
; lda #$aa ;%10101010
; sta $05
; sta $aa
; lda #$00
; tax
; ora ($05,x)
; brk
; lda #$00
; ora $05
; brk
; lda #$00
; ora #$aa
; brk
; lda #$00
; ora $0005
; brk
; lda #$05
; sta $06
; lda #$00
; sta $07
; tay
; ora ($06),y
; brk
; lda #$00
; tax
; ora $05,x
; brk
; lda #$00
; tay
; ora $0005,y
; brk
; lda #$00
; tax
; ora $0005,x
; brk
ORG
$0700
$A9 $AA
$85 $05
$85 $AA
$A9 $00
$AA
$01 $05
$00 $00
$A9 $00
$05 $05
$00 $00
$A9 $00
$09 $AA
$00 $00
$A9 $00
$0D $05 $00
$00 $00
$A9 $05
$85 $06
$A9 $00
$85 $07
$A8
$11 $06
$00 $00
$A9 $00
$AA
$15 $05
$00 $00
$A9 $00
$A8
$19 $05 $00
$00 $00
$A9 $00
$AA
$1D $05 $00
$00 $00
;
; test program #6
; Test JSR opcode.
; After each break examine memory at $c000 and $c001.
; After 1-st break, $c000 should equal $dd.
; Return address-1 ($0802) should be on stack.
; After 2-nd break, PC counter should be at $0805.
; After 3-rd break, $c000 should equal $ee.
; Return address-1 ($0807) should be on stack.
; After 4-th break, PC counter should be at $080a.
;
; start:
; jsr sub1
; brk
; jsr sub2
; brk
; brk
; brk
; sub1:
; lda #$dd
; sta $c000
; brk
; rts
; sub2:
; lda #$ee
; sta $c000
; brk
; rts
;
ORG
$0800
$20 $0B $08
$00 $00
$20 $13 $08
$00
$00
$00
$A9 $DD
$8D $00 $C0
$00 $00
$60
$A9 $EE
$8D $00 $C0
$00 $00
$60
;
; test program #7
; Test ADC opcode.
; Expected results:
; First break: Acc=$01, Carry=1
; 2-nd break: Acc=$02, Carry=1
; 3-rd break: Acc=$22, Carry=0
; 4-th break: Acc=$23, Carry=0
;
; start:
; clc
; lda #$ff
; adc #$02
; brk
; sec
; lda #$ff
; adc #$02
; brk
; clc
; lda #$20
; adc #$02
; brk
; sec
; lda #$20
; adc #$02
; brk
;
ORG
$0900
$18
$A9 $FF
$69 $02
$00 $00
$38
$A9 $FF
$69 $02
$00 $00
$18
$A9 $20
$69 $02
$00 $00
$38
$A9 $20
$69 $02
$00 $00
;
; test program #8
; Test ROR opcode.
;
; start:
; sec
; lda #$00
; loop:
; ror
; brk
; bcc loop ;(-5 -> $FB)
; brk
;
ORG
$0920
$38
$A9 $00
$6A
$00 $00
$90 $FB
$00 $00
;

630
dummy.rom
View File

@ -1,315 +1,317 @@
;
; test program #1
; address: $0200
; load Acc with value 12
; write Acc to address $c000 (49152)
;
; nop
; nop
; lda #$0c
; sta $c000
; brk
;
$EA $EA $A9 $0c $8D $00 $c0 $00 $00
;
; test program #2
; address: $0400
; copy 0-terminated string from
; address $d000 to $0200
; "Hello World!"
;
; ORG=$0400
; hello:
; ldx #0
; loop:
; lda $d000,x
; beq $06 ;branch to end (+6) if A=0
; sta $0200,x
; inx
; bne $f5 ; branch to loop (-11) if X<>0
; end:
; brk
ORG
$0400
$A2 $00
$BD $00 $d0
$F0 $06
$9D $00 $02
$E8
$D0 $F5
$00 $00
; data
; address: $d000
ORG
$D000
;DEC: 53248
; "Hello World!"
72 101 108 108 111 32 87 111 114 108 100 33 0
;
; test program #3 - copy Hello World! string to $0300
; using different assembly instructions
; address: $0500
;
; ORG=$0500 ;dec: 1280
; hello:
; lda #0
; sta $05
; ldx $05
; loop:
; lda $d000,x
; sta $0300,x
; beq end ;(+6)
; inx
; beq end ;(+3)
; jmp loop
; end:
; brk
ORG
$0500
;DEC: 1280
$A9 $00
$85 $05
$A6 $05
$BD $00 $d0
$9D $00 $03
$F0 $06
$E8
$F0 $03
$4C $06 $05
$00 $00
;
; test program #4
; left-shift memory location $05 at zero page,
; then location $06 using zero page indexed addressing,
; then memory location $c001 (outside zero page) using absolute addressing
; then location $c002 using indexed absolute addressing
; and finally left-shift Acc.
; stop after each step for debugging
; exit loop when Acc=0
;
; start:
; lda #$ff
; ldx #$01
; sta $05
; sta $05,x
; sta $c000,x
; inx
; sta $c000,x
; ldx #$01
; loop2:
; brk
; asl $05
; asl $05,x
; asl $c001
; asl $c001,x
; asl
; bne loop2 ;(-15 or $f1)
; brk
ORG
$0600
$A9 $FF
$A2 $01
$85 $05
$95 $05
$9D $00 $C0
$E8
$9D $00 $C0
$A2 $01
$00 $00
$06 $05
$16 $05
$0E $01 $C0
$1E $01 $C0
$0A
$D0 $F1
$00 $00
;
; test program #5
; Test ORA opcode with various arguments and addressing modes.
; At each break, the contents of Acc should equal $AA.
;
; start:
; lda #$aa ;%10101010
; sta $05
; sta $aa
; lda #$00
; tax
; ora ($05,x)
; brk
; lda #$00
; ora $05
; brk
; lda #$00
; ora #$aa
; brk
; lda #$00
; ora $0005
; brk
; lda #$05
; sta $06
; lda #$00
; sta $07
; tay
; ora ($06),y
; brk
; lda #$00
; tax
; ora $05,x
; brk
; lda #$00
; tay
; ora $0005,y
; brk
; lda #$00
; tax
; ora $0005,x
; brk
ORG
$0700
$A9 $AA
$85 $05
$85 $AA
$A9 $00
$AA
$01 $05
$00 $00
$A9 $00
$05 $05
$00 $00
$A9 $00
$09 $AA
$00 $00
$A9 $00
$0D $05 $00
$00 $00
$A9 $05
$85 $06
$A9 $00
$85 $07
$A8
$11 $06
$00 $00
$A9 $00
$AA
$15 $05
$00 $00
$A9 $00
$A8
$19 $05 $00
$00 $00
$A9 $00
$AA
$1D $05 $00
$00 $00
;
; test program #6
; Test JSR opcode.
; After each break examine memory at $c000 and $c001.
; After 1-st break, $c000 should equal $dd.
; Return address-1 ($0802) should be on stack.
; After 2-nd break, PC counter should be at $0805.
; After 3-rd break, $c000 should equal $ee.
; Return address-1 ($0807) should be on stack.
; After 4-th break, PC counter should be at $080a.
;
; start:
; jsr sub1
; brk
; jsr sub2
; brk
; brk
; brk
; sub1:
; lda #$dd
; sta $c000
; brk
; rts
; sub2:
; lda #$ee
; sta $c000
; brk
; rts
;
ORG
$0800
$20 $0B $08
$00 $00
$20 $13 $08
$00
$00
$00
$A9 $DD
$8D $00 $C0
$00 $00
$60
$A9 $EE
$8D $00 $C0
$00 $00
$60
;
; test program #7
; Test ADC opcode.
; Expected results:
; First break: Acc=$01, Carry=1
; 2-nd break: Acc=$02, Carry=1
; 3-rd break: Acc=$22, Carry=0
; 4-th break: Acc=$23, Carry=0
;
; start:
; clc
; lda #$ff
; adc #$02
; brk
; sec
; lda #$ff
; adc #$02
; brk
; clc
; lda #$20
; adc #$02
; brk
; sec
; lda #$20
; adc #$02
; brk
;
ORG
$0900
$18
$A9 $FF
$69 $02
$00 $00
$38
$A9 $FF
$69 $02
$00 $00
$18
$A9 $20
$69 $02
$00 $00
$38
$A9 $20
$69 $02
$00 $00
;
; test program #8
; Test ROR opcode.
;
; start:
; sec
; lda #$00
; loop:
; ror
; brk
; bcc loop ;(-5 -> $FB)
; brk
;
ORG
$0920
$38
$A9 $00
$6A
$00 $00
$90 $FB
$00 $00
ORG
$0200
;
; test program #1
; address: $0200
; load Acc with value 12
; write Acc to address $c000 (49152)
;
; nop
; nop
; lda #$0c
; sta $c000
; brk
;
$EA $EA $A9 $0c $8D $00 $c0 $00 $00
;
; test program #2
; address: $0400
; copy 0-terminated string from
; address $d000 to $0200
; "Hello World!"
;
; ORG=$0400
; hello:
; ldx #0
; loop:
; lda $d000,x
; beq $06 ;branch to end (+6) if A=0
; sta $0200,x
; inx
; bne $f5 ; branch to loop (-11) if X<>0
; end:
; brk
ORG
$0400
$A2 $00
$BD $00 $d0
$F0 $06
$9D $00 $02
$E8
$D0 $F5
$00 $00
; data
; address: $d000
ORG
$D000
;DEC: 53248
; "Hello World!"
72 101 108 108 111 32 87 111 114 108 100 33 0
;
; test program #3 - copy Hello World! string to $0300
; using different assembly instructions
; address: $0500
;
; ORG=$0500 ;dec: 1280
; hello:
; lda #0
; sta $05
; ldx $05
; loop:
; lda $d000,x
; sta $0300,x
; beq end ;(+6)
; inx
; beq end ;(+3)
; jmp loop
; end:
; brk
ORG
$0500
;DEC: 1280
$A9 $00
$85 $05
$A6 $05
$BD $00 $d0
$9D $00 $03
$F0 $06
$E8
$F0 $03
$4C $06 $05
$00 $00
;
; test program #4
; left-shift memory location $05 at zero page,
; then location $06 using zero page indexed addressing,
; then memory location $c001 (outside zero page) using absolute addressing
; then location $c002 using indexed absolute addressing
; and finally left-shift Acc.
; stop after each step for debugging
; exit loop when Acc=0
;
; start:
; lda #$ff
; ldx #$01
; sta $05
; sta $05,x
; sta $c000,x
; inx
; sta $c000,x
; ldx #$01
; loop2:
; brk
; asl $05
; asl $05,x
; asl $c001
; asl $c001,x
; asl
; bne loop2 ;(-15 or $f1)
; brk
ORG
$0600
$A9 $FF
$A2 $01
$85 $05
$95 $05
$9D $00 $C0
$E8
$9D $00 $C0
$A2 $01
$00 $00
$06 $05
$16 $05
$0E $01 $C0
$1E $01 $C0
$0A
$D0 $F1
$00 $00
;
; test program #5
; Test ORA opcode with various arguments and addressing modes.
; At each break, the contents of Acc should equal $AA.
;
; start:
; lda #$aa ;%10101010
; sta $05
; sta $aa
; lda #$00
; tax
; ora ($05,x)
; brk
; lda #$00
; ora $05
; brk
; lda #$00
; ora #$aa
; brk
; lda #$00
; ora $0005
; brk
; lda #$05
; sta $06
; lda #$00
; sta $07
; tay
; ora ($06),y
; brk
; lda #$00
; tax
; ora $05,x
; brk
; lda #$00
; tay
; ora $0005,y
; brk
; lda #$00
; tax
; ora $0005,x
; brk
ORG
$0700
$A9 $AA
$85 $05
$85 $AA
$A9 $00
$AA
$01 $05
$00 $00
$A9 $00
$05 $05
$00 $00
$A9 $00
$09 $AA
$00 $00
$A9 $00
$0D $05 $00
$00 $00
$A9 $05
$85 $06
$A9 $00
$85 $07
$A8
$11 $06
$00 $00
$A9 $00
$AA
$15 $05
$00 $00
$A9 $00
$A8
$19 $05 $00
$00 $00
$A9 $00
$AA
$1D $05 $00
$00 $00
;
; test program #6
; Test JSR opcode.
; After each break examine memory at $c000 and $c001.
; After 1-st break, $c000 should equal $dd.
; Return address-1 ($0802) should be on stack.
; After 2-nd break, PC counter should be at $0805.
; After 3-rd break, $c000 should equal $ee.
; Return address-1 ($0807) should be on stack.
; After 4-th break, PC counter should be at $080a.
;
; start:
; jsr sub1
; brk
; jsr sub2
; brk
; brk
; brk
; sub1:
; lda #$dd
; sta $c000
; brk
; rts
; sub2:
; lda #$ee
; sta $c000
; brk
; rts
;
ORG
$0800
$20 $0B $08
$00 $00
$20 $13 $08
$00
$00
$00
$A9 $DD
$8D $00 $C0
$00 $00
$60
$A9 $EE
$8D $00 $C0
$00 $00
$60
;
; test program #7
; Test ADC opcode.
; Expected results:
; First break: Acc=$01, Carry=1
; 2-nd break: Acc=$02, Carry=1
; 3-rd break: Acc=$22, Carry=0
; 4-th break: Acc=$23, Carry=0
;
; start:
; clc
; lda #$ff
; adc #$02
; brk
; sec
; lda #$ff
; adc #$02
; brk
; clc
; lda #$20
; adc #$02
; brk
; sec
; lda #$20
; adc #$02
; brk
;
ORG
$0900
$18
$A9 $FF
$69 $02
$00 $00
$38
$A9 $FF
$69 $02
$00 $00
$18
$A9 $20
$69 $02
$00 $00
$38
$A9 $20
$69 $02
$00 $00
;
; test program #8
; Test ROR opcode.
;
; start:
; sec
; lda #$00
; loop:
; ror
; brk
; bcc loop ;(-5 -> $FB)
; brk
;
ORG
$0920
$38
$A9 $00
$6A
$00 $00
$90 $FB
$00 $00
;

View File

@ -15,6 +15,11 @@ $FFFF
; Enable char IO and ROM
ENIO
ENROM
; Enable generic graphics display
; and set its base address.
ENGRAPH
GRAPHADDR
$FFE2
; Auto-execute
;EXEC
;$C000

BIN
ehbas_grdemo.snap Normal file

Binary file not shown.

276
main.cpp
View File

@ -9,6 +9,8 @@
#include "Memory.h"
#include "Display.h"
#include "VMachine.h"
#include "GraphDisp.h"
#include "MemMapDev.h"
#include "MKGenException.h"
using namespace std;
@ -20,8 +22,10 @@ const bool ClsIfDirty = true;
VMachine *pvm = NULL;
Regs *preg = NULL;
bool ioecho = false, opbrk = false;
bool ioecho = false, opbrk = false, needhelp = false;
bool loadbin = false, loadhex = false, reset = false, execvm = false;
int g_stackdisp_lines = 1;
string ramfile = "dummy.ram";
bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat);
void ShowHelp();
@ -69,58 +73,60 @@ void CopyrightBanner();
/*
*--------------------------------------------------------------------
* Method: PrintLoadBinImgErr()
* Purpose: Print the warning/error message after loading binary
* image.
* Method: PrintVMErr()
* Purpose: Print the warning/error message.
* Arguments: err - integer, error code
* Returns: n/a
*--------------------------------------------------------------------
*/
void PrintLoadBinImgErr(int err)
void PrintVMErr(int err)
{
bool pressenter = true;
switch (err) {
case 1: cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
case 2: cout << "WARNING: Unable to open memory image file." << endl;
break;
case 3: cout << "WARNING: Problem with binary image header." << endl;
break;
case 4: cout << "WARNING: No header found in binary image." << endl;
break;
case 5: cout << "WARNING: Problem with binary image header." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
case 6: cout << "WARNING: No header found in binary image." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
default: pressenter = false; break;
}
if (pressenter) {
cout << "Press [ENTER]...";
getchar();
}
}
/*
*--------------------------------------------------------------------
* Method: PrintLoadHexImgErr()
* Purpose: Print the warning/error message after loading Intel HEX
* image.
* Arguments: err - integer, error code
* Returns: n/a
*--------------------------------------------------------------------
*/
void PrintLoadHexImgErr(int err)
{
bool pressenter = true;
switch (err) {
case 1: cout << "WARNING: Unable to open file." << endl;
break;
case 2: cout << "ERROR: Syntax error." << endl;
break;
case 3: cout << "ERROR: Intel HEX format error." << endl;
break;
case MEMIMGERR_RAMBIN_EOF:
cout << "WARNING: Unexpected EOF (image shorter than 64kB).";
cout << endl;
break;
case MEMIMGERR_RAMBIN_OPEN:
cout << "WARNING: Unable to open memory image file." << endl;
break;
case MEMIMGERR_RAMBIN_HDR:
cout << "WARNING: Problem with binary image header." << endl;
break;
case MEMIMGERR_RAMBIN_NOHDR:
cout << "WARNING: No header found in binary image." << endl;
break;
case MEMIMGERR_RAMBIN_HDRANDEOF:
cout << "WARNING: Problem with binary image header." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB).";
cout << endl;
break;
case MEMIMGERR_RAMBIN_NOHDRANDEOF:
cout << "WARNING: No header found in binary image." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB).";
cout << endl;
break;
case MEMIMGERR_INTELH_OPEN:
cout << "WARNING: Unable to open Intel HEX file." << endl;
break;
case MEMIMGERR_INTELH_SYNTAX:
cout << "ERROR: Syntax error." << endl;
break;
case MEMIMGERR_INTELH_FMT:
cout << "ERROR: Intel HEX format error." << endl;
break;
case MEMIMGERR_VM65_OPEN:
cout << "ERROR: Unable to open memory definition file.";
cout << endl;
break;
case MEMIMGERR_VM65_IGNPROCWRN:
cout << "WARNING: There were problems while processing";
cout << " memory definition file." << endl;
break;
case VMERR_SAVE_SNAPSHOT:
cout << "WARNING: There was a problem saving memory snapshot.";
cout << endl;
break;
default: pressenter = false; break;
}
if (pressenter) {
@ -278,17 +284,7 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat)
cout << "| " << bitset<8>((int)preg->Flags) << " |";
cout << " Last instr.: " << preg->LastInstr << " " << endl;
cout << "*-------------*" << endl;
//cout << "Last instr.: " << preg->LastInstr << " " << endl;
cout << endl;
/*
cout << "Registers:" << endl;
cout << " Acc: $" << hex << (unsigned short)preg->Acc << "\t(%" << bitset<8>((int)preg->Acc) << ")" << endl;
cout << " X: $" << hex << (unsigned short)preg->IndX << " " << endl;
cout << " Y: $" << hex << (unsigned short)preg->IndY << " " << endl;
cout << " PC: $" << hex << preg->PtrAddr << " " << endl;
//cout << " Acc16: $" << hex << preg->Acc16 << " " << endl;
//cout << " Ptr16: $" << hex << preg->Ptr16 << " " << endl;
*/
cout << "Stack: $" << hex << (unsigned short)preg->PtrStack << " " << endl;
cout << " \r";
// display stack contents
@ -317,11 +313,13 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat)
cout << endl << "I/O status: " << (pvm->GetCharIOActive() ? "enabled" : "disabled") << ", ";
cout << " at: $" << hex << pvm->GetCharIOAddr() << ", ";
cout << " local echo: " << (ioecho ? "ON" : "OFF") << "." << endl;
cout << "Graphics status: " << (pvm->GetGraphDispActive() ? "enabled" : "disabled") << ", ";
cout << " at: $" << hex << pvm->GetGraphDispAddr() << endl;
cout << "ROM: " << ((pvm->IsROMEnabled()) ? "enabled." : "disabled.") << " ";
cout << "Range: $" << hex << pvm->GetROMBegin() << " - $" << hex << pvm->GetROMEnd() << "." << endl;
}
cout << " \r";
// cout << "-------------------------------------------------------------------------------" << endl;
return ret;
}
@ -345,6 +343,7 @@ void ShowMenu()
cout << " K - toggle ROM emulation | R - show registers, Y - snapshot" << endl;
cout << " L - load memory image | O - display op-codes history" << endl;
cout << " D - disassemble code in memory | Q - quit, 0 - reset, H - help" << endl;
cout << " V - toggle graphics emulation |" << endl;
cout << "------------------------------------+----------------------------------------" << endl;
}
@ -393,10 +392,54 @@ void ShowMenu()
} \
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
/*
*--------------------------------------------------------------------
* Method: LoadArgs()
* Purpose: Parse command line arguments.
* Arguments: int argc, char *argv[], standard C command line args.
* Returns: n/a
*--------------------------------------------------------------------
*/
void LoadArgs(int argc, char *argv[])
{
for (int i=1; i<argc; i++) {
if (!strcmp(argv[i], "-r")) {
reset = true;
execvm = true;
} else if (!strcmp(argv[i], "-b")) {
loadbin = true;
} else if (!strcmp(argv[i], "-x")) {
loadhex = true;
} else if (!strcmp(argv[i], "-h")) {
needhelp = true;
} else {
ramfile = argv[i];
}
}
}
int main(int argc, char** argv) {
bool loadbin = false, loadhex = false, reset = false, execvm = false;
/************ corrected in makefile
// Quick and dirty SDL2 workaround to 'undefined reference to WinMain'
#ifdef main
#undef main
#endif
*****************/
/*
*--------------------------------------------------------------------
* Method: main()
* Purpose: Application entry point/main loop.
* Arguments: int argc, char *argv[], standard C command line args.
* Returns: int - general principle is to return 0 if OK, non-zero
* otherwise
*--------------------------------------------------------------------
*/
int main(int argc, char *argv[]) {
#if defined(LINUX)
signal(SIGINT, trap_signal);
signal(SIGTERM, trap_signal);
@ -404,41 +447,29 @@ int main(int argc, char** argv) {
#if defined(WINDOWS)
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE );
#endif
string romfile("dummy.rom"), ramfile("dummy.ram");
if (argc > 1) {
if (argc > 2) {
reset = execvm = loadbin = (0 == strcmp(argv[1], "-r")); // load binary image and reset
if (!loadbin) loadbin = (0 == strcmp(argv[1], "-b")); // just load binary image
if (!loadbin) loadhex = (0 == strcmp(argv[1], "-x")); // just load Intel HEX image
if (loadbin && loadhex) {
cout << "ERROR: Can't load both formats at the same time." << endl;
exit(-1);
}
ramfile = argv[2];
} else {
if (0 == strcmp(argv[1], "-h")) {
CmdArgHelp(argv[0]);
exit(0);
}
ramfile = argv[1];
}
}
string romfile("dummy.rom");
LoadArgs(argc, argv);
if (needhelp) { CmdArgHelp(argv[0]); exit(0); }
if (loadbin && loadhex) {
cout << "ERROR: Can't load both formats at the same time." << endl;
exit(-1);
}
try {
cout << endl;
if (loadbin) {
pvm = new VMachine(romfile, "dummy.ram");
if (NULL != pvm) {
PrintLoadBinImgErr (pvm->LoadRAMBin(ramfile));
if (!reset && !execvm)
reset = execvm = pvm->IsAutoReset();
PrintVMErr (pvm->LoadRAMBin(ramfile));
if (!reset) { reset = execvm = pvm->IsAutoReset(); }
}
} else if (loadhex) {
pvm = new VMachine(romfile, "dummy.ram");
if (NULL != pvm) PrintLoadHexImgErr (pvm->LoadRAMHex(ramfile));
if (NULL != pvm) PrintVMErr (pvm->LoadRAMHex(ramfile));
}
else {
pvm = new VMachine(romfile, ramfile);
reset = execvm = pvm->IsAutoReset();
if (NULL != pvm) PrintVMErr(pvm->GetLastError());
if (NULL != pvm && !reset) { reset = execvm = pvm->IsAutoReset(); }
}
if (NULL == pvm) {
throw MKGenException("Out of memory");
@ -449,6 +480,7 @@ int main(int argc, char** argv) {
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;
unsigned int graddr = pvm->GetGraphDispAddr();
unsigned int rombegin = pvm->GetROMBegin(), romend = pvm->GetROMEnd(), delay = ANIM_DELAY;
int nsteps = 0;
if (pvm->IsAutoExec()) {
@ -538,18 +570,33 @@ int main(int argc, char** argv) {
}
} else if (c == 'l') { // load memory image
char typ = 0;
while (tolower(typ) != 'b' && tolower(typ) != 'd' && tolower(typ) != 'h') {
cout << "Type (B - binary/H - Intel HEX/D - definition): ";
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 << " [" << ((tolower(typ) == 'b') ? "binary" : "definition") << "]" << endl;
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') PrintLoadBinImgErr (pvm->LoadRAMBin(name));
else if (typ == 'h') PrintLoadHexImgErr (pvm->LoadRAMHex(name));
else {
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;
@ -603,10 +650,20 @@ int main(int argc, char** argv) {
cout << "I/O deactivated." << endl;
} else {
ioaddr = PromptNewAddress("Address (0..FFFF): ");
cout << " [" << hex << ioaddr << "]" << endl;
cout << " [" << hex << ioaddr << "]" << endl;
pvm->SetCharIO(ioaddr, ioecho);
cout << "I/O activated." << endl;
}
} 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;
}
} else if (c == 'w') { // write to memory
tmpaddr = PromptNewAddress("Address (0..FFFF): ");
cout << " [" << hex << tmpaddr << "]" << endl;
@ -730,27 +787,26 @@ void CmdArgHelp(string prgname)
cout << endl << endl;
cout << "Usage:" << endl << endl;
cout << "\t" << prgname;
cout << " [-h] | [ramdeffile] | [-b ramimage] | [-r ramimage]" << endl;
cout << "\tOR" << endl;
cout << "\t" << prgname << " [-x intelheximage]";
cout << " [-h] | [ramdeffile] [-b | -x] [-r]" << endl;
cout << endl << endl;
cout << "Where:" << endl << endl;
cout << "\tramdeffile - RAM definition file name" << endl;
cout << "\tintelheximage - Intel HEX format file" << endl;
cout << "\tramimage - RAM binary image file name" << endl;
cout << "\t-b - specify input format as binary" << endl;
cout << "\t-x - specify input format as Intel HEX" << endl;
cout << "\t-r - after loading, perform CPU RESET" << endl;
cout << "\t-h - print this help screen" << endl;
cout << R"(
When ran with no arguments, program will load default memory
definition files: default.rom, default.ram and will enter the debug
console menu.
When ramdeffile argument is provided, program will load the memory
definition from the file, set the flags and parameters depending on the
contents of the memory definition file and enter the corresponding mode
of operation as defined in that file.
If used with flag -b or -x, program will load memory from the provided image
file and enter the debug console menu.
If used with flag -r, program will load memory from the provided image
file and execute CPU reset sequence.
When ramdeffile argument is provided with no input format specified,
program will attempt to automatically detect input format and load the
memory definition from the file, set the flags and parameters depending
on the contents of the memory definition file and enter the corresponding
mode of operation as defined in that file.
If input format is specified (-b|-x), program will load memory from the
provided image file and enter the debug console menu.
)";
cout << endl;
@ -809,6 +865,14 @@ I - toggle char I/O emulation
to the specified memory address also writes a character code to
to a virtual console. All reads from specified memory address
are interpreted as console character input.
V - toggle graphics display (video) emulation
Usage: V [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF],
Toggles basic raster (pixel) based RGB graphics display 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.
R - show registers
Displays CPU registers, flags and stack.
Y - snapshot
@ -845,10 +909,12 @@ K - toggle ROM emulation
L - load memory image
Usage: L [image_type] [image_name]
Where:
image_type - B (binary), H (Intel HEX) OR D (definition),
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
image file, Intel HEX format file or the ASCII definition file.
With option 'A' selected, automatic input format detection will be
attempted.
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:

View File

@ -2,19 +2,23 @@
# Makefile created by Dev-C++ 5.11
# and modified for standalone MINGW compiler installation.
#SDLBASE = "C:\src\SDL"
SDLBASE = $(SDLDIR)
CPP = g++.exe -D__DEBUG__
CC = gcc.exe -D__DEBUG__
WINDRES = windres.exe
OBJ = main.o VMachine.o MKBasic.o MKCpu.o Memory.o Display.o MKGenException.o
OBJ = main.o VMachine.o MKBasic.o MKCpu.o Memory.o Display.o GraphDisp.o MemMapDev.o MKGenException.o
OBJ2 = bin2hex.o
LINKOBJ = main.o VMachine.o MKBasic.o MKCpu.o Memory.o Display.o MKGenException.o
LINKOBJ = main.o VMachine.o MKBasic.o MKCpu.o Memory.o Display.o GraphDisp.o MemMapDev.o MKGenException.o
LINKOBJ2 = bin2hex.o
LIBS = -L"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/lib" -L"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/lib" -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic
LIBS = -L"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/lib" -L"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/lib" -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic -lmingw32
SDLLIBS = -L"$(SDLBASE)\x86_64-w64-mingw32/lib" -lSDL2main -lSDL2
INCS = -I"C:\mingw-w64\x86_64-5.3.0\mingw64/include" -I"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/include" -I"C:\mingw-w64\x86_64-5.3.0\mingw64\lib\gcc\x86_64-w64-mingw32\5.3.0/include"
CXXINCS = -I"C:\mingw-w64\x86_64-5.3.0\mingw64/include" -I"C:\mingw-w64\x86_64-5.3.0\mingw64\x86_64-w64-mingw32/include" -I"C:\mingw-w64\x86_64-5.3.0\mingw64\lib\gcc\x86_64-w64-mingw32\5.3.0/include"
BIN = mkbasic.exe
BIN = vm65.exe
BIN2 = bin2hex.exe
CXXFLAGS = $(CXXINCS) -std=c++11 -Wall -Wextra -pedantic -g3
SDLINCS = -I"$(SDLBASE)/include"
CFLAGS = $(INCS) -std=c++11 -Wall -Wextra -pedantic -g3
CXXFLAGS2 = $(CXXINCS)
CFLAGS2 = $(INCS)
@ -28,27 +32,33 @@ clean: clean-custom
${RM} $(OBJ) $(OBJ2) $(BIN) $(BIN2)
$(BIN): $(OBJ)
$(CPP) $(LINKOBJ) -o $(BIN) $(LIBS)
$(CPP) $(LINKOBJ) -o $(BIN) $(LIBS) $(SDLLIBS)
main.o: main.cpp
$(CPP) -c main.cpp -o main.o $(CXXFLAGS)
$(CPP) -c main.cpp -o main.o $(CXXFLAGS) $(SDLINCS)
VMachine.o: VMachine.cpp
$(CPP) -c VMachine.cpp -o VMachine.o $(CXXFLAGS)
VMachine.o: VMachine.cpp VMachine.h
$(CPP) -c VMachine.cpp -o VMachine.o $(CXXFLAGS) $(SDLINCS)
MKBasic.o: MKBasic.cpp
$(CPP) -c MKBasic.cpp -o MKBasic.o $(CXXFLAGS)
MKBasic.o: MKBasic.cpp MKBasic.h
$(CPP) -c MKBasic.cpp -o MKBasic.o $(CXXFLAGS) $(SDLINCS)
MKCpu.o: MKCpu.cpp
$(CPP) -c MKCpu.cpp -o MKCpu.o $(CXXFLAGS)
MKCpu.o: MKCpu.cpp MKCpu.h
$(CPP) -c MKCpu.cpp -o MKCpu.o $(CXXFLAGS) $(SDLINCS)
Memory.o: Memory.cpp
$(CPP) -c Memory.cpp -o Memory.o $(CXXFLAGS)
Memory.o: Memory.cpp Memory.h
$(CPP) -c Memory.cpp -o Memory.o $(CXXFLAGS) $(SDLINCS)
Display.o: Display.cpp
Display.o: Display.cpp Display.h
$(CPP) -c Display.cpp -o Display.o $(CXXFLAGS)
MKGenException.o: MKGenException.cpp
GraphDisp.o: GraphDisp.cpp GraphDisp.h
$(CPP) -c GraphDisp.cpp -o GraphDisp.o $(CXXFLAGS) $(SDLINCS)
MemMapDev.o: MemMapDev.cpp MemMapDev.h
$(CPP) -c MemMapDev.cpp -o MemMapDev.o $(CXXFLAGS) $(SDLINCS)
MKGenException.o: MKGenException.cpp MKGenException.h
$(CPP) -c MKGenException.cpp -o MKGenException.o $(CXXFLAGS)
$(BIN2): $(OBJ2)

BIN
tb.snap Normal file

Binary file not shown.