Based on Gianluca's MOS6502 Emulator in C++, I forked and modified his emulator to create an implementation of a 6502 CPU simulator that I could use to test algorithms and get an accurate cycle count.
The main features from Gianluca's are retained:
- 100% coverage of legal opcodes.
- Jump table for op-code execution.
New features I've added are:
- Use of a C++ concept called
SystemBus
that will provide bus reads, writes. /IRQ asserts, and /NMI asserts. - Accurate cycle counting for all legal opcodes and interrupt asserts.
- Addition of an instruction or step counter.
- Inner loop now based arround stepping, which can also be manually invoked for debugging.
Planned features that I want to implement:
- Definition of a
Debugger
concept for hanlding breakpoints and debugging symbols or listings, along with a basic implementation. - If Gianluca implements them, then illegal opcodes.
- If Gianluca implements them, hardware glitches.
Some possibilities, but not decided:
- Implmentation of other variants of the 6502.
According to Gianluca, his emulator was tested against the following test suite:
https://github.com/Klaus2m5/6502_65C02_functional_tests
I provided mos6502-test.cpp
to tests the cycle accuracy.
The mos::mos6502
class is the 6502 CPU simulator. Using mos::mos6502
requires at least one
template parameter to define the additional hardware attached to the simulated system. This type
must implements the SystemBus
concept. The `mos::mos6502 class provide a nice public API.
mos6502(args...)
; a variable template constructor that will pass all of its arguments to theSystemBus
.bus()
; provides access to the system bus.a()
,a(byte)
,x()
,x(byte)
,y()
,y(byte)
; provides read/write access to the main registers of the CPU.sp()
,pc()
; provides read-only access to the stack-pointer and program-counter registers.go(word)
; provides a means to jump the CPU program-counter to any location on the system bus.status()
; provides read/write access to the CPU status flags. These are available using:negative()
,negative(bool)
; the N/Negative flag.overflow()
,overflow(bool)
; the V/oVerflow flag.brk()
,brk(bool)
; the B/Break flag.decimal()
,decimal(bool)
; the D/Decimal Enable flag.interrupt()
,interrupt(bool)
; the I/Interrupt Disable flag.zero()
,zero(bool)
; the Z/Zero flag.carry()
,carry(bool)
; the C/Carry flag.get()
; gets the raw value of the register.set(byte)
; sets the raw value of the register.reset()
; resets the register to the usual initial value.
cycles()
,steps()
; counters that indicate the number of cycles and steps the CPU has performed.power_off()
; Powers the CPU off, stopping all running code.reset()
; Resets the CPU to its initial state.run()
; Runs the CPU till it is powered off or lands on an illegal instruction.step()
; Executes the next instruction and stops.push(byte)
,push_word(word)
; pushes data to the stack without using cycles or steps.pop(byte)
,pop_word(word)
; pushes data to the stack without using cycles or steps.
One C++ concept is defined by this library, the SystemBus
. This concept is is used to provide the
CPU with access to memory, memory mapped hardware, and interrupt sources. A default implemenation
of this concept is provided called mos::basic_memory
. A SystemBus
must support the following
features.
SystemBus()
. DefaultConstructible, requires the type can be constructed by the default without any additional arguments.~SystemBus() noexcept
. Destructible, requires the type can be destructed without throwing any exceptions.bool pending_irq() const noexcept
. requires the type can signal if the /IRQ line is asserted.bool pending_nmi() const noexcept
. requires the type can signal if the /NMI line is asserted.void write(std::uint16_t address, std::uint8_t data, bool burn = false) noexcept
. requires the type can write to the system bus, and optionally provide a means to write to read-only memory. Please note the CPU will never pass trur for theburn
argument.auto read(std::uint16_t address) noexcept -> std::uint8_t
. requires the type can read from system bus.
A SystemBus
may also support a constructor with number of arguments which are forwarded to it by
the mos::mos6502
.
When the SystemBus
is exposed from mos::mos6502
, it is wrapped by mos::system_bus
which
provides many additional useful features. These are:
read_word(address)
; reads a word from the bus, using little-endian format.write_work(address, word)
; writes a word to the bus, using little-endian format.fill(address, count, byte)
; fills an area of the bus with the same byte.load_input(address, first, last)
; loads data from a pair ofInputIterators
onto the bus.load_stream(address, stream)
; loads data from anistream
onto the bus.
- Gianluca's MOS6502 Emulator in C++, the original source.
- Wikipedia's MOS Technology 6502, provides some history and basic information about the original CPU.
- 6502.org Document Archive, provides lots of useful documents about the 6502.
- Preliminary data-sheet on the 6500 Microprocessor Series