Multiple changes/general development.

Reset option in dbg console.
RESET keyword in memory definition file.
Command line arguments added.
Save snapshot added.
Refactoring - huge switch replaced with array of methods.
IRQ support.
Cycle accurate emulation.
Intel HEX format support.
This commit is contained in:
Marek Karcz 2016-04-17 22:54:35 -04:00
parent dce9babd36
commit 0d47565b9d
14 changed files with 5852 additions and 2650 deletions

5985
MKCpu.cpp

File diff suppressed because it is too large Load Diff

1094
MKCpu.h

File diff suppressed because it is too large Load Diff

View File

@ -225,10 +225,7 @@ unsigned char Memory::ReadCharKb(bool nonblock)
set_conio_terminal_mode();
#endif
static int c = ' ';
//putchar('\n');
if (mIOEcho && isprint(c)) putchar(c);
//else putchar(' ');
//fputs("<-Character Input (CTRL-Y to BREAK) ?\r",stdout);
fflush(stdout);
if (!nonblock) while(!kbhit());
else c = 0;
@ -239,8 +236,6 @@ unsigned char Memory::ReadCharKb(bool nonblock)
kill(getpid(),SIGINT);
}
#endif
//fputs(" \r",stdout);
//fflush(stdout);
mCharIOBufIn[mInBufDataEnd] = c;
mInBufDataEnd++;
if (mInBufDataEnd >= CHARIO_BUF_SIZE) mInBufDataEnd = 0;

View File

@ -49,9 +49,43 @@ Parts of this project is based on or contains 3-rd party work:
2. Format of the memory image definition file.
Emulator recognizes 4 formats of memory image:
- raw binary, no header, up to 64 kB of raw data,
- binary image with a configuration/snapshot header,
- Intel HEX format,
- plain text memory image definition file.
Please see detailed description of each format below:
Program can load raw binary image of MOS 6502 opcodes.
Binary image is always loaded from address $0000 and can be up to 64 kB long,
so the code must be properly located inside that image.
so the code must be properly located inside that image. Image can be shorter
than 64 kB, user will receive warning in such case, but it will be loaded.
Binary image may have header attached at the top.
It consists of magic keyword 'SNAPSHOT' followed by 15 bytes of data
(subject to change in future versions).
The header data saves the status of CPU and emulation facilities like
character I/O address and enable flag, ROM boundaries and enable flag etc.
This header is added when user saves snapshot of the VM from debug console
menu with command: Y [file_name].
Header is not mandatory, so the binary image created outside application can
also be used. User will receive warning at startup during image load if
header is missing, but image will be loaded. In this case, user may need
to configure necessary emulation facilities manually in debug console
before executing native 6502 code.
When binary image with a header is loaded, user can continue executing the
program from the place where it was saved by executing command from debug
console of the emulator:
X pc_value
Where:
pc_value - the address currently showing in CPU's PC register.
If the reset vector is set right, execute the 6502 code right from command
line:
mkbasic -r image_name
Above will execute the code set in reset vector without having to start it
from debug console. If 6502 code requires character I/O and/or ROM facilities
then image should include header with proper setup.
Depending on your favorite 6502 assembler, you may need to use proper command
line arguments or configuration to achieve properly formatted binary file.
E.g.: if using CL65 from CC65 package, create configuration file that defines
@ -62,7 +96,18 @@ Two CFG files, one for microchess and one for All Suite from hmc-6502 project
are supplied with this project and assembler source code adapted to be
compiled with CL65.
Other assemblers may need a different approach or may not be able to generate
binary images that are required for this emulator.
binary images in format required for this emulator. In such case you may need
to design your own custom conversion tools to generate such images.
NOTE:
Simple conversion utility is supplied with this project (bin2hex), which is
described later in this file.
Emulator recognizes Intel HEX format file. It recognizes properly data
records and end-of-file record only at this time. Similar to binary image
with no header, when Intel HEX file is loaded, user may need to configure
necessary emulation facilities manually in debug console before executing
native 6502 code.
Program can also load memory image definition file (plain text), which is
a format developed especially for this project.
@ -86,22 +131,25 @@ ENROM
ENIO
EXEC
address
RESET
Where:
ADDR - label indicating that starting and run address will follow in
the next line
ORG - label indicating that the address counter will change to the
ADDR - label indicating that starting and run address will follow in
the next line
ORG - label indicating that the address counter will change to the
value provided in next line
IOADDR - label indicating that character I/O emulation trap address will
follow in the next line
ROMBEGIN - label indicating that the emulated read-only memory start address
will follow in the next line
follow in the next line
ROMBEGIN - label indicating that the emulated read-only memory start
address will follow in the next line
ROMEND - label indicating that the emulated read-only memory end address
will follow in the next line
ENROM - enable read-only memory emulation
ENIO - enable character I/O emulation
EXEC - label indicating that the auto-execute address will follow
in the next line
will follow in the next line
ENROM - enable read-only memory emulation
ENIO - enable character I/O emulation
EXEC - label indicating that the auto-execute address will follow
in the next line, 6502 program will auto-execute from that
address after memory definition file is done loading
RESET - initiate CPU reset sequence after loading memory definition file
address - decimal or hexadecimal (prefix $) address in memory
@ -275,7 +323,248 @@ If you experience unexpected character input prompt while emulating
6502 code, this may be the case. Reconfigure your IOADDR to be inside ROM in
such case and try again.
6. Warranty and License Agreement.
Emulator is "cycle accurate" but not time or speed accurate.
This means that each call to MKCpu::ExecOpcode() method is considered a single
CPU cycle, so depending on the executed opcode, multiple calls (# varies per
opcode and other conditions) are needed to complete the opcode execution and
proceed to the next one. Method returns pointer to the the virtual CPU
registers. One of the members of this structure is named CyclesLeft. When this
variable reaches 0, the opcode execution is considered complete.
The VMachine class calls the ExecOpcode() method as fast as possible, so it is
not real-time accurate, as already mentioned. To implement real-time accurate
emulation, the MKCpu::ExecOpcode() method calls would have to be synchronized
to some fairly accurate time scale (some kind of timer thread or a different
solution).
6. Debugger Console Command Reference.
S - step
Executes single opcode at current address.
C - continue
Continues code execution from current address until BRK.
M - dump memory
Usage: M [startaddr] [endaddr]
Where: startaddr,endaddr - memory addr. in hexadecimal format [0000..FFFF].
Dumps contents of memory, hexadecimal and ASCII formats."
G - go/continue from new address until BRK
Usage: G [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF].
Executes code at provided address, interrupted by BRK opcode.
X - execute code from new address until RTS
Usage: X [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF].
Executes code at provided address, until RTS (last one).
Q - quit
Exits from the emulator/debugger.
A - set address for next step
Usage: A [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF].
Sets current address to a new value.
N - go number of steps
Usage: N [steps]
Where: steps - number of steps in decimal format
Execute number of opcodes provided in steps argument starting
from current address.
P - IRQ
Send maskable interrupt request to CPU (set the IRQ line LOW).
W - write to memory
Usage: W [address] [hexval] [hexval] ... 100
Where: address - memory addr. in hexadecimal format [0000.FFFF],
hexval - byte value in hexadecimal format [00.FF].
Writes provided values to memory starting at specified address.
I - toggle char I/O emulation
Usage: I [address]
Where: address - memory addr. in hexadecimal format [0000.FFFF],
Toggles basic character I/O emulation. When enabled, all writes
to the specified memory address also writes a character code to
to a virtual console. All reads from specified memory address
are interpreted as console character input.
R - show registers
Displays CPU registers, flags and stack.
Y - snapshot
Usage: Y [file_name]
Where: file_name - the name of the output file.
Save snapshot of current CPU and memory in a binary file.
T - show I/O console
Displays/prints the contents of the virtual console screen.
Note that in run mode (commands X, G or C), virtual screen is
displayed automatically in real-time if I/O emulation is enabled.
E - toggle I/O local echo
Toggles local echo on/off when I/O emulation is enabled.
B - blank (clear) screen
Clears the screen, useful when after exiting I/O emulation or
registers animation (long stack) your screen is messed up.
F - toggle registers animation mode
When in multi-step debug mode (command: N), displaying registers
can be suppressed or, when animation mode is enabled - they will
be continuously displayed after each executed step.
J - set registers status animation delay
Usage: J [delay]
Where: delay - time of delay in milliseconds,
Sets the time added at the end of each execution step in multi
step mode (command: N). The default value is 250 ms.
K - toggle ROM emulation
Usage: K [rombegin] [romend] - to enable,
K - to disable,
(OR just use 'K' in both cases and be prompted for arguments.)
Where:
rombegin - hexadecimal address [0200..FFFF],
romend - hexadecimal address [rombegin+1..FFFF].
Enable/disable ROM emulation and define address range to which the ROM
(read-only memory) will be mapped. Default range: $D000-$DFFF.
L - load memory image
Usage: L [image_type] [image_name]
Where:
image_type - B (binary), H (Intel HEX) OR D (definition),
image_name - name of the image file.
This function allows to load new memory image from either binary
image file, Intel HEX format file or the ASCII definition file.
The binary image is always loaded from address 0x0000 and can be up to
64kB long. The definition file format is a plain text file that can
contain following keywords and data:
ADDR This keyword defines the run address of the executable code.
It is optional, but if exists, it must be the 1-st keyword
in the definition file.
Address in decimal or hexadecimal ($xxxx) format must follow
in the next line.
ORG Changes the current address counter. The line that follows
sets the new address in decimal or hexadecimal format.
Data that follows will be put in memory starting from that
address. This keyword is optional and can be used multiple
times in the definition file.
IOADDR Defines the address of the character I/O emulation. The
next line sets the address of I/O emulation in decimal or
hexadecimal format. If the I/O emulation is enabled
(see ENIO keyword), then any character written to this
address will be sent to the virtual console. The reading
from that address will invoke character input from the
emulated console. That input procedure is of blocking
type. To invoke non-blocking character procedure, reading
should be performed from IOADDR+1.
ROMBEGIN Defines the address in memory where the beginning of the
Read Only memory is mapped. The next line that follows this
keyword sets the address in decimal or hexadecimal format.
ROMEND Defines the address in memory where the end of the Read
Only Memory is mapped. The next line that follows this
keyword sets the address in decimal or hexadecimal format.
ENIO Putting this keyword in memory definition file enables
rudimentary character I/O emulation and virtual console
emulation.
ENROM Putting this keyword in memory definition file enables
emulation of Read Only Memory, in range of addresses
defined by ROMBEGIN and ROMEND keywords.
EXEC Define starting address of code which will be automatically
executed after the memory image is loaded.
The next line that follows this keyword sets the address
in decimal or hexadecimal format.
RESET Enables auto-reset of the CPU. After loading the memory
definition file, the CPU reset sequence will be initiated.
O - display op-codes history
Show the history of last executed op-codes/instructions, full with
disassembled mnemonic and argument.
D - diassemble code in memory
Usage: D [startaddr] [endaddr]
Where: startaddr,endaddr - hexadecimal address [0000..FFFF].
Attempt to disassemble code in specified address range and display
the results (print) on the screen in symbolic form.
0 - reset
Run the processor initialization sequence, just like the real CPU
when its RTS signal is set to LOW and HIGH again. CPU will disable
interrupts, copy address from vector $FFFC to processors PC and will
start executing code. Programmer must put initialization routine
under address pointed by $FFFC vector, which will set the arithmetic
mode, initialize stack, I/O devices and enable IRQ if needed before
jumping to main loop. The reset routine disables trapping last RTS
opcode if stack is empty, so the VM will never return from opcodes
execution loop unless user interrupts with CTRL-C or CTRL-Break.
NOTE:
1. If no arguments provided, each command will prompt user to enter
missing data.
2. It is possible to exit from running program to debugger console
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)
7. Command line usage.
D:\src\wrk\mkbasic>mkbasic -h
Virtual Machine/CPU Emulator (MOS 6502) and Debugger.
Copyright (C) by Marek Karcz 2016. All rights reserved.
Usage:
mkbasic [-h] | [ramdeffile] | [-b ramimage] | [-r ramimage]
OR
mkbasic [-x intelheximage]
Where:
ramdeffile - RAM definition file name
intelheximage - Intel HEX format file
ramimage - RAM binary image file name
When ran with no arguments, program will load default memory
definition files: default.rom, default.ram and will enter the debug
console menu.
When ramdeffile argument is provided, program will load the memory
definition from the file, set the flags and parameters depending on the
contents of the memory definition file and enter the corresponding mode
of operation as defined in that file.
If used with flag -b or -x, program will load memory from the provided image
file and enter the debug console menu.
If used with flag -r, program will load memory from the provided image
file and execute CPU reset sequence.
8. Utilities.
Utility bin2hex is supplied with the project to aid in conversion from raw
binary memory image to one of the plain text formats recognized by emulator
NOTE: In current version, emulator can load raw binary format directly, so
usefulness of this utility is somewhat deprecated.
D:\src\wrk\mkbasic>bin2hex -h
Program: bin2hex
Convert binary file to Intel HEX format.
OR
Convert binary file to memory image definition for MKBASIC (VM65) emulator.
Copyright: Marek Karcz 2016. All rights reserved.
Free for personal and educational use.
Usage:
bin2hex -f input -o output [-w addr] [-x exec] [[-s] [-z] | -i]
Where:
input - binary file name
output - output file name
addr - starting address to load data (default: 2048)
exec - address to auto-execute code from (default: 2048)
-s - suppress auto-execute statement in output
-z - suppress data blocks with 0-s only
-i - convert to Intel HEX format
NOTE: When this switch is used, addr, exec, -s, -z are ignored,
addr = 0, exec is not set and data blocks with 0-s only
are always suppressed.
9. Warranty and License Agreement.
This software is provided with No Warranty.
I (The Author) will not be held responsible for any damage to computer

View File

@ -74,7 +74,7 @@ VMachine::~VMachine()
* Purpose: Initialize class.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
f *--------------------------------------------------------------------
*/
void VMachine::InitVM()
{
@ -82,6 +82,7 @@ void VMachine::InitVM()
mpRAM = new Memory();
mAutoExec = false;
mAutoReset = false;
mCharIOAddr = CHARIO_ADDR;
mCharIOActive = mCharIO = false;
if (NULL == mpRAM) {
@ -106,10 +107,11 @@ void VMachine::InitVM()
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* Method: ClearScreen()
* Purpose: Clear the working are of the VM - DOS.
* This is not a part of virtual display emulation.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::ClearScreen()
@ -151,10 +153,11 @@ void VMachine::ClearScreen()
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* Method: ScrHome()
* Purpose: Bring the console cursor to home position - DOS.
* This is not a part of virtual display emulation.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::ScrHome()
@ -175,10 +178,11 @@ void VMachine::ScrHome()
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* 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()
@ -188,10 +192,11 @@ void VMachine::ClearScreen()
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* 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()
@ -203,10 +208,10 @@ void VMachine::ScrHome()
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
* Method: ShowDisp()
* Purpose: Show the emulated virtual text display device contents.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::ShowDisp()
@ -234,7 +239,7 @@ Regs *VMachine::Run()
ShowDisp();
while (true) {
cpureg = Step();
if (mCharIO) {
if (cpureg->CyclesLeft == 0 && mCharIO) {
ShowDisp();
}
if (cpureg->SoftIrq || mOpInterrupt)
@ -264,8 +269,8 @@ Regs *VMachine::Run(unsigned short addr)
/*
*--------------------------------------------------------------------
* Method: Exec()
* Purpose: Run VM from current address until last RTS.
* NOTE: Stack must be empty!
* Purpose: Run VM from current address until last RTS (if enabled).
* NOTE: Stack must be empty for last RTS to be trapped.
* Arguments: n/a
* Returns: Pointer to CPU registers and flags.
*--------------------------------------------------------------------
@ -279,7 +284,7 @@ Regs *VMachine::Exec()
ShowDisp();
while (true) {
cpureg = Step();
if (mCharIO) {
if (cpureg->CyclesLeft == 0 && mCharIO) {
cout << mpDisp->GetLastChar();
cout << flush;
}
@ -322,7 +327,7 @@ Regs *VMachine::Step()
addr = cpureg->PtrAddr;
mRunAddr = addr;
if (mCharIOActive && !mOpInterrupt) {
if (cpureg->CyclesLeft == 0 && mCharIOActive && !mOpInterrupt) {
char c = -1;
mCharIO = false;
while ((c = mpRAM->GetCharOut()) != -1) {
@ -380,35 +385,340 @@ void VMachine::LoadRAM(string ramfname)
/*
*--------------------------------------------------------------------
* Method: LoadRAMBin()
* Purpose: Load data from binary image file to the memory.
* Arguments: ramfname - name of the RAM file definition
* Method: HasHdrData()
* Purpose: Check for header in the binary memory image.
* Arguments: File pointer.
* Returns: true if magic keyword found at the beginning of the
* memory image file, false otherwise
*--------------------------------------------------------------------
*/
bool VMachine::HasHdrData(FILE *fp)
{
bool ret = false;
int n = 0, l = strlen(HDRMAGICKEY);
char buf[20];
memset(buf, 0, 20);
while (0 == feof(fp) && 0 == ferror(fp)) {
unsigned char val = fgetc(fp);
buf[n] = val;
n++;
if (n >= l) break;
}
ret = (0 == strncmp(buf, HDRMAGICKEY, l));
return ret;
}
/*
*--------------------------------------------------------------------
* Method: LoadHdrData()
* Purpose: Load data from binary image header.
* Arguments: File pointer.
* Returns: bool, true if success, false if error
*
* Details:
* Header of the binary memory image consists of magic keyword
* string followed by the data (unsigned char values).
* It has following format:
*
* MAGIC_KEYWORD
* aabbccddefghijk
*
* Where:
* MAGIC_KEYWORD - text string indicating header, may vary between
* versions thus rendering headers from previous
* versions incompatible - currently: "SNAPSHOT"
* aa - low and hi bytes of execute address (PC)
* bb - low and hi bytes of char IO address
* cc - low and hi bytes of ROM begin address
* dd - low and hi bytes of ROM end address
* e - 0 if char IO is disabled, 1 if enabled
* f - 0 if ROM is disabled, 1 if enabled
* g - value in CPU Acc (accumulator) register
* h - value in CPU X (X index) register
* i - value in CPU Y (Y index) register
* j - value in CPU PS (processor status/flags)
* k - value in CPU SP (stack pointer) register
*
* NOTE:
* If magic keyword was detected, this part is already read and file
* pointer position is at the 1-st byte of data. Therefore this
* method does not have to read and skip the magic keyword.
*--------------------------------------------------------------------
*/
bool VMachine::LoadHdrData(FILE *fp)
{
int n = 0, l = 0;
unsigned short rb = 0, re = 0;
Regs r;
bool ret = false;
while (0 == feof(fp) && 0 == ferror(fp) && n < HDRDATALEN) {
unsigned char val = fgetc(fp);
switch (n)
{
case 1: mRunAddr = l + 256 * val;
break;
case 3: mCharIOAddr = l + 256 * val;
break;
case 5: rb = l + 256 * val;
break;
case 7: re = l + 256 * val;
break;
case 8: mCharIOActive = (val != 0);
break;
case 9: if (val != 0) {
mpRAM->EnableROM(rb, re);
} else {
mpRAM->SetROM(rb, re);
}
break;
case 10: r.Acc = val;
break;
case 11: r.IndX = val;
break;
case 12: r.IndY = val;
break;
case 13: r.Flags = val;
break;
case 14: r.PtrStack = val;
ret = true;
break;
default: break;
}
l = val;
n++;
}
if (ret) {
r.PtrAddr = mRunAddr;
mpCPU->SetRegs(r);
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: SaveHdrData()
* Purpose: Save header data to binary file (memory snapshot).
* Arguments: File pointer, must be opened for writing in binary mode.
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::LoadRAMBin(string ramfname)
void VMachine::SaveHdrData(FILE *fp)
{
char buf[20] = {0};
strcpy(buf, HDRMAGICKEY);
for (unsigned int i = 0; i < strlen(HDRMAGICKEY); i++) {
fputc(buf[i], fp);
}
Regs *reg = mpCPU->GetRegs();
unsigned char lo = 0, hi = 0;
lo = (unsigned char) (reg->PtrAddr & 0x00FF);
hi = (unsigned char) ((reg->PtrAddr & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
lo = (unsigned char) (mCharIOAddr & 0x00FF);
hi = (unsigned char) ((mCharIOAddr & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
lo = (unsigned char) (GetROMBegin() & 0x00FF);
hi = (unsigned char) ((GetROMBegin() & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
lo = (unsigned char) (GetROMEnd() & 0x00FF);
hi = (unsigned char) ((GetROMEnd() & 0xFF00) >> 8);
fputc(lo, fp);
fputc(hi, fp);
lo = (mCharIOActive ? 1 : 0);
fputc(lo, fp);
lo = (IsROMEnabled() ? 1 : 0);
fputc(lo, fp);
Regs *pregs = mpCPU->GetRegs();
if (pregs != NULL) {
fputc(pregs->Acc, fp);
fputc(pregs->IndX, fp);
fputc(pregs->IndY, fp);
fputc(pregs->Flags, fp);
fputc(pregs->PtrStack, fp);
}
}
/*
*--------------------------------------------------------------------
* Method: SaveSnapshot()
* Purpose: Save current state of the VM and memory image.
* Arguments: String - file name.
* Returns: int, 0 if successful, greater then 0 if not (# of bytes
* not written).
*--------------------------------------------------------------------
*/
int VMachine::SaveSnapshot(string fname)
{
FILE *fp = NULL;
int ret = MAX_8BIT_ADDR+1;
if ((fp = fopen(fname.c_str(), "wb")) != NULL) {
SaveHdrData(fp);
for (int addr = 0; addr < MAX_8BIT_ADDR+1; addr++) {
if (addr != mCharIOAddr && addr != mCharIOAddr+1) {
unsigned char b = mpRAM->Peek8bit((unsigned short)addr);
if (EOF != fputc(b, fp)) ret--;
else break;
} else {
if (EOF != fputc(0, fp)) ret--;
else break;
}
}
fclose(fp);
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: LoadRAMBin()
* Purpose: Load data from binary image file to the memory.
* Arguments: ramfname - name of the RAM file definition
* Returns: int - error code
* 0 - OK
* 1 - WARNING: Unexpected EOF (image shorter than 64kB).
* 2 - WARNING: Unable to open memory image file.
* 3 - WARNING: Problem with binary image header.
* 4 - WARNING: No header found in binary image.
* 5 - WARNING: Problem with binary image header and
* Unexpected EOF (image shorter than 64kB).
* 6 - WARNING: No header found in binary image and
* Unexpected EOF (image shorter than 64kB).
* TO DO:
* - Add fixed size header to binary image with emulator
* configuration data. Presence of the header will be detected
* by magic key at the beginning. Header should also include
* snapshot info, so the program can continue from the place
* where it was frozen/saved.
*--------------------------------------------------------------------
*/
int VMachine::LoadRAMBin(string ramfname)
{
FILE *fp = NULL;
unsigned short addr = 0x0000;
int n = 0;
Memory *pm = mpRAM;
int ret = 2;
if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) {
if (HasHdrData(fp)) {
ret = (LoadHdrData(fp) ? 0 : 3);
} else {
ret = 4;
rewind(fp);
}
// temporarily disable emulation facilities to allow
// proper memory image initialization
bool tmp1 = mCharIOActive, tmp2 = mpRAM->IsROMEnabled();
DisableCharIO();
DisableROM();
while (0 == feof(fp) && 0 == ferror(fp)) {
unsigned char val = fgetc(fp);
pm->Poke8bit(addr, val);
addr++; n++;
}
fclose(fp);
// restore emulation facilities status
if (tmp1) SetCharIO(mCharIOAddr, false);
if (tmp2) EnableROM();
if (n <= 0xFFFF) {
cout << "WARNING: Unexpected EOF." << endl;
switch (ret) {
case 0: ret = 1; break;
case 3: ret = 5; break;
case 4: ret = 6; break;
default: break;
}
}
}
else {
cout << "WARNING: Unable to open memory image file: " << ramfname << endl;
cout << "Press [ENTER]...";
getchar();
}
return ret;
}
/*
*--------------------------------------------------------------------
* Method: LoadRAMHex()
* Purpose: Load data from Intel HEX file format to memory.
* Arguments: hexfname - name of the HEX file
* Returns: int, 0 if OK, >0 - error code:
* 1 - unable to open file
* 2 - syntax error
* 3 - hex format error
*--------------------------------------------------------------------
*/
int VMachine::LoadRAMHex(string hexfname)
{
char line[256] = {0};
FILE *fp = NULL;
int ret = 0;
unsigned int addr = 0;
bool tmp1 = mCharIOActive, tmp2 = mpRAM->IsROMEnabled();
DisableCharIO();
DisableROM();
if ((fp = fopen(hexfname.c_str(), "r")) != NULL) {
while (0 == feof(fp) && 0 == ferror(fp)) {
line[0] = '\0';
fgets(line, 256, fp);
if (line[0] == ':') {
if (0 == strcmp(line, HEXEOF)) {
break; // EOF, we are done here.
}
char blen[3] = {0,0,0};
char baddr[5] = {0,0,0,0,0};
char brectype[3] = {0,0,0};
blen[0] = line[1];
blen[1] = line[2];
blen[2] = 0;
baddr[0] = line[3];
baddr[1] = line[4];
baddr[2] = line[5];
baddr[3] = line[6];
baddr[4] = 0;
brectype[0] = line[7];
brectype[1] = line[8];
brectype[2] = 0;
unsigned int reclen = 0, rectype = 0;
sscanf(blen, "%02x", &reclen);
sscanf(baddr, "%04x", &addr);
sscanf(brectype, "%02x", &rectype);
if (reclen == 0 && rectype == 1) break; // EOF, we are done here.
if (rectype != 0) continue; // not a data record, next!
for (unsigned int i=9; i<reclen*2+9; i+=2,addr++) {
if (i>=strlen(line)-3) {
ret = 3; // hex format error
break;
}
char dbuf[3] = {0,0,0};
unsigned int byteval = 0;
Memory *pm = mpRAM;
dbuf[0] = line[i];
dbuf[1] = line[i+1];
dbuf[2] = 0;
sscanf(dbuf, "%02x", &byteval);
pm->Poke8bit(addr, (unsigned char)byteval&0x00FF);
}
} else {
ret = 2; // syntax error
break;
}
}
fclose(fp);
} else {
ret = 1; // unable to open file
}
if (tmp1) SetCharIO(mCharIOAddr, false);
if (tmp2) EnableROM();
return ret;
}
/*
@ -506,6 +816,10 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
Memory *pm = pmem;
if ((fp = fopen(memfname.c_str(), "r")) != NULL) {
// to ensure proper memory initialization, disable emulation
// of char I/O and ROM
DisableROM();
DisableCharIO();
while (0 == feof(fp) && 0 == ferror(fp))
{
line[0] = '\0';
@ -593,6 +907,11 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
}
continue;
}
// auto reset
if (0 == strncmp(line, "RESET", 5)) {
mAutoReset = true;
continue;
}
// define ROM begin address
if (0 == strncmp(line, "ROMBEGIN", 8)) {
line[0] = '\0';
@ -651,7 +970,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
else
pm->SetROM(rombegin, romend);
} else {
if (enrom) pm->EnableROM();
if (enrom) EnableROM();
}
if (enio) {
SetCharIO(mCharIOAddr, false);
@ -796,6 +1115,19 @@ bool VMachine::IsAutoExec()
return mAutoExec;
}
/*
*--------------------------------------------------------------------
* Method: IsAutoReset()
* Purpose: Return status of auto-reset flag.
* Arguments: n/a
* Returns: bool - true if auto-exec flag is enabled.
*--------------------------------------------------------------------
*/
bool VMachine::IsAutoReset()
{
return mAutoReset;
}
/*
*--------------------------------------------------------------------
* Method:
@ -954,4 +1286,33 @@ unsigned short VMachine::Disassemble(unsigned short addr, char *buf)
return mpCPU->Disassemble(addr, buf);
}
/*
*--------------------------------------------------------------------
* Method: Reset()
* Purpose: Reset VM and CPU (should never return except operator
* induced interrupt).
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::Reset()
{
mpCPU->Reset();
Exec(mpCPU->GetRegs()->PtrAddr);
mpCPU->mExitAtLastRTS = true;
}
/*
*--------------------------------------------------------------------
* Method: Interrupt()
* Purpose: Send Interrupt ReQuest to CPU (set the IRQ line LOW).
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void VMachine::Interrupt()
{
mpCPU->Interrupt();
}
} // namespace MKBasic

View File

@ -15,6 +15,9 @@
#define IOREFRESH 32
#define OPINTERRUPT 25 // operator interrupt code (CTRL-Y)
#define HDRMAGICKEY "SNAPSHOT"
#define HDRDATALEN 15
#define HEXEOF ":00000001FF"
using namespace std;
@ -36,7 +39,8 @@ class VMachine
Regs *Step(unsigned short addr);
void LoadROM(string romfname);
void LoadRAM(string ramfname);
void LoadRAMBin(string ramfname);
int LoadRAMBin(string ramfname);
int LoadRAMHex(string hexfname);
unsigned short MemPeek8bit(unsigned short addr);
void MemPoke8bit(unsigned short addr, unsigned char v);
Regs *GetRegs();
@ -48,6 +52,7 @@ class VMachine
void ClearScreen();
void ScrHome();
bool IsAutoExec();
bool IsAutoReset();
void EnableROM();
void DisableROM();
void SetROM(unsigned short start, unsigned short end);
@ -60,6 +65,9 @@ class VMachine
bool IsOpInterrupt();
queue<string> GetExecHistory();
unsigned short Disassemble(unsigned short addr, char *buf);
void Reset();
void Interrupt();
int SaveSnapshot(string fname);
protected:
@ -75,9 +83,13 @@ class VMachine
bool mCharIO;
bool mOpInterrupt; // operator interrupt from console
bool mAutoExec;
bool mAutoReset;
void LoadMEM(string memfname, Memory *pmem);
void ShowDisp();
bool HasHdrData(FILE *fp);
bool LoadHdrData(FILE *fp);
void SaveHdrData(FILE *fp);
};
} // namespace MKBasic

151
bin2hex.c
View File

@ -24,29 +24,51 @@ int g_nStartAddr = 2048; /* $0800 */
int g_nExecAddr = 2048; /* $0800 */
int g_nSuppressAutoExec = 1;
int g_nSuppressAllZeroRows = 0;
int g_nConvert2IntelHex = 0;
void ScanArgs(int argc, char *argv[]);
void ConvertFile(void);
void Convert2IntelHex(void);
/*
*--------------------------------------------------------------------
* Method: Usage()
* Purpose: Print usage information/help.
* Arguments: char * - program name.
* Returns: n/a
*--------------------------------------------------------------------
*/
void Usage(char *prgn)
{
printf("\nProgram: %s\n Convert binary file to memory image definition for MKBASIC (VM65) emulator.\n\n", prgn);
printf("\nProgram: %s\n Convert binary file to Intel HEX format.\nOR\n", prgn);
printf(" Convert binary file to memory image definition for MKBASIC (VM65) emulator.\n\n");
printf("Copyright: Marek Karcz 2016. All rights reserved.\n");
printf("Free for personal and educational use.\n\n");
printf("Usage:\n\n");
printf(" %s -f input_fname -o output_fname [-w load_addr] [-x exec_addr] [-s] [-z]\n\n", prgn);
printf(" %s -f input -o output [-w addr] [-x exec] [[-s] [-z] | -i]\n\n", prgn);
printf("Where:\n\n");
printf(" input_fname - binary file name\n");
printf(" output_fname - output file name\n");
printf(" load_addr - starting address to load data (default: %d)\n", g_nStartAddr);
printf(" exec_addr - address to auto-execute code from (default: %d)\n", g_nExecAddr);
printf(" -s - suppress auto-execute statement in output\n");
printf(" -z - suppress data blocks with 0-s only\n");
printf(" input - binary file name\n");
printf(" output - output file name\n");
printf(" addr - starting address to load data (default: %d)\n", g_nStartAddr);
printf(" exec - address to auto-execute code from (default: %d)\n", g_nExecAddr);
printf(" -s - suppress auto-execute statement in output\n");
printf(" -z - suppress data blocks with 0-s only\n");
printf(" -i - convert to Intel HEX format\n");
printf(" NOTE: When this switch is used, addr, exec, -s, -z are ignored,\n");
printf(" addr = 0, exec is not set and data blocks with 0-s only\n");
printf(" are always suppressed.\n");
printf("\n");
}
/*
* bin2hex -f InputFile -o OutputFile -w StartAddr
*--------------------------------------------------------------------
* Method: ScanArgs()
* Purpose: Scan/parse command line arguments and set internal
* flags and parameters.
* Arguments: int argc - # of command line arguments,
* char *argv[] - array of command line arguments.
* Returns: n/a
*--------------------------------------------------------------------
*/
void ScanArgs(int argc, char *argv[])
{
@ -85,11 +107,24 @@ void ScanArgs(int argc, char *argv[])
{
g_nSuppressAllZeroRows = 1;
}
else if (strcmp(argv[n],"-i") == 0)
{
g_nConvert2IntelHex = 1;
}
n++;
}
}
/*
*--------------------------------------------------------------------
* Method: ConvertFile()
* Purpose: Convert binary file to plain text memory definition
* file for MKBASIC (VM65) emulator.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void ConvertFile(void)
{
FILE *fpi = NULL;
@ -170,6 +205,96 @@ void ConvertFile(void)
printf("ERROR: Unable to open input file.\n");
}
/*
*--------------------------------------------------------------------
* Method: Convert2IntelHex()
* Purpose: Convert binary file to Intel HEX format.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void Convert2IntelHex(void)
{
FILE *fpi = NULL;
FILE *fpo = NULL;
unsigned char bt[17], cksum;
char hex[80];
int i, allzero;
unsigned int addr;
addr = 0;
g_nSuppressAllZeroRows = 1;
g_nExecAddr = 0;
printf("Processing...\n");
printf("Start address: $%04x\n", addr);
if (NULL != (fpi = fopen(g_szInputFileName,"rb")))
{
if (NULL != (fpo = fopen(g_szHexFileName,"w")))
{
while(0 == feof(fpi) && addr <= 0xFFFF)
{
memset(bt, 0, 17);
memset(hex, 0, 80);
if (DEBUG) printf("Reading input file...");
fread(bt, sizeof(char), 16, fpi);
if (DEBUG) printf("done.\n");
if (DEBUG) printf("Preparing hex string...\n");
/* start the Intel HEX data record, all data blocks
generated by this program are 16-bytes long
*/
sprintf(hex, ":10%04x00", addr);
cksum = 0;
allzero = 1;
/* append data to record */
for(i=0; i<16; i++)
{
cksum += bt[i];
if (DEBUG) printf("Code: %d\n", bt[i]);
sprintf(hex, "%s%02x", hex, bt[i]);
if (allzero && bt[i]) allzero = 0;
}
cksum = ~cksum + 1; /* Two's complement of modulo 256 sum */
/* end record with check sum value */
sprintf(hex, "%s%02x", hex, cksum);
/* output only if non-zero data present in 16-byte block */
if (0 == g_nSuppressAllZeroRows
||
(g_nSuppressAllZeroRows && 0 == allzero)
)
{
sprintf(hex, "%s\n", hex);
if (DEBUG) printf("Adding line: %s", hex);
fputs(hex, fpo);
}
addr += 16;
}
/* add EOF */
hex[0] = 0;
strcpy(hex, ":00000001FF");
sprintf(hex, "%s\n", hex);
fputs(hex, fpo);
fclose(fpi);
fclose(fpo);
printf("Done.\n");
printf("End address: $%04x\n", (addr <= 0xFFFF) ? addr : 0xFFFF);
printf("Run address: $%04x\n", g_nExecAddr);
}
else
printf("ERROR: Unable to create output file.\n");
}
else
printf("ERROR: Unable to open input file.\n");
}
/*
*--------------------------------------------------------------------
* Method: main()
* Purpose: Main program loop/routine.
* Arguments: int argc - # of provided in command line arguments.
* char *argv[] - array of command line arguments.
* Returns: int - always 0.
*--------------------------------------------------------------------
*/
int main(int argc, char *argv[])
{
if (argc == 1)
@ -178,8 +303,12 @@ int main(int argc, char *argv[])
ScanArgs(argc, argv);
if (*g_szInputFileName == 0 || *g_szHexFileName == 0)
Usage(argv[0]);
else
ConvertFile();
else {
if (0 == g_nConvert2IntelHex)
ConvertFile();
else
Convert2IntelHex();
}
}
return 0;
}

View File

@ -10,13 +10,15 @@ $FFE0
ROMBEGIN
$FFC0
ROMEND
$FFDF
$FFFF
;$FFDF
; Enable char IO and ROM
ENIO
ENROM
; Auto-execute
EXEC
$C000
;EXEC
;$C000
RESET
; Data/Code
$D8, $A0, $08, $B9, $FB, $E0, $99, $00
$02, $88, $10, $F7, $A2, $FF, $8E, $21
@ -2062,3 +2064,6 @@ $AD, $E1, $FF, $F0, $0C, $C9, $61, $90
$06, $C9, $7B, $B0, $02, $29, $5F, $38
$60, $18, $60, $8D, $E0, $FF, $29, $FF
$60, $00, $00, $00, $00, $00, $00, $00
ORG
$FFFC
$00 $C0

283
main.cpp
View File

@ -3,6 +3,7 @@
#include <bitset>
#include <chrono>
#include <thread>
#include <string.h>
#include "system.h"
#include "MKCpu.h"
#include "Memory.h"
@ -24,6 +25,109 @@ int g_stackdisp_lines = 1;
bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat);
void ShowHelp();
void CmdArgHelp(string prgname);
void CopyrightBanner();
/*
*--------------------------------------------------------------------
* Method: RunSingleInstr()
* Purpose: Execute single instruction of the CPU (all cycles).
* Arguments: addr - unsigned short, instruction address
* Returns: pointer to CPU registers
*--------------------------------------------------------------------
*/
Regs *RunSingleInstr(unsigned short addr)
{
Regs *ret = NULL;
do {
ret = pvm->Step(addr);
} while (ret->CyclesLeft > 0);
return ret;
}
/*
*--------------------------------------------------------------------
* Method: RunSingleCurrInstr()
* Purpose: Execute single instruction of the CPU (all cycles)
* at current address.
* Arguments: n/a
* Returns: pointer to CPU registers
*--------------------------------------------------------------------
*/
Regs *RunSingleCurrInstr()
{
Regs *ret = NULL;
do {
ret = pvm->Step();
} while (ret->CyclesLeft > 0);
return ret;
}
/*
*--------------------------------------------------------------------
* Method: PrintLoadBinImgErr()
* Purpose: Print the warning/error message after loading binary
* image.
* Arguments: err - integer, error code
* Returns: n/a
*--------------------------------------------------------------------
*/
void PrintLoadBinImgErr(int err)
{
bool pressenter = true;
switch (err) {
case 1: cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
case 2: cout << "WARNING: Unable to open memory image file." << endl;
break;
case 3: cout << "WARNING: Problem with binary image header." << endl;
break;
case 4: cout << "WARNING: No header found in binary image." << endl;
break;
case 5: cout << "WARNING: Problem with binary image header." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
case 6: cout << "WARNING: No header found in binary image." << endl;
cout << "WARNING: Unexpected EOF (image shorter than 64kB)." << endl;
break;
default: pressenter = false; break;
}
if (pressenter) {
cout << "Press [ENTER]...";
getchar();
}
}
/*
*--------------------------------------------------------------------
* Method: PrintLoadHexImgErr()
* Purpose: Print the warning/error message after loading Intel HEX
* image.
* Arguments: err - integer, error code
* Returns: n/a
*--------------------------------------------------------------------
*/
void PrintLoadHexImgErr(int err)
{
bool pressenter = true;
switch (err) {
case 1: cout << "WARNING: Unable to open file." << endl;
break;
case 2: cout << "ERROR: Syntax error." << endl;
break;
case 3: cout << "ERROR: Intel HEX format error." << endl;
break;
default: pressenter = false; break;
}
if (pressenter) {
cout << "Press [ENTER]...";
getchar();
}
}
#if defined(LINUX)
@ -233,14 +337,14 @@ void ShowMenu()
{
cout << "------------------------------------+----------------------------------------" << endl;
cout << " C - continue, S - step | A - set address for next step" << endl;
cout << " G - go/cont. from new address | N - go number of steps" << endl;
cout << " G - go/cont. from new address | N - go number of steps, P - IRQ" << endl;
cout << " I - toggle char I/O emulation | X - execute from new address" << endl;
cout << " T - show I/O console | B - blank (clear) screen" << endl;
cout << " E - toggle I/O local echo | F - toggle registers animation" << endl;
cout << " J - set animation delay | M - dump memory, W - write memory" << endl;
cout << " K - toggle ROM emulation | R - show registers" << endl;
cout << " K - toggle ROM emulation | R - show registers, Y - snapshot" << endl;
cout << " L - load memory image | O - display op-codes history" << endl;
cout << " D - disassemble code in memory | Q - quit, H - help" << endl;
cout << " D - disassemble code in memory | Q - quit, 0 - reset, H - help" << endl;
cout << "------------------------------------+----------------------------------------" << endl;
}
@ -274,7 +378,7 @@ void ShowMenu()
while(step && nsteps > 1 && !brk && !lrts && !opbrk) { \
cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct; \
cout << " \r"; \
preg = pvm->Step(); \
preg = RunSingleCurrInstr(); \
if (anim) { \
if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; } \
pvm->ScrHome(); \
@ -292,6 +396,7 @@ void ShowMenu()
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
bool loadbin = false, loadhex = false, reset = false, execvm = false;
#if defined(LINUX)
signal(SIGINT, trap_signal);
signal(SIGTERM, trap_signal);
@ -301,17 +406,48 @@ int main(int argc, char** argv) {
#endif
string romfile("dummy.rom"), ramfile("dummy.ram");
if (argc > 1) {
ramfile = argv[1];
if (argc > 2) {
reset = execvm = loadbin = (0 == strcmp(argv[1], "-r")); // load binary image and reset
if (!loadbin) loadbin = (0 == strcmp(argv[1], "-b")); // just load binary image
if (!loadbin) loadhex = (0 == strcmp(argv[1], "-x")); // just load Intel HEX image
if (loadbin && loadhex) {
cout << "ERROR: Can't load both formats at the same time." << endl;
exit(-1);
}
ramfile = argv[2];
} else {
if (0 == strcmp(argv[1], "-h")) {
CmdArgHelp(argv[0]);
exit(0);
}
ramfile = argv[1];
}
}
try {
cout << endl;
pvm = new VMachine(romfile, ramfile);
if (loadbin) {
pvm = new VMachine(romfile, "dummy.ram");
if (NULL != pvm) {
PrintLoadBinImgErr (pvm->LoadRAMBin(ramfile));
if (!reset && !execvm)
reset = execvm = pvm->IsAutoReset();
}
} else if (loadhex) {
pvm = new VMachine(romfile, "dummy.ram");
if (NULL != pvm) PrintLoadHexImgErr (pvm->LoadRAMHex(ramfile));
}
else {
pvm = new VMachine(romfile, ramfile);
reset = execvm = pvm->IsAutoReset();
}
if (NULL == pvm) {
throw MKGenException("Out of memory");
}
pvm->ClearScreen();
cout << "Virtual Machine/CPU Emulator (MOS 6502) and Debugger." << endl;
cout << "Copyright (C) by Marek Karcz 2016. All rights reserved." << endl;
CopyrightBanner();
string cmd;
bool runvm = false, step = false, brk = false, execaddr = false, stop = true;
bool lrts = false, execvm = false, anim = false, enrom = pvm->IsROMEnabled();
bool lrts = false, anim = false, enrom = pvm->IsROMEnabled();
unsigned int newaddr = pvm->GetRunAddr(), ioaddr = pvm->GetCharIOAddr(), tmpaddr = 0x0000;
unsigned int rombegin = pvm->GetROMBegin(), romend = pvm->GetROMEnd(), delay = ANIM_DELAY;
int nsteps = 0;
@ -325,11 +461,11 @@ int main(int argc, char** argv) {
if (anim) pvm->ClearScreen();
int stct = 1;
if (execaddr) {
preg = ((step) ? pvm->Step(newaddr) : pvm->Run(newaddr));
preg = ((step) ? RunSingleInstr(newaddr) : pvm->Run(newaddr));
RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay);
execaddr = false;
} else {
preg = ((step) ? pvm->Step() : pvm->Run());
preg = ((step) ? RunSingleCurrInstr() : pvm->Run());
RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay);
}
if (step)
@ -338,7 +474,13 @@ int main(int argc, char** argv) {
runvm = step = false;
newaddr = 0x10000;
} else if (execvm) {
preg = (execaddr ? pvm->Exec(newaddr) : pvm->Exec());
if (reset) {
pvm->Reset();
preg = pvm->GetRegs();
reset = false;
} else {
preg = (execaddr ? pvm->Exec(newaddr) : pvm->Exec());
}
execvm = false;
execaddr = false;
brk = preg->SoftIrq;
@ -368,6 +510,24 @@ int main(int argc, char** argv) {
char c = tolower(cmd.c_str()[0]);
if (c == 'h') { // display help
ShowHelp();
} else if (c == 'p') { // Interrupt ReQuest
pvm->Interrupt();
cout << "OK" << endl;
} else if (c == 'y') { // save snapshot of current CPU and memory in binary image
string name;
cout << "Enter file name: ";
cin >> name;
cout << " [" << name << "]" << endl;
if (0 == pvm->SaveSnapshot(name)) {
cout << "OK" << endl;
} else {
cout << "ERROR!" << endl;
cout << "errno=" << errno << endl;
}
} else if (c == '0') { // reset CPU
reset = true;
execvm = true;
runvm = false;
} else if (c == 'o') {
queue<string> exechist(pvm->GetExecHistory());
cout << "PC : INSTR ACC | X | Y | PS | SP" << endl;
@ -378,8 +538,8 @@ int main(int argc, char** argv) {
}
} else if (c == 'l') { // load memory image
char typ = 0;
while (tolower(typ) != 'b' && tolower(typ) != 'd') {
cout << "Type (B - binary/D - definition): ";
while (tolower(typ) != 'b' && tolower(typ) != 'd' && tolower(typ) != 'h') {
cout << "Type (B - binary/H - Intel HEX/D - definition): ";
cin >> typ;
}
cout << " [" << ((tolower(typ) == 'b') ? "binary" : "definition") << "]" << endl;
@ -387,7 +547,8 @@ int main(int argc, char** argv) {
cout << "Memory Image File Name: ";
cin >> name;
cout << " [" << name << "]" << endl;
if (typ == 'b') pvm->LoadRAMBin(name);
if (typ == 'b') PrintLoadBinImgErr (pvm->LoadRAMBin(name));
else if (typ == 'h') PrintLoadHexImgErr (pvm->LoadRAMHex(name));
else {
pvm->LoadRAM(name);
if (pvm->IsAutoExec()) execvm = true;
@ -542,8 +703,63 @@ int main(int argc, char** argv) {
/*
*--------------------------------------------------------------------
* Method: ShowHel2p()
* Purpose: Display commands help.
* Method: CopyrightBanner()
* Purpose: Display copyright information.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
*/
void CopyrightBanner()
{
cout << "Virtual Machine/CPU Emulator (MOS 6502) and Debugger." << endl;
cout << "Copyright (C) by Marek Karcz 2016. All rights reserved." << endl;
}
/*
*--------------------------------------------------------------------
* Method: CmdArgHelp()
* Purpose: Display command line arguments help/Usage.
* Arguments: prgname - string, program name
* Returns: n/a
*--------------------------------------------------------------------
*/
void CmdArgHelp(string prgname)
{
CopyrightBanner();
cout << endl << endl;
cout << "Usage:" << endl << endl;
cout << "\t" << prgname;
cout << " [-h] | [ramdeffile] | [-b ramimage] | [-r ramimage]" << endl;
cout << "\tOR" << endl;
cout << "\t" << prgname << " [-x intelheximage]";
cout << endl << endl;
cout << "Where:" << endl << endl;
cout << "\tramdeffile - RAM definition file name" << endl;
cout << "\tintelheximage - Intel HEX format file" << endl;
cout << "\tramimage - RAM binary image file name" << endl;
cout << R"(
When ran with no arguments, program will load default memory
definition files: default.rom, default.ram and will enter the debug
console menu.
When ramdeffile argument is provided, program will load the memory
definition from the file, set the flags and parameters depending on the
contents of the memory definition file and enter the corresponding mode
of operation as defined in that file.
If used with flag -b or -x, program will load memory from the provided image
file and enter the debug console menu.
If used with flag -r, program will load memory from the provided image
file and execute CPU reset sequence.
)";
cout << endl;
}
/*
*--------------------------------------------------------------------
* Method: ShowHelp()
* Purpose: Display Debugger Console Command Reference help.
* Arguments: n/a
* Returns: n/a
*--------------------------------------------------------------------
@ -579,6 +795,8 @@ N - go number of steps
Where: steps - number of steps in decimal format
Execute number of opcodes provided in steps argument starting
from current address.
P - IRQ
Send maskable interrupt request to CPU (set the IRQ line LOW).
W - write to memory
Usage: W [address] [hexval] [hexval] ... 100
Where: address - memory addr. in hexadecimal format [0000.FFFF],
@ -593,6 +811,10 @@ I - toggle char I/O emulation
are interpreted as console character input.
R - show registers
Displays CPU registers, flags and stack.
Y - snapshot
Usage: Y [file_name]
Where: file_name - the name of the output file.
Save snapshot of current CPU and memory in a binary file.
T - show I/O console
Displays/prints the contents of the virtual console screen.
Note that in run mode (commands X, G or C), virtual screen is
@ -623,13 +845,13 @@ K - toggle ROM emulation
L - load memory image
Usage: L [image_type] [image_name]
Where:
image_type - B (binary) OR D (definition),
image_type - B (binary), H (Intel HEX) OR D (definition),
image_name - name of the image file.
This function allows to load new memory image from either binary
image file or the ASCII definition file. The binary image is always
loaded from address 0x0000 and can be up to 64kB long. The definition
file format is a plain text file that can contain following keywords
and data:
image file, Intel HEX format file or the ASCII definition file.
The binary image is always loaded from address 0x0000 and can be up to
64kB long. The definition file format is a plain text file that can
contain following keywords and data:
ADDR This keyword defines the run address of the executable code.
It is optional, but if exists, it must be the 1-st keyword
@ -673,6 +895,9 @@ L - load memory image
executed after the memory image is loaded.
The next line that follows this keyword sets the address
in decimal or hexadecimal format.
RESET Enables auto-reset of the CPU. After loading the memory
definition file, the CPU reset sequence will be initiated.
O - display op-codes history
Show the history of last executed op-codes/instructions, full with
@ -682,6 +907,16 @@ D - diassemble code in memory
Where: startaddr,endaddr - hexadecimal address [0000..FFFF].
Attempt to disassemble code in specified address range and display
the results (print) on the screen in symbolic form.
0 - reset
Run the processor initialization sequence, just like the real CPU
when its RTS signal is set to LOW and HIGH again. CPU will disable
interrupts, copy address from vector $FFFC to processors PC and will
start executing code. Programmer must put initialization routine
under address pointed by $FFFC vector, which will set the arithmetic
mode, initialize stack, I/O devices and enable IRQ if needed before
jumping to main loop. The reset routine disables trapping last RTS
opcode if stack is empty, so the VM will never return from opcodes
execution loop unless user interrupts with CTRL-C or CTRL-Break.
NOTE:
1. If no arguments provided, each command will prompt user to enter
@ -690,7 +925,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).
)";
cout << endl;
}
}

View File

@ -9,7 +9,7 @@ LIBS = -static-libgcc -m32 -g3 -ltermcap
CLIBS = -static-libgcc -m32 -g3
INCS =
CXXINCS =
CXXFLAGS = $(CXXINCS) -m32 -std=c++0x -Wall -pedantic -g3
CXXFLAGS = $(CXXINCS) -m32 -std=c++0x -Wall -pedantic -g3 -fpermissive
#CFLAGS = $(INCS) -m32 -std=c++0x -Wall -pedantic -g3
CFLAGS = $(INCS) -m32 -Wall -pedantic -g3
RM = rm -f

View File

@ -1,5 +1,5 @@
; Created with BIN2HEX (C) Marek Karcz 2016. All rights reserved.
; 03/13/16 00:08:59
; Mon Mar 14 00:17:15 2016
ADDR
$0400
ORG

View File

@ -13,6 +13,8 @@ $F0 $06 $8D $00 $E0 $E8 $D0 $F5
$00 $00 $EA $4C $00 $02 $45 $6E
$74 $65 $72 $20 $74 $65 $78 $74
$3A $00 $00 $00 $00 $00 $00 $00
ORG
$FFFC
$00 $02
ENIO
EXEC
$0200
RESET

View File

@ -54,20 +54,27 @@
;
; 2/15/2016
; Ported to my own 6502 emulator.
;
; 3/30/2016
;
; Changed char input address from blocking to non-blocking.
;
;
;--------------------------------------------------------------------------------------
;.segment "BASIC"
.segment "BEGN"
.ORG $0000
.segment "BASIC"
;
; Tiny Basic starts here
;
.org $0400 ; Start of Basic. First 1K of ROM is shaded by I/O on the OMS-02
.ORG $4400 ; Start of Basic. First 1K of ROM is shaded by I/O on the OMS-02
SOBAS:
;CLRSC = ClearScreen
C_00BC = $00BC ; These are needed because my assembler
C_20 = $20 ; does not hanle standard 6502 notation
C_22 = $22 ; properly
@ -86,8 +93,8 @@ SR_V_H = SOBAS + $1F ; Base address for subroutine vector hi byte
CV: jmp COLD_S ; Cold start vector
WV: jmp WARM_S ; Warm start vector
IN_V: jmp RCCHR ; Input routine address.
OUT_V: jmp SNDCHR ; Output routine address.
IN_V: jmp GetCh ; Input routine address.
OUT_V: jmp PutCh ; Output routine address.
BREAK: nop ; Begin dummy break routine
clc ; Clear the carry flag
rts ; End break routine
@ -247,7 +254,7 @@ LBL003: .byte >ILTBL ;$1B ; $1B - hi byte of IL address
COLD_S: lda #$00 ; Load accumulator with lo byte of lower and upper prg memory limits
sta $20 ; Store in $20
sta $22 ; Store in $22
lda #$1C ; Load accumulator with hi byte of lower and upper prg memory limits
lda #$50 ; Load accumulator with hi byte of lower and upper prg memory limits
sta $21 ; Store in $21
sta $23 ; Store in $23
; NOTE: $22,$23 vector will be updated by routine below to be the upper RAM limit for TB.
@ -1288,9 +1295,9 @@ ILTBL: .byte $24, $3A, $91, $27, $10, $E1, $59, $C5, $2A, $56, $10, $11, $2C,
; End of Tiny Basic
;.segment "MAIN"
.segment "MAIN"
.org $0CF0 ; Address of main program
.org $4CF0 ; Address of main program
; Code needs work below here, BIOS must be changed for MKHBC-8-R1
@ -1316,57 +1323,45 @@ ILTBL: .byte $24, $3A, $91, $27, $10, $E1, $59, $C5, $2A, $56, $10, $11, $2C,
; sta ACIACN ; 2400, 8 bits, 1 stop bit, external Rx clock
;--------------------
main:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
main: cli
cld
jsr CLRSC ; Go clear the screen
ldy #$00 ; Offset for welcome message and prompt
jsr SNDMSG ; Go print it
ST_LP: JSR RCCHR ; Go get a character from the console
ST_LP: JSR GetCh ; Go get a character from the console
cmp #$43 ; Check for 'C'
BNE IS_WRM ; If not branch to next check
jmp COLD_S ; Otherwise cold-start Tiny Basic
IS_WRM: cmp #$57 ; Check for 'W'
BNE PRMPT ; If not, branch to re-prompt them
jmp WARM_S ; Otherwise warm-start Tiny Basic
PRMPT: ldx #$2F ; Offset of prompt
PRMPT: ldy #$00 ; Offset of prompt
jsr SNDMSG ; Go print the prompt
jmp ST_LP ; Go get the response
;.segment "MESG"
.segment "MESG"
.org $0E00 ; Address of message area
.org $4E00 ; Address of message area
MBLK:
;
; The message block begins at $1E00 and is at most 256 bytes long.
; The message block begins at $4E00 and is at most 256 bytes long.
; Messages terminate with an FF.
;
.byte "OMEGA MICRO SYSTEMS"
.byte "MKHBC-8-R2 TINY BASIC 6502 PORT"
.byte $0D, $0A ;, $0A
.byte "MKHBC-8-R1 TINY BASIC 6502 PORT"
.byte "Adapted from OMEGA MICRO SYSTEMS."
.byte $0D, $0A ;, $0A
.byte "Version: 1.0.2, 1/11/2012"
.byte "Version: 1.0.3, 3/31/2016"
.byte $0D, $0A ;, $0A
.byte "(NOTE: Use caps for Basic)"
.byte "(NOTE: Use upper case letters.)"
.byte $0D, $0A ;, $0A
.byte "Boot ([C]old/[W]arm)? "
.byte $07, $FF
;.segment "SUBR"
.segment "SUBR"
.org $0F00 ;address of subroutine area
.org $4F00 ;address of subroutine area
SBLK:
;
@ -1377,14 +1372,6 @@ SBLK:
StrPtr = $E0
; Kernel jump table
GetCh = $FFED
PutCh = $FFF0
Gets = $FFF3
Puts = $FFF6
;SNDCHR = PutCh
;RCCHR = GetCh
@ -1426,9 +1413,9 @@ TwoBytePeek:
CLRSC: ldx #$19 ; Load X - we're going tp print 25 lines
lda #$0D ; CR
jsr SNDCHR ; Send a carriage retuen
jsr PutCh ; Send a carriage retuen
lda #$0A ; LF
CSLP: jsr SNDCHR ; Send the line feed
CSLP: jsr PutCh ; Send the line feed
dex ; One less to do
bne CSLP ; Go send another untill we're done
rts ; Return
@ -1440,57 +1427,73 @@ CSLP: jsr SNDCHR ; Send the line feed
SNDMSG: lda MBLK,y ; Get a character from teh message block
cmp #$FF ; Look for end of message marker
beq EXSM ; Finish up if it is
jsr SNDCHR ; Otherwise send the character
jsr PutCh ; Otherwise send the character
iny ; Increment the pointer
jmp SNDMSG ; Go get next character
EXSM: rts ; Return
;
; Get a character from the ACIA
; Get a character from the character input device
; Runs into SNDCHR to provide echo
;
RCCHR: ;lda ACIAST ; Read the ACAI status to (for OMS-02)
;and #$08 ; Check if there is character in the receiver (for OMS-02)
;beq RCCHR ; Loop util we get one (for OMS-02)
;lda ACIARW ; Load it into the accumulator (for OMS-02)
LDA $E000 ; Check if a character typed (for emulator)
BEQ RCCHR ; Loop until we get one (for emulator)
;RCCHR: jsr GetCh
; jsr SNDCHR ; echo character to the console
; rts
RCCHR: lda $E001 ; Check if a character typed (for emulator, non-blocking)
beq RCCHR ; Loop until we get one (for emulator)
cmp #'a' ; < 'a'?
bcc SNDCHR ; yes, done
cmp #'{' ; >= '{'?
bcs SNDCHR ; yes, done
and #$5f ; convert to upper case
;
;Send a character to the ACIA
; Send a character to the character output device
;
SNDCHR: sta $FE ; Save the character to be printed
cmp #$FF ; Check for a bunch of characters
BEQ EXSC ; that we don't want to print to
beq EXSC ; that we don't want to print to
cmp #$00 ; the terminal and discard them to
BEQ EXSC ; clean up the output
beq EXSC ; clean up the output
cmp #$91 ;
BEQ EXSC ;
beq EXSC ;
cmp #$93 ;
BEQ EXSC ;
beq EXSC ;
cmp #$80 ;
BEQ EXSC ;
jmp SCLP
jsr PutCh ; MKHBC-8-R1
lda $fe
rts
SCLP: ;lda ACIAST ; Check ACIA Status (don't need for emulator)
;and #$10 ; See if transmiter it busy (don't need for emulator)
;beq SCLP ; Wait for it (don't need for emulator)
lda $FE ; Restore the character
;sta ACIARW ; Transmit it (for OMS-02)
STA $E000 ; Transmit it (for emulator)
beq EXSC ;
SCLP: lda $FE ; Restore the character
sta $E000 ; Transmit it (for emulator)
EXSC: rts ; Return
; .org $1FFC ; Address of reset vector (for 6507 not required for emulator)
;RSVEC
; .byte $F0, $1C ; Reset vector
; Kernel jump table
;GetCh = $FFED
;PutCh = $FFF0
;Gets = $FFF3
;Puts = $FFF6
; String I/O not used at this time.
STRIN: rts
STROUT: rts
; Vector jumps handlers
NmiHandle: rti
RstHandle: jmp main
IrqHandle: rti
.segment "KERN"
.ORG $FFED
GetCh: jmp RCCHR
PutCh: jmp SNDCHR
Gets: jmp STRIN
Puts: jmp STROUT
.segment "VECT"
.ORG $FFFA
.byte <NmiHandle, >NmiHandle, <RstHandle, >RstHandle, <IrqHandle, >IrqHandle
; .org $3000 ; Address of last byte of EPROM
;EOROM:
;--------------------------- END ----------------------------------------------------------------------

40
tinybasic.cfg Normal file
View File

@ -0,0 +1,40 @@
########################################################
#
# File: tinybasic.cfg
#
# Purpose: cc65 configuration file for Tiny Basic
# interpreter.
#
# Author: Marek Karcz
#
# Date created: January 2012
#
# Revision history:
#
# 2/13/2012
# Relocated from $0400 to $4400
#
# 3/31/2016
# Adapted for MKBASIC (VM65) emulator.
#
MEMORY {
RAM0: start = $0000, size = $4400, fill = yes;
RAM1: start = $4400, size = $08f0, fill = yes;
RAM2: start = $4cf0, size = $0110, fill = yes;
RAM3: start = $4e00, size = $0100, fill = yes;
RAM4: start = $4f00, size = $b0ed, fill = yes;
KERN: start = $ffed, size = $0d, fill = yes;
VECT: start = $fffa, size = 6;
}
SEGMENTS {
BEGN: load = RAM0, type = rw;
BASIC: load = RAM1, type = rw;
MAIN: load = RAM2, type = rw;
MESG: load = RAM3, type = rw;
SUBR: load = RAM4, type = rw;
KERN: load = KERN, type = ro;
VECT: load = VECT, type = ro;
}