diff --git a/.gitignore b/.gitignore index fb4dd95..b0162ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /build /tmp *.bin +*.nes *.sym +*.mlb +*.nl diff --git a/.travis.yml b/.travis.yml index d9c6f13..4cd1d55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/6502.lua b/6502.lua index 68db983..8185e75 100644 --- a/6502.lua +++ b/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= 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 diff --git a/samples/nes_hello.l65 b/samples/nes_hello.l65 index 3e26295..14a1f71 100644 --- a/samples/nes_hello.l65 +++ b/samples/nes_hello.l65 @@ -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) diff --git a/samples/nes_hello.nes b/samples/nes_hello.nes deleted file mode 100644 index 192b054..0000000 Binary files a/samples/nes_hello.nes and /dev/null differ