mirror of
https://github.com/g012/l65.git
synced 2024-12-28 10:30:18 +00:00
Added FCEUX and Mesen symbol files generation for NES.
This commit is contained in:
parent
cbe967a074
commit
1245156c19
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
/build
|
||||
/tmp
|
||||
*.bin
|
||||
*.nes
|
||||
*.sym
|
||||
*.mlb
|
||||
*.nl
|
||||
|
@ -15,7 +15,9 @@ script:
|
||||
- ../l65 vcs_hello.l65
|
||||
- ../l65 vcs_hooks.l65
|
||||
- ../l65 vcs_music.l65
|
||||
- ../l65 vcs_mcart.l65
|
||||
- ../l65 vcs_spr48.l65
|
||||
- ../l65 nes_hello.l65
|
||||
- cd ..
|
||||
- cp l65 l65-$TRAVIS_TAG-$TRAVIS_OS_NAME
|
||||
deploy:
|
||||
|
48
6502.lua
48
6502.lua
@ -1,6 +1,6 @@
|
||||
local M = {}
|
||||
|
||||
local symbols={} M.symbols=symbols
|
||||
local symbols,symbolsorg={},{} M.symbols,M.symbolsorg=symbols,symbolsorg
|
||||
local locations={} M.locations=locations
|
||||
local sections={} M.sections=sections
|
||||
local relations={} M.relations=relations
|
||||
@ -167,6 +167,7 @@ M.link = function()
|
||||
if position then
|
||||
section.org = position
|
||||
chunk_reserve(section, chunk_ix)
|
||||
symbolsorg[section.label] = position
|
||||
symbols[section.label] = rorg(position)
|
||||
--print(section.label, string.format("%04X\t%d", position, section.size))
|
||||
--for k,v in ipairs(location.chunks) do print(string.format(" %04X %04X %d", v.start, v.size+v.start-1, v.size)) end
|
||||
@ -224,6 +225,7 @@ M.link = function()
|
||||
for chunk_ix,chunk in ipairs(location.chunks) do
|
||||
if chunk.start <= section.org and chunk.size - (section.org - chunk.start) >= section.size then
|
||||
chunk_reserve(section, chunk_ix)
|
||||
symbolsorg[section.label] = section.org
|
||||
symbols[section.label] = rorg(section.org)
|
||||
goto chunk_located
|
||||
end
|
||||
@ -267,6 +269,7 @@ M.link = function()
|
||||
section.org = position + (section.location.start - location_start) + offset
|
||||
local chunk,chunk_ix = chunk_from_address(section, section.org)
|
||||
chunk_reserve(section, chunk_ix)
|
||||
symbolsorg[section.label] = section.org
|
||||
symbols[section.label] = section.location.rorg(section.org)
|
||||
end
|
||||
end end
|
||||
@ -366,13 +369,10 @@ M.writebin = function(filename, bin)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- write a DASM symbol file for debuggers
|
||||
M.writesym = function(filename)
|
||||
if not filename then filename = 'main.sym' end
|
||||
local f = assert(io.open(filename, "wb"), "failed to open " .. filename .. " for writing")
|
||||
table.sort(symbols)
|
||||
local ins,fmt,rep = table.insert,string.format,string.rep
|
||||
local s,sym_rev = {'--- Symbol List'},{}
|
||||
-- return a table of entry(address, label)
|
||||
M.getsym = function(entry)
|
||||
local ins = table.insert
|
||||
local s,sym_rev = {},{}
|
||||
for k,v in pairs(symbols) do if type(v) == 'number' then ins(sym_rev,k) end end
|
||||
table.sort(sym_rev, function(a,b) local x,y=symbols[a],symbols[b] if x==y then return a<b end return x<y end)
|
||||
for _,v in ipairs(sym_rev) do
|
||||
@ -380,11 +380,35 @@ M.writesym = function(filename)
|
||||
local u=v:match'.*()_' if u then -- change _ to . in local labels
|
||||
local parent=v:sub(1,u-1) if symbols[parent] then v = parent..'.'..v:sub(u+1) end
|
||||
end
|
||||
ins(s, fmt("%s%s %04x", v, rep(' ',24-#v), k))
|
||||
local e = entry(k,v) if e then
|
||||
if type(e) == 'table' then for _,ev in ipairs(e) do ins(s, ev) end
|
||||
else ins(s, e) end
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
M.getsym_as = {
|
||||
lua = function() -- .lua
|
||||
local fmt,rep = string.format,string.rep
|
||||
local s = M.getsym(function(a,l) return fmt("%s = 0x%04x", l, a) end)
|
||||
return table.concat(s, '\n')
|
||||
end,
|
||||
dasm = function() -- .sym
|
||||
local fmt,rep = string.format,string.rep
|
||||
local s = M.getsym(function(a,l) return fmt("%s%s %04x", l, rep(' ',24-#l), a) end)
|
||||
table.insert(s, 1, {'--- Symbol List'})
|
||||
s[#s+1] = '--- End of Symbol List.'
|
||||
return table.concat(s, '\n')
|
||||
end,
|
||||
}
|
||||
-- write a symbol file for debuggers, using specified format (defaults to DASM)
|
||||
M.writesym = function(filename, format)
|
||||
assert(filename)
|
||||
local s = M.getsym_as[format or 'dasm'](filename)
|
||||
if s then
|
||||
local f = assert(io.open(filename, "wb"), "failed to open " .. filename .. " for writing")
|
||||
f:write(s) f:close()
|
||||
end
|
||||
s[#s+1] = '--- End of Symbol List.'
|
||||
f:write(table.concat(s, '\n'))
|
||||
f:close()
|
||||
end
|
||||
|
||||
stats.__tostring = function()
|
||||
|
172
nes.l65
172
nes.l65
@ -6,15 +6,15 @@ nes = {
|
||||
OAM = 0x200, -- 0x100 bytes
|
||||
RAM = 0x300, -- 0x500 bytes + ZP 0x100 bytes + Stack 0x100 bytes + OAM 0x100 bytes = 0x800 bytes
|
||||
|
||||
-- 2C02 PPU
|
||||
PPUCNT0 = 0x2000,
|
||||
PPUCNT1 = 0x2001,
|
||||
-- 2C02 / 2C07 PPU
|
||||
PPUCTRL = 0x2000,
|
||||
PPUMASK = 0x2001,
|
||||
PPUSTAT = 0x2002,
|
||||
SPRADDR = 0x2003,
|
||||
SPRIO = 0x2004,
|
||||
OAMADDR = 0x2003,
|
||||
OAMDATA = 0x2004,
|
||||
BGSCROL = 0x2005,
|
||||
PPUADDR = 0x2006,
|
||||
PPUIO = 0x2007,
|
||||
PPUDATA = 0x2007,
|
||||
|
||||
-- 2A03 / 2A07 CPU+APU
|
||||
SQ1VOL = 0x4000,
|
||||
@ -35,7 +35,7 @@ nes = {
|
||||
DMCRAW = 0x4011,
|
||||
DMCSTART = 0x4012,
|
||||
DMCLEN = 0x4013,
|
||||
SPRDMA = 0x4014,
|
||||
OAMDMA = 0x4014,
|
||||
SNDCNT = 0x4015,
|
||||
SPECIO1 = 0x4016,
|
||||
SPECIO2 = 0x4017,
|
||||
@ -58,9 +58,52 @@ do
|
||||
for k,v in pairs(nes) do symbols[k] = v end
|
||||
end
|
||||
|
||||
vblank_waitend = function()
|
||||
-- add some symbol file formats for NES debuggers
|
||||
cpu.getsym_as.mesen = function() -- .mlb
|
||||
local ins,fmt = table.insert,string.format
|
||||
local s = getsym(function(a,l)
|
||||
local prefix = {}
|
||||
if a < 0x2000 then prefix[1]='R'
|
||||
elseif a >= 0x6000 and a < 0x8000 then prefix[1]='S' prefix[2]='W' a=a-0x6000
|
||||
elseif a >= 0x8000 and a < 0x10000 then prefix[1]='P' a=a-0x8000
|
||||
else prefix[1]='G' end
|
||||
local s = {}
|
||||
for _,p in ipairs(prefix) do ins(s, fmt("%s:%04x:%s", p, a, l)) end
|
||||
return s
|
||||
end)
|
||||
return table.concat(s, '\n')
|
||||
end
|
||||
cpu.getsym_as.fceux = function(filename) -- .nl, multiple files
|
||||
local ins,fmt = table.insert,string.format
|
||||
local ram,rom = {},{}
|
||||
local s = getsym(function(a,l)
|
||||
local s = fmt("$%04x#%s#", a, l)
|
||||
if a < 0x8000 then ins(ram, s)
|
||||
else
|
||||
local a_org = symbolsorg[l] or a
|
||||
local bank = math.floor((a_org - 0x8000) / 0x4000)
|
||||
if not rom[bank] then rom[bank] = {} end
|
||||
ins(rom[bank], s)
|
||||
end
|
||||
end)
|
||||
local fn = filename
|
||||
if not fn:find('%.') then fn = fn .. '.nes' end
|
||||
local fni = fn .. '.ram.nl'
|
||||
local f = assert(io.open(fni, "wb"), "failed to open " .. fni .. " for writing")
|
||||
f:write(table.concat(ram, '\n')) f:close()
|
||||
for k,v in pairs(rom) do
|
||||
fni = fn .. '.' .. k .. '.nl'
|
||||
f = assert(io.open(fni, "wb"), "failed to open " .. fni .. " for writing")
|
||||
f:write(table.concat(v, '\n')) f:close()
|
||||
end
|
||||
end
|
||||
|
||||
vblank_waitbegin = function()
|
||||
local l=label() lda PPUSTAT bpl l
|
||||
end
|
||||
vblank_waitend = function()
|
||||
local l=label() lda PPUSTAT bmi l
|
||||
end
|
||||
|
||||
ppu_addr = function(addr)
|
||||
lda #addr>>8 sta PPUADDR
|
||||
@ -68,34 +111,93 @@ ppu_addr = function(addr)
|
||||
sta PPUADDR
|
||||
end
|
||||
|
||||
oam_clear = function()
|
||||
ldx #0 lda #0xf8
|
||||
local l=label() sta OAM,x inx inx inx inx bne l
|
||||
oam_bytes = function(t)
|
||||
return {
|
||||
t.y - 1,
|
||||
t.tile,
|
||||
(t.palette or 0)&3 | ((t.behind or t.priority==1) and 0x20 or 0) | (t.flipx and 0x40 or 0) | (t.flipy and 0x80 or 0),
|
||||
t.x
|
||||
}
|
||||
end
|
||||
oam_set = function(t)
|
||||
local b = oam_bytes(t)
|
||||
lda #t[1]*4 sta OAMADDR
|
||||
for _,v in ipairs(b) do lda #v sta OAMDATA end
|
||||
end
|
||||
|
||||
oam_flush = function()
|
||||
lda #0 sta SPRADDR lda #2 sta SPRDMA
|
||||
oamcache = OAM -- change it to set other location
|
||||
oamcache_clear = function()
|
||||
local oam = oamcache
|
||||
ldx #0 lda #0xff
|
||||
local l=label() sta oam,x inx inx inx inx bne l
|
||||
end
|
||||
oamcache_flush = function()
|
||||
local oam = oamcache
|
||||
lda #0 sta OAMADDR lda #oam>>8 sta OAMDMA
|
||||
end
|
||||
oamcache_set = function(t)
|
||||
local oam = oamcache
|
||||
local b = oam_bytes(t)
|
||||
ldx #t[1]*4 lda #b[1] sta oam,x
|
||||
inx lda #b[2] sta oam,x
|
||||
inx lda #b[3] sta oam,x
|
||||
inx lda #b[4] sta oam,x
|
||||
end
|
||||
|
||||
--[[ button state:
|
||||
bit: 7 6 5 4 3 2 1 0
|
||||
button: A B Select Start Up Down Left Right
|
||||
https://wiki.nesdev.com/w/index.php/Controller_Reading
|
||||
]]
|
||||
-- fast reading of just the A button
|
||||
-- return C if A is pressed, c otherwise
|
||||
read_joy_a = function(joy_index)
|
||||
local joy = joy_index == 2 and SPECIO2 or SPECIO1
|
||||
lda #1 sta joy lsr sta joy lda joy lsr
|
||||
end
|
||||
-- read one joypad state into dst
|
||||
read_joy = function(dst, joy_index)
|
||||
local joy = joy_index == 2 and SPECIO2 or SPECIO1
|
||||
lda #1 sta joy sta dst lsr sta joy
|
||||
@_readbutton lda joy lsr rol dst bcc _readbutton
|
||||
end
|
||||
-- read both joypad states and Famicom's DA15 expansion port
|
||||
read_joys = function(dst1, dst2)
|
||||
lda #1 sta SPECIO1 sta dst2 lsr sta SPECIO1
|
||||
@_readbuttons
|
||||
lda SPECIO1 and #3 cmp #1 rol dst1
|
||||
lda SPECIO2 and #3 cmp #1 rol dst2
|
||||
bcc _readbuttons
|
||||
end
|
||||
-- read both joypad states on even cycles only, to safely work with DPCM
|
||||
-- must be called right after oamcache_flush or any other sta OAMDMA
|
||||
read_joys_even = function(dst1, dst2)
|
||||
ldx #1 stx dst1 stx SPECIO1 dex stx SPECIO1
|
||||
@_readbuttons
|
||||
lda SPECIO2 and #3 cmp #1 rol dst2,x
|
||||
lda SPECIO1 and #3 cmp #1 rol dst1
|
||||
bcc _readbuttons
|
||||
end
|
||||
|
||||
init = function()
|
||||
sei cld
|
||||
ldx #0x40 stx SPECIO2
|
||||
ldx #0xff txs inx stx PPUCNT0 stx PPUCNT1 stx DMCFREQ vblank_waitend()
|
||||
ldx #0xff txs inx stx PPUCTRL stx PPUMASK stx DMCFREQ
|
||||
vblank_waitbegin()
|
||||
-- stop APU channels
|
||||
lda #0 sta SNDCNT
|
||||
-- clear CPU RAM
|
||||
@_zeroram
|
||||
sta 0x00,x
|
||||
sta 0x0100,x sta 0x0200,x sta 0x0300,x sta 0x0400,x
|
||||
sta 0x0500,x sta 0x0600,x sta 0x0700,x
|
||||
sta 0x0000,x sta 0x0100,x sta 0x0200,x sta 0x0300,x
|
||||
sta 0x0400,x sta 0x0500,x sta 0x0600,x sta 0x0700,x
|
||||
inx bne _zeroram
|
||||
vblank_waitend()
|
||||
vblank_waitbegin()
|
||||
-- clear OAM
|
||||
oam_clear() oam_flush()
|
||||
oamcache_clear() oamcache_flush()
|
||||
-- clear PPU RAM
|
||||
lda PPUSTAT ppu_addr(0x2000) tax ldy #0x10
|
||||
@_zeroppu
|
||||
sta PPUIO dex bne _zeroppu dey bne _zeroppu
|
||||
sta PPUDATA dex bne _zeroppu dey bne _zeroppu
|
||||
-- reset latch
|
||||
lda PPUSTAT
|
||||
end
|
||||
@ -122,7 +224,7 @@ header = function(t)
|
||||
local prgsize = math.tointeger((t.prgsize or 16384) / 16384)
|
||||
assert(prgsize, "prgsize must be a multiple of 16384")
|
||||
-- chrsize
|
||||
local chrsize = math.tointeger((t.chrsize or 8192) / 8192)
|
||||
local chrsize = math.tointeger((t.chrsize or 0) / 8192)
|
||||
assert(chrsize, "chrsize must be a multiple of 8192")
|
||||
-- wramsize (not battery-backed)
|
||||
local wramsize = logsz(t.wramsize or 0)
|
||||
@ -167,15 +269,29 @@ end
|
||||
|
||||
mappers = {}
|
||||
|
||||
mappers.NROM = function(prgsize)
|
||||
if not prgsize then prgsize = 16384 end
|
||||
if not chrsize then chrsize = 8192 end
|
||||
assert(prgsize == 16384 or prgsize == 32768)
|
||||
local prgstart = 0x10000 - prgsize
|
||||
local n0ne = function(x) return not x or x == 0 end
|
||||
local val0 = function(x) return x and x or 0 end
|
||||
|
||||
mappers.NROM = function(t)
|
||||
if not t then t = {} end
|
||||
if not t.prgsize then t.prgsize = 16384 end
|
||||
assert(t.prgsize == 16384 or t.prgsize == 32768, "prgrom must be 16 or 32kB")
|
||||
if n0ne(t.chrsize) and n0ne(t.chrramsize) and n0ne(t.chrbramsize) then t.chrsize = 8192 end
|
||||
assert(val0(t.chrsize) + val0(t.chrramsize) + val0(t.chrbramsize) == 8192, "chrrom/ram must be 8kB")
|
||||
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
|
||||
local prgstart = 0x10000 - t.prgsize
|
||||
hdrrom = location{prgstart - 16, prgstart - 1, name='header'}
|
||||
header{ prgsize = prgsize }
|
||||
header(t)
|
||||
prgrom = location{prgstart, 0xffff, name='prgrom'}
|
||||
section{"vectors", org=0xfffa} dc.w nmi, main, irq
|
||||
chrrom = location{0x10000, 0x10000 + chrsize-1, name='chrrom'}
|
||||
if (t.chrsize > 0) then chrrom = location{0x10000, 0x10000 + 0x1fff, name='chrrom'} end
|
||||
end
|
||||
mappers[0] = mappers.NROM
|
||||
|
||||
mappers.MMC1 = function(t)
|
||||
end
|
||||
mappers[1] = mappers.MMC1
|
||||
|
||||
mappers.MMC3 = function(t)
|
||||
end
|
||||
mappers[4] = mappers.MMC3
|
||||
|
@ -20,19 +20,21 @@ local hello = "Hello World!"
|
||||
|
||||
@@main
|
||||
init()
|
||||
--lda#0x80 sta PPUSTAT -- enable VBlank IRQ on NMI vector
|
||||
vblank_waitbegin()
|
||||
-- load BG palette in PPU RAM
|
||||
ppu_addr(BGPAL)
|
||||
for _,v in ipairs{ 0x1f, 0x00, 0x10, 0x20 } do lda #v sta PPUIO end
|
||||
for _,v in ipairs{ 0x1f, 0x00, 0x10, 0x20 } do lda #v sta PPUDATA end
|
||||
-- load screen text in PPU RAM 0x21CA
|
||||
ppu_addr(0x21ca)
|
||||
ldy #0 @_loadtxt lda text,y sta PPUIO iny cpy ##hello bne _loadtxt
|
||||
ldy #0 @_loadtxt lda text,y sta PPUDATA iny cpy ##hello bne _loadtxt
|
||||
-- reset scroll position
|
||||
ppu_addr(0) sta BGSCROL sta BGSCROL
|
||||
-- turn screen on
|
||||
lda #0x90 sta PPUCNT0 lda #0x1e sta PPUCNT1
|
||||
lda #0x90 sta PPUCTRL lda #0x1e sta PPUMASK
|
||||
-- idle
|
||||
@_loop jmp _loop
|
||||
|
||||
writebin(filename..'.nes')
|
||||
writesym(filename..'.mlb', 'mesen')
|
||||
writesym(filename..'.nes', 'fceux')
|
||||
print(stats)
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user