Compare commits

...

68 Commits

Author SHA1 Message Date
g012 31cccce3e1 [NES] Added UNROM-512 mapper and sample 2024-04-11 14:46:37 +02:00
g012 5f71b92bcd Converted line endings in VIM files to unix 2024-03-23 16:25:23 +01:00
g012 29f5666b20 Added build.zig to cross compile
Warning: currently you need to edit ConfigHeader.zig in Zig's std
library, and comment "try output.appendSlice(c_generated_line);"
2024-03-12 16:01:12 +01:00
g012 0a65af8621
Merge pull request #3 from BlockoS/master
Fixed jr range check.
2024-03-01 13:08:46 +01:00
g012 990b30f410
Merge pull request #2 from maraflush/master
some new nes samples
2019-06-09 21:05:12 +02:00
maraflush e57aee36bd nes samples :
- show anametable
- show a 8x8 pixels sprite
- show the metasprites
2019-06-09 08:11:02 -07:00
MooZ 8ade3f3fd4 Fixed jr range check. 2019-04-10 21:37:48 +02:00
g012 5105f8bd8d
Merge pull request #1 from BlockoS/master
Added support for µPD7801
2018-11-21 21:25:29 +01:00
mooz 52a5777949 Use l7801 extension for µPD7801 compiler. 2018-11-21 21:21:07 +01:00
mooz f2ab52c0ae Added Plogue uPD1771C Tester by David Viens (@plgDavid). 2018-11-19 23:16:33 +01:00
mooz fa59803eeb Fixed JR instruction. 2018-11-19 23:15:46 +01:00
mooz 8f5e37dd82 Added install configuration. 2018-11-19 19:18:44 +01:00
mooz 18faa0ba34 Fixed cmake invokation in appveyor configuration file. 2018-11-19 19:00:37 +01:00
mooz d6254b2ca3 Fixed extension. 2018-11-19 18:53:02 +01:00
mooz 7bfb04cf86 Added appveyor configuration file. 2018-11-19 18:39:09 +01:00
mooz ae99514183 Added standalone version of the uPD7801 compiler. 2018-11-18 22:13:04 +01:00
mooz 345f9d522d Added the last opcodes of 0x70xx. 2018-11-18 00:10:32 +01:00
mooz c42546e483 Fixed opcode size. 2018-11-17 23:43:03 +01:00
mooz ec56b7b504 Added MOV r8,(hhll) instructions. 2018-11-17 23:40:10 +01:00
mooz b47f3ebb99 Added MOV (hhll),R8 instructions. 2018-11-17 23:17:09 +01:00
mooz bac144896a Added SSPD, LSPD, SBCD, LBCD, SDED, LDED, SHLD and LHLD instructions. 2018-11-17 22:30:07 +01:00
mooz b9fc343548 Added 0x74xx instructions. 2018-11-17 18:58:07 +01:00
mooz 08086d222e Added 0x64xx instructions. 2018-11-16 23:31:48 +01:00
mooz a2e44ae24a Added ANA, XRA, ORA, ADDNC, GTA, SUBNB, LTA, ADD, ADC, SUB, NEA, SBB, EQA, ONA, OFFA instructions. 2018-11-15 21:46:34 +01:00
mooz db038e29e5 Added 0x4dxx MOV instructions. 2018-11-15 19:44:13 +01:00
mooz 0d66e70a69 Added 0x4Cxx MOV instructions. 2018-11-15 19:25:57 +01:00
mooz c09a04964a Added IN and OUT instructions. 2018-11-12 23:27:09 +01:00
mooz 283c4256f5 Added SKIT, SKNIT, RLL, RLR, SKC, SKZ, SKNC and SKNZ. 2018-11-11 23:31:52 +01:00
mooz 711fe67574 PUSH V and POP V were changed to PUSH VA and POP VA. 2018-11-11 22:29:06 +01:00
mooz 636b5297c1 Added PUSH and POP. 2018-11-11 22:26:30 +01:00
mooz 77b69902c2 Added LDAX+, LDAX-, STAX+ and STAX- (renamed ldaxi, ldaxd, staxi, staxd). 2018-11-11 15:54:56 +01:00
mooz fe3cdabf4f Added MVWI and EQIW instructions. 2018-11-11 01:04:16 +01:00
mooz b9a9b9027d Added LDAX and STAX instructions. 2018-11-10 21:41:42 +01:00
mooz 09a45eaffb Added MVIX instruction. 2018-11-10 17:48:14 +01:00
mooz 93ffdf28dd Added DCX and INX instructions. 2018-11-10 17:15:00 +01:00
mooz b04ff787d4 Added BITx instructions. 2018-11-10 17:03:38 +01:00
mooz 4ef0a69e8e Added INRW, LDAW, DCRW and STAW instructions. 2018-11-10 14:22:38 +01:00
mooz f917afff6a Added JRE instruction. 2018-11-10 12:50:03 +01:00
mooz 2d96e4e1d2 Added MOV (8 bits version) instruction. 2018-11-10 00:23:56 +01:00
mooz 6708ddaecc Added and tested a bunch of opcodes. 2018-11-09 23:40:05 +01:00
mooz 6fc00c2b63 Fixed table opcode. 2018-11-09 21:55:21 +01:00
mooz 6efbd07034 Added 16 bits implied instructions (ei, di, etc...). 2018-11-09 19:04:44 +01:00
mooz 564746133a Added LXI instruction. 2018-11-08 01:11:17 +01:00
mooz 6fafc034a0 Added support for single register addressing. 2018-11-07 22:42:48 +01:00
mooz e56a44afa9 Added CALF instruction. 2018-11-07 20:28:48 +01:00
mooz f8ad543033 first uPD7801 assembler tests. 2018-11-06 00:29:27 +01:00
mooz d5c53b2f01 op_eval_byte and op_eval_word are now arch specific. 2018-11-04 23:12:28 +01:00
mooz e88b89cd01 Started working adding uPD7801 2018-11-04 22:52:47 +01:00
mooz 662ef74b53 Moved architecture independent code to asm.lua 2018-11-03 17:16:17 +01:00
g012 9c576ef774 [NES] Revamped and tested all GxROM PRG bank switching, and added a sample.
Added asserts in 6502.lua.
2018-01-03 20:34:09 +01:00
g012 c888a63ee3 [NES] Added required ; before and in GxROM mapper functions. 2017-12-29 10:49:06 +01:00
g012 0eefee0282 Added one more PRG bank in nes_bank1.l65 sample because Fceux is not happy with 3 only. 2017-12-28 22:31:52 +01:00
g012 9bf0461f41 Fixed NROM missing variable. 2017-12-28 22:13:23 +01:00
g012 d539f9c194 Added nes_bank1.l65 sample.
Allowed more customization for CHR rom layout.
2017-12-28 21:16:45 +01:00
g012 808bf10b26 Fixed labels which are not sections not added to symbolsorg table. 2017-12-28 16:47:00 +01:00
g012 f3bd018090 Added ability to define the zero page outside 00-FF, with PC-Engine in mind. 2017-12-25 23:23:22 +01:00
g012 75db428750 [NES] Fixed wrong index in FCEUX nl debugger file generation. 2017-12-24 00:43:07 +01:00
g012 22a89970f8 Allowed free positioning of referenced-by-string using charset of CHR data, by delaying evaluation of charset value. 2017-12-23 23:58:42 +01:00
g012 08d1dd99ad NES Hello World sample modified to define CHR data directly in source, like the VCS sample. 2017-12-23 16:36:02 +01:00
g012 63fcde04ee [NES] Added MMC5 mapper. 2017-12-23 00:39:42 +01:00
g012 a60cf98c57 [NES] Added UxROM, GxROM, CNROM, MMC1 mappers. 2017-12-22 13:24:01 +01:00
g012 d740492e8f [NES] Added MMC3 mapper. 2017-12-22 01:42:54 +01:00
g012 99deb2b875 Fixed DASM symbol file generation. 2017-12-20 19:18:21 +01:00
g012 1245156c19 Added FCEUX and Mesen symbol files generation for NES. 2017-12-20 18:55:53 +01:00
g012 cbe967a074 Added first version of NES lib and hello world sample. 2017-12-18 23:59:43 +01:00
g012 d27e4ce8d1 Added VCS music cart sample. 2017-12-12 18:20:03 +01:00
g012 c95b8f8f24 Fixed init bug in hook sample too. 2017-10-23 09:19:58 +02:00
g012 f589f55961 Fixed VCS init function: used tsx instead of txs. 2017-10-22 09:49:53 +02:00
46 changed files with 9483 additions and 2247 deletions

14
.gitignore vendored
View File

@ -1,4 +1,10 @@
/build
/tmp
*.bin
*.sym
/build
/tmp
/zig-*
*.bin
*.cdl
*.nes
*.nes.deb
*.sym
*.mlb
*.nl

View File

@ -8,15 +8,8 @@ compiler:
script:
- cmake . -DCMAKE_BUILD_TYPE=Release
- make
- cd samples
- ../l65 vcs_banks.l65
- ../l65 vcs_basic.l65
- ../l65 vcs_flush.l65
- ../l65 vcs_hello.l65
- ../l65 vcs_hooks.l65
- ../l65 vcs_music.l65
- ../l65 vcs_spr48.l65
- cd ..
- cd samples; for f in *.l65; do echo $f; ../l65 $f || break; done; cd ..
- cd samples; for f in *.l7801; do echo $f; ../l7801 $f || break; done; cd ..
- cp l65 l65-$TRAVIS_TAG-$TRAVIS_OS_NAME
deploy:
provider: releases

991
6502.lua

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ set(L65_SOURCE_DIR ${PROJECT_SOURCE_DIR})
set(L65_BINARY_DIR ${PROJECT_BINARY_DIR})
set(L65_VERSION_MAJOR "1")
set(L65_VERSION_MINOR "2")
set(L65_VERSION_MINOR "3")
set(L65_VERSION_REVISION "0")
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")
@ -63,7 +63,7 @@ else ()
add_definitions(-D_FILE_OFFSET_BITS=64)
add_compile_options(-fno-strict-aliasing -fomit-frame-pointer -ffast-math -fvisibility=hidden)
add_compile_options(-Wsign-compare -Wno-missing-braces -Wno-unused-result -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-parentheses)
add_compile_options(-Wsign-compare -Wno-missing-braces -Wno-unused-result -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-parentheses -Wno-string-plus-int)
endif()
@ -80,6 +80,7 @@ set(L65_HEADERS
file(GLOB L65_FILES ${L65_SOURCE_DIR}/*.l65)
set(L65_SCRIPTS
${L65_SOURCE_DIR}/asm.lua
${L65_SOURCE_DIR}/6502.lua
${L65_SOURCE_DIR}/dkjson.lua
${L65_SOURCE_DIR}/l65.lua
@ -116,3 +117,42 @@ endif()
target_link_libraries(embed ${LINKLIBS})
target_link_libraries(${PROJECT_NAME} ${LINKLIBS})
set(L7801_SOURCES
${L65_SOURCE_DIR}/lfs.c
${L65_SOURCE_DIR}/lpeg.c
${L65_SOURCE_DIR}/l7801.c
)
set(L7801_HEADERS
${L65_HEADERS}
)
set(L7801_FILES ${L65_SOURCE_DIR}/scv.l7801)
set(L7801_SCRIPTS
${L65_SOURCE_DIR}/asm.lua
${L65_SOURCE_DIR}/uPD7801.lua
${L65_SOURCE_DIR}/dkjson.lua
${L65_SOURCE_DIR}/l7801.lua
${L65_BINARY_DIR}/l65cfg.lua
${L65_SOURCE_DIR}/re.lua
${L7801_FILES}
)
add_custom_command(
OUTPUT ${L65_BINARY_DIR}/scripts_7801.h
COMMAND embed -o ${L65_BINARY_DIR}/scripts_7801.h ${L7801_SCRIPTS}
DEPENDS embed ${L7801_SCRIPTS}
)
add_custom_target(prereq_7801 DEPENDS ${L65_BINARY_DIR}/scripts_7801.h)
add_executable(l7801 ${L7801_SOURCES} ${L7801_HEADERS} ${L7801_FILES})
add_dependencies(l7801 prereq_7801)
set_property(TARGET l7801 PROPERTY C_STANDARD 99)
target_include_directories(l7801 PRIVATE "${L7801_SOURCE_DIR}" "${L65_BINARY_DIR}")
target_link_libraries(l7801 ${LINKLIBS})
install(TARGETS l65 l7801
RUNTIME DESTINATION bin)
install(DIRECTORY samples DESTINATION doc)
install(FILES README.md DESTINATION doc)

View File

@ -1,7 +1,7 @@
# l65
[![Build Status](https://travis-ci.org/g012/l65.svg?branch=master)](https://travis-ci.org/g012/l65)
l65 is a 6502 assembler, operating from within Lua and written in Lua. This means assembler mnemonics become regular Lua statements anywhere in the middle of Lua code.
l7801 is a µPD7801 assembler, using l65 framework, created and maintained by [@MooZ] (https://github.com/BlockoS).
Table of Contents
=================
@ -47,7 +47,7 @@ Table of Contents
* [resolve()](#resolve)
* [genbin([filler])](#genbinfiller)
* [writebin(filename)](#writebinfilename)
* [writesym(filename)](#writesymfilename)
* [writesym(filename [, format])](#writesymfilename--format)
* [Parser Functions](#parser-functions)
* [Pragmas](#pragmas)
* [syntax6502 on|off](#syntax6502-onoff)
@ -66,11 +66,12 @@ Table of Contents
* [searcher(name)](#searchername)
* [load, loadfile, dofile ; installhooks(), uninstallhooks()](#load-loadfile-dofile--installhooks-uninstallhooks)
* [image(filename)](#imagefilename)
* [VCS Module](#vcs-module)
* [Platform Modules](#platform-modules)
* [Building](#building)
* [Windows](#windows)
* [Linux](#linux)
* [Vim files installation](#vim-files-installation)
* [Visual Studio Code extension](#vscode-extension)
* [TODO List](#todo-list)
* [Credits](#credits)
* [License](#license)
@ -105,6 +106,9 @@ Have a look at these files in the `samples` folder to get started with l65:
* `vcs_flush.l65`: a port of flewww's famous flush logo from the demo [.bin](http://www.pouet.net/prod.php?which=69666)
* `vcs_spr48.l65`: a port of [.bin](http://www.pouet.net/prod.php?which=69666) title 48 pixels sprite animation.
* `vcs_music.l65`: imports and plays back a TIATracker .ttt file directly.
* `vcs_mcart.l65`: imports all TIATracker .ttt files in the folder supplied as arg, or current directory, and builds a music cart.
* `nes_hello.l65`: a NES 'Hello World' using BG tiles, and automatic positioned font within a 4kB CHR rom page.
There's also `vcspal.act`, a palette file for authoring software for VCS. Use this palette or a similar one to create 8b PNG for `l65.image` and helper loaders depending on it. You can generate such a palette, or a GPL one for GIMP using [vcsconv](https://github.com/g012/vcsconv) `authpalette` command.
@ -314,6 +318,8 @@ Check if `v` is within long range, call `error` if it is not, or convert negativ
`pcall_za`: ...unless this field is set to system's `pcall`. Defaults to module's `pcall`. This field is used only by the `za*` (`zab`, `zax`, `zay`) virtual addressing modes, to discriminate between zeropage and absolute addressing.
`zeropage`: a function with one number parameter, returning its zeropage byte value if it is within zero/direct page addressing range, nothing otherwise. Set by default to page [0x0000, 0x00ff].
`symbols`: list of symbols, resolved or not. Values can be anything before the resolve phase, but must be numbers after (except for the metatable fields). Set as the metatable of the 6502 module, which itself should be set as the metatable of the current `_ENV` environment.
`locations`: list of all the registered locations.
@ -333,7 +339,7 @@ Check if `v` is within long range, call `error` if it is not, or convert negativ
`id()`: return a new unique numeric identifier.
`stats`: a table of statistics regarding the build:
* `cycles`: the total 6502 cycle count of the program, considering no branch is taken and no page is crossed.
* `cycles`: the total 6502 cycle count of the program, assuming no branch is taken and no page is crossed.
* `used`: the total ROM bytes actually used by the program.
* `unused`: total empty ROM space.
* `resolved_count`: number of symbols resolved during resolve phase.
@ -465,9 +471,13 @@ Return a table, where each entry is a byte.
Write the final binary into `filename`.
#### writesym(filename)
#### writesym(filename [, format])
Write a DASM symbol file into `filename` for debuggers. The last `_` in a label is turned into a `.`, to get stripping of the prefixed global label working in Stella. As such, it's best to avoid using `_` in local label names, after the initial one.
Write one or more symbol files into `filename` (prefix in case of multiple outputs) for debuggers. The last `_` in a label is turned into a `.`, to get stripping of the prefixed global label working in Stella. As such, it's best to avoid using `_` in local label names, after the initial one.
`format` defaults to 'dasm'.
All platforms: 'dasm', 'lua'.
NES: 'mesen', 'fceux'.
### Parser Functions
@ -593,10 +603,12 @@ Return `nil` and an error message on failure, or a table with the following fiel
* `height`: image height, in pixels
* [1..width\*height]: the pixels, as palette indices (all bytes)
### VCS Module
### Platform Modules
`vcs.l65` is a helper file for developing on Atari 2600 VCS. It's embedded into the l65 executable. It sets the 6502.lua module as metatable of the current `_ENV`, defines all TIA and PIA symbols, some helper constants and functions, as well as mapper helpers and automatic cross bank call functions. See the samples directory for usage examples, and browse vcs.l65 directly for the list of self-explanatory helpers.
`nes.l65` is for NES/Famicom. It provides templates for some mappers, including 0-NROM, 1-MMC1, 2-UxROM, 3-CNROM, 4-MMC3, 5-MMC5, and 66-GxROM.
## Building
Use CMake to build a standalone executable. Following are basic instructions if you've never used CMake.
@ -645,6 +657,10 @@ make
Note that the syntax file includes some highlighting for features only activated via pragmas: `dna`, `xsr`, `rtx` and `far`. If you do not want to use these keywords, remove them from the syntax file.
## Visual Studio Code extension
Install the "l65" extension from the marketplace or check [l65-vscode](https://github.com/g012/l65-vscode) repository.
## TODO List
* [k65](http://devkk.net/wiki/index.php?title=K65) style syntax

41
appveyor.yml Normal file
View File

@ -0,0 +1,41 @@
version: '0.1.{build}'
os: Visual Studio 2013
platform: x64
configuration: Release
before_build:
- mkdir install
- mkdir build
- cd build
- cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_INSTALL_PREFIX=../install ..
build_script:
- FOR /F "tokens=*" %%i in ('git describe') do SET COMMITNOW=%%i
- if defined APPVEYOR_REPO_TAG_NAME (set L65_RELEASE=true) else (set L65_SNAPSHOT=true)
- if defined L65_RELEASE set L65_VERSION=%APPVEYOR_REPO_TAG_NAME:~1%
- if defined L65_RELEASE echo Building l65 %L65_VERSION%... (from %COMMITNOW%)
- if defined L65_SNAPSHOT set L65_VERSION=%APPVEYOR_BUILD_VERSION%
- if defined L65_SNAPSHOT echo Building l65 snapshot %L65_VERSION%... (from %COMMITNOW%)
- cmake --build . --config Release
- cmake --build . --config Release --target install
after_build:
- cd ../install
- 7z a ../l65.zip * -tzip
artifacts:
- path: l65.zip
name: l65-${L65_VERSION}.zip
deploy:
- provider: GitHub
release: l65-${L65_VERSION}
description: 'l65 msvc12 win64 build'
auth_token:
secure: xRIravp3mvMiAgogn6KGuK1yrolmSJUsum/wPXwu82bh97O7YkuQ3B178ac+WHml
artifact: /l65.*\.zip/
draft: true
on:
appveyor_repo_tag: true
push_release: true

827
asm.lua Normal file
View File

@ -0,0 +1,827 @@
local M = {}
local symbols,symbolsorg={},{} M.symbols,M.symbolsorg=symbols,symbolsorg
local locations={} M.locations=locations
local sections={} M.sections=sections
local relations={} M.relations=relations
local stats={} M.stats=stats setmetatable(stats, stats)
local before_link={} M.before_link=before_link
M.strip = true -- set to false to disable dead stripping of relocatable sections
M.strip_empty = false -- set to true to strip empty sections: their label will then not resolve
M.pcall = pcall -- set to empty function returning false to disable eval during compute_size()
-- set to pcall directly if you want to keep ldazab/x/y eval during compute_size() even if
-- disabled for other parts (required to distinguish automatically between zp/abs addressing)
M.pcall_za = function(...) return M.pcall(...) end
M.__index = M
M.__newindex = function(t,k,v)
local kk = k
if type(k) == 'string' and k:sub(1,1) == '_' and M.label_current then
kk = M.label_current .. k
end
if symbols[kk] then error("attempt to modify symbol " .. k) end
rawset(t,k,v)
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
for _,v in ipairs(before_link) do v() end
if M.strip then
symbols.__index = function(tab,key)
local val = rawget(symbols, key)
if type(val) == 'table' and val.type == 'label' then
val.section.refcount = val.section.refcount + 1
end
return val
end
end
for _,section in ipairs(sections) do
section:compute_size()
end
symbols.__index = symbols
local chunk_reserve = function(section, chunk_ix)
local chunks = section.location.chunks
local chunk = chunks[chunk_ix]
local holes = section.holes
local new_chunks,ins = {},table.insert
local chunk1 = { id=id(), start=chunk.start, size=section.org-chunk.start }
local hole_ix = 1
local hole1 = holes[1]
if hole1 and hole1.start==0 then
chunk1.size = chunk1.size + hole1.size
hole_ix = 2
end
if chunk1.size > 0 then ins(new_chunks, chunk1) end
while hole_ix <= #holes do
local hole = holes[hole_ix]
local chunki = { id=id(), start=section.org+hole.start, size=hole.size }
ins(new_chunks, chunki)
hole_ix = hole_ix + 1
end
local chunkl = { id=id(), start=section.org+section.size, size=chunk.start+chunk.size-(section.org+section.size) }
local chunkn = new_chunks[#new_chunks]
if chunkn and chunkn.start+chunkn.size==chunkl.start then
chunkn.size = chunkn.size + chunkl.size
elseif chunkl.size > 0 then
ins(new_chunks, chunkl)
end
table.remove(chunks, chunk_ix)
for i=chunk_ix,chunk_ix+#new_chunks-1 do ins(chunks, i, new_chunks[i-chunk_ix+1]) end
end
local chunk_from_address = function(section, address)
local chunks,rorg = section.location.chunks,section.location.rorg
for i,chunk in ipairs(chunks) do
if address >= chunk.start and address+section.size <= chunk.start+chunk.size then
return chunk,i
end
end
end
local check_section_position = function(section, address, chunk)
local chunk = chunk_from_address(section, address)
if not chunk then return end
local rorg = section.location.rorg
if section.align then
local raddress = rorg(address)
if section.offset then raddress = raddress - section.offset end
if raddress % section.align ~= 0 then return end
end
for _,constraint in ipairs(section.constraints) do
local cstart, cfinish = address+constraint.start, address+constraint.finish
local s, f = rorg(cstart) // 0x100, rorg(cfinish) // 0x100
if s==f or math.ceil(s)==math.floor(s) and s+1==f and cfinish-cstart==0x100 then
if constraint.type == 'crosspage' then return end
else
if constraint.type == 'samepage' then return end
end
end
local address_end = address+section.size
local waste = math.min(address - chunk.start, chunk.size - (address_end - chunk.start))
local raddress,raddress_end = rorg(address),rorg(address_end)
local align,cross=0x100,0
repeat
local cross_count = (raddress_end+align-1)//align - (raddress+align-1)//align
if raddress&(align-1) == 0 then cross_count=cross_count+1 end
cross = cross + align * align * cross_count
align = align>>1
until align==1
local lsb=0
for i=0,15 do if raddress&(1<<i) == 0 then lsb=lsb+1 else break end end
return waste, cross, lsb
end
local position_section = function(section, constrain)
local location = section.location
local chunks,rorg = location.chunks,location.rorg
table.sort(chunks, function(a,b) if a.size==b.size then return a.id<b.id end return a.size<b.size end)
for chunk_ix,chunk in ipairs(chunks) do if chunk.size >= section.size then
local waste,cross,lsb,position = math.maxinteger,math.maxinteger,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
local nwaste, ncross, nlsb = check_section_position(section, address, chunk)
if nwaste then
if constrain then
nwaste, ncross, nlsb = constrain(address, nwaste, ncross, nlsb)
if not nwaste then goto skip end
end
if nwaste > waste then goto skip end
if nwaste == waste then
-- if waste is the same, keep the one that uses the least amount of aligned addresses
if ncross > cross then goto skip end
if ncross == cross then
-- if cross count is same, take the one with the most set LSB count (eg. select 11 over 10)
if nlsb > lsb then goto skip end
end
end
position,waste,cross,lsb = address,nwaste,ncross,nlsb
::skip::
end
end
end
local finish = math.min(chunk.start + 0xff, chunk.start + chunk.size - section.size)
usage_lowest(chunk.start, finish)
if chunk.size ~= math.huge then
local start = math.max(chunk.start + chunk.size - section.size - 0xff, chunk.start)
usage_lowest(start, chunk.start + chunk.size - section.size)
end
if position then
section.org = position
chunk_reserve(section, chunk_ix)
--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
return position
end
end end
end
stats.used = 0
stats.unused = 0
stats.cycles = 0
local related_sections = {}
for _,location in ipairs(locations) do
local sections,rorg = location.sections,location.rorg
-- filter sections list
local position_independent_sections = {}
local symbols_to_remove = {}
local section_count = #sections
location.cycles=0 location.used=0
for ix,section in ipairs(sections) do
location.cycles = location.cycles + section.cycles
location.used = location.used + section.size
if section.size == 0 then
if M.strip_empty or section.weak then
sections[ix]=nil
if not section.org then table.insert(symbols_to_remove, section.label) end
else
section.org = location.start
end
elseif not section.org then
if M.strip and not section.refcount and not section.strong then
sections[ix]=nil
table.insert(symbols_to_remove, section.label)
elseif section.related then
table.insert(related_sections, section)
else
table.insert(position_independent_sections, section)
end
end
end
do local j=0 for i=1,section_count do
if sections[i] ~= nil then j=j+1 sections[j],sections[i] = sections[i],sections[j] end
end end
for _,v in ipairs(symbols_to_remove) do symbols[v] = nil end
location.position_independent_sections = position_independent_sections
stats.cycles = stats.cycles + location.cycles
stats.used = stats.used + location.used
-- fixed position sections
for section_ix,section in ipairs(sections) do if section.org then
if section.org < location.start or section.org > (location.finish or math.huge) then
error("ORG section " .. section.label .. " starts outside container location")
end
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)
goto chunk_located
end
end
error("ORG section " .. section.label .. " overflows its location")
::chunk_located::
end end
end
table.sort(related_sections, function(a,b) if a.size==b.size then return a.id<b.id end return a.size>b.size end)
for _,section in ipairs(related_sections) do if not section.org then
local related,ins = {},table.insert
local function collect(section_parent, offset)
local relatives = relations[section_parent]
if relatives then
for relative,relative_offset in pairs(relatives) do
if not related[relative] and relative ~= section then
relative_offset = relative_offset + offset
related[relative] = relative_offset
collect(relative, relative_offset)
end
end
end
end
collect(section, 0)
local location_start = section.location.start
local position = position_section(section, function(address, waste, cross, lsb)
local waste, cross, lsb = 0, 0, 0
for section,offset in pairs(related) do
local section_address = address + (section.location.start - location_start) + offset
local nwaste, ncross, nlsb = check_section_position(section, section_address)
if not nwaste then return end
waste, cross, lsb = waste+nwaste, cross+ncross, lsb+nlsb
end
return waste, cross, lsb
end)
if not position then
error("unable to find space for related section '" .. section.label .. "' of size " .. section.size)
end
for section,offset in pairs(related) do
section.org = position + (section.location.start - location_start) + offset
local chunk,chunk_ix = chunk_from_address(section, section.org)
chunk_reserve(section, chunk_ix)
end
end end
for _,location in ipairs(locations) do
local position_independent_sections = location.position_independent_sections
table.sort(position_independent_sections, function(a,b) if a.size==b.size then return a.label>b.label end return a.size>b.size end)
for _,section in ipairs(position_independent_sections) do
if not position_section(section) then
error("unable to find space for section '" .. section.label .. "' of size " .. section.size)
end
end
-- unused space stats
local unused = 0
for _,chunk in ipairs(location.chunks) do
if chunk.size ~= math.huge then
unused = unused + chunk.size
else
location.stops_at = chunk.start-1
end
end
location.unused = unused
stats.unused = stats.unused + unused
end
end
M.resolve = function()
if stats.resolved_count then return end
M.link()
stats.resolved_count = 0
repeat local count = 0
for k,v in pairs(symbols) do if k ~= '__index' then
local t = type(v)
if t == 'function' then v=v() t=type(v) symbols[k]=v count=count+1 end
if t == 'table' and type(v.resolve) == 'function' then symbols[k],symbolsorg[k]=v.resolve() count=count+1 end
if t == 'string' and symbols[v] then symbols[k]=symbols[v] count=count+1 end
stats.resolved_count = stats.resolved_count + count
end end until count == 0
-- set local label references resolver
local llresolver = { __index = function(tab,key)
if type(key) ~= 'string' or key:sub(1,1) ~= '_' or not M.label_current then return nil end
return symbols[M.label_current .. key]
end }
setmetatable(symbols, llresolver)
end
M.genbin = function(filler)
if #locations == 0 then return end
if not filler then filler = 0 end -- brk opcode
M.resolve()
local bin = {}
local ins,mov = table.insert,table.move
table.sort(locations, function(a,b) return a.start < b.start end)
local of0 = locations[1].start
local fill
for _,location in ipairs(locations) do
if location.start < #bin then
error(string.format("location [%04x,%04x] overlaps another", location.start, location.finish or location.stops_at))
end
if fill then for i=#bin+of0,location.start-1 do ins(bin, filler) end end
M.size=0 M.cycles=0
local sections = location.sections
table.sort(sections, function(a,b) return a.org < b.org end)
for _,section in ipairs(sections) do
for i=#bin+of0,section.org-1 do ins(bin, filler) end
local bin_offset = math.min(#bin, section.org-of0)+1
for _,instruction in ipairs(section.instructions) do
local b,o = instruction.bin
if type(b) == 'function' then b,o = b(filler) end
if type(b) == 'table' then mov(b,1,#b,bin_offset,bin) bin_offset=bin_offset+#b
elseif b then bin[bin_offset]=b bin_offset=bin_offset+1 end
if o then
bin_offset=bin_offset+o
for i=#bin,bin_offset-1 do ins(bin, filler) end
end
M.size=#bin M.cycles=M.cycles+(instruction.cycles or 0)
end
end
fill = not location.nofill
if location.finish and fill then
for i=#bin+of0,location.finish do ins(bin, filler) end
end
end
stats.bin_size = #bin
return bin
end
M.writebin = function(filename, bin)
if not filename then filename = 'main.bin' end
if not bin then bin = M.genbin() end
local f = assert(io.open(filename, "wb"), "failed to open " .. filename .. " for writing")
f:write(string.char(table.unpack(bin)))
f:close()
end
-- 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
local k,vorg=symbols[v],v
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
local e = entry(k,v,vorg) 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
stats.__tostring = function()
local s,ins={},table.insert
ins(s, " Free Used Size Area")
for _,location in ipairs(locations) do
local name = (location.name or ''):sub(1,14)
name = string.rep(' ', 14-#name) .. name
local fmt = "%s %5d %5d %5d [%04X-%04X]"
if location.finish then
local size = location.finish-location.start+1
ins(s, string.format(fmt, name,
location.unused, size-location.unused, size, location.start, location.finish))
else
ins(s, string.format(fmt, name,
location.unused, location.used, location.stops_at-location.start+1, location.start, location.stops_at))
end
end
if #locations > 1 then
ins(s, string.format(" --- Total --- %5d %5d %5d", stats.unused, stats.used, stats.bin_size))
end
return table.concat(s, '\n')
end
M.location = function(start, finish)
local location
if type(start) ~= 'table' then
location = { start=start, finish=finish }
else
if start.type == 'location' then
for _,v in ipairs(locations) do if v == start then
M.location_current = start
return start
end end
error("unable to find reference to location [" .. (start.start or '?') .. ", " .. (start.finish or '?') .. "]")
end
location = start
location.start = start[1]
location.finish = start[2]
if type(location.rorg) == 'number' then
local offset = location.rorg - location.start
location.rorg = function(x) return x+offset end
end
end
location.type = 'location'
location.sections = {}
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={ { id=id(), start=location.start, size=size } }
locations[#locations+1] = location
M.location_current = location
return location
end
M.section = function(t)
local section = {}
local name = t or 'S'..id()
if type(name) ~= 'string' then
assert(type(t) == 'table', "invalid arguments for section")
if t.type == 'section' then
for _,v in ipairs(sections) do if v == t then
M.location_current = t.location
M.section_current = t
return t
end end
error("unable to find reference to section " .. (t.label or '?'))
end
section=t name=t[1] or 'S'..id() 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)
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)
section.holes = {}
section.refcount = 0
function section:compute_size()
local instructions = self.instructions
self.size=0 self.cycles=0
for _,instruction in ipairs(instructions) do
instruction.offset = self.size
local ins_sz = instruction.size or 0
if type(ins_sz) == 'function' then
-- evaluation is needed to get the size (distinguish zpg/abs)
-- labels and sections are not resolved at this point, so
-- evaluation will fail if the size is not explicitly stated (.b/.w);
-- in that case, assume max size
ins_sz = ins_sz()
end
self.size = self.size + ins_sz
self.cycles = self.cycles + (instruction.cycles or 0)
end
for _,constraint in ipairs(self.constraints) do
constraint.start = instructions[constraint.from].offset
constraint.finish = constraint.to==#instructions and self.size or instructions[constraint.to+1].offset
end
end
return section
end
-- relate(section1, section2 [, [offset1,] offset2])
-- Add a position relationship between 'section1' and 'section2', with 'offset1'
-- bytes from selected position for 'section2', and 'offset2' bytes from selec-
-- -ted positon for 'section1'.
-- If offset1 is omitted, -offset2 is used.
M.relate = function(section1, section2, offset, offset2)
assert(section1.type == 'section', "section1 is not a section")
assert(section2.type == 'section', "section2 is not a section")
local rel1 = relations[section1] or {}
rel1[section2] = (offset2 or offset) or 0
relations[section1] = rel1
local rel2 = relations[section2] or {}
rel2[section1] = (offset2 and offset) or -rel1[section2]
relations[section2] = rel2
section1.related = true
section2.related = true
end
M.label = function(name)
local label,offset
local section,rorg = M.section_current,M.location_current.rorg
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
M.label_current = name
label.bin = function() M.label_current = name end
end
if symbols[name] then error("duplicate symbol: " .. name) end
symbols[name] = label
label.label = name
label.size = function()
offset = section.size
label.size = 0
return 0
end
label.resolve = function()
local o = section.org + offset
return rorg(o),o
end
table.insert(section.instructions, label)
return name,label
end
M.samepage = function()
local section = M.section_current
table.insert(section.constraints, { type='samepage', from=#section.instructions+1 })
end
M.crosspage = function()
local section = M.section_current
table.insert(section.constraints, { type='crosspage', from=#section.instructions+1 })
end
M.endpage = function()
local section = M.section_current
local constraint = section.constraints[#section.constraints]
assert(constraint and not constraint.to, "closing constraint, but no constraint is open")
constraint.to = #section.instructions
end
-- skip(bytes)
-- Insert a hole in the section of 'bytes' bytes, which can be used by other
-- relocatable sections.
M.skip = function(bytes)
local l65dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local ins,section = {},M.section_current
ins.size = function()
table.insert(section.holes, { start=ins.offset, size=bytes })
return bytes
end
ins.bin = function(filler) return nil,bytes end
table.insert(section.instructions, ins)
end
-- sleep(cycles [, noillegal])
-- Waste 'cycles' cycles. If 'noillegal' is true, trashes NZ flags.
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 op_resolve = function(v)
if type(v) == 'function' then v=v() end
if type(v) == 'table' and v.label then v = symbols[v.label] end
if type(v) == 'string' then v = symbols[v] end
if type(v) ~= 'number' then error("unresolved symbol: " .. tostring(v)) end
return v
end M.op_resolve = op_resolve
local size_ref = function(v)
if type(v) == 'string' then v=symbols[v] end
if type(v) == 'table' and v.type == 'label' then v.section.refcount = 1 + (v.section.refcount or 0) end
end
M.size_ref = size_ref
local size_dc = function(v)
if type(v) == 'function' then
local r,x = M.pcall(v)
if not r or not x then return v end
end
size_ref(v)
return v
end
M.size_dc = size_dc
local size_op = function(late, early)
if type(late) == 'function' then
local r,x = M.pcall(late, early or 0, op_resolve)
if not r or not x then return late,early end
late=x early=nil
end
size_ref(late) size_ref(early)
return late,early
end
M.size_op = size_op
local byte_normalize = function(v)
if v < -0x80 or v > 0xFF then error("value out of byte range: " .. v) end
if v < 0 then v = v + 0x100 end
return v & 0xff
end
M.byte_normalize = byte_normalize
local word_normalize = function(v)
if v < -0x8000 or v > 0xFFFF then error("value out of word range: " .. v) end
if v < 0 then v = v + 0x10000 end
return v & 0xffff
end
M.word_normalize = word_normalize
local long_normalize = function(v)
if v < -0x80000000 or v > 0xFFFFFFFF then error("value out of word range: " .. v) end
if v < 0 then v = v + 0x100000000 end
return v & 0xffffffff
end
M.long_normalize = long_normalize
-- charset([s] [, f])
-- Set a new charset to be used for next string data in byte().
-- Without argument, revert to Lua charset.
-- s: string of all letters of charset
-- f: letter index offset or function to transform the letter index
M.charset = function(s, f)
local st = type(s)
if st == 'nil' then M.cs = nil return s end
if st == 'table' then M.cs = s return s end
if not f then f = function(v) return v end
elseif type(f) == 'number' then f = function(v) return v + f end end
local t,i={},0
for c in s:gmatch'.' do local v=i t[c]=function() return f(v) end i=i+1 end
M.cs=t
return t
end
M.byte_impl = function(args, nrm)
local l65dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local data,cs = {},M.cs
for k,v in ipairs(args) do
local t = type(v)
if t == 'number' or t == 'function' then data[#data+1] = v
elseif t == 'table' then table.move(v,1,#v,#data+1,data)
elseif t == 'string' then
if cs then
for c in v:gmatch'.' do
local i=cs[c]
if not i then error("character " .. c .. " is not part of current charset") end
data[#data+1]=i
end
else
local s = {v:byte(1,#v)}
table.move(s, 1, #s, #data+1, data)
end
else error("unsupported type for byte() argument: " .. t .. ", value: " .. v)
end
end
local size = function()
for i,v in ipairs(data) do data[i] = size_dc(v) end
return #data
end
local bin = function() local l65dbg=l65dbg
local b={}
for k,v in ipairs(data) do
if type(v) == 'function' then v = v() end
local vt = type(v)
if vt == 'table' and v.label then v = symbols[v.label]
elseif vt == 'string' then v = symbols[v] end
if type(v) ~= 'number' then error("unresolved symbol for dc.b, index " .. k) end
b[#b+1] = nrm(v)
end
return b
end
table.insert(M.section_current.instructions, { data=data, size=size, bin=bin })
end
-- byte(...)
-- Declare bytes to go into the binary stream.
-- Each argument can be either:
-- * a number resolving to a valid range byte
-- * a string, converted to bytes using the charset previously defined,
-- or Lua's charset if none was defined
-- * a table, with each entry resolving to a valid range byte
-- * a function, resolving to exactly one valid range byte, evaluated
-- after symbols have been resolved
M.byte = function(...)
return M.byte_impl({...}, byte_normalize)
end
local byte_encapsulate = function(args)
for k,v in ipairs(args) do
local vt = type(v)
if vt == 'string' or vt == 'table' and (v.type == 'section' or v.type == 'label') then
args[k] = function() return v end
end
end
return args
end
M.byte_hi = function(...)
return M.byte_impl(byte_encapsulate{...}, function(v) return (v>>8)&0xff end)
end
M.byte_lo = function(...)
return M.byte_impl(byte_encapsulate{...}, function(v) return v&0xff end)
end
-- word(...)
-- Declare words to go into the binary stream.
-- Each argument can be either:
-- * a section or a label
-- * a number resolving to a valid range word
-- * a table, with each entry resolving to a valid range word
-- * a function, resolving to exactly one valid range word, evaluated
-- after symbols have been resolved
M.word = function(...)
local l65dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local args = {...}
local data = {}
for k,v in ipairs(args) do
local t = type(v)
if t == 'number' or t == 'function' or t == 'string' then data[#data+1] = v
elseif t == 'table' then
if v.type == 'section' or v.type == 'label' then data[#data+1] = function() return v end
else table.move(v,1,#v,#data+1,data) end
else error("unsupported type for word() argument: " .. t .. ", value: " .. v)
end
end
local size = function()
for i,v in ipairs(data) do data[i] = size_dc(v) end
return #data*2
end
local bin = function() local l65dbg=l65dbg
local b={}
for k,v in ipairs(data) do
if type(v) == 'function' then v = v() end
local vt = type(v)
if vt == 'table' and v.label then v = symbols[v.label]
elseif vt == 'string' then v = symbols[v] end
if type(v) ~= 'number' then error("unresolved symbol for dc.w, index " .. k) end
v = word_normalize(v)
b[#b+1] = v&0xff
b[#b+1] = v>>8
end
return b
end
table.insert(M.section_current.instructions, { data=data, size=size, bin=bin })
end
M.long = function(...)
local l65dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local args = {...}
local data = {}
for k,v in ipairs(args) do
local t = type(v)
if t == 'number' or t == 'function' or t == 'string' then data[#data+1] = v
elseif t == 'table' then
if v.type == 'section' or v.type == 'label' then data[#data+1] = function() return v end
else table.move(v,1,#v,#data+1,data) end
else error("unsupported type for long() argument: " .. t .. ", value: " .. v)
end
end
local size = function()
for i,v in ipairs(data) do data[i] = size_dc(v) end
return #data*4
end
local bin = function() local l65dbg=l65dbg
local b={}
for k,v in ipairs(data) do
if type(v) == 'function' then v = v() end
local vt = type(v)
if vt == 'table' and v.label then v = symbols[v.label]
elseif vt == 'string' then v = symbols[v] end
if type(v) ~= 'number' then error("unresolved symbol for dc.l, index " .. k) end
v = long_normalize(v)
b[#b+1] = v&0xff
b[#b+1] = (v>>8)&0xff
b[#b+1] = (v>>16)&0xff
b[#b+1] = v>>24
end
return b
end
table.insert(M.section_current.instructions, { data=data, size=size, bin=bin })
end
local op = function(code, cycles, extra_on_crosspage)
return { opc=code, cycles=cycles or cycles_def, xcross=extra_on_crosspage or xcross_def }
end
M.op = op
local op_eval = function(late, early)
local x = early or 0
return type(late) == 'function' and late(x,op_resolve) or x+op_resolve(late)
end
M.op_eval = op_eval
return M

120
build.zig Normal file
View File

@ -0,0 +1,120 @@
const std = @import("std");
const major: u32 = 1;
const minor: u32 = 3;
const revision: u32 = 0;
const targets: []const std.Target.Query = &.{
.{ .os_tag = .windows, .cpu_arch = .x86_64, .cpu_model = .baseline },
.{ .os_tag = .windows, .cpu_arch = .aarch64, .cpu_model = .baseline },
.{ .os_tag = .macos, .cpu_arch = .x86_64, .cpu_model = .baseline, .os_version_min = std.zig.CrossTarget.OsVersion{ .semver = .{ .major = 10, .minor = 7, .patch = 0 } } },
.{ .os_tag = .macos, .cpu_arch = .aarch64, .cpu_model = .baseline, .os_version_min = std.zig.CrossTarget.OsVersion{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } } },
.{ .os_tag = .linux, .cpu_arch = .x86_64, .cpu_model = .baseline, .abi = .musl },
.{ .os_tag = .linux, .cpu_arch = .aarch64, .cpu_model = .baseline },
};
fn setupExe(exe: *std.Build.Step.Compile, embed_output: *const std.Build.LazyPath) !void {
exe.addCSourceFiles(.{
.files = &.{ "lfs.c", "lpeg.c", "main.c" },
.flags = &.{},
});
exe.addIncludePath(embed_output.dirname());
exe.linkLibC();
}
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
///////////////////////////////////////////////////////////////////////////
// Embed tool
const version_storage = struct { var str: [32]u8 = undefined; };
const version = try std.fmt.bufPrint(version_storage.str[0..], "{d}.{d}.{d}", .{ major, minor, revision });
const l65cfg_lua_in = b.addConfigHeader(
.{
.style = .{ .cmake = .{ .path = "l65cfg.lua.in" } },
.include_path = "l65cfg.lua",
},
.{
.L65_VERSION_MAJOR = major,
.L65_VERSION_MINOR = minor,
.L65_VERSION_REVISION = revision,
.L65_VERSION = version,
},
);
// FIXME current version (0.12-dev) of ConfigHeader adds a C comment on top of file
// - comment "try output.appendSlice(c_generated_line);" in ConfigHeader.zig to avoid it
const embed_exe = b.addExecutable(.{
.name = "embed",
.target = target,
.optimize = optimize,
});
embed_exe.addCSourceFile(.{ .file = .{ .path = "embed.c" }, .flags = &.{ "-std=c99" } });
embed_exe.linkLibC();
const embed = b.addRunArtifact(embed_exe);
embed.step.dependOn(&l65cfg_lua_in.step);
embed.addArg("-o");
const embed_output = embed.addOutputFileArg("scripts.h");
embed.addFileArg(l65cfg_lua_in.getOutput());
embed.addFileArg(.{ .path = "asm.lua" });
embed.addFileArg(.{ .path = "6502.lua" });
embed.addFileArg(.{ .path = "dkjson.lua" });
embed.addFileArg(.{ .path = "l65.lua" });
embed.addFileArg(.{ .path = "re.lua" });
embed.addFileArg(.{ .path = "nes.l65" });
embed.addFileArg(.{ .path = "pce.l65" });
embed.addFileArg(.{ .path = "vcs.l65" });
///////////////////////////////////////////////////////////////////////////
// Build for current machine
var exe = b.addExecutable(.{
.name = "l65",
.target = target,
.optimize = optimize,
});
exe.step.dependOn(&embed.step);
try setupExe(exe, &embed_output);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run l65");
run_step.dependOn(&run_cmd.step);
///////////////////////////////////////////////////////////////////////////
// Release binaries
const release_step = b.step("release", "Generate a release");
for (targets) |t| {
const rel_exe = b.addExecutable(.{
.name = "l65",
.target = b.resolveTargetQuery(t),
.optimize = .ReleaseSmall,
});
const target_output = b.addInstallArtifact(rel_exe, .{
.dest_dir = .{
.override = .{
.custom = try t.zigTriple(b.allocator),
},
},
});
rel_exe.step.dependOn(&embed.step);
try setupExe(rel_exe, &embed_output);
release_step.dependOn(&target_output.step);
}
///////////////////////////////////////////////////////////////////////////
// Build samples as test
// TODO
}

68
embed.c
View File

@ -82,40 +82,40 @@ static int writer(lua_State* L, const void* p, size_t size, void* f)
// use custom version so that filename is not used for debug info
LUALIB_API int luaL_loadfilex2 (lua_State *L, const char *filename,
const char *mode, const char *chunkname) {
luai_LoadF lf;
int status, readstatus;
int c;
int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */
if (filename == NULL) {
lua_pushliteral(L, "=stdin");
lf.f = stdin;
}
else {
lua_pushfstring(L, "@%s", filename);
lf.f = fopen(filename, "r");
if (lf.f == NULL) return luai_errfile(L, "open", fnameindex);
}
if (luai_skipcomment(&lf, &c)) /* read initial portion */
lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */
if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */
lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */
if (lf.f == NULL) return luai_errfile(L, "reopen", fnameindex);
luai_skipcomment(&lf, &c); /* re-read initial portion */
}
if (c != EOF)
lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */
lua_pop(L, 1);
lua_pushstring(L, chunkname);
status = lua_load(L, luai_getF, &lf, lua_tostring(L, -1), mode);
readstatus = ferror(lf.f);
if (filename) fclose(lf.f); /* close file (even in case of errors) */
if (readstatus) {
lua_settop(L, fnameindex); /* ignore results from 'lua_load' */
return luai_errfile(L, "read", fnameindex);
}
lua_remove(L, fnameindex);
return status;
const char *mode, const char *chunkname) {
luai_LoadF lf;
int status, readstatus;
int c;
int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */
if (filename == NULL) {
lua_pushliteral(L, "=stdin");
lf.f = stdin;
}
else {
lua_pushfstring(L, "@%s", filename);
lf.f = fopen(filename, "r");
if (lf.f == NULL) return luai_errfile(L, "open", fnameindex);
}
if (luai_skipcomment(&lf, &c)) /* read initial portion */
lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */
if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */
lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */
if (lf.f == NULL) return luai_errfile(L, "reopen", fnameindex);
luai_skipcomment(&lf, &c); /* re-read initial portion */
}
if (c != EOF)
lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */
lua_pop(L, 1);
lua_pushstring(L, chunkname);
status = lua_load(L, luai_getF, &lf, lua_tostring(L, -1), mode);
readstatus = ferror(lf.f);
if (filename) fclose(lf.f); /* close file (even in case of errors) */
if (readstatus) {
lua_settop(L, fnameindex); /* ignore results from 'lua_load' */
return luai_errfile(L, "read", fnameindex);
}
lua_remove(L, fnameindex);
return status;
}
static int pmain(lua_State* L)

View File

@ -32,7 +32,7 @@ local Keywords = lookupify{
'return', 'then', 'true', 'until', 'while',
};
-- 6502
-------------------------------------------------------- 6502::begin
local Keywords_control = {
-- control keywords
'samepage', 'crosspage',
@ -168,6 +168,7 @@ local addressing_map = {
iny = opcode_indirect_y,
rel = opcode_relative,
}
-------------------------------------------------------- 6502::end
local Scope = {
new = function(self, parent)

View File

@ -1,8 +1,8 @@
local M = {}
M.major = 0
M.minor = 0
M.revision = 0
M.version = "0.0.0"
return M
local M = {}
M.major = 0
M.minor = 0
M.revision = 0
M.version = "0.0.0"
return M

View File

@ -1,8 +1,8 @@
local M = {}
M.major = ${L65_VERSION_MAJOR}
M.minor = ${L65_VERSION_MINOR}
M.revision = ${L65_VERSION_REVISION}
M.version = "${L65_VERSION}"
return M
local M = {}
M.major = ${L65_VERSION_MAJOR}
M.minor = ${L65_VERSION_MINOR}
M.revision = ${L65_VERSION_REVISION}
M.version = "${L65_VERSION}"
return M

222
l7801.c Normal file
View File

@ -0,0 +1,222 @@
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_FAILURE_STRINGS
#include "stb_image.h"
#define LUA_IMPLEMENTATION
#include "lua.h"
#include "scripts_7801.h"
extern int luaopen_lpeg(lua_State *L);
extern int luaopen_lfs(lua_State *L);
// l7801 lib
static int r_s32be(uint8_t **b) { uint8_t *p = *b; int v = ((int)(p[0]))<<24 | ((int)(p[1]))<<16 | ((int)p[2])<<8 | p[3]; *b += 4; return v; }
typedef struct { int len, nam; } chunk_s;
static chunk_s r_chunk(uint8_t **b) { int len = r_s32be(b), nam = r_s32be(b); chunk_s c = { len, nam }; return c; }
static int open_image(lua_State *L)
{
const char *filename = luaL_checkstring(L, 1);
FILE *file = fopen(filename, "rb");
if (!file)
{
lua_pushnil(L);
lua_pushfstring(L, "failed to open file %s", filename);
return 2;
}
fseek(file, 0, SEEK_END);
size_t sz = ftell(file);
fseek(file, 0, SEEK_SET);
uint8_t *png = malloc(sz);
fread(png, sz, 1, file);
fclose(file);
static uint8_t png_sig[8] = { 137,80,78,71,13,10,26,10 };
if (memcmp(png, png_sig, 8) != 0)
{
free(png);
lua_pushnil(L);
lua_pushfstring(L, "file %s is not a PNG", filename);
return 2;
}
uint8_t *b = png + 8;
int w, h;
uint8_t *d = 0; long d_sz = 0;
#define CHUNK_NAM(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d))
for (;;)
{
chunk_s chunk = r_chunk(&b);
switch (chunk.nam)
{
case CHUNK_NAM('I','H','D','R'): {
w = r_s32be(&b); h = r_s32be(&b);
if (b[0] != 8 || b[1] != 3)
{
free(png);
lua_pushnil(L);
lua_pushfstring(L, "PNG file %s must be 8b indexed", filename);
return 2;
}
b += 9;
} break;
case CHUNK_NAM('I','D','A','T'): {
d = realloc(d, d_sz + chunk.len);
memcpy(d + d_sz, b, chunk.len);
d_sz += chunk.len;
b += chunk.len+4;
} break;
case CHUNK_NAM('I','E','N','D'): {
free(png);
if (!d)
{
lua_pushnil(L);
lua_pushfstring(L, "invalid PNG file %s", filename);
return 2;
}
int px_sz;
uint8_t *px_raw = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((void*)d, d_sz, (w+1) * h, &px_sz, 1);
free(d);
uint8_t *px = calloc(w,h);
uint8_t *px0 = px, *px_raw0 = px_raw;
for (int y = 0; y < h; ++y)
{
int filter = *px_raw++;
#define prev (x==0 ? 0 : px[x-1])
#define up (px[x-w])
#define prevup (x==0 ? 0 : px[x-w-1])
switch (filter)
{
case 0: memcpy(px, px_raw, w); break;
case 1: for (int x = 0; x < w; ++x) { px[x] = px_raw[x] + prev; } break;
case 2: for (int x = 0; x < w; ++x) { px[x] = px_raw[x] + up; } break;
case 3: for (int x = 0; x < w; ++x) { px[x] = px_raw[x] + ((prev+up)>>1); } break;
case 4: for (int x = 0; x < w; ++x) { px[x] = px_raw[x] + stbi__paeth(prev,up,prevup); } break;
}
#undef prev
#undef up
#undef prevup
px += w;
px_raw += w;
}
STBI_FREE(px_raw0);
lua_createtable(L, w*h, 3);
lua_pushstring(L, filename);
lua_setfield(L, -2, "filename");
lua_pushinteger(L, w);
lua_setfield(L, -2, "width");
lua_pushinteger(L, h);
lua_setfield(L, -2, "height");
for (int i = 0; i < w*h; ++i)
{
lua_pushinteger(L, px0[i]);
lua_rawseti(L, -2, i+1);
}
free(px0);
return 1;
}
default:
b += chunk.len+4;
}
}
#undef CHUNK_NAM
if (d) free(d);
free(png);
lua_pushnil(L);
lua_pushfstring(L, "invalid PNG file %s", filename);
return 2;
}
static const struct luaL_Reg l7801lib[] = {
{"image", open_image},
{NULL, NULL},
};
static int luaopen_l7801(lua_State *L)
{
luaL_newlib(L, l7801lib);
return 1;
}
#define SRC_LUA(name) { #name, 0, script_ ## name ## _lua, sizeof(script_ ## name ## _lua) }
#define SRC_L7801(name) { #name, 1, script_ ## name ## _l7801, sizeof(script_ ## name ## _l7801) }
static struct script { const char *name; int t; const char *data; size_t sz; } embedded[] = {
SRC_LUA(dkjson),
SRC_LUA(l65cfg),
SRC_LUA(re),
SRC_L7801(scv),
};
#undef SRC_LUA
#undef SRC_L7801
static int getembedded(lua_State *L)
{
const char *name = lua_tostring(L, 1);
for (struct script *s = embedded, *e = s + sizeof(embedded) / sizeof(embedded[0]); s != e; ++s)
{
if (!strcmp(s->name, name))
{
lua_pushlstring(L, s->data, s->sz);
lua_pushboolean(L, s->t);
return 2;
}
}
return 0;
}
static int msghandler(lua_State *L)
{
const char *msg = lua_tostring(L, 1);
if (msg == NULL)
{
if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING)
return 1;
msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1);
return 1;
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_requiref(L, "lpeg", luaopen_lpeg, 1); lua_pop(L, 1);
luaL_requiref(L, "lfs", luaopen_lfs, 1); lua_pop(L, 1);
luaL_requiref(L, "l7801", luaopen_l7801, 1); lua_pop(L, 1);
// preload embedded lua scripts
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
luaL_loadbufferx(L, script_asm_lua, sizeof(script_asm_lua), "asm.lua", "b");
lua_setfield(L, -2, "asm");
luaL_loadbufferx(L, script_uPD7801_lua, sizeof(script_uPD7801_lua), "uPD7801.lua", "b");
lua_setfield(L, -2, "uPD7801");
lua_pop(L, 1);
// error handler
lua_pushcfunction(L, msghandler);
// l65.lua script
luaL_loadbufferx(L, script_l7801_lua, sizeof(script_l7801_lua), "l7801.lua", "b");
// arg[] table
lua_createtable(L, argc-1, 2);
lua_pushcfunction(L, getembedded); // pass embedded script lookup function as arg[-1]
lua_rawseti(L, -2, -1);
for (int i = 0; i < argc; i++) lua_pushstring(L, argv[i]), lua_rawseti(L, -2, i);
lua_pushvalue(L, -1);
lua_setglobal(L, "arg");
// ... arguments
{ int i; for (i = 1; i < argc; ++i) lua_rawgeti(L, -i, i); lua_remove(L, -i); }
// call l7801
int status = lua_pcall(L, argc-1, 0, -argc-1);
if (status != LUA_OK)
{
const char *msg = lua_tostring(L, -1);
fprintf(stderr, "%s\n", msg);
lua_pop(L, 1);
}
lua_pop(L, 1); // remove msghandler
lua_close(L);
return status;
}

2751
l7801.lua Normal file

File diff suppressed because it is too large Load Diff

2
lua.h
View File

@ -20280,7 +20280,7 @@ static void luai_fchecksize (luai_LoadState *S, size_t size, const char *tname)
#define luai_checksize(S,t) luai_fchecksize(S,sizeof(t),#t)
static void luai_checkHeader (luai_LoadState *S) {
luai_checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */
luai_checkliteral(S, (const char*)LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */
if (luai_LoadByte(S) != LUAC_VERSION)
luai_error(S, "version mismatch in");
if (luai_LoadByte(S) != LUAC_FORMAT)

5
main.c
View File

@ -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
@ -163,7 +164,7 @@ static int getembedded(lua_State *L)
}
}
return 0;
}
}
static int msghandler(lua_State *L)
{
@ -188,6 +189,8 @@ int main(int argc, char *argv[])
// preload embedded lua scripts
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
luaL_loadbufferx(L, script_asm_lua, sizeof(script_asm_lua), "asm.lua", "b");
lua_setfield(L, -2, "asm");
luaL_loadbufferx(L, script_6502_lua, sizeof(script_6502_lua), "6502.lua", "b");
lua_setfield(L, -2, "6502");
lua_pop(L, 1);

902
nes.l65 Normal file
View File

@ -0,0 +1,902 @@
-- 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 / 2C07 PPU
PPUCTRL = 0x2000,
PPUMASK = 0x2001,
PPUSTAT = 0x2002,
OAMADDR = 0x2003,
OAMDATA = 0x2004,
BGSCROL = 0x2005,
PPUADDR = 0x2006,
PPUDATA = 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,
OAMDMA = 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
NTSC = {
CLOCK = 1789773
}
PAL = {
CLOCK = 1662607
}
-- 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,lorg)
if a >= 0x10000 then return end
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 then prefix[1]='P' a=(symbolsorg[lorg] or 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,lorg)
local s = fmt("$%04x#%s#", a, l)
if a < 0x8000 then ins(ram, s)
elseif a < 0x10000 then
local a_org = symbolsorg[lorg] or a
local romstart = locations[2].start -- header location should always be defined first, skip it
if a_org >= romstart then
local bank = math.floor((a_org - romstart) / 0x4000)
if not rom[bank] then rom[bank] = {} end
ins(rom[bank], s)
end
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
mappers = {}
vblank_waitbegin = function()
local l=label() bit PPUSTAT bpl l
end
vblank_waitend = function()
local l=label() bit PPUSTAT bmi 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_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
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 not needed, no BCD support
ldx #0x40 stx SPECIO2 -- disable APU frame IRQ
ldx #0xff txs inx stx PPUCTRL stx PPUMASK stx DMCFREQ -- disable NMI, rendering, DMC IRQs
bit PPUSTAT -- clear remnant VBlank PPU status flag on reset
vblank_waitbegin()
lda #0 sta SNDCNT -- stop APU channels
-- clear CPU RAM
@_zeroram
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_waitbegin()
-- clear OAM
oamcache_clear() oamcache_flush()
-- clear PPU RAM
bit PPUSTAT ppu_addr(0x2000) tax ldy #0x10
@_zeroppu
sta PPUDATA dex bne _zeroppu dey bne _zeroppu
bit PPUSTAT -- reset latch
if mappers.init then mappers.init() end
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 0) / 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:sub(1,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 * 16384
t.chrsize = chrsize * 8192
t.wramsize = math.tointeger(2^wramsize*64)
t.bramsize = math.tointeger(2^bramsize*64)
t.chrbramsize = math.tointeger(2^chrbramsize*64)
t.chrramsize = math.tointeger(2^chrramsize*64)
mappers.header=t
end
local n0ne = function(x) return not x or x == 0 end
local val0 = function(x) return x and x or 0 end
-- 2a03 APU emulator
apuemu = {
--a=0,x=0,y=0,s=0,p=0,pc=0,xjam=false,
--reset = function(self)
-- self.a,self.x,self.y,self.s,self.p,self.pc,self.xjam = 0,0,0,0xff,
--end
}
-- plays a NSF tune and record its register writes for replay
-- stops at C00
-- FamiTracker must be in the path
-- Ported from Shiru's nsf2vgm
nsf = function(filename, song)
local f, nsf = assert(io.open(filename,'rb')) nsf=f:read('*all') f:close()
assert(nsf[0x7a] == 0, 'expansion chips are not supported')
nsf.songs = nsf[7]
nsf.loadadr=nsf[9]+(nsf[10]<<8) nsf.initadr=nsf[11]+(nsf[12]<<8) nsf.playadr=nsf[13]+(nsf[14]<<8)
local bs = false for i=1,8 do local p=nsf[0x70+i] nsf.page[i]=p if p~=0 then bs=true end end
if not bs then for i=1,8 do nsf.page[i]=i-1 end end
local ram,rom,page = {},{},{}
for i=1,#nsf-0x80 do rom[nsf.loadadr-0x7FFF] = nsf[0x81] end
local reg = { 0x13f, 0x108, 0x100, 0x100, 0x13f, 0x108, 0x100, 0x100, 0x180, 0x100, 0x100, 0x100, 0x13f, 0x100, 0x100, 0x100,
0x100, 0x100, 0x100, 0x100, 0x100, 0x11f, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100 }
end
--[[ For all mappers, where relevant:
prg rom banks:
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
prgrom0, which must contain the reset vector (main). The switchprgrom* functions expect
a bank index according to this reversed numbering (0 is last physical bank in ROM).
t.prgmap is an optional function taking a prgrom bank index and returning its rorg value.
Default is to map banks in sequence according to their size, eg.:
* for sizes 32kB: always 0x8000.
* for sizes 16kB+16kB: repeat sequence 0x8000, 0xa000.
* for sizes 16kB+8kB+8kB: repeat sequence 0x8000, 0xc000, 0xe000.
* for sizes 8kB+8kB+8kB+8kB: repeat sequence 0x8000, 0xa000, 0xc000, 0xe000.
If last bank is fixed, it is not part of the sequence and its mapping value is skipped:
* for sizes 8kB+8kB+ last fixed 16kB: repeat sequence 0x8000, 0xa000, set last bank to 0xc000.
chr rom banks:
chrroms are numbered in physical ROM address order, unlike prgroms.
Banks are created with the smallest switchable size, maximum being 4kB by default.
t.chrmap is an optional function taking a chrrom bank index and returning its offset in
ROM from the beginning of the first chrrom, its size (defaults to everything remaining),
and its relocation origin (defaults to 0).
So if a mapper allows switching chrroms in any of 1-1-1-1-1-1-1-1kB / 2-2-2-2kB, etc.,
the default is to create only 1kB chrroms. Conversely, with a mapper switching only in 8kB
slices, 4kB chrrom are created, not 8kB (but you can specify any size using t.chrmap).
]]
--[[
https://wiki.nesdev.com/w/index.php/NROM
]]
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, "prgsize 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, "combined chrrom size 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(t)
prgrom0 = location{prgstart, 0xffff, name='prgrom'}
prgrom = prgrom0
section{"vectors", org=0xfffa} dc.w nmi, main, irq
if (t.chrsize > 0) then
local ci = 0
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + 0x10000
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
end
end
mappers[0] = mappers.NROM
--[[
https://wiki.nesdev.com/w/index.php/UxROM
Has bus conflicts.
]]
mappers.UxROM = function(t)
if not t then t = {} end
t.mapperid = 2
if not t.prgsize then t.prgsize = 32768 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x40000, "prgsize must be at least 32kB and at most 256kB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize == 0x2000, "chrsize must be 8kB")
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
hdrrom = location{0x7ff0, 0x7fff, name='header'}
header(t)
local bc = t.prgsize//0x4000
for bi=0,bc-2 do
local o,ix = 0x8000 + bi*0x4000, bc-1-bi
_ENV['prgrom'..ix] = location{o, o+0x3fff, rorg=0x8000, name='prgrom'..ix}
end
local prglast = 0x8000 + (bc-1)*0x4000
prgrom0 = location{prglast, prglast+0x3fff, rorg=0xc000, name='prgrom0'}
prgrom = prgrom0
section{"vectors", org=prglast+0x3ffa} dc.w nmi, main, irq
@@bankbytes -- for handling bus conflicts
samepage for i=bc-1,0,-1 do byte(i) end end
local ci, chrstart = 0, 0x8000 + bc*0x4000
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + chrstart
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
function switchprgrom(bankix)
if bankix then assert(bankix < bc, "mappers.switchprgrom: bank out of range: " .. bankix .. ", expected 0-" .. (bc-1)) lda #bankix end
-- lda to reverse map [n..0] to [0..n]
tax lda bankbytes,x sta bankbytes,x
end
mappers.init = function()
switchprgrom(1)
end
end
mappers[2] = mappers.UxROM
--[[
https://www.nesdev.org/wiki/UNROM_512
]]
mappers.UNROM512 = function(t)
if not t then t = {} end
t.mapperid = 30
if not t.prgsize then t.prgsize = 32 * 16384 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
assert(n0ne(t.chrsize), "chrsize must be 0")
if n0ne(t.chrramsize) and n0ne(t.chrbramsize) then t.chrramsize = 4 * 8192 end
local csize = val0(t.chrramsize) + val0(t.chrbramsize)
assert(csize == 0x2000 or csize == 0x4000 or csize == 0x8000, "combined chrram size must be 8, 16 or 32kB")
hdrrom = location{0x7ff0, 0x7fff, name='header'}
header(t)
local bc = t.prgsize//0x4000
for bi=0,bc-2 do
local o,ix = 0x8000 + bi*0x4000, bc-1-bi
_ENV['prgrom'..ix] = location{o, o+0x3fff, rorg=0x8000, name='prgrom'..ix}
end
local prglast = 0x8000 + (bc-1)*0x4000
prgrom0 = location{prglast, prglast+0x3fff, rorg=0xc000, name='prgrom0'}
prgrom = prgrom0
section{"vectors", org=prglast+0x3ffa} dc.w nmi, main, irq
section{ "bankbytes", align=256 } -- for handling bus conflicts
for m=0,1 do
for c=0,3 do
for p=0x1f,0,-1 do byte(m<<7 | c<<5 | p) end
end
end
function clearchrram(page_count, offset)
ppu_addr(CHAR0 + (offset or 0)) tay ldx#(page_count or 32) @_clear sta PPUDATA iny bne _clear dex bne _clear
end
function loadchrram(var, page_count, offset)
ppu_addr(CHAR0 + (offset or 0)) ldx#(page_count or 32) ldy#0 @_load lda (var),y sta PPUDATA iny bne _load inc var+1 dex bne _load
end
function switch(prgbankix, chrbankix, mirror)
if prgbankix then assert(prgbankix < bc, "mappers.switch: PRG bank out of range: " .. prgbankix .. ", expected 0-" .. (bc-1)) end
if chrbankix then assert(chrbankix < 4, "mappers.switch: CHR bank out of range: " .. chrbankix .. ", expected 0-3") end
if mirror then assert(mirror == 0 or mirror == 1, "mappers.switch: mirror out of range: " .. mirror .. ", expected 0-1") end
if prgbankix or chrbankix or mirror then
local r = (mirror or 0)<<7 | (chrbankix or 0)<<5 | (prgbankix or 1)
lda #r
end
-- lda to reverse prg map [n..0] to [0..n]
tax lda bankbytes,x sta bankbytes,x
end
mappers.init = function()
switch(1)
end
end
mappers[30] = mappers.UxROM
--[[
https://wiki.nesdev.com/w/index.php/CNROM
Has bus conflicts.
]]
mappers.CNROM = function(t)
if not t then t = {} end
t.mapperid = 3
if not t.prgsize then t.prgsize = 16384 end
assert(t.prgsize == 16384 or t.prgsize == 32768, "prgsize must be 16 or 32kB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x8000, "chrsize must be at least 8kB and at most 32kB")
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(t)
prgrom = location{prgstart, 0xffff, name='prgrom'}
section{"vectors", org=0xfffa} dc.w nmi, main, irq
@@bankbytes samepage -- for handling bus conflicts
dc.b 0x00, 0x01, 0x02, 0x03
dc.b 0x10, 0x11, 0x12, 0x13
dc.b 0x20, 0x21, 0x22, 0x23
dc.b 0x30, 0x31, 0x32, 0x33
end
local ci, cc = 0, t.chrsize//0x2000
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + 0x10000
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
securitydiodes = 0 -- set to actual value, eg 0x20
function switchchrrom(bankix)
assert(securitydiodes < 0x40)
if bankix then
assert(bankix < cc)
ldx #bankix|securitydiodes>>2 lda #bankix|securitydiodes
else
ora #securitydiodes>>2 tax and #3 ora #securitydiodes
end
sta bankbytes,x
end
mappers.init = function()
switchchrrom(0)
end
end
mappers[3] = mappers.CNROM
--[[
https://wiki.nesdev.com/w/index.php/GxROM
Has bus conflicts.
]]
mappers.GxROM = function(t)
if not t then t = {} end
t.mapperid = 66
if not t.prgsize then t.prgsize = 32768 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x20000, "prgsize must be at least 32kB and at most 128kB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x8000, "chrsize must be at least 8kB and at most 32kB")
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
hdrrom = location{0x7ff0, 0x7fff, name='header'}
header(t)
local bc = t.prgsize//0x8000
local cc = t.chrsize//0x2000
local ci, chrstart = 0, 0x8000 + bc*0x8000
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + chrstart
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
-- RAM address of bank register copy, to switch using A instead of immediate
-- if bankregister_shadow is negative, only immediate bankswitching is available
bankregister_shadow = -1
-- otherwise, just set the value directly: xxPPxxCC in A, xxxxPPCC in X with sta bankbytes0,x
function switchroms(prgbankix, chrbankix)
if prgbankix then
assert(prgbankix < bc) prgbankix = bc-1-prgbankix
assert(chrbankix, "GxROM must specify both PRG and CHR roms to switch simultaneously")
assert(chrbankix < cc)
local br = prgbankix<<4 | chrbankix
ldx #br&3|br>>2 lda #br sta bankbytes0,x
else -- A contains 00PP00CC
if bankregister_shadow >= 0 then
sta bankregister_shadow -- bankregister_shadow = 00PP00CC
lsr lsr -- A = 0000PP00, c
-- compute bc-1-prgbankix (reverse indexing)
eor #0xff adc #(bc-1<<2)+1 -- negate and +bc-1 combined, NN = bc-1-PP: A = 0000NN00
ora bankregister_shadow ;and #0xf tax -- X = 0000NNCC
else -- use stack instead
tsx sta 0x100,x lsr lsr -- A = 0000PP00, s[0] = 00PP00CC, c
-- compute bc-1-prgbankix (reverse indexing)
eor #0xff adc #(bc-1<<2)+1 -- negate and +bc-1 combined, NN = bc-1-PP: A = 0000NN00
ora 0x100,x ;and #0xf tax -- X = 0000NNCC
end
lda bankbytes0,x sta bankbytes0,x -- A = 00NN00CC, from table
end
end
for bi=0,bc-1 do
local o,ix = 0x8000 + bi*0x8000, bc-1-bi
_ENV['prgrom'..ix] = location{o, o+0x7fff, rorg=0x8000, name='prgrom'..ix}
local start=section{"entry"..ix, org=o+0x8000-6-16-10} switchroms(0,0) if ix==0 then jmp main end
section{"vectors"..ix, org=o+0x8000-6} dc.w "nmi"..ix, start, "irq"..ix
section{"bankbytes"..ix, org=o+0x8000-6-16} samepage -- for handling bus conflicts
dc.b 0x00, 0x01, 0x02, 0x03
dc.b 0x10, 0x11, 0x12, 0x13
dc.b 0x20, 0x21, 0x22, 0x23
dc.b 0x30, 0x31, 0x32, 0x33
end
end
prgrom = prgrom0
function switchprgrom(bankix)
if bankix then
assert(bankix < bc)
lda #bc-1-bankix
else
clc eor #0xff adc #bc -- bc-1 - A
end
assert(bankregister_shadow >= 0, "no RAM slot assigned to bankregister_shadow")
tay lda bankregister_shadow ;and #3 sta bankregister_shadow
tya asl asl ora bankregister_shadow tax
lda bankbytes0,x sta bankregister_shadow sta bankbytes0,x
end
function switchchrrom(bankix)
if bankix then
assert(bankix < cc)
lda #bankix
end
assert(bankregister_shadow >= 0, "no RAM slot assigned to bankregister_shadow")
tay lda bankregister_shadow lsr lsr sta bankregister_shadow
tya ora bankregister_shadow tax
lda bankbytes0,x sta bankregister_shadow sta bankbytes0,x
end
end
mappers[66] = mappers.GxROM
--[[
https://wiki.nesdev.com/w/index.php/MMC1
t.prgmap is an optional function taking a prgrom bank index and returning its rorg value.
Default is to create 16kB banks if prgswitchmode is not all, 32kB otherwise, and rorg them
accordingly.
Same goes for chrrom sizes.
t.prgswitchmode:
'first' makes 0x8000-0xbfff switchable (default)
'last' makes 0xc000-0xffff switchable
'all' makes 0x8000-0xffff switchable
t.chrswitchmode:
'all' switches whole 8kB at a time
'half' switches 2 separate 4kB banks (default)
]]
mappers.MMC1 = function(t)
if not t then t = {} end
t.mapperid = 1
if not t.prgswitchmode then t.prgswitchmode = 'first' end
if not t.chrswitchmode then t.chrswitchmode = 'half' end
if not t.wramsize then t.wramsize = 0 end
if not t.bramsize and t.wramsize == 0 then t.bramsize = 8192 end
local prgram = t.bramsize + t.wramsize
assert(prgram >= 0x2000 and prgram <= 0x8000, "bramsize or wramsize must be at least 8kB and at most 32kB")
if not t.prgsize then t.prgsize = 32768 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x20000, "chrsize must be at least 8kB and at most 128kB")
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
header(t)
local bsz = t.prgswitchmode=='all' and 0x8000 or 0x4000
local prgmap = t.prgmap or function(bi, bc) return t.prgswitchmode=='last' and 0xc000 or 0x8000 end
local bc = t.prgsize//bsz
for bi=0,bc-1 do
local o,ix = 0x8000 + bi*bsz, bc-1-bi
_ENV['prgrom'..ix] = location{o, o+bsz-1, rorg=prgmap(ix,bc), name='prgrom'..ix}
end
section{"vectors", org=0x8000+bc*bsz-6} dc.w nmi, main, irq
prgrom = prgrom0
local ci, chrstart = 0, 0x8000 + bc*bsz
local csz = t.chrswitchmode=='all' and 0x2000 or 0x1000
local cc = t.chrsize//csz
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + chrstart
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
local prgswitchmodemap = { all=0, first=3<<2, last=2<<2 }
local chrswitchmodemap = { all=0, half=1<<4 }
mmc1ctrl = (mappers.header.mirror==1 and 2 or 3) | prgswitchmodemap[t.prgswitchmode] | chrswitchmodemap[t.chrswitchmode]
function mmc1write(reg)
sta reg lsr sta reg lsr sta reg lsr sta reg lsr sta reg
end
-- Can be turned into a function, A must contain the bank index.
-- eg:
-- @@switchprgrom_func switchprgrom_f()
-- switchprgrom_f = function() jsr switchprgrom_func rts end
function switchprgrom_f() mmc1write(0xe000) end
function switchprgrom(bankix)
assert(bankix < t.prgsize//0x4000)
lda #bankix
switchprgrom_f()
end
function switchchrrom_f(slot) assert(slot<2) mmc1write(slot==0 and 0xa000 or 0xc000) end
function switchchrrom(bankix, slot)
assert(bankix < t.chrsize//0x1000)
lda #bankix
switchchrrom_f(slot or 0)
end
function setmirror(mirror)
mirror = assert(({ h=3, v=2, hi=1, lo=0 })[mirror:lower()])
mmc1ctrl = (mmc1ctrl & ~3) | mirror
lda #mmc1ctrl mmc1write(0x8000)
end
function setprgswitchmode(mode)
mode = assert(prgswitchmodemap[mode])
mmc1ctrl = (mmc1ctrl & ~0xc) | mode
lda #mmc1ctrl mmc1write(0x8000)
end
function setchrswitchmode(mode)
mode = assert(chrswitchmodemap[mode])
mmc1ctrl = (mmc1ctrl & ~0x10) | mode
lda #mmc1ctrl mmc1write(0x8000)
end
mappers.init = function()
lda #0x80 sta 0x8000
lda #mmc1ctrl mmc1write(0x8000)
if t.prgswitchmode ~= 'all' then switchprgrom(1) end
switchchrrom(0) if t.chrswitchmode ~= 'all' then switchchrrom(1,1) end
end
end
mappers[1] = mappers.MMC1
--[[
https://wiki.nesdev.com/w/index.php/MMC3
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
prgrom0, which must contain the reset vector (main).
Last 2 prg banks are merged into 1 16kB bank, to allow linker optimization - hence, 0 must
always be set on bit 6 of bank select, even; also, there is no prgrom1 as a consequence.
chrroms are all 1kB, so they can work with chr A12 inversion enabled or not.
With default submapper id of 0, this defaults to revision MM3C, which generates a scanline
interrupt at each scanline when counter is loaded with 0.
]]
mappers.MMC3 = function(t)
if not t then t = {} end
t.mapperid = 4
if not t.bramsize then t.bramsize = 8192 end
assert(t.bramsize == 8192, "bramsize must be 8kB")
if not t.prgsize then t.prgsize = 32768 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x40000, "chrsize must be at least 8kB and at most 256kB")
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
header(t)
local prgmap = t.prgmap or function(bi, bc) return 0x8000+(bi&1)*0x2000 end
local bc = t.prgsize//0x2000
for bi=0,bc-3 do
local o,ix = 0x8000 + bi*0x2000, bc-bi-1
_ENV['prgrom'..ix] = location{o, o+0x1fff, rorg=prgmap(ix,bc), name='prgrom'..ix}
end
do
local o = 0x8000 + (bc-2)*0x2000
prgrom0 = location{o, o+0x3fff, rorg=0xc000, name='prgrom0'}
section{"vectors", org=o+0x3ffa} dc.w nmi, main, irq
end
prgrom = prgrom0
local ci, chrstart = 0, 0x8000 + bc*0x2000
local chrmap = t.chrmap or function(ci) return ci*0x400, 0x400, (ci&7)*0x400 end
local cc = t.chrsize//0x400
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + chrstart
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
local a12inv = false
function seta12inv(enabled) a12inv = enabled end
function switchprgrom(bankix, slot)
assert(slot<2)
slot = slot + 6
if a12inv then slot = slot | 0x80 end
lda #slot sta 0x8000
assert(bankix < bc)
bankix = bc-1-bankix -- reverse index order, since we set 0 as last
lda #bankix sta 0x8001
end
-- slot [0, 7], each slot is 1kB counting in order, regardless of a12inv state
function switchchrrom(bankix, slot)
assert(slot<8)
if a12inv then
assert(slot ~= 5 and slot ~= 7)
if slot == 6 then slot = 1
elseif slot == 4 then slot = 0
else slot = slot + 2 end
slot = slot | 0x80
else
assert(slot ~= 1 and slot ~= 3)
if slot == 2 then slot = 1
elseif slot > 3 then slot = slot - 2 end
end
lda #slot sta 0x8000
assert(bankix < cc)
lda #bankix sta 0x8001
end
function setmirror(mirror)
mirror = assert(({ h=1, v=0 })[mirror:lower()])
lda #mirror sta 0xa000
end
function protectsram() lda 0x40 sta 0xa001 end
function scanlineirq(count) ldx #1 stx 0xe000 lda #count-1 sta 0xc000 sta 0xc001 stx 0xe001 end
mappers.init = function()
switchprgrom(2, 0) switchprgrom(3, 1)
switchchrrom(0, 0) switchchrrom(2, 2)
switchchrrom(4, 4) switchchrrom(5, 5) switchchrrom(6, 6) switchchrrom(7, 7)
local mirror = mappers.header.mirror
if mirror==0 or mirror==1 then lda #mirror~1 sta 0xa000 end
lda #0x80 sta 0xa001
end
end
mappers[4] = mappers.MMC3
--[[
https://wiki.nesdev.com/w/index.php/MMC5
https://forums.nesdev.com/viewtopic.php?f=9&t=16789
main MUST be in 0xe000-0xfff9 of last prgrom.
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
prgrom0, which must contain the reset vector (main).
chrroms are all 1kB.
]]
mappers.MMC5 = function(t)
if not t then t = {} end
t.mapperid = 5
assert(val0(t.bramsize) + val0(t.wramsize) <= 0x10000, "bramsize + wramsize must be at most 64kB")
if not t.prgsize then t.prgsize = 8192 end
assert(t.prgsize <= 0x100000, "prgsize must be at most 1MB")
if not t.chrsize then t.chrsize = 8192 end
assert(t.chrsize <= 0x100000, "chrsize must be at most 1MB")
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
header(t)
local prgmap = t.prgmap or function(bi, bc) return 0x8000+(bi&3)*0x2000 end
local bc = t.prgsize//0x2000
for bi=0,bc-2 do
local o,ix = 0x8000 + bi*0x2000, bc-bi-1
_ENV['prgrom'..ix] = location{o, o+0x1fff, rorg=prgmap(ix,bc), name='prgrom'..ix}
end
do
local o = 0x8000 + (bc-1)*0x2000
prgrom0 = location{o, o+0x1fff, rorg=0xe000, name='prgrom0'}
section{"vectors", org=o+0x1ffa} dc.w nmi, main, irq
section{"main", org=o} -- enforce entry point in last bank
end
prgrom = prgrom0
local ci, chrstart = 0, 0x8000 + bc*0x2000
local chrmap = t.chrmap or function(ci) return ci*0x400, 0x400, (ci&7)*0x400 end
local cc = t.chrsize//0x400
repeat
local off, sz, rorg = chrmap(ci)
sz = sz or t.chrsize - off
local o = off + chrstart
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
ci = ci+1
until off + sz >= t.chrsize
chrrom = chrrom0
function prgbankmode(mode) if mode then lda #mode end sta 0x5100 end
function chrbankmode(mode) if mode then lda #mode end sta 0x5101 end
function wrammode(mode) if mode then lda #mode end sta 0x5104 end
function protectsram() lda #0 sta 0x5102 sta 0x5103 end
function screenmap(map) if map then lda #map end sta 0x5105 end
function filltile(tile) if tile then lda #tile end sta 0x5106 end
function fillcolor(col) if col then lda #col end sta 0x5107 end
function switchprgram(chip, bank)
assert(chip == 0 or chip == 1)
assert(bank < 4)
lda #chip<<2|bank sta 0x5113
end
function switchprgrom0(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5114 end
function switchprgrom1(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5115 end
function switchprgrom2(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5116 end
function switchprgrom3(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5117 end
function switchchrrom(bank, slot) -- 1kB mode
assert(slot < 12)
assert(bank < cc)
lda #bank>>7 sta 0x5130
lda #bank sta slot+0x5120
end
vsplitmode = 0
function vsplitstart(starttile)
assert(starttile < 32)
vsplitmode = (vsplitmode & ~31) | starttile
lda #vsplitmode sta 0x5200
end
function vsplitside(side)
assert(side == 'left' or side == 'right')
vsplitmode = (vsplitmode & ~0x40) | (side == 'right' and 1 or 0)
lda #vsplitmode sta 0x5200
end
function vsplitenable(enabled)
vsplitmode = (vsplitmode & ~0x80) | (enabled and 1 or 0)
lda #vsplitmode sta 0x5200
end
function vsplitscroll(vscroll) if vscroll then lda #vscroll end sta 0x5201 end
function vsplitbank(chrbank) if chrbank then lda #chrbank end sta 0x5202 end
function scanlineirq(atscanline) lda #0 sta 0x5204 lda #atscanline sta 0x5203 lda #0x80 sta 0x5204 end
function irqenable(enabled) lda #enabled and 0x80 or 0 sta 0x5204 end
-- 0x40 set: PPU is rendering visible scanlines
-- 0x80 set: a scanlineirq is pending (internal counter reach atscanline value set with scanlineirq())
function irqstatus() lda 0x5204 end
-- multiplies a*x = x:a (x hi, a lo)
function mul() sta 0x5205 stx 0x5206 lda 0x5205 ldx 0x5206 end
mappers.init = function()
ldx #2 stx 0x5102 dex stx 0x5103
lda #vsplitmode sta 0x5200
prgbankmode(2)
switchprgrom1(3) -- map prg rom 3, 2, x, 0
switchprgrom2(1) -- map prg rom 3, 2, 1, 0
chrbankmode(0) switchchrrom(0, 7) chrbankmode(3) -- map chr rom 0 to the 8kB, and set mode to 1kB slices
end
end
mappers[5] = mappers.MMC5

5
pce.l65 Normal file
View File

@ -0,0 +1,5 @@
-- set cpu to HuC6280
cpu = require "6502"
setmetatable(_ENV, cpu)
cpu.zeropage = function(x) if x >= 0x2000 and x <= 0x20ff then return x&0xff end end

95
samples/nes_bank1.l65 Normal file
View File

@ -0,0 +1,95 @@
require'nes'
-- Press A to switch between the two PRG banks.
-- fixed 16kB for prgrom0, 3 other banks of 16kB each mappable at 0x8000
mappers.UxROM{ prgsize=(16+16*3)*1024 }
location(chrrom0)
local font = section("font")
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -- <SPC>
dc.b 0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00,0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00 -- A
dc.b 0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00,0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00 -- B
dc.b 0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00,0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00 -- C
dc.b 0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00,0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00 -- D
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00,0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00 -- E
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00,0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00 -- F
dc.b 0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00,0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00 -- G
dc.b 0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00,0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00 -- H
dc.b 0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00 -- I
dc.b 0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00,0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00 -- J
dc.b 0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00,0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00 -- K
dc.b 0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00,0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00 -- L
dc.b 0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00,0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00 -- M
dc.b 0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00 -- N
dc.b 0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00,0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00 -- O
dc.b 0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00,0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00 -- P
dc.b 0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00,0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00 -- Q
dc.b 0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00,0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00 -- R
dc.b 0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00,0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00 -- S
dc.b 0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00,0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00 -- T
dc.b 0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00,0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00 -- U
dc.b 0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00,0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00 -- V
dc.b 0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00 -- W
dc.b 0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00,0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00 -- X
dc.b 0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00,0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00 -- Y
dc.b 0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00,0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00 -- Z
charset(" abcdefghijklmnopqrstuvwxyz", \x(x + (font.org - font.location.start) // 16))
-- define some text of same length in both swappable PRG banks 1 and 2
location(prgrom1)
local text1 = "bank one"
local sec_text1 = section("text1") byte(text1)
location(prgrom2)
local text2 = "bank two"
local sec_text2 = section("text2") byte(text2)
-- force sec_text2 to have same offset in prgrom2 than sec_text1 in prgrom1,
-- so we can use either address to reference the custom bank data
relate(sec_text1, sec_text2)
-- fixed bank, last 16kB
location(prgrom)
@@nmi @irq rti
joya = 1
curbank = 0
@@switchbank
lda #3
eor curbank
sta curbank
switchprgrom()
-- load screen text in PPU RAM 0x21CA
ppu_addr(0x21ca)
ldy #0 @_loadtxt lda sec_text1,y sta PPUDATA iny cpy ##text1 bne _loadtxt
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
rts
@@main
init()
vblank_waitbegin()
-- load BG palette in PPU RAM
ppu_addr(BGPAL)
for _,v in ipairs{ 0x1f, 0x00, 0x10, 0x20 } do lda #v sta PPUDATA end
-- switch between the two banks
lda #2 sta curbank
jsr switchbank
-- show BG
lda #0x0a sta PPUMASK
-- idle
@_loop
vblank_waitbegin()
read_joy_a(1) bcc _noswitch
lda joya bne _switch
jsr switchbank lda #1 sta joya bne _switch
@_noswitch
lda #0 sta joya
@_switch
jmp _loop
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

118
samples/nes_bank2.l65 Normal file
View File

@ -0,0 +1,118 @@
require'nes'
-- 2 32kB PRG roms
mappers.GxROM{ prgsize=(32*2)*1024 }
location(chrrom0)
local font = section("font")
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -- <SPC>
dc.b 0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00,0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00 -- A
dc.b 0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00,0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00 -- B
dc.b 0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00,0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00 -- C
dc.b 0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00,0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00 -- D
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00,0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00 -- E
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00,0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00 -- F
dc.b 0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00,0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00 -- G
dc.b 0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00,0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00 -- H
dc.b 0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00 -- I
dc.b 0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00,0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00 -- J
dc.b 0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00,0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00 -- K
dc.b 0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00,0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00 -- L
dc.b 0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00,0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00 -- M
dc.b 0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00 -- N
dc.b 0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00,0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00 -- O
dc.b 0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00,0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00 -- P
dc.b 0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00,0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00 -- Q
dc.b 0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00,0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00 -- R
dc.b 0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00,0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00 -- S
dc.b 0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00,0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00 -- T
dc.b 0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00,0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00 -- U
dc.b 0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00,0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00 -- V
dc.b 0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00 -- W
dc.b 0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00,0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00 -- X
dc.b 0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00,0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00 -- Y
dc.b 0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00,0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00 -- Z
charset(" abcdefghijklmnopqrstuvwxyz", \x(x+(font.org - font.location.start) // 16))
local copytext = function(sec_text, text)
-- load screen text in PPU RAM 0x21CA
ppu_addr(0x21ca)
ldy #0 @_loadtxt lda sec_text,y sta PPUDATA iny cpy ##text bne _loadtxt
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
end
-- RAM
joya = 0
--[[
BANK 0 - startup bank
]]
location(prgrom0)
local text0 = "bank one"
@@text0s byte(text0)
@@nmi0 @irq0 rti
local toprg1s = section("toprg1")
switchroms(1,0)
local fromprg1s = section("fromprg1")
jmp mainloop
@@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 PPUDATA end
-- turn screen on
lda #0x0a sta PPUMASK -- show BG
-- idle
@mainloop
vblank_waitbegin()
copytext(text0s, text0)
read_joy_a(1) bcc _noswitch
lda joya bne _switch
lda #1 sta joya jmp toprg1
@_noswitch
lda #0 sta joya
@_switch
jmp mainloop
--[[
BANK 1
]]
location(prgrom1)
local text1 = "bank two"
@@text1s byte(text1)
@@nmi1 @irq1 rti
local toprg0s = section("toprg0")
relate(toprg0s, fromprg1s, 7) -- switchprgrom(immediate) takes 7 bytes
switchroms(0,0)
local fromprg0s = section("fromprg0")
relate(toprg1s, fromprg0s, 7)
jmp mainloop1
@mainloop1
vblank_waitbegin()
copytext(text1s, text1)
read_joy_a(1) bcc _noswitch
lda joya bne _switch
lda #1 sta joya jmp toprg0
@_noswitch
lda #0 sta joya
@_switch
jmp mainloop1
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

BIN
samples/nes_beer.chr Normal file

Binary file not shown.

1
samples/nes_beer.pal Normal file
View File

@ -0,0 +1 @@
80!1& )

View File

@ -0,0 +1,51 @@
require'nes'
-- asm code : 2019 - mara/flush
-- gfx : mara/flush
mappers.CNROM{chrmap = function(ci) return ci*0x2000, 0x2000, (ci&1)*0x2000 end}
location(chrrom0)
-- set beer tileset for sprite
@@beer_tileset do local f, s = assert(io.open('nes_beer.chr','rb')) s=f:read('*all') f:close() byte{s:byte(1,-1)} end
location(prgrom)
@@nmi rti
@@irq rti
-- the beer sprite's palette
local beer_pal = { 0x0F,0x10,0x38,0x30,0x0F,0x01,0x21,0x31,0x0F,0x06,0x16,0x26,0x0F,0x09,0x19,0x29 }
-- describe oam bank for the sprite
-- number, x position, x position, tile number, index in palette
local sprite_beer = { 0, x=0x80, y=0x80, tile=1, palette=0 }
@@main
init()
vblank_waitbegin()
-- activate sprite
lda #0b00011000
sta PPUMASK
-- load SPRITE palette in PPU RAM
ppu_addr(OBJPAL)
-- set sprite palette
for _,v in ipairs(beer_pal) do lda #v sta PPUDATA end
ppu_addr(0x2000)
-- set the sprite on sprite bank 0
oam_set(sprite_beer)
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
-- idle
@_loop jmp _loop
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

75
samples/nes_chrram.l65 Normal file
View File

@ -0,0 +1,75 @@
require'nes'
mappers.UNROM512()
location(prgrom1)
local font_size = 27 * 16
@@font
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -- <SPC>
dc.b 0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00,0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00 -- A
dc.b 0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00,0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00 -- B
dc.b 0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00,0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00 -- C
dc.b 0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00,0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00 -- D
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00,0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00 -- E
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00,0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00 -- F
dc.b 0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00,0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00 -- G
dc.b 0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00,0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00 -- H
dc.b 0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00 -- I
dc.b 0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00,0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00 -- J
dc.b 0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00,0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00 -- K
dc.b 0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00,0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00 -- L
dc.b 0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00,0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00 -- M
dc.b 0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00 -- N
dc.b 0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00,0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00 -- O
dc.b 0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00,0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00 -- P
dc.b 0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00,0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00 -- Q
dc.b 0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00,0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00 -- R
dc.b 0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00,0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00 -- S
dc.b 0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00,0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00 -- T
dc.b 0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00,0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00 -- U
dc.b 0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00,0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00 -- V
dc.b 0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00 -- W
dc.b 0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00,0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00 -- X
dc.b 0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00,0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00 -- Y
dc.b 0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00,0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00 -- Z
-- RAM
chr = 0
location(prgrom)
@@nmi rti
@@irq rti
charset(" abcdefghijklmnopqrstuvwxyz")
local hello = "hello world"
@@text byte(hello)
@@main
init()
clearchrram()
-- load font in CHR RAM
ppu_addr(CHAR0)
lda #font&0xff sta chr lda #font>>8 sta chr+1
ldy #0 @_loadfnt1 lda (chr),y sta PPUDATA iny bne _loadfnt1
inc chr+1
@_loadfnt2 lda (chr),y sta PPUDATA iny cpy #font_size-256 bne _loadfnt2
-- load BG palette in PPU RAM
ppu_addr(BGPAL)
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 PPUDATA iny cpy ##hello bne _loadtxt
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
-- show BG
vblank_waitbegin()
lda #0x0a sta PPUMASK
-- idle
@_loop jmp _loop
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

BIN
samples/nes_ghosts.chr Normal file

Binary file not shown.

BIN
samples/nes_ghosts.msb Normal file

Binary file not shown.

BIN
samples/nes_ghosts.nam Normal file

Binary file not shown.

1
samples/nes_ghosts.pal Normal file
View File

@ -0,0 +1 @@
0%!10

View File

@ -0,0 +1,93 @@
require'nes'
-- samples : extacted of the demo : Let's Go To The Very Important Party 2018
-- original code : 2018 - mara/flush
-- asm code : 2019 - mara/flush
-- gfx : Exocet/Up Rough
mappers.CNROM{chrmap = function(ci) return ci*0x2000, 0x2000, (ci&1)*0x2000 end}
location(chrrom0)
@@ghosts_tiles do local f, s = assert(io.open('nes_ghosts.chr','rb')) s=f:read('*all') f:close() byte{s:byte(1,-1)} end
location(prgrom)
@@nmi rti
@@irq rti
-- ghosts' palette
local ghosts_pal = { 0x0F, 0x30, 0x25, 0x15, 0x0F, 0x21, 0x31, 0x30, 0x0F, 0x1B, 0x19, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F }
-- metasprite is a set of sprites to draw a big sprite
-- here you have two big sprites, the son and mother ghosts
-- it's a set of oam bank
-- index, x position, y position, tile number, palette number
local ghosts_spr = {
{ 2, x= 0+0x80, y= 0+0x88, tile=0x01, palette=1 }, -- begin sprite son
{ 3, x= 8+0x80, y= 0+0x88, tile=0x02, palette=1 },
{ 4, x=16+0x80, y= 0+0x88, tile=0x03, palette=1 },
{ 5, x=24+0x80, y= 0+0x88, tile=0x04, palette=1 },
{ 6, x= 0+0x80, y= 8+0x88, tile=0x07, palette=1 },
{ 7, x= 8+0x80, y= 8+0x88, tile=0x08, palette=1 },
{ 8, x=16+0x80, y= 8+0x88, tile=0x09, palette=1 },
{ 9, x=24+0x80, y= 8+0x88, tile=0x0a, palette=1 },
{ 10, x= 0+0x80, y=16+0x88, tile=0x0f, palette=1 },
{ 11, x= 8+0x80, y=16+0x88, tile=0x10, palette=1 },
{ 12, x=16+0x80, y=16+0x88, tile=0x11, palette=1 },
{ 13, x=24+0x80, y=16+0x88, tile=0x12, palette=1 },
{ 14, x= 0+0x80, y=24+0x88, tile=0x17, palette=1 },
{ 15, x= 8+0x80, y=24+0x88, tile=0x18, palette=1 },
{ 16, x=16+0x80, y=24+0x88, tile=0x19, palette=1 },
{ 17, x=24+0x80, y=24+0x88, tile=0x1a, palette=1 },
---------------------------------------------------------------
{ 18, x= 8+0x40, y= 0+0x80, tile=0x05, palette=0 }, --begin sprite mother
{ 19, x=16+0x40, y= 0+0x80, tile=0x06, palette=0 },
{ 20, x= 0+0x40, y= 8+0x80, tile=0x0b, palette=0 },
{ 21, x= 8+0x40, y= 8+0x80, tile=0x0c, palette=0 },
{ 22, x=16+0x40, y= 8+0x80, tile=0x0d, palette=0 },
{ 23, x=24+0x40, y= 8+0x80, tile=0x0e, palette=0 },
{ 24, x= 0+0x40, y= 16+0x80, tile=0x13, palette=0 },
{ 25, x= 8+0x40, y= 16+0x80, tile=0x14, palette=0 },
{ 26, x=16+0x40, y= 16+0x80, tile=0x15, palette=0 },
{ 27, x=24+0x40, y= 16+0x80, tile=0x16, palette=0 },
{ 28, x= 0+0x40, y= 24+0x80, tile=0x1b, palette=0 },
{ 29, x= 8+0x40, y= 24+0x80, tile=0x1c, palette=0 },
{ 30, x=16+0x40, y= 24+0x80, tile=0x1d, palette=0 },
{ 31, x=24+0x40, y= 24+0x80, tile=0x1e, palette=0 },
{ 32, x= 0+0x40, y= 32+0x80, tile=0x1f, palette=0 },
{ 33, x= 8+0x40, y= 32+0x80, tile=0x20, palette=0 },
{ 34, x=16+0x40, y= 32+0x80, tile=0x21, palette=0 },
{ 35, x=24+0x40, y= 32+0x80, tile=0x22, palette=0 }
}
@@main
init()
vblank_waitbegin()
lda #0b00011000
sta PPUMASK
lda #0
sta PPUCTRL
-- load SPRITE palette in PPU RAM
ppu_addr(OBJPAL)
for _,v in ipairs(ghosts_pal) do lda #v sta PPUDATA end
-- load the metasprites
for _,v in ipairs(ghosts_spr) do oam_set(v) end
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
-- idle
@_loop jmp _loop
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

63
samples/nes_hello.l65 Normal file
View File

@ -0,0 +1,63 @@
require'nes'
mappers.NROM()
location(chrrom0)
local font = section("font")
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -- <SPC>
dc.b 0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00,0x38,0x38,0x2c,0x64,0x7e,0x46,0xce,0x00 -- A
dc.b 0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00,0xfc,0x62,0x66,0x7c,0x62,0x66,0xfc,0x00 -- B
dc.b 0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00,0x7c,0xe6,0xc2,0xc0,0xc0,0xe6,0x7c,0x00 -- C
dc.b 0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00,0xfc,0x4e,0x46,0x46,0x46,0xce,0xfc,0x00 -- D
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00,0xfe,0x66,0x60,0x7c,0x60,0x66,0xfe,0x00 -- E
dc.b 0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00,0xfe,0x66,0x60,0x7c,0x60,0x60,0xf0,0x00 -- F
dc.b 0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00,0x7c,0xe6,0xc0,0xce,0xc6,0xe6,0x7c,0x00 -- G
dc.b 0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00,0xee,0x66,0x66,0x7e,0x66,0x66,0xee,0x00 -- H
dc.b 0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00 -- I
dc.b 0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00,0x0e,0x06,0x06,0xc6,0xc6,0xce,0x7c,0x00 -- J
dc.b 0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00,0xce,0xdc,0xf8,0xf0,0xf8,0xdc,0xce,0x00 -- K
dc.b 0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00,0xc0,0xc0,0xc0,0xc0,0xc6,0xc6,0xfe,0x00 -- L
dc.b 0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00,0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00 -- M
dc.b 0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00 -- N
dc.b 0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00,0x7c,0xee,0xc6,0xc6,0xc6,0xee,0x7c,0x00 -- O
dc.b 0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00,0xfc,0xc6,0xc6,0xc6,0xfc,0xc0,0xc0,0x00 -- P
dc.b 0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00,0x7c,0xce,0xc6,0xc6,0xde,0xec,0x7e,0x00 -- Q
dc.b 0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00,0xfc,0x66,0x66,0x7c,0x58,0x6c,0xe6,0x00 -- R
dc.b 0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00,0x7c,0xc6,0xc0,0x7c,0x06,0xc6,0x7c,0x00 -- S
dc.b 0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00,0xfe,0x92,0x10,0x10,0x10,0x10,0x38,0x00 -- T
dc.b 0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00,0xe6,0xe6,0xc2,0xc2,0xc2,0xe6,0x7c,0x00 -- U
dc.b 0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00,0xc6,0xc6,0xc6,0x6c,0x6c,0x38,0x38,0x00 -- V
dc.b 0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x82,0x00 -- W
dc.b 0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00,0x86,0xcc,0x78,0x30,0x78,0xcc,0x86,0x00 -- X
dc.b 0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00,0xc6,0xc6,0x6c,0x38,0x18,0x18,0x38,0x00 -- Y
dc.b 0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00,0x7e,0xce,0x98,0x30,0x62,0xe6,0xfc,0x00 -- Z
location(prgrom)
@@nmi rti
@@irq rti
charset(" abcdefghijklmnopqrstuvwxyz", \x(x + (font.org - font.location.start) // 16))
local hello = "hello world"
@@text byte(hello)
@@main
init()
vblank_waitbegin()
-- load BG palette in PPU RAM
ppu_addr(BGPAL)
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 PPUDATA iny cpy ##hello bne _loadtxt
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
-- show BG
lda #0x0a sta PPUMASK
-- idle
@_loop jmp _loop
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

BIN
samples/nes_house.nam Normal file

Binary file not shown.

View File

@ -0,0 +1,80 @@
require'nes'
-- samples : extacted of the demo : Let's Go To The Very Important Party 2018
-- original code : 2018 - mara/flush
-- asm code : 2019 - mara/flush
-- gfx : Exocet/Up Rough
mappers.CNROM{chrmap = function(ci) return ci*0x2000, 0x2000, (ci&1)*0x2000 end}
location(chrrom0)
-- create a variable house_tiles which contains tileset.chr's data (house's tiles)
@@house_tiles do local f, s = assert(io.open('nes_house_tileset.chr','rb')) s=f:read('*all') f:close() byte{s:byte(1,-1)} end
-- don't define nmi and irq routines
location(prgrom)
@@nmi rti
@@irq rti
-- create the house palette
local home_pal = { 0x32,0x22,0x11,0x30,0x32,0x06,0x16,0x26,0x32,0x27,0x37,0x17,0x32,0x0f,0x00,0x10 }
-- create a variable nam which contains the house's nametable (map of house)
do
local f, s = assert(io.open('nes_house.nam','rb')) s=f:read('*all') f:close()
@@nam byte{s:byte(1,-1)}
end
local nam_high = 1
local nam_low = 0
@@main
-- initialize the hardware registers
init()
-- wait a vertical blank
vblank_waitbegin()
-- load BG palette in PPU RAM
ppu_addr(BGPAL)
-- put the house's palette
for _,v in ipairs(home_pal) do lda #v sta PPUDATA end
ppu_addr(0x2000)
-- load and set nametable
lda #nam>>8&0xff
sta nam_high
lda #nam & 0xff
sta nam_low
jsr loadBG
-- reset scroll position
ppu_addr(0) sta BGSCROL sta BGSCROL
lda #0x0a sta PPUMASK
-- idle
@_loop jmp _loop
-- routine to load a nametable with the attributes
@loadBG
ldy #0
ldx #0
@loopLoadBG
lda (nam_low), y -- can only be used with y
sta 0x2007
iny
bne loopLoadBG
inc nam_high
inx
cpx #04
bne loopLoadBG
rts
-- produce NES file, and debug symbols
writebin(filename..'.nes')
writesym(filename..'.mlb', 'mesen')
writesym(filename..'.nes', 'fceux')
print(stats)

Binary file not shown.

BIN
samples/nespal.act Normal file

Binary file not shown.

40
samples/scv_hello.l7801 Normal file
View File

@ -0,0 +1,40 @@
require 'scv'
location(0x8000, 0x8FFF)
section{"rom", org=0x8000}
dc.b 'H'
@main
di
lxi sp,0xFFD2
ei
calt 0x8C
lxi hl,vdc_data
lxi de,0x3400
mvi c,0x03
block
lxi hl,message
lxi de,0x3044
lxi bc,0x01ff
@loop_0
block
dcr b
jr loop_0
-- beep
lxi hl,0x3600
calf 0xfb0
@loop_1
nop
jr loop_1
section{"vdc_data", org=0x8030}
dc.b 0xC0,0x00,0x00,0xF2
section{"message", org=0x8040}
dc.b "\t\t\t\t Hello world! \t\t\t\t"
dc.b 0x00
writebin(filename .. '.bin')

View File

@ -0,0 +1,498 @@
----------------------------------------------------------------
-- Plogue uPD1771C Tester
-- BY David Viens january 2010 chipsounds R&D
-- (from Hello World! Program Ver 1.1 by) Programmed By Enri
-- haxored to emit 7801 instructions using
-- Macro Assembler AS V1.42's 7810 mode
-- http://john.ccac.rwth-aachen.de:8000/as
-- Converted to l65
----------------------------------------------------------------
require 'scv'
location(0x8000, 0x9fff)
section{"rom", org=0x8000}
dc.b 'H' -- Headder
-- 0FF8X seems reserved, but not FF9X
local PARAMS = 0xFF90
local ACTIVE_PARAM = 0xFF9A
local PLAYING = 0xFF9B
-------------------------
-- Clear Text All Area --
-------------------------
calt 0x8c -- bios function!!!
-------------
-- Set VDC --
-------------
lxi hl,VDC_DATA
lxi de,0x3400
mvi c,0x03
block
---------------------
-- Set INITIAL RAM --
---------------------
lxi hl,INITIAL_RAM
lxi de,PARAMS
mvi c,0x0F
block
--------------------------------------------------------------
----------------- END INITIALISATION POUTINE -----------------
--------------------------------------------------------------
-------------------------
-- Print Screen Format --
-------------------------
lxi hl,DISPLAY_DATA
lxi de,0x3040 -- Text RAM Address
lxi bc,0x0180
@LOOP0
block
dcr b
jr LOOP0
--------------------------------------------------------------
------------------------- MAIN LOOP -------------------------
--------------------------------------------------------------
@LOOPN
-- reposition pointer to current param
lxi hl,PARAMS
mov b,(ACTIVE_PARAM)
dcx hl
@POSITIONLOOP
inx hl
dcr b
jmp POSITIONLOOP
lxi de,0x3090
-- SLEEP before note
mvi c,0x10 -- wait 1 second of vblanks
call SLEEPFUNC
mvi a,0xFD -- we want to store that into PA so that portB can be later scanned i guess like the vic/c64 kb
mov pa,a
mov a,pb -- see which are closed upon return
oni a,2
jr RIGHT_DOWN
jr RIGHT_END
@RIGHT_DOWN
ldax (hl)
adi a,1
stax (hl)
-- if playing replay patter
mov a,(PLAYING)
eqi a,1
jmp ALL_DONE
-- fake not already playing
mvi a,0
mov (PLAYING),a -- ON
call PLAY_PATTERN
jmp ALL_DONE
@RIGHT_END
oni a,1
jr DOWN_DOWN
jr DOWN_END
@DOWN_DOWN
mov a,(ACTIVE_PARAM)
adi a,1
mov (ACTIVE_PARAM),a
jmp ALL_DONE
@DOWN_END
mvi a,0xFE
mov pa,a -- fill port with voltages
mov a,pb -- see which are closed upon return
oni a,1
jr LEFT_DOWN
jr LEFT_END
@LEFT_DOWN
ldax (hl)
sui a,1
stax (hl)
-- if playing replay patter
mov a,(PLAYING)
eqi a,1
jmp ALL_DONE
-- fake not already playing
mvi a,0
mov (PLAYING),a -- OFF
call PLAY_PATTERN
jmp ALL_DONE
@LEFT_END
oni a,2
jr UP_DOWN
jr UP_END
@UP_DOWN
mov a,(ACTIVE_PARAM)
sui a,1
mov (ACTIVE_PARAM),a
jmp ALL_DONE
@UP_END
oni a,4
jr BUTTON_DOWN
jr BUTTON_UP
@BUTTON_DOWN
call PLAY_PATTERN
jmp ALL_DONE
@BUTTON_UP
call STOP_PATTERN
jr BUTTON_END
@BUTTON_END
@ALL_DONE
-- check bounds on active param
mov a,(ACTIVE_PARAM)
lti a,0x0A
jr ACT_GREATER
jr ACT_DONE
@ACT_GREATER
mvi a,0x00
mov (ACTIVE_PARAM),a
jr ACT_DONE
@ACT_DONE
-- print where we at!
call DISPLAY_LOOP
jmp LOOPN
-------------------------------------------------------------------------
-- Display init data
-------------------------------------------------------------------------
@VDC_DATA
dc.b 0xC0,0x00,0x00,0xF1 -- 0F1 -> black
-------------------------------------------------------------------------
-- lame sleep (with C param)
-------------------------------------------------------------------------
section{"SLEEPFUNC", org=0x8100}
-- sleep the amount of time depending on C
push bc
@SLOOP
mvi b,0xff
@SLEEP0
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
dcr b
jr SLEEP0
dcr c
jr SLOOP
pop bc
ret
-------------------------------------------------------------------------
-- wait on VBLANK
-------------------------------------------------------------------------
section{"WVBLANK", org=0x8200}
skit f2 -- wait until acknowledged
jr WVBLANK
ret
-------------------------------------------------------------------------
-- wait on D1771g (wait until INT1=1)
-------------------------------------------------------------------------
section{"WD1771G", org=0x8220}
skit f1
jr WD1771G
ret
-------------------------------------------------------------------------
-- Print ONE UINT8_T on screen as HEX (two screen chars printed)
--
-- A should contain the hex value to print (A returns unchanged)
-- D should contain the screen value to dump the high part (D returns unchanged)
-- V is thrashed (used as temporary val)
-------------------------------------------------------------------------
section{"PRINT_HEX", org=0x8240}
mvi v,0 -- trick to do V=A
add v,a -- trick to do V=A
ani a,0xF0 -- a = a & 0x0f;
clc -- clear carry so not to fuck up the following right shifts
rlr a --
rlr a --
rlr a --
rlr a -- ok we got the HEX value for the up 4bits
gti a,0x09
jr HIGH_GREATER
jr HIGH_LOWER
@HIGH_GREATER
adi a,0x30 -- a+='0' (ascii)
jr HIGH_DONE
@HIGH_LOWER
adi a,0x37 -- a+=('A'-10) (ascii)
jr HIGH_DONE
@HIGH_DONE
staxi (de) --
mvi a,0 -- trick to do A=V
add a,v -- trick to do A=V
ani a,0x0F -- a = a & 0x0f
gti a,0x09
jr LOW_GREATER
jr LOW_LOWER
@LOW_GREATER
adi a,0x30 -- a+='0' (ascii)
jr LOW_DONE
@LOW_LOWER
adi a,0x37 -- a+=('A'-10) (ascii)
jr LOW_DONE
@LOW_DONE
staxd (de) --
mvi a,0 -- trick to do A=V
add a,v -- trick to do A=V
ret
-------------------------------------------------------------------------
-- Prints all current param values
-------------------------------------------------------------------------
section{"DISPLAY_LOOP", org=0x8280}
mvi b,0x0B
lxi de,0x3090
lxi hl,PARAMS
@DLOOP
-- if b==ACTIVE_PARAM show arrow
-- else show space
mvi a,0x0B
mov c,(ACTIVE_PARAM)
sub a,c
eqa a,b
jr NOCURSOR
mvi a,0x1C
stax (de)
jr CURSDONE
@NOCURSOR
mvi a,0x20
stax (de)
@CURSDONE
-- print all ram
ldaxi (hl)
-- increase D pos to display in hex
inx de
inx de
inx de
call PRINT_HEX
dcx de
dcx de
dcx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
inx de
dcr b
jmp DLOOP
ret
-------------------------------------------------------------------------
-- Stop current pattern
-------------------------------------------------------------------------
section{"STOP_PATTERN", org=0x82d0}
-- if (!playing) return;
mov a,(PLAYING)
eqi a,1
jr STOP_DONE
mvi a,0
mov (PLAYING),a -- OFF
di
lxi hl,0x3600 -- SOUND REGISTER in H
call WVBLANK -- cwait on intf2
skit f1
nop
mvix (hl),0x00 -- note OFF
ei
@STOP_DONE
ret
-------------------------------------------------------------------------
-- Plays current pattern
-- Start with beep please
-------------------------------------------------------------------------
section{"PLAY_PATTERN", org=0x82f0}
mov a,(PLAYING)
eqi a,0
jmp PLAY_DONE
-- setting variable true!
mvi a,1
mov (PLAYING),a -- ON
lxi de,PARAMS
lxi hl,0x3600 -- SOUND REGISTER in H
ldaxi (de)
eqi a,1
jmp PLAY_TONE
jmp PLAY_NOISE
@PLAY_TONE
------------------------ Tone Start ------------------------
di
call WVBLANK
skit f1
nop
-- instrument params start
mvi a,2 -- tone!
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WVBLANK -- cwait on intf2
-- instrument params end
ei
jmp PLAY_DONE
@PLAY_NOISE
------------------------ Tone Start ------------------------
di
call WVBLANK -- cwait on intf2
skit f1
nop
-- instrument params start
mvi a,1 -- NOISE
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WD1771G -- cwait on intf1
ldaxi (de)
stax (hl)
call WVBLANK -- cwait on intf2
-- instrument params end
ei
jmp PLAY_DONE
@PLAY_DONE
ret
-------------------------------------------------------------------------
-- Message Data1
-- this version has left+audio changes working
-------------------------------------------------------------------------
@DISPLAY_DATA
dc.b " Plogue uPD1771C Tester "
dc.b " v0.0018 "
dc.b " Param[0]: ( ) "
dc.b " Param[1]: ( ) "
dc.b " Param[2]: ( ) "
dc.b " Param[3]: ( ) "
dc.b " Param[4]: ( ) "
dc.b " Param[5]: ( ) "
dc.b " Param[6]: ( ) "
dc.b " Param[7]: ( ) "
dc.b " Param[8]: ( ) "
dc.b " Param[9]: ( ) "
-- go to [FF90;FF9F]
@INITIAL_RAM
-- params
dc.b 0x02
dc.b 0x80
dc.b 0xFF
dc.b 0x1F
dc.b 0xAB
dc.b 0xFE
dc.b 0xCD
dc.b 0xDC
dc.b 0x45
dc.b 0x01
dc.b 0x00 -- FF9A curent param
dc.b 0x00 -- FF9B BOOL note on
dc.b 0x83 -- misc
dc.b 0x39 -- misc
dc.b 0x44 -- misc
dc.b 0x36 -- misc
writebin(filename..'.bin', genbin(0xff))

738
samples/scv_test.l7801 Normal file
View File

@ -0,0 +1,738 @@
require 'scv'
location(0x8000, 0x8FFF)
section{"rom", org=0x8000}
dc.b 'H'
@main
block
calb
ei
daa
di
ex
exx
halt
jb
nop
ret
reti
rets
sio
softi
stm
table
dcr a
dcr b
dcr c
inr a
inr b
inr c
dcx sp
inx sp
mvi a,0xca
mvi b,0xfe
mvi c,0xbe
mvi d,0xef
mvi e,0xf0
mvi h,0x0d
mvi l,0x78
mvi v,0x01
aci a,0x0f
adi a,0x1e
adinc a,0x2d
ani a,0x3c
eqi a,0x4b
gti a,0x5a
lti a,0x69
nei a,0x87
offi a,0x96
oni a,0xa5
ori a,0xb4
sbi a,0xc3
sui a,0xd2
suinb a,0xe1
xri a,0xf0
lxi bc,0xabcd
lxi de,0xbeef
lxi hl,0xcafe
lxi sp,0xf00d
call 0xd701
jmp 0x8e07
clc
pen
per
pex
rld
rrd
stc
mov a,b
mov a,c
mov a,d
mov a,e
mov a,h
mov a,l
mov b,a
mov c,a
mov d,a
mov e,a
mov h,a
mov l,a
@l0 jre l0
@l1 nop
jre l1
@l2 nop nop
jre l2
@l3 nop nop nop
jre l3
@l4 nop nop nop nop
jre l4
jre l5
@l5 jre l6
nop
@l6 jre l7
nop nop
@l7 jre l8
nop nop nop
@l8 jre l9
nop nop nop nop
@l9
ldax (bc)
ldax (de)
ldax (hl)
stax (bc)
stax (de)
stax (hl)
inrw (v,0x01)
ldaw (v,0x23)
dcrw (v,0x45)
staw (v,0x67)
bit0 (v,0x89)
bit1 (v,0xab)
bit2 (v,0xcd)
bit3 (v,0xef)
bit4 (v,0xfe)
bit5 (v,0xdc)
bit6 (v,0xba)
bit7 (v,0x98)
dcx bc
dcx de
dcx hl
inx bc
inx de
inx hl
mvix (bc),0xf9
mvix (de),0xe8
mvix (hl),0xd7
mviw (v,0x9a),0x3f
eqiw (v,0xc5),0x1b
ldaxd (de)
ldaxi (de)
ldaxd (hl)
ldaxi (hl)
staxd (de)
staxi (de)
staxd (hl)
staxi (hl)
push bc
push de
push hl
pop hl
pop de
pop bc
push va
pop va
rll a
rlr a
rll c
rlr c
sll a
slr a
sll c
slr c
skc
skz
sknc
sknz
skit f0
skit ft
skit f1
skit f2
skit fs
sknit f0
sknit ft
sknit f1
sknit f2
sknit fs
in 0x00
in 0xa7
in 0xbf
out 0x00
out 0x5a
out 0xbf
mov a,pa
mov a,pb
mov a,pc
mov a,mk
mov a,mb
mov a,mc
mov a,tm0
mov a,tm1
mov a,s
mov pa,a
mov pb,a
mov pc,a
mov mk,a
mov mb,a
mov mc,a
mov tm0,a
mov tm1,a
mov s,a
ana v,a
ana a,a
ana b,a
ana c,a
ana d,a
ana e,a
ana h,a
ana l,a
xra v,a
xra a,a
xra b,a
xra c,a
xra d,a
xra e,a
xra h,a
xra l,a
ora v,a
ora a,a
ora b,a
ora c,a
ora d,a
ora e,a
ora h,a
ora l,a
addnc v,a
addnc a,a
addnc b,a
addnc c,a
addnc d,a
addnc e,a
addnc h,a
addnc l,a
gta v,a
gta a,a
gta b,a
gta c,a
gta d,a
gta e,a
gta h,a
gta l,a
subnb v,a
subnb a,a
subnb b,a
subnb c,a
subnb d,a
subnb e,a
subnb h,a
subnb l,a
lta v,a
lta a,a
lta b,a
lta c,a
lta d,a
lta e,a
lta h,a
lta l,a
add v,a
add a,a
add b,a
add c,a
add d,a
add e,a
add h,a
add l,a
adc v,a
adc a,a
adc b,a
adc c,a
adc d,a
adc e,a
adc h,a
adc l,a
sub v,a
sub a,a
sub b,a
sub c,a
sub d,a
sub e,a
sub h,a
sub l,a
nea v,a
nea a,a
nea b,a
nea c,a
nea d,a
nea e,a
nea h,a
nea l,a
sbb v,a
sbb a,a
sbb b,a
sbb c,a
sbb d,a
sbb e,a
sbb h,a
sbb l,a
eqa v,a
eqa a,a
eqa b,a
eqa c,a
eqa d,a
eqa e,a
eqa h,a
eqa l,a
ana a,v
ana a,a
ana a,b
ana a,c
ana a,d
ana a,e
ana a,h
ana a,l
xra a,v
xra a,a
xra a,b
xra a,c
xra a,d
xra a,e
xra a,h
xra a,l
ora a,v
ora a,a
ora a,b
ora a,c
ora a,d
ora a,e
ora a,h
ora a,l
addnc a,v
addnc a,a
addnc a,b
addnc a,c
addnc a,d
addnc a,e
addnc a,h
addnc a,l
gta a,v
gta a,a
gta a,b
gta a,c
gta a,d
gta a,e
gta a,h
gta a,l
subnb a,v
subnb a,a
subnb a,b
subnb a,c
subnb a,d
subnb a,e
subnb a,h
subnb a,l
lta a,v
lta a,a
lta a,b
lta a,c
lta a,d
lta a,e
lta a,h
lta a,l
add a,v
add a,a
add a,b
add a,c
add a,d
add a,e
add a,h
add a,l
ona a,v
ona a,a
ona a,b
ona a,c
ona a,d
ona a,e
ona a,h
ona a,l
adc a,v
adc a,a
adc a,b
adc a,c
adc a,d
adc a,e
adc a,h
adc a,l
offa a,v
offa a,a
offa a,b
offa a,c
offa a,d
offa a,e
offa a,h
offa a,l
sub a,v
sub a,a
sub a,b
sub a,c
sub a,d
sub a,e
sub a,h
sub a,l
nea a,v
nea a,a
nea a,b
nea a,c
nea a,d
nea a,e
nea a,h
nea a,l
sbb a,v
sbb a,a
sbb a,b
sbb a,c
sbb a,d
sbb a,e
sbb a,h
sbb a,l
eqa a,v
eqa a,a
eqa a,b
eqa a,c
eqa a,d
eqa a,e
eqa a,h
eqa a,l
ani v,0x00
ani a,0x01
ani b,0x02
ani c,0x03
ani d,0x04
ani e,0x05
ani h,0x06
ani l,0x07
xri v,0x08
xri a,0x09
xri b,0x0a
xri c,0x0b
xri d,0x0c
xri e,0x0d
xri h,0x0e
xri l,0x0f
ori v,0x10
ori a,0x11
ori b,0x12
ori c,0x13
ori d,0x14
ori e,0x15
ori h,0x16
ori l,0x17
adinc v,0x18
adinc a,0x19
adinc b,0x1a
adinc c,0x1b
adinc d,0x1c
adinc e,0x1d
adinc h,0x1e
adinc l,0x1f
gti v,0x20
gti a,0x21
gti b,0x22
gti c,0x23
gti d,0x24
gti e,0x25
gti h,0x26
gti l,0x27
suinb v,0x28
suinb a,0x29
suinb b,0x2a
suinb c,0x2b
suinb d,0x2c
suinb e,0x2d
suinb h,0x2e
suinb l,0x2f
lti v,0x30
lti a,0x31
lti b,0x32
lti c,0x33
lti d,0x34
lti e,0x35
lti h,0x36
lti l,0x37
adi v,0x38
adi a,0x39
adi b,0x3a
adi c,0x3b
adi d,0x3c
adi e,0x3d
adi h,0x3e
adi l,0x4f
oni v,0x40
oni a,0x41
oni b,0x42
oni c,0x43
oni d,0x44
oni e,0x45
oni h,0x46
oni l,0x47
aci v,0x48
aci a,0x49
aci b,0x4a
aci c,0x4b
aci d,0x4c
aci e,0x4d
aci h,0x4e
aci l,0x4f
offi v,0x50
offi a,0x51
offi b,0x52
offi c,0x53
offi d,0x54
offi e,0x55
offi h,0x56
offi l,0x57
sui v,0x58
sui a,0x59
sui b,0x5a
sui c,0x5b
sui d,0x5c
sui e,0x5d
sui h,0x5e
sui l,0x5f
nei v,0x60
nei a,0x61
nei b,0x62
nei c,0x63
nei d,0x64
nei e,0x65
nei h,0x66
nei l,0x67
sbi v,0x68
sbi a,0x69
sbi b,0x6a
sbi c,0x6b
sbi d,0x6c
sbi e,0x6d
sbi h,0x6e
sbi l,0x6f
eqi v,0x70
eqi a,0x71
eqi b,0x72
eqi c,0x73
eqi d,0x74
eqi e,0x75
eqi h,0x76
eqi l,0x77
ani pa,0x78
ani pb,0x79
ani pc,0x7a
ani mk,0x7b
xri pa,0x7c
xri pb,0x7d
xri pc,0x7e
xri mk,0x7f
ori pa,0x80
ori pb,0x81
ori pc,0x82
ori mk,0x83
adinc pa,0x84
adinc pb,0x85
adinc pc,0x86
adinc mk,0x87
gti pa,0x88
gti pb,0x89
gti pc,0x8a
gti mk,0x8b
suinb pa,0x8c
suinb pb,0x8d
suinb pc,0x8e
suinb mk,0x8f
lti pa,0x90
lti pb,0x91
lti pc,0x92
lti mk,0x93
adi pa,0x94
adi pb,0x95
adi pc,0x96
adi mk,0x97
oni pa,0x98
oni pb,0x99
oni pc,0x9a
oni mk,0x9b
aci pa,0x9c
aci pb,0x9d
aci pc,0x9e
aci mk,0x9f
offi pa,0xa0
offi pb,0xa1
offi pc,0xa2
offi mk,0xa3
sui pa,0xa4
sui pb,0xa5
sui pc,0xa6
sui mk,0xa7
nei pa,0xa8
nei pb,0xa9
nei pc,0xaa
nei mk,0xab
sbi pa,0xac
sbi pb,0xad
sbi pc,0xae
sbi mk,0xaf
eqi pa,0xb0
eqi pb,0xb1
eqi pc,0xb2
eqi mk,0xb3
anaw (v,0xf0)
xraw (v,0xe1)
oraw (v,0xd2)
addncw (v,0xc3)
gtaw (v,0xb4)
subnbw (v,0xa5)
ltaw (v,0x96)
addw (v,0x87)
onaw (v,0x78)
adcw (v,0x69)
offaw (v,0x5a)
subw (v,0x4b)
neaw (v,0x3c)
sbbw (v,0x2d)
eqaw (v,0x1e)
sspd (0x8000)
lspd (0x8001)
sbcd (0x8010)
lbcd (0x8011)
sded (0x8100)
lded (0x8101)
shld (0x8110)
lhld (0x8111)
mov (0xab00),v
mov (0xbc01),a
mov (0xde02),b
mov (0xfa03),c
mov (0xeb04),d
mov (0xdc05),e
mov (0xcd06),h
mov (0xbe07),l
mov v,(0x000f)
mov a,(0x00f0)
mov b,(0x00ff)
mov c,(0x0f00)
mov d,(0x0f0f)
mov e,(0x0ff0)
mov h,(0x0fff)
mov l,(0xf000)
anax (bc)
xrax (bc)
orax (bc)
addncx (bc)
gtax (bc)
subnbx (bc)
ltax (bc)
addx (bc)
onax (bc)
adcx (bc)
offax (bc)
subx (bc)
neax (bc)
sbbx (bc)
eqax (bc)
anax (de)
xrax (de)
orax (de)
addncx (de)
gtax (de)
subnbx (de)
ltax (de)
addx (de)
onax (de)
adcx (de)
offax (de)
subx (de)
neax (de)
sbbx (de)
eqax (de)
anax (hl)
xrax (hl)
orax (hl)
addncx (hl)
gtax (hl)
subnbx (hl)
ltax (hl)
addx (hl)
onax (hl)
adcx (hl)
offax (hl)
subx (hl)
neax (hl)
sbbx (hl)
eqax (hl)
anaxi (de)
xraxi (de)
oraxi (de)
addncxi (de)
gtaxi (de)
subnbxi (de)
ltaxi (de)
addxi (de)
onaxi (de)
adcxi (de)
offaxi (de)
subxi (de)
neaxi (de)
sbbxi (de)
eqaxi (de)
anaxi (hl)
xraxi (hl)
oraxi (hl)
addncxi (hl)
gtaxi (hl)
subnbxi (hl)
ltaxi (hl)
addxi (hl)
onaxi (hl)
adcxi (hl)
offaxi (hl)
subxi (hl)
neaxi (hl)
sbbxi (hl)
eqaxi (hl)
anaxd (de)
xraxd (de)
oraxd (de)
addncxd (de)
gtaxd (de)
subnbxd (de)
ltaxd (de)
addxd (de)
onaxd (de)
adcxd (de)
offaxd (de)
subxd (de)
neaxd (de)
sbbxd (de)
eqaxd (de)
anaxd (hl)
xraxd (hl)
oraxd (hl)
addncxd (hl)
gtaxd (hl)
subnbxd (hl)
ltaxd (hl)
addxd (hl)
onaxd (hl)
adcxd (hl)
offaxd (hl)
subxd (hl)
neaxd (hl)
sbbxd (hl)
eqaxd (hl)
writebin(filename .. '.bin')

View File

@ -1,113 +1,113 @@
require'vcs'
mappers['4K']()
local HEADER_LEN = 92
local PICTURE_LEN = 64
local STEP_COUNT = 32
-- FLUSH text as playfield
local logo_img = assert(l65.image("flush.png")) -- analyse the image twice, so load it separately
@@logo_col samepage byte(0x00, 0xd4, 0x72, linecol(logo_img)) end
local pfs = playfield(logo_img)
for i=1,#pfs do
local pf = pfs[i]
section("logo_pf"..(i-1)) samepage byte(0, pf[1], pf[#pf], pf) end
end
local LOGO_HEIGHT = #pfs[1]
-- background
local bg = linecol("flushbg.png")
@@logo_bg_all
samepage
@logo_bg_pre for i=1,8 do dc.b bg[1] end
@logo_bg
for i=1,13 do dc.b bg[1] end
byte(bg)
for i=1,13 do dc.b bg[#bg] end
@logo_bg_post for i=1,8 do dc.b bg[#bg] end
end
-- generate offset tables into logo_pf*
do
local DISP_HEIGHT = PICTURE_LEN
local transfo,bgshift = {},{}
for x=0,STEP_COUNT-1 do
local theta = x/(STEP_COUNT-1) * math.pi/2 - math.pi/4
local s,c = math.sin(theta),math.cos(theta)
local ra,rb,rc,rd = -c, s-c, s+c, c
for y=0,DISP_HEIGHT-1 do
local r = DISP_HEIGHT / LOGO_HEIGHT
local yn = r * (2 * y/(DISP_HEIGHT-1) - 1)
local function f()
local y = (s-yn) / c
return math.min(math.floor((y+1)/2*LOGO_HEIGHT), LOGO_HEIGHT-1) + 3
end
local v
if rd < rc then v = yn>rc and 0 or yn>rb and f(yn) or yn>ra and 2 or 0
else v = yn>rd and 0 or yn>rc and 1 or yn>rb and f(yn) or 0 end
transfo[x*DISP_HEIGHT+DISP_HEIGHT-y] = v
end
bgshift[#bgshift+1] = math.floor(math.sin(math.pi+theta)*LOGO_HEIGHT/4)
end
@@logo_transfo byte(transfo)
@@logo_bgshift byte(bgshift)
end
-- rotation anim
do
local PERIOD = 128
local f = \x((math.sin(x*2*math.pi/PERIOD)+1)/2 * (STEP_COUNT-1))
local rotation = {}
for x=1,PERIOD do
rotation[#rotation+1] = math.floor(math.min(f(x-1), (STEP_COUNT-1)))
end
@@logo_rotation byte(rotation)
end
local bg_ptr = 0x80
local trans_ptr = 0x82
local time = 0x84
local tmp = 0x85
local on_vblank = function()
inc time asl time lsr time
ldy time lda logo_rotation,y sta tmp
ldy tmp lda logo_bgshift,y sta bg_ptr
lda#logo_bg&0xff clc adc bg_ptr sta bg_ptr lda#logo_bg>>8 sta bg_ptr+1
setptr(logo_transfo, trans_ptr)
lda tmp asl asl asl asl asl asl clc adc trans_ptr sta trans_ptr
lda trans_ptr+1 adc#0 sta trans_ptr+1
lda tmp lsr lsr clc adc trans_ptr+1 sta trans_ptr+1
ldy#PICTURE_LEN-1 lda (bg_ptr),y sta COLUBK
end
local kernel = function()
ldy#HEADER_LEN @_header sta WSYNC dey bne _header
samepage @_line
lda (trans_ptr),y tax
lda (bg_ptr),y
sta WSYNC sta COLUBK
lda logo_col,x sta COLUPF
lda logo_pf0,x sta PF0
lda logo_pf1,x sta PF1
lda logo_pf2,x sta PF2
lda logo_pf3,x sta PF0
lda logo_pf4,x sta PF1
lda logo_pf5,x sta PF2
iny cpy#PICTURE_LEN bne _line
end
lda#0 sta WSYNC sta PF0 sta PF1 sta PF2
end
@@main
init()
@_frame
overscan() vblank(on_vblank) screen(kernel) jmp _frame
;
writebin(filename..'.bin')
writesym(filename..'.sym')
print(stats)
require'vcs'
mappers['4K']()
local HEADER_LEN = 92
local PICTURE_LEN = 64
local STEP_COUNT = 32
-- FLUSH text as playfield
local logo_img = assert(l65.image("flush.png")) -- analyse the image twice, so load it separately
@@logo_col samepage byte(0x00, 0xd4, 0x72, linecol(logo_img)) end
local pfs = playfield(logo_img)
for i=1,#pfs do
local pf = pfs[i]
section("logo_pf"..(i-1)) samepage byte(0, pf[1], pf[#pf], pf) end
end
local LOGO_HEIGHT = #pfs[1]
-- background
local bg = linecol("flushbg.png")
@@logo_bg_all
samepage
@logo_bg_pre for i=1,8 do dc.b bg[1] end
@logo_bg
for i=1,13 do dc.b bg[1] end
byte(bg)
for i=1,13 do dc.b bg[#bg] end
@logo_bg_post for i=1,8 do dc.b bg[#bg] end
end
-- generate offset tables into logo_pf*
do
local DISP_HEIGHT = PICTURE_LEN
local transfo,bgshift = {},{}
for x=0,STEP_COUNT-1 do
local theta = x/(STEP_COUNT-1) * math.pi/2 - math.pi/4
local s,c = math.sin(theta),math.cos(theta)
local ra,rb,rc,rd = -c, s-c, s+c, c
for y=0,DISP_HEIGHT-1 do
local r = DISP_HEIGHT / LOGO_HEIGHT
local yn = r * (2 * y/(DISP_HEIGHT-1) - 1)
local function f()
local y = (s-yn) / c
return math.min(math.floor((y+1)/2*LOGO_HEIGHT), LOGO_HEIGHT-1) + 3
end
local v
if rd < rc then v = yn>rc and 0 or yn>rb and f(yn) or yn>ra and 2 or 0
else v = yn>rd and 0 or yn>rc and 1 or yn>rb and f(yn) or 0 end
transfo[x*DISP_HEIGHT+DISP_HEIGHT-y] = v
end
bgshift[#bgshift+1] = math.floor(math.sin(math.pi+theta)*LOGO_HEIGHT/4)
end
@@logo_transfo byte(transfo)
@@logo_bgshift byte(bgshift)
end
-- rotation anim
do
local PERIOD = 128
local f = \x((math.sin(x*2*math.pi/PERIOD)+1)/2 * (STEP_COUNT-1))
local rotation = {}
for x=1,PERIOD do
rotation[#rotation+1] = math.floor(math.min(f(x-1), (STEP_COUNT-1)))
end
@@logo_rotation byte(rotation)
end
local bg_ptr = 0x80
local trans_ptr = 0x82
local time = 0x84
local tmp = 0x85
local on_vblank = function()
inc time asl time lsr time
ldy time lda logo_rotation,y sta tmp
ldy tmp lda logo_bgshift,y sta bg_ptr
lda#logo_bg&0xff clc adc bg_ptr sta bg_ptr lda#logo_bg>>8 sta bg_ptr+1
setptr(logo_transfo, trans_ptr)
lda tmp asl asl asl asl asl asl clc adc trans_ptr sta trans_ptr
lda trans_ptr+1 adc#0 sta trans_ptr+1
lda tmp lsr lsr clc adc trans_ptr+1 sta trans_ptr+1
ldy#PICTURE_LEN-1 lda (bg_ptr),y sta COLUBK
end
local kernel = function()
ldy#HEADER_LEN @_header sta WSYNC dey bne _header
samepage @_line
lda (trans_ptr),y tax
lda (bg_ptr),y
sta WSYNC sta COLUBK
lda logo_col,x sta COLUPF
lda logo_pf0,x sta PF0
lda logo_pf1,x sta PF1
lda logo_pf2,x sta PF2
lda logo_pf3,x sta PF0
lda logo_pf4,x sta PF1
lda logo_pf5,x sta PF2
iny cpy#PICTURE_LEN bne _line
end
lda#0 sta WSYNC sta PF0 sta PF1 sta PF2
end
@@main
init()
@_frame
overscan() vblank(on_vblank) screen(kernel) jmp _frame
;
writebin(filename..'.bin')
writesym(filename..'.sym')
print(stats)

View File

@ -25,7 +25,7 @@ local wait = function() local l=label() lda INTIM bne l end
@@start
-- clear zeropage
cld ldx#0 txa @_clear dex tsx pha bne _clear
cld ldx#0 txa @_clear dex txs pha bne _clear
@main
-- overscan
sta WSYNC lda#2 sta VBLANK lda#TIM_OVERSCAN sta TIM64T wait()

582
samples/vcs_mcart.l65 Normal file
View File

@ -0,0 +1,582 @@
require'vcs'
-- change this if for some reason it fails to link
-- space for 10 song pointers
local FREESPACE = 3759-10*2
#pragma encapsulate far farcall
#pragma alias and dna
mappers.F4()
rom0.name = "core"
do
local ram = {
{ tt_timer=1 },
{ tt_cur_pat_index=2 },
{ tt_cur_note_index=2 },
{ tt_envelope_index=2 },
{ tt_cur_ins=2 },
{ tt_ptr = \(tmp) },
{ tt_song = \(tmp+2) },
{ tt_ptrtab = \(tmp+4) },
{ tt_song_ix=1 }, -- index into song_table
{ tt_song_ix_next=1 }, -- queued song to play after fade out
{ song_delay=1 }, -- fade out timer
{ cur_song_ix=1 }, -- current song index, cached
{ input_prev=1 }, -- previous SWCHA 4 MSB (player 0) value >> 1 | INPT4 MSB (fire player 0)
{ AUDC0s=1 }, { AUDC1s=1 }, { AUDF0s=1 }, { AUDF1s=1 }, { AUDV0s=1 }, { AUDV1s=1 },
{ vubars=16 },
{ tmp=30 },
{ print_txt = \(tmp+1) }, -- text pointer, can cross
{ print_line_count = \(tmp) }, -- number of lines to print
{ print_ptr = \(tmp+3) }, -- array of pointers to the characters
}
local ad = 0x80
for k,v in ipairs(ram) do
local f,t = pairs(v)
k,v = f(t)
if type(v) ~= 'function' then
_ENV[k] = ad
ad = ad + v
end
end
assert(ad <= 0x100)
for k,v in ipairs(ram) do
local f,t = pairs(v)
k,v = f(t)
if type(v) == 'function' then _ENV[k] = v() end
symbols[k] = _ENV[k]
end
end
-- 30 bytes fixed per song
TT_INSCTRLTABLE = 0
TT_INSADINDEXES = 2
TT_INSSUSTAININDEXES = 4
TT_INSRELEASEINDEXES = 6
TT_INSFREQVOLTABLE = 8
TT_PERCINDEXES = 10
TT_PERCFREQTABLE = 12
TT_PERCCTRLVOLTABLE = 14
TT_PATTERNSPEEDS = 16
TT_SEQUENCETABLE = 18
TT_PATTERNPTRLO = 20
TT_PATTERNPTRHI = 22
TT_PTR_SZ = 24
TT_GLOBAL_SPEED = 0+TT_PTR_SZ
TT_SPEED = 1+TT_PTR_SZ
TT_ODD_SPEED = 2+TT_PTR_SZ
TT_USE_FUNKTEMPO = 3+TT_PTR_SZ
TT_C0INIT = 4+TT_PTR_SZ
TT_C1INIT = 5+TT_PTR_SZ
TT_FREQ_MASK = 0b00011111
TT_INS_HOLD = 8
TT_INS_PAUSE = 16
TT_FIRST_PERC = 17
-- far calls to players to generate the stubs - the section will be stripped as it's unreferenced
location(rom0)
local sec_farstubgen = section("farstubsgen")
-- full ttt player with all possible options active, one for each rom bank but 0
for i=1,7 do
local loc = location(_ENV['rom'..i])
loc.songs_lo,loc.songs_hi = section("songs_lo"..i), section("songs_hi"..i)
local tt_fetchCurrentNote = section("tt_fetchCurrentNote"..i)
@_constructPatPtr
ldy tt_cur_pat_index,x lda (tt_ptrtab+TT_SEQUENCETABLE),y
bpl _noPatternGoto
dna #0b01111111 sta tt_cur_pat_index,x bpl _constructPatPtr
@_noPatternGoto
tay lda (tt_ptrtab+TT_PATTERNPTRLO),y sta tt_ptr lda (tt_ptrtab+TT_PATTERNPTRHI),y sta tt_ptr+1
clv
lda tt_cur_note_index,x bpl _notPrefetched
dna #0b01111111 sta tt_cur_note_index,x
bit "bit6Set"..i
@_notPrefetched
tay
lda (tt_ptr),y bne _noEndOfPattern
sta tt_cur_note_index,x
inc tt_cur_pat_index,x
bne _constructPatPtr
@_noEndOfPattern
rts
local tt_CalcInsIndex = section("tt_CalcInsIndex"..i)
lsr lsr lsr lsr lsr
tay
label("bit6Set"..i)
rts
loc.tt_tick = section("tt_tick"..i).instructions[1] -- get label table, used as index for far stubs
lda loc.songs_lo,y sta tt_song lda loc.songs_hi,y sta tt_song+1
bit tt_timer bpl _nosonginit
lda #0 sta tt_timer
ldy #TT_C0INIT lda (tt_song),y sta tt_cur_pat_index iny lda (tt_song),y sta tt_cur_pat_index+1
@_nosonginit
ldy #TT_PTR_SZ-1 @_setptrs lda (tt_song),y sta tt_ptrtab,y dey bpl _setptrs
dec tt_timer bpl _noNewNote
ldx #1
@_advanceLoop
jsr tt_fetchCurrentNote
cmp #TT_INS_PAUSE
beq _pause
bcs _newNote
adc tt_cur_ins,x sec sbc #8 sta tt_cur_ins,x
bcs _finishedNewNote
@_pause
lda tt_cur_ins,x jsr tt_CalcInsIndex
lda (tt_ptrtab+TT_INSRELEASEINDEXES),y
clc adc #1 bcc _storeADIndex
@_newNote
sta tt_cur_ins,x
cmp #TT_FREQ_MASK+1 bcs _startInstrument
tay
lda (tt_ptrtab+TT_PERCINDEXES),y
bne _storeADIndex
@_startInstrument
bvs _finishedNewNote
jsr tt_CalcInsIndex
lda (tt_ptrtab+TT_INSADINDEXES),y
@_storeADIndex
sta tt_envelope_index,x
@_finishedNewNote
inc tt_cur_note_index,x
@_sequencerNextChannel
dex
bpl _advanceLoop
ldy #TT_GLOBAL_SPEED lda (tt_song),y beq _localspeed
iny lax (tt_song),y
ldy #TT_USE_FUNKTEMPO lda (tt_song),y beq _globalnofunktempo
lda tt_cur_note_index lsr bcc _noOddFrame ldy #TT_ODD_SPEED lax (tt_song),y @_noOddFrame
@_globalnofunktempo
stx tt_timer
jmp _speeddone
@_localspeed
ldy tt_cur_pat_index lda (tt_ptrtab+TT_SEQUENCETABLE),y tay
sty tt_ptr ldy #TT_USE_FUNKTEMPO lda (tt_song),y ldy tt_ptr cmp #0 beq _localnofunktempo
lda tt_cur_note_index lsr
lda (tt_ptrtab+TT_PATTERNSPEEDS),y
bcc _evenFrame
dna #0x0f
bcs _storeFunkTempo
@_evenFrame
lsr lsr lsr lsr
@_storeFunkTempo
sta tt_timer
jmp _speeddone
@_localnofunktempo
lda (tt_ptrtab+TT_PATTERNSPEEDS),y sta tt_timer
@_speeddone
@_noNewNote
ldx #1
@_updateLoop
lda tt_cur_ins,x
beq _afterAudioUpdate
cmp #TT_FREQ_MASK+1 bcs _instrument
ldy tt_envelope_index,x
lda (tt_ptrtab+TT_PERCCTRLVOLTABLE),y beq _endOfPercussion inc tt_envelope_index,x @_endOfPercussion
sta AUDV0s,x lsr lsr lsr lsr sta AUDC0s,x
lda (tt_ptrtab+TT_PERCFREQTABLE),y
sta AUDF0s,x
bpl _afterAudioUpdate
jsr tt_fetchCurrentNote
cmp #TT_FREQ_MASK+1
bcc _afterAudioUpdate
sta tt_cur_ins,x
jsr tt_CalcInsIndex
lda (tt_ptrtab+TT_INSSUSTAININDEXES),y sta tt_envelope_index,x
asl tt_cur_note_index,x sec ror tt_cur_note_index,x
bmi _afterAudioUpdate
@_instrument
jsr tt_CalcInsIndex
lda (tt_ptrtab+TT_INSCTRLTABLE),y sta AUDC0s,x
lda tt_envelope_index,x cmp (tt_ptrtab+TT_INSRELEASEINDEXES),y bne _noEndOfSustain lda (tt_ptrtab+TT_INSSUSTAININDEXES),y @_noEndOfSustain
tay
lda (tt_ptrtab+TT_INSFREQVOLTABLE),y beq _endOfEnvelope iny @_endOfEnvelope
sty tt_envelope_index,x
sta AUDV0s,x
lsr lsr lsr lsr clc adc tt_cur_ins,x sec sbc #8
sta AUDF0s,x
@_afterAudioUpdate
dex
bpl _updateLoop
rts
section(sec_farstubgen) far loc.tt_tick
end
-- set free size in each location
location(rom0)
local songs_meta_lo,songs_meta_hi = section("songs_meta_lo"),section("songs_meta_hi")
local locsz = {}
for lix,loc in ipairs(locations) do
if loc ~= rom0 then
location(rom0)
local secmeta = section("songs"..lix.."_meta")
section(songs_meta_lo) byte_lo(secmeta)
section(songs_meta_hi) byte_hi(secmeta)
table.insert(locsz, { lix=lix, secmeta=secmeta, space=FREESPACE })
end
end
-- load all songs
local songs = {}
do
local lfs = require'lfs'
local dir = ... or lfs.currentdir()
for file in lfs.dir(dir) do
if file:sub(-4) == '.ttt' then
local song = ttt(dir .. '/' .. file)
table.insert(songs, song)
local size = 0
for _,v in pairs(song) do
if type(v) == 'table' then
if type(v[1]) == 'table' then
for _,vv in ipairs(v) do size = size + #vv end
else
size = size + #v
end
end
end
size = size + 2*#song.patterns -- pattern pointers
song.size = size + 30
song.filename = file:sub(1,-5)
end
end
end
-- place all songs
charset(" abcdefghijklmnopqrstuvwxyz", \x(x*8))
local meta = function(txt)
txt = txt:lower()
local out = {}
for i=1,#txt do
local c = string.byte(txt:sub(i,i))
if c < string.byte('a') or c > string.byte('z') then c = string.byte(' ') end
table.insert(out, string.char(c))
end
for i = 1, 12-#out do
if i&1 ~= 0 then table.insert(out, 1, ' ')
else table.insert(out, ' ')
end
end
return table.concat(out):sub(1,12)
end
local song_ids = {} -- 4 LSB: song index, 4 MSB: bank index
table.sort(songs, \a,b(a.size > b.size))
for _,song in ipairs(songs) do
table.sort(locsz, function(a,b)
if a.space == b.space then return locations[a.lix].start < locations[b.lix].start end
return a.space < b.space
end)
for _,locinfo in ipairs(locsz) do
if locinfo.space >= song.size then
locinfo.space = locinfo.space - song.size
local loc = location(locations[locinfo.lix])
local n = song.filename
song_ids[n] = locinfo.lix-2 << 4 | #loc.songs_lo.instructions-1
local s = function(str) return section(str..n) end
s("tt_InsCtrlTable") byte(song.insctrltable)
s("tt_InsADIndexes") byte(song.insadindexes)
s("tt_InsSustainIndexes") byte(song.inssustainindexes)
s("tt_InsReleaseIndexes") byte(song.insreleaseindexes)
s("tt_InsFreqVolTable") byte(song.insfreqvoltable)
s("tt_PercIndexes") byte(song.percindexes)
s("tt_PercFreqTable") byte(song.percfreqtable)
s("tt_PercCtrlVolTable") byte(song.percctrlvoltable)
local patterns = {}
for i,pattern in ipairs(song.patterns) do
table.insert(patterns, section("pattern"..n..i)) byte(pattern)
end
s("tt_PatternSpeeds") byte(song.patternspeeds)
s("tt_PatternPtrLo") byte_lo(patterns)
s("tt_PatternPtrHi") byte_hi(patterns)
s("tt_SequenceTable") byte(song.sequence[1]) byte(song.sequence[2])
s("tt_song")
local function ptr(t, v) t=t..n v=v or 0 byte_lo(\(symbols[t]-v)) byte_hi(\(symbols[t]-v)) end
ptr("tt_InsCtrlTable", 1)
ptr("tt_InsADIndexes", 1)
ptr("tt_InsSustainIndexes", 1)
ptr("tt_InsReleaseIndexes", 1)
ptr("tt_InsFreqVolTable")
ptr("tt_PercIndexes", 17)
ptr("tt_PercFreqTable", 1)
ptr("tt_PercCtrlVolTable", 1)
ptr("tt_PatternSpeeds")
ptr("tt_SequenceTable")
ptr("tt_PatternPtrLo")
ptr("tt_PatternPtrHi")
dc.b song.globalspeed and 1 or 0, song.speed-1, song.oddspeed-1, song.usefunktempo and 1 or 0
dc.b song.c0init, song.c1init
-- append song pointer to pointer table
section(loc.songs_lo) byte_lo("tt_song"..n)
section(loc.songs_hi) byte_hi("tt_song"..n)
-- rom0 data
section(locinfo.secmeta)
byte(meta(song.author))
byte(meta(song.name and #song.name>0 and song.name or song.filename))
break
end
end
end
----------------------------------------------------------------------
-- songs data
----------------------------------------------------------------------
-- sort songs by alphabetic order
local song_ixs = {}
for k in pairs(song_ids) do song_ixs[#song_ixs+1] = k end
table.sort(song_ixs)
for k,v in ipairs(song_ixs) do song_ixs[k] = song_ids[v] end
location(rom0)
@@song_table byte(song_ixs)
-- player pointers table
local song_players = {}
for i=1,7 do
table.insert(song_players, \(op_resolve(far_stubs[location(_ENV['rom'..i]).tt_tick][rom0])-1))
end
location(rom0)
@@song_players_lo byte_lo(song_players)
@@song_players_hi byte_hi(song_players)
@@song_tick
lda cur_song_ix
tax lsr lsr lsr lsr tay
lda song_players_hi,y pha lda song_players_lo,y pha
txa dna #0xf tay
rts
@@song_switch
ldy tt_song_ix_next sty tt_song_ix lda song_table,y sta cur_song_ix
lda #0x80 sta tt_timer -- request song reset from player
lda #0 ldx #8 @_resettt sta tt_timer,x dex bne _resettt
ldx #5 @_resetaud sta AUDC0s,x dex bpl _resetaud
rts
local song_norm = function()
-- clamp to valid TIA range
lda AUDC0s dna #0x0f sta AUDC0s lda AUDC1s dna #0x0f sta AUDC1s
lda AUDF0s dna #0x1f sta AUDF0s lda AUDF1s dna #0x1f sta AUDF1s
lda AUDV0s dna #0x0f sta AUDV0s lda AUDV1s dna #0x0f sta AUDV1s
end
@volmul for x=0,255 do dc.b math.floor(15*(((x/16)/16)*((x%16)/15))) end
local song_transition = function()
lda song_delay beq _transdone bpl _transapply
inc song_delay bne _noswitch
jsr song_switch jmp _transdone
@_noswitch
lda song_delay eor #0xff sec adc #0
@_transapply
asl asl asl dna #0xf0 ora AUDV0s tay ldx volmul,y stx AUDV0s
dna #0xf0 ora AUDV1s tay ldx volmul,y stx AUDV1s
@_transdone
-- set result
ldx #5 @_setaud lda AUDC0s,x sta AUDC0,x dex bpl _setaud
end
----------------------------------------------------------------------
-- display song name and author
----------------------------------------------------------------------
section{ "font", align=256 } dc.b
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,-- <SPC>
0x18,0x3c,0x66,0x7e,0x66,0x66,0x66,0x00,-- A
0x7c,0x66,0x66,0x7c,0x66,0x66,0x7c,0x00,-- B
0x3c,0x66,0x60,0x60,0x60,0x66,0x3c,0x00,-- C
0x78,0x6c,0x66,0x66,0x66,0x6c,0x78,0x00,-- D
0x7e,0x60,0x60,0x78,0x60,0x60,0x7e,0x00,-- E
0x7e,0x60,0x60,0x78,0x60,0x60,0x60,0x00,-- F
0x3c,0x66,0x60,0x6e,0x66,0x66,0x3c,0x00,-- G
0x66,0x66,0x66,0x7e,0x66,0x66,0x66,0x00,-- H
0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,-- I
0x1e,0x0c,0x0c,0x0c,0x0c,0x6c,0x38,0x00,-- J
0x66,0x6c,0x78,0x70,0x78,0x6c,0x66,0x00,-- K
0x60,0x60,0x60,0x60,0x60,0x60,0x7e,0x00,-- L
0x63,0x77,0x7f,0x6b,0x63,0x63,0x63,0x00,-- M
0x66,0x76,0x7e,0x7e,0x6e,0x66,0x66,0x00,-- N
0x3c,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- O
0x7c,0x66,0x66,0x7c,0x60,0x60,0x60,0x00,-- P
0x3c,0x66,0x66,0x66,0x66,0x3c,0x0e,0x00,-- Q
0x7c,0x66,0x66,0x7c,0x78,0x6c,0x66,0x00,-- R
0x3c,0x66,0x60,0x3c,0x06,0x66,0x3c,0x00,-- S
0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00,-- T
0x66,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- U
0x66,0x66,0x66,0x66,0x66,0x3c,0x18,0x00,-- V
0x63,0x63,0x63,0x6b,0x7f,0x77,0x63,0x00,-- W
0x66,0x66,0x3c,0x18,0x3c,0x66,0x66,0x00,-- X
0x66,0x66,0x66,0x3c,0x18,0x18,0x18,0x00,-- Y
0x7e,0x06,0x0c,0x18,0x30,0x60,0x7e,0x00 -- Z
-- a = line count
@@print12
sta print_line_count
lda#6 sta NUSIZ0 sta NUSIZ1
-- set MSB of font character addresses
lda#font>>8 ldx#23 @_loadfont sta print_ptr,x dex dex bpl _loadfont
-- position sprites
samepage
sta WSYNC
ldx#6 @_delay dex bne _delay
sta RESP0 nop sta RESP1 lda#0x70 sta HMP0 lda#0x60 sta HMP1 sta WSYNC sta HMOVE
end
@_loop
-- load text line
ldx#0 ldy#0 @_loadline lda (print_txt),y sta print_ptr,x inx inx iny cpy#12 bne _loadline
lda#0x80 sta HMP0 sta HMP1
ldy#0 samepage @_printline
sta WSYNC sta HMOVE
-- first scanline
lda (print_ptr+2),y sta GRP0 lda (print_ptr+6),y sta GRP1
lda (print_ptr+22),y tax sleep(6)
lda (print_ptr+10),y sta GRP0 lda (print_ptr+14),y sta GRP1 lda (print_ptr+18),y sta GRP0 stx GRP1
sta HMCLR sleep(8) sta HMOVE
-- second scanline
lda (print_ptr),y sta GRP0 lda (print_ptr+4),y sta GRP1
lda (print_ptr+20),y tax lda#0x80 sta HMP0 sta HMP1 nop
lda (print_ptr+8),y sta GRP0 lda (print_ptr+12),y sta GRP1 lda (print_ptr+16),y sta GRP0 stx GRP1
iny cpy#8 bne _printline
end
dec print_line_count beq _end
lda print_txt clc adc#12 sta print_txt lda print_txt+1 adc#0 sta print_txt+1
jmp _loop
@_end
lda#0 sta GRP0 sta GRP1
rts
----------------------------------------------------------------------
-- equalizer anim
----------------------------------------------------------------------
-- convert to freq: http://atariage.com/forums/topic/257526-musicsound-programming-question-atari-2600/
-- maps AUDCi to 0-7 3 bits (<<5) value for wave length of: 1, 2, 6, 15, 31, 93, 465, 511
@@wavlen samepage
dc.b 0x00,0x60,0xC0,0xC0,0x20,0x20,0x80,0x80,
0xF0,0x80,0x80,0x00,0x40,0x40,0xA0,0xA0
end
local wavelen_map = { 1, 2, 6, 15, 31, 93, 465, 511 }
local freq_ranges = { 30, 60, 120, 240, 480, 960, 1920, 9999999 }
local t = {}
for i=0,255 do
local wavelen = wavelen_map[(i>>5)+1]
local note = (i&31)+1
local freq = 3546894 / 114 / wavelen / note
for x=1,9 do
if freq <= freq_ranges[x] then
t[#t+1] = x-1
break
end
end
end
section{'regfreq', align=256} byte(t)
@@vm_pf2 samepage
dc.b 0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
end
@@vm_pf1 samepage
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F
end
@@vm_col samepage
dc.b 0x3C,0x7C,0x9C,0xDC,0x2C,0x4C,0x6C,0xAC
end
local vumeter_tick = function()
lda #0 ldy #15 @_vudec ldx vubars,y dex bmi _vudecskip stx vubars,y @_vudecskip dey bpl _vudec
lda AUDF0s ldy AUDC0s ora wavlen,y tax ldy regfreq,x
lda AUDV0s cmp vubars,y bcc _vuless0 sta vubars,y @_vuless0
lda AUDF1s ldy AUDC1s ora wavlen,y tax ldy regfreq,x
lda AUDV1s cmp vubars+8,y bcc _vuless1 sta vubars+8,y @_vuless1
end
local vumeter_draw = function()
lda #ctrlpf.PF_MIRRORED sta CTRLPF
ldy #7
@_vudraw
samepage
lda #16 sta tmp
@_vudraw1
sta WSYNC
lda vm_col,y sta COLUPF sleep(4)
ldx vubars,y
lda vm_pf1,x sta PF1
lda vm_pf2,x sta PF2
sleep(8)
ldx vubars+8,y
lda vm_pf2,x sta PF2
lda vm_pf1,x sta PF1
dec tmp bpl _vudraw1
sta WSYNC lda #0 sta PF2 sta PF1
sta WSYNC
dey bpl _vudraw
end
lda #0 sta CTRLPF
sta WSYNC sta WSYNC
end
@@song_count dc.b #songs
local next_song = function()
inc tt_song_ix_next lda song_count cmp tt_song_ix_next bne _nextsongdone lda#0 sta tt_song_ix_next @_nextsongdone
end
local prev_song = function()
dec tt_song_ix_next bpl _prevsongdone lda song_count sta tt_song_ix_next dec tt_song_ix_next @_prevsongdone
end
local check_input = function()
local JOYSTICK_RIGHT= 0x80
local JOYSTICK_LEFT = 0x40
lda SWCHA cmp input_prev beq _inputend
sta input_prev
lda #JOYSTICK_RIGHT bit input_prev bne _noright next_song() jmp _inputdone @_noright
lda #JOYSTICK_LEFT bit input_prev bne _inputdone prev_song()
@_inputdone
lda #0xe0 sta song_delay
@_inputend
end
local tick = function()
jsr song_tick
song_norm()
song_transition()
vumeter_tick()
end
@@mul24 for x=0,9 do byte(x*24) end
local kernel = function()
lda#0x8a sta COLUP0 sta COLUP1
lda cur_song_ix tax lsr lsr lsr lsr tay
lda songs_meta_lo,y sta print_txt lda songs_meta_hi,y sta print_txt+1
txa dna #0xf tay lda mul24,y clc adc print_txt sta print_txt lda print_txt+1 adc #0 sta print_txt+1
lda#1 jsr print12
vumeter_draw()
lda#0xaa sta COLUP0 sta COLUP1
lda print_txt clc adc #12 sta print_txt lda print_txt+1 adc #0 sta print_txt+1
lda#1 jsr print12
end
@@main
init()
lda SWCHA sta input_prev
jsr song_switch
@_frame
overscan(check_input) vblank(tick) screen(kernel) jmp _frame
; -- needed if last instruction is implied
writebin(filename..'.bin')
writesym(filename..'.sym')
print(stats)

View File

@ -1,413 +1,413 @@
require'vcs'
mappers['2K']()
local AUDC0s = 0x90
local AUDC1s, AUDF0s, AUDF1s, AUDV0s, AUDV1s = AUDC0s+1, AUDC0s+2, AUDC0s+3, AUDC0s+4, AUDC0s+5
local vubars = 0xA0
local tmp = 0xB0
local zic_filename = ... or 'Ishtar.ttt'
local zic = ttt(zic_filename)
print(string.format('Using file %s (ttt version %d)\n Name: %s\n Author: %s\n%s', zic_filename, zic.version, zic.name, zic.author, zic.comment))
-- debug printing of zic data
--[[
local printbytetable = function(v)
local s = ' '
for kk,vv in ipairs(v) do
s = s .. string.format('%02x, ', vv)
if #s >= 34 then print(s) s = ' ' end
end
if #s > 0 then print(s) end
end
for k,v in pairs(zic) do
if type(v) ~= 'table' then
print(k,v)
elseif type(v[1]) == 'number' then
print(k) printbytetable(v)
elseif type(v[1]) == 'table' then
for ek, e in ipairs(v) do
print(k, ek, e.name) printbytetable(e)
end
end
end
]]
@@tt_InsCtrlTable byte(zic.insctrltable)
@@tt_InsADIndexes byte(zic.insadindexes)
@@tt_InsSustainIndexes byte(zic.inssustainindexes)
@@tt_InsReleaseIndexes byte(zic.insreleaseindexes)
@@tt_InsFreqVolTable byte(zic.insfreqvoltable)
@@tt_PercIndexes byte(zic.percindexes)
@@tt_PercFreqTable byte(zic.percfreqtable)
@@tt_PercCtrlVolTable byte(zic.percctrlvoltable)
local patterns = {}
for i,pattern in ipairs(zic.patterns) do
table.insert(patterns, section("pattern"..i)) byte(pattern)
end
@@tt_PatternSpeeds byte(zic.patternspeeds)
@@tt_PatternPtrLo byte_lo(patterns)
@@tt_PatternPtrHi byte_hi(patterns)
@@tt_SequenceTable byte(zic.sequence[1]) byte(zic.sequence[2])
local tt = {
-- =====================================================================
-- Permanent variables. These are states needed by the player.
-- =====================================================================
'timer', -- current music timer value
'cur_pat_index_c0', -- current pattern index into tt_SequenceTable
'cur_pat_index_c1',
'cur_note_index_c0', -- note index into current pattern
'cur_note_index_c1',
'envelope_index_c0', -- index into ADSR envelope
'envelope_index_c1',
'cur_ins_c0', -- current instrument
'cur_ins_c1',
-- =====================================================================
-- Temporary variables. These will be overwritten during a call to the
-- player routine, but can be used between calls for other things.
-- =====================================================================
'ptr', -- 2 bytes
}
for k,v in ipairs(tt) do tt[v] = k + 0x7F end
tt.FREQ_MASK = 0b00011111
tt.INS_HOLD = 8
tt.INS_PAUSE = 16
tt.FIRST_PERC = 17
tt.fetchCurrentNoteImpl = function()
@_constructPatPtr
ldy tt.cur_pat_index_c0,x lda tt_SequenceTable,y
if zic.usegoto then
bpl _noPatternGoto
;and #0b01111111 sta tt.cur_pat_index_c0,x bpl _constructPatPtr
@_noPatternGoto
end
tay lda tt_PatternPtrLo,y sta tt.ptr lda tt_PatternPtrHi,y sta tt.ptr+1
if zic.overlay then
clv
lda tt.cur_note_index_c0,x bpl _notPrefetched
;and #0b01111111 sta tt.cur_note_index_c0,x
bit tt_Bit6Set
@_notPrefetched
tay
else
ldy tt.cur_note_index_c0,x
end
lda (tt.ptr),y bne _noEndOfPattern
sta tt.cur_note_index_c0,x
inc tt.cur_pat_index_c0,x
bne _constructPatPtr
@_noEndOfPattern
end
if zic.useoverlay then
@@tt_fetchCurrentNoteSection tt.fetchCurrentNoteImpl() rts
tt.fetchCurrentNote = function() jsr tt_fetchCurrentNoteSection end
else
tt.fetchCurrentNote = tt.fetchCurrentNoteImpl
end
@@tt_CalcInsIndex
lsr lsr lsr lsr lsr
tay
@tt_Bit6Set
rts
local function zic_tick()
dec tt.timer bpl _noNewNote
ldx #1
@_advanceLoop
tt.fetchCurrentNote()
cmp #tt.INS_PAUSE
if zic.useslide then
beq _pause
bcs _newNote
adc tt.cur_ins_c0,x sec sbc #8 sta tt.cur_ins_c0,x
bcs _finishedNewNote
else
bcc _finishedNewNote
bne _newNote
end
@_pause
lda tt.cur_ins_c0,x jsr tt_CalcInsIndex
lda tt_InsReleaseIndexes-1,y
clc adc #1 bcc _storeADIndex
@_newNote
sta tt.cur_ins_c0,x
cmp #tt.FREQ_MASK+1 bcs _startInstrument
tay
lda tt_PercIndexes-tt.FIRST_PERC,y
bne _storeADIndex
@_startInstrument
if zic.useoverlay then
bvs _finishedNewNote
end
jsr tt_CalcInsIndex
lda tt_InsADIndexes-1,y
@_storeADIndex
sta tt.envelope_index_c0,x
@_finishedNewNote
inc tt.cur_note_index_c0,x
@_sequencerNextChannel
dex
bpl _advanceLoop
if zic.globalspeed then
ldx #zic.speed-1
if zic.usefunktempo then
lda tt.cur_note_index_c0 lsr bcc _noOddFrame ldx #zic.oddspeed-1 @_noOddFrame
end
stx tt.timer
else
ldx tt.cur_pat_index_c0 ldy tt_SequenceTable,x
if zic.usefunktempo then
lda tt.cur_note_index_c0 lsr
lda tt_PatternSpeeds,y
bcc _evenFrame
;and #0x0f
bcs _storeFunkTempo
@_evenFrame
lsr lsr lsr lsr
@_storeFunkTempo
sta tt.timer
else
lda tt_PatternSpeeds,y sta tt.timer
end
end
@_noNewNote
ldx #1
@_updateLoop
lda tt.cur_ins_c0,x
if not zic.startswithnotes then
beq _afterAudioUpdate
end
cmp #tt.FREQ_MASK+1 bcs _instrument
ldy tt.envelope_index_c0,x
lda tt_PercCtrlVolTable-1,y beq _endOfPercussion inc tt.envelope_index_c0,x @_endOfPercussion
sta AUDV0,x sta AUDV0s,x lsr lsr lsr lsr sta AUDC0,x sta AUDC0s,x
lda tt_PercFreqTable-1,y
sta AUDF0,x
sta AUDF0s,x -- EXTRA for vumeter rendering
if zic.useoverlay then
bpl _afterAudioUpdate
tt.fetchCurrentNote()
cmp #tt.FREQ_MASK+1
bcc _afterAudioUpdate
sta tt.cur_ins_c0,x
jsr tt_CalcInsIndex
lda tt_InsSustainIndexes-1,y sta tt.envelope_index_c0,x
asl tt.cur_note_index_c0,x sec ror tt.cur_note_index_c0,x
bmi _afterAudioUpdate
else
jmp _afterAudioUpdate
end
@_instrument
jsr tt_CalcInsIndex
lda tt_InsCtrlTable-1,y sta AUDC0,x
sta AUDC0s,x -- EXTRA for vumeter rendering
lda tt.envelope_index_c0,x cmp tt_InsReleaseIndexes-1,y bne _noEndOfSustain lda tt_InsSustainIndexes-1,y @_noEndOfSustain
tay
lda tt_InsFreqVolTable,y beq _endOfEnvelope iny @_endOfEnvelope
sty tt.envelope_index_c0,x
sta AUDV0,x
sta AUDV0s,x -- EXTRA for vumeter rendering
lsr lsr lsr lsr clc adc tt.cur_ins_c0,x sec sbc #8
sta AUDF0,x
sta AUDF0s,x -- EXTRA for vumeter rendering
@_afterAudioUpdate
dex
bpl _updateLoop
end
local function zic_init()
if zic.c0init ~= 0 then lda#zic.c0init sta tt.cur_pat_index_c0 end
if zic.c1init ~= 0 then lda#zic.c1init sta tt.cur_pat_index_c1 end
end
----------------------------------------------------------------------
-- display song name and author
----------------------------------------------------------------------
section{ "font", align=256 } dc.b
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,-- <SPC>
0x18,0x3c,0x66,0x7e,0x66,0x66,0x66,0x00,-- A
0x7c,0x66,0x66,0x7c,0x66,0x66,0x7c,0x00,-- B
0x3c,0x66,0x60,0x60,0x60,0x66,0x3c,0x00,-- C
0x78,0x6c,0x66,0x66,0x66,0x6c,0x78,0x00,-- D
0x7e,0x60,0x60,0x78,0x60,0x60,0x7e,0x00,-- E
0x7e,0x60,0x60,0x78,0x60,0x60,0x60,0x00,-- F
0x3c,0x66,0x60,0x6e,0x66,0x66,0x3c,0x00,-- G
0x66,0x66,0x66,0x7e,0x66,0x66,0x66,0x00,-- H
0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,-- I
0x1e,0x0c,0x0c,0x0c,0x0c,0x6c,0x38,0x00,-- J
0x66,0x6c,0x78,0x70,0x78,0x6c,0x66,0x00,-- K
0x60,0x60,0x60,0x60,0x60,0x60,0x7e,0x00,-- L
0x63,0x77,0x7f,0x6b,0x63,0x63,0x63,0x00,-- M
0x66,0x76,0x7e,0x7e,0x6e,0x66,0x66,0x00,-- N
0x3c,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- O
0x7c,0x66,0x66,0x7c,0x60,0x60,0x60,0x00,-- P
0x3c,0x66,0x66,0x66,0x66,0x3c,0x0e,0x00,-- Q
0x7c,0x66,0x66,0x7c,0x78,0x6c,0x66,0x00,-- R
0x3c,0x66,0x60,0x3c,0x06,0x66,0x3c,0x00,-- S
0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00,-- T
0x66,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- U
0x66,0x66,0x66,0x66,0x66,0x3c,0x18,0x00,-- V
0x63,0x63,0x63,0x6b,0x7f,0x77,0x63,0x00,-- W
0x66,0x66,0x3c,0x18,0x3c,0x66,0x66,0x00,-- X
0x66,0x66,0x66,0x3c,0x18,0x18,0x18,0x00,-- Y
0x7e,0x06,0x0c,0x18,0x30,0x60,0x7e,0x00 -- Z
charset(" abcdefghijklmnopqrstuvwxyz", \x(x*8))
local normtxt = function(txt)
txt = txt:lower()
local out = {}
for i=1,#txt do
local c = string.byte(txt:sub(i,i))
if c < string.byte('a') or c > string.byte('z') then c = string.byte(' ') end
table.insert(out, string.char(c))
end
local ex = 12 - #out
for i=1,ex do
if i&1 ~= 0 then table.insert(out, 1, ' ')
else table.insert(out, ' ')
end
end
return table.concat(out)
end
@@song_name byte(normtxt(zic.name))
@@song_author byte(normtxt(zic.author))
local print_txt = tmp+1 -- text pointer, can cross
local print_line_count=tmp -- number of lines to print
local print_ptr = tmp+3 -- array of pointers to the characters
-- a = line count
@@print12
sta print_line_count
lda#6 sta NUSIZ0 sta NUSIZ1
-- set MSB of font character addresses
lda#font>>8 ldx#23 @_loadfont sta print_ptr,x dex dex bpl _loadfont
-- position sprites
samepage
sta WSYNC
ldx#6 @_delay dex bne _delay
sta RESP0 nop sta RESP1 lda#0x70 sta HMP0 lda#0x60 sta HMP1 sta WSYNC sta HMOVE
end
@_loop
-- load text line
ldx#0 ldy#0 @_loadline lda (print_txt),y sta print_ptr,x inx inx iny cpy#12 bne _loadline
lda#0x80 sta HMP0 sta HMP1
ldy#0 samepage @_printline
sta WSYNC sta HMOVE
-- first scanline
lda (print_ptr+2),y sta GRP0 lda (print_ptr+6),y sta GRP1
lda (print_ptr+22),y tax sleep(6)
lda (print_ptr+10),y sta GRP0 lda (print_ptr+14),y sta GRP1 lda (print_ptr+18),y sta GRP0 stx GRP1
sta HMCLR sleep(8) sta HMOVE
-- second scanline
lda (print_ptr),y sta GRP0 lda (print_ptr+4),y sta GRP1
lda (print_ptr+20),y tax lda#0x80 sta HMP0 sta HMP1 nop
lda (print_ptr+8),y sta GRP0 lda (print_ptr+12),y sta GRP1 lda (print_ptr+16),y sta GRP0 stx GRP1
iny cpy#8 bne _printline
end
dec print_line_count beq _end
lda print_txt clc adc#12 sta print_txt lda print_txt+1 adc#0 sta print_txt+1
jmp _loop
@_end
lda#0 sta GRP0 sta GRP1
rts
----------------------------------------------------------------------
-- equalizer anim
----------------------------------------------------------------------
-- convert to freq: http://atariage.com/forums/topic/257526-musicsound-programming-question-atari-2600/
-- maps AUDCi to 0-7 3 bits (<<5) value for wave length of: 1, 2, 6, 15, 31, 93, 465, 511
@@wavlen samepage
dc.b 0x00,0x60,0xC0,0xC0,0x20,0x20,0x80,0x80,
0xF0,0x80,0x80,0x00,0x40,0x40,0xA0,0xA0
end
local wavelen_map = { 1, 2, 6, 15, 31, 93, 465, 511 }
local freq_ranges = { 30, 60, 120, 240, 480, 960, 1920, 9999999 }
local t = {}
for i=0,255 do
local wavelen = wavelen_map[(i>>5)+1]
local note = (i&31)+1
local freq = 3546894 / 114 / wavelen / note
for x=1,9 do
if freq <= freq_ranges[x] then
t[#t+1] = x-1
break
end
end
end
section{'regfreq', align=256} byte(t)
@@vm_pf2 samepage
dc.b 0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
end
@@vm_pf1 samepage
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F
end
@@vm_col samepage
dc.b 0x3C,0x7C,0x9C,0xDC,0x2C,0x4C,0x6C,0xAC
end
local vumeter_tick = function()
lda #0 ldy #15 @_vudec ldx vubars,y dex bmi _vudecskip stx vubars,y @_vudecskip dey bpl _vudec
lda AUDF0s ldy AUDC0s ora wavlen,y tax ldy regfreq,x
lda AUDV0s cmp vubars,y bcc _vuless0 sta vubars,y @_vuless0
lda AUDF1s ldy AUDC1s ora wavlen,y tax ldy regfreq,x
lda AUDV1s cmp vubars+8,y bcc _vuless1 sta vubars+8,y @_vuless1
end
local vumeter_draw = function()
lda #ctrlpf.PF_MIRRORED sta CTRLPF
ldy #7
@_vudraw
samepage
lda #16 sta tmp
@_vudraw1
sta WSYNC
lda vm_col,y sta COLUPF sleep(4)
ldx vubars,y
lda vm_pf1,x sta PF1
lda vm_pf2,x sta PF2
sleep(8)
ldx vubars+8,y
lda vm_pf2,x sta PF2
lda vm_pf1,x sta PF1
dec tmp bpl _vudraw1
sta WSYNC lda #0 sta PF2 sta PF1
sta WSYNC
dey bpl _vudraw
end
lda #0 sta CTRLPF
sta WSYNC sta WSYNC
end
local tick = function()
zic_tick()
-- clamp to valid TIA range
lda AUDC0s ;and #0x0f sta AUDC0s lda AUDC1s ;and #0x0f sta AUDC1s
lda AUDF0s ;and #0x1f sta AUDF0s lda AUDF1s ;and #0x1f sta AUDF1s
lda AUDV0s ;and #0x0f sta AUDV0s lda AUDV1s ;and #0x0f sta AUDV1s
vumeter_tick()
end
local kernel = function()
lda#0x8a sta COLUP0 sta COLUP1
setptr(song_author,print_txt) lda#1 jsr print12
vumeter_draw()
lda#0xaa sta COLUP0 sta COLUP1
setptr(song_name,print_txt) lda#1 jsr print12
end
@@main
init()
zic_init()
@_frame
overscan() vblank(tick) screen(kernel) jmp _frame
; -- needed if last instruction is implied
writebin(filename..'.bin')
writesym(filename..'.sym')
print(stats)
require'vcs'
mappers['2K']()
local AUDC0s = 0x90
local AUDC1s, AUDF0s, AUDF1s, AUDV0s, AUDV1s = AUDC0s+1, AUDC0s+2, AUDC0s+3, AUDC0s+4, AUDC0s+5
local vubars = 0xA0
local tmp = 0xB0
local zic_filename = ... or 'Ishtar.ttt'
local zic = ttt(zic_filename)
print(string.format('Using file %s (ttt version %d)\n Name: %s\n Author: %s\n%s', zic_filename, zic.version, zic.name, zic.author, zic.comment))
-- debug printing of zic data
--[[
local printbytetable = function(v)
local s = ' '
for kk,vv in ipairs(v) do
s = s .. string.format('%02x, ', vv)
if #s >= 34 then print(s) s = ' ' end
end
if #s > 0 then print(s) end
end
for k,v in pairs(zic) do
if type(v) ~= 'table' then
print(k,v)
elseif type(v[1]) == 'number' then
print(k) printbytetable(v)
elseif type(v[1]) == 'table' then
for ek, e in ipairs(v) do
print(k, ek, e.name) printbytetable(e)
end
end
end
]]
@@tt_InsCtrlTable byte(zic.insctrltable)
@@tt_InsADIndexes byte(zic.insadindexes)
@@tt_InsSustainIndexes byte(zic.inssustainindexes)
@@tt_InsReleaseIndexes byte(zic.insreleaseindexes)
@@tt_InsFreqVolTable byte(zic.insfreqvoltable)
@@tt_PercIndexes byte(zic.percindexes)
@@tt_PercFreqTable byte(zic.percfreqtable)
@@tt_PercCtrlVolTable byte(zic.percctrlvoltable)
local patterns = {}
for i,pattern in ipairs(zic.patterns) do
table.insert(patterns, section("pattern"..i)) byte(pattern)
end
@@tt_PatternSpeeds byte(zic.patternspeeds)
@@tt_PatternPtrLo byte_lo(patterns)
@@tt_PatternPtrHi byte_hi(patterns)
@@tt_SequenceTable byte(zic.sequence[1]) byte(zic.sequence[2])
local tt = {
-- =====================================================================
-- Permanent variables. These are states needed by the player.
-- =====================================================================
'timer', -- current music timer value
'cur_pat_index_c0', -- current pattern index into tt_SequenceTable
'cur_pat_index_c1',
'cur_note_index_c0', -- note index into current pattern
'cur_note_index_c1',
'envelope_index_c0', -- index into ADSR envelope
'envelope_index_c1',
'cur_ins_c0', -- current instrument
'cur_ins_c1',
-- =====================================================================
-- Temporary variables. These will be overwritten during a call to the
-- player routine, but can be used between calls for other things.
-- =====================================================================
'ptr', -- 2 bytes
}
for k,v in ipairs(tt) do tt[v] = k + 0x7F end
tt.FREQ_MASK = 0b00011111
tt.INS_HOLD = 8
tt.INS_PAUSE = 16
tt.FIRST_PERC = 17
tt.fetchCurrentNoteImpl = function()
@_constructPatPtr
ldy tt.cur_pat_index_c0,x lda tt_SequenceTable,y
if zic.usegoto then
bpl _noPatternGoto
;and #0b01111111 sta tt.cur_pat_index_c0,x bpl _constructPatPtr
@_noPatternGoto
end
tay lda tt_PatternPtrLo,y sta tt.ptr lda tt_PatternPtrHi,y sta tt.ptr+1
if zic.overlay then
clv
lda tt.cur_note_index_c0,x bpl _notPrefetched
;and #0b01111111 sta tt.cur_note_index_c0,x
bit tt_Bit6Set
@_notPrefetched
tay
else
ldy tt.cur_note_index_c0,x
end
lda (tt.ptr),y bne _noEndOfPattern
sta tt.cur_note_index_c0,x
inc tt.cur_pat_index_c0,x
bne _constructPatPtr
@_noEndOfPattern
end
if zic.useoverlay then
@@tt_fetchCurrentNoteSection tt.fetchCurrentNoteImpl() rts
tt.fetchCurrentNote = function() jsr tt_fetchCurrentNoteSection end
else
tt.fetchCurrentNote = tt.fetchCurrentNoteImpl
end
@@tt_CalcInsIndex
lsr lsr lsr lsr lsr
tay
@tt_Bit6Set
rts
local function zic_tick()
dec tt.timer bpl _noNewNote
ldx #1
@_advanceLoop
tt.fetchCurrentNote()
cmp #tt.INS_PAUSE
if zic.useslide then
beq _pause
bcs _newNote
adc tt.cur_ins_c0,x sec sbc #8 sta tt.cur_ins_c0,x
bcs _finishedNewNote
else
bcc _finishedNewNote
bne _newNote
end
@_pause
lda tt.cur_ins_c0,x jsr tt_CalcInsIndex
lda tt_InsReleaseIndexes-1,y
clc adc #1 bcc _storeADIndex
@_newNote
sta tt.cur_ins_c0,x
cmp #tt.FREQ_MASK+1 bcs _startInstrument
tay
lda tt_PercIndexes-tt.FIRST_PERC,y
bne _storeADIndex
@_startInstrument
if zic.useoverlay then
bvs _finishedNewNote
end
jsr tt_CalcInsIndex
lda tt_InsADIndexes-1,y
@_storeADIndex
sta tt.envelope_index_c0,x
@_finishedNewNote
inc tt.cur_note_index_c0,x
@_sequencerNextChannel
dex
bpl _advanceLoop
if zic.globalspeed then
ldx #zic.speed-1
if zic.usefunktempo then
lda tt.cur_note_index_c0 lsr bcc _noOddFrame ldx #zic.oddspeed-1 @_noOddFrame
end
stx tt.timer
else
ldx tt.cur_pat_index_c0 ldy tt_SequenceTable,x
if zic.usefunktempo then
lda tt.cur_note_index_c0 lsr
lda tt_PatternSpeeds,y
bcc _evenFrame
;and #0x0f
bcs _storeFunkTempo
@_evenFrame
lsr lsr lsr lsr
@_storeFunkTempo
sta tt.timer
else
lda tt_PatternSpeeds,y sta tt.timer
end
end
@_noNewNote
ldx #1
@_updateLoop
lda tt.cur_ins_c0,x
if not zic.startswithnotes then
beq _afterAudioUpdate
end
cmp #tt.FREQ_MASK+1 bcs _instrument
ldy tt.envelope_index_c0,x
lda tt_PercCtrlVolTable-1,y beq _endOfPercussion inc tt.envelope_index_c0,x @_endOfPercussion
sta AUDV0,x sta AUDV0s,x lsr lsr lsr lsr sta AUDC0,x sta AUDC0s,x
lda tt_PercFreqTable-1,y
sta AUDF0,x
sta AUDF0s,x -- EXTRA for vumeter rendering
if zic.useoverlay then
bpl _afterAudioUpdate
tt.fetchCurrentNote()
cmp #tt.FREQ_MASK+1
bcc _afterAudioUpdate
sta tt.cur_ins_c0,x
jsr tt_CalcInsIndex
lda tt_InsSustainIndexes-1,y sta tt.envelope_index_c0,x
asl tt.cur_note_index_c0,x sec ror tt.cur_note_index_c0,x
bmi _afterAudioUpdate
else
jmp _afterAudioUpdate
end
@_instrument
jsr tt_CalcInsIndex
lda tt_InsCtrlTable-1,y sta AUDC0,x
sta AUDC0s,x -- EXTRA for vumeter rendering
lda tt.envelope_index_c0,x cmp tt_InsReleaseIndexes-1,y bne _noEndOfSustain lda tt_InsSustainIndexes-1,y @_noEndOfSustain
tay
lda tt_InsFreqVolTable,y beq _endOfEnvelope iny @_endOfEnvelope
sty tt.envelope_index_c0,x
sta AUDV0,x
sta AUDV0s,x -- EXTRA for vumeter rendering
lsr lsr lsr lsr clc adc tt.cur_ins_c0,x sec sbc #8
sta AUDF0,x
sta AUDF0s,x -- EXTRA for vumeter rendering
@_afterAudioUpdate
dex
bpl _updateLoop
end
local function zic_init()
if zic.c0init ~= 0 then lda#zic.c0init sta tt.cur_pat_index_c0 end
if zic.c1init ~= 0 then lda#zic.c1init sta tt.cur_pat_index_c1 end
end
----------------------------------------------------------------------
-- display song name and author
----------------------------------------------------------------------
section{ "font", align=256 } dc.b
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,-- <SPC>
0x18,0x3c,0x66,0x7e,0x66,0x66,0x66,0x00,-- A
0x7c,0x66,0x66,0x7c,0x66,0x66,0x7c,0x00,-- B
0x3c,0x66,0x60,0x60,0x60,0x66,0x3c,0x00,-- C
0x78,0x6c,0x66,0x66,0x66,0x6c,0x78,0x00,-- D
0x7e,0x60,0x60,0x78,0x60,0x60,0x7e,0x00,-- E
0x7e,0x60,0x60,0x78,0x60,0x60,0x60,0x00,-- F
0x3c,0x66,0x60,0x6e,0x66,0x66,0x3c,0x00,-- G
0x66,0x66,0x66,0x7e,0x66,0x66,0x66,0x00,-- H
0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00,-- I
0x1e,0x0c,0x0c,0x0c,0x0c,0x6c,0x38,0x00,-- J
0x66,0x6c,0x78,0x70,0x78,0x6c,0x66,0x00,-- K
0x60,0x60,0x60,0x60,0x60,0x60,0x7e,0x00,-- L
0x63,0x77,0x7f,0x6b,0x63,0x63,0x63,0x00,-- M
0x66,0x76,0x7e,0x7e,0x6e,0x66,0x66,0x00,-- N
0x3c,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- O
0x7c,0x66,0x66,0x7c,0x60,0x60,0x60,0x00,-- P
0x3c,0x66,0x66,0x66,0x66,0x3c,0x0e,0x00,-- Q
0x7c,0x66,0x66,0x7c,0x78,0x6c,0x66,0x00,-- R
0x3c,0x66,0x60,0x3c,0x06,0x66,0x3c,0x00,-- S
0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00,-- T
0x66,0x66,0x66,0x66,0x66,0x66,0x3c,0x00,-- U
0x66,0x66,0x66,0x66,0x66,0x3c,0x18,0x00,-- V
0x63,0x63,0x63,0x6b,0x7f,0x77,0x63,0x00,-- W
0x66,0x66,0x3c,0x18,0x3c,0x66,0x66,0x00,-- X
0x66,0x66,0x66,0x3c,0x18,0x18,0x18,0x00,-- Y
0x7e,0x06,0x0c,0x18,0x30,0x60,0x7e,0x00 -- Z
charset(" abcdefghijklmnopqrstuvwxyz", \x(x*8))
local normtxt = function(txt)
txt = txt:lower()
local out = {}
for i=1,#txt do
local c = string.byte(txt:sub(i,i))
if c < string.byte('a') or c > string.byte('z') then c = string.byte(' ') end
table.insert(out, string.char(c))
end
local ex = 12 - #out
for i=1,ex do
if i&1 ~= 0 then table.insert(out, 1, ' ')
else table.insert(out, ' ')
end
end
return table.concat(out)
end
@@song_name byte(normtxt(zic.name))
@@song_author byte(normtxt(zic.author))
local print_txt = tmp+1 -- text pointer, can cross
local print_line_count=tmp -- number of lines to print
local print_ptr = tmp+3 -- array of pointers to the characters
-- a = line count
@@print12
sta print_line_count
lda#6 sta NUSIZ0 sta NUSIZ1
-- set MSB of font character addresses
lda#font>>8 ldx#23 @_loadfont sta print_ptr,x dex dex bpl _loadfont
-- position sprites
samepage
sta WSYNC
ldx#6 @_delay dex bne _delay
sta RESP0 nop sta RESP1 lda#0x70 sta HMP0 lda#0x60 sta HMP1 sta WSYNC sta HMOVE
end
@_loop
-- load text line
ldx#0 ldy#0 @_loadline lda (print_txt),y sta print_ptr,x inx inx iny cpy#12 bne _loadline
lda#0x80 sta HMP0 sta HMP1
ldy#0 samepage @_printline
sta WSYNC sta HMOVE
-- first scanline
lda (print_ptr+2),y sta GRP0 lda (print_ptr+6),y sta GRP1
lda (print_ptr+22),y tax sleep(6)
lda (print_ptr+10),y sta GRP0 lda (print_ptr+14),y sta GRP1 lda (print_ptr+18),y sta GRP0 stx GRP1
sta HMCLR sleep(8) sta HMOVE
-- second scanline
lda (print_ptr),y sta GRP0 lda (print_ptr+4),y sta GRP1
lda (print_ptr+20),y tax lda#0x80 sta HMP0 sta HMP1 nop
lda (print_ptr+8),y sta GRP0 lda (print_ptr+12),y sta GRP1 lda (print_ptr+16),y sta GRP0 stx GRP1
iny cpy#8 bne _printline
end
dec print_line_count beq _end
lda print_txt clc adc#12 sta print_txt lda print_txt+1 adc#0 sta print_txt+1
jmp _loop
@_end
lda#0 sta GRP0 sta GRP1
rts
----------------------------------------------------------------------
-- equalizer anim
----------------------------------------------------------------------
-- convert to freq: http://atariage.com/forums/topic/257526-musicsound-programming-question-atari-2600/
-- maps AUDCi to 0-7 3 bits (<<5) value for wave length of: 1, 2, 6, 15, 31, 93, 465, 511
@@wavlen samepage
dc.b 0x00,0x60,0xC0,0xC0,0x20,0x20,0x80,0x80,
0xF0,0x80,0x80,0x00,0x40,0x40,0xA0,0xA0
end
local wavelen_map = { 1, 2, 6, 15, 31, 93, 465, 511 }
local freq_ranges = { 30, 60, 120, 240, 480, 960, 1920, 9999999 }
local t = {}
for i=0,255 do
local wavelen = wavelen_map[(i>>5)+1]
local note = (i&31)+1
local freq = 3546894 / 114 / wavelen / note
for x=1,9 do
if freq <= freq_ranges[x] then
t[#t+1] = x-1
break
end
end
end
section{'regfreq', align=256} byte(t)
@@vm_pf2 samepage
dc.b 0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
end
@@vm_pf1 samepage
dc.b 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F
end
@@vm_col samepage
dc.b 0x3C,0x7C,0x9C,0xDC,0x2C,0x4C,0x6C,0xAC
end
local vumeter_tick = function()
lda #0 ldy #15 @_vudec ldx vubars,y dex bmi _vudecskip stx vubars,y @_vudecskip dey bpl _vudec
lda AUDF0s ldy AUDC0s ora wavlen,y tax ldy regfreq,x
lda AUDV0s cmp vubars,y bcc _vuless0 sta vubars,y @_vuless0
lda AUDF1s ldy AUDC1s ora wavlen,y tax ldy regfreq,x
lda AUDV1s cmp vubars+8,y bcc _vuless1 sta vubars+8,y @_vuless1
end
local vumeter_draw = function()
lda #ctrlpf.PF_MIRRORED sta CTRLPF
ldy #7
@_vudraw
samepage
lda #16 sta tmp
@_vudraw1
sta WSYNC
lda vm_col,y sta COLUPF sleep(4)
ldx vubars,y
lda vm_pf1,x sta PF1
lda vm_pf2,x sta PF2
sleep(8)
ldx vubars+8,y
lda vm_pf2,x sta PF2
lda vm_pf1,x sta PF1
dec tmp bpl _vudraw1
sta WSYNC lda #0 sta PF2 sta PF1
sta WSYNC
dey bpl _vudraw
end
lda #0 sta CTRLPF
sta WSYNC sta WSYNC
end
local tick = function()
zic_tick()
-- clamp to valid TIA range
lda AUDC0s ;and #0x0f sta AUDC0s lda AUDC1s ;and #0x0f sta AUDC1s
lda AUDF0s ;and #0x1f sta AUDF0s lda AUDF1s ;and #0x1f sta AUDF1s
lda AUDV0s ;and #0x0f sta AUDV0s lda AUDV1s ;and #0x0f sta AUDV1s
vumeter_tick()
end
local kernel = function()
lda#0x8a sta COLUP0 sta COLUP1
setptr(song_author,print_txt) lda#1 jsr print12
vumeter_draw()
lda#0xaa sta COLUP0 sta COLUP1
setptr(song_name,print_txt) lda#1 jsr print12
end
@@main
init()
zic_init()
@_frame
overscan() vblank(tick) screen(kernel) jmp _frame
; -- needed if last instruction is implied
writebin(filename..'.bin')
writesym(filename..'.sym')
print(stats)

3
scv.l7801 Normal file
View File

@ -0,0 +1,3 @@
cpu = require 'uPD7801'
setmetatable(_ENV, cpu)

638
uPD7801.lua Normal file
View File

@ -0,0 +1,638 @@
M = require "asm"
local op_eval_byte = function(late, early) return M.byte_normalize(M.op_eval(late, early)) end
M.op_eval_byte = op_eval_byte
local op_eval_word = function(late, early) return M.word_normalize(M.op_eval(late, early)) end
M.op_eval_word = op_eval_word
local opimp={
block=M.op(0x31,13),
calb=M.op(0x63,13),
daa=M.op(0x61,4),
ex=M.op(0x10,4),
exx=M.op(0x11,4),
halt=M.op(0x01,6),
jb=M.op(0x73,4),
nop=M.op(0x00,4),
ret=M.op(0x08,11),
reti=M.op(0x62,15),
rets=M.op(0x18,11),
sio=M.op(0x09,4),
softi=M.op(0x72,19),
stm=M.op(0x19,4),
['table']=M.op(0x21,19)
} M.opimp = opimp
for k,v in pairs(opimp) do
M[k .. 'imp' ] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opa={
dcr=M.op(0x51,4),
inr=M.op(0x41,4),
} M.opa = opa
for k,v in pairs(opa) do
M[k .. 'a'] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opb={
dcr=M.op(0x52,4),
inr=M.op(0x42,4),
} M.opb = opb
for k,v in pairs(opb) do
M[k .. 'b'] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opc={
dcr=M.op(0x53,4),
inr=M.op(0x43,4),
} M.opc = opc
for k,v in pairs(opc) do
M[k .. 'c'] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opsp={
dcx=M.op(0x03,7),
inx=M.op(0x02,7)
} M.opsp = opsp
for k,v in pairs(opsp) do
M[k .. 'sp'] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opr16={
dcxbc=M.op(0x13,7),
dcxde=M.op(0x23,7),
dcxhl=M.op(0x33,7),
inxbc=M.op(0x12,7),
inxde=M.op(0x22,7),
inxhl=M.op(0x32,7),
ldaxbc=M.op(0x29,7),
ldaxdde=M.op(0x2e,7),
ldaxde=M.op(0x2a,7),
ldaxhl=M.op(0x2b,7),
ldaxide=M.op(0x2c,7),
ldaxdhl=M.op(0x2f,7),
ldaxihl=M.op(0x2d,7),
staxbc=M.op(0x39,7),
staxde=M.op(0x3a,7),
staxdde=M.op(0x3e,7),
staxdhl=M.op(0x3f,7),
staxhl=M.op(0x3b,7),
staxide=M.op(0x3c,7),
staxihl=M.op(0x3d,7),
} M.opr16 = opr16
for k,v in pairs(opr16) do
M[k] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opregxx ={
mvib=M.op(0x6a,7),
mvic=M.op(0x6b,7),
mvid=M.op(0x6c,7),
mvie=M.op(0x6d,7),
mvih=M.op(0x6e,7),
mvil=M.op(0x6f,7),
mviv=M.op(0x68,7),
mvixbc=M.op(0x49,10),
mvixde=M.op(0x4a,10),
mvixhl=M.op(0x4b,10),
} M.opregxx = opregxx
for k,v in pairs(opregxx) do
M[k] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 2 end
local bin = function() local l7801dbg=l7801dbg return { v.opc, M.op_eval_byte(late,early) } end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local opaxx ={
aci=M.op(0x56,7),
adi=M.op(0x46,7),
adinc=M.op(0x26,7),
ani=M.op(0x07,7),
eqi=M.op(0x77,7),
gti=M.op(0x27,7),
lti=M.op(0x37,7),
mvi=M.op(0x69,7),
nei=M.op(0x67,7),
offi=M.op(0x57,7),
oni=M.op(0x47,7),
ori=M.op(0x17,7),
sbi=M.op(0x76,7),
sui=M.op(0x66,7),
suinb=M.op(0x36,7),
xri=M.op(0x16,7)
} M.opaxx = opaxx
for k,v in pairs(opaxx) do
M[k .. 'a'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 2 end
local bin = function() local l7801dbg=l7801dbg return { v.opc, M.op_eval_byte(late,early) } end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local opr8r8 ={
movab=M.op(0x0a,4), movac=M.op(0x0b,4),
movad=M.op(0x0c,4), movae=M.op(0x0d,4),
movah=M.op(0x0e,4), moval=M.op(0x0f,4),
movba=M.op(0x1a,4), movca=M.op(0x1b,4),
movda=M.op(0x1c,4), movea=M.op(0x1d,4),
movha=M.op(0x1e,4), movla=M.op(0x1f,4),
} M.opr8r8 = opr8r8
for k,v in pairs(opr8r8) do
M[k] = function()
table.insert(M.section_current.instructions, { size=1, cycles=v.cycles, bin=v.opc })
end
end
local opw = {
call=M.op(0x44,16),
jmp=M.op(0x54,10),
} M.opw = opw
for k,v in pairs(opw) do
M[k .. 'imm'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 3 end
local bin = function() local l7801dbg=l7801dbg
local x = M.op_eval_word(late,early)
return { v.opc, x&0xff, x>>8 }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local opr16w = {
lxisp=M.op(0x04,10),
lxibc=M.op(0x14,10),
lxide=M.op(0x24,10),
lxihl=M.op(0x34,10)
} M.opr16w = opr16w
for k,v in pairs(opr16w) do
M[k] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 3 end
local bin = function() local l7801dbg=l7801dbg
local x = M.op_eval_word(late,early)
return { v.opc, x&0xff, x>>8 }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
M['calt' .. 'imm'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local op = { cycles=19 }
op.size = function() late,early = M.size_op(late,early) return 1 end
op.bin = function()
local l7801dbg=l7801dbg
local x = M.op_eval_byte(late,early)
if (x%2 == 1) then error("offset should be even : " .. x) end
if x < 0x80 or x > 0xfe then error("offset out of range : " .. x) end
x = (x>>1) + 0x40
return x
end
table.insert(M.section_current.instructions, op)
end
M['calf' .. 'imm'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local op = { cycles=16 }
op.size = function() late,early = M.size_op(late,early) return 2 end
op.bin = function() local l7801dbg=l7801dbg
local x = 0 + M.op_eval_word(late,early)
if x < 0x0800 or x > 0xffff then error("subroutine address out of range [0x0800-0xffff]: " .. x) end
x = x - 0x0800;
return { 0x78 | ((x>>8) & 0x07), x&0xff }
end
table.insert(M.section_current.instructions, op)
end
M.jr = function(label)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local parent,offset = M.label_current
local section,rorg = M.section_current,M.location_current.rorg
local op = { cycles=13 }
op.size = function()
offset = section.size
label = M.size_dc(label)
return 1
end
op.bin = function()
local l7801dbg=l7801dbg
local x,l = label,label
if type(x) == 'function' then x=x() end
if type(x) == 'string' then
if x:sub(1,1) == '_' then x=parent..x l=x end
x = symbols[x]
end
if type(x) ~= 'number' then error("unresolved branch target: " .. tostring(x)) end
x = x-1 - offset - rorg(section.org)
if x < -32 or x > 32 then error("branch target out of range for " .. l .. ": " .. x)
elseif x >= 0 then
x = 0xc0 + x
return x
else
return x & 0xff
end
end
table.insert(M.section_current.instructions, op)
end
M.jre = function(label)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local parent,offset = M.label_current
local section,rorg = M.section_current,M.location_current.rorg
local op = { cycles=17 }
op.size = function()
offset = section.size
label = M.size_dc(label)
return 2
end
op.bin = function()
local l7801dbg=l7801dbg
local x,l = label,label
if type(x) == 'function' then x=x() end
if type(x) == 'string' then
if x:sub(1,1) == '_' then x=parent..x l=x end
x = symbols[x]
end
if type(x) ~= 'number' then error("unresolved branch target: " .. tostring(x)) end
x = x-2 - offset - rorg(section.org)
if x < -128 or x > 127 then error("branch target out of range for " .. l .. ": " .. x) end
local opcode = x >= 0 and 0x4e or 0x4f
return { opcode, x&0xff }
end
table.insert(M.section_current.instructions, op)
end
local opwa = {
inrw=M.op(0x20,13),
ldaw=M.op(0x28,10),
dcrw=M.op(0x30,13),
staw=M.op(0x38,10),
bit0=M.op(0x58,10),
bit1=M.op(0x59,10),
bit2=M.op(0x5a,10),
bit3=M.op(0x5b,10),
bit4=M.op(0x5c,10),
bit5=M.op(0x5d,10),
bit6=M.op(0x5e,10),
bit7=M.op(0x5f,10)
} M.opwa = opwa
for k,v in pairs(opwa) do
M[k .. 'wa'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 2 end
local bin = function() local l7801dbg=l7801dbg return { v.opc, M.op_eval_byte(late,early) } end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local opwaxx = {
mviw=M.op(0x71,3,13),
eqiw=M.op(0x75,3,13),
} M.opwaxx = opwaxx
for k,v in pairs(opwaxx) do
M[k .. 'waxx'] = function(late_offset, late_data, early_offset, early_data)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function()
late_offset,early_offset = M.size_op(late_offset,early_offset)
late_data,early_data = M.size_op(late_data,early_data)
return 3
end
local bin = function()
local l7801dbg=l7801dbg
return { v.opc, M.op_eval_byte(late_offset,early_offset), M.op_eval_byte(late_data,early_data) }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local op48imp = {
ei=M.op(0x20,8),
di=M.op(0x24,8),
clc=M.op(0x2a,8),
pen=M.op(0x2c,11),
per=M.op(0x3c,11),
pex=M.op(0x2d,11),
rld=M.op(0x38,17),
rrd=M.op(0x39,17),
skc=M.op(0x0a,8),
skz=M.op(0x0c,8),
sknc=M.op(0x1a,8),
sknz=M.op(0x1c,8),
stc=M.op(0x2b,8),
} M.op48imp = op48imp
for k,v in pairs(op48imp) do
M[k .. 'imp'] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x48, v.opc } })
end
end
local op48r8={
rlla=M.op(0x30,8),
rlra=M.op(0x31,8),
rllc=M.op(0x32,8),
rlrc=M.op(0x33,8),
slla=M.op(0x34,8),
slra=M.op(0x35,8),
sllc=M.op(0x36,8),
slrc=M.op(0x37,8),
} M.op48r8 = op48r8
for k,v in pairs(op48r8) do
M[k] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x48, v.opc } })
end
end
local op48r16={
pushbc=M.op(0x1e,17),
pushde=M.op(0x2e,17),
pushhl=M.op(0x3e,17),
pushva=M.op(0x0e,17),
popbc=M.op(0x1f,15),
popde=M.op(0x2f,15),
pophl=M.op(0x3f,15),
popva=M.op(0x0f,15),
} M.op48r16 = op48r16
for k,v in pairs(op48r16) do
M[k] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x48, v.opc } })
end
end
local op48int={
skitf0=M.op(0x00,8),
skitft=M.op(0x01,8),
skitf1=M.op(0x02,8),
skitf2=M.op(0x03,8),
skitfs=M.op(0x04,8),
sknitf0=M.op(0x10,8),
sknitft=M.op(0x11,8),
sknitf1=M.op(0x12,8),
sknitf2=M.op(0x13,8),
sknitfs=M.op(0x14,8),
} M.op48int = op48int
for k,v in pairs(op48int) do
M[k] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x48, v.opc } })
end
end
-- IN/OUT
local opinout={
['in']=M.op(0x4c,10),
out=M.op(0x4d,10),
} M.opinout = op4inout
for k,v in pairs(opinout) do
M[k .. 'imm'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local op = { cycles=v.cycles }
op.size = function() late,early = M.size_op(late,early) return 2 end
op.bin = function() local l7801dbg=l7801dbg
local x = 0x00 + M.op_eval_byte(late,early)
return { v.opc, 0x00, x }
end
table.insert(M.section_current.instructions, op)
end
end
local op4car8={
movapa=M.op(0xc0,10),
movapb=M.op(0xc1,10),
movapc=M.op(0xc2,10),
movamk=M.op(0xc3,10),
movamb=M.op(0xc4,10),
movamc=M.op(0xc5,10),
movatm0=M.op(0xc6,10),
movatm1=M.op(0xc7,10),
movas=M.op(0xc8,10),
} M.op4car8 = op4car8
for k,v in pairs(op4car8) do
M[k] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x4c, v.opc } })
end
end
local op4dr8a={
movpaa=M.op(0xc0,10),
movpba=M.op(0xc1,10),
movpca=M.op(0xc2,10),
movmka=M.op(0xc3,10),
movmba=M.op(0xc4,10),
movmca=M.op(0xc5,10),
movtm0a=M.op(0xc6,10),
movtm1a=M.op(0xc7,10),
movsa=M.op(0xc8,10),
} M.op4dr8a = op4dr8a
for k,v in pairs(op4dr8a) do
M[k] = function()
table.insert(M.section_current.instructions, { size=2, cycles=v.cycles, bin={ 0x4d, v.opc } })
end
end
local op60names = {'ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa'}
local register_names = {'v','a','b','c','d','e','h','l'}
local k = 0x08
for i,o in ipairs(op60names) do
if o == '' then
k = k + #register_names
else
for j,r in ipairs(register_names) do
local l = k
M[o .. r .. 'a'] = function()
table.insert(M.section_current.instructions, { size=2, cycles=8, bin={ 0x60, l } })
end
k = k + 1
end
end
end
k = 0x88
op60names[9] = 'ona'
op60names[11] = 'offa'
for i,o in ipairs(op60names) do
if o == '' then
k = k + #register_names
else
for j,r in ipairs(register_names) do
local l = k
local name = o .. 'a' .. r
if not M[name] then
M[name] = function()
table.insert(M.section_current.instructions, { size=2, cycles=8, bin={ 0x60, l } })
end
end
k = k + 1
end
end
end
k = 0x08
local op64names = { 'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi' }
for i,o in ipairs(op64names) do
for j,r in ipairs(register_names) do
local name = o .. r
if not M[name] then
local l = k
M[name] = function(late,early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local op = { cycles=11 }
op.size = function() late,early = M.size_op(late,early) return 3 end
op.bin = function() local l7801dbg=l7801dbg
local x = 0x00 + l;
local y = 0x00 + M.op_eval_byte(late,early)
return { 0x64, x, y }
end
table.insert(M.section_current.instructions, op)
end
end
k = k + 1
end
end
k = 0x88
local ex_register_names = {'pa','pb','pc','mk'}
for i,o in ipairs(op64names) do
for j,r in ipairs(ex_register_names) do
local name = o .. r
if not M[name] then
local l = k
M[name] = function(late,early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local op = { cycles=11 }
op.size = function() late,early = M.size_op(late,early) return 3 end
op.bin = function() local l7801dbg=l7801dbg
local x = 0x00 + l;
local y = 0x00 + M.op_eval_byte(late,early)
return { 0x64, x, y }
end
table.insert(M.section_current.instructions, op)
end
end
k = k + 1
end
k = k + 4
end
local op74wa = {
anaw=M.op(0x88,14),
xraw=M.op(0x90,14),
oraw=M.op(0x98,14),
addncw=M.op(0xa0,14),
gtaw=M.op(0xa8,14),
subnbw=M.op(0xb0,14),
ltaw=M.op(0xb8,14),
addw=M.op(0xc0,14),
onaw=M.op(0xc8,14),
adcw=M.op(0xd0,14),
offaw=M.op(0xd8,14),
subw=M.op(0xe0,14),
neaw=M.op(0xe8,14),
sbbw=M.op(0xf0,14),
eqaw=M.op(0xf8,14),
} M.op74wa = op74wa
for k,v in pairs(op74wa) do
M[k .. 'wa'] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 3 end
local bin = function() local l7801dbg=l7801dbg return { 0x74, v.opc, M.op_eval_byte(late,early) } end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local op70ind = {
sspd=M.op(0x0e,20),
lspd=M.op(0x0f,20),
sbcd=M.op(0x1e,20),
lbcd=M.op(0x1f,20),
sded=M.op(0x2e,20),
lded=M.op(0x2f,20),
shld=M.op(0x3e,20),
lhld=M.op(0x3f,20),
} M.op70ind = op70ind
for k,v in pairs(op70ind) do
M[k] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 4 end
local bin = function() local l7801dbg=l7801dbg
local x = M.op_eval_word(late,early)
return { 0x70, v.opc, x&0xff, x>>8 }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local op70indr8 = {
movindv=M.op(0x78,17),
movinda=M.op(0x79,17),
movindb=M.op(0x7a,17),
movindc=M.op(0x7b,17),
movindd=M.op(0x7c,17),
movinde=M.op(0x7d,17),
movindh=M.op(0x7e,17),
movindl=M.op(0x7f,17),
} M.op70indr8 = op70indr8
for k,v in pairs(op70indr8) do
M[k] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 4 end
local bin = function() local l7801dbg=l7801dbg
local x = M.op_eval_word(late,early)
return { 0x70, v.opc, x&0xff, x>>8 }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
local op70r8ind = {
movvind=M.op(0x68,17),
movaind=M.op(0x69,17),
movbind=M.op(0x6a,17),
movcind=M.op(0x6b,17),
movdind=M.op(0x6c,17),
moveind=M.op(0x6d,17),
movhind=M.op(0x6e,17),
movlind=M.op(0x6f,17),
} M.op70r8ind = op70r8ind
for k,v in pairs(op70r8ind) do
M[k] = function(late, early)
local l7801dbg = { info=debug.getinfo(2, 'Sl'), trace=debug.traceback(nil, 1) }
local size = function() late,early = M.size_op(late,early) return 4 end
local bin = function() local l7801dbg=l7801dbg
local x = M.op_eval_word(late,early)
return { 0x70, v.opc, x&0xff, x>>8 }
end
table.insert(M.section_current.instructions, { size=size, cycles=v.cycles, bin=bin })
end
end
k = 0x89
local op70names = {'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax'}
local op70suffixes = {'bc','de','hl','ide','ihl','dde','dhl'}
for i,o in ipairs(op70names) do
for j,s in ipairs(op70suffixes) do
local l = 0x00 + k + (j-1) + (8 * (i-1))
M[o .. s] = function()
table.insert(M.section_current.instructions, { size=2, cycles=11, bin={ 0x70, l } })
end
end
end
return M

View File

@ -167,7 +167,7 @@ PAL = {
}
TV = PAL
init = function() cld ldx#0 txa local l=label() dex tsx pha bne l end
init = function() cld ldx#0 txa local l=label() dex txs 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
@ -375,7 +375,7 @@ mappers.X07 = function()
end
--------------------------------------------------------------------------------
-- Image converters
-- Data converters
--------------------------------------------------------------------------------
image_scan = function(img, opt)

View File

@ -1,122 +1,122 @@
" Vim indent file
" Language: l65
" URL: modified from https://github.com/tbastos/vim-lua
" Initialization ------------------------------------------{{{1
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal autoindent
setlocal nosmartindent
setlocal indentexpr=GetL65Indent()
setlocal indentkeys+=0=end,0=until,0=elseif,0=else
" Only define the function once.
if exists("*GetL65Indent")
finish
endif
" Variables -----------------------------------------------{{{1
let s:open_patt = '\C\%(\<\%(function\|if\|repeat\|do\)\>\|(\|{\)'
let s:middle_patt = '\C\<\%(else\|elseif\)\>'
let s:close_patt = '\C\%(\<\%(end\|until\)\>\|)\|}\)'
let s:anon_func_start = '\S\+\s*[({].*\<function\s*(.*)\s*$'
let s:anon_func_end = '\<end\%(\s*[)}]\)\+'
" Expression used to check whether we should skip a match with searchpair().
let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~# 'luaComment\\|luaString'"
" Auxiliary Functions -------------------------------------{{{1
function s:IsInCommentOrString(lnum, col)
return synIDattr(synID(a:lnum, a:col, 1), 'name') =~# 'luaCommentLong\|luaStringLong'
\ && !(getline(a:lnum) =~# '^\s*\%(--\)\?\[=*\[') " opening tag is not considered 'in'
endfunction
" Find line above 'lnum' that isn't blank, in a comment or string.
function s:PrevLineOfCode(lnum)
let lnum = prevnonblank(a:lnum)
while s:IsInCommentOrString(lnum, 1)
let lnum = prevnonblank(lnum - 1)
endwhile
return lnum
endfunction
" Gets line contents, excluding trailing comments.
function s:GetContents(lnum)
return substitute(getline(a:lnum), '\v\m--.*$', '', '')
endfunction
" GetL65Indent Function -----------------------------------{{{1
function GetL65Indent()
" if the line is in a long comment or string, don't change the indent
if s:IsInCommentOrString(v:lnum, 1)
return -1
endif
let prev_line = s:PrevLineOfCode(v:lnum - 1)
if prev_line == 0
" this is the first non-empty line
return 0
endif
let contents_cur = s:GetContents(v:lnum)
let contents_prev = s:GetContents(prev_line)
let original_cursor_pos = getpos(".")
let i = 0
" check if the previous line opens blocks
call cursor(v:lnum, 1)
let num_pairs = searchpair(s:open_patt, s:middle_patt, s:close_patt,
\ 'mrb', s:skip_expr, prev_line)
if num_pairs > 0
let i += num_pairs
endif
" special case: call(with, {anon = function() -- should indent only once
if num_pairs > 1 && contents_prev =~# s:anon_func_start
let i = 1
endif
" check if current line closes blocks
call cursor(prev_line, col([prev_line,'$']))
let num_pairs = searchpair(s:open_patt, s:middle_patt, s:close_patt,
\ 'mr', s:skip_expr, v:lnum)
if num_pairs > 0
let i -= num_pairs
endif
" special case: end}) -- end of call with anon func should unindent once
if num_pairs > 1 && contents_cur =~# s:anon_func_end
let i = -1
endif
" if the previous line closed a paren, unindent (except with anon funcs)
call cursor(prev_line - 1, col([prev_line - 1, '$']))
let num_pairs = searchpair('(', '', ')', 'mr', s:skip_expr, prev_line)
if num_pairs > 0 && contents_prev !~ s:anon_func_end
let i -= 1
endif
" if this line closed a paren, indent (except with anon funcs)
call cursor(prev_line, col([prev_line, '$']))
let num_pairs = searchpair('(', '', ')', 'mr', s:skip_expr, v:lnum)
if num_pairs > 0 && contents_cur !~ s:anon_func_end
let i += 1
endif
" restore cursor
call setpos(".", original_cursor_pos)
return indent(prev_line) + (&sw * i)
endfunction
" Vim indent file
" Language: l65
" URL: modified from https://github.com/tbastos/vim-lua
" Initialization ------------------------------------------{{{1
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal autoindent
setlocal nosmartindent
setlocal indentexpr=GetL65Indent()
setlocal indentkeys+=0=end,0=until,0=elseif,0=else
" Only define the function once.
if exists("*GetL65Indent")
finish
endif
" Variables -----------------------------------------------{{{1
let s:open_patt = '\C\%(\<\%(function\|if\|repeat\|do\)\>\|(\|{\)'
let s:middle_patt = '\C\<\%(else\|elseif\)\>'
let s:close_patt = '\C\%(\<\%(end\|until\)\>\|)\|}\)'
let s:anon_func_start = '\S\+\s*[({].*\<function\s*(.*)\s*$'
let s:anon_func_end = '\<end\%(\s*[)}]\)\+'
" Expression used to check whether we should skip a match with searchpair().
let s:skip_expr = "synIDattr(synID(line('.'),col('.'),1),'name') =~# 'luaComment\\|luaString'"
" Auxiliary Functions -------------------------------------{{{1
function s:IsInCommentOrString(lnum, col)
return synIDattr(synID(a:lnum, a:col, 1), 'name') =~# 'luaCommentLong\|luaStringLong'
\ && !(getline(a:lnum) =~# '^\s*\%(--\)\?\[=*\[') " opening tag is not considered 'in'
endfunction
" Find line above 'lnum' that isn't blank, in a comment or string.
function s:PrevLineOfCode(lnum)
let lnum = prevnonblank(a:lnum)
while s:IsInCommentOrString(lnum, 1)
let lnum = prevnonblank(lnum - 1)
endwhile
return lnum
endfunction
" Gets line contents, excluding trailing comments.
function s:GetContents(lnum)
return substitute(getline(a:lnum), '\v\m--.*$', '', '')
endfunction
" GetL65Indent Function -----------------------------------{{{1
function GetL65Indent()
" if the line is in a long comment or string, don't change the indent
if s:IsInCommentOrString(v:lnum, 1)
return -1
endif
let prev_line = s:PrevLineOfCode(v:lnum - 1)
if prev_line == 0
" this is the first non-empty line
return 0
endif
let contents_cur = s:GetContents(v:lnum)
let contents_prev = s:GetContents(prev_line)
let original_cursor_pos = getpos(".")
let i = 0
" check if the previous line opens blocks
call cursor(v:lnum, 1)
let num_pairs = searchpair(s:open_patt, s:middle_patt, s:close_patt,
\ 'mrb', s:skip_expr, prev_line)
if num_pairs > 0
let i += num_pairs
endif
" special case: call(with, {anon = function() -- should indent only once
if num_pairs > 1 && contents_prev =~# s:anon_func_start
let i = 1
endif
" check if current line closes blocks
call cursor(prev_line, col([prev_line,'$']))
let num_pairs = searchpair(s:open_patt, s:middle_patt, s:close_patt,
\ 'mr', s:skip_expr, v:lnum)
if num_pairs > 0
let i -= num_pairs
endif
" special case: end}) -- end of call with anon func should unindent once
if num_pairs > 1 && contents_cur =~# s:anon_func_end
let i = -1
endif
" if the previous line closed a paren, unindent (except with anon funcs)
call cursor(prev_line - 1, col([prev_line - 1, '$']))
let num_pairs = searchpair('(', '', ')', 'mr', s:skip_expr, prev_line)
if num_pairs > 0 && contents_prev !~ s:anon_func_end
let i -= 1
endif
" if this line closed a paren, indent (except with anon funcs)
call cursor(prev_line, col([prev_line, '$']))
let num_pairs = searchpair('(', '', ')', 'mr', s:skip_expr, v:lnum)
if num_pairs > 0 && contents_cur !~ s:anon_func_end
let i += 1
endif
" restore cursor
call setpos(".", original_cursor_pos)
return indent(prev_line) + (&sw * i)
endfunction

File diff suppressed because it is too large Load Diff