From 20d12b5eae339b5d9e441161fcf471713aa59dc8 Mon Sep 17 00:00:00 2001 From: Marek Karcz Date: Tue, 9 Aug 2016 23:39:52 -0400 Subject: [PATCH] Memory Mapped Device Layer Implemented MemMapDev layer, added User Manual and Programmers Reference. --- GraphDisp.cpp | 464 +++++++++++++++++++++++ GraphDisp.h | 72 ++++ MemMapDev.cpp | 663 +++++++++++++++++++++++++++++++++ MemMapDev.h | 236 ++++++++++++ Memory.cpp | 540 +++++++++++++++++---------- Memory.h | 151 ++++---- ProgrammersReferenceManual.txt | 567 ++++++++++++++++++++++++++++ ReadMe.txt | 202 ++++++++-- VMachine.cpp | 424 ++++++++++++++++++--- VMachine.h | 59 ++- dummy.ram | 630 +++++++++++++++---------------- dummy.rom | 630 +++++++++++++++---------------- ehbas.dat | 5 + ehbas_grdemo.snap | Bin 0 -> 65673 bytes main.cpp | 276 ++++++++------ makefile.mingw | 42 ++- tb.snap | Bin 0 -> 65673 bytes 17 files changed, 3838 insertions(+), 1123 deletions(-) create mode 100644 GraphDisp.cpp create mode 100644 GraphDisp.h create mode 100644 MemMapDev.cpp create mode 100644 MemMapDev.h create mode 100644 ProgrammersReferenceManual.txt create mode 100644 ehbas_grdemo.snap create mode 100644 tb.snap diff --git a/GraphDisp.cpp b/GraphDisp.cpp new file mode 100644 index 0000000..e7045f8 --- /dev/null +++ b/GraphDisp.cpp @@ -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 diff --git a/GraphDisp.h b/GraphDisp.h new file mode 100644 index 0000000..a782525 --- /dev/null +++ b/GraphDisp.h @@ -0,0 +1,72 @@ +#ifndef GRAPHDISP_H +#define GRAPHDISP_H + +/* + * Rudimentary emulation of generic raster graphics RGB display device. + */ + +#include +#include + +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 \ No newline at end of file diff --git a/MemMapDev.cpp b/MemMapDev.cpp new file mode 100644 index 0000000..6c546fd --- /dev/null +++ b/MemMapDev.cpp @@ -0,0 +1,663 @@ + +#include "MemMapDev.h" +#include "Memory.h" +#include "MKGenException.h" + +#include +#include +#if defined(WINDOWS) +#include +#endif + +//#define DBG 1 +#if defined (DBG) +#include +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 +#include +#include + +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 diff --git a/MemMapDev.h b/MemMapDev.h new file mode 100644 index 0000000..b51b513 --- /dev/null +++ b/MemMapDev.h @@ -0,0 +1,236 @@ +#ifndef MEMMAPDEV_H +#define MEMMAPDEV_H + +#include +#include +#include "system.h" +//#include "Memory.h" +#include "GraphDisp.h" + +#if defined(LINUX) +#include +#include +#include +#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 MemAddrRanges; +typedef vector 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 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 \ No newline at end of file diff --git a/Memory.cpp b/Memory.cpp index 96fdb3a..0f569da 100644 --- a/Memory.cpp +++ b/Memory.cpp @@ -1,12 +1,6 @@ #include "Memory.h" -#include -#include -#if defined(WINDOWS) -#include -#endif - //#define DBG 1 #if defined (DBG) #include @@ -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 -#include -#include - -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::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::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::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 active_new; + + for (vector::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::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 diff --git a/Memory.h b/Memory.h index 2151a63..6216001 100644 --- a/Memory.h +++ b/Memory.h @@ -1,77 +1,74 @@ -#ifndef MEMORY_H -#define MEMORY_H - -#include "system.h" - -#if defined(LINUX) -#include -#include -#include -#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 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 diff --git a/ProgrammersReferenceManual.txt b/ProgrammersReferenceManual.txt new file mode 100644 index 0000000..fb20808 --- /dev/null +++ b/ProgrammersReferenceManual.txt @@ -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 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(). diff --git a/ReadMe.txt b/ReadMe.txt index 120b8ce..db257cd 100644 --- a/ReadMe.txt +++ b/ReadMe.txt @@ -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 diff --git a/VMachine.cpp b/VMachine.cpp index 5277f13..991ab9b 100644 --- a/VMachine.cpp +++ b/VMachine.cpp @@ -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=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 diff --git a/VMachine.h b/VMachine.h index d256289..0411dc7 100644 --- a/VMachine.h +++ b/VMachine.h @@ -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 diff --git a/dummy.ram b/dummy.ram index 43eaabb..aa6d9fa 100644 --- a/dummy.ram +++ b/dummy.ram @@ -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 ; \ No newline at end of file diff --git a/dummy.rom b/dummy.rom index 43eaabb..aa6d9fa 100644 --- a/dummy.rom +++ b/dummy.rom @@ -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 ; \ No newline at end of file diff --git a/ehbas.dat b/ehbas.dat index e2b8799..1d69133 100644 --- a/ehbas.dat +++ b/ehbas.dat @@ -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 diff --git a/ehbas_grdemo.snap b/ehbas_grdemo.snap new file mode 100644 index 0000000000000000000000000000000000000000..57b4802d6e06c1c757fdec37b6f2641a47a94920 GIT binary patch literal 65673 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 z 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 O4x(r8((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 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: diff --git a/makefile.mingw b/makefile.mingw index 2525976..5713f1c 100644 --- a/makefile.mingw +++ b/makefile.mingw @@ -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) diff --git a/tb.snap b/tb.snap new file mode 100644 index 0000000000000000000000000000000000000000..5fddc2db311ef84829cf457bd7eedcc65695b6b0 GIT binary patch literal 65673 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 literal 0 HcmV?d00001