From 9d6706df962a05a2e06a84f1b8dfb2a75b9f2dad Mon Sep 17 00:00:00 2001 From: Marek Karcz Date: Wed, 7 Sep 2016 22:52:01 -0400 Subject: [PATCH] Performance, bug fixes, debug options, documentation updates. Performance stats., refactoring, bug fixes, documentation updates, cosmetic changes, debug options. --- ConsoleIO.cpp | 176 +++++++++++ ConsoleIO.h | 61 ++++ Display.cpp | 36 +++ Display.h | 32 ++ GraphDisp.cpp | 38 +++ GraphDisp.h | 40 ++- MKBasic.cpp | 59 +++- MKBasic.h | 70 +++-- MKCpu.cpp | 36 ++- MKCpu.h | 34 ++- MemMapDev.cpp | 160 +++++++++- MemMapDev.h | 45 ++- Memory.cpp | 97 ++++-- Memory.h | 33 +++ ProgrammersReferenceManual.txt | 225 +++++++++++++- ReadMe.txt | 14 +- VMachine.cpp | 518 ++++++++++++++++++++++++--------- VMachine.h | 94 +++++- grdevdemo.bas | 63 ++++ main.cpp | 377 ++++++++++++++++++------ makefile.mingw | 12 +- system.h | 31 ++ 22 files changed, 1926 insertions(+), 325 deletions(-) create mode 100644 ConsoleIO.cpp create mode 100644 ConsoleIO.h create mode 100644 grdevdemo.bas diff --git a/ConsoleIO.cpp b/ConsoleIO.cpp new file mode 100644 index 0000000..7d56767 --- /dev/null +++ b/ConsoleIO.cpp @@ -0,0 +1,176 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: ConsoleIO.cpp + * + * Purpose: Implementation of ConsoleIO class. + * The ConsoleIO class has methods helpful when + * UI is utilizing STDIO (DOS) console. + * + * Date: 8/26/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ +#include +#include +#include +#include +#include "system.h" +#include "ConsoleIO.h" + +#include "MKGenException.h" + +#if defined(WINDOWS) +#include +#endif + +using namespace std; + +namespace MKBasic { + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +ConsoleIO::ConsoleIO() +{ + +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +ConsoleIO::~ConsoleIO() +{ + +} + +#if defined(WINDOWS) + +/* + *-------------------------------------------------------------------- + * Method: ClearScreen() + * Purpose: Clear the console screen. + * Arguments: n/a + * Returns: n/a + *-------------------------------------------------------------------- + */ +void ConsoleIO::ClearScreen() +{ + HANDLE hStdOut; + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD count; + DWORD cellCount; + COORD homeCoords = { 0, 0 }; + + hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); + if (hStdOut == INVALID_HANDLE_VALUE) return; + + /* Get the number of cells in the current buffer */ + if (!GetConsoleScreenBufferInfo( hStdOut, &csbi )) return; + cellCount = csbi.dwSize.X *csbi.dwSize.Y; + + /* Fill the entire buffer with spaces */ + if (!FillConsoleOutputCharacter( + hStdOut, + (TCHAR) ' ', + cellCount, + homeCoords, + &count + )) return; + + /* Fill the entire buffer with the current colors and attributes */ + if (!FillConsoleOutputAttribute( + hStdOut, + csbi.wAttributes, + cellCount, + homeCoords, + &count + )) return; + + /* Move the cursor home */ + SetConsoleCursorPosition( hStdOut, homeCoords ); +} + +/* + *-------------------------------------------------------------------- + * Method: ScrHome() + * Purpose: Bring the console cursor to home position. + * Arguments: n/a + * Returns: n/a + *-------------------------------------------------------------------- + */ +void ConsoleIO::ScrHome() +{ + HANDLE hStdOut; + COORD homeCoords = { 0, 0 }; + + hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); + if (hStdOut == INVALID_HANDLE_VALUE) return; + + /* Move the cursor home */ + SetConsoleCursorPosition( hStdOut, homeCoords ); +} + +#endif + +#if defined(LINUX) + +/* + *-------------------------------------------------------------------- + * Method: ClearScreen() + * Purpose: Clear the console screen. + * Arguments: n/a + * Returns: n/a + *-------------------------------------------------------------------- + */ +void ConsoleIO::ClearScreen() +{ + system("clear"); +} + +/* + *-------------------------------------------------------------------- + * Method: ScrHome() + * Purpose: Bring the console cursor to home position. + * Arguments: n/a + * Returns: n/a + *-------------------------------------------------------------------- + */ +void ConsoleIO::ScrHome() +{ + cout << "\033[1;1H"; +} + +#endif // #devine LINUX + +} // END namespace MKBasic \ No newline at end of file diff --git a/ConsoleIO.h b/ConsoleIO.h new file mode 100644 index 0000000..5a67dad --- /dev/null +++ b/ConsoleIO.h @@ -0,0 +1,61 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: ConsoleIO.h + * + * Purpose: Prototype of ConsoleIO class. + * + * Date: 8/26/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ +#ifndef CONSOLEIO_H +#define CONSOLEIO_H + +#include +#include +#include +#include "system.h" + +//#define WINDOWS 1 +#if defined (WINDOWS) +#include +#endif + +namespace MKBasic { + + class ConsoleIO + { + public: + + ConsoleIO(); + ~ConsoleIO(); + + void ClearScreen(); + void ScrHome(); + + }; + +} // namespace MKBasic + +#endif // #ifndef CONSOLEIO_H \ No newline at end of file diff --git a/Display.cpp b/Display.cpp index 94cd094..70ae1ad 100644 --- a/Display.cpp +++ b/Display.cpp @@ -1,3 +1,39 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: Display.cpp + * + * Purpose: Implementation of Display class. + * The Display class emulates the character I/O device. + * It defines the abstract device. The actual + * input/output to the screen of the platform on which + * the emulator runs is defined in MemMapDev class + * or on higher level of abstraction. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include "Display.h" #include #include diff --git a/Display.h b/Display.h index 7c9dd15..5cbb8bb 100644 --- a/Display.h +++ b/Display.h @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: Display.h + * + * Purpose: Prototype of Display class and all supportig data + * structures, enumerations, constants and macros. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef DISPLAY_H #define DISPLAY_H diff --git a/GraphDisp.cpp b/GraphDisp.cpp index e7045f8..e083ec4 100644 --- a/GraphDisp.cpp +++ b/GraphDisp.cpp @@ -1,4 +1,40 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: GraphDisp.cpp + * + * Purpose: Implementation of GraphDisp class. + * The GraphDisp class emulates the graphics raster + * device. It handles displaying of the graphics, the + * events loop and the 'hardware' API to the device. + * The higher level abstraction layer - MemMapDev + * defines the actual behavior/response of the emulated + * device in the emulated system. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include "GraphDisp.h" #include "MKGenException.h" #include "system.h" @@ -75,6 +111,8 @@ void GraphDisp::Initialize() { int desk_w, desk_h, winbd_top = 5, winbd_right = 5; + mPixelSizeX = GRAPHDISP_MAXW / mWidth; + mPixelSizeY = GRAPHDISP_MAXH / mHeight; GetDesktopResolution(desk_w, desk_h); // Available in version > 2.0.4 //SDL_GetWindowBordersSize(mpWindow, &winbd_top, NULL, NULL, &winbd_right); diff --git a/GraphDisp.h b/GraphDisp.h index a782525..c391a71 100644 --- a/GraphDisp.h +++ b/GraphDisp.h @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: GraphDisp.h + * + * Purpose: Prototype of GraphDisp class and supporting data + * structures, enumerations, constants and macros. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef GRAPHDISP_H #define GRAPHDISP_H @@ -12,8 +44,8 @@ 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 +const int GRAPHDISP_MAXW = 960; // "real" display width or maximum virtual width +const int GRAPHDISP_MAXH = 600; // "real" display width or maximum virtual height class GraphDisp { @@ -43,8 +75,8 @@ class GraphDisp { 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 mPixelSizeX = 3; // virtual pixel width + int mPixelSizeY = 3; // 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 diff --git a/MKBasic.cpp b/MKBasic.cpp index 6f84b69..641d087 100644 --- a/MKBasic.cpp +++ b/MKBasic.cpp @@ -1,13 +1,46 @@ -#include "MKBasic.h" - -namespace MKBasic { - -MKBasic::MKBasic() -{ -} - -MKBasic::~MKBasic() -{ -} - -} // namespace MKBasic +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: + * + * Purpose: Implementation of MKBasic class. + * NOTE: This is obsolete concept and will likely be + * removed from project. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ +#include "MKBasic.h" + +namespace MKBasic { + +MKBasic::MKBasic() +{ +} + +MKBasic::~MKBasic() +{ +} + +} // namespace MKBasic diff --git a/MKBasic.h b/MKBasic.h index fbe06ff..229e606 100644 --- a/MKBasic.h +++ b/MKBasic.h @@ -1,18 +1,52 @@ -#ifndef MKBASIC_H -#define MKBASIC_H - -#include "VMachine.h" - -namespace MKBasic { - -class MKBasic : public VMachine -{ - public: - MKBasic(); - ~MKBasic(); - protected: -}; - -} // namespace MKBasic - -#endif +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: + * + * Purpose: Prototype of MKBasic class and all supporting data + * structures, enumerations, constants and macros. + * NOTE: This is obsolete concept and will likely be + * removed from project. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ +#ifndef MKBASIC_H +#define MKBASIC_H + +#include "VMachine.h" + +namespace MKBasic { + +class MKBasic : public VMachine +{ + public: + MKBasic(); + ~MKBasic(); + protected: +}; + +} // namespace MKBasic + +#endif diff --git a/MKCpu.cpp b/MKCpu.cpp index 823b488..07488a2 100644 --- a/MKCpu.cpp +++ b/MKCpu.cpp @@ -1,4 +1,38 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: MKCpu.cpp + * + * Purpose: Implementation of MKCpu class. + * MKCpu class implements the op-codes and other + * supporting functions of a virtual CPU. This one in + * particular emulates a real world microprocessor from + * 1970-s, the venerable MOS-6502. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and exercise common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include #include "MKCpu.h" #include "MKGenException.h" @@ -347,7 +381,7 @@ void MKCpu::InitCpu() mReg.PtrStack = 0xFF; // top of stack mReg.SoftIrq = false; mReg.IrqPending = false; - mReg.CyclesLeft = 0; + mReg.CyclesLeft = 1; mReg.PageBoundary = false; mLocalMem = false; mExitAtLastRTS = true; diff --git a/MKCpu.h b/MKCpu.h index 75f7eb8..2a59c3b 100644 --- a/MKCpu.h +++ b/MKCpu.h @@ -1,3 +1,35 @@ + /* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: MKCpu.h + * + * Purpose: Prototype of MKCpu class. Data structures, enumerations + * and constants definitions supporting MKCpu class. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and exercise common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef MKCPU_H #define MKCPU_H @@ -26,7 +58,7 @@ struct Regs { bool SoftIrq; // true when interrupted with BRK or trapped opcode bool LastRTS; // true if RTS encountered and stack empty. unsigned short LastAddr; // PC at the time of previous op-code - string LastInstr; // instruction and argument executed in previous step + //string LastInstr; // instruction and argument executed in previous step int LastOpCode; // op-code of last instruction unsigned short LastArg; // argument to the last instruction int LastAddrMode; // addressing mode of last instruction diff --git a/MemMapDev.cpp b/MemMapDev.cpp index 3479d34..ee786f9 100644 --- a/MemMapDev.cpp +++ b/MemMapDev.cpp @@ -1,4 +1,47 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: MemMapDev.cpp + * + * Purpose: Implementation of MemMapDev class. + * The MemMapDev class implements the highest abstraction + * layer for interfacing with emulated devices via memory + * addressing space, which is a typical way of + * interfacing the CPU with peripherals in the real world + * microprocessor systems. + * It also implements/emulates the behavior of the + * devices. The core implementation of the devices may + * be contained in separate classes or inside MemMapDev + * class. Note that MemMapDev class always contains + * at least partial (highest abstraction layer) + * implementation of the emulated device as it defines + * the methods triggered by corresponding memory address + * accesses. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include "MemMapDev.h" #include "Memory.h" #include "MKGenException.h" @@ -83,6 +126,7 @@ void MemMapDev::Initialize() mCharIOAddr = CHARIO_ADDR; mGraphDispAddr = GRDISP_ADDR; mpGraphDisp = NULL; + mpCharIODisp = NULL; AddrRange addr_range(CHARIO_ADDR, CHARIO_ADDR+1); DevPar dev_par("echo", "false"); MemAddrRanges addr_ranges_chario; @@ -112,6 +156,7 @@ void MemMapDev::Initialize() &MemMapDev::GraphDispDevice_Write, dev_params_grdisp); mDevices.push_back(dev_grdisp); + mCharIOActive = false; } /* @@ -287,7 +332,7 @@ int MemMapDev::getch() } } -#endif +#endif // #define LINUX /* *-------------------------------------------------------------------- @@ -304,10 +349,11 @@ unsigned char MemMapDev::ReadCharKb(bool nonblock) #if defined(LINUX) set_conio_terminal_mode(); #endif - static int c = ' '; - if (mIOEcho && isprint(c)) putchar(c); - fflush(stdout); - + static int c = ' '; // static, initializes once, remembers prev. + // value + // checking mCharIOActive may be too much of a precaution since + // this method will not be called unless char I/O is enabled + if (mCharIOActive && mIOEcho && isprint(c)) putchar(c); if (nonblock) { // get a keystroke only if character is already in buffer if (kbhit()) c = getch(); @@ -375,10 +421,31 @@ char MemMapDev::GetCharOut() return ret; } +/* + *-------------------------------------------------------------------- + * Method: CharIOFlush() + * Purpose: Flush the character I/O FIFO output buffer contents + * to the character I/O device's screen buffer. + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void MemMapDev::CharIOFlush() +{ + char cr = -1; + while ((cr = GetCharOut()) != -1) { + mpCharIODisp->PutChar(cr); + } +} + /* *-------------------------------------------------------------------- * Method: PutCharIO() * Purpose: Put character in the output char I/O FIFO buffer. + * If character I/O device emulation is enabled, print the + * character to the standard output, then flush the I/O + * FIFO output buffer to the character device screen + * buffer. * Arguments: c - character * Returns: n/a *-------------------------------------------------------------------- @@ -388,6 +455,10 @@ void MemMapDev::PutCharIO(char c) mCharIOBufOut[mOutBufDataEnd] = c; mOutBufDataEnd++; if (mOutBufDataEnd >= CHARIO_BUF_SIZE) mOutBufDataEnd = 0; + if (mCharIOActive) { + putchar((int)c); + CharIOFlush(); + } } /* @@ -451,6 +522,69 @@ bool MemMapDev::GetCharIOEchoOn() return mIOEcho; } +/* + *-------------------------------------------------------------------- + * Method: IsCharIOActive() + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +bool MemMapDev::IsCharIOActive() +{ + return mCharIOActive; +} + +/* + *-------------------------------------------------------------------- + * Method: ActivateCharIO() + * Purpose: Activate character I/O device, create Display object. + * Arguments: n/a + * Returns: Pointer to Display object. + *-------------------------------------------------------------------- + */ +Display *MemMapDev::ActivateCharIO() +{ + if (NULL == mpCharIODisp) { + mpCharIODisp = new Display(); + if (NULL == mpCharIODisp) + throw MKGenException( + "Out of memory while initializing Character I/O Display Device"); + } + mCharIOActive = true; + mpCharIODisp->ClrScr(); + + return mpCharIODisp; +} + +/* + *-------------------------------------------------------------------- + * Method: GetDispPtr() + * Purpose: + * Arguments: + * Returns: Pointer to Display object. + *-------------------------------------------------------------------- + */ +Display *MemMapDev::GetDispPtr() +{ + return mpCharIODisp; +} + +/* + *-------------------------------------------------------------------- + * Method: DeactivateCharIO() + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void MemMapDev::DeactivateCharIO() +{ + if (NULL != mpCharIODisp) delete mpCharIODisp; + mpCharIODisp = NULL; + mCharIOActive = false; +} + /* *-------------------------------------------------------------------- * Method: GetGraphDispAddrBase() @@ -668,4 +802,20 @@ void MemMapDev::GraphDisp_Update() if (NULL != mpGraphDisp) mpGraphDisp->Update(); } +/* + *-------------------------------------------------------------------- + * Method: SetCharIODispPtr() + * Purpose: Set internal pointer to character I/O device object. + * Arguments: p - pointer to Display object. + * active - bool, true if character I/O is active + * Returns: n/a + *-------------------------------------------------------------------- + */ + /* +void MemMapDev::SetCharIODispPtr(Display *p, bool active) +{ + mpCharIODisp = p; + mCharIOActive = active; +}*/ + } // namespace MKBasic diff --git a/MemMapDev.h b/MemMapDev.h index 698d3d2..9706eec 100644 --- a/MemMapDev.h +++ b/MemMapDev.h @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: MemMapDev.h + * + * Purpose: Prototype of MemMapDev class and all supporting + * data structures, enumarators, constants and macros. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef MEMMAPDEV_H #define MEMMAPDEV_H @@ -6,6 +38,7 @@ #include "system.h" //#include "Memory.h" #include "GraphDisp.h" +#include "Display.h" #if defined(LINUX) #include @@ -102,6 +135,7 @@ struct Device { typedef vector MemMappedDevices; +// currently supported devices enum DevNums { DEVNUM_CHARIO = 0, // character I/O device DEVNUM_GRDISP = 1, // raster graphics display device @@ -112,7 +146,7 @@ enum DevNums { * * 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 + * Depending on the device, the memory address range may be 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 @@ -184,8 +218,13 @@ class MemMapDev { char GetCharIn(); char GetCharOut(); + void CharIOFlush(); unsigned short GetCharIOAddr(); bool GetCharIOEchoOn(); + bool IsCharIOActive(); + Display* ActivateCharIO(); + Display* GetDispPtr(); + void DeactivateCharIO(); int CharIODevice_Read(int addr); void CharIODevice_Write(int addr, int val); @@ -200,6 +239,8 @@ class MemMapDev { void GraphDisp_ReadEvents(); void GraphDisp_Update(); + //void SetCharIODispPtr(Display *p, bool active); + private: Memory *mpMem; // pointer to memory object @@ -214,6 +255,8 @@ class MemMapDev { bool mIOEcho; unsigned int mGraphDispAddr; GraphDisp *mpGraphDisp; // pointer to Graphics Device object + Display *mpCharIODisp; // pointer to character I/O device object + bool mCharIOActive; // indicate if character I/O is active GraphDeviceRegs mGrDevRegs; // graphics display device registers void Initialize(); diff --git a/Memory.cpp b/Memory.cpp index 4221a3f..0631bea 100644 --- a/Memory.cpp +++ b/Memory.cpp @@ -1,4 +1,42 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: Memory.cpp. + * + * Purpose: Implementation of class Memory. + * The Memory class implements the highest abstraction + * layer of Visrtual Machine's memory. in this particular + * case the Virtual Machine emulates a computer system + * based on a 8-bit microprocessor. Therefore it + * implements image size and addressing space adequate + * for the specific architecture of such microprocessor. + * The Memory class also interfaces with the MemMapDev + * API (Memory Mapped Devices). + * + * Date: + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include "Memory.h" #include "MKGenException.h" @@ -322,6 +360,7 @@ void Memory::SetCharIO(unsigned short addr, bool echo) SetupDevice(DEVNUM_CHARIO, memaddr_ranges, dev_params); if (false == mCharIOActive) AddDevice(DEVNUM_CHARIO); mCharIOActive = true; + mpMemMapDev->ActivateCharIO(); } /* @@ -336,6 +375,7 @@ void Memory::DisableCharIO() { mCharIOActive = false; DeleteDevice(DEVNUM_CHARIO); + mpMemMapDev->DeactivateCharIO(); } /* @@ -405,10 +445,10 @@ unsigned short Memory::GetGraphDispAddr() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: GetROMBegin() + * Purpose: Get starting address of read-only memory. + * Arguments: + * Returns: unsigned short - address ($0000-$FFFF) *-------------------------------------------------------------------- */ unsigned short Memory::GetROMBegin() @@ -418,10 +458,10 @@ unsigned short Memory::GetROMBegin() /* *-------------------------------------------------------------------- - * Method: - * Purpose: + * Method: GetROMEnd() + * Purpose: Get end address of read-only memory. * Arguments: - * Returns: + * Returns: unsigned short - address ($0000-$FFFF) *-------------------------------------------------------------------- */ unsigned short Memory::GetROMEnd() @@ -431,10 +471,10 @@ unsigned short Memory::GetROMEnd() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: IsROMEnabled() + * Purpose: Get status of ROM. + * Arguments: + * Returns: bool - true if enabled. *-------------------------------------------------------------------- */ bool Memory::IsROMEnabled() @@ -445,7 +485,7 @@ bool Memory::IsROMEnabled() /* *-------------------------------------------------------------------- * Method: AddDevice() - * Purpose: Add device number to active devices list. + * Purpose: Add device to active devices cache. * Arguments: devnum - device number * Returns: -1 if device is not supported OR already cached * devnum - device number if it was found @@ -487,7 +527,7 @@ int Memory::AddDevice(int devnum) } } } // END if (dev.num >= 0) - // else device with wuch number is not supported + // else device with such number is not supported return ret; } @@ -495,7 +535,7 @@ int Memory::AddDevice(int devnum) /* *-------------------------------------------------------------------- * Method: DeleteDevice() - * Purpose: Delete device number from active devices list. + * Purpose: Delete device from active devices cache. * Arguments: devnum - device number * Returns: >=0 if device was found in local cache and deleted * -1 if device was not found @@ -607,10 +647,10 @@ char Memory::GetCharOut() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: GraphDisp_ReadEvents() + * Purpose: Read events from the graphics display window. + * Arguments: n/a + * Returns: n/a *-------------------------------------------------------------------- */ void Memory::GraphDisp_ReadEvents() @@ -620,10 +660,10 @@ void Memory::GraphDisp_ReadEvents() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: GraphDisp_Update() + * Purpose: Trigger update handler of the graphics display window. + * Arguments: n/a + * Returns: n/a *-------------------------------------------------------------------- */ void Memory::GraphDisp_Update() @@ -646,4 +686,17 @@ bool Memory::GraphDispOp() return mDispOp; } +/* + *-------------------------------------------------------------------- + * Method: GetMemMapDevPtr() + * Purpose: Get the pointer to MemMapDev object. + * Arguments: n/a + * Returns: Pointer to MemMapDev object. + *-------------------------------------------------------------------- + */ +MemMapDev *Memory::GetMemMapDevPtr() +{ + return mpMemMapDev; +} + } // namespace MKBasic diff --git a/Memory.h b/Memory.h index 2a48132..c52c381 100644 --- a/Memory.h +++ b/Memory.h @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: Memory.h + * + * Purpose: Prototype of Memory class and supporting data + * structures, constants, enumerations and macros. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and exercise common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef MEMORY_H #define MEMORY_H @@ -49,6 +81,7 @@ class Memory void GraphDisp_ReadEvents(); void GraphDisp_Update(); bool GraphDispOp(); + MemMapDev *GetMemMapDevPtr(); protected: diff --git a/ProgrammersReferenceManual.txt b/ProgrammersReferenceManual.txt index fb20808..de31e1d 100644 --- a/ProgrammersReferenceManual.txt +++ b/ProgrammersReferenceManual.txt @@ -82,11 +82,14 @@ provided image file and enter the debug console menu. which will be referred to as Debug Console: STOPPED at 0 + +Emulation performance stats is OFF. + *-------------*-----------------------*----------*----------* | PC: $0000 | Acc: $00 (00000000) | X: $00 | Y: $00 | *-------------*-----------------------*----------*----------* -| NV-BDIZC | -| 00100000 | Last instr.: +| NV-BDIZC | : +| 00100000 | : *-------------* Stack: $ff @@ -95,6 +98,7 @@ Stack: $ff I/O status: disabled, at: $e000, local echo: OFF. Graphics status: disabled, at: $e002 ROM: disabled. Range: $d000 - $dfff. +Op-code execute history: disabled. ------------------------------------+---------------------------------------- C - continue, S - step | A - set address for next step G - go/cont. from new address | N - go number of steps, P - IRQ @@ -103,11 +107,38 @@ ROM: disabled. Range: $d000 - $dfff. 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 + L - load memory image | O - display op-code exec. history D - disassemble code in memory | Q - quit, 0 - reset, H - help - V - toggle graphics emulation | + V - toggle graphics emulation | U - enable/disable exec. history + Z - enable/disable debug traces | 1 - enable/disable perf. stats + 2 - display debug traces | ? - show this menu ------------------------------------+---------------------------------------- -> _ +> + + Upper part of the screen consists of current CPU registers status. + If performance statistics are enabled, they are right above the CPU + registers. + PC shows address of currently executed op-code, while to the right of status + flags below PC is a disassembled (now empty, showing just the ':' signs) + instructions section. It shows 2 lines, 1-st line is the previously executed + instruction while the 2-nd line shows current instruction. Instructions are + shown in binary (hex) and symbolic (disassembled mnemonics, hex arguments) + format. + Accumulator and index registers X and Y values are also shown in the CPU + registers section. + Stack pointer and its contents are displayed below the CPU registers. + The contents of the Processor registers, disassembled executed instructions + and stack pointer and contents are updated dymanically during the multi-step + program debugging. + + Below the stack data we have current status of various flags and parameters + that don't belong to emulated CPU. + + Next goes the full list of available commands with short descriptions. + The menu of commands is not always displayed. It is usually skipped when + there were no screen actions that could potentially obstruct that menu. + The menu of commands can be always recalled to the screen by issuing + command '?' after the command prompt '>' and pressing ENTER. 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 @@ -116,9 +147,9 @@ ROM: disabled. Range: $d000 - $dfff. 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: + A quick overview of some of the commands available in Debug Console: - * Code execution and debugging commands. + 2.1. Code execution and debugging aid commands. C - Continue @@ -126,11 +157,12 @@ ROM: disabled. Range: $d000 - $dfff. S - Step - Executes single opcode at current address. + Executes single op-code at current address. The address for the next + step can be changed with command 'A'. A - Set address for next step - Sets current address to one provided is argument (or prompts for one). + Sets current address to one provided as argument (or prompts for one). G - Execute (continue) from address. @@ -138,6 +170,10 @@ ROM: disabled. Range: $d000 - $dfff. N - Execute # of steps. + Useful when we want to skip ahead (e.g.: in a loop) and execute multiple + op-codes before next stop. Look also at commands 'F' and 'J' for + features and parameters associated with it. + X - Execute from address. F - Toggle registers animation. @@ -159,7 +195,7 @@ ROM: disabled. Range: $d000 - $dfff. Displays current status of CPU registers, flags and stack. - O - Display op-codes history. + O - Display op-codes execute history. Show the history of last executed op-codes/instructions, full with disassembled mnemonic and argument. @@ -181,7 +217,47 @@ ROM: disabled. Range: $d000 - $dfff. 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. + Z - Toggle enable/disable debug traces. + + Emulator may produce debug messages that can be helpful when + troubleshooting issues with 6502 code. Since the immediate output + to the same console where emulated program is connected is in many + cases undesired, the debug messages are remembered in an internal + log. The log holds last 200 messages. If enabled, this log can be + recalled with command '2'. When the log is disabled and then enabled + again, the old log messages will be deleted. + + 2 - Display debug traces. + + Displays debug traces log (200 maximum) in 20 messages long pages. + + U - Toggle enable/disable op-codes execute history. + + See also option 'O'. + This debuggin aid is not needed during normal emulator run, therefore + option to disable this feature was added, mainly because it affects + performance. But in debugging mode, when we often trace code step by + step, the performance compromise may be justified as a trade off for + getting more internal data from the system. + + 1 - Toggle enable/disable emulation performance statistics. + + MKCpu class includes code to measure the emulation speed. + The speed is measured as a % of model 1 MHz MOS-6502. Number of executed + cycles is divided by number of microseconds that passed and multiplied + by 100 to get this figure. + Performance is only measured in the execute/run modes. + It doesn't make much sense in debug/step by step modes, therefore even + when the feature is enabled, the stats are not calculated in these modes + of operation. + Emulation performance feature is useful mainly during the development + cycle of the emulator and since this is a programming framework for + buiding your own virtual machines/CPU emulators, then it is included. + However as with any debugging aid, it affects performance, therefore + option was added to disable it when we just want to run the 6502 code + and enjoy it at maximum possible speed. + + 2.2. Memory access commands. M - Dump memory. @@ -191,7 +267,7 @@ ROM: disabled. Range: $d000 - $dfff. Writes provided values to memory starting at specified address. - * I/O devices commands. + 2.3. I/O devices commands. I - Toggle char I/O emulation. @@ -251,6 +327,76 @@ ROM: disabled. Range: $d000 - $dfff. NOTE: All addresses and memory values are to be entered in hexadecimal format. + 2.4. Typical debug session. + + Your typical debug session, after starting VM65 is: + + * Load the memory image with command 'L' (if not already loaded from + command line). E.g.: + + L A 6502_func_test.bin + + * Set the program start address with command 'A' (if not already set + in the memory image definition file). E.g.: + + A 0400 + + * Enable/disable various debug and I/O facilities as required by the + loaded 6502 code. E.g.: to enable dymanic processor registers updates + and show the executed code dynamically while executing multiple steps + with command 'N', you need to issue command 'F' to enable registers + animation and optionally 'J' to change the speed (delay) of the + step-by-step animation: + + F + J 100 + N 1000 + + OR if command 'S' is used to manually step through 6502 instructions, + the animation of the CPU registers doesn't have top be enabled. + The registers values and disassembled instructions will be refreshed + after each step on the screen. + Each step is considered a single instructions (not a clock cycle). + If user wants to skip several (thousands perhaps) instructions, then + registers animation (command 'F') should be disabled, which will + make the steps to proceed much quicker. E.g.: assuming that currently + the animation is enabled, and user wants to skip 5000 instructions + quickly without looking at the changing registers and disassembled + instructions, user would issue commands: + + F + N 5000 + + At any moment during multi-step execution (command 'N'), user can + interrupt before all the steps are concluded with CTRL-C and then + lool-up/alter memory content (commands 'M', 'W'), change the address + of the next executed instruction (command 'A') etc. and then continue + debugging with 'S', 'N', 'X', 'C', '0' or 'P' commands. + + User can enable the history of executed op-codes with command 'U' and + display the history of the last 20 op-codes and CPU registers values + in the last 20 steps. E.g.: + + L A 6502_func_test.bin + A 0400 + U + N 5 + O + + Output from 'O': + + > o + PC : INSTR ACC | X | Y | PS | SP + ------------------------------------+-----+-----+-----+----- + $0400: CLD $00 | $00 | $00 | $20 | $ff + $0401: LDX #$FF $00 | $ff | $00 | $a0 | $ff + $0403: TXS $00 | $ff | $00 | $a0 | $ff + $0404: LDA #$00 $00 | $ff | $00 | $22 | $ff + $0406: STA $0200 $00 | $ff | $00 | $22 | $ff + + Type '?' and press [ENTER] for Menu ... + + 3. Implementing the Virtual Machine. The Virtual Machine (or Emulator) is implemented by the means of multiple @@ -565,3 +711,58 @@ ROM: disabled. Range: $d000 - $dfff. 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(). + +5. Debug traces. + + VMachine class implements debug messages queue which can be used during + development cycle. + + void EnableDebugTrace(); + void DisableDebugTrace(); + bool IsDebugTraceActive(); + queue GetDebugTraces(); + void AddDebugTrace(string msg); + + This is a FIFO queue and it is maintained to not exceed the length as + defined: + + #define DBG_TRACE_SIZE 200 // maximum size of debug messages queue + + Therefore the next message added to queue that overfills the queue causes + the earliest added message to be removed. + Shortly put - the last 200 added messages only are remembered internally + in VMachine object and can be recalled with GetDebugTraces() method like + shown in this example: + + VMachine *pvm; + [...] + queue dbgtrc(pvm->GetDebugTraces()); + while (dbgtrc.size()) { + cout << dbgtrc.front() << endl; + dbgtrc.pop(); + } + + Good example of AddDebugTrace() use is EnableROM() method: + + void VMachine::EnableROM(unsigned short start, unsigned short end) + { + mpRAM->EnableROM(start, end); + if (mDebugTraceActive) { + stringstream sssa, ssea; + string msg, startaddr, endaddr; + sssa << start; + sssa >> startaddr; + ssea << end; + ssea >> endaddr; + msg = "ROM ENABLED, Start: " + startaddr + ", End: " + endaddr + "."; + AddDebugTrace(msg); + } + } + + NOTE: The AddDebugTrace method checks mDebugTraceActive flag internally, + however for performance's sake we also check it in the higher level + code especially if there are intermediate objects to be created + to produce the debug string. This way we don't execute code related + to creation of debug message unnecessarily if the debugging messages + are disabled. + diff --git a/ReadMe.txt b/ReadMe.txt index 48da66e..80e07e8 100644 --- a/ReadMe.txt +++ b/ReadMe.txt @@ -21,13 +21,15 @@ Graphics display emulation requires SDL2. Makefile are included to build under Windows 32/64 (mingw compiler required) and under Linux Ubuntu or Ubuntu based distro. SDL2 library must be on your execution path in order to run program. +E.g.: +set PATH=C:\src\SDL\lib\x64;%PATH% 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. +* Set environment variable SDLDIR. (E.g.: set SDLDIR=C:\src\SDL) * Run: makeming.bat To build under Linux: @@ -595,6 +597,12 @@ D - diassemble code in memory U - enable/disable exec. history Toggle enable/disable of op-codes execute history. Disabling this feature improves performance. +Z - enable/disable debug traces + Toggle enable/disable of debug traces. +2 - display debug traces + Display recent debug traces. +1 - enable/disable performance stats + Toggle enable/disable emulation speed measurement. NOTE: 1. If no arguments provided, each command will prompt user to enter @@ -603,7 +611,7 @@ NOTE: by pressing CTRL-C or CTRL-Pause/Break, which will generate a "Operator Interrupt". However in the character input mode use CTRL-Y combination or CTRL-Break (DOS), CTRL-C (Linux). - You may need to press ENTER after that in input mode (DOS). + You may need to press ENTER after that in input mode (DOS). 7. Command line usage. @@ -770,7 +778,7 @@ With all peripherals disabled and op-code history enabled we are down to 411 % on PC1 and 312 % on PC2. Enabling and adding the emulated memory mapped devices to the pool may cause -the emulation speed to drop as well. Hovever even with currently implemented +the emulation speed to drop as well. However even with currently implemented peripherals (char I/O, graphics raster device) enabled and actively used and op-codes execute history enabled the performance is still well above 300 % on both PC1 and on PC2 (* see annotations for PC configurations/specs). diff --git a/VMachine.cpp b/VMachine.cpp index 592b734..fcdef4a 100644 --- a/VMachine.cpp +++ b/VMachine.cpp @@ -1,14 +1,52 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: VMachine.cpp + * + * Purpose: Implementation of VMachine class. + * The VMachine class implements the Virtual Machine + * in its entirety. It creates all the objects that + * emulate the component's of the whole system and + * implements the methods to execute the code on the + * emulated platform. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include #include +#include #include #include "system.h" #include "VMachine.h" #include "MKGenException.h" +/* #if defined(WINDOWS) #include #endif +*/ using namespace std; @@ -62,10 +100,11 @@ VMachine::VMachine(string romfname, string ramfname) */ VMachine::~VMachine() { - delete mpDisp; + //delete mpDisp; delete mpCPU; delete mpROM; delete mpRAM; + delete mpConIO; } /* @@ -82,7 +121,6 @@ void VMachine::InitVM() mpRAM = new Memory(); mPerfStats.cycles = 0; - mPerfStats.micro_secs = 0; mPerfStats.perf_onemhz = 0; mPerfStats.prev_cycles = 0; mPerfStats.prev_usec = 0; @@ -93,6 +131,8 @@ void VMachine::InitVM() mCharIOAddr = CHARIO_ADDR; mCharIOActive = mCharIO = false; mGraphDispActive = false; + mPerfStatsActive = false; + mDebugTraceActive = false; if (NULL == mpRAM) { throw MKGenException("Unable to initialize VM (RAM)."); } @@ -105,14 +145,18 @@ void VMachine::InitVM() if (NULL == mpCPU) { throw MKGenException("Unable to initialize VM (CPU)."); } + /* mpDisp = new Display(); if (NULL == mpDisp) { throw MKGenException("Unable to initialize VM (Display)."); - } + } */ + mpConIO = new ConsoleIO(); + if (NULL == mpConIO) { + throw MKGenException("Unable to initialize VM (ConsoleIO)"); + } + mBeginTime = high_resolution_clock::now(); } -#if defined(WINDOWS) - /* *-------------------------------------------------------------------- * Method: ClearScreen() @@ -124,39 +168,7 @@ void VMachine::InitVM() */ void VMachine::ClearScreen() { - HANDLE hStdOut; - CONSOLE_SCREEN_BUFFER_INFO csbi; - DWORD count; - DWORD cellCount; - COORD homeCoords = { 0, 0 }; - - hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); - if (hStdOut == INVALID_HANDLE_VALUE) return; - - /* Get the number of cells in the current buffer */ - if (!GetConsoleScreenBufferInfo( hStdOut, &csbi )) return; - cellCount = csbi.dwSize.X *csbi.dwSize.Y; - - /* Fill the entire buffer with spaces */ - if (!FillConsoleOutputCharacter( - hStdOut, - (TCHAR) ' ', - cellCount, - homeCoords, - &count - )) return; - - /* Fill the entire buffer with the current colors and attributes */ - if (!FillConsoleOutputAttribute( - hStdOut, - csbi.wAttributes, - cellCount, - homeCoords, - &count - )) return; - - /* Move the cursor home */ - SetConsoleCursorPosition( hStdOut, homeCoords ); + mpConIO->ClearScreen(); } /* @@ -170,50 +182,9 @@ void VMachine::ClearScreen() */ void VMachine::ScrHome() { - HANDLE hStdOut; - COORD homeCoords = { 0, 0 }; - - hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); - if (hStdOut == INVALID_HANDLE_VALUE) return; - - /* Move the cursor home */ - SetConsoleCursorPosition( hStdOut, homeCoords ); + mpConIO->ScrHome(); } -#endif - -#if defined(LINUX) - -/* - *-------------------------------------------------------------------- - * Method: ClearScreen() - * Purpose: Clear the working are of the VM - Linux. - * This is not a part of virtual display emulation. - * Arguments: n/a - * Returns: n/a - *-------------------------------------------------------------------- - */ -void VMachine::ClearScreen() -{ - system("clear"); -} - -/* - *-------------------------------------------------------------------- - * Method: ScrHome() - * Purpose: Bring the console cursor to home position - Linux. - * This is not a part of virtual display emulation. - * Arguments: n/a - * Returns: n/a - *-------------------------------------------------------------------- - */ -void VMachine::ScrHome() -{ - cout << "\033[1;1H"; -} - -#endif - /* *-------------------------------------------------------------------- * Method: ShowDisp() @@ -224,7 +195,7 @@ void VMachine::ScrHome() */ void VMachine::ShowDisp() { - if (mCharIOActive) { + if (mCharIOActive && NULL != mpDisp) { ScrHome(); mpDisp->ShowScr(); } @@ -233,36 +204,67 @@ void VMachine::ShowDisp() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: CalcCurrPerf() + * Purpose: Calculate CPU emulation performance based on 1 MHz model + * CPU. + * Arguments: n/a + * Returns: Integer, the % of speed as compared to 1 MHz CPU. *-------------------------------------------------------------------- */ int VMachine::CalcCurrPerf() { - auto lap = high_resolution_clock::now(); - auto beg = mPerfStats.begin_time; - mPerfStats.micro_secs = duration_cast(lap-beg).count(); + if (!mPerfStatsActive) return 0; - if (mPerfStats.micro_secs > 0) { - int currperf = (int) - (((double)mPerfStats.cycles / (double)mPerfStats.micro_secs) * 100.0); + auto lap = high_resolution_clock::now(); + long usec = duration_cast(lap-mPerfStats.begin_time).count(); + + if (usec > 0) { + int currperf = (int)(((double)mPerfStats.cycles / (double)usec) * 100.0); if (mPerfStats.perf_onemhz == 0) mPerfStats.perf_onemhz = currperf; else mPerfStats.perf_onemhz = (mPerfStats.perf_onemhz + currperf) / 2; mPerfStats.prev_cycles = mPerfStats.cycles; - mPerfStats.prev_usec = mPerfStats.micro_secs; + mPerfStats.prev_usec = usec; mPerfStats.cycles = 0; - mPerfStats.micro_secs = 0; - mPerfStats.begin_time = high_resolution_clock::now(); + mPerfStats.begin_time = lap; + if (mDebugTraceActive) { // prepare and log some debug traces + stringstream sscp, ssap; + string msg, avgprf, curprf; + ssap << mPerfStats.perf_onemhz; + ssap >> avgprf; + sscp << currperf; + sscp >> curprf; + msg = "Perf. measured. Curr.: " + curprf + " %, Avg.: " + avgprf + " %"; + AddDebugTrace(msg); + } } return mPerfStats.perf_onemhz; } +/* + *-------------------------------------------------------------------- + * Method: PERFSTAT_LAP (macro) + * Purpose: Calculate emulation performace at pre-defined interval + * of real time and clock ticks. + * Arguments: cycles - long : number of clock ticks executed so far + * begin - time_point : the moment + * when time measurement started + * Returns: n/a + * Remarks: Call inside emulation execute loop. + *-------------------------------------------------------------------- + */ +#define PERFSTAT_LAP(cycles,begin) \ +{ \ + if (mPerfStatsActive && cycles%PERFSTAT_CYCLES == 0) { \ + long usec = duration_cast \ + (high_resolution_clock::now()-begin).count(); \ + if (usec >= PERFSTAT_INTERVAL) CalcCurrPerf(); \ + } \ +} + /* *-------------------------------------------------------------------- * Method: Run() @@ -275,18 +277,18 @@ Regs *VMachine::Run() { Regs *cpureg = NULL; + AddDebugTrace("Running code at: $" + Addr2HexStr(mRunAddr)); mOpInterrupt = false; ClearScreen(); ShowDisp(); mPerfStats.cycles = 0; - mPerfStats.micro_secs = 0; mPerfStats.begin_time = high_resolution_clock::now(); while (true) { mPerfStats.cycles++; cpureg = Step(); if (cpureg->CyclesLeft == 0 && mCharIO) ShowDisp(); if (cpureg->SoftIrq || mOpInterrupt) break; - //if (mPerfStats.cycles == PERFSTAT_INTERVAL) CalcCurrPerf(); + PERFSTAT_LAP(mPerfStats.cycles,mPerfStats.begin_time); } CalcCurrPerf(); @@ -323,21 +325,17 @@ Regs *VMachine::Exec() { Regs *cpureg = NULL; + AddDebugTrace("Executing code at: $" + Addr2HexStr(mRunAddr)); mOpInterrupt = false; ClearScreen(); ShowDisp(); mPerfStats.cycles = 0; - mPerfStats.micro_secs = 0; mPerfStats.begin_time = high_resolution_clock::now(); while (true) { mPerfStats.cycles++; cpureg = Step(); - if (cpureg->CyclesLeft == 0 && mCharIO) { - cout << mpDisp->GetLastChar(); - cout << flush; - } if (cpureg->LastRTS || mOpInterrupt) break; - //if (mPerfStats.cycles == PERFSTAT_INTERVAL) CalcCurrPerf(); + PERFSTAT_LAP(mPerfStats.cycles,mPerfStats.begin_time); } CalcCurrPerf(); @@ -383,28 +381,13 @@ Regs *VMachine::Exec(unsigned short addr) */ Regs *VMachine::Step() { - unsigned short addr = mRunAddr; Regs *cpureg = NULL; - cpureg = mpCPU->ExecOpcode(addr); + cpureg = mpCPU->ExecOpcode(mRunAddr); if (mGraphDispActive && cpureg->CyclesLeft == 0) { 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) { - mpDisp->PutChar(c); - mCharIO = true; - } - } - } + mRunAddr = cpureg->PtrAddr; return cpureg; } @@ -458,6 +441,15 @@ int VMachine::LoadRAM(string ramfname) break; } mError = err; + if (mDebugTraceActive && err) { + stringstream sserr; + string msg, strerr; + sserr << err; + sserr >> strerr; + msg = "ERROR: LoadRAM, error code: " + strerr; + AddDebugTrace(msg); + } + return err; } @@ -560,6 +552,8 @@ bool VMachine::HasHdrData(FILE *fp) } ret = (0 == strncmp(buf, HDRMAGICKEY, l)); + AddDebugTrace(((ret) ? "HasHdrData: YES" : "HasHdrData: NO")); + return ret; } @@ -590,6 +584,8 @@ bool VMachine::HasOldHdrData(FILE *fp) } ret = (0 == strncmp(buf, HDRMAGICKEY_OLD, l)); + AddDebugTrace(((ret) ? "HasOldHdrData: YES" : "HasOldHdrData: NO")); + return ret; } @@ -655,20 +651,26 @@ bool VMachine::LoadHdrData(FILE *fp) switch (n) { case 1: mRunAddr = l + 256 * val; + ADD_DBG_LDMEMPARHEX("LoadHdrData : mRunAddr",mRunAddr); break; case 3: mCharIOAddr = l + 256 * val; + ADD_DBG_LDMEMPARHEX("LoadHdrData : mCharIOAddr",mCharIOAddr); break; case 5: rb = l + 256 * val; break; case 7: re = l + 256 * val; break; case 8: mCharIOActive = (val != 0); + ADD_DBG_LDMEMPARVAL("LoadHdrData : mCharIOActive",mCharIOActive); break; case 9: if (val != 0) { mpRAM->EnableROM(rb, re); } else { mpRAM->SetROM(rb, re); } + ADD_DBG_LDMEMPARHEX("LoadHdrData : ROM begin",rb); + ADD_DBG_LDMEMPARHEX("LoadHdrData : ROM end",re); + ADD_DBG_LDMEMPARVAL("LoadHdrData : ROM enable",((val!=0)?1:0)); break; case 10: r.Acc = val; break; @@ -682,9 +684,11 @@ bool VMachine::LoadHdrData(FILE *fp) ret = true; break; case 15: mGraphDispActive = (val != 0); + ADD_DBG_LDMEMPARVAL("LoadHdrData : mGraphDispActive",mGraphDispActive); break; case 17: if (mGraphDispActive) SetGraphDisp(l + 256 * val); else DisableGraphDisp(); + ADD_DBG_LDMEMPARHEX("LoadHdrData : Graph. Disp. addr",(l + 256 * val)); break; default: break; } @@ -699,9 +703,6 @@ 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() @@ -788,6 +789,14 @@ int VMachine::SaveSnapshot(string fname) fclose(fp); } if (0 != ret) mError = VMERR_SAVE_SNAPSHOT; + if (mDebugTraceActive && ret) { + stringstream sserr; + string msg, strerr; + sserr << ret; + sserr >> strerr; + msg = "ERROR: SaveSnapshot, error code: " + strerr; + AddDebugTrace(msg); + } return ret; } @@ -829,6 +838,7 @@ int VMachine::LoadRAMBin(string ramfname) Memory *pm = mpRAM; int ret = MEMIMGERR_RAMBIN_OPEN; + AddDebugTrace("LoadRAMBin : " + ramfname); mOldStyleHeader = false; if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) { if (HasHdrData(fp) || (mOldStyleHeader = HasOldHdrData(fp))) { @@ -1074,7 +1084,11 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) bool romendset = false, engraph = false, graphset = false; Memory *pm = pmem; int err = MEMIMGERR_OK; - + + if (mDebugTraceActive) { + string msg = "LoadMEM: " + memfname; + AddDebugTrace(msg); + } if ((fp = fopen(memfname.c_str(), "r")) != NULL) { DisableROM(); DisableCharIO(); @@ -1100,8 +1114,10 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: Run address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: Run address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: Run address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("ADDR",addr); continue; } // change address counter @@ -1115,6 +1131,7 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { addr = (unsigned short) atoi(line); } + ADD_DBG_LDMEMPARHEX("ORG",addr); continue; } // define I/O emulation address (once) @@ -1133,8 +1150,10 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: I/O address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: I/O address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: I/O address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("IOADDR",mCharIOAddr); continue; } // define generic graphics display device base address (once) @@ -1153,23 +1172,28 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: graphics device base address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: graphics device base address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: graphics device base address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("GRAPHADDR",graphaddr); continue; } // enable character I/O emulation if (0 == strncmp(line, "ENIO", 4)) { enio = true; + ADD_DBG_LDMEMPARVAL("ENIO",enio); continue; } // enable generic graphics display emulation if (0 == strncmp(line, "ENGRAPH", 7)) { engraph = true; + ADD_DBG_LDMEMPARVAL("ENIO",engraph); continue; } // enable ROM emulation if (0 == strncmp(line, "ENROM", 5)) { enrom = true; + ADD_DBG_LDMEMPARVAL("ENROM",enrom); continue; } // auto execute from address @@ -1189,13 +1213,16 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: auto-exec address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: auto-exec address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: auto-exec address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("EXEC",mRunAddr); continue; } // auto reset if (0 == strncmp(line, "RESET", 5)) { mAutoReset = true; + ADD_DBG_LDMEMPARVAL("RESET",mAutoReset); continue; } // define ROM begin address @@ -1214,8 +1241,10 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: ROM-begin address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: ROM-begin address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: ROM-begin address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("ROMBEGIN",rombegin); continue; } // define ROM end address @@ -1234,8 +1263,10 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_IGNPROCWRN; errc++; - cout << "LINE #" << dec << lc << " WARNING: ROM-end address was already set. Ignoring..." << endl; + ADD_DBG_LOADMEM(lc," WARNING: ROM-end address was already set. Ignoring..."); + //cout << "LINE #" << dec << lc << " WARNING: ROM-end address was already set. Ignoring..." << endl; } + ADD_DBG_LDMEMPARHEX("ROMEND",romend); continue; } if (';' == *line) continue; // skip comment lines @@ -1269,7 +1300,8 @@ int VMachine::LoadMEM(string memfname, Memory *pmem) } else { err = MEMIMGERR_VM65_OPEN; - cout << "WARNING: Unable to open memory definition file: " << memfname << endl; + cout << "WARNING: Unable to open memory definition file: "; + cout << memfname << endl; errc++; } if (errc) { @@ -1341,7 +1373,13 @@ void VMachine::SetCharIO(unsigned short addr, bool echo) mCharIOAddr = addr; mCharIOActive = true; mpRAM->SetCharIO(addr, echo); - mpDisp->ClrScr(); + mpDisp = mpRAM->GetMemMapDevPtr()->GetDispPtr(); + if (mDebugTraceActive) { + string msg; + msg = "Char I/O activated at: $" + Addr2HexStr(addr) + ", echo: " + + ((echo) ? "ON" : "OFF"); + AddDebugTrace(msg); + } } /* @@ -1356,6 +1394,8 @@ void VMachine::DisableCharIO() { mCharIOActive = false; mpRAM->DisableCharIO(); + AddDebugTrace("Char I/O DISABLED"); + //mpRAM->GetMemMapDevPtr()->DeactivateCharIO(); } /* @@ -1386,16 +1426,21 @@ bool VMachine::GetCharIOActive() /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: SetGraphDisp() + * Purpose: Set graphics device address and enable. + * Arguments: addr - unsigned short : device base address. + * Returns: n/a *-------------------------------------------------------------------- */ void VMachine::SetGraphDisp(unsigned short addr) { mGraphDispActive = true; mpRAM->SetGraphDisp(addr); + if (mDebugTraceActive) { + string msg; + msg = "Graphics Device set at: $" + Addr2HexStr(addr) + "."; + AddDebugTrace(msg); + } } /* @@ -1410,6 +1455,7 @@ void VMachine::DisableGraphDisp() { mGraphDispActive = false; mpRAM->DisableGraphDisp(); + AddDebugTrace("Graphics Device DISABLED."); } /* @@ -1448,7 +1494,7 @@ bool VMachine::GetGraphDispActive() */ void VMachine::ShowIO() { - if (mCharIOActive) + if (mCharIOActive && NULL != mpDisp) mpDisp->ShowScr(); } @@ -1489,6 +1535,7 @@ bool VMachine::IsAutoReset() void VMachine::EnableROM() { mpRAM->EnableROM(); + AddDebugTrace("ROM ENABLED."); } /* @@ -1502,6 +1549,7 @@ void VMachine::EnableROM() void VMachine::DisableROM() { mpRAM->DisableROM(); + AddDebugTrace("ROM DISABLED."); } /* @@ -1515,19 +1563,32 @@ void VMachine::DisableROM() void VMachine::SetROM(unsigned short start, unsigned short end) { mpRAM->SetROM(start, end); + if (mDebugTraceActive) { + string msg; + msg = "ROM SET, Start: $" + Addr2HexStr(start) + ", End: $"; + msg += Addr2HexStr(end) + "."; + AddDebugTrace(msg); + } } /* *-------------------------------------------------------------------- - * Method: - * Purpose: - * Arguments: - * Returns: + * Method: EnableROM() + * Purpose: Sets and enables Read Only Memory range. + * Arguments: start, end - unsigned short : start and end addresses + * of the ROM. + * Returns: n/a *-------------------------------------------------------------------- */ void VMachine::EnableROM(unsigned short start, unsigned short end) { mpRAM->EnableROM(start, end); + if (mDebugTraceActive) { + string msg; + msg = "ROM ENABLED, Start: $" + Addr2HexStr(start) + ", End: $"; + msg += Addr2HexStr(end) + "."; + AddDebugTrace(msg); + } } /* @@ -1650,6 +1711,7 @@ void VMachine::Reset() mpCPU->Reset(); Exec(mpCPU->GetRegs()->PtrAddr); mpCPU->mExitAtLastRTS = true; + AddDebugTrace("*** CPU RESET ***"); } /* @@ -1692,6 +1754,13 @@ int VMachine::GetLastError() void VMachine::EnableExecHistory(bool enexehist) { mpCPU->EnableExecHistory(enexehist); + if (mDebugTraceActive) { + string msg; + msg = "The op-code execute history " + + (string)((enexehist) ? "ENABLED" : "DISABLED"); + msg += "."; + AddDebugTrace(msg); + } } /* @@ -1707,4 +1776,173 @@ bool VMachine::IsExecHistoryActive() return mpCPU->IsExecHistoryEnabled(); } +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void VMachine::EnableDebugTrace() +{ + if (!mDebugTraceActive) { + mDebugTraceActive = true; + while (!mDebugTraces.empty()) mDebugTraces.pop(); + } +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void VMachine::DisableDebugTrace() +{ + mDebugTraceActive = false; +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +bool VMachine::IsDebugTraceActive() +{ + return mDebugTraceActive; +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +queue VMachine::GetDebugTraces() +{ + return mDebugTraces; +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void VMachine::EnablePerfStats() +{ + if (!mPerfStatsActive) { + mPerfStatsActive = true; + mPerfStats.cycles = 0; + mPerfStats.prev_cycles = 0; + mPerfStats.prev_usec = 0; + mPerfStats.perf_onemhz = 0; + AddDebugTrace("Performance stats ENABLED."); + } +} +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void VMachine::DisablePerfStats() +{ + mPerfStatsActive = false; + AddDebugTrace("Performance stats DISABLED."); +} + +/* + *-------------------------------------------------------------------- + * Method: + * Purpose: + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +bool VMachine::IsPerfStatsActive() +{ + return mPerfStatsActive; +} + +/* + *-------------------------------------------------------------------- + * Method: AddDebugTrace() + * Purpose: Add string to the debug messages queue. String is + * prefixed with time stamp (number of microseconds since + * start of program. Queue is maintained to not exceed + * DBG_TRACE_SIZE. If the size is exceeded with the next + * added debug message, the first one in the queue is + * deleted (FIFO). + * Arguments: msg - string : debug message. + * Returns: + *-------------------------------------------------------------------- + */ +void VMachine::AddDebugTrace(string msg) +{ + if (mDebugTraceActive) { + stringstream ss; + string mmsg; + + // add timestamp in front (micro seconds) + auto lap = high_resolution_clock::now(); + unsigned long usec = duration_cast(lap-mBeginTime).count(); + ss << usec; + ss >> mmsg; + mmsg += " : " + msg; + mDebugTraces.push(mmsg); + while (mDebugTraces.size() > DBG_TRACE_SIZE) mDebugTraces.pop(); + } +} + +/* + *-------------------------------------------------------------------- + * Method: Addr2HexStr() + * Purpose: Convert 16-bit address to a hex notation string. + * Arguments: addr - 16-bit unsigned + * Returns: string + *-------------------------------------------------------------------- + */ +string VMachine::Addr2HexStr(unsigned short addr) +{ + stringstream ss; + string ret; + + ss << hex << addr; + ss >> ret; + + return ret; +} + +/* + *-------------------------------------------------------------------- + * Method: Addr2DecStr() + * Purpose: Convert 16-bit address to a decimal notation string. + * Arguments: addr - 16-bit unsigned + * Returns: string + *-------------------------------------------------------------------- + */ +string VMachine::Addr2DecStr(unsigned short addr) +{ + stringstream ss; + string ret; + + ss << addr; + ss >> ret; + + return ret; +} + } // namespace MKBasic diff --git a/VMachine.h b/VMachine.h index 8feeb22..02202a1 100644 --- a/VMachine.h +++ b/VMachine.h @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: VMachine.h + * + * Purpose: Prototype of VMachine class and all supporting data + * structures, enumerations, constants and macros. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #ifndef VMACHINE_H #define VMACHINE_H @@ -8,6 +40,7 @@ #include "MKCpu.h" #include "Memory.h" #include "Display.h" +#include "ConsoleIO.h" //#define WINDOWS 1 #if defined (WINDOWS) @@ -21,11 +54,46 @@ #define HDRDATALEN 128 #define HDRDATALEN_OLD 15 #define HEXEOF ":00000001FF" -//#define PERFSTAT_INTERVAL 30000000 +// take emulation speed measurement every 2 minutes (120,000,000 usec) +#define PERFSTAT_INTERVAL 120000000 +// but not more often than 30,000,000 clock ticks +#define PERFSTAT_CYCLES 30000000 +#define DBG_TRACE_SIZE 200 // maximum size of debug messages queue using namespace std; using namespace chrono; +// Macros for debug log. +#define ADD_DBG_LOADMEM(lc,txt) \ + if (mDebugTraceActive) \ + { \ + stringstream ss; \ + string msg, s; \ + ss << lc; \ + ss >> s; \ + msg = "LINE #" + s + txt; \ + AddDebugTrace(msg); \ + } + +#define ADD_DBG_LDMEMPARHEX(name,value) \ + if (mDebugTraceActive) \ + { \ + string msg; \ + msg = (string)name + " = $" + Addr2HexStr(value); \ + AddDebugTrace(msg); \ + } + +#define ADD_DBG_LDMEMPARVAL(name,value) \ + if (mDebugTraceActive) \ + { \ + string msg; \ + msg = (string)name + " = " + Addr2DecStr(value); \ + AddDebugTrace(msg); \ + } + +// 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--;} + namespace MKBasic { // Types of memory image definition file. @@ -70,7 +138,6 @@ struct PerfStats { time_point begin_time; // the moment of time count start long cycles; // performance stats - long micro_secs; // performance stats long prev_cycles; // previously measured stats long prev_usec; // previously measured stats int perf_onemhz; // avg. % perf. based on 1MHz CPU. @@ -131,16 +198,24 @@ class VMachine // cycles per second (1 MHz CPU). void EnableExecHistory(bool enexehist); bool IsExecHistoryActive(); + void EnableDebugTrace(); + void DisableDebugTrace(); + bool IsDebugTraceActive(); + void EnablePerfStats(); + void DisablePerfStats(); + bool IsPerfStatsActive(); + queue GetDebugTraces(); protected: private: - MKCpu *mpCPU; - Memory *mpROM; - Memory *mpRAM; - Display *mpDisp; + MKCpu *mpCPU; // object maintained locally + Memory *mpROM; // object maintained locally + Memory *mpRAM; // object maintained locally + Display *mpDisp; // just a pointer + ConsoleIO *mpConIO; // object maintained locally unsigned short mRunAddr; unsigned short mCharIOAddr; bool mCharIOActive; @@ -152,6 +227,10 @@ class VMachine bool mGraphDispActive; bool mOldStyleHeader; PerfStats mPerfStats; + queue mDebugTraces; + bool mPerfStatsActive; + bool mDebugTraceActive; + time_point mBeginTime; int LoadMEM(string memfname, Memory *pmem); void ShowDisp(); @@ -161,6 +240,9 @@ class VMachine void SaveHdrData(FILE *fp); eMemoryImageTypes GetMemoryImageType(string ramfname); int CalcCurrPerf(); + void AddDebugTrace(string msg); + string Addr2HexStr(unsigned short addr); + string Addr2DecStr(unsigned short addr); }; } // namespace MKBasic diff --git a/grdevdemo.bas b/grdevdemo.bas new file mode 100644 index 0000000..b020285 --- /dev/null +++ b/grdevdemo.bas @@ -0,0 +1,63 @@ +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 +120 PRINT "... HIT [SPACE] TO END ..." +125 GET K$:IF K$=" " THEN END +130 FOR I=1 TO 2000:NEXT I:REM SHORT PAUSE +140 GOTO 15 +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 diff --git a/main.cpp b/main.cpp index 76d54e1..bc6ecb6 100644 --- a/main.cpp +++ b/main.cpp @@ -1,3 +1,35 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: main.cpp + * + * Purpose: Define User Interface, Debug Console and main loop + * of the app. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and exercise common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #include #include #include @@ -17,9 +49,16 @@ using namespace std; using namespace MKBasic; #define ANIM_DELAY 250 +#define PROMPT_ADDR "Address (0..FFFF): " +#define PROMPT_START_ADDR "Start address (0..FFFF): " +#define PROMPT_RANGE_ADDR "Enter address range (0..0xFFFF).." +#define PROMPT_END_ADDR "End address (0..FFFF): " const bool ClsIfDirty = true; +char diss_buf[DISS_BUF_SIZE]; // last disassembled instruction buffer +char curr_buf[DISS_BUF_SIZE]; // current disassembled instruction buffer + VMachine *pvm = NULL; Regs *preg = NULL; bool ioecho = false, opbrk = false, needhelp = false; @@ -32,6 +71,26 @@ void ShowHelp(); void CmdArgHelp(string prgname); void CopyrightBanner(); +/* + *-------------------------------------------------------------------- + * Method: PressEnter2Cont() + * Purpose: Print a message and wait for ENTER to be pressed. + * Arguments: msg - string : message + * Returns: + *-------------------------------------------------------------------- + */ +void PressEnter2Cont(string msg) +{ + string mesg = msg; + if (0 == msg.length()) mesg = "Press [ENTER]..."; + cout << mesg; + fflush(stdin); + while (true) { + int c = getchar(); + if ('\n' == c || EOF == c) break; + } +} + /* *-------------------------------------------------------------------- * Method: RunSingleInstr() @@ -44,9 +103,14 @@ void CopyrightBanner(); { Regs *ret = NULL; + pvm->Disassemble(addr, diss_buf); + // skip # cycles per op-code specs do { ret = pvm->Step(addr); } while (ret->CyclesLeft > 0); + // and now execute the actual op-code + ret = pvm->Step(addr); + pvm->Disassemble(ret->PtrAddr, curr_buf); return ret; } @@ -64,13 +128,56 @@ void CopyrightBanner(); { Regs *ret = NULL; + pvm->Disassemble(preg->PtrAddr, diss_buf); + // skip # cycles per op-code specs do { ret = pvm->Step(); } while (ret->CyclesLeft > 0); + // and now execute the actual op-code + ret = pvm->Step(); + pvm->Disassemble(ret->PtrAddr, curr_buf); return ret; } +/* + *-------------------------------------------------------------------- + * Method: VMErr + * Purpose: Data structure and macros supporting VM errors + * messages + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ + +#define WARN_UNEXPECTED_EOF "WARNING: Unexpected EOF (image shorter than 64kB)." +#define WARN_NOHDR_BINIMG "WARNING: No header found in binary image." +#define WARN_HDRPRBLM_BINIMG "WARNING: Problem with binary image header." + +struct VMErr { + + int id; + char text1[80]; + char text2[80]; + +} g_vmerrtbl[] = { + + {MEMIMGERR_RAMBIN_EOF, WARN_UNEXPECTED_EOF, ""}, + {MEMIMGERR_RAMBIN_OPEN, "WARNING: Unable to open memory image file.", ""}, + {MEMIMGERR_RAMBIN_HDR, WARN_HDRPRBLM_BINIMG, ""}, + {MEMIMGERR_RAMBIN_NOHDR, WARN_NOHDR_BINIMG, ""}, + {MEMIMGERR_RAMBIN_HDRANDEOF, WARN_HDRPRBLM_BINIMG, WARN_UNEXPECTED_EOF}, + {MEMIMGERR_RAMBIN_NOHDRANDEOF,WARN_NOHDR_BINIMG, WARN_UNEXPECTED_EOF}, + {MEMIMGERR_INTELH_OPEN, "WARNING: Unable to open Intel HEX file.", ""}, + {MEMIMGERR_INTELH_SYNTAX, "ERROR: Syntax error.", ""}, + {MEMIMGERR_INTELH_FMT, "ERROR: Intel HEX format error.", ""}, + {MEMIMGERR_VM65_OPEN, "ERROR: Unable to open memory definition file.", ""}, + {MEMIMGERR_VM65_IGNPROCWRN, "WARNING: There were problems while processing memory definition file.", ""}, + {VMERR_SAVE_SNAPSHOT, "WARNING: There was a problem saving memory snapshot.", ""}, + {-1, "", ""} + +}; + /* *-------------------------------------------------------------------- * Method: PrintVMErr() @@ -81,57 +188,23 @@ void CopyrightBanner(); */ void PrintVMErr(int err) { - bool pressenter = true; - switch (err) { - case MEMIMGERR_RAMBIN_EOF: - cout << "WARNING: Unexpected EOF (image shorter than 64kB)."; - cout << endl; - break; - case MEMIMGERR_RAMBIN_OPEN: - cout << "WARNING: Unable to open memory image file." << endl; - break; - case MEMIMGERR_RAMBIN_HDR: - cout << "WARNING: Problem with binary image header." << endl; - break; - case MEMIMGERR_RAMBIN_NOHDR: - cout << "WARNING: No header found in binary image." << endl; - break; - case MEMIMGERR_RAMBIN_HDRANDEOF: - cout << "WARNING: Problem with binary image header." << endl; - cout << "WARNING: Unexpected EOF (image shorter than 64kB)."; - cout << endl; - break; - case MEMIMGERR_RAMBIN_NOHDRANDEOF: - cout << "WARNING: No header found in binary image." << endl; - cout << "WARNING: Unexpected EOF (image shorter than 64kB)."; - cout << endl; - break; - case MEMIMGERR_INTELH_OPEN: - cout << "WARNING: Unable to open Intel HEX file." << endl; - break; - case MEMIMGERR_INTELH_SYNTAX: - cout << "ERROR: Syntax error." << endl; - break; - case MEMIMGERR_INTELH_FMT: - cout << "ERROR: Intel HEX format error." << endl; + bool pressenter = false; + + for (int i=0; g_vmerrtbl[i].id >= 0; i++) { + if (g_vmerrtbl[i].id == err) { + pressenter = true; + if (strlen(g_vmerrtbl[i].text1)) { + cout << g_vmerrtbl[i].text1 << endl; + } + if (strlen(g_vmerrtbl[i].text2)) { + cout << g_vmerrtbl[i].text2 << endl; + } break; - case MEMIMGERR_VM65_OPEN: - cout << "ERROR: Unable to open memory definition file."; - cout << endl; - break; - case MEMIMGERR_VM65_IGNPROCWRN: - cout << "WARNING: There were problems while processing"; - cout << " memory definition file." << endl; - break; - case VMERR_SAVE_SNAPSHOT: - cout << "WARNING: There was a problem saving memory snapshot."; - cout << endl; - break; - default: pressenter = false; break; - } + } + } + if (pressenter) { - cout << "Press [ENTER]..."; - getchar(); + PressEnter2Cont(""); } } @@ -156,7 +229,7 @@ void trap_signal(int signum) pvm->SetOpInterrupt(true); opbrk = true; } - //exit(signum); + return; } @@ -181,7 +254,7 @@ BOOL CtrlHandler(DWORD fdwCtrlType) switch( fdwCtrlType ) { case CTRL_C_EVENT: - //Beep( 750, 300 ); + if (NULL != pvm && NULL != preg) { pvm->SetOpInterrupt(true); opbrk = true; @@ -190,12 +263,12 @@ BOOL CtrlHandler(DWORD fdwCtrlType) case CTRL_CLOSE_EVENT: - //Beep( 600, 200 ); + cout << "Ctrl-Close event" << endl; return TRUE ; case CTRL_BREAK_EVENT: - //Beep( 900, 200 ); + if (NULL != pvm && NULL != preg) { pvm->SetOpInterrupt(true); opbrk = true; @@ -203,7 +276,7 @@ BOOL CtrlHandler(DWORD fdwCtrlType) return TRUE; case CTRL_LOGOFF_EVENT: - //Beep( 1000, 200 ); + cout << "Ctrl-Logoff event" << endl; return FALSE; @@ -275,18 +348,24 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat) bool ret = false; char sBuf[80] = {0}; - sprintf(sBuf, "| PC: $%04x | Acc: $%02x (" BYTETOBINARYPATTERN ") | X: $%02x | Y: $%02x |", - preg->PtrAddr, preg->Acc, BYTETOBINARY(preg->Acc), preg->IndX, preg->IndY); - cout << "*-------------*-----------------------*----------*----------*" << endl; + sprintf(sBuf, "| PC: $%04x | Acc: $%02x (" BYTETOBINARYPATTERN + ") | X: $%02x | Y: $%02x |", + preg->PtrAddr, preg->Acc, BYTETOBINARY(preg->Acc), + preg->IndX, preg->IndY); + cout << "*-------------*-----------------------*----------*----------*"; + cout << endl; cout << sBuf << endl; - cout << "*-------------*-----------------------*----------*----------*" << endl; - cout << "| NV-BDIZC |" << endl; + cout << "*-------------*-----------------------*----------*----------*"; + cout << endl; + cout << "| NV-BDIZC |"; + cout << " : " << diss_buf << " " << endl; cout << "| " << bitset<8>((int)preg->Flags) << " |"; - cout << " Last instr.: " << preg->LastInstr << " " << endl; + cout << " : " << curr_buf << " " << endl; cout << "*-------------*" << endl; cout << endl; cout << "Stack: $" << hex << (unsigned short)preg->PtrStack << " " << endl; - cout << " \r"; + cout << " "; + cout << " \r"; // display stack contents cout << " ["; int j = 0, stacklines = 1; @@ -310,14 +389,19 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat) // end display stack contents if (showiostat) { - cout << endl << "I/O status: " << (pvm->GetCharIOActive() ? "enabled" : "disabled") << ", "; + cout << endl << "I/O status: "; + cout << (pvm->GetCharIOActive() ? "enabled" : "disabled") << ", "; cout << " at: $" << hex << pvm->GetCharIOAddr() << ", "; cout << " local echo: " << (ioecho ? "ON" : "OFF") << "." << endl; - cout << "Graphics status: " << (pvm->GetGraphDispActive() ? "enabled" : "disabled") << ", "; + cout << "Graphics status: "; + cout << (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 << "Op-code execute history: " << (pvm->IsExecHistoryActive() ? "enabled" : "disabled"); + cout << "ROM: "; + cout << ((pvm->IsROMEnabled()) ? "enabled." : "disabled.") << " "; + cout << "Range: $" << hex << pvm->GetROMBegin() << " - $"; + cout << hex << pvm->GetROMEnd() << "." << endl; + cout << "Op-code execute history: "; + cout << (pvm->IsExecHistoryActive() ? "enabled" : "disabled"); cout << "." << endl; } cout << " \r"; @@ -327,8 +411,8 @@ bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat) /* *-------------------------------------------------------------------- - * Method: - * Purpose: + * Method: ShowMenu() + * Purpose: Print available commands on the console. * Arguments: * Returns: *-------------------------------------------------------------------- @@ -346,6 +430,8 @@ void ShowMenu() cout << " L - load memory image | O - display op-code exec. history" << endl; cout << " D - disassemble code in memory | Q - quit, 0 - reset, H - help" << endl; cout << " V - toggle graphics emulation | U - enable/disable exec. history" << endl; + cout << " Z - enable/disable debug traces | 1 - enable/disable perf. stats" << endl; + cout << " 2 - display debug traces | ? - show this menu" << endl; cout << "------------------------------------+----------------------------------------" << endl; } @@ -377,9 +463,9 @@ void ShowMenu() brk = preg->SoftIrq; \ lrts = preg->LastRTS; \ while(step && nsteps > 1 && !brk && !lrts && !opbrk) { \ + preg = RunSingleCurrInstr(); \ cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct; \ cout << " \r"; \ - preg = RunSingleCurrInstr(); \ if (anim) { \ if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; } \ pvm->ScrHome(); \ @@ -404,13 +490,19 @@ void ShowMenu() */ void ShowSpeedStats() { + if (pvm->IsPerfStatsActive()) { cout << endl; - cout << dec; - cout << "CPU emulation speed stats: " << endl; - cout << "|-> Average speed based on 1MHz CPU: " << pvm->GetPerfStats().perf_onemhz << " %" << endl; - cout << "|-> Last measured # of cycles exec.: " << pvm->GetPerfStats().prev_cycles << endl; - cout << "|-> Last measured time of execution: " << pvm->GetPerfStats().prev_usec << " usec" << endl; - cout << endl; + cout << dec; + cout << "CPU emulation speed stats: " << endl; + cout << "|-> Average speed based on 1MHz CPU: " << pvm->GetPerfStats().perf_onemhz << " %" << endl; + cout << "|-> Last measured # of cycles exec.: " << pvm->GetPerfStats().prev_cycles << endl; + cout << "|-> Last measured time of execution: " << pvm->GetPerfStats().prev_usec << " usec" << endl; + cout << endl; + } else { + cout << endl; + cout << "Emulation performance stats is OFF." << endl; + cout << endl; + } } /* @@ -425,8 +517,10 @@ void ExecHistory() { if (pvm->IsExecHistoryActive()) { queue exechist(pvm->GetExecHistory()); - cout << "PC : INSTR ACC | X | Y | PS | SP" << endl; - cout << "------------------------------------+-----+-----+-----+-----" << endl; + cout << "PC : INSTR ACC | X | Y | PS | SP"; + cout << endl; + cout << "------------------------------------+-----+-----+-----+-----"; + cout << endl; while (exechist.size()) { cout << exechist.front() << endl; exechist.pop(); @@ -478,6 +572,7 @@ unsigned int LoadImage(unsigned int newaddr) if (pvm->IsAutoExec()) execvm = true; if (newaddr == 0) newaddr = 0x10000; } + PrintVMErr(pvm->GetLastError()); return newaddr; } @@ -496,7 +591,7 @@ unsigned int ToggleIO(unsigned int ioaddr) pvm->DisableCharIO(); cout << "I/O deactivated." << endl; } else { - ioaddr = PromptNewAddress("Address (0..FFFF): "); + ioaddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << ioaddr << "]" << endl; pvm->SetCharIO(ioaddr, ioecho); cout << "I/O activated." << endl; @@ -519,7 +614,7 @@ unsigned int ToggleGrDisp(unsigned int graddr) pvm->DisableGraphDisp(); cout << "Graphics display deactivated." << endl; } else { - graddr = PromptNewAddress("Address (0..FFFF): "); + graddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << graddr << "]" << endl; pvm->SetGraphDisp(graddr); cout << "Graphics display activated." << endl; @@ -531,14 +626,14 @@ unsigned int ToggleGrDisp(unsigned int graddr) /* *-------------------------------------------------------------------- * Method: WriteToMemory() - * Purpose: + * Purpose: Take user input and write to memory. * Arguments: * Returns: *-------------------------------------------------------------------- */ void WriteToMemory() { - unsigned int tmpaddr = PromptNewAddress("Address (0..FFFF): "); + unsigned int tmpaddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << tmpaddr << "]" << endl; cout << "Enter hex bytes [00..FF] values separated with NL or spaces, end with [100]:" << endl; unsigned short v = 0; @@ -554,7 +649,7 @@ void WriteToMemory() /* *-------------------------------------------------------------------- * Method: DisassembleMemory() - * Purpose: + * Purpose: Disassemble machine code in memory to symbolic format. * Arguments: * Returns: *-------------------------------------------------------------------- @@ -562,10 +657,10 @@ void WriteToMemory() void DisassembleMemory() { unsigned int addrbeg = 0x10000, addrend = 0x10000; - cout << "Enter address range (0..0xFFFF)..." << endl; - addrbeg = PromptNewAddress("Start address (0..FFFF): "); + cout << PROMPT_RANGE_ADDR << endl; + addrbeg = PromptNewAddress(PROMPT_START_ADDR); cout << " [" << hex << addrbeg << "]" << endl; - addrend = PromptNewAddress("End address (0..FFFF): "); + addrend = PromptNewAddress(PROMPT_END_ADDR); cout << " [" << hex << addrend << "]" << endl; cout << endl; for (unsigned int addr = addrbeg; addr <= addrend;) { @@ -578,7 +673,7 @@ void DisassembleMemory() /* *-------------------------------------------------------------------- * Method: DumpMemory() - * Purpose: + * Purpose: Display contents of memory, range entered by user. * Arguments: * Returns: *-------------------------------------------------------------------- @@ -586,10 +681,10 @@ void DisassembleMemory() void DumpMemory() { unsigned int addrbeg = 0x10000, addrend = 0x10000; - cout << "Enter address range (0..0xFFFF)..." << endl; - addrbeg = PromptNewAddress("Start address (0..FFFF): "); + cout << PROMPT_RANGE_ADDR << endl; + addrbeg = PromptNewAddress(PROMPT_START_ADDR); cout << " [" << hex << addrbeg << "]" << endl; - addrend = PromptNewAddress("End address (0..FFFF): "); + addrend = PromptNewAddress(PROMPT_END_ADDR); cout << " [" << hex << addrend << "]" << endl; cout << endl; for (unsigned int addr = addrbeg; addr <= addrend; addr+=16) { @@ -615,6 +710,87 @@ void DumpMemory() } } +/* + *-------------------------------------------------------------------- + * Method: ToggleDebugTraces() + * Purpose: Enable/disable debug traces. + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void ToggleDebugTraces() +{ + if (pvm->IsDebugTraceActive()) { + pvm->DisableDebugTrace(); + cout << "Debug traces disabled." << endl; + } else { + pvm->EnableDebugTrace(); + cout << "Debug traces enabled." << endl; + } +} + +/* + *-------------------------------------------------------------------- + * Macro: SCRDIV_xxCOL + * Purpose: Print line out of xx '-' signs, no NL. + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +#define SCRDIV_20COL cout << "--------------------"; +#define SCRDIV_19COL cout << "-------------------"; +#define SCRDIV_79COL SCRDIV_20COL; SCRDIV_20COL; SCRDIV_20COL; SCRDIV_19COL; + +/* + *-------------------------------------------------------------------- + * Method: DebugTraces() + * Purpose: Show debug traces. + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void DebugTraces() +{ + if (pvm->IsDebugTraceActive()) { + queue dbgtrc(pvm->GetDebugTraces()); + cout << "Time [usec] : Message" << endl; + SCRDIV_79COL; cout << endl; + int n=0; + while (dbgtrc.size()) { + cout << dbgtrc.front() << endl; + dbgtrc.pop(); + if (n++ == 20) { + n = 0; + cout << endl; + PressEnter2Cont("Press [ENTER] for more..."); + cout << endl; + } + } + SCRDIV_79COL; cout << endl; + } else { + cout << "Sorry. Debug traces are currently disabled." << endl; + } +} + +/* + *-------------------------------------------------------------------- + * Method: TogglePerfStats() + * Purpose: Enable/disable performance stats. + * Arguments: + * Returns: + *-------------------------------------------------------------------- + */ +void TogglePerfStats() +{ + if (pvm->IsPerfStatsActive()) { + pvm->DisablePerfStats(); + cout << "Performance stats were disabled." << endl; + } else { + pvm->EnablePerfStats(); + cout << "Performance stats were enabled." << endl; + } +} + /* *-------------------------------------------------------------------- * Method: LoadArgs() @@ -769,7 +945,7 @@ int main(int argc, char *argv[]) { } else { cout << endl; - cout << "Type '?' and press ENTER for Menu ..." << endl; + cout << "Type '?' and press [ENTER] for Menu ..." << endl; cout << endl; } @@ -805,6 +981,7 @@ int main(int argc, char *argv[]) { ExecHistory(); } else if (c == 'l') { // load memory image newaddr = LoadImage(newaddr); + ioaddr = pvm->GetCharIOAddr(); } else if (c == 'k') { // toggle ROM emulation if (!enrom) { enrom = true; @@ -856,7 +1033,7 @@ int main(int argc, char *argv[]) { WriteToMemory(); } else if (c == 'a') { // change run address execaddr = stop = true; - newaddr = PromptNewAddress("Address (0..FFFF): "); + newaddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << newaddr << "]" << endl; } else if (c == 's') { runvm = step = stop = true; @@ -875,13 +1052,13 @@ int main(int argc, char *argv[]) { } else if (c == 'g') { // run from new address until BRK runvm = true; execaddr = true; - newaddr = PromptNewAddress("Address (0..FFFF): "); + newaddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << newaddr << "]" << endl; show_menu = true; } else if (c == 'x') { // execute code at address execvm = true; execaddr = true; - newaddr = PromptNewAddress("Address (0..FFFF): "); + newaddr = PromptNewAddress(PROMPT_ADDR); cout << " [" << hex << newaddr << "]" << endl; show_menu = true; } else if (c == 'q') { // quit @@ -895,6 +1072,14 @@ int main(int argc, char *argv[]) { cout << "Op-code execute history has been "; cout << (pvm->IsExecHistoryActive() ? "enabled" : "disabled") << "."; cout << endl; + } else if (c == 'z') { // toggle enable/disable debug traces in VM + ToggleDebugTraces(); + } else if (c == '2') { // show debug traces + DebugTraces(); + } else if (c == '1') { // toggle enable/disable perf. stats + TogglePerfStats(); + } else { + cout << "ERROR: Unknown command." << endl; } } } @@ -1158,6 +1343,12 @@ D - diassemble code in memory U - enable/disable exec. history Toggle enable/disable of op-codes execute history. Disabling this feature improves performance. +Z - enable/disable debug traces + Toggle enable/disable of debug traces. +2 - display debug traces + Display recent debug traces. +1 - enable/disable performance stats + Toggle enable/disable emulation speed measurement. NOTE: 1. If no arguments provided, each command will prompt user to enter diff --git a/makefile.mingw b/makefile.mingw index 5713f1c..a42f14c 100644 --- a/makefile.mingw +++ b/makefile.mingw @@ -1,4 +1,4 @@ -# Project: MKBasic +# Project: VM65 # Makefile created by Dev-C++ 5.11 # and modified for standalone MINGW compiler installation. @@ -7,9 +7,9 @@ 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 GraphDisp.o MemMapDev.o MKGenException.o +OBJ = main.o VMachine.o MKCpu.o Memory.o Display.o GraphDisp.o MemMapDev.o MKGenException.o ConsoleIO.o OBJ2 = bin2hex.o -LINKOBJ = main.o VMachine.o MKBasic.o MKCpu.o Memory.o Display.o GraphDisp.o MemMapDev.o MKGenException.o +LINKOBJ = main.o VMachine.o MKCpu.o Memory.o Display.o GraphDisp.o MemMapDev.o MKGenException.o ConsoleIO.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 -lmingw32 SDLLIBS = -L"$(SDLBASE)\x86_64-w64-mingw32/lib" -lSDL2main -lSDL2 @@ -40,9 +40,6 @@ main.o: main.cpp VMachine.o: VMachine.cpp VMachine.h $(CPP) -c VMachine.cpp -o VMachine.o $(CXXFLAGS) $(SDLINCS) -MKBasic.o: MKBasic.cpp MKBasic.h - $(CPP) -c MKBasic.cpp -o MKBasic.o $(CXXFLAGS) $(SDLINCS) - MKCpu.o: MKCpu.cpp MKCpu.h $(CPP) -c MKCpu.cpp -o MKCpu.o $(CXXFLAGS) $(SDLINCS) @@ -61,6 +58,9 @@ MemMapDev.o: MemMapDev.cpp MemMapDev.h MKGenException.o: MKGenException.cpp MKGenException.h $(CPP) -c MKGenException.cpp -o MKGenException.o $(CXXFLAGS) +ConsoleIO.o: ConsoleIO.cpp ConsoleIO.h + $(CPP) -c ConsoleIO.cpp -o ConsoleIO.o $(CXXFLAGS) + $(BIN2): $(OBJ2) $(CC) $(LINKOBJ2) -o $(BIN2) $(LIBS) diff --git a/system.h b/system.h index b9aebea..0b85be4 100644 --- a/system.h +++ b/system.h @@ -1,3 +1,34 @@ +/* + *-------------------------------------------------------------------- + * Project: VM65 - Virtual Machine/CPU emulator programming + * framework. + * + * File: system.h + * + * Purpose: Definitions related to platform portability. + * + * Date: 8/25/2016 + * + * Copyright: (C) by Marek Karcz 2016. All rights reserved. + * + * Contact: makarcz@yahoo.com + * + * License Agreement and Warranty: + + This software is provided with No Warranty. + I (Marek Karcz) will not be held responsible for any damage to + computer systems, data or user's health resulting from use. + Please proceed responsibly and apply common sense. + This software is provided in hope that it will be useful. + It is free of charge for non-commercial and educational use. + Distribution of this software in non-commercial and educational + derivative work is permitted under condition that original + copyright notices and comments are preserved. Some 3-rd party work + included with this project may require separate application for + permission from their respective authors/copyright owners. + + *-------------------------------------------------------------------- + */ #if !defined(LINUX) #define WINDOWS #endif