diff --git a/6502.lua b/6502.lua index a8ab7eb..34094b0 100644 --- a/6502.lua +++ b/6502.lua @@ -2,6 +2,7 @@ local M = {} local symbols={} M.symbols=symbols local locations={} M.locations=locations +local sections={} M.sections=sections local stats={} M.stats=stats setmetatable(stats, stats) M.strip = true -- set to false to disable dead stripping of relocatable sections @@ -22,6 +23,9 @@ end symbols.__index = symbols setmetatable(M, symbols) +local id_ = 0 +local id = function() id_=id_+1 return id_ end M.id=id + M.link = function() if stats.unused then return end @@ -34,33 +38,105 @@ M.link = function() return val end end - for _,location in ipairs(locations) do - for _,section in ipairs(location.sections) do - section:compute_size() - end + for _,section in ipairs(sections) do + section:compute_size() end symbols.__index = symbols + local chunk_reserve = function(chunks, chunk_ix, chunk, start, size) + if start == chunk.start then + if size == chunk.size then chunks[chunk_ix] = nil + else chunk.start=start+size chunk.size=chunk.size-size end + else + if chunk.size - (start - chunk.start) == size then chunk.size = chunk.size - size + else + local sz = start - chunk.start + table.insert(chunks, chunk_ix+1, { start=start+size, size=chunk.size-(sz+size) }) + chunk.size = sz + end + end + end + + local position_section = function(section, constrain) + local location = section.location + local rorg = location.rorg + local chunks = {} + for _,chunk in ipairs(location.chunks) do + if chunk.size >= section.size then chunks[#chunks+1] = chunk end + end + table.sort(chunks, function(a,b) return a.size < b.size end) + for chunk_ix,chunk in ipairs(chunks) do + local waste,position,position_end = math.maxinteger + local usage_lowest = function(start, finish) + local inc=1 + if section.align then + local rstart = rorg(start) + local arstart = (rstart + section.align - 1) // section.align * section.align + if section.offset then arstart = arstart + section.offset end + start = start + arstart-rstart + inc = section.align + end + for address=start,finish,inc do + for _,constraint in ipairs(section.constraints) do + local cstart, cfinish = address+constraint.start, address+constraint.finish + if rorg(cstart) // 0x100 == rorg(cfinish) // 0x100 then + if constraint.type == 'crosspage' then goto constraints_not_met end + else + if constraint.type == 'samepage' then goto constraints_not_met end + end + end + local address_end = address+section.size + local w = math.min(address - chunk.start, chunk.size - (address_end - chunk.start)) + if w > waste then goto constraints_not_met end + if w==waste then + local rposition,rposition_end = rorg(position),rorg(position_end) + local raddress,raddress_end = rorg(address),rorg(address_end) + -- if waste is the same, keep the one that uses the least amount of aligned addresses + local align=0x100 + repeat + local cross_count_cur = (rposition_end+align-1)//align - (rposition+align-1)//align + if rposition&(align-1) == 0 then cross_count_cur=cross_count_cur+1 end + local cross_count_new = (raddress_end+align-1)//align - (raddress+align-1)//align + if raddress&(align-1) == 0 then cross_count_new=cross_count_new+1 end + if cross_count_new < cross_count_cur then goto select_pos end + align = align>>1 + until align==1 + -- if cross count is same, take the one with the most set LSB count (eg. select 11 over 10) + local lsb_cur,lsb_new=0,0 + for i=0,15 do if rposition&(1<= section.size then - chunk_reserve(chunk_ix, chunk, section.org, section.size) + chunk_reserve(location.chunks, chunk_ix, chunk, section.org, section.size) symbols[section.label] = rorg(section.org) goto chunk_located end @@ -103,79 +182,32 @@ M.link = function() error("ORG section " .. section.label .. " overflows its location") ::chunk_located:: end end - - -- position independent sections + end +--[[ + for _,section in ipairs(sibling_sections) do + local sections = section.siblings + table.insert(sections, section) + -- biggest of all siblings first + table.sort(sections, function(a,b) return a.size==b.size and a.idb.size end) + end + table.sort(sibling_sections, function(a,b) return a[1].size==b[1].size and a[1].idb[1].size end) + for _,section in ipairs(sibling_sections) do + local siblings = section.siblings + local relate = section.relate or function(sibling, siblings) + end + for _,sibling in siblings do + if not relate(sibling, siblings) then goto not_suitable end + end + ::not_suitable:: + end +]] + for _,location in ipairs(locations) do + local position_independent_sections = location.position_independent_sections table.sort(position_independent_sections, function(a,b) return a.size==b.size and a.label>b.label or a.size>b.size end) for _,section in ipairs(position_independent_sections) do - local chunks = {} - for _,chunk in ipairs(location.chunks) do - if chunk.size >= section.size then chunks[#chunks+1] = chunk end + if not position_section(section) then + error("unable to find space for section " .. section.label) end - table.sort(chunks, function(a,b) return a.size < b.size end) - for chunk_ix,chunk in ipairs(chunks) do - local waste,position,position_end = math.maxinteger - local usage_lowest = function(start, finish) - local inc=1 - if section.align then - local rstart = rorg(start) - local arstart = (rstart + section.align - 1) // section.align * section.align - if section.offset then arstart = arstart + section.offset end - start = start + arstart-rstart - inc = section.align - end - for address=start,finish,inc do - for _,constraint in ipairs(section.constraints) do - local cstart, cfinish = address+constraint.start, address+constraint.finish - if rorg(cstart) // 0x100 == rorg(cfinish) // 0x100 then - if constraint.type == 'crosspage' then goto constraints_not_met end - else - if constraint.type == 'samepage' then goto constraints_not_met end - end - end - local address_end = address+section.size - local w = math.min(address - chunk.start, chunk.size - (address_end - chunk.start)) - if w > waste then goto constraints_not_met end - if w==waste then - local rposition,rposition_end = rorg(position),rorg(position_end) - local raddress,raddress_end = rorg(address),rorg(address_end) - -- if waste is the same, keep the one that uses the least amount of aligned addresses - local align=0x100 - repeat - local cross_count_cur = (rposition_end+align-1)//align - (rposition+align-1)//align - if rposition&(align-1) == 0 then cross_count_cur=cross_count_cur+1 end - local cross_count_new = (raddress_end+align-1)//align - (raddress+align-1)//align - if raddress&(align-1) == 0 then cross_count_new=cross_count_new+1 end - if cross_count_new < cross_count_cur then goto select_pos end - align = align>>1 - until align==1 - -- if cross count is same, take the one with the most set LSB count (eg. select 11 over 10) - local lsb_cur,lsb_new=0,0 - for i=0,15 do if rposition&(1<= #bin+of0) for i=#bin+of0,section.org-1 do ins(bin, filler) end - M.label_current = section.label for _,instruction in ipairs(section.instructions) do local b,f = instruction.bin,instruction.asbin if b then mov(b,1,#b,#bin+1,bin) @@ -323,7 +354,7 @@ M.location = function(start, finish) location.rorg = function(x) return x+offset end end end - location.sections = {} + location.sections = {} -- TODO remove if not location.rorg then location.rorg = function(x) return x end end local size = (location.finish or math.huge) - location.start + 1 location.chunks={ { start=location.start, size=size } } @@ -334,21 +365,22 @@ end M.section = function(t) local section = {} - if (type(t) == 'string') then section.label = t - else - assert(type(t) == 'table') - assert(type(t[1]) == 'string' and string.len(t[1]) > 0) - section=t section.label=t[1] section[1]=nil - if section.offset and not section.align then error("section " .. section.label .. " has offset, but no align") end + local name = t or 'S'..id() + if (type(name) ~= 'string') then + assert(type(t) == 'table', "invalid arguments for section") + assert(type(t[1]) == 'string' and string.len(t[1]) > 0, "invalid name for section") + section=t name=t[1] section[1]=nil + if section.offset and not section.align then error("section " .. name .. " has offset, but no align") end end - table.insert(M.location_current.sections, section) - if symbols[section.label] then error("duplicate symbol: " .. section.label) end - symbols[section.label] = section - M.label_current = section.label + table.insert(M.location_current.sections, section) -- TODO remove + table.insert(M.sections, section) + section.location = M.location_current M.section_current = section - section.type = 'section' + section.id = id() section.constraints = {} section.instructions = {} + assert(name:sub(1,1) ~= '_', "sections can't be named with a local label") + section.label = M.label(name) function section:compute_size() local instructions = self.instructions self.size=0 self.cycles=0 @@ -373,12 +405,11 @@ M.section = function(t) return section end -M.label_gen_ix = 0 M.label = function(name) local label,offset local section,rorg = M.section_current,M.location_current.rorg - label = { type='label' } - if not name then name='_L'..M.label_gen_ix M.label_gen_ix=M.label_gen_ix+1 end + label = { type='label', section=section } + if not name then name='_L'..id() end if name:sub(1,1) == '_' then -- local label name = M.label_current .. name else @@ -412,9 +443,18 @@ M.endpage = function() constraint.to = #section.instructions end +M.sleep = function(cycles, noillegal) + assert(cycles > 1, "can't sleep for less than 2 cycles") + if cycles & 1 ~= 0 then + if noillegal then bitzpg(0) else nopzpg(0) end + cycles = cycles - 3 + end + for i=1,cycles/2 do nopimp() end +end + local size_ref = function(v) if type(v) == 'string' then v=symbols[v] end - if type(v) == 'table' and v.type == 'section' then v.refcount = 1 + (v.refcount or 0) end + if type(v) == 'table' and v.type == 'label' then v.section.refcount = 1 + (v.section.refcount or 0) end end local size_dc = function(v) if type(v) == 'function' then diff --git a/samples/vcs0.l65 b/samples/vcs0.l65 new file mode 100644 index 0000000..1432968 --- /dev/null +++ b/samples/vcs0.l65 @@ -0,0 +1,15 @@ +require'vcs' +mappers['2K']() + +local kernel = function() + ldx#0xb0 @_loop sta WSYNC stx COLUBK dex bne _loop +end + +@@main + init() +@_frame + overscan() vblank() screen(kernel) jmp _frame + +writebin(filename..'.bin') +writesym(filename..'.sym') +print(stats) diff --git a/vcs.l65 b/vcs.l65 index f4161c3..ef85eba 100644 --- a/vcs.l65 +++ b/vcs.l65 @@ -84,45 +84,160 @@ do for k,v in pairs(vcs) do symbols[k] = v end end +NTSC = { + TIM_OVERSCAN = 38, -- TIM64T, 2432 cycles = 32 scanlines + TIM_VBLANK = 45, -- TIM64T, 2880 cycles = ~ 38 scanlines + TIM_KERNEL = 15, -- T1024T, 15360 cycles = ~202 scanlines +} +PAL = { + TIM_OVERSCAN = 50, -- TIM64T, 3200 cycles = ~ 42 scanlines + TIM_VBLANK = 61, -- TIM64T, 3904 cycles = ~ 51 scanlines + TIM_KERNEL = 17, -- T1024T, 17408 cycles = ~229 scanlines +} +TV = PAL + +init = function() cld ldx#0 txa local l=label() dex tsx pha bne l end +wait = function() local l=label() lda INTIM bne l end +overscan_begin = function() sta WSYNC lda#2 sta VBLANK lda#TV.TIM_OVERSCAN sta TIM64T end +overscan_end = wait +overscan = function(f) overscan_begin() if f then f() end overscan_end() end +vblank_begin = function() + lda#0b1110 local l=label() sta WSYNC sta VSYNC lsr bne l + lda#2 sta VBLANK lda#TV.TIM_VBLANK sta TIM64T +end +vblank_end = function() wait() sta WSYNC sta VBLANK end +vblank = function(f) vblank_begin() if f then f() end vblank_end() end +screen_begin = function() lda#TV.TIM_KERNEL sta T1024T end +screen_end = wait +screen = function(f) screen_begin() if f then f() end screen_end() end + +nusiz = { + MSL_SIZE_1 = 16*0, + MSL_SIZE_2 = 16*1, + MSL_SIZE_4 = 16*2, + MSL_SIZE_8 = 16*3, + ONE_COPY = 0, + TWO_COPIES_CLOSE = 1, + TWO_COPIES_MEDIUM = 2, + THREE_COPIES_CLOSE = 3, + TWO_COPIES_WIDE = 4, + DOUBLE_SIZED_PLAYER = 5, + THREE_COPIES_MEDIUM = 6, + QUAD_SIZED_PLAYER = 7, +} + +ctrlpf = { + BALL_SIZE_1 = 16*0, + BALL_SIZE_2 = 16*1, + BALL_SIZE_4 = 16*2, + BALL_SIZE_8 = 16*3, + PF_MIRRORED = 1, + PF_SCOREMODE = 2, + PF_PRIO_BALLABOVE = 4, +} + +enable = { + DISABLE = 0, + ENABLE = 2, +} + +vdel = { + DISABLE = 0, + ENABLE = 1, +} + +hm = { + LEFT_7 = 7*16, + LEFT_6 = 6*16, + LEFT_5 = 5*16, + LEFT_4 = 4*16, + LEFT_3 = 3*16, + LEFT_2 = 2*16, + LEFT_1 = 1*16, + NO_MOTION = 0, + RIGHT_1 = 15*16, + RIGHT_2 = 14*16, + RIGHT_3 = 13*16, + RIGHT_4 = 12*16, + RIGHT_5 = 11*16, + RIGHT_6 = 10*16, + RIGHT_7 = 9*16, + RIGHT_8 = 8*16, + + C74_LEFT_15 = 7*16, + C74_LEFT_14 = 6*16, + C74_LEFT_13 = 5*16, + C74_LEFT_12 = 4*16, + C74_LEFT_11 = 3*16, + C74_LEFT_10 = 2*16, + C74_LEFT_9 = 1*16, + C74_LEFT_8 = 0*16, + C74_LEFT_7 = 15*16, + C74_LEFT_6 = 14*16, + C74_LEFT_5 = 13*16, + C74_LEFT_4 = 12*16, + C74_LEFT_3 = 11*16, + C74_LEFT_2 = 10*16, + C74_LEFT_1 = 9*16, + C74_NO_MOTION = 8*16, +} + +-- for mappers that swap banks in place +-- call an asm function into another bank and generate the stubs if needed +local far_stubs = {} M.far_stubs=far_stubs +local far = function(dst) + local stub,loc_src,loc_dst = far_stubs[dst],location_current,dst.section.location + if not (stub and stub.stub_caller.location==loc_src and stub.stub_callee.location==loc_dst) then + local seccur = section_current + local stub_caller = section() switchrom(loc_dst.rom) skip(6) rts + location(loc_dst) + local stub_callee = section() jsr dst switchrom(loc_src.rom) + location(loc_src) section_current=seccur + far_stubs[dst] = relate(stub_caller, stub_callee, 3) + end + jsr stub_caller +end + mappers = {} -mappers['2K'] = function() +mappers['2K'] = function(irq) rom0 = location(0xf800, 0xffff) - section{"vectors", org=0xfffc} word(main,main) + section{"vectors", org=0xfffc} dc.w main if irq then dc.w main end end mappers.CV = mappers['2K'] -mappers['4K'] = function() +mappers['4K'] = function(irq) rom0 = location(0xf000, 0xffff) - section{"vectors", org=0xfffc} word(main,main) + section{"vectors", org=0xfffc} dc.w main if irq then dc.w main end end -local bank_stubs = function(count, hotspot, entry) +local bank_stubs = function(irq, count, hotspot, entry) function switchrom(i) assert(i>=0 and i=0 and i=0 and i<7) bit 0x1fe0+i end function enableram() bit 0x1fe7 end function switchram(i) assert(i>=0 and i<4) bit 0x1fe8+i end @@ -161,56 +276,58 @@ mappers.E7 = function() end rom7 = location{0xf800, 0xffff, rorg=0xf800} section{"switchtab", org=0xffe0} for i=1,12 do byte(0) end - section{"vectors", org=0xfffc} word(main,main) + section{"vectors", org=0xfffc} dc.w main if irq then dc.w main end end -mappers.F0 = function() +mappers.F0 = function(irq) function switchrom() lda 0x1ff0 end for bi=0,15 do local o = bi*0x1000 + 0x1000 _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-8} - @_loop switchrom() bne _loop + local l=label() switchrom() bne l if bi==0 then jmp main end section{"switchtab"..bi, org=o+0xff0} byte(bi) - section{"vectors"..bi, org=o+0xffc} word(start,start) + section{"vectors"..bi, org=o+0xffc} dc.w start if irq then dc.w start end end end -local bank_stubs2 = function(hotspot0, hotspot1) +local bank_stubs2 = function(irq, hotspot0, hotspot1) function switchrom(i) if i==0 then bit hotspot0 elseif i==1 then bit hotspot1 else error("invalid rom index: " .. i) end end + far = far local base = 0xe000 for bi=0,1 do local o = base+(bi<<12) _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end - section{"vectors"..bi, org=o+0xffc} word(start,start) + section{"vectors"..bi, org=o+0xffc} dc.w start if irq then dc.w start end end end -mappers.UA = function() bank_stubs2(0x220, 0x240) end -mappers['0840'] = function() bank_stubs2(0x800, 0x840) end -mappers.SB = function(count) +mappers.UA = function(irq) bank_stubs2(irq, 0x220, 0x240) end +mappers['0840'] = function(irq) bank_stubs2(irq, 0x800, 0x840) end +mappers.SB = function(count, irq) function switchrom(i) assert(i>=0 and i=0 and i=0 and i<128) lda#i sta 0x3c+i end _ENV['switchram'..i] = function(i) assert(i>=0 and i<128) lda#i+0x80 sta 0x3c+i end @@ -220,17 +337,18 @@ mappers.MC = function(rom_count, ram_count) _ENV["rom"..(count-1-bi)] = location{o, 0x3ff+o, rorg=0xf000+(o&0xfff)} end local start=section("entry") switchrom3(0) jmp main - section{"vectors", org=0xfffc} word(start,start) + section{"vectors", org=0xfffc} dc.w start if irq then dc.w start end end -mappers.X07 = function() +mappers.X07 = function(irq) function switchrom(i) assert(i>=0 and i<16) bit 0x80b+(i<<4) end + far = far -- map TIA also to 0x40-0x7F: use VSYNC for bank 14, and VSYNC2 for bank 15 vcs.VSYNC2=0x40 cpu.symbols.VSYNC2=0x40 for bi=0,count-1 do local o = bi*0x1000 + 0x1000 _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end - section{"vectors"..bi, org=o+0xffc} word(start,start) + section{"vectors"..bi, org=o+0xffc} dc.w start if irq then dc.w start end end end