minor update

Small refactoring and documentation update.
This commit is contained in:
Marek Karcz 2017-07-28 18:58:48 -04:00
parent f4526b73c0
commit c90ad3e7c0
5 changed files with 1378 additions and 173 deletions

945
DevJournal.txt Normal file
View File

@ -0,0 +1,945 @@
-------------------------------------------- TO DO:
* Implement all 6502 opcodes.
STATUS: DONE. Testing passed.
* Implement memory write in external machine code monitor/control program.
STATUS: DONE.
* Implement extended opcodes to make VM easier for BASIC language implementation.
STATUS: Not started.
* Write BASIC compiler for my VM.
STATUS: Not started.
* Step by step debugging (do not rely on BRK).
STATUS: Done.
* In debugger, under Stack pointer display, display the contents of the stack from
the top (*SP) to bottom ($1FF).
Something like:
STOPPED at 46b
Registers:
Acc: $38 (%00111000)
X: $0
Y: $1
Addr: $46b
Acc16: $0
Ptr16: $0
Stack: $fd
[$34 $04]
Flags: NV-BDIZC
00101000
----------------------------------------
STATUS: DONE.
* In debugger add displaying of the decimal values.
STATUS: Not started.
* Add ability to set breakpoints.
This will also need a new execute or continue command variation that will stop
at the next breakpoint.
STATUS: Not started.
* Add basic character I/O emulation.
STATUS: DONE.
* Add displaying last executed op-code and argument.
STATUS: DONE.
* Implement disassembler in the debug console.
STATUS: DONE.
* Add "animation" mode, where in multi-step debugging mode the registers will
be displayed continuously at the fixed area of the screen.
STATUS: DONE.
* Implement time-scale and cycles emulation.
Note that the opcodes map contains only base # of cycles per opcode.
For accurate emulation, add 1 cycle if page boundary is crossed for select
opcodes and 1 cycle on branches taken.
Reference: http://www.oxyron.de/html/opcodes02.html
STATUS: IN PROGRESS.
Since cycle-accurate emulation was introduced, a step in debugger is
no longer a step but a cycle. It should be updated in documentation or
corrected in code so the step calls the MKCpu::Step until the # of
cycles left is 0. - DONE, Step() method is called until # of cycles
is 0.
VMachine::Run correct so the virtual display is shown only at the
end of each instruction, not after each cycle. - DONE.
* Add ability to load binary 6502 code and hex formats.
Should be able to load from command line or debug console.
STATUS: DONE.
* 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.
* Add hot-key to create snapshot/save memory image in binary file
* with header data including next execute address in memory.
STATUS: DONE.
The hot-key was not added. User must interrupt program
(CTRL-C, CTRL-Pause/Break) and then save snapshot from debugger console
menu (Y filename).
* Add ability to configure ROM range and I/O address as well as turn on/off
features in the memory image definition file.
Add keywords:
IOADDR for setting up I/O emulation address.
ENIO for I/O enabling.
ROMBEGIN, ROMEND for setting up ROM address range.
ENROM to enable ROM.
EXEC to automatically execute code at address.
STATUS: DONE.
* Refactor - MKCpu::ExecOpcode()
Replace huge switch/case statement with array of functions.
STATUS: DONE.
* Display emulation - method Display::ShowScr() works well only when console/DOS
window width matches exactly the emulated display width (currently hardcoded
80 characters). Make it work when console is wider. Automatically decrease
emulated display size when console is smaller.
STATUS: COMPLETED.
* Add Reset option in debug console, which will send the processor through its
initialization/reset procedure.
STATUS: DONE.
* Add public API methods to MKCpu class that will trigger IRQ, NMI and RESET.
According to MOS 6502 specs, when the ORQ signal comes, the execution of
current opcode is allowed to finished and then 7-cycle interrupt sequence
is executed.
STATUS: IN PROGRESS.
Reset() added to VMachine and MKCpu.
Interrupt() (IRQ) added to VMachine and MKCpu. Need to make it cycle
accurate.
Need to implement NMI.
* Add automatic recognition of memory image file format.
STATUS: DONE.
* Add graphical display device emulator.
STATUS: In progress.
Abstraction layer for memory mapped devices. - OK
Activate/deactivate graphics display from debugger console. - OK
Activate/deactivate graphics display in memory definition file
and binary header. - DONE.
NOTE: I did not reserve space for future
expansion in header section, therefore I have to recognize the older
version of header by old magic keyword: HDRMAGICKEY_OLD
New header is recognized by HDRMAGICKEY and this one has space for
future expansion (128 bytes of data vs 15 in older format) so there
will be no need to change header format if data are added up to 128
total bytes (currently only 18 bytes used).
Add line draw function/command. - DONE
Add line erase function/command. - DONE
Add rectangle draw/erase function/command. - NOT DONE
Add closed shape fill function/command. - NOT DONE
Add sprites. - NOT DONE.
Add text mode. - IN PROGRESS.
Add VM65 memory mapped raster buffer - NOT DONE.
Review all load/save methods in VMachine.cpp and correct where needed. - DONE.
[Either disable devices emulation facilities for the time of accessing memory
during load/save or skip the memory ranges assigned to active devices.
I can also introduce and use Peek8BitImg/Poke8BitImg methods that ignore memory
mapped devices.]
[Methods to review:
VMachine::SaveSnapshot,
VMachine::LoadRAMBin,
VMachine::LoadRAMHex,
VMachine::LoadMEM]
Implementing with SDL2.
I have a rudimentary graphical display class now which can clear
the screen, set bg/fg colors, set/unset pixel etc.
Now what is needed is some fake VIC chip emulation class
and some elegant way of introducing the device's registers into the
memory address space.
I designed an abstract layer between the memory and memory
mapped device. Any read/write operation on memory checks
the list of memory mapped devices. If the address falls in to the
range of any of these devices, the r/w operation is
redirected to appropriate class entry/handler method.
The devices implementations: MemMapDev.h/MemMapDev.cpp.
Text mode will require:
- the characters table base address (one of the 16 4 KB banks)
- characters table
- cursor and cursor position control/movement/mode
---------------------------------------------- TEXT TEMPLATES:
/*
*--------------------------------------------------------------------
* Method:
* Purpose:
* Arguments:
* Returns:
*--------------------------------------------------------------------
*/
/*
*--------------------------------------------------------------------
* Project: VM65 - Virtual Machine/CPU emulator programming
* framework.
*
* File:
*
* Purpose:
*
* Date:
*
* Copyright: (C) by Marek Karcz 2016. All rights reserved.
*
* Contact: makarcz@yahoo.com
*
* License Agreement and Warranty:
This software is provided with No Warranty.
I (Marek Karcz) will not be held responsible for any damage to
computer systems, data or user's health resulting from use.
Please proceed responsibly and apply common sense.
This software is provided in hope that it will be useful.
It is free of charge for non-commercial and educational use.
Distribution of this software in non-commercial and educational
derivative work is permitted under condition that original
copyright notices and comments are preserved. Some 3-rd party work
included with this project may require separate application for
permission from their respective authors/copyright owners.
*--------------------------------------------------------------------
*/
---------------------------------------------- LOG:
1/13/2016
Implemented ORA and ASL.
Need to test and verify proper functioning of ORA opcodes.
1/14/2016
Tested ORA opcodes.
Corrected bug in addressing mode: izy = ($00),Y
1/15/2016
Corrected problems with relative jump computation.
Implemented some new opcodes (PHP, JSR, CLC).
Optimized some code.
I rely on Piotr Kowalski's 6502 emulator/debugger to figure out some opcodes implementation.
This way I found I implemented incorrectly relative jump and also figured out how PC should
be pushed to stack during JSR.
1/18/2016
Implemented opcodes:
BIT, ROL, PLP, BMI, SEC, RTI, EOR, LSR.
1/21/2016
Implemented opcodes:
PHA, BVC, CLI.
Tested JSR, corrected bugs in JSR, RTI, RTS.
In process of implementing ADC opcodes (most difficult so far and must consider decimal
mode as well).
ADC opcodes implemented, not optimized, not tested.
1/22/2016
ADC being tested and optimized.
Still unsure how Overflow flag (V) should work.
I used this algorithm:
Logic:
t = A + M + P.C
P.V = (A.7!=t.7) ? 1:0
P.N = A.7
P.Z = (t==0) ? 1:0
IF (P.D)
t = bcd(A) + bcd(M) + P.C
P.C = (t>99) ? 1:0
ELSE
P.C = (t>255) ? 1:0
A = t & 0xFF
But Kowalski's emulator works differently.
ADC tested in IMM mode.
Implemented opcodes today:
ROR, BVS, BCC, BCS.
Improvements to debugger (step-by-step added, no need to rely on BRK only).
1/25/2016
Implemented opcodes:
CLV, TSX, CPY, CMP, DEC, DEX, CLD, CPX.
Created method MKCpu::SubWithCarry() (helper for SBC implementation).
To do:
Implement SBC and re-test ADC and SBC - compare to Kowalski's emulator results and
also to real MOS 6502 results.
2/2/2016
Finished implementing opcodes.
2/7/2016
Worknig on multiple steps function in debugger.
Testing opcodes.
2/9/2016
Testing opcodes, testing BCD mode.
I use Kowalski's emulator as my reference.
I fixed many bugs, but still something doesn't work near the end of the procedure (4k+ cycles in).
Code: testbcd.dat (TestBCD.65s).
2/10/2016
There is still problem with SBC opcode.
I get different results than Kowalski's emulator.
Around cycle 4969, address $04b7.
17:00
Still working on SBC opcode.
I may need to re-invent the whole BCD arithmetic thing and implement full internal BCD arithmetics
emulation.
2/12/2016
I finally gave up and shamelessly ripped ADC code from frodo emulator.
It almost works.
I still get unexpected sign flag result in one of the operations.
Frodo code:
/*
* Adc instruction
*/
inline void MOS6502_1541::do_adc(uint8 byte)
{
if (!d_flag) {
uint16 tmp;
// Binary mode
tmp = a + byte + (c_flag ? 1 : 0);
c_flag = tmp > 0xff;
v_flag = !((a ^ byte) & 0x80) && ((a ^ tmp) & 0x80);
z_flag = n_flag = a = tmp;
} else {
uint16 al, ah;
// Decimal mode
al = (a & 0x0f) + (byte & 0x0f) + (c_flag ? 1 : 0); // Calculate lower nybble
if (al > 9) al += 6; // BCD fixup for lower nybble
ah = (a >> 4) + (byte >> 4); // Calculate upper nybble
if (al > 0x0f) ah++;
z_flag = a + byte + (c_flag ? 1 : 0); // Set flags
n_flag = ah << 4; // Only highest bit used
v_flag = (((ah << 4) ^ a) & 0x80) && !((a ^ byte) & 0x80);
if (ah > 9) ah += 6; // BCD fixup for upper nybble
c_flag = ah > 0x0f; // Set carry flag
a = (ah << 4) | (al & 0x0f); // Compose result
}
}
/*
* Sbc instruction
*/
inline void MOS6502_1541::do_sbc(uint8 byte)
{
uint16 tmp = a - byte - (c_flag ? 0 : 1);
if (!d_flag) {
// Binary mode
c_flag = tmp < 0x100;
v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
z_flag = n_flag = a = tmp;
} else {
uint16 al, ah;
// Decimal mode
al = (a & 0x0f) - (byte & 0x0f) - (c_flag ? 0 : 1); // Calculate lower nybble
ah = (a >> 4) - (byte >> 4); // Calculate upper nybble
if (al & 0x10) {
al -= 6; // BCD fixup for lower nybble
ah--;
}
if (ah & 0x10) ah -= 6; // BCD fixup for upper nybble
c_flag = tmp < 0x100; // Set flags
v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
z_flag = n_flag = tmp;
a = (ah << 4) | (al & 0x0f); // Compose result
}
}
I guess I am going to rip off the Frodo's code for now for ADC and SBC.
See what tests results will be.
OK, I ripped Frodo code for ADC/SBC and the results are good.
There is only one inconsistency compared to real 6502, where N-flag returned is wrong.
I fixed some minor issues and added help and memory write command.
I started to implement emulated console Display, class implementation for now.
2/15/2016
I/O emulation somewhat works.
Needs more testing.
NOTE:
* Fix BRK opcode (it is really 2-byte opcode, one padding by te must be added, also it alters the stack! - I didn't implement that).
* I compiled Tiny Basic for my emulator, but it doesn't work.
* Update help function (new commands were added).
17:40
I fixed BRK opcode.
I updated help function.
Corrected bug in I/O emulation (actually it was in memory, the array was too short).
Tiny BASIC - I think it doesn't work because it performs RAM test at the beginning to determine
where RAM ends by writing the memory and reading it back.
I must implement memory protection scheme so I can define addresses where writing is prohibited.
I think I will add keywords ROM_BEGIN and ROM_END to the memory definition file, which will define it at startup for now.
I also need to eliminate separate ROM image from VM. I will invent a different scheme, perhaps MMU-like.
18:25
Some success with Tiny Basic, but still some issues.
2/16/2016
0:10
TB works except it does not print numbers.
E.g.: program listing shows no line numbers, also unable to print integers.
This seems to be a problem with my emulator, because the same code works in Kowalski's emulator.
19:40
Some improvements to I/O (display) emulation.
Still no luck with emulation issue.
02/17/2016
$0F2C - RCCHR
$0F31 - SNDCHR
LBL010: sta $BD
stx $BC
jmp LBL069
JUMP22: ldx $C1 ; entry point to TBIL PN (print number) $07A7
lda $01,X
bpl LBL070
jsr LBL071
lda #$2D
jsr LBL042
LBL070: jsr LBL035
LBL069: lda #$1F
sta $B8
sta $BA
lda #$2A
sta $B9
sta $BB
ldx $BC
ldy $BD
sec
LBL072: inc $B8
txa
sbc #$10
tax
tya
sbc #$27
tay
bcs LBL072
LBL073: dec $B9
txa
adc #$E8
tax
tya
adc #$03
tay
bcc LBL073
txa
LBL074: sec
inc $BA
sbc #$64
bcs LBL074
dey
bpl LBL074
LBL075: dec $BB
adc #$0A
bcc LBL075
ora #$30
sta $BC
lda #$20
sta $BD
ldx #$FB
LBL199: stx $C3
lda $BD,X
ora $BD
cmp #$20
beq LBL076
ldy #$30
sty $BD
ora $BD
jsr LBL042
LBL076: ldx $C3
inx
bne LBL199
rts
4:50
Testing reveals that my addressing mode IZX (also called IDX), mnemonic (zpg,X) works incorrectly.
05:25
I corrected IZX addressing mode, the TESTALL.DAT now passes, but Tiny Basic still has issue with printing numbers.
17:05
I corrected addressing modes ZPX and ZPY and Tiny Basic issue with printing integers is corrected. Yeeey!
2/19/2016
I did some refactoring.
I still can't decide if I should set SoftIrq flag during BRK in RTI.
2/22/2016
Settled with SoftIrq set while emulating RTI opcode. This way user is returned to debugger console
when address counter is at address just after return from soft brk instead of in the IRQ routine.
I also made changes to Tiny Basic source code.
2/25/2016
Started implementing opcodes disassembler.
2/26/2016
Implemented animation for registers status when in multi-step mode.
Implementing disassembler.
2/27/2016
LINUX port.
Cont. disassembler implementation.
Displaying last executed opcode with argument (disassembled) in regs status - completed.
Added stack display to regs status.
Trapped all unidentified opcodes (illegal opcodes with undefined behavior).
2/28/2016
Added CTRL-Y key combination trap in I/O input mode.
Added new keywords to memory image definition file.
Created tbe.dat image that automatically enables character I/O at $E000 and auto-executes Tiny Basic.
2/29/2016
Added ROm emulation toggle switch to menu and ability to setup ROM address range.
Added new keywords to memory image definition file.
Corrected start/run address initialization, tb.dat and tbe.dat files.
Cosmetic changes to character input.
3/1/2016
Code refactoring.
Added signal handling to Windows version.
OPerator can interrupt running program from console (CTRL-C, CTRL-Break) and not only during
character input - it is not an interrupt/signal handled by OS and customized in code.
Needs testing on Linux.
3/2/2016
Added non-blocking mode to char I/O input.
3/7/2016
Corrected bugs in BRK, RTI and BIT opcodes.
EhBasic works!
The functional 6502 test now fails on ADC/SBC test at $35b5:
; binary ADC / SBC zp
3594 : 08 php ;save carry for subtract
3595 : a50d lda ad1
3597 : 650e adc ad2 ;perform add
3599 : 08 php
359a : c50f cmp adrl ;check result
trap_ne ;bad result
359c : d0fe > bne * ;failed not equal (non zero)
359e : 68 pla ;check flags
359f : 29c3 and #$c3 ;mask NV----ZC
35a1 : c511 cmp adrf
trap_ne ;bad flags
35a3 : d0fe > bne * ;failed not equal (non zero)
35a5 : 28 plp
35a6 : 08 php ;save carry for next add
35a7 : a50d lda ad1
35a9 : e512 sbc sb2 ;perform subtract
35ab : 08 php
35ac : c50f cmp adrl ;check result
trap_ne ;bad result
35ae : d0fe > bne * ;failed not equal (non zero)
35b0 : 68 pla ;check flags
35b1 : 29c3 and #$c3 ;mask NV----ZC
35b3 : c511 cmp adrf
trap_ne ;bad flags
35b5 : d0fe > bne * ;failed not equal (non zero)
18:05
Corrected ADC/SBC (setting flags).
Implemented execute/op-codes history.
Functional opcodes test passed!
3/8/2016
Added disassembler.
Refactored code.
3/9/2016
Fixed bugs in memory definition loading function.
Created tool hex2bin to convert binary file to memory definition.
Created configuration file for CL65 (CC65 package linker) and modified testall.asm
to be compiled by CL65/CA65.
Improvements to disassembler (now also see opcodes and arguments in hex, not only symbolic form).
Changes to registers UI appearance (more compact form, better readibility).
Added date/time to bin2hex tool.
3/10/2016
Improved Display/char IO emulation under Windows (DOS console) - now if the DOS console window
is wider than emulated display, extra NL is added so the lines are properly aligned.
Need to implement Linux port and also I need to implement what to do if the DOS/Linux shell
console width is too narrow for char IO emulated console width.
Added mingw makefile to compile project with standalone mingw (gcc for Windows) installation
outside Dev C++ IDE.
NOTE: I must install mingw at my home PC.
3/11/2016
Added microchess port to my emulator - need to contact Peter Jennings for permission to distribute
derivative work.
3/12/2016
Received permission from Peter Jennings.
Improved the Microchess port:
- the chessboard is not printed after each key press now but only after move
- echo entered characters
- copyright banner printed only once at the program start
Update ReadMe.txt file.
3/13/2016
Improvements to char I/O console emulation.
Now the width of the terminal/DOS session is detected at startup and the emulated console width
is adjusted accordingly:
- For terminal width < 80, the emulated console width is reduced.
- For terminal width > 80, extra NL character is added while showing the emulated console
contents (this was added already earlier).
Above was also implemented for Linux port.
Operator Interrupt flag - bug corrected.
3/14/2016
Improved performance of char I/O emulation (Display::ShowScr()).
Changed the way character I/O is emulated a bit.
In the execute mode now the char I/O from 6502 program is directly translated to stdio
of the user's DOS/shell console. Shadow copy of text screen is kept in Display device
so it has the same data. In the step-by-step mode, program is emulating the Display
by refreshing it on the user's console. Like before user can always call the contents
of the text display device in debug console by issuing 'T' command.
3/17/2016
Added reset option in debugger console and RESET sequence to the emulation.
Added RESET keyword to memory definition file.
Added command line arguments to the emulator. Now can load binary image from command line,
can also initialize CPU reset from command line.
Modified ReadMe file. Also added Debugger Console Command Reference section to ReadMe file.
4/4/2016
Implemented VM snapshot save.
4/10/2016
Refactoring (replace huge switch/case with array of pointers to methods).
4/11/2016
Refactoring (replace huge switch/case with array of pointers to methods) - completed.
IRQ added to API (MKCpu, VMachine).
4/12/2016
Working on cycle accurate emulation - DONE.
4/17/2016
Finished implementing various memory image formats.
Now program can load binary image, binary image with a header, Intel HEX format,
my own plain text memory image definition format and save binary snapshot (with header).
Also, bin2hex utility can now convert binary image (must have no VM65 header) to Intel HEX.
4/21/2016
Minor bug fixed.
4/22/2016
Automatic detection of input memory image file added.
5/23/2016
Adding SDL2 to project.
5/24/2016
Working on Graphic Display emulation.
5/26/2016
Memory mapped device.
5/27/2016
Separate class MemMapDev was a wrong idea.
Cross references between Memory and MemMapDev make it impractical to use.
Also it will be difficult to manipulate memory image directly.
I will move everything to Memory class.
5/31/2016
Working on memory mapped devices implementation.
6/1/2016
I got the memory mapped devices implementation working.
I fixed the cross references between Memory and MemMapDev classes.
Right now it is only one device - character I/O.
Shortly I will add raster display.
14:45 - raster display added. I am yet to write a test 6502 program using it
and also update the memory definition file format with new keywords to enable
or disable graphics display, setup base address etc. and also the binary
header format.
Also I need to create programmers reference guide how to program the device.
6/2/2016
I wrote a short BASIC demo program for graphics display which just draws the
diagonal line pixel by pixel.
The graphics display works somewhat, but there are problems.
The SDL window needs to be continuously refreshed and events need to be read
in a continuous loop. But the loop of my program stops when in debug
console and waits for input.
I cannot run it in a separate thread because I lose the event stream from the
main application thread. So I read events and update SDL window in handler
function for the display in MemMapDev class and I only read events in VMachine
in each step when op-code is executed.
But if no op-code is executed, the window becomes unresponsive.
When window becomes unresponsive, killing it kills my emulator.
A bit messy, I must improve upon this.
E.g: perhaps I should disable system icons on SDL window (minimize, close).
6/6/2016
Demo program now draws sinusoid and some horizontal and vertical lines.
I disabled DSL windows's title and system icons.
Tried to fix loosing alread drawn pixels when SDL window is resized, but
I failed (I tried blitting (copying) surface to temporary surface and back).
6/8/2016
Added line drawing capability to Graphics Device emulator.
6/9/2016
Added line erasing capability. Corrected scaling.
7/22/2016
Minor bug fixed.
Changes related to devices and saved images format.
7/24/2016
Finished code related to new header format, new keywords added to image
definition file (graphics display device).
7/27/2016
Correction to method VMachine::GetMemoryImageType() (not a bug, just match
additional keywords in file during file format detection process).
8/12/2016
Experimenting with code performance.
It turns out the SDL graphics device reduces performance of the emulator
significantly.
8/15/2016
Concerned about performance I added code measuring the speed of CPU emulation
using 1 MHz CPU as a reference point.
In the process I discovered that my Char I/O non-blocking input is not, well
quite non-blocking. It doesn't require CR, sure, but is still waiting for the
character to be entered from keyboard.
Reproducing:
Start EhBasic interpreter.
Interrupt to return to Debug Console.
Resume code (x addr).
Do not perform any keyboard action or run programs inside EhBasic.
Interrupt to return to Debug Console.
Note # of cycles when no keyboard actions are performed in EhBasic
is very low and always the same.
Debugging step by step shows the code stops at $FFC0 (LDA $FFE1).
Thus my performance measuring routine shows very low emulation speed.
8/16/2016
I fixed the problem with blocking/non-blocking char I/O.
Turns out I had a bug in MemMapDev::ReadCharKb() method.
Slightly optimized memory access methods (specifically memory mapped devices
iteration). Got a bit of performance boost when emmory mapped devices are
enabled (still a bottleneck though).
8/17/2016
Further performance optimizations in memory access routines.
Devices are synchronized (cached) locally to vector member in Memory class.
It turns out iterating through devices in MemMapDev class and creating
device object copy locally on stack (in the loop context) was slow.
Device object is not small enough structure to ignore cost associated with
object creation.
I also added an integer array of size equal to number of memory pages (256).
That array keeps device numbers under the indexes of memory page numbers
where there is any memory mapped device. Under the indexes of memory page
numbers where no device is present, the array has -1 value.
That value is used during memory access to decide if the memory mapped device
needs to be found and handled or not.
Note that the device number kept for given page is only one number, but
there may be more devices on given page. Therefore the values in this array
can only be used as a Y/N flag (is there device? - value >= 0
or not - value < 0) rather than the device number to obtain device handler.
If the value on given memory page is >= 0, then we know we need to query
devices and call corresponding methods that handle given device register
access.
8/18/2016
Updating benchmark results, documentation.
Major optimization in MKCpu::ExecOpcode(). The code disassembling previous
instruction to history was really slow, because I have been disassembling
to symbolic form at each op-code execution. I changed the code now in such
a way that:
- op-code execute history can be enabled/disabled
- during op-code execute I only add the last instruction op-code and other
parameters of the instruction and CPU status to the history, but I do not
parse/disassemble at this point.
- the history of op-codes execute is disassembled only when history is
requested via MKCpu::GetExecHistory().
The code is now 3 times faster with op-code execute history enabled and
about 4 times faster with op-code execute history disabled (crude estimate).
8/19/2016
Found and corrected bugs in Memory::AddDevice() and Memory::DeleteDevice().
Cosmetic changes: removed obsolete code and changes towards better code
readability.
8/22/2016
Experimenting with performance measuring code.
Added debug traces and ability to enable/disable perf. stats and debug traces.
Now the perf. stats are measured in time intervals but not more often than
predefined number of internal cycles (CPU clock ticks).
Also added proper PressEnter2Cont() function.
8/23/2016
After recent changes, the 'Last instr.' feature in Debug Console stopped
working. Since real-time disassembling is not performed now in
MKCpu::ExecOpcode() method, I have to explicitly call MKCpu::Disassemble()
to produce data for this feature.
8/26/2016
I want to move Display object from VMachine to MemMapDev. I started
coding but it is not all done.
There is experimental code that needs testing.
11:28
I almost finished this refactoring. Now Display object is maintained
inside MemMapDev. VMachine still keeps the pointer to Display object locally
since it is needed to perform few screen actions in Debug Console.
Also I found a bug in main.cpp where the ioaddr was not refreshed after
new memory image was loaded with 'L' command.
Added multitude of debug messages.
Removed 'cout' statements from memory image loading LoadMEM() and replaced
then with debug messages.
Moved Console IO specific functions from VMachine to a separate class
ConsoleIO.
Some testing done, but more testing recommended and pending for these changes.
Interesting idea:
- dump the debug log to file each time it is updated DBG_TRACE_SIZE times.
8/28/2016
Found and fixed few bugs in main.cpp (step-by-step execute).
Few cosmetic changes.
Documentation updates.
9/6/2016
Cosmetic changes. Documentation updates.
9/7/2016
Final touches before commit.
9/8/2016
I started a little research project if I will be able to do character I/O
on graphics device. I downloaded c64 character ROM and converted it to
c654_char.dat. This loads from $B000. I created new version of EhBasic
eh_basic_mk.asm which has RAM top at $AFFF, so I can use it with the
character ROM in $B000-$BFFF. EhBasic starts at $C000 as before.
I compile eh_basic_mk.asm with Kowalski's 6502 emulator built-in assembler
to pure binary ehbas.65b then convert with bin2hex to ehbas_xx.dat:
bin2hex -f ehbas.65b -o ehbas_xx.dat -w 49152 -x 49152
and modify to enable I/O.
What needs to happen:
- graphics device need to support memory mapped character data and text
mode
- character I/O routines in EhBasic must point to new char I/O device address.
9/9/2016
OK, the Linux portability (and older pre c++11 compiler compatibility)
was neglected for a long time, not to mention that I added SDL2 library
in the meantime. So I started to work on Linux. Code compiles now, but my
recent changes to character I/O left the char I/O not working on Linux.
I made some additional changes and hopefully when at home in the evening
char I/O will work on my Linux box.
21:39
Implemented 1-st working version of bitmap text mode in graphics device.
Now I am going to check the code to SVN, see if it works in Linux.
Tested on Linux - char I/O still not working. And there is a bug in local
echo mode.
9/13/2016
Wow! Many many hours to finally get it working on Linux.
I use ncurses for text and SDL2 for graphics.
Performance on Linux over XWindows is poor, but at least it works.
Huge refactoring, but better layering as a result.
Now I need to finish text mode in graphics device and update documentation.
9/14/2016
Working on a text mode in graphics display device.
To improve performance I added method
void GraphDisp::RenderChar8x8(unsigned char chdef[8], int x, int y, bool reversed);
Instead of rendering the 8x8 character definition pixel by pixel (and refreshing
the SDL surface each time) I now paint the whole character and then refresh the
surface. The performance boost is significant (at least 3-4 times faster).
Next step - copy the entire character ROM to the internal buffer of GraphDisp class
each time the address of character table changes. This way, there will be no more
VM RAM accesses when character is to be rendered.
Currently each time I render the character I copy the 8 bytes of character definition
from memory to internal 8 bytes buffer and then pass it to RenderChar8x8() method.
20:30 - I have done the internal buffer optimization. Performance was further improved
by nearly 2 times. Now method GraphDisp::RenderChar8x8() is private.
I added:
void PrintChar8x8(int code, int col, int row, bool reversed);
void CopyCharRom8x8(unsigned char *pchrom);
The 2-nd one copies characters table to internal buffer in GraphDisp class.
The 1-st one renders the character using data from internal buffer.
CopyCharRom8x8() must be called each time when address of character ROM changes.
Code snippet from MemMapDev class shows how its done:
[...]
} else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_CHRTBL) {
// set new address of the character table, 2 kB bank #0-31
mGrDevRegs.mGraphDispChrTbl = (unsigned char)(val & 0x003F);
mCharTblAddr = mGrDevRegs.mGraphDispChrTbl * ((MAX_8BIT_ADDR+1) / 0x20);
unsigned char char_rom[CHROM_8x8_SIZE];
for (unsigned int i=0; i<CHROM_8x8_SIZE; i++) {
char_rom[i] = mpMem->Peek8bitImg((unsigned short)((mCharTblAddr + i) & 0xFFFF));
}
mpGraphDisp->CopyCharRom8x8(char_rom);
[...]
3/21/2017
Reactivated from long break.
I just studied the project to refresh my memory.
Replaced one macro with inline function.
3/22/2017
A small refactoring in main.cpp (replaced big if/else branch statement with switch/case).
7/28/2017
Update github repository.

View File

@ -615,14 +615,14 @@ NOTE:
7. Command line usage.
C:\src\devcppprj\mkbasic>mkbasic -h
D:\src\wrk\mkbasic>vm65 -h
Virtual Machine/CPU Emulator (MOS 6502) and Debugger.
Copyright (C) by Marek Karcz 2016. All rights reserved.
Usage:
mkbasic [-h] | [ramdeffile] [-b | -x] [-r]
vm65 [-h] | [ramdeffile] [-b | -x] [-r]
Where:
@ -707,24 +707,32 @@ If we assume that GRDEVBASE is the base address of the Graphics Device, there
are following registers:
Offset Register Description
------------------------------------------------------------------------------
----------------------------------------------------------------------------
0 GRAPHDEVREG_X_LO Least significant part of pixel's X (column)
coordinate or begin of line coord. (0-255)
1 GRAPHDEVREG_X_HI Most significant part of pixel's X (column)
coordinate or begin of line coord. (0-1)
coordinate or begin of line coord. (0-1)
2 GRAPHDEVREG_Y Pixel's Y (row) coordinate (0-199)
3 GRAPHDEVREG_PXCOL_R Pixel's RGB color component - Red (0-255)
4 GRAPHDEVREG_PXCOL_G Pixel's RGB color component - Green (0-255)
5 GRAPHDEVREG_PXCOL_B Pixel's RGB color component - Blue (0-255)
6 GRAPHDEVREG_BGCOL_R Background RGB color component - Red (0-255)
7 GRAPHDEVREG_BGCOL_G Background RGB color component - Green (0-255)
8 GRAPHDEVREG_BGCOL_B Background RGB color component - Blue (0-255)
6 GRAPHDEVREG_BGCOL_R Backgr. RGB color component - Red (0-255)
7 GRAPHDEVREG_BGCOL_G Backgr. RGB color component - Green (0-255)
8 GRAPHDEVREG_BGCOL_B Backgr. RGB color component - Blue (0-255)
9 GRAPHDEVREG_CMD Command code
10 GRAPHDEVREG_X2_LO Least significant part of end of line's X
coordinate
11 GRAPHDEVREG_X2_HI Most significant part of end of line's X
coordinate
12 GRAPHDEVREG_Y2 End of line's Y (row) coordinate (0-199)
13 GRAPHDEVREG_CHRTBL Set the 2 kB bank where char. table resides
14 GRAPHDEVREG_TXTCURX Set text cursor position (column)
15 GRAPHDEVREG_TXTCURY Set text cursor position (row)
16 GRAPHDEVREG_PUTC Output char. to current pos. and move cursor
17 GRAPHDEVREG_CRSMODE Set cursor mode : 0 - not visible, 1 - block
18 GRAPHDEVREG_TXTMODE Set text mode : 0 - normal, 1 - reverse
NOTE: Functionality maintaining text cursor is not yet implemented.
Writing values to above memory locations when Graphics Device is enabled
allows to set the corresponding parameters of the device, while writing to
@ -764,11 +772,12 @@ end of the run. Captured speed is summed with previous result and divided by 2
to produce average emulation speed during single session.
This emulator has been optimized for performance. I had issues with
emulation speed in previous version, but I took a good look at all the
critical parts of code and fixed the problems. I am sure there is still
space for improvement, but now the emulation speed leaves good margin
for expansion with new emulated peripherals and still should compare
well to the model 1 MHz CPU.
emulation speed in previous version, mostly because it was a prototype with
many debugging aids enabled by default and not yet optimized for speed.
I took a good look at all the critical parts of code and fixed the problems.
I am sure there is still space for improvement, but now the emulation speed
leaves good margin for expansion with new emulated peripherals and still
should compare well to the model 1 MHz CPU.
Emulating of pure 6502 machine code with all peripherals (memory mapped
devices, I/O etc.) emulation disabled and time critical debugging facility,
the op-codes execute history also disabled, returns performance in range of

166
benchmarks.txt Normal file
View File

@ -0,0 +1,166 @@
BM1
10 S=0
20 X=0
30 FOR I=1 TO 1000
40 S=S+X*X
50 X=X+0.00123
60 NEXT I
70 PRINT S,X
80 END
3.9 seconds on Z80 BBC BASIC (CP/M), 10MHz Z80
32 seconds in C64 WinVice emulator.
29 sec in C64 FrodoSC emulator.
4 seconds on VM65 with GD OFF, on PC1 (*)
4.5 seconds on VM65 with GD ON, on PC1 (*)
5 seconds on VM65 with GD OFF, on PC2 (*)
5 seconds on VM65 with GD ON, on PC2 (*)
*) See NOTES.
------------------------------------------------------
BM2
130 PRINT "BM2"
140 FOR N=1 TO 1000
150 FOR K=2 TO 500
160 M=N/K
170 L=INT(M)
180 IF L=0 THEN 230
190 IF L=1 THEN 220
200 IF M>L THEN 220
210 IF M=L THEN 240
220 NEXT K
230 PRINT N
240 NEXT N
250 PRINT "E"
260 END
5 min 20 sec on Z80 BBC BASIC (CP/M), 10MHz Z80
19 min 52 sec on C64 WinVice emulator
2 min 44 sec on VM65 with GD OFF on PC1 (*) at ??? % (**)
3 min 15 sec on VM65 with GD ON on PC1 at ??? %
3 min 18 sec on VM65 with GD OFF on PC2 (*) at 486 %
3 min 33 sec on VM65 with GD ON on PC2 at 478 % (**)
*), **) See NOTES.
------------------------------------------------------
BM3
10 LET W=500:DIM F(W):LET P=1:LET A=3
20 LET F(P)=A:LET P=P+1:IF P>W THEN STOP
30 LET A=A+2:LET X=1
40 LET S=A/F(X):IF S=INT(S) THEN 30
50 LET X=X+1:IF X<P AND F(X)*F(X)<=A THEN 40
60 GOTO 20
System CPU Time [sec]
----------------------------------------------------
Acorn Electron 2.0MHz 6502 138
Amstrad CPC464 4.0MHz Z80A 140
Commodore C64 1.0MHz 6510 254
Commodore Plus/4 1.0 MHz 8501 267
Tandy 64K CoCo 2 0.895MHz 6809E 271
Atari 800XL 1.8MHz 6502 316
Sinclair Spectrum +3 3.55MHz Z80A 388
VM65 GD OFF Emulated 6502 42
VM65 GD ON Emulated 6502 46
------------------------------------------------------
BM4
Primes generator, BASIC benchmark - original code from BYTE Volume 6
Issue 9.
1 SIZE=8190
2 DIM FLAGS(8191)
3 PRINT "Only 1 iteration"
5 COUNT=0
6 FOR I=0 TO SIZE
7 FLAGS(I)=1
8 NEXT I
9 FOR I=0 TO SIZE
10 IF FLAGS(I)=0 THEN 18
11 PRIME=I+I+3
12 K=I+PRIME
13 IF K>SIZE THEN 17
14 FLAGS(K)=0
15 K=K+PRIME
16 GOTO 13
17 COUNT=COUNT+1
18 NEXT I
19 PRINT COUNT," PRIMES"
Results:
VM65 - 57 seconds with GD OFF
VM65 - 62 seconds with GD ON
------------------------------------------------------
BM5
Primes generator, BASIC benchmark, based on above code.
10 S=8190: DIM F(8191): N=0
20 FOR I=0 TO S: F(I)=1: NEXT I
30 FOR I=0 TO S: IF F(I)=0 THEN 80
40 P=I+I+3: K=I+P
50 IF K>S THEN 70
60 F(K)=0: K=K+P: GOTO 50
70 N=N+1: PRINT P;" ";
80 NEXT I
90 PRINT: PRINT N;" PRIMES": END
Results:
VM65 - 55 seconds with GD OFF
VM65 - 60 seconds with GD ON
------------------------------------------------------
NOTES:
*)
PC1 stats:
Type: Desktop
CPU: 2.49 GHz (64-bit Quad-core Q8300)
RAM: 4,060 MB
OS: Windows 10 Pro (no SP) [6.2.9200]
PC2 stats:
Type: Laptop
CPU: 2.3 GHz (64-bit Quad-core i5-6300HQ)
RAM: 15.9 GB
OS: Win 10 Home.
**)
Emulation speed is measured inside emulator with 1 MHz CPU as a reference.
In other words 100% speed is achieved when emulator executes 1,000,000 cycles
in 1 second. Measurements are taken at the end of execution. Each time the
average of previous measurements and current speed measurement result are
summed together and divided by 2 to provide continuous calculation of average
emulation speed in a single session.
Executing 6502 code with no memory mapped devices enabled shows emulation
speed ~645 % (PC1) or ~470 % (PC2).
Emulation speed will likely decrease with # of enabled I/O devices and
with # of enabled debugging facilities (e.g.: op-code execute history),
but this depens on the PC specs and the current load on the system.
With a graphics demo program modified to run in a continuous loop with char
I/O enabled, graphics device enabled and actively used, op-code execute
history disabled, I'm getting a decent performance of 407% on PC1.
Note that all above benchmarks were ran with op-code execute history
disabled and character I/O emulation enabled unless specified otherwise.
**)
Each place where results are preceeded with ??? they are outdated and
tests must be repeated due to changes in code.

After

Width:  |  Height:  |  Size: 4.1 KiB

360
main.cpp
View File

@ -455,10 +455,9 @@ void ShowMenu()
cout << "------------------------------------+----------------------------------------" << endl;
}
/*
*--------------------------------------------------------------------
* Method: RUNSTEPS() - macro
* Method: RunSteps()
* Purpose: Execute multiple steps of CPU emulation.
* Arguments:
* step - boolean flag, true if step by step mode
@ -470,34 +469,39 @@ void ShowMenu()
* lrts - status of last RTS flag
* anim - boolean flag, true - registers animation mode
* delay - delay for anim mode
* enrom - rom enabled/disabled flag
* rombegin - begin address of emulated ROM
* romend - end address of emulated ROM
*
* Returns: n/a
*--------------------------------------------------------------------
*/
#define RUNSTEPS(step,nsteps,brk,preg,stct,pvm,lrts,anim,delay) \
{ \
bool cls = false; \
brk = preg->SoftIrq; \
lrts = preg->LastRTS; \
while(step && nsteps > 1 && !brk && !lrts && !opbrk) { \
preg = RunSingleCurrInstr(); \
cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct; \
cout << " \r"; \
if (anim) { \
if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; } \
pvm->ScrHome(); \
cls = ShowRegs(preg,pvm,false,false); \
cout << endl; \
this_thread::sleep_for(chrono::milliseconds(delay)); \
} \
brk = preg->SoftIrq; \
lrts = preg->LastRTS; \
nsteps--; \
stct++; \
} \
inline void RunSteps(bool step,
int nsteps,
bool brk,
Regs *preg,
int stct,
VMachine *pvm,
bool lrts,
bool anim,
int delay)
{
bool cls = false;
brk = preg->SoftIrq;
lrts = preg->LastRTS;
while(step && nsteps > 1 && !brk && !lrts && !opbrk) {
preg = RunSingleCurrInstr();
cout << "addr: $" << hex << preg->PtrAddr << ", step: " << dec << stct;
cout << " \r";
if (anim) {
if (cls & ClsIfDirty) { pvm->ClearScreen(); cls = false; }
pvm->ScrHome();
cls = ShowRegs(preg,pvm,false,false);
cout << endl;
this_thread::sleep_for(chrono::milliseconds(delay));
}
brk = preg->SoftIrq;
lrts = preg->LastRTS;
nsteps--;
stct++;
}
}
/*
@ -528,7 +532,8 @@ void ShowSpeedStats()
/*
*--------------------------------------------------------------------
* Method: ExecHistory()
* Purpose:
* Purpose: Display history of executed VM65 code in assembly
* mnemonics format.
* Arguments:
* Returns:
*--------------------------------------------------------------------
@ -914,18 +919,19 @@ int main(int argc, char *argv[]) {
execvm = true;
}
if (newaddr == 0) newaddr = 0x10000;
while (true) {
bool bloop = true;
while (bloop) {
preg = pvm->GetRegs();
if (runvm) {
if (anim) pvm->ClearScreen();
int stct = 1;
if (execaddr) {
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;
} else {
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);
}
pconio->InitCursesScr();
pconio->CloseCursesScr();
@ -984,136 +990,172 @@ int main(int argc, char *argv[]) {
cout << "> ";
cin >> cmd;
char c = tolower(cmd.c_str()[0]);
if (c == '?') {
show_menu = true;
}
else if (c == 'h') { // display help
ShowHelp();
} else if (c == 'p') { // Interrupt ReQuest
pvm->Interrupt();
cout << "OK" << endl;
show_menu = true;
} 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;
show_menu = true;
} else if (c == 'o') {
ExecHistory();
} else if (c == 'l') { // load memory image
newaddr = LoadImage(newaddr);
ioaddr = pvm->GetCharIOAddr();
} else if (c == 'k') { // toggle ROM emulation
if (!enrom) {
enrom = true;
do {
rombegin = PromptNewAddress("ROM begin (0200..FFFF): ");
} while (rombegin < 0x0200);
cout << " [" << hex << rombegin << "]" << endl;
do {
romend = PromptNewAddress("ROM end (ROMBEGIN+1..FFFF): ");
} while (romend <= rombegin);
cout << " [" << hex << romend << "]" << endl;
pvm->EnableROM(rombegin, romend);
cout << "ROM activated." << endl;
} else {
enrom = false;
pvm->DisableROM();
cout << "ROM deactivated." << endl;
}
} else if (c == 'j') { // set registers animation delay
cout << "Delay [ms]: ";
cin >> dec >> delay;
cout << " [" << dec << delay << "]" << endl;
} else if (c == 'f') { // toggle registers animation in step mode
anim = !anim;
cout << "Registers status animation " << ((anim) ? "enabled." : "disabled.") << endl;
} else if (c == 'b') { // clear screen
pconio->CloseCursesScr();
pvm->ClearScreen();
} else if (c == 'r') { // show registers
stop = true;
} else if (c == 'e') { // toggle local echo for I/O console
if (pvm->GetCharIOActive()) {
ioecho = !ioecho;
cout << "I/O echo is " << (ioecho ? "activated." : "deactivated.") << endl;
pvm->SetCharIO(ioaddr, ioecho);
} else {
cout << "ERROR: I/O is deactivated." << endl;
}
} else if (c == 't') { // show I/O console
if (pvm->GetCharIOActive()) {
pvm->ShowIO();
} else {
cout << "ERROR: I/O is deactivated." << endl;
}
} else if (c == 'i') { // toggle I/O
ioaddr = ToggleIO(ioaddr);
} else if (c == 'v') { // toggle graphics display
graddr = ToggleGrDisp(graddr);
} else if (c == 'w') { // write to memory
WriteToMemory();
} else if (c == 'a') { // change run address
execaddr = stop = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
} else if (c == 's') {
runvm = step = stop = true;
} else if (c == 'n') { // execute # of steps
nsteps = 0;
while (nsteps < 1) {
cout << "# of steps [n>1]: ";
cin >> dec >> nsteps;
}
cout << " [" << dec << nsteps << "]" << endl;
runvm = step = stop = true;
show_menu = true;
} else if (c == 'c') { // continue running code
runvm = true;
show_menu = true;
} else if (c == 'g') { // run from new address until BRK
runvm = true;
execaddr = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
show_menu = true;
} else if (c == 'x') { // execute code at address
execvm = true;
execaddr = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
show_menu = true;
} else if (c == 'q') { // quit
break;
} else if (c == 'd') { // disassemble code in memory
DisassembleMemory();
} else if (c == 'm') { // dump memory
DumpMemory();
} else if (c == 'u') { // toggle enable/disable op-code exec. history
pvm->EnableExecHistory(!pvm->IsExecHistoryActive());
cout << "Op-code execute history has been ";
cout << (pvm->IsExecHistoryActive() ? "enabled" : "disabled") << ".";
cout << endl;
} else if (c == 'z') { // toggle enable/disable debug traces in VM
ToggleDebugTraces();
} else if (c == '2') { // show debug traces
DebugTraces();
} else if (c == '1') { // toggle enable/disable perf. stats
TogglePerfStats();
} else {
cout << "ERROR: Unknown command." << endl;
// Interpret and execute user input / commands.
switch (c) {
case '?': show_menu = true;
break;
// display help
case 'h': ShowHelp();
break;
// Interrupt ReQuest
case 'p': pvm->Interrupt();
cout << "OK" << endl;
show_menu = true;
break;
// save snapshot of current CPU and memory in binary image
case 'y': {
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;
}
}
break;
// reset CPU
case '0': reset = true;
execvm = true;
runvm = false;
show_menu = true;
break;
case 'o': ExecHistory();
break;
// load memory image
case 'l': newaddr = LoadImage(newaddr);
ioaddr = pvm->GetCharIOAddr();
break;
// toggle ROM emulation
case 'k': if (!enrom) {
enrom = true;
do {
rombegin = PromptNewAddress("ROM begin (0200..FFFF): ");
} while (rombegin < 0x0200);
cout << " [" << hex << rombegin << "]" << endl;
do {
romend = PromptNewAddress("ROM end (ROMBEGIN+1..FFFF): ");
} while (romend <= rombegin);
cout << " [" << hex << romend << "]" << endl;
pvm->EnableROM(rombegin, romend);
cout << "ROM activated." << endl;
} else {
enrom = false;
pvm->DisableROM();
cout << "ROM deactivated." << endl;
}
break;
// set registers animation delay
case 'j': cout << "Delay [ms]: ";
cin >> dec >> delay;
cout << " [" << dec << delay << "]" << endl;
break;
// toggle registers animation in step mode
case 'f': anim = !anim;
cout << "Registers status animation " << ((anim) ? "enabled." : "disabled.") << endl;
break;
// clear screen
case 'b': pconio->CloseCursesScr();
pvm->ClearScreen();
break;
// show registers
case 'r': stop = true;
break;
// toggle local echo for I/O console
case 'e': if (pvm->GetCharIOActive()) {
ioecho = !ioecho;
cout << "I/O echo is " << (ioecho ? "activated." : "deactivated.") << endl;
pvm->SetCharIO(ioaddr, ioecho);
} else {
cout << "ERROR: I/O is deactivated." << endl;
}
break;
// show I/O console
case 't': if (pvm->GetCharIOActive()) {
pvm->ShowIO();
} else {
cout << "ERROR: I/O is deactivated." << endl;
}
break;
// toggle I/O
case 'i': ioaddr = ToggleIO(ioaddr);
break;
// toggle graphics display
case 'v': graddr = ToggleGrDisp(graddr);
break;
// write to memory
case 'w': WriteToMemory();
break;
// change run address
case 'a': execaddr = stop = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
break;
case 's': runvm = step = stop = true;
break;
// execute # of steps
case 'n': nsteps = 0;
while (nsteps < 1) {
cout << "# of steps [n>1]: ";
cin >> dec >> nsteps;
}
cout << " [" << dec << nsteps << "]" << endl;
runvm = step = stop = true;
show_menu = true;
break;
// continue running code
case 'c': runvm = true;
show_menu = true;
break;
// run from new address until BRK
case 'g': runvm = true;
execaddr = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
show_menu = true;
break;
// execute code at address
case 'x': execvm = true;
execaddr = true;
newaddr = PromptNewAddress(PROMPT_ADDR);
cout << " [" << hex << newaddr << "]" << endl;
show_menu = true;
break;
// quit
case 'q': bloop = false;
break;
// disassemble code in memory
case 'd': DisassembleMemory();
break;
// dump memory
case 'm': DumpMemory();
break;
// toggle enable/disable op-code exec. history
case 'u': pvm->EnableExecHistory(!pvm->IsExecHistoryActive());
cout << "Op-code execute history has been ";
cout << (pvm->IsExecHistoryActive() ? "enabled" : "disabled") << ".";
cout << endl;
break;
// toggle enable/disable debug traces in VM
case 'z': ToggleDebugTraces();
break;
// show debug traces
case '2': DebugTraces();
break;
// toggle enable/disable perf. stats
case '1': TogglePerfStats();
break;
default: cout << "ERROR: Unknown command." << endl;
break;
}
}
if (NULL != pconio) pconio->CloseCursesScr();
}

View File

@ -30,12 +30,55 @@ Current file: microchess.asm
000000r 1 ; please note that I acted with no malicious intent and will remove
000000r 1 ; this file from my project if I receive such request.
000000r 1 ;
000000r 1 ; To build this program with CL65:
000000r 1 ;
000000r 1 ; cl65 -C microchess.cfg -l --start-addr 1024 -t none -o microchess.bin
000000r 1 ; microchess.asm
000000r 1 ;
000000r 1 ; Binary image microchess.bin can be loaded to emulator with 'L'
000000r 1 ; command in debug console. Start emulator: mkbasic, then issue
000000r 1 ; command in debug console:
000000r 1 ; L B MICROCHESS.BIN
000000r 1 ;
000000r 1 ; Memory image definition file can be generated which can be loaded
000000r 1 ; to emulator via command line argument and automatically executed.
000000r 1 ; To create that file, build microchess.bin image, then execute:
000000r 1 ;
000000r 1 ; bin2hex -f microchess.bin -o microchess.dat -w 0 -x 1024 -z
000000r 1 ;
000000r 1 ; and add following lines at the end of microchess.dat file:
000000r 1 ;
000000r 1 ; IOADDR
000000r 1 ; $E000
000000r 1 ; ENIO
000000r 1 ;
000000r 1 ; Instructions to play:
000000r 1 ;
000000r 1 ; Load the game to emulator and auto-execute with command:
000000r 1 ; mkbasic microchess.dat
000000r 1 ; then perform following steps:
000000r 1 ; 1. Press 'C' to setup board.
000000r 1 ; 2. Enter your move: 4 digits - BBEE, BB - piece coordinates,
000000r 1 ; EE - destination coordinates and press ENTER
000000r 1 ; 3. After board is updated, press 'P' to make program make the move.
000000r 1 ; 4. Repeat steps 2 and 3 until the game is finished.
000000r 1 ;
000000r 1 ;
000000r 1 ; 1/14/2012
000000r 1 ; Modified by Marek Karcz to run on MKHBC-8-R1 under MKHBCOS
000000r 1 ; (derivative of M.O.S. by Scott Chidester)
000000r 1 ; Modified Daryl Rictor's port to run on MKHBC-8-R1 homebrew
000000r 1 ; computer under MKHBCOS (derivative of M.O.S. by Scott
000000r 1 ; Chidester).
000000r 1 ;
000000r 1 ; 3/11/2016
000000r 1 ; Adapted to run in MKBASIC (V65) emulator.
000000r 1 ;
000000r 1 ; 3/12/2016
000000r 1 ; Modified UI behavior:
000000r 1 ; - chess board is only printed after move, not after each
000000r 1 ; keystroke
000000r 1 ; - copyright banner is only printed once at the start
000000r 1 ; of the program
000000r 1 ;
000000r 1 ; 6551 I/O Port Addresses
000000r 1 ;
000000r 1 ;ACIADat = $7F70