150 lines
5.5 KiB
Markdown
150 lines
5.5 KiB
Markdown
This is a complete pure-Lua 65C02 emulator.
|
|
|
|
This requires Lua 5.3 (because of the bitwise functions). Yes, you
|
|
could probably port it to 5.2 and 5.1 fairly easily with one of the
|
|
Lua bitwise libraries. I'm more inclined to just leave it at Lua
|
|
5.3+. If you want to fork, then have at it!
|
|
|
|
# How would you use this?
|
|
|
|
Well, if I wanted to build an (original) Apple ][ emulator, I would
|
|
probably do it similarly to how I structured the tests. First build a
|
|
CPU...
|
|
|
|
``` lua
|
|
local _6502 = require '6502'
|
|
local cpu = _6502:new()
|
|
```
|
|
|
|
... and then override the simple memory array with a memory management
|
|
unit, which implements the various pieces of Apple ][ memory magic:
|
|
|
|
``` lua
|
|
local mmu = { ram = {} }
|
|
local mmu_metatable = { __index = function(t, key)
|
|
return t.ram[key] or 0
|
|
end,
|
|
__newindex = function(t, k, v)
|
|
if (k == 0xF001 and v ~= nil) then
|
|
io.write(string.format("%c", v))
|
|
end
|
|
if (k == 0x200 and v ~= nil) then
|
|
if (v == 240) then
|
|
print("All tests successful!")
|
|
os.exit(0)
|
|
end
|
|
print(string.format("Start test %d", v))
|
|
end
|
|
t.ram[k] = v
|
|
end,
|
|
}
|
|
setmetatable(mmu, mmu_metatable)
|
|
cpu.ram = mmu
|
|
```
|
|
|
|
Basically, the **__index** function gets called for any memory "read"; and
|
|
the **__newindex** function gets called for any "write". This MMU was
|
|
specifically built for the verbose CPU test; whenever it wants to
|
|
output a character, the test writes it to memory location 0xF001; and
|
|
whenever it starts a new test, it writes to memory location 0x200. So
|
|
those two memory locations are treated specially when writing to this
|
|
MMU; but it returns straight out of its "ram" table when reading
|
|
(since there's nothing special about its reads).
|
|
|
|
For memory in an Apple ][, it would do straight reads and writes for
|
|
any memory <= 0xC000. For 0xC000 through 0xCFFF, you'd do something
|
|
with the hardware I/O (where reads and writes are both special); and
|
|
then from 0xD000 through 0xFFFF you have ROM, which will need to be
|
|
loaded from a file or some such:
|
|
|
|
``` lua
|
|
local f = assert(io.open("apple2o.rom", "rb") )
|
|
local data = f:read("*a")
|
|
assert(#data == 12288)
|
|
|
|
local i = 0
|
|
while (i < #data) do
|
|
mmu[0xC000 + i] = data:byte(i+1)
|
|
i = i + 1
|
|
end
|
|
```
|
|
|
|
Finally, you would reinitialize the CPU, perform a reset, and then
|
|
start running it in a loop.
|
|
|
|
``` lua
|
|
cpu:init()
|
|
cpu:rst()
|
|
while (true) do
|
|
cpu:step()
|
|
end
|
|
```
|
|
|
|
Of course, then you get in to I/O issues, like keyboards and disk
|
|
drives; how you're going to draw a display; performance issues; what
|
|
about proper speed timing?; and details, details, details...
|
|
|
|
# Tests
|
|
|
|
The tests are from the fantastic project
|
|
|
|
https://github.com/Klaus2m5/6502_65C02_functional_tests
|
|
|
|
The tests in the tests/ directory are...
|
|
|
|
## 6502_functional_test.lua
|
|
from git commit fe99e5616243a1bdbceaf5907390ce4443de7db0
|
|
using files
|
|
6502_functional_test.bin
|
|
6502_functional_test.lst
|
|
|
|
This is basic testing of all core 6502 functions. In all, there are 43
|
|
tests; it takes some time to execute them (about 40 seconds on my 2015
|
|
Macbook Pro).
|
|
|
|
## 6502_functional_test_verbose.lua
|
|
from git commit fe99e5616243a1bdbceaf5907390ce4443de7db0
|
|
which I assembled with as65, with 'report' enabled
|
|
6502_functional_test_verbose.bin
|
|
6502_functional_test_verbose.lst
|
|
|
|
These are the same tests as above, but I assembled it in verbose mode;
|
|
if there's a test failure, it's much more explicit about it. An error
|
|
elicits output like this:
|
|
|
|
```
|
|
regs Y X A PS PCLPCH
|
|
01F9 04 02 20 B0 0B 2F 30
|
|
000C 20 00 00 00 00 00 00
|
|
0200 1E 00 00 00 00 00 00 00
|
|
press C to continue
|
|
```
|
|
|
|
(Of course, I haven't implemented "press C to continue" so it just
|
|
busy-loops forever.)
|
|
|
|
## 65C02_extended_opcodes_test.lua
|
|
from git commit f54e9a77efad2d78077107a919a412407c106f22
|
|
65C02_extended_opcodes_test.bin
|
|
65C02_extended_opcodes_test.lst
|
|
|
|
This tests much of the 65C02's extended behavior, including the
|
|
"invalid" opcodes. This has 21 tests and should end with "All tests
|
|
successful!" just like the other two tests.
|
|
|
|
## decimal_tests/*
|
|
|
|
The BCD "decimal mode" ADC and SBC operations behave in unexpected and difficult to explain ways - particularly the oVerflow flag, and especially when operating on "invalid" BCD numbers. For example - in Decimal mode, the operation "0x19 ADC 0x01" (hex 19 plus 1 -- or 25 + 1 in decimal) equals "0x20" (32). That's binary coded decimal, where the "hex" number 0x20 actually represents the decimal number 20.
|
|
|
|
So what happens when you tell it to add 0x1C + 0x01? 0x1C isn't a valid BCD number, so it's not obvious what should happen.
|
|
|
|
The test **65c02-all.bin** is a complete test of all decimal mode addition and subtraction, with validation of all of the N, V, Z, and C status flags. The wrapper 6502_decimal_test.lua take a "-f" argument that tells it which of the .bin files you want to load and execute. So it's invoked like this:
|
|
|
|
```
|
|
$ tests/decimal-tests/6502_decimal_test.lua -f tests/decimal-tests/65c02-all.bin
|
|
```
|
|
|
|
## Questions?
|
|
|
|
You can try emailing me at jorj@jorj.org. Glad to answer what I can, but my mailbox overfloweth perpetually and sometimes it takes a while for a reply!
|