1
0
mirror of https://github.com/makarcz/vm6502.git synced 2024-12-28 05:29:47 +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:
Marek Karcz 2016-04-17 22:54:35 -04:00
parent dce9babd36
commit 0d47565b9d
14 changed files with 5852 additions and 2650 deletions

4459
MKCpu.cpp

File diff suppressed because it is too large Load Diff

178
MKCpu.h
View File

@ -29,6 +29,9 @@ struct Regs {
int LastOpCode; // op-code of last instruction int LastOpCode; // op-code of last instruction
unsigned short LastArg; // argument to the last instruction unsigned short LastArg; // argument to the last instruction
int LastAddrMode; // addressing mode of last instruction int LastAddrMode; // addressing mode of last instruction
bool IrqPending; // pending Interrupt ReQuest (IRQ)
int CyclesLeft; // # of cycles left to complete current opcode
bool PageBoundary; // true if page boundary was crossed
}; };
/* /*
@ -351,11 +354,16 @@ enum eOpCodes {
OPCODE_ILL_FF = 0xFF // illegal opcode OPCODE_ILL_FF = 0xFF // illegal opcode
}; };
class MKCpu;
typedef void (MKCpu::*OpCodeHdlrFn)();
struct OpCode { struct OpCode {
int code; // the byte value of the opcode int code; // the byte value of the opcode
int addrmode; // addressing mode (see eAddrModes) int addrmode; // addressing mode (see eAddrModes)
int time; // # of cycles int time; // # of cycles
string amf; // assembler mnemonic string amf; // assembler mnemonic
OpCodeHdlrFn pfun; // opcoce handler function
}; };
typedef map<eOpCodes,OpCode> OpCodesMap; typedef map<eOpCodes,OpCode> OpCodesMap;
@ -409,15 +417,20 @@ class MKCpu
{ {
public: public:
bool mExitAtLastRTS;
MKCpu(); MKCpu();
MKCpu(Memory *pmem); MKCpu(Memory *pmem);
~MKCpu(); ~MKCpu();
Regs *ExecOpcode(unsigned short memaddr); Regs *ExecOpcode(unsigned short memaddr);
Regs *GetRegs(); Regs *GetRegs();
void SetRegs(Regs r);
queue<string> GetExecHistory(); queue<string> GetExecHistory();
unsigned short Disassemble(unsigned short addr, unsigned short Disassemble(unsigned short addr,
char *instrbuf); // Disassemble instruction in memory, return next instruction addr. char *instrbuf); // Disassemble instruction in memory, return next instruction addr.
void Reset(); // reset CPU
void Interrupt(); // Interrupt ReQuest (IRQ)
protected: protected:
@ -455,6 +468,163 @@ class MKCpu
int mode); // Get argument from address with specified addr. mode int mode); // Get argument from address with specified addr. mode
unsigned short Disassemble(); // Disassemble instruction and argument per addressing mode unsigned short Disassemble(); // Disassemble instruction and argument per addressing mode
void Add2History(string s); // add entry to op-codes execute history void Add2History(string s); // add entry to op-codes execute history
bool PageBoundary(unsigned short startaddr,
unsigned short endaddr); // detect if page boundary was crossed
// opcode execute methods
void OpCodeBrk();
void OpCodeNop();
void OpCodeLdaIzx();
void OpCodeLdaZp();
void OpCodeLdaImm();
void OpCodeLdaAbs();
void OpCodeLdaIzy();
void OpCodeLdaZpx();
void OpCodeLdaAby();
void OpCodeLdaAbx();
void OpCodeLdxImm();
void OpCodeLdxZp();
void OpCodeLdxAbs();
void OpCodeLdxZpy();
void OpCodeLdxAby();
void OpCodeLdyImm();
void OpCodeLdyZp();
void OpCodeLdyAbs();
void OpCodeLdyZpx();
void OpCodeLdyAbx();
void OpCodeTax();
void OpCodeTay();
void OpCodeTxa();
void OpCodeTya();
void OpCodeTsx();
void OpCodeTxs();
void OpCodeStaIzx();
void OpCodeStaZp();
void OpCodeStaAbs();
void OpCodeStaIzy();
void OpCodeStaZpx();
void OpCodeStaAby();
void OpCodeStaAbx();
void OpCodeStxZp();
void OpCodeStxAbs();
void OpCodeStxZpy();
void OpCodeStyZp();
void OpCodeStyAbs();
void OpCodeStyZpx();
void OpCodeBneRel();
void OpCodeBeqRel();
void OpCodeBplRel();
void OpCodeBmiRel();
void OpCodeBvcRel();
void OpCodeBvsRel();
void OpCodeBccRel();
void OpCodeBcsRel();
void OpCodeIncZp();
void OpCodeIncAbs();
void OpCodeIncZpx();
void OpCodeIncAbx();
void OpCodeInx();
void OpCodeDex();
void OpCodeIny();
void OpCodeDey();
void OpCodeJmpAbs();
void OpCodeJmpInd();
void OpCodeOraIzx();
void OpCodeOraZp();
void OpCodeOraImm();
void OpCodeOraAbs();
void OpCodeOraIzy();
void OpCodeOraZpx();
void OpCodeOraAby();
void OpCodeOraAbx();
void OpCodeAslZp();
void OpCodeAslAcc();
void OpCodeAslAbs();
void OpCodeAslZpx();
void OpCodeAslAbx();
void OpCodeJsrAbs();
void OpCodeAndIzx();
void OpCodeAndZp();
void OpCodeAndImm();
void OpCodeAndAbs();
void OpCodeAndIzy();
void OpCodeAndZpx();
void OpCodeAndAby();
void OpCodeAndAbx();
void OpCodeBitZp();
void OpCodeBitAbs();
void OpCodeRolZp();
void OpCodeRolAcc();
void OpCodeRolAbs();
void OpCodeRolZpx();
void OpCodeRolAbx();
void OpCodePhp();
void OpCodePha();
void OpCodePlp();
void OpCodePla();
void OpCodeClc();
void OpCodeSec();
void OpCodeCli();
void OpCodeClv();
void OpCodeCld();
void OpCodeSed();
void OpCodeSei();
void OpCodeRti();
void OpCodeRts();
void OpCodeEorIzx();
void OpCodeEorZp();
void OpCodeEorImm();
void OpCodeEorAbs();
void OpCodeEorIzy();
void OpCodeEorZpx();
void OpCodeEorAby();
void OpCodeEorAbx();
void OpCodeLsrZp();
void OpCodeLsrAcc();
void OpCodeLsrAbs();
void OpCodeLsrZpx();
void OpCodeLsrAbx();
void OpCodeAdcIzx();
void OpCodeAdcZp();
void OpCodeAdcImm();
void OpCodeAdcAbs();
void OpCodeAdcIzy();
void OpCodeAdcZpx();
void OpCodeAdcAby();
void OpCodeAdcAbx();
void OpCodeRorZp();
void OpCodeRorAcc();
void OpCodeRorAbs();
void OpCodeRorZpx();
void OpCodeRorAbx();
void OpCodeCpyImm();
void OpCodeCpyZp();
void OpCodeCpyAbs();
void OpCodeCmpIzx();
void OpCodeCmpZp();
void OpCodeCmpImm();
void OpCodeCmpAbs();
void OpCodeCmpIzy();
void OpCodeCmpZpx();
void OpCodeCmpAby();
void OpCodeCmpAbx();
void OpCodeDecZp();
void OpCodeDecAbs();
void OpCodeDecZpx();
void OpCodeDecAbx();
void OpCodeCpxImm();
void OpCodeCpxZp();
void OpCodeCpxAbs();
void OpCodeSbcZp();
void OpCodeSbcAbs();
void OpCodeSbcIzx();
void OpCodeSbcIzy();
void OpCodeSbcZpx();
void OpCodeSbcAby();
void OpCodeSbcAbx();
void OpCodeSbcImm();
void OpCodeDud();
}; };
} // namespace MKBasic } // namespace MKBasic

View File

@ -225,10 +225,7 @@ unsigned char Memory::ReadCharKb(bool nonblock)
set_conio_terminal_mode(); set_conio_terminal_mode();
#endif #endif
static int c = ' '; static int c = ' ';
//putchar('\n');
if (mIOEcho && isprint(c)) putchar(c); if (mIOEcho && isprint(c)) putchar(c);
//else putchar(' ');
//fputs("<-Character Input (CTRL-Y to BREAK) ?\r",stdout);
fflush(stdout); fflush(stdout);
if (!nonblock) while(!kbhit()); if (!nonblock) while(!kbhit());
else c = 0; else c = 0;
@ -239,8 +236,6 @@ unsigned char Memory::ReadCharKb(bool nonblock)
kill(getpid(),SIGINT); kill(getpid(),SIGINT);
} }
#endif #endif
//fputs(" \r",stdout);
//fflush(stdout);
mCharIOBufIn[mInBufDataEnd] = c; mCharIOBufIn[mInBufDataEnd] = c;
mInBufDataEnd++; mInBufDataEnd++;
if (mInBufDataEnd >= CHARIO_BUF_SIZE) mInBufDataEnd = 0; 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. 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. 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, 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 Depending on your favorite 6502 assembler, you may need to use proper command
line arguments or configuration to achieve properly formatted binary file. line arguments or configuration to achieve properly formatted binary file.
E.g.: if using CL65 from CC65 package, create configuration file that defines 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 are supplied with this project and assembler source code adapted to be
compiled with CL65. compiled with CL65.
Other assemblers may need a different approach or may not be able to generate 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 Program can also load memory image definition file (plain text), which is
a format developed especially for this project. a format developed especially for this project.
@ -86,22 +131,25 @@ ENROM
ENIO ENIO
EXEC EXEC
address address
RESET
Where: Where:
ADDR - label indicating that starting and run address will follow in ADDR - label indicating that starting and run address will follow in
the next line the next line
ORG - label indicating that the address counter will change to the ORG - label indicating that the address counter will change to the
value provided in next line value provided in next line
IOADDR - label indicating that character I/O emulation trap address will IOADDR - label indicating that character I/O emulation trap address will
follow in the next line follow in the next line
ROMBEGIN - label indicating that the emulated read-only memory start address ROMBEGIN - label indicating that the emulated read-only memory start
will follow in the next line address will follow in the next line
ROMEND - label indicating that the emulated read-only memory end address ROMEND - label indicating that the emulated read-only memory end address
will follow in the next line will follow in the next line
ENROM - enable read-only memory emulation ENROM - enable read-only memory emulation
ENIO - enable character I/O emulation ENIO - enable character I/O emulation
EXEC - label indicating that the auto-execute address will follow EXEC - label indicating that the auto-execute address will follow
in the next line 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 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 6502 code, this may be the case. Reconfigure your IOADDR to be inside ROM in
such case and try again. 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. This software is provided with No Warranty.
I (The Author) will not be held responsible for any damage to computer I (The Author) will not be held responsible for any damage to computer

View File

@ -74,7 +74,7 @@ VMachine::~VMachine()
* Purpose: Initialize class. * Purpose: Initialize class.
* Arguments: n/a * Arguments: n/a
* Returns: n/a * Returns: n/a
*-------------------------------------------------------------------- f *--------------------------------------------------------------------
*/ */
void VMachine::InitVM() void VMachine::InitVM()
{ {
@ -82,6 +82,7 @@ void VMachine::InitVM()
mpRAM = new Memory(); mpRAM = new Memory();
mAutoExec = false; mAutoExec = false;
mAutoReset = false;
mCharIOAddr = CHARIO_ADDR; mCharIOAddr = CHARIO_ADDR;
mCharIOActive = mCharIO = false; mCharIOActive = mCharIO = false;
if (NULL == mpRAM) { if (NULL == mpRAM) {
@ -106,10 +107,11 @@ void VMachine::InitVM()
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: * Method: ClearScreen()
* Purpose: * Purpose: Clear the working are of the VM - DOS.
* Arguments: * This is not a part of virtual display emulation.
* Returns: * Arguments: n/a
* Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
void VMachine::ClearScreen() void VMachine::ClearScreen()
@ -151,10 +153,11 @@ void VMachine::ClearScreen()
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: * Method: ScrHome()
* Purpose: * Purpose: Bring the console cursor to home position - DOS.
* Arguments: * This is not a part of virtual display emulation.
* Returns: * Arguments: n/a
* Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
void VMachine::ScrHome() void VMachine::ScrHome()
@ -175,10 +178,11 @@ void VMachine::ScrHome()
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: * Method: ClearScreen()
* Purpose: * Purpose: Clear the working are of the VM - Linux.
* Arguments: * This is not a part of virtual display emulation.
* Returns: * Arguments: n/a
* Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
void VMachine::ClearScreen() void VMachine::ClearScreen()
@ -188,10 +192,11 @@ void VMachine::ClearScreen()
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: * Method: ScrHome()
* Purpose: * Purpose: Bring the console cursor to home position - Linux.
* Arguments: * This is not a part of virtual display emulation.
* Returns: * Arguments: n/a
* Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
void VMachine::ScrHome() void VMachine::ScrHome()
@ -203,10 +208,10 @@ void VMachine::ScrHome()
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: * Method: ShowDisp()
* Purpose: * Purpose: Show the emulated virtual text display device contents.
* Arguments: * Arguments: n/a
* Returns: * Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
void VMachine::ShowDisp() void VMachine::ShowDisp()
@ -234,7 +239,7 @@ Regs *VMachine::Run()
ShowDisp(); ShowDisp();
while (true) { while (true) {
cpureg = Step(); cpureg = Step();
if (mCharIO) { if (cpureg->CyclesLeft == 0 && mCharIO) {
ShowDisp(); ShowDisp();
} }
if (cpureg->SoftIrq || mOpInterrupt) if (cpureg->SoftIrq || mOpInterrupt)
@ -264,8 +269,8 @@ Regs *VMachine::Run(unsigned short addr)
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: Exec() * Method: Exec()
* Purpose: Run VM from current address until last RTS. * Purpose: Run VM from current address until last RTS (if enabled).
* NOTE: Stack must be empty! * NOTE: Stack must be empty for last RTS to be trapped.
* Arguments: n/a * Arguments: n/a
* Returns: Pointer to CPU registers and flags. * Returns: Pointer to CPU registers and flags.
*-------------------------------------------------------------------- *--------------------------------------------------------------------
@ -279,7 +284,7 @@ Regs *VMachine::Exec()
ShowDisp(); ShowDisp();
while (true) { while (true) {
cpureg = Step(); cpureg = Step();
if (mCharIO) { if (cpureg->CyclesLeft == 0 && mCharIO) {
cout << mpDisp->GetLastChar(); cout << mpDisp->GetLastChar();
cout << flush; cout << flush;
} }
@ -322,7 +327,7 @@ Regs *VMachine::Step()
addr = cpureg->PtrAddr; addr = cpureg->PtrAddr;
mRunAddr = addr; mRunAddr = addr;
if (mCharIOActive && !mOpInterrupt) { if (cpureg->CyclesLeft == 0 && mCharIOActive && !mOpInterrupt) {
char c = -1; char c = -1;
mCharIO = false; mCharIO = false;
while ((c = mpRAM->GetCharOut()) != -1) { while ((c = mpRAM->GetCharOut()) != -1) {
@ -380,35 +385,340 @@ void VMachine::LoadRAM(string ramfname)
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: LoadRAMBin() * Method: HasHdrData()
* Purpose: Load data from binary image file to the memory. * Purpose: Check for header in the binary memory image.
* Arguments: ramfname - name of the RAM file definition * 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 * 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; FILE *fp = NULL;
unsigned short addr = 0x0000; unsigned short addr = 0x0000;
int n = 0; int n = 0;
Memory *pm = mpRAM; Memory *pm = mpRAM;
int ret = 2;
if ((fp = fopen(ramfname.c_str(), "rb")) != NULL) { 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)) { while (0 == feof(fp) && 0 == ferror(fp)) {
unsigned char val = fgetc(fp); unsigned char val = fgetc(fp);
pm->Poke8bit(addr, val); pm->Poke8bit(addr, val);
addr++; n++; addr++; n++;
} }
fclose(fp); fclose(fp);
// restore emulation facilities status
if (tmp1) SetCharIO(mCharIOAddr, false);
if (tmp2) EnableROM();
if (n <= 0xFFFF) { 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; return ret;
cout << "Press [ENTER]..."; }
getchar();
} /*
*--------------------------------------------------------------------
* 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; Memory *pm = pmem;
if ((fp = fopen(memfname.c_str(), "r")) != NULL) { 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)) while (0 == feof(fp) && 0 == ferror(fp))
{ {
line[0] = '\0'; line[0] = '\0';
@ -593,6 +907,11 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
} }
continue; continue;
} }
// auto reset
if (0 == strncmp(line, "RESET", 5)) {
mAutoReset = true;
continue;
}
// define ROM begin address // define ROM begin address
if (0 == strncmp(line, "ROMBEGIN", 8)) { if (0 == strncmp(line, "ROMBEGIN", 8)) {
line[0] = '\0'; line[0] = '\0';
@ -651,7 +970,7 @@ void VMachine::LoadMEM(string memfname, Memory *pmem)
else else
pm->SetROM(rombegin, romend); pm->SetROM(rombegin, romend);
} else { } else {
if (enrom) pm->EnableROM(); if (enrom) EnableROM();
} }
if (enio) { if (enio) {
SetCharIO(mCharIOAddr, false); SetCharIO(mCharIOAddr, false);
@ -796,6 +1115,19 @@ bool VMachine::IsAutoExec()
return mAutoExec; 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: * Method:
@ -954,4 +1286,33 @@ unsigned short VMachine::Disassemble(unsigned short addr, char *buf)
return mpCPU->Disassemble(addr, 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 } // namespace MKBasic

View File

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

151
bin2hex.c
View File

@ -24,29 +24,51 @@ int g_nStartAddr = 2048; /* $0800 */
int g_nExecAddr = 2048; /* $0800 */ int g_nExecAddr = 2048; /* $0800 */
int g_nSuppressAutoExec = 1; int g_nSuppressAutoExec = 1;
int g_nSuppressAllZeroRows = 0; int g_nSuppressAllZeroRows = 0;
int g_nConvert2IntelHex = 0;
void ScanArgs(int argc, char *argv[]); void ScanArgs(int argc, char *argv[]);
void ConvertFile(void); void ConvertFile(void);
void Convert2IntelHex(void);
/*
*--------------------------------------------------------------------
* Method: Usage()
* Purpose: Print usage information/help.
* Arguments: char * - program name.
* Returns: n/a
*--------------------------------------------------------------------
*/
void Usage(char *prgn) 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("Copyright: Marek Karcz 2016. All rights reserved.\n");
printf("Free for personal and educational use.\n\n"); printf("Free for personal and educational use.\n\n");
printf("Usage:\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("Where:\n\n");
printf(" input_fname - binary file name\n"); printf(" input - binary file name\n");
printf(" output_fname - output file name\n"); printf(" output - output file name\n");
printf(" load_addr - starting address to load data (default: %d)\n", g_nStartAddr); printf(" 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(" exec - address to auto-execute code from (default: %d)\n", g_nExecAddr);
printf(" -s - suppress auto-execute statement in output\n"); printf(" -s - suppress auto-execute statement in output\n");
printf(" -z - suppress data blocks with 0-s only\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"); 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[]) void ScanArgs(int argc, char *argv[])
{ {
@ -85,11 +107,24 @@ void ScanArgs(int argc, char *argv[])
{ {
g_nSuppressAllZeroRows = 1; g_nSuppressAllZeroRows = 1;
} }
else if (strcmp(argv[n],"-i") == 0)
{
g_nConvert2IntelHex = 1;
}
n++; 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) void ConvertFile(void)
{ {
FILE *fpi = NULL; FILE *fpi = NULL;
@ -170,6 +205,96 @@ void ConvertFile(void)
printf("ERROR: Unable to open input file.\n"); 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[]) int main(int argc, char *argv[])
{ {
if (argc == 1) if (argc == 1)
@ -178,8 +303,12 @@ int main(int argc, char *argv[])
ScanArgs(argc, argv); ScanArgs(argc, argv);
if (*g_szInputFileName == 0 || *g_szHexFileName == 0) if (*g_szInputFileName == 0 || *g_szHexFileName == 0)
Usage(argv[0]); Usage(argv[0]);
else else {
ConvertFile(); if (0 == g_nConvert2IntelHex)
ConvertFile();
else
Convert2IntelHex();
}
} }
return 0; return 0;
} }

View File

@ -10,13 +10,15 @@ $FFE0
ROMBEGIN ROMBEGIN
$FFC0 $FFC0
ROMEND ROMEND
$FFDF $FFFF
;$FFDF
; Enable char IO and ROM ; Enable char IO and ROM
ENIO ENIO
ENROM ENROM
; Auto-execute ; Auto-execute
EXEC ;EXEC
$C000 ;$C000
RESET
; Data/Code ; Data/Code
$D8, $A0, $08, $B9, $FB, $E0, $99, $00 $D8, $A0, $08, $B9, $FB, $E0, $99, $00
$02, $88, $10, $F7, $A2, $FF, $8E, $21 $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 $06, $C9, $7B, $B0, $02, $29, $5F, $38
$60, $18, $60, $8D, $E0, $FF, $29, $FF $60, $18, $60, $8D, $E0, $FF, $29, $FF
$60, $00, $00, $00, $00, $00, $00, $00 $60, $00, $00, $00, $00, $00, $00, $00
ORG
$FFFC
$00 $C0

281
main.cpp
View File

@ -3,6 +3,7 @@
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <string.h>
#include "system.h" #include "system.h"
#include "MKCpu.h" #include "MKCpu.h"
#include "Memory.h" #include "Memory.h"
@ -24,6 +25,109 @@ int g_stackdisp_lines = 1;
bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat); bool ShowRegs(Regs *preg, VMachine *pvm, bool ioecho, bool showiostat);
void ShowHelp(); 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) #if defined(LINUX)
@ -233,14 +337,14 @@ void ShowMenu()
{ {
cout << "------------------------------------+----------------------------------------" << endl; cout << "------------------------------------+----------------------------------------" << endl;
cout << " C - continue, S - step | A - set address for next step" << 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 << " I - toggle char I/O emulation | X - execute from new address" << endl;
cout << " T - show I/O console | B - blank (clear) screen" << 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 << " E - toggle I/O local echo | F - toggle registers animation" << endl;
cout << " J - set animation delay | M - dump memory, W - write memory" << 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 << " 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; cout << "------------------------------------+----------------------------------------" << endl;
} }
@ -274,7 +378,7 @@ void ShowMenu()
while(step && nsteps > 1 && !brk && !lrts && !opbrk) { \ while(step && nsteps > 1 && !brk && !lrts && !opbrk) { \
cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct; \ cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct; \
cout << " \r"; \ cout << " \r"; \
preg = pvm->Step(); \ preg = RunSingleCurrInstr(); \
if (anim) { \ if (anim) { \
if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; } \ if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; } \
pvm->ScrHome(); \ 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 */ /* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) { int main(int argc, char** argv) {
bool loadbin = false, loadhex = false, reset = false, execvm = false;
#if defined(LINUX) #if defined(LINUX)
signal(SIGINT, trap_signal); signal(SIGINT, trap_signal);
signal(SIGTERM, trap_signal); signal(SIGTERM, trap_signal);
@ -301,17 +406,48 @@ int main(int argc, char** argv) {
#endif #endif
string romfile("dummy.rom"), ramfile("dummy.ram"); string romfile("dummy.rom"), ramfile("dummy.ram");
if (argc > 1) { 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 { try {
cout << endl; 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(); pvm->ClearScreen();
cout << "Virtual Machine/CPU Emulator (MOS 6502) and Debugger." << endl; CopyrightBanner();
cout << "Copyright (C) by Marek Karcz 2016. All rights reserved." << endl;
string cmd; string cmd;
bool runvm = false, step = false, brk = false, execaddr = false, stop = true; 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 newaddr = pvm->GetRunAddr(), ioaddr = pvm->GetCharIOAddr(), tmpaddr = 0x0000;
unsigned int rombegin = pvm->GetROMBegin(), romend = pvm->GetROMEnd(), delay = ANIM_DELAY; unsigned int rombegin = pvm->GetROMBegin(), romend = pvm->GetROMEnd(), delay = ANIM_DELAY;
int nsteps = 0; int nsteps = 0;
@ -325,11 +461,11 @@ int main(int argc, char** argv) {
if (anim) pvm->ClearScreen(); if (anim) pvm->ClearScreen();
int stct = 1; int stct = 1;
if (execaddr) { 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); RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay);
execaddr = false; execaddr = false;
} else { } else {
preg = ((step) ? pvm->Step() : pvm->Run()); preg = ((step) ? RunSingleCurrInstr() : pvm->Run());
RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay); RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay);
} }
if (step) if (step)
@ -338,7 +474,13 @@ int main(int argc, char** argv) {
runvm = step = false; runvm = step = false;
newaddr = 0x10000; newaddr = 0x10000;
} else if (execvm) { } 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; execvm = false;
execaddr = false; execaddr = false;
brk = preg->SoftIrq; brk = preg->SoftIrq;
@ -368,6 +510,24 @@ int main(int argc, char** argv) {
char c = tolower(cmd.c_str()[0]); char c = tolower(cmd.c_str()[0]);
if (c == 'h') { // display help if (c == 'h') { // display help
ShowHelp(); 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') { } else if (c == 'o') {
queue<string> exechist(pvm->GetExecHistory()); queue<string> exechist(pvm->GetExecHistory());
cout << "PC : INSTR ACC | X | Y | PS | SP" << endl; 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 } else if (c == 'l') { // load memory image
char typ = 0; char typ = 0;
while (tolower(typ) != 'b' && tolower(typ) != 'd') { while (tolower(typ) != 'b' && tolower(typ) != 'd' && tolower(typ) != 'h') {
cout << "Type (B - binary/D - definition): "; cout << "Type (B - binary/H - Intel HEX/D - definition): ";
cin >> typ; cin >> typ;
} }
cout << " [" << ((tolower(typ) == 'b') ? "binary" : "definition") << "]" << endl; cout << " [" << ((tolower(typ) == 'b') ? "binary" : "definition") << "]" << endl;
@ -387,7 +547,8 @@ int main(int argc, char** argv) {
cout << "Memory Image File Name: "; cout << "Memory Image File Name: ";
cin >> name; cin >> name;
cout << " [" << name << "]" << endl; 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 { else {
pvm->LoadRAM(name); pvm->LoadRAM(name);
if (pvm->IsAutoExec()) execvm = true; if (pvm->IsAutoExec()) execvm = true;
@ -542,8 +703,63 @@ int main(int argc, char** argv) {
/* /*
*-------------------------------------------------------------------- *--------------------------------------------------------------------
* Method: ShowHel2p() * Method: CopyrightBanner()
* Purpose: Display commands help. * 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 * Arguments: n/a
* Returns: n/a * Returns: n/a
*-------------------------------------------------------------------- *--------------------------------------------------------------------
@ -579,6 +795,8 @@ N - go number of steps
Where: steps - number of steps in decimal format Where: steps - number of steps in decimal format
Execute number of opcodes provided in steps argument starting Execute number of opcodes provided in steps argument starting
from current address. from current address.
P - IRQ
Send maskable interrupt request to CPU (set the IRQ line LOW).
W - write to memory W - write to memory
Usage: W [address] [hexval] [hexval] ... 100 Usage: W [address] [hexval] [hexval] ... 100
Where: address - memory addr. in hexadecimal format [0000.FFFF], 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. are interpreted as console character input.
R - show registers R - show registers
Displays CPU registers, flags and stack. 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 T - show I/O console
Displays/prints the contents of the virtual console screen. Displays/prints the contents of the virtual console screen.
Note that in run mode (commands X, G or C), virtual screen is 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 L - load memory image
Usage: L [image_type] [image_name] Usage: L [image_type] [image_name]
Where: 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. image_name - name of the image file.
This function allows to load new memory image from either binary This function allows to load new memory image from either binary
image file or the ASCII definition file. The binary image is always image file, Intel HEX format file or the ASCII definition file.
loaded from address 0x0000 and can be up to 64kB long. The definition The binary image is always loaded from address 0x0000 and can be up to
file format is a plain text file that can contain following keywords 64kB long. The definition file format is a plain text file that can
and data: contain following keywords and data:
ADDR This keyword defines the run address of the executable code. ADDR This keyword defines the run address of the executable code.
It is optional, but if exists, it must be the 1-st keyword It is optional, but if exists, it must be the 1-st keyword
@ -674,6 +896,9 @@ L - load memory image
The next line that follows this keyword sets the address The next line that follows this keyword sets the address
in decimal or hexadecimal format. 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 O - display op-codes history
Show the history of last executed op-codes/instructions, full with Show the history of last executed op-codes/instructions, full with
disassembled mnemonic and argument. disassembled mnemonic and argument.
@ -682,6 +907,16 @@ D - diassemble code in memory
Where: startaddr,endaddr - hexadecimal address [0000..FFFF]. Where: startaddr,endaddr - hexadecimal address [0000..FFFF].
Attempt to disassemble code in specified address range and display Attempt to disassemble code in specified address range and display
the results (print) on the screen in symbolic form. 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: NOTE:
1. If no arguments provided, each command will prompt user to enter 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 by pressing CTRL-C or CTRL-Pause/Break, which will generate
a "Operator Interrupt". However in the character input mode a "Operator Interrupt". However in the character input mode
use CTRL-Y combination or CTRL-Break (DOS), CTRL-C (Linux). 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; cout << endl;
} }

View File

@ -9,7 +9,7 @@ LIBS = -static-libgcc -m32 -g3 -ltermcap
CLIBS = -static-libgcc -m32 -g3 CLIBS = -static-libgcc -m32 -g3
INCS = INCS =
CXXINCS = 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 -std=c++0x -Wall -pedantic -g3
CFLAGS = $(INCS) -m32 -Wall -pedantic -g3 CFLAGS = $(INCS) -m32 -Wall -pedantic -g3
RM = rm -f RM = rm -f

View File

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

View File

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

View File

@ -55,19 +55,26 @@
; 2/15/2016 ; 2/15/2016
; Ported to my own 6502 emulator. ; 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 ; 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: SOBAS:
;CLRSC = ClearScreen
C_00BC = $00BC ; These are needed because my assembler C_00BC = $00BC ; These are needed because my assembler
C_20 = $20 ; does not hanle standard 6502 notation C_20 = $20 ; does not hanle standard 6502 notation
C_22 = $22 ; properly 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 CV: jmp COLD_S ; Cold start vector
WV: jmp WARM_S ; Warm start vector WV: jmp WARM_S ; Warm start vector
IN_V: jmp RCCHR ; Input routine address. IN_V: jmp GetCh ; Input routine address.
OUT_V: jmp SNDCHR ; Output routine address. OUT_V: jmp PutCh ; Output routine address.
BREAK: nop ; Begin dummy break routine BREAK: nop ; Begin dummy break routine
clc ; Clear the carry flag clc ; Clear the carry flag
rts ; End break routine 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 COLD_S: lda #$00 ; Load accumulator with lo byte of lower and upper prg memory limits
sta $20 ; Store in $20 sta $20 ; Store in $20
sta $22 ; Store in $22 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 $21 ; Store in $21
sta $23 ; Store in $23 sta $23 ; Store in $23
; NOTE: $22,$23 vector will be updated by routine below to be the upper RAM limit for TB. ; 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 ; 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 ; 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 ; sta ACIACN ; 2400, 8 bits, 1 stop bit, external Rx clock
;-------------------- ;--------------------
main: main: cli
nop cld
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
jsr CLRSC ; Go clear the screen jsr CLRSC ; Go clear the screen
ldy #$00 ; Offset for welcome message and prompt ldy #$00 ; Offset for welcome message and prompt
jsr SNDMSG ; Go print it 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' cmp #$43 ; Check for 'C'
BNE IS_WRM ; If not branch to next check BNE IS_WRM ; If not branch to next check
jmp COLD_S ; Otherwise cold-start Tiny Basic jmp COLD_S ; Otherwise cold-start Tiny Basic
IS_WRM: cmp #$57 ; Check for 'W' IS_WRM: cmp #$57 ; Check for 'W'
BNE PRMPT ; If not, branch to re-prompt them BNE PRMPT ; If not, branch to re-prompt them
jmp WARM_S ; Otherwise warm-start Tiny Basic 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 jsr SNDMSG ; Go print the prompt
jmp ST_LP ; Go get the response jmp ST_LP ; Go get the response
;.segment "MESG" .segment "MESG"
.org $0E00 ; Address of message area .org $4E00 ; Address of message area
MBLK: 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. ; Messages terminate with an FF.
; ;
.byte "OMEGA MICRO SYSTEMS" .byte "MKHBC-8-R2 TINY BASIC 6502 PORT"
.byte $0D, $0A ;, $0A .byte $0D, $0A ;, $0A
.byte "MKHBC-8-R1 TINY BASIC 6502 PORT" .byte "Adapted from OMEGA MICRO SYSTEMS."
.byte $0D, $0A ;, $0A .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 $0D, $0A ;, $0A
.byte "(NOTE: Use caps for Basic)" .byte "(NOTE: Use upper case letters.)"
.byte $0D, $0A ;, $0A .byte $0D, $0A ;, $0A
.byte "Boot ([C]old/[W]arm)? " .byte "Boot ([C]old/[W]arm)? "
.byte $07, $FF .byte $07, $FF
;.segment "SUBR" .segment "SUBR"
.org $0F00 ;address of subroutine area .org $4F00 ;address of subroutine area
SBLK: SBLK:
; ;
@ -1377,14 +1372,6 @@ SBLK:
StrPtr = $E0 StrPtr = $E0
; Kernel jump table
GetCh = $FFED
PutCh = $FFF0
Gets = $FFF3
Puts = $FFF6
;SNDCHR = PutCh ;SNDCHR = PutCh
;RCCHR = GetCh ;RCCHR = GetCh
@ -1426,9 +1413,9 @@ TwoBytePeek:
CLRSC: ldx #$19 ; Load X - we're going tp print 25 lines CLRSC: ldx #$19 ; Load X - we're going tp print 25 lines
lda #$0D ; CR lda #$0D ; CR
jsr SNDCHR ; Send a carriage retuen jsr PutCh ; Send a carriage retuen
lda #$0A ; LF lda #$0A ; LF
CSLP: jsr SNDCHR ; Send the line feed CSLP: jsr PutCh ; Send the line feed
dex ; One less to do dex ; One less to do
bne CSLP ; Go send another untill we're done bne CSLP ; Go send another untill we're done
rts ; Return rts ; Return
@ -1440,57 +1427,73 @@ CSLP: jsr SNDCHR ; Send the line feed
SNDMSG: lda MBLK,y ; Get a character from teh message block SNDMSG: lda MBLK,y ; Get a character from teh message block
cmp #$FF ; Look for end of message marker cmp #$FF ; Look for end of message marker
beq EXSM ; Finish up if it is beq EXSM ; Finish up if it is
jsr SNDCHR ; Otherwise send the character jsr PutCh ; Otherwise send the character
iny ; Increment the pointer iny ; Increment the pointer
jmp SNDMSG ; Go get next character jmp SNDMSG ; Go get next character
EXSM: rts ; Return EXSM: rts ; Return
; ;
; Get a character from the ACIA ; Get a character from the character input device
; Runs into SNDCHR to provide echo ; Runs into SNDCHR to provide echo
; ;
RCCHR: ;lda ACIAST ; Read the ACAI status to (for OMS-02) RCCHR: lda $E001 ; Check if a character typed (for emulator, non-blocking)
;and #$08 ; Check if there is character in the receiver (for OMS-02) beq RCCHR ; Loop until we get one (for emulator)
;beq RCCHR ; Loop util we get one (for OMS-02) cmp #'a' ; < 'a'?
;lda ACIARW ; Load it into the accumulator (for OMS-02) bcc SNDCHR ; yes, done
LDA $E000 ; Check if a character typed (for emulator) cmp #'{' ; >= '{'?
BEQ RCCHR ; Loop until we get one (for emulator) bcs SNDCHR ; yes, done
and #$5f ; convert to upper case
;RCCHR: jsr GetCh
; jsr SNDCHR ; echo character to the console
; rts
; ;
;Send a character to the ACIA ; Send a character to the character output device
; ;
SNDCHR: sta $FE ; Save the character to be printed SNDCHR: sta $FE ; Save the character to be printed
cmp #$FF ; Check for a bunch of characters 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 cmp #$00 ; the terminal and discard them to
BEQ EXSC ; clean up the output beq EXSC ; clean up the output
cmp #$91 ; cmp #$91 ;
BEQ EXSC ; beq EXSC ;
cmp #$93 ; cmp #$93 ;
BEQ EXSC ; beq EXSC ;
cmp #$80 ; cmp #$80 ;
BEQ EXSC ; beq EXSC ;
jmp SCLP SCLP: lda $FE ; Restore the character
jsr PutCh ; MKHBC-8-R1 sta $E000 ; Transmit it (for emulator)
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)
EXSC: rts ; Return EXSC: rts ; Return
; .org $1FFC ; Address of reset vector (for 6507 not required for emulator) ; Kernel jump table
;RSVEC
; .byte $F0, $1C ; Reset vector ;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 ---------------------------------------------------------------------- ;--------------------------- 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;
}