From cbe967a074793e7ff7a44d90df29df9b42a7f680 Mon Sep 17 00:00:00 2001 From: g012 Date: Mon, 18 Dec 2017 23:59:43 +0100 Subject: [PATCH] Added first version of NES lib and hello world sample. --- CMakeLists.txt | 2 +- main.c | 1 + nes.l65 | 181 ++++++++++++++++++++++++++++++++++++++++++ samples/nes_ascii.chr | Bin 0 -> 8192 bytes samples/nes_hello.l65 | 38 +++++++++ samples/nes_hello.nes | Bin 0 -> 24592 bytes samples/nespal.act | Bin 0 -> 772 bytes 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 nes.l65 create mode 100644 samples/nes_ascii.chr create mode 100644 samples/nes_hello.l65 create mode 100644 samples/nes_hello.nes create mode 100644 samples/nespal.act diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ed32b..b16cbac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ set(L65_BINARY_DIR ${PROJECT_BINARY_DIR}) set(L65_VERSION_MAJOR "1") set(L65_VERSION_MINOR "2") -set(L65_VERSION_REVISION "1") +set(L65_VERSION_REVISION "2") set(L65_VERSION "${L65_VERSION_MAJOR}.${L65_VERSION_MINOR}.${L65_VERSION_REVISION}") configure_file("${L65_SOURCE_DIR}/l65cfg.lua.in" "${L65_BINARY_DIR}/l65cfg.lua") diff --git a/main.c b/main.c index 43b270e..16f3599 100644 --- a/main.c +++ b/main.c @@ -145,6 +145,7 @@ static struct script { const char *name; int t; const char *data; size_t sz; } SRC_LUA(dkjson), SRC_LUA(l65cfg), SRC_LUA(re), + SRC_L65(nes), SRC_L65(vcs), }; #undef SRC_LUA diff --git a/nes.l65 b/nes.l65 new file mode 100644 index 0000000..e525237 --- /dev/null +++ b/nes.l65 @@ -0,0 +1,181 @@ +-- set cpu to 6502 +cpu = require "6502" +setmetatable(_ENV, cpu) + +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, + PPUSTAT = 0x2002, + SPRADDR = 0x2003, + SPRIO = 0x2004, + BGSCROL = 0x2005, + PPUADDR = 0x2006, + PPUIO = 0x2007, + + -- 2A03 / 2A07 CPU+APU + SQ1VOL = 0x4000, + SQ1SWEEP = 0x4001, + SQ1LO = 0x4002, + SQ1HI = 0x4003, + SQ2VOL = 0x4004, + SQ2SWEEP = 0x4005, + SQ2LO = 0x4006, + SQ2HI = 0x4007, + TRILINEAR = 0x4008, + TRILO = 0x400A, + TRIHI = 0x400B, + NOISEVOL = 0x400C, + NOISELO = 0x400E, + NOISEHI = 0x400F, + DMCFREQ = 0x4010, + DMCRAW = 0x4011, + DMCSTART = 0x4012, + DMCLEN = 0x4013, + SPRDMA = 0x4014, + SNDCNT = 0x4015, + SPECIO1 = 0x4016, + SPECIO2 = 0x4017, + + SRAM = 0x6000, -- 0x2000 bytes + ROM = 0x8000, -- 0x8000 bytes + + -- PPU Memory declarations + CHAR0 = 0x0000, -- 0x1000 bytes + CHAR1 = 0x1000, -- 0x1000 bytes + SCREEN0 = 0x2000, -- 0x400 bytes + SCREEN1 = 0x2400, -- 0x400 bytes + SCREEN2 = 0x2800, -- 0x400 bytes + SCREEN3 = 0x2C00, -- 0x400 bytes + BGPAL = 0x3F00, -- 0x10 bytes + OBJPAL = 0x3F10, -- 0x10 bytes +} +do + local symbols = cpu.symbols + for k,v in pairs(nes) do symbols[k] = v end +end + +vblank_waitend = function() + local l=label() lda PPUSTAT bpl l +end + +ppu_addr = function(addr) + lda #addr>>8 sta PPUADDR + if addr&0xff ~= addr>>8 then lda #addr&0xff end + sta PPUADDR +end + +oam_clear = function() + ldx #0 lda #0xf8 + local l=label() sta OAM,x inx inx inx inx bne l +end + +oam_flush = function() + lda #0 sta SPRADDR lda #2 sta SPRDMA +end + +init = function() + sei cld + ldx #0x40 stx SPECIO2 + ldx #0xff txs inx stx PPUCNT0 stx PPUCNT1 stx DMCFREQ vblank_waitend() + -- 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 + inx bne _zeroram + vblank_waitend() + -- clear OAM + oam_clear() oam_flush() + -- clear PPU RAM + lda PPUSTAT ppu_addr(0x2000) tax ldy #0x10 +@_zeroppu + sta PPUIO dex bne _zeroppu dey bne _zeroppu + -- reset latch + lda PPUSTAT +end + +-- NES 2.0 (backward compatible with iNES) +-- https://wiki.nesdev.com/w/index.php/NES_2.0 +header = function(t) + if not t then t = {} end + local logsz = function(sz) + assert(sz >= 0 and sz <= 1048576, "invalid size: " .. sz .. ", expected [0, 1048576]") + if sz < 1 then return 0 end + if sz <= 128 then return 1 end + return math.ceil(math.log(sz/64, 2)) + end + -- mapper + local mi1 = t.mapperid or 0 + assert(mi1 >= 0 and mi1 < 4096, "invalid mapper id: " .. mi1 .. ", expected [0, 4095]") + local ms1 = t.submapperid or 0 + assert(ms1 >= 0 and ms1 < 16, "invalid submapper id: " .. ms1 .. ", expected [0, 15]") + local mapper6 = (mi1 & 0xf) << 4 + local mapper7 = mi1 & 0xf0 + local mapper8 = (mi1 >> 8) | (ms1 << 4) + -- prgsize + 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) + assert(chrsize, "chrsize must be a multiple of 8192") + -- wramsize (not battery-backed) + local wramsize = logsz(t.wramsize or 0) + -- bramsize (battery-backed) + local bramsize = logsz(t.bramsize or 0) + -- chrbramsize (battery-backed) + local chrbramsize = logsz(t.chrbramsize or 0) + -- chrramsize (not battery-backed) + local chrramsize = logsz(t.chrramsize or (chrbramsize==0 and chrsize==0 and 8192 or 0)) + local battery_bit = bramsize == 0 and chrbramsize == 0 and 0 or 2 + -- mirror: 'H' for horizontal mirroring, 'V' for vertical mirroring + -- '4' for four-screen VRAM, 218 for four-screen and vertical + local mirror = (t.mirror or 'h'):lower() + mirror = ({ h=0, v=1, ['4']=8, [218]=9 })[mirror] + assert(mirror, "invalid mirror mode: " .. mirror .. ", expected 'H', 'V', '4', or 218") + -- tv: 'N' for NTSC, 'P' for PAL, 'NP' for both preferring NTSC, 'PN' for both preferring PAL + local tv, tvm = 0, (t.tv or 'n'):lower() + assert(tvm=='n' or tvm=='p' or tvm=='np' or tvm=='pn', "invalid tv mode: " .. tostring(t.tv) .. ", expected 'N', 'P', 'NP' or 'PN'") + if tvm[1] == 'p' then tv = 1 end + if #tvm > 1 then tv = tv + 2 end + + @@header -- size: 16 bytes + dc.b 0x4e, 0x45, 0x53 -- 'NES' + dc.b 0x1a + dc.b prgsize, chrsize + dc.b mapper6 | mirror | battery_bit + dc.b mapper7 | 8 + dc.b mapper8 + dc.b ((chrsize >> 4) & 0xF0) | ((prgsize >> 8) & 0x0F) + dc.b (bramsize << 4) | wramsize + dc.b (chrbramsize << 4) | chrramsize + dc.b tv, 0, 0, 0 + + -- update table with defaulted values + t.prgsize = prgsize + t.chrsize = chrsize + t.wramsize = wramsize + t.bramsize = bramsize + t.chrbramsize = chrbramsize + t.chrramsize = chrramsize +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 + hdrrom = location{prgstart - 16, prgstart - 1, name='header'} + header{ prgsize = prgsize } + prgrom = location{prgstart, 0xffff, name='prgrom'} + section{"vectors", org=0xfffa} dc.w nmi, main, irq + chrrom = location{0x10000, 0x10000 + chrsize-1, name='chrrom'} +end +mappers[0] = mappers.NROM diff --git a/samples/nes_ascii.chr b/samples/nes_ascii.chr new file mode 100644 index 0000000000000000000000000000000000000000..61cc2aaa2aa1f0db993d473054426ddf4b7d3df4 GIT binary patch literal 8192 zcmeH`A&=WI6vrPbimF0`1E-;Zf`EYlYv@!B4NX(QfK^r2fHf5y2(SVM`T>UaA@(7n z0&AcOJPn+xp`fWcG#!Yd{T=6Zokngd+3E5CdXB42XgM$v~dxGs<&(60Lo96Zg;jR~PxK_xa)LEuEOpuHzh-eqg-5G3NtZrsHBJ}vCl)vF-Yi#|PAul@YSB9|gXSUSpl|CY4C51z)>hUYiK(N0_-9ThlMotD z%;x^_;qks9W{n7;CuTVIcQ{cxvHf{yVm{IzQ9a`MM|McNx+bPa^WjHD%+6ZBbS*J` zsC_!0#QxI^Q4q6jce@?-$Her`6$Sm~U4IN8ktc5VWnS*L#6)|cRP0Zzn$P*F?TGoL zL%yV`$h7w;eVq%!n83v3@^SW_< z7hiS!J>pLyhAOpO=1gPOt}J7JVtiW=Xc_|Re*1zrBOc#5T`w;(lq&P#=ix z=qw{*I=_i!Vrt{F5_zIh9t&q!KM{S17qS1u=f!Nu6W=1{$eC8F`2sl;!?fBA;|Kby ZV&hR~;!;8khygJm2E>3E5Caz&_yxKQn5+N* literal 0 HcmV?d00001 diff --git a/samples/nes_hello.l65 b/samples/nes_hello.l65 new file mode 100644 index 0000000..3e26295 --- /dev/null +++ b/samples/nes_hello.l65 @@ -0,0 +1,38 @@ +require'nes' + +mappers.NROM() + +location(chrrom) + +do + -- nes_ascii.chr: MIT License Copyright (c) 2016 Doug Fraker, www.nesdoug.com + local f = assert(io.open('nes_ascii.chr', 'rb')) + local d = f:read('*a') f:close() + @@chrdata byte(d) +end + +location(prgrom) +@@nmi rti +@@irq rti + +local hello = "Hello World!" +@@text byte(hello) + +@@main + init() + --lda#0x80 sta PPUSTAT -- enable VBlank IRQ on NMI vector + -- load BG palette in PPU RAM + ppu_addr(BGPAL) + for _,v in ipairs{ 0x1f, 0x00, 0x10, 0x20 } do lda #v sta PPUIO end + -- load screen text in PPU RAM 0x21CA + ppu_addr(0x21ca) + ldy #0 @_loadtxt lda text,y sta PPUIO 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 + -- idle + @_loop jmp _loop + +writebin(filename..'.nes') +print(stats) diff --git a/samples/nes_hello.nes b/samples/nes_hello.nes new file mode 100644 index 0000000000000000000000000000000000000000..192b054a865b8a62714e95ec850f3e19579ecdfc GIT binary patch literal 24592 zcmeH`v5O;B9LImz^Kur$l0tEiL+7k4R+++LkU>+N;R?A%(Jd^7B@PyYw}{0Qj}#Wm zHCaRuLzo50J}^%Suv?@wMQ=;*YzF zGh2993)5FIjuu>O2WH@M!&V>Qa!-DKbA^%ng}3;vT?m8YtM)VfdMv^8m2oF{|7`Wf z>h0Ce>KQ!>J~j=|01eOp4bT7$&;Sk401eOp4bT7$(7^vR5Qbq-!Vn+FG=1%+T}t_1 zmkn=y7M|SyQZ5+w0=ov5Z)xu@8@iVAU+dw0+E8vZ#A#WUvs{#Z+qSNZlcFw>E1S~u;Z~FeMO)wC$j(RJ(w@cU*QLEcx$!Db zb!*Cl7^jQ%9iy9{e7L(8%LSvI;b<_IXw7;rHG63-N>d#dMTO^Ev%WnT4Cbh>*`$d{ z(nkKlE-|c literal 0 HcmV?d00001 diff --git a/samples/nespal.act b/samples/nespal.act new file mode 100644 index 0000000000000000000000000000000000000000..69df8f5c5d045c8b9b85cec9f5fa2bf23304d12f GIT binary patch literal 772 zcmeHDAr8Vo5FA-qfj}aWNCcwd9)Lic-~oNYiAuyh08xpkIDtT(IKcxD$d!l+Byz+i z<(l#XY$vmuO?GBCnWkv~1(I2(Gfn}kP{J>;-vAD%0Si>Qen=_75tg-4!K2U97X7ij z<}(opnFih$MKKvj?vmDCTSsQvmd@(MFd}kk