This software is in essence a simulator for the MOS 6502, 6510 and the 65C02 microprocessors. In contrast to the plethora of
emulators that already exist for these microprocessors it does not aim to emulate an existing retro computer with all its features like graphics
and sound. It is rather intended to be a development tool for optimizing and verifying the implementation of algorithms on old or new
machines which use these classic microprocessors. To state this again: No graphics or sound capabilities of any kind are emulated and
6502profiler
works at a purely logical level.
The two main use cases for 6502profiler
are unit testing for and performance analysis of 6502 assembly programs. 6502profiler
offers the
possibility to implement tests where arranging the test data and evaluating the results is offloaded to Lua scripts. Have a look at my
6502 arithmetic lib to see this functionality in action.6502profiler
uses
gopher-lua to embed Lua in Go.
When used for performance analysis 6502profiler
executes an existing binary inside the simulator. While running the program the number of clock
cycles that are used up during execution are counted. Additionally 6502profiler
can be used to identify "hot spots" in the program because it
also keeps track of how many times each byte in memory is accessed (i.e. read and/or written).
The usefulness of 6502profiler
as a simulator is increased by a feature which allows code running in the simulator to call functions in an
associated Lua script, which has full access to the simulator's state. This can be used to simulate additional system components or software
modules which have not been implemented in assembly yet. See below for more info about this feature or look at
this example which uses 6502profiler
to implement the "hangman" word guessing game.
6502profiler
has a command line interface. The first parameter is a so called command. Currently the following
commands are implemented.
The following commands are available:
delcase: Delete the files of an existing test case
info: Return info about program
list: List all test cases and their descriptions
newcase: Create a new test case skeleton
profile: Run program, record and evaulute performance data
run: Run program
verify: Run a test on an assembler program
verifyall: Run all tests
6502profiler
expects an installed assembler for most of its functionality to work. Its location in the file system can be configured through
the AcmeBinary
configuration entry. Currently acme
, 64tass
and ca65
are supported. The type of assembler which is in use can be defined
through the AsmType
config entry. Use the values acme
, 64tass
or ca65
to set the entry.
This command is intended to collect and evaluate data about the performance of the program under test. It understands the following command line options:
./6502profiler profile -h
Usage of 6502profiler profile:
-c string
Config file name
-dump string
Dump memory after program has stopped. Format 'startaddr:len'
-label string
Path to the label file generated by the ACME assembler
-lua string
Lua script to call when trap is triggered
-out string
Path to the out file that holds the generated data
-prcnt uint
Percentage used to determine cut off value (default 10)
-prg string
Path to the program to run
-silent
Do not print additional info
-strategy string
Strategy to determine cutoff value (default "median")
-trapaddr uint
Address to use for triggering a trap
The most important option is the -prg
option which is used to specify the binary to run. It is expected that the first two bytes
of the binary contain the load address in the usual form (lo byte first). It is also expected that execution of the program will start at
this address. The final instruction in a program that is run by 6502profiler
has to be BRK
. BRK
halts the simulator.
If the -out
option is specified 6502profiler
outputs statistical data about the current program execution. The output contains two
types of lines. Label lines and address lines. The following example illustrates a label line followed by three address lines.
SQ_TAB_LSB
0803: 00 50350
0804: 01 1056087
### 0805: 8A 5591244
Label lines are created from the data contained in the symbol list file generated by the acme
or 64tass
macro assember when
called with the -l
option. The path to this file can be provided through the -label
option of 6502profiler
. Label lines
serve as a basic link between the output of 6502profiler
and the source code of the program that is evaluated. ca65
does not
offer an easy way to just dump the addresses for which all the labels stand into a file and therefore specifying a label
file is optional.
An address line contains a 16 bit hex address followed by a colon. The address is followed by the byte stored at this memory location
at the end of the execution of the program. This in turn is follwed by the number of times the address has been accessed (read and written)
by the running program. When an address line starts with ###
the corresponding address has been accessed "more often" than is usual during
program execution. The meaning of "more often" is defined by the options -strategy
and -prcnt
.
6502profiler
counts how often each byte is accessed during program execution and stores these so called access numbers so that they
can be evaluated after the program has teminated. If an access number for a byte in memory where a machine language program resides
is high then this means that the corresponding code is executed often and therefore optmizing these parts of the program has potentially
a big effect on overall performance.
The option -prcnt
has to be a number between 0 and 100 and denotes a percentage. Specifying any value p
results in those addresses
in the output to be flagged with ###
that have access numbers which are in the top p
percent of all access numbers.
The -strategy
option determines what all access numbers are. When the value median
is used all access numbers (including duplicates)
are sorted and and the lowest value in the top p
% (p
being the value of the -prcnt
option) is selected as a threshold. Any
other value for the -strategy
option sorts the access values after removing all duplicates and after that determines the threshold.
In first experiments no significant differences between the two strategies have been found. -prcnt
and -strategy
are optional. The
default values for these options are 10 and median
.
The report file created by ACME
when specifying its -r
option, or the listings generated by 64tass
(-L
option) or ca65
(-l
option)
can be used to more precisely link the output of 6502profiler
to the assembly source code.
The -dump
command line option can be used to print a hex dump of a portion of the simulator's memory to the screen after the program has
finished. The start address and length of the memory to dump can be selected by the parameter of the option using the format address:length
.
Both numbers have to be specified in decimal.
The -trapaddr
and -lua
options can be used to enable a simulated 6502 program to execute Lua code in an associated script. The following
description of the run
command documents what can be done with these.
This command can be used to simply run an existing binary in the simulator implemented by 6502profiler
. It is expected that the first two bytes
of the binary contain the load address in the usual form (lo byte first). The following command line options can be used:
Usage of 6502profiler run:
-c string
Config file name
-dump string
Dump memory after program has stopped. Format 'startaddr:len'
-lua string
Lua script to call when trap is triggered
-prg string
Path to the program to run
-silent
Do not print additional info
-trapaddr uint
Address to use for triggering a trap
The -trapaddr
and -lua
options can be used to enable a 6502 program to execute Lua code. The idea is that a write operation
to a so called trap address (specified by the option -trapaddr
) is caught by the simulator, which then gives control to the Lua script
(given by -lua
). To be precise the simulator calls the trap()
function in the referenced Lua script. This function is called with the byte
written to the trap address as its only parameter. The parameter is called the trap code. The following code may serve as an example. Consider
the following assembler program (see testprg/trap_test.a
):
!to "trap_test",cbm
* = $0800
TRAP_ADDRESS = $FF00
TRAP_CODE = 42
lda #<TEXT
sta $80
lda #>TEXT
sta $81
; get string to print from Lua
jsr getData
ldy #0
.loopPrint
lda ($80), y
beq .finished
; Here $2DDD is a special write address which upon each write
; prints the character to the screen
sta $2DDD
iny
jmp .loopPrint
.finished
brk
; This routine triggers a "trap" which gives control to
; a Lua script. This script copies the string to print to
; the target address which is determined by the two bytes
; on top of the stack.
getData
; push target address on stack
lda $80
pha
lda $81
pha
; write to trap address
lda #TRAP_CODE
; this write triggers the trap
sta TRAP_ADDRESS
; trap routine in Lua has poped target address from
; stack
rts
TEXT
!byte 0
In the subroutine getData
the value TRAP_CODE
is written to the TRAP_ADDESS
$FF00 which triggers the execution of the trap()
function
in the following Lua script (testprg/trap.lua
)
function pop()
local sp = get_sp()
if sp == 0xFF then
sp = 0x00
else
sp = sp + 1
end
set_sp(sp)
return read_byte(0x100 + sp)
end
function trap(code)
print("----- Running Lua code")
print("Trap code: " .. code)
local address_hi = pop()
local address_lo = pop()
local address = address_hi * 256 + address_lo
print("Copying data to address " .. address)
local data_to_print = "48454c4c4f2046524f4d204c55410d0a00"
set_memory(address, data_to_print)
print("----- Done with Lua code")
end
function cleanup()
print("Cleaning up Lua part")
end
In order to run this example use acme
to create a binary from trap_test.a
and store it in the testprg
directory. After that issue the following
command in the 6502profiler
project directory
./6502profiler run -c config_printer.json -prg testprg/trap_test -trapaddr 0xFF00 -lua testprg/trap.lua
This results in the following output
Program loaded to address $0800
Using trap address $ff00
----- Running Lua code
Trap code: 42
Copying data to address 2085
----- Done with Lua code
HELLO FROM LUA
Program ran for 289 clock cycles
Cleaning up Lua part
When the -trapaddr
option is not present or its value is $0000 then the trap facility is disabled. When a value different from $0000 is specified
as a trap address a Lua script has to be specified via the -lua
option. This feature is intended to allow mocking away functionality or system
components needed by an assembly program. The Lua script can use all the functions and variables that are described below
to control the state of the simulator. Optionally the Lua script can implement a cleanup()
function which is called by 6502profiler
after the
assembly program has stopped. A program stops if it executes a BRK
instruction.
These commands are intended to facilitate the testing of assembly subroutines. You can see 6502profiler
in action for this purpose in my 6502 arithmetic library project.
The verify
command can be used to run one specific test case and its command line syntax is as follows:
Usage of 6502profiler verify:
-c string
Config file name
-prexec string
Program to run before test
-t string
Test case file
-trapaddr uint
Set trap address
-verbose
Give more information
The name of the test case file (specified by the -t
option) is interpreted relative to the directory specified by the AcmeTestDir
configuration entry (see below). The .json
suffix of the filename can be omitted. In order to run all test cases in that directory
see the verifyall
command as described below. The -prexec
command line option can be used to specify the source code of an assembly
program that is compiled and run before the test in order to perform a global test setup. This file name is interpreted relative to the
AcmeTestDir
defined in the config file.
The general idea is to have a collection of source files which contain the assembly subroutines to test in one directory (the source
directory as given in AcmeSrcDir
) and additional separate assembly test driver programs in a test directory (named by AcmeTestDir
)
which call the routines that are to be tested in an appropriate fashion. The test drivers can use the !source
or .include
pseudo opcode
of the chosen assembler to access routines from the source directory. The test drivers are automatically assembled (or compiled) into
the test binary directory. This directory is specified by AcmeBinDir
.
The verify
command loads the test driver binary and a corresponding Lua test script. This script has to define at least
two functions arrange
and assert
. Before running the test driver in the simulator the verify
command calls the arrange
function in the Lua script which can modify the simulator state before the test driver is run (for instance to arrange test data).
Then the test driver is run by the simulator and after it finishes the assert
function of the test script is called to evaluate
whether the program returned the expected results. The test is successfull if the assert
script returns true
.
The source files for the test driver and the Lua test script have to be referenced in a JSON test case file which has the following format:
{
"Name": "Simple loop test",
"TestDriverSource": "test1.a",
"TestScript": "test1.lua"
}
The file names in this file are interpreted relative to the directory specified by the AcmeTestDir
configuration entry.
Here an example for a test driver and a test script. Let's say we want to test the subroutine simpleLoop
defined in test_loop.a
in the source directory. This routine is expected to copy a four byte vector stored at the load address plus three bytes to the memory
starting at the load address plus seven bytes. The test driver looks as follows and is stored as test1.a
in the test directory.
* = $0800
jmp testStart
!source "test_loop.a"
testStart
jsr simpleLoop
brk
As a default it is assumed that a test driver starts its execution at the load address (here specified by the * = $xxxx
expression). This
can be changed though by calling set_pc()
with the desired start address in the test script's arrange
function. The corresponding Lua
test script is also stored (here as test1.lua
) in the test directory:
test_vector = "10203040"
function arrange()
set_memory(load_address+3, test_vector)
end
function assert()
d = get_memory(load_address+7, 4)
fl = get_flags()
data_ok = (d == test_vector)
negative_is_set = (string.find(fl, "N", 0, true) ~= nil)
error_msg = " \n"
res = data_ok and negative_is_set
if not data_ok then
error_msg = error_msg .. string.format("data wrong '%s'\n", d)
end
if not negative_is_set then
error_msg = error_msg .. string.format("negative flag not set: %s\n", fl)
end
return res, error_msg
end
The arrange
function copies the test vector into the simulator's memory before the test driver is run. After the test driver has finished the
assert
function is called to evaluate the results. In this example it is tested whether the test vector has been copied to the correct address
and if the negative flag is set at the end of the test driver. If these conditions are not met corresponding error messages are returned. Have a
look in the testprg/tests
directory in this repo for additional examples. To run only the test described above you can use
./6502profiler verify -c config.json -t test1
.
The trap facility described above can also be used with the verify
command. The Lua test script then additionally has to define at least the
trap
function and optionally the cleanup
function. You have to specify the -trapaddr
option to use this feature.
Test scripts have to implement an assert
and an arrange
function and optionally a trap
, a cleanup
or num_iterations
function.
arrange
is expected to take no arguments and return no value. assert
also takes no arguments but has to return two values. The first one
is a boolean and is set to true if the test was successfull. The second return value is a string and should contain some helpful message in
case the test has failed. The following functions can be used in Lua to query and manipulate the simulator's memory and processor state:
Function Name | Description |
---|---|
set_memory(address, hex_data) |
Store the data given in hex_data at address address |
get_memory(address, length) |
Return length bytes from the simulator beginning with the byte at address address as a hex string |
read_byte(address) |
Returns a single byte from memory at the given 16 bit address |
write_byte(address, value) |
Writes a single byte to memory at the given 16 bit address |
read_byte_long(address) |
Returns a single byte from memory at the given linearized address, which allows to access all of the simulated machine's memory in a flat address space |
write_byte_long(address, value) |
Writes a single byte to memory at the given linearized address, which allows to access all of the simulated machine's memory in a flat address space |
get_flags() |
Returns an eight character string that contains the letters NVBDIZC- . A letter is in the string if the corresponding flag is set |
set_flags(flag_data) |
Sets the value of the flag register. If flag_data contains any of the letters described above the corresponding flag is set. Using "" clears all flags |
get_pc() |
Returns the program counter |
set_pc(val) |
Sets the program counter to val |
get_sp() |
Returns the stack pointer |
set_sp(value) |
Sets the stack pointer |
get_accu() |
Returns the value stored in the accumulator |
set_accu(val) |
Stores val in the accu |
get_xreg() |
Returns the value stored in the X register |
set_xreg(val) |
Stores val in the X register |
get_yreg() |
Returns the value stored in the Y register |
set_yreg(val) |
Stores val in the Y register |
get_cycles() |
Returns the number of clock cycles used for executing the test |
The set_memory
and get_memory
functions can be used to get and set blocks of simulator memory. These memory blocks are always
represented as hex strings. On top of that the following three variables are injected into the Lua script from the Go host program:
Variable Name | Description |
---|---|
load_address |
Address to which the test driver has been loaded and from which it is run |
prog_len |
Length in bytes of the loaded test driver |
test_dir |
Path to the test dir which can be used with require to load additional scripts |
ident |
An identifier which is intended to give the running script a sort of identitiy for instance for logging or similar purposes |
Assigning a value to these variables remains local to the Lua test script and does not influence what is happening in the golang host application.
Test scripts can optionally implement a num_iterations
function which takes no argument and returns an integer. The golang
host program will call arrange
, execute the assembly routines under test and then call assert
as many times as the result
value of num_iterations
indicates. The number of clock cycles which is reported by 6502profiler
is the total number
of clock cycles used for all the iterations. When using this feature the Lua test script has to explicitly reset the program
counter (for instance using set_pc(load_address)
) in the arrange
function. A usage example can be found in the file
testprg/tests/itertest.lua
.
If you also want to use the trap facility in tests then you must specify the -trapaddr
CLI option and you have to implement
at least a function called trap
which takes a byte (the so called trap code) as its only parameter and does not return a value.
Optionally a cleanup
function can be implemented which takes no parameters and returns no value. This function is called when
the Lua interpreter is spun down by the golang main program at the end of each test.
Here a summary of the functions which are called from the golang side of 6502profiler
in one context or another and have to
be implemented in the corresponding Lua script:
Function Name | Description |
---|---|
arrange() |
Returns nothing. This function is called to setup things before a test is run. It is optional if the Lua script is not part of a test. Depending on the value of num_iterations() this function can be called several times for each test |
assert() |
Returns a boolean and a string. The function is called after a test was run to assert whether the test was successfull. If the returned bool is true the test is deemed to have been successfull. The string should contain an error message if the test was not successfull. Implementing this function is optional if the Lua script is not part of a test. Depending on the value of num_iterations() this function can be called several times for each test |
num_iterations() |
Returns an integer. This function is optional and is called once for each test script. Its return value determines how often arrange and assert are called for a test |
trap(trapcode) |
Returns nothing and takes a byte value. This function is called each time a trap was triggered. It is optional if the trap mechanism is not used. |
cleanup() |
Returns nothing. This function is optional and is only relevant when the trap mechanism is in use. It is called once after the simulated machine language program has finished. It is primarily intended to allow clean resource management (for instance closing files) |
The Lua functions read_byte_long
and write_byte_long
allow Lua test scripts to access all of the memory of a simulated machine in a linear fashion which makes it much easier to
verify software on machines which make heavy use of banked memory, i.e. the NeoGeo cartridge, the Commander X16 and the Foenix F256K and F256 Jr. This section describes the linear memory
layout implemented in 6502profiler
for these machines.
In this model the 64K base memory can be accessed through the addresses 0 to 0xFFFF. The addresses 0x10000 and above access the memory of the cartridge in a linear fashion.
The memory layout is best described using the following table.
Memory type | Begin | End |
---|---|---|
Base RAM | 0x0000 | 0x9FFF |
Banked RAM | 0xA000 | 0x089FFF (512K machine) |
Banked ROM | 0x8A000 | 0x109FFF (512K machine) |
Banked RAM | 0xA000 | 0x209FFF (2048K machine) |
Banked ROM | 0x20A000 | 0x289FFF (2048K machine) |
Again a table makes it easier to describe the memory layout with and without added expansion RAM. The first table describes the layout for a machine with expansion RAM:
Memory type | Begin | End |
---|---|---|
Banked RAM | 0x000000 | 0x07FFFF |
Banked ROM | 0x080000 | 0x0FFFFF |
Expansion RAM | 0x100000 | 0x13FFFF |
32 KB IO Memory | 0x140000 | 0x147FFF |
The same table for a machine without expansion RAM.
Memory type | Begin | End |
---|---|---|
Banked RAM | 0x000000 | 0x07FFFF |
Banked ROM | 0x080000 | 0x0FFFFF |
32 KB IO Memory | 0x100000 | 0x107FFF |
The verifyall
command can be used to execute all test cases that are found in the AcmeTestDir
as defined in the referenced
config file. It has the following syntax:
Usage of 6502profiler verifyall:
-c string
Config file name
-prexec string
Program to run before first test
-trapaddr uint
Set trap address
-verbose
Give more information
Here an example what kind of output ./6502profiler verifyall -c config.json
generates
Executing test case '32 bit multiplication 1' ... (3180 clock cycles) OK
Executing test case '16 Bit multiplication 5' ... (98 clock cycles) OK
Executing test case '32 Bit is zero 3' ... (67 clock cycles) OK
Executing test case '32 Bit compare 5' ... (132 clock cycles) OK
Executing test case '32 bit multiplication 4' ... (3052 clock cycles) OK
Executing test case '32 Bit addition test 1' ... (166 clock cycles) OK
Executing test case '32 Bit is equal 2' ... (162 clock cycles) OK
Executing test case '32 Bit is zero 4' ... (34 clock cycles) OK
The -prexec
command line option can be used to specify the source code of an assembly program that is compiled and run before the
first test in order to perform a global test setup. The program name is interpreted relative to the AcmeTestDir
defined in the config
file. The verifyall
command also allows to use the trap facility when the -trapaddr
option is specified.
This command can be used to create a JSON test case file, a Lua script and a test driver file in the test directory. It uses the following command line options.
Usage of 6502profiler newcase:
-c string
Config file name
-d string
Test description
-ext string
Extension to use for assembly test driver files (optional)
-p string
Test case file name
-t string
Full name of test driver file in test dir (optional)
The value of -p
is used to generate the file names of all three files in the test directory by appending the corresponding
file endings .json
, .a
and .lua
. If -t
is specified the test driver name in the newly created test case is set to the
value of -t
. This value has to include the file ending (typically .a
) and is interpreted as a file name relative to AcmeTestDir
.
The -d
option is used to add a description to the test case file which is printed when the test is run. Through the option
-ext
an alternative file extension for the assembly test driverfiles can be specified in case you do not like the default value
of .a
.
This command can be used to delete the files related to a test case. If a file is referenced by several test cases then this file is not deleted.
Usage of 6502profiler delcase:
-c string
Config file name
-t string
Test case file
The -t
option has to be used to specify the test case name which was given by the -p
option when the case was created via the
newcase
command.
The list command can be used to list the descriptions and the test case file names of all tests in the test directory. It has the following syntax:
Usage of 6502profiler list:
-c string
Config file name
The config is stored in a JSON file and can be referenced through the -c
option. The config file is structured as follows
{
"Model": "6502",
"MemSpec": "Linear64K",
"IoMask": 45,
"IoAddrConfig": {
"221": "stdout:16",
"222": "printer:petscii"
},
"PreLoad": {
"40960": "/home/martin/data/vice_roms/C64/basic",
"57344": "/home/martin/data/vice_roms/C64/kernal"
},
"F256MCoprocFlags": 0,
"F256MCoprocBase": 56832,
"Ca65StartAddress": 2048,
"AsmType": "acme",
"AcmeBinary": "acme",
"AcmeSrcDir": "./testprg",
"AcmeBinDir": "./testprg/tests/bin",
"AcmeTestDir": "./testprg/tests"
}
Model
can be 6502
or 65C02
. At the moment MemSpec
can be Linear16K
, Linear32K
, Linear48K
, Linear64K
,
XSixteen512K
, XSixteen2048K
, GeoRam_512K
, GeoRam_2048K
, F256_512K
or F256_768K
. The linear memory specifications
denote a contiguous chunk of memory starting at address 0 with a length of 16, 32, 48 or 64 kilobytes. The XSixteen
memory
specifications configure the simulator to use the memory model of the Commander X16 with either 512K oder 2048K of banked RAM.
GeoRam_512K
and GeoRam_2048K
can be used to simulate the memory model of the GeoRAM or NeoRAM cartridge, where the memory
locations $DFFE
and $DFFF
select which 256 byte page of extended memory is banked into the address space starting at $DE00
.
F256_512K
and F256_768K
implement the memory model of the Foenix F256 Jr. (Revision B) and F256K which are modern retro
computers. The 768K option simulates the presence of a 256K memory expansion.
IoMask
and IoAddrConfig
can be used to configure special I/O adresses that allow to exfiltrate data from the simulator by
means of writing to a special virtual I/O address.
The value in IoMask
specifies the hi byte of all such special addresses and each entry in IoAddrConfig
specifies the
corresponding lo byte of one special address. In the example above the first resulting special address is $2ddd
($2d=45, $dd=221). Each entry in IoAddrConfig
also has to contain a specification of what should happen each time data is
stored in that address via sta
, stx
, sty
or instructions that modify data in place as for instance inc
. If no such
special addresses are needed then IoAddrConfig
should be empty.
Currently three types of special IO addresses are defined. The first type of special IO address outputs the data hex encoded
to stdout. Such entries start with sdtdout:
and the remaining part of the entry specifies the number of bytes to be printed
on one line as a decimal number. Entries of the second kind begin with printer:
and cause the data bytes to be printed to
stdout as characters. The value after the colon specifies the encoding to use. At the moment the only legal value is petscii
.
The third type of entry is identified by the string sdtdout:bin
and causes the data written to the special output
addres to be sent to stdout without further processing.
If you want to load binaries into the simulator's RAM before any program is run you can list these binaries in the PreLoad
property. Each entry is a key value pair where the key is the address to which the binary should be loaded and the value is
the name of the file which contains the binary to load. This can for instance be used to load ROM images. It has to be noted
though that these images are of limited use because 6502profiler
does not emulate any I/O, timing or interrupt behaviour.
The AcmeBinary
entry defines the path to the binary of the assembler to use. If the program is in your PATH
then the name
of the binary suffices. AcmeSrcDir
has to describe the path to the directory where the assembler source files (which do
not implement the tests themselves) are stored. AcmeTestDir
holds the directory where the test case files, the assembler
source for the test drivers and the test scripts are located. Assembled test drivers are stored in the directory referenced
by AcmeBinDir
. The entry AsmType
specifies the assembler to use. Currently the values acme
, 64tass
and ca65
are
allowed.
When using ca65
the value of AcmeBinary
only has to specify the path to the tools ca65
and cl65
but it must not
contain the names of the tools themselves. If for instance ca65
and cl65
are located in /usr/bin
you can set AcmeBinary
to /usr/bin
. If the tools are in your PATH
then you can simply use ""
. Setting the start address of a program in ca65
requires a command line option. The value of the config entry Ca65StartAddress
can be used to set this option and thereby
change the target address of the assembled binary. If the Ca65StartAddress
entry is mssing then $0800 is used as a default.
The entries F256MCoprocFlags
and F256MCoprocBase
can be used to control the emulation of a math coprocessor for 16 bit by
16 bit unsigned multiplication and division in the style of the one used in the F256 K. If F256MCoprocFlags
is 0 or not
present then this feature is turned off. If it is 1 then the mutliplier is active and if it is 4 then the divisor is active.
If you want to activate both use the value 5. The multiplier expects its input values at the addresses F256MCoprocBase
and
F256MCoprocBase
+2 (as usual lo byte first) and provides a result at the address F256MCoprocBase
+16. The divisor expects
its input values at the addresses F256MCoprocBase
+4 and F256MCoprocBase
+6 and provides the division result at
F256MCoprocBase
+20 as well as the remainder at F256MCoprocBase
+22. The real hardware uses 0xDE00 as F256MCoprocBase
.
The 32 bit adder of the F256 coprocessor is currently not emulated. In 6502profiler
it is possible to write to the addresses
0xDE10-0xDE1B where this is not possible in real hardware.
I have used 6502profiler
to further optimize the calculation routines for my C64
and Commander X16 Mandelbrot set viewers. A C64 needs about 75 minutes to create
the default visualization in hires mode using a program of 1827 bytes length. 6502profiler
executes this program in about
a minute. The corresponding assembler source code can be found in testprg/fixed_test.a
and testprg/fixed_point.a
Currently all 6502/6510/65C02 addressing modes and all but three instructions are simulated. The first is RTI
as I do not
see any use for this instruction on the purely logical level on which 6502profiler
operates. Furthermore the 65C02 instructions
STP
and WAI
are also not implemented for the same reason.
The software is written in Go and therefore it can be built by the usual go build
command. You will need at least Go 1.18
for the build to work as the implementaion of the info
command relies on a feature that was added in that version. Tests
are provided for all 6502 instructions and can be executed through go test ./...
.
- Maybe add a possibility to deactivate a test case temporarily