1
0
mirror of https://github.com/g012/l65.git synced 2024-12-29 01:31:04 +00:00

Added FCEUX and Mesen symbol files generation for NES.

This commit is contained in:
g012 2017-12-20 18:55:53 +01:00
parent cbe967a074
commit 1245156c19
6 changed files with 191 additions and 44 deletions

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
/build /build
/tmp /tmp
*.bin *.bin
*.nes
*.sym *.sym
*.mlb
*.nl

View File

@ -15,7 +15,9 @@ script:
- ../l65 vcs_hello.l65 - ../l65 vcs_hello.l65
- ../l65 vcs_hooks.l65 - ../l65 vcs_hooks.l65
- ../l65 vcs_music.l65 - ../l65 vcs_music.l65
- ../l65 vcs_mcart.l65
- ../l65 vcs_spr48.l65 - ../l65 vcs_spr48.l65
- ../l65 nes_hello.l65
- cd .. - cd ..
- cp l65 l65-$TRAVIS_TAG-$TRAVIS_OS_NAME - cp l65 l65-$TRAVIS_TAG-$TRAVIS_OS_NAME
deploy: deploy:

View File

@ -1,6 +1,6 @@
local M = {} local M = {}
local symbols={} M.symbols=symbols local symbols,symbolsorg={},{} M.symbols,M.symbolsorg=symbols,symbolsorg
local locations={} M.locations=locations local locations={} M.locations=locations
local sections={} M.sections=sections local sections={} M.sections=sections
local relations={} M.relations=relations local relations={} M.relations=relations
@ -167,6 +167,7 @@ M.link = function()
if position then if position then
section.org = position section.org = position
chunk_reserve(section, chunk_ix) chunk_reserve(section, chunk_ix)
symbolsorg[section.label] = position
symbols[section.label] = rorg(position) symbols[section.label] = rorg(position)
--print(section.label, string.format("%04X\t%d", position, section.size)) --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 --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 for chunk_ix,chunk in ipairs(location.chunks) do
if chunk.start <= section.org and chunk.size - (section.org - chunk.start) >= section.size then if chunk.start <= section.org and chunk.size - (section.org - chunk.start) >= section.size then
chunk_reserve(section, chunk_ix) chunk_reserve(section, chunk_ix)
symbolsorg[section.label] = section.org
symbols[section.label] = rorg(section.org) symbols[section.label] = rorg(section.org)
goto chunk_located goto chunk_located
end end
@ -267,6 +269,7 @@ M.link = function()
section.org = position + (section.location.start - location_start) + offset section.org = position + (section.location.start - location_start) + offset
local chunk,chunk_ix = chunk_from_address(section, section.org) local chunk,chunk_ix = chunk_from_address(section, section.org)
chunk_reserve(section, chunk_ix) chunk_reserve(section, chunk_ix)
symbolsorg[section.label] = section.org
symbols[section.label] = section.location.rorg(section.org) symbols[section.label] = section.location.rorg(section.org)
end end
end end end end
@ -366,13 +369,10 @@ M.writebin = function(filename, bin)
f:close() f:close()
end end
-- write a DASM symbol file for debuggers -- return a table of entry(address, label)
M.writesym = function(filename) M.getsym = function(entry)
if not filename then filename = 'main.sym' end local ins = table.insert
local f = assert(io.open(filename, "wb"), "failed to open " .. filename .. " for writing") local s,sym_rev = {},{}
table.sort(symbols)
local ins,fmt,rep = table.insert,string.format,string.rep
local s,sym_rev = {'--- Symbol List'},{}
for k,v in pairs(symbols) do if type(v) == 'number' then ins(sym_rev,k) end end 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) 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 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 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 local parent=v:sub(1,u-1) if symbols[parent] then v = parent..'.'..v:sub(u+1) end
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 end
s[#s+1] = '--- End of Symbol List.'
f:write(table.concat(s, '\n'))
f:close()
end end
stats.__tostring = function() stats.__tostring = function()

172
nes.l65
View File

@ -6,15 +6,15 @@ nes = {
OAM = 0x200, -- 0x100 bytes OAM = 0x200, -- 0x100 bytes
RAM = 0x300, -- 0x500 bytes + ZP 0x100 bytes + Stack 0x100 bytes + OAM 0x100 bytes = 0x800 bytes RAM = 0x300, -- 0x500 bytes + ZP 0x100 bytes + Stack 0x100 bytes + OAM 0x100 bytes = 0x800 bytes
-- 2C02 PPU -- 2C02 / 2C07 PPU
PPUCNT0 = 0x2000, PPUCTRL = 0x2000,
PPUCNT1 = 0x2001, PPUMASK = 0x2001,
PPUSTAT = 0x2002, PPUSTAT = 0x2002,
SPRADDR = 0x2003, OAMADDR = 0x2003,
SPRIO = 0x2004, OAMDATA = 0x2004,
BGSCROL = 0x2005, BGSCROL = 0x2005,
PPUADDR = 0x2006, PPUADDR = 0x2006,
PPUIO = 0x2007, PPUDATA = 0x2007,
-- 2A03 / 2A07 CPU+APU -- 2A03 / 2A07 CPU+APU
SQ1VOL = 0x4000, SQ1VOL = 0x4000,
@ -35,7 +35,7 @@ nes = {
DMCRAW = 0x4011, DMCRAW = 0x4011,
DMCSTART = 0x4012, DMCSTART = 0x4012,
DMCLEN = 0x4013, DMCLEN = 0x4013,
SPRDMA = 0x4014, OAMDMA = 0x4014,
SNDCNT = 0x4015, SNDCNT = 0x4015,
SPECIO1 = 0x4016, SPECIO1 = 0x4016,
SPECIO2 = 0x4017, SPECIO2 = 0x4017,
@ -58,9 +58,52 @@ do
for k,v in pairs(nes) do symbols[k] = v end for k,v in pairs(nes) do symbols[k] = v end
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 local l=label() lda PPUSTAT bpl l
end end
vblank_waitend = function()
local l=label() lda PPUSTAT bmi l
end
ppu_addr = function(addr) ppu_addr = function(addr)
lda #addr>>8 sta PPUADDR lda #addr>>8 sta PPUADDR
@ -68,34 +111,93 @@ ppu_addr = function(addr)
sta PPUADDR sta PPUADDR
end end
oam_clear = function() oam_bytes = function(t)
ldx #0 lda #0xf8 return {
local l=label() sta OAM,x inx inx inx inx bne l 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 end
oam_flush = function() oamcache = OAM -- change it to set other location
lda #0 sta SPRADDR lda #2 sta SPRDMA 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 end
init = function() init = function()
sei cld sei cld
ldx #0x40 stx SPECIO2 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 -- stop APU channels
lda #0 sta SNDCNT lda #0 sta SNDCNT
-- clear CPU RAM -- clear CPU RAM
@_zeroram @_zeroram
sta 0x00,x sta 0x0000,x sta 0x0100,x sta 0x0200,x sta 0x0300,x
sta 0x0100,x sta 0x0200,x sta 0x0300,x sta 0x0400,x sta 0x0400,x sta 0x0500,x sta 0x0600,x sta 0x0700,x
sta 0x0500,x sta 0x0600,x sta 0x0700,x
inx bne _zeroram inx bne _zeroram
vblank_waitend() vblank_waitbegin()
-- clear OAM -- clear OAM
oam_clear() oam_flush() oamcache_clear() oamcache_flush()
-- clear PPU RAM -- clear PPU RAM
lda PPUSTAT ppu_addr(0x2000) tax ldy #0x10 lda PPUSTAT ppu_addr(0x2000) tax ldy #0x10
@_zeroppu @_zeroppu
sta PPUIO dex bne _zeroppu dey bne _zeroppu sta PPUDATA dex bne _zeroppu dey bne _zeroppu
-- reset latch -- reset latch
lda PPUSTAT lda PPUSTAT
end end
@ -122,7 +224,7 @@ header = function(t)
local prgsize = math.tointeger((t.prgsize or 16384) / 16384) local prgsize = math.tointeger((t.prgsize or 16384) / 16384)
assert(prgsize, "prgsize must be a multiple of 16384") assert(prgsize, "prgsize must be a multiple of 16384")
-- chrsize -- 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") assert(chrsize, "chrsize must be a multiple of 8192")
-- wramsize (not battery-backed) -- wramsize (not battery-backed)
local wramsize = logsz(t.wramsize or 0) local wramsize = logsz(t.wramsize or 0)
@ -167,15 +269,29 @@ end
mappers = {} mappers = {}
mappers.NROM = function(prgsize) local n0ne = function(x) return not x or x == 0 end
if not prgsize then prgsize = 16384 end local val0 = function(x) return x and x or 0 end
if not chrsize then chrsize = 8192 end
assert(prgsize == 16384 or prgsize == 32768) mappers.NROM = function(t)
local prgstart = 0x10000 - prgsize 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'} hdrrom = location{prgstart - 16, prgstart - 1, name='header'}
header{ prgsize = prgsize } header(t)
prgrom = location{prgstart, 0xffff, name='prgrom'} prgrom = location{prgstart, 0xffff, name='prgrom'}
section{"vectors", org=0xfffa} dc.w nmi, main, irq 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 end
mappers[0] = mappers.NROM mappers[0] = mappers.NROM
mappers.MMC1 = function(t)
end
mappers[1] = mappers.MMC1
mappers.MMC3 = function(t)
end
mappers[4] = mappers.MMC3

View File

@ -20,19 +20,21 @@ local hello = "Hello World!"
@@main @@main
init() init()
--lda#0x80 sta PPUSTAT -- enable VBlank IRQ on NMI vector vblank_waitbegin()
-- load BG palette in PPU RAM -- load BG palette in PPU RAM
ppu_addr(BGPAL) 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 -- load screen text in PPU RAM 0x21CA
ppu_addr(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 -- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL ppu_addr(0) sta BGSCROL sta BGSCROL
-- turn screen on -- turn screen on
lda #0x90 sta PPUCNT0 lda #0x1e sta PPUCNT1 lda #0x90 sta PPUCTRL lda #0x1e sta PPUMASK
-- idle -- idle
@_loop jmp _loop @_loop jmp _loop
writebin(filename..'.nes') writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats) print(stats)

Binary file not shown.