Yet another 6502 emulator that one day dreams of being an Atari 2600.
x6502 is an emulator for the 6502 class of processors. It currently supports the full instruction set of the 6502 (plus a few extensions) and has an extremely rudimentary simulated I/O bus. It should be able to run arbitrary x6502 bytecode with "correct" results, although most binaries for common 6502 systems (Amiga, C64, Apple II, etc) won't function as expected, since they expect I/O devices to be mapped into memory where there are currently none.
To build x6502, just run make
in the project root. You
will need clang and Python installed. To use gcc, change
the CC
var in the Makefile. No libraries beyond POSIX
libc are used. This will produce the x6502 binary.
x6502 takes the compiled 6502 object file as an
argument, and runs it until it encounters an EXT
instruction (EXT instructions are an extension to 6502
bytecode, see below). You can use any 6502 assembler to
compile to 6502 bytecode; xa
is one that is bundled
with Debian-based distros. Note that, by default, x6502
loads code in at address 0x00
; you therefore need to
either tell your assembler that that's the base address
for the text section of your binary or override the
default load address using the -b
flag of x6502.
If you want to compile a version of x6502 that dumps
machine state after every instruction, run make debug
instead of make
. This will also disable compiler
optimizations.
x6502 recognizes two instructions that are not in the original 6502 instruction set. These are:
DEBUG (0xFC)
prints debugging information about the current state of the emulatorEXT (0xFF)
stops the emulator and exitsBoth of these are defined as macros in stdlib/stdio.s
.
To disable these extensions, compile with
-DDISABLE_EXTENSIONS
(right now, this can be done by
adding that flag to the Makefile).
There are only two I/O devices right now: a character
input device and a character output device. Convenience
constants and macros for all I/O devices are defined in
stdlib/stdio.s
for use in user programs. Add stdlib/
to
your include path and then add #include <stdio.s>
to
your program to use these constants.
The character output device is mapped to FF00
. Any
character written to FF00
is immediately echoed to the
terminal.
The character input device is mapped to FF01
. When a
character is available on standard in, an interrupt is
raised and FF01
is set to the character that was
received. Note that one character is delivered per
interrupt; if the user types abc
, they will get
three interrupts, one after the other.
A commented example of how to use the I/O capabilities
of x6502 is provided in test-programs/echo.s
.
The code for opcode handling is a little strange; there
are lots of "header" files in the opcode_handlers
directory that are not really header files at all. These
files all contain code for handling opcode parsing and
interpretation; with over 150 opcodes, having all of the
code to handle these in one file would be excessive and
difficult to navigate, and dispatching out to functions
to handle each opcode carries unnecessary overhead in
what should be the tightest loop in the project. Thus,
each of these header files is #include
d in emu.c
in the
middle of a switch statement, and gets access to the
local scope within the main_loop
function. It's weird
but it gets the job done, and is the least bad of all
considered options.