mirror of
https://github.com/makarcz/vm6502.git
synced 2024-12-26 22:31:18 +00:00
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:
parent
dce9babd36
commit
0d47565b9d
@ -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;
|
||||
|
317
ReadMe.txt
317
ReadMe.txt
@ -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
|
||||
|
435
VMachine.cpp
435
VMachine.cpp
@ -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
|
||||
|
14
VMachine.h
14
VMachine.h
@ -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
151
bin2hex.c
@ -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;
|
||||
}
|
||||
|
11
ehbas.dat
11
ehbas.dat
@ -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
283
main.cpp
@ -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;
|
||||
}
|
||||
}
|
||||
|
2
makefile
2
makefile
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
157
tinybasic.asm
157
tinybasic.asm
@ -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
40
tinybasic.cfg
Normal 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user