Retro Assembler
Table of Contents
Assembler mode
Disassembler mode
Integration into Projects
Supported CPU Types
Updates and Version numbers
Settings (General, Txt Output, Debug, Gameboy, NES, SNES, SMS/GG, TAP)
Normal Labels
Local Labels
Current Memory Address
MOS 6502 Family
WDC 65C02 / 65SC02
WDC 65816
MEGA65 45GS02 / CSC 4510
Intel 4004
Intel 4040
Intel 8008
Intel 8080
Intel 8085
Nintendo Gameboy
Zilog Z80
BIN – Binary file
PRG – Binary file with load address header
SBIN – Binary file with load address and data length header
T64 – Tape image format for Commodore computers
D64 – Disk image format for Commodore computers
TXT – Configurable text file format
XEX – Binary file with data chunks and launcher for Atari DOS
GB – Nintendo Gameboy ROM format
NES – Nintendo Entertainment System ROM format
SNES – Super Nintendo Entertainment System ROM format
SMS – Sega Master System ROM format
GG – Sega Game Gear ROM format
TAP – ZX Spectrum 48K Tape format
Integration with IDEs and Text Editors
About the Assembler
Retro Assembler was created as a hobby project to work with source code targeting microcomputers and classic game consoles. Hence the name Retro. I grew up coding a lot of demos and a few games on Commodore 64 and Plus/4, so the main target was the 6502 CPU family, which is the closest to my heart. I happen to know I'm not alone with that feeling. But I also worked on the Amiga and Gameboy Color, so the assembler was created in a way that it can support multiple platforms with little effort on my part. Ultimately the goal is to support numerous CPUs and output formats, and help the task of software development with neat assembler features.
The application is being developed on Windows 10 in C#, using .NET 5.0
It runs on any platform where .NET 5.0 can be installed, including Windows, macOS and Linux, on X86, X64 and ARM architectures.
Run the Windows version with retroassembler.exe, and on other operating systems run the portable version with dotnet retroassembler.dll
If you use Retro Assembler, I would be happy to hear from you. You can tweet me at @Peter_Tihanyi – I'd love to see what you do with it!
Enjoy!
Assembler mode
This is the default mode, where the assembler loads the main source file (with optional source file includes in it) and compiles it according to the rules of the target platform. The default target CPU is 6502 and the full 32 bit address space can be used for code and data.
The assembler works with separate memory banks and segments (using unique names). When the end result is saved, it saves a file made of all segments in each separate bank. If multiple banks are in use, the merged file names get a notation with the bank number. In case of a single bank (the default Bank 0), this info is omitted from the filename.
If enabled in settings, the assembler saves each individual segment as well, separately, so if you use overlapping segments, you can link them up manually as you please. It also can save a *-Info.txt report file which lists each bank and their segments, with the minimum and maximum memory addresses used by each segment.
If the optional Output file path is not set, the assembler uses the input file's directory and name to build the output file. For example the file MyCode.asm would be compiled and saved as MyCode.bin
Usage
- Windows
retroassembler.exe [options] <input file path> [output file path] - macOS, Linux (or Windows)
dotnet retroassembler.dll [options] <input file path> [output file path]
Command line options
Option | Description |
-c | Turns on Case Sensitive mode for Labels, Functions and Macros. |
-x | Prints out Labels and their Values after a successful code compiling. |
-l | Sets (overrides) the setting Launch to True for IDEs like VS Code to Build & Start the compiled code automatically. |
-g | Sets (overrides) the setting Debug to True for IDEs like VS Code to Build & Debug the compiled code automatically. |
-C=<type> | Sets the target platform's CPU type. Default: 6502. See Supported CPU Types for accepted values. |
Sets the output file's type. See Output File Formats for accepted values. |
Disassembler mode
Optionally the assembler is capable of loading a binary file (software compiled to a certain supported CPU) and disassemble it into a text file according to the rules of the target platform that the binary file was made for. The disassembled code goes either to the standard output, or into the optional Output file as text.
When disassembling 65816 code, the disassembler tries to do its best to follow the state of the M and X registers as they are being changed in the sequentially read code. Unless you set those from subroutines, the disassembler will likely figure out whether the Accumulator and the Index Registers are in 8 or 16 bit mode, decoding the instructions accordingly. This unfortunate ambiguity of the 65816 instruction set makes things a bit risky, especially that all 256 bytes are assigned to individual instructions. Once it goes off the track, it can decode completely false instructions for a while before it accidentally finds the address of an actual instruction, and not just a half of one. But in the right circumstances, it will work okay.
Usage
- Windows
retroassembler.exe -d [other options] <input file path> [output file path] - macOS, Linux (or Windows)
dotnet retroassembler.dll -d [other options] <input file path> [output file path]
Command line options
Option | Description |
-d | Required: Turns on the Disassembler mode. |
Sets the load address for the input file. It will be determined for PRG, SBIN and XEX files automatically from the load address header. The entered number can be in either decimal format (4096) or in hexadecimal format ($1000 or 0x1000). Also see the Advanced Mode below! | |
-C=<type> | Sets the input file's CPU type. Default: 6502. See Supported CPU Types for accepted values. |
-u | Allows using undocumented instructions in certain CPU types. |
There is an advanced mode for the load address option. You can also specify the start address in the memory and the length of the code chunk that will be disassembled. Please note that these optional values are not handled as Offset and Length for the binary file loader. The whole file will be loaded into the target memory, these only control what the disassembler should turn into readable code.
-D=<LoadAddress>,<StartFrom>,<Length> (Must be without spaces!)
- LoadAddress: As explained above, this is where the file will be loaded into the target memory. If the value is a negative number, it's used as an offset, so that many bytes will be skipped and the load address is set to 0. If the loaded file is a PRG, SBIN or XEX file, this value is ignored because PRG, SBIN and XEX files start with a load address header. So for those type of files, you may set it to 0 or anything, it won't matter.
- StartFrom: The memory address where disassembling should start from. For example the file may be loaded to $2000, but disassembling can start from $3800.
- Length: Optionally, this non-zero value sets the number of bytes that will be disassembled. For example $80.
Examples
//The file is loaded to $2000, but only the code between $3800-$3880 will be disassembled. -D=$2000,$3800,$80 //A C64 file is loaded to $0801, but skip the BASIC RUN header and disassemble the whole file from $0810 -D=$0801,$0810 -D=0,$0810 //Does the same, since this example file was a PRG file with a known load address. //The file is loaded to $0000, but only the code between $0740-$07c0 will be disassembled. //The first $100 bytes of the file is ignored, for example it may be a header or something //that's not part of the actual code file. (Note the negative number!) -D=-$0100,$0740,$80
Integration into Projects
If you have a project that needs to integrate Retro Assembler for unit testing or for some other reasons, you can of course always just call the assembler as a new process, capture its output to the console, read the files it generated...
But if you need to integrate it into a .NET 5.0 project, you can reference the retroassembler.dll file, add the Settings XML file(s) so they are in the same directory with the DLL file after compilation, and you can call these functions from your application:
- List<byte> Assembler.UnitTestHelper.Compile(string filename)
- List<byte> Assembler.UnitTestHelper.Compile(List<string> args)
- bool Assembler.UnitTestHelper.CompileAndCompare(string filename, List<byte> knownGoodBytes)
- bool Assembler.UnitTestHelper.CompileAndCompare(List<string> args, List<byte> knownGoodBytes)
- bool Assembler.UnitTestHelper.CompareBytes(List<byte> bytes1, List<byte> bytes2)
I think these are fairly straightforward functions. You can either pass in the source filename (with full path) or your list of command line arguments along with the source filename (with full path) as the last argument.
By calling Compile() you can grab the array of compiled bytes. It returns an empty array on error, no error messages, but this should do for most projects.
By calling CompileAndCompare() you can send in an array of known good bytes for this source code's output binary file and you simply get whether they are equal.
These functions were mainly made to make binary comparation simple with a single known good binary file in a unit test. If you call it with a source code and settings that would save individual segments, use banks and other complex output schemes, the resulting single byte array is possibly going to be incorrect and weird.
Supported CPU types
CPU Name | Description |
---|---|
6502 | The well known MOS 6502 CPU and its standardized variants. Aliases: 6502C, 6510 |
65C02 | An extension over the standard MOS 6502 instruction set, with additional vendor specific WDC/Rockwell instructions. Alias: 65SC02 |
65816 | A serious extension over the standard MOS 6502 instruction set, with 16 bit registers, 24 bit address size, bank switching etc. Aliases: 65C816, 65C816S, 65802 |
4510 | The CSC 4510, an extension over the MOS 6502 that was used in the unreleased Commodore 65. The MEGA65 project utilizes this CPU. |
45GS02 | A 32-bit extension over the standard CSC 4510 instruction set using the Q pseudo-register. Alias: MEGA65 |
4004 | The Intel 4004 CPU, Intel's first microprocessor from 1971 with a 4 bit data bus. |
4040 | The Intel 4040 CPU, a small extension over the Intel 4004. |
8008 | The Intel 8008 CPU. |
8080 | The Intel 8080 CPU. |
8085 | The Intel 8085 CPU, a small extension over the Intel 8080. |
Gameboy | Nintendo's Gameboy CPU based on the Z80, but with a modified set of instructions and registers. |
Z80 | The well known Zilog Z80 CPU. |
Automatic CPU detection from file names
Figuring out what CPU the source code file was made for is not trivial. You always should have a .target directive in the main source code file to select the correct CPU, but it's useful to have a fallback method for some special cases.
You can tag your source code files with a special sub-extension, like this: MyFile.6502.asm
The assembler (and even the disassembler) will choose the correct CPU type for your file, automatically. It was "invented" for the Visual Studio Code Extension support, which really needs some help to choose the correct language syntax, but it was easy to add it to the assembler itself as well. Use the main CPU type strings above, not aliases.
Future roadmap: 6809, Motorola 68000 (and family) support is planned.
ARM V7 and others are to be decided.
Updates and Version numbers
The assembler looks for updated versions periodically, unless it's disabled in Settings by setting UpdateCheck to False. It's recommended to install the latest version when available, to get the newest improvements and bug fixes.
The version number style is Year.Release, for example 2020.3
Settings
The application has various command line switches that can change the assembler's behavior. You can set up your own defaults in the retroassembler-settings.xml file for most of these, along with your preferred include paths. Just be careful with future updates that would overwrite this customized file of yours.
To avoid such accidents, create the retroassembler-usersettings.xml file based on the normal settings Xml file, where you can keep the settings you changed from their defaults. The assembler package will never contain (thus overwrite) this file, but the assembler will always load this for your final settings. You should delete all <Setting /> lines from it, except for the ones you actually changed with your custom values, just to make it clear for you to read.
Most settings can be set or changed in the source code files, using the .setting directive.
The flow by which settings get their values:
- Each known setting gets initialized to a "good default" by the assembler itself.
- These settings are also listed in the retroassembler-settings.xml file with the "good default" values, they are loaded and overwrite the defaults set in Point 1. You can edit these to set your own defaults, by following the rules of accepted value types, sane values etc. It's in your hands, literally. But you should just not edit this, and see Point 3 instead.
- Some of these settings may be listed in the custom retroassembler-usersettings.xml file with your "custom good default" values, they are loaded and overwrite the defaults set in Point 2. You can create this by duplicating and renaming retroassembler-settings.xml. It's best to keep only the customized settings in this file for clarity. This file is optional. If it's missing, you will not get an error.
- Certain settings are exposed as command line options, they can be updated that way for the loaded source code or binary file.
- Most settings can be updated from the source code file itself, using the .setting directive. This allows for maximum customization for certain projects, while the rest use the defaults loaded from the Xml file.
- A few special settings can only be managed from the source code file, they are not present in the Xml file.
Perhaps best practice would be changing the Xml file's defaults only for the values you can only edit in that file (or you positively want to be the default in all your projects), and set up the rest from the main source code file's header, with comments about why it changes those defaults. Make sure you have a backup of your good Xml file, because an updated version of the assembler will overwrite your file if you're not careful. New versions may come with more or different Xml values.
The known settings and their values are described below.
The Name of the settings are handled case-insensitively, eg "RandomSeed" is the same as "randomseed".
General settings for the assembler
Name | Type | Description | Default |
CpuType | String | The default one of the Supported CPU Types that the assembler to identify instructions, when it's not set in the source code by the .target directive. Can't be updated with .setting, use .target to do so. |
6502 |
OutputFormat | String | The default one of the Output File Formats that the assembler uses to save the compiled source code. (Alias: OutputFileType) | bin |
DefaultScreencode | String | The default Encoding for the .stext directive. It can be anything, but typically it's a screencode type with case conversion. Enter the Type and Case as comma-separated values. | screencode, lower |
IncludePath | String | Directory paths separated by ';' where the assembler looks for files that are referenced by the .include and .incbin directives. Example: "C:\MyFiles;C:\MyFiles\Macros" Can't be updated with .setting because it's loaded only on application launch. |
|
VicePath | String | The directory path where the VICE emulator is, to access the program "c1541" for T64->D64 output file conversion. Example: "C:\Emulators\C64\WinVICE-3.2-x86" Can't be updated with .setting because it's loaded only on application launch. |
|
CaseSensitiveMode | Boolean | Enables case sensitive handling of Labels, Functions and Macros. | false |
ShowLabelsAfterCompiling | Boolean | Enables printing out Labels and Variables after compiling. | false |
ShowLocalLabelsAfterCompiling | Boolean | When ShowLabelsAfterCompiling is enabled, this option can enable printing Local labels, too. | false |
TreatWarningsAsErrors | Boolean | The assembler may show some warnings after compiling. This setting turns those warnings into errors for additional strictness. | false |
AllowUndocumentedInstructions | Boolean | Allows using Undocumented Instructions in participating CPU types. It can be turned on or off anywhere in the source code. | true |
OmitUnusedFunctions | Boolean | Allows omitting (ignoring and removing) those Functions that are not actually called from anywhere in the source code. This allows for building large libraries included into projects, but including only those that are referenced by the project itself. | false |
HandleLongBranch | Boolean | Instructions with relative addressing, typically branching ones may throw an error, if the branch address is out of the addressing range. On 8-bit CPUs this is a signed 7 bit value with the relative address. This setting can set the assembler to work in auto-fix mode, where it replaces the branch instruction with an absolute jump, using the branch instruction's counterpart to make the code behave correctly. For example: bne $1234 (error, branch too far, becomes...) beq AfterJmp jmp $1234 AfterJmp (your other instructions) This works for 6502, 65C02, 65SC02, 65816, Gameboy, Z80 |
false |
OutputSaveEntireBanks | Boolean | Normally the output files are only as long as the number of bytes they use in memory, with gaps included. This setting forces saving entire memory banks, using each bank's SizeKB setting and mapping address. | false |
OutputSaveIndividualSegments | Boolean | Normally only merged memory segments get saved for each memory bank. This setting enables saving individual segments beside the merged memory banks for manual linking. | false |
OutputSaveInfoFile | Boolean | Enables saving an Info file for the compiled source code with start-end addresses of each segment, in case it's needed. | false |
OutputSaveTimeStamp | Boolean | Enables appending a time stamp to each saved file's name, so all compiled versions are kept alongside in the output directory. | false |
RandomSeed | Integer | Sets the Seed value for the random number generator. 0 is for "use a truly random seed". Other values will make the random number generator more predictive. It's recommended to change it only from the source code, if you really need to. | 0 |
BeforeBuild | String | A command that should be executed before code compiling. When it's set in the Settings XML file, it truly gets executed before the build starts. If one or multiple of these are placed into the source code (as well), the assembler executes them when these .setting lines are processed. This setting can be defined multiple times for multiple commands. See LaunchCommand for the command style details. |
|
AfterBuild | String | A command that should be executed after a successful code compiling. Whether it comes from the Settings XML file, and/or one or multiple of these are set in the source code, they get collected up, and they all get executed (in order) after a successful code compiling. This setting can be defined multiple times for multiple commands. See LaunchCommand for the command style details. |
|
Launch | Boolean | Enables the automatic launch of the successfully compiled source code, by using the LaunchCommand setting for process information. The assembler's -l command line switch overrides this setting to True, so you can just build your code without launching, and launch it with a specific IDE command, such as VS Code's Retro Assembler: Build & Start | false |
LaunchCommand | String | The command that should be used to launch the successfully compiled source code, if Launch is True. This may be something like "C:\\Emulators\\MyEmu\\emulator.exe -run {0}" where the optional {0} is replaced by the compiled code file's filename with full path. The same rules apply as for the terminal of your choice: if you have paths or parameters with space in them, you have to put those into "quotes"! Additionally, a plain "{0}" may also work just fine, if your system associates the file's extension to a specific emulator or utility, like for .nes files. If this string is empty, the launcher will never run. | |
UpdateCheck | Boolean | Enables checking for assembler updates periodically. It checks the enginedesigns.net website about once a day and creates an updatecheck.txt file to keep track of things, so it doesn't need to go online that often. It's on by default, but it can be turned off. | true |
Settings for the configurable Txt output format (byte dump with memory addresses)
Name | Type | Description | Default |
OutputTxtAddressFormatFirst | String | String formatting for the memory address in the first line of a new area. Example: "8fc0 " (with space). {0:X04} with uppercase X would make it print an uppercase hexadecimal number. |
|
OutputTxtAddressFormatNext | String | String formatting for the memory address in subsequent lines of the area, in case you want to drop the address display there. Example: "8fc0 " (with space) | "{0:x04} " |
OutputTxtValueFormat | String | String formatting for each displayed byte value. Example: "7f" {0:X02} with uppercase X would make it print an uppercase hexadecimal number. |
"{0:x02}" |
OutputTxtValueSeparator | String | Separator placed between each displayed byte value in a line of text. | " " |
OutputTxtAreaSeparator | String | Separator placed between each area. Areas are continuous bytes, if there is a gap in the memory, a new area opens. | "\r\n" |
OutputTxtLineSeparator | String | Separator placed between each displayed line. It's basically the newline character of your choice. | "\r\n" |
OutputTxtValuesInLine | Integer | The maximum number of displayed byte values in each line. | 8 |
Settings for the configurable Debug file (labels with memory addresses and breakpoints)
Name | Type | Description | Default |
Debug | Boolean | Enables saving the Debug text file, which contains labels, memory addresses and breakpoints. If at least one breakpoint is set, this file is always saved. | false |
DebugCommand | String | The command that should be used to launch the debugger with the successfully compiled source code, if Debug is True. This may be something like "C:\\Emulators\\MyEmu\\emulator.exe -debug {0}" where the optional {0} is replaced by the compiled code file's filename with full path. The same rules apply as for the terminal of your choice: if you have paths or parameters with space in them, you have to put those into "quotes"! Additionally, a plain "{0}" may also work just fine, if your system associates the file's extension to a specific debugger, like for .nes files. If this string is empty, the debugger launcher will never run. | |
DebugFile | String | The filename of the Debug text file, saved into the default Output directory. | |
DebugCodeFile | String | The filename of the Debug Code text file, saved into the default Output directory. | |
DebugLabelFormat | String | String formatting for Labels, using values Memory Address {0} and Label {1} | |
DebugBreakpointFormat | String | String formatting for Breakpoints, using the value Memory Address {0} | |
DebugBreakpointConditionFormat | String | String formatting for Breakpoints, using values Memory Address {0} and Condition {1} |
Nintendo Gameboy (GB) ROM format and CPU settings
Name | Type | Description | Default |
GameboyStart | Integer | The code in Gameboy a ROM usually starts at the memory address $0150. The CPU jumps to $0100 on reset, which contains a user-set "nop ; jr StartAddress" instruction pair. The linker puts these instructions to $0100, using this address value. Set this to 0 if you really want to control those 4 bytes yourself. | 0x0150 |
GameboyTitle | String | A maximum 15 characters ASCII string with the ROM's name. It will be saved as uppercase text. | Image Title" |
GameboyLicenseeCode | String | A 2 characters ASCII string with the licensee code. You can set it to anything. | "GB" |
GameboyLicenseeCodeOld | Integer | Old, deprecated licensee code. It's recommended to leave it at the default $33 and use the GameboyLicenseeCode string instead. | 0x33 |
GameboyManufacturerCode | String | A 4 characters ASCII string with the manufacturer code, which will overwrite the last 4 characters of a 15 character GameboyTitle, making it max 11 characters long. Not recommended to set. | |
GameboyCartridgeType | Integer | The Memory Bank Controller (MBC) type ID of the cartridge. Unless you're doing something special, leave it as 0 and the linker will figure out which MBC5 ID you need, based on the cartridge settings below. | 0 |
GameboyCartridgeRamKB | Integer | Sets how much external RAM the cartridge has (if any). Set it to 0, 2, 8 or 32 KB. | 0 |
GameboyCartridgeBattery | Boolean | Sets whether there is battery backed external RAM on the cartridge. | false |
GameboyCartridgeRumble | Boolean | Sets whether there is a rumble motor on the cartridge. | false |
GameboyRomVersion | Integer | ROM version number. | 0 |
GameboyRomJapanese | Boolean | Sets whether the ROM is for the Japanese market. Otherwise International. | false |
GameboyMonochromeEnabled | Boolean | Sets whether the ROM runs on monochrome devices (DMG), or perhaps only on Gameboy Color (CGB). | true |
GameboyColorEnabled | Boolean | Sets whether the ROM has Gameboy Color (CGB) functions enabled. | true |
GameboySuperGBEnabled | Boolean | Sets whether the ROM has Super Gameboy (SGB) functions enabled. | false |
GameboyPutNopAfterHalt | Boolean | Sets whether the assembler should put a "nop" instruction after "halt" instructions automatically to avoid hardware bugs. Can't be updated with .setting because it's loaded only on application launch. |
true |
GameboyPutNopAfterStop | Boolean | Sets whether the assembler should put a "nop" instruction after "stop" instructions automatically to avoid hardware bugs. Can't be updated with .setting because it's loaded only on application launch. |
true |
Nintendo Entertainment System (NES) ROM format settings
Name | Type | Description | Default |
NESMapper | Integer | Sets the cartridge board type (usually with a Memory Manager Controller) and capabilities for the ROM. By default it's 0 for a small ROM. You have to manage this value for your project. Normally 0-255. When over 255, the extra 4 bits go into Byte 8 in the ROM header. |
0 |
NESSubMapper | Integer | Sets the selected cartridge board type's extended capabilities. 0-15, these 4 bits go into Byte 8 in the ROM header as bits 5-8. |
0 |
NESVerticalMirroring | Boolean | Enables vertical mirroring mode, instead of the default horizontal mirroring mode. | false |
NESBatteryBackedWRAM | Boolean | Sets whether there is battery backed external RAM on the cartridge. | false |
NESFourScreenMode | Boolean | Enables the Four Screen mode. | false |
NESPlayChoice10 | Boolean | Indicates that it's a PC-10 game. | false |
NESVsUnisystem | Boolean | Indicates that it's a Vs. game. | false |
NESPal | Boolean | Sets whether the ROM is compatible with the PAL video standard (in Byte 12). | false |
NESNtsc | Boolean | Sets whether the ROM is compatible with the NTSC video standard (in Byte 12). | true |
NESByte10 | Integer | Sets the NES 2.0 standard's extended flags in Byte 10 of the ROM header, raw. | 0 |
NESByte11 | Integer | Sets the NES 2.0 standard's extended flags in Byte 11 of the ROM header, raw. | 0 |
NESByte13 | Integer | Sets the NES 2.0 standard's extended flags in Byte 13 of the ROM header, raw. | 0 |
NESByte14 | Integer | Sets the NES 2.0 standard's extended flags in Byte 14 of the ROM header, raw. | 0 |
Super Nintendo Entertainment System (SNES) ROM format settings
Name | Type | Description | Default |
SNESTitle | String | A maximum 21 characters ASCII string with the ROM's name. It will be saved as uppercase text. | Image Title" |
SNESPadding | Boolean |
Enables ROM padding with empty banks, up to the next standard (or accepted) ROM size.
It's turned On by default and it's highly recommended to use this setting, because the checksum calculator sets a correct result only for standard ROM sizes and a few exceptions underlined below.
The standard (or accepted) ROM sizes are the following: You can turn it off if you want to manually manage the ROM's size, especially if you want to use a non-standard size, but then it's up to you to use enough empty banks at the end if needed, and you'll have to repair the checksum in the SNES header. For most users, this should be turned On, and a 4MB ROM should be enough for most projects. |
true |
SNESHiROM | Boolean | Sets whether the ROM uses 64KB banks, instead of the standard 32KB banks used by the LoROM format. | false |
SNESExLoROM | Boolean | Sets whether the ROM uses the Extended LoROM format for non-standard ROM sizes. | false |
SNESExHiROM | Boolean | Sets whether the ROM uses the Extended HiROM format for non-standard ROM sizes. | false |
SNESFastROM | Boolean | Sets whether the ROM uses FastROM, which is a setting for physical cartridges. It may matter timing-wise for a system that emulates the ROM access speed (120ns vs 200ns). | false |
SNESCartridgeType | Integer | Sets the cartridge type. Only standard ROMs are supported without special chips, so you likely want to use these values: 0=ROM only, 1=ROM and RAM, 2=ROM and Save RAM (SRAM). Of course other values may be set here, but then you have to make sure the ROM is linked up correctly. | 0 |
SNESSRAMSize | Integer | Sets the SRAM size as enumerator value (not in KB) because there may be other settings I don't know of. The typical settings are: 0=None, 1=2KB, 2=4KB, 3=8KB | 0 |
SNESCountry | Integer | Sets the country code which also has an effect on the system type. The most used ones are: 0=Japan (NTSC), 1=USA (NTSC), 2=Europe, Australia, Oceania and Asia (PAL) | 1 |
SNESLicenseeCode | Integer | Sets the licensee code of the publisher. You can use 1 for Nintendo, or anything else you pick. | 1 |
SNESVersion | Integer | Sets the version number of the ROM. It can be a number between 0 and 127, and usually understood as Version 1.N The SNES doesn't check this, it's only for the developer. | 0 |
Sega Master System (SMS) & Sega Game Gear (GG) ROM format settings
Name | Type | Description | Default |
SMSCountryCode | Integer | Sets the country code which could also have an effect on the system type. 3=SMS Japan 4=SMS Export 5=GG Japan 6=GG Export 7=GG International |
4 |
SMSProductCode | Integer | Sets the product code for the ROM. It can be a number between 0 and 159999. It doesn't make any functional difference. | 0 |
SMSVersion | Integer | Sets the version number of the ROM. It can be a number between 0 and 15. The systems don't check this, it's only for the developer. | 0 |
ZX Spectrum 48K Tape (TAP) format settings
Name | Type | Description | Default |
TapStart | Integer | Sets the start address of the program. | 0x8000 |
TapClear | Integer | Sets the memory clearing start address, executed before the program would be loaded from the tape file. | 0x5fff |
Settings managed only from source code files
Name | Type | Description | Default |
RegA16 | Boolean | Sets whether immediate values in instructions using the Accumulator should be saved as 16 bit numbers, even with 8 bit values. (65816 only) Example: lda #$12 --> lda #$0012 |
false |
RegXY16 | Boolean | Sets whether immediate values in instructions using the Index registers (X and Y) should be saved as 16 bit numbers, even with 8 bit values. (65816 only) Example: ldx #$12 --> ldx #$0012 |
false |
Value Types
The following value types can be used in directives and instructions:
Numbers
Numbers can be decimal, hexadecimal or binary values.
123 //Decimal value. $12 //Hexadecimal value. 0x12 //Hexadecimal value (alternative). $1234 //Hexadecimal value, 16 bit. 0x1234 //Hexadecimal value, 16 bit (alternative). $12_34 //Hexadecimal value with optional value separator(s). %100101 //Binary value. 0b100101 //Binary value (alternative). %ffff_0000 //Binary value with optional value separator(s). 0o17 //Octal value with "zero + letter o"
Number values can go up to 32 bit unsigned values, or 31 bit signed values for negative numbers.
In 8 bit systems it's useful to access a 16 bit value's lower byte and higher byte. This can be done using the < and > modifiers entered directly in front of a label or number. It's needed to set up interrupt vectors and other pointers in the code.
Label .equ $1234 ldx #<Label //Same as ldx #$34 (lower byte of $1234) ldy #>Label //Same as ldy #$12 (higher byte of $1234)
Preferred Number Size
In certain situations you may need to force a value into a different (always bigger) number size. For example in a self-modifying code you may want to ensure you start out with a 16 bit value, but $0000 would normally be normalized to $00 which would generate an instruction with a different addressing format, different byte count.
You can enforce a different number size by prefixing it with extra "0" characters (only in hexadecimal mode!), and if there is an addressing format for the chosen instruction that can handle it in the bigger size, that specific addressing format will be chosen.
sta $12,x --> sta $12,x ($95 $12) sta $0012,x --> sta $0012,x ($9d $12 $00) sta 8 + $0012,x --> sta $001a,x ($9d $1a $00) (Expressions remember the preferred size.)
This works with 16, 24 and 32 bit values, and you may prefix it with even just a single $0. As long as the number can be stepped up to the next size, it will be done. Then it's up to the CPU's instruction set, whether it has what you need. If the instruction doesn't exist with the 16 bit value for example, but your value was 8 bit to begin with, it will be tried as an 8 bit value as well. A working code has higher priority over a possibly mangled code.
When it comes to expressions, the "preferred number size" is remembered after each operation made on two (or more) numbers,
but the 2nd number's preferred size is kept only in case of Addition or Subtraction. Which means, an
Strings
Strings are one or more characters in double quotes, translating to ASCII bytes.
"Hello world!" //Normal text "Hello\nWorld!" //Normal text with an escaped newline character.
Characters
A character is one character in single quotes, translating to an ASCII byte.
'X' //X character '\r' //An escaped newline character. lda #'X' //The X character used as a byte value in an instruction.
Expressions and Operators
Expressions constructed of operators, values and Labels can be used in virtually any directive or instruction, which allows for some clever code building mechanics.
Brackets (parentheses) can be used to form more complex expressions in directives, instructions and macro calls. Be careful how you form them. It may be better to evaluate complex expressions into .var variables and use them in the instructions, but it's up to you.
The available operators, in the order of evaluation:
Symbol ( ) ~ * / + - <N >N << >> & ^ | == != < > <= >= && || , |
Type of operation Expression Bitwise-NOT Multiplicative Additive Low and High Byte Bitwise shift Bitwise-AND Bitwise-exclusive-OR Bitwise-inclusive-OR Equality Relational Logical-AND Logical-OR Sequential evaluation |
Labels (including the Current Memory Address pointer) get replaced by their number value during expression evaluation, and in directives it's required to use labels with prior definitions in order to build reliable code.
The comparers (== != <= etc) and logical operators (&& ||) work best in .if and .while directives, because their end result is either 1 for true, or 0 for false. If you use them with .equ, you'll just end up with a 0/1 number that you can use as a flag.
Examples
MyValue .equ (MyConstant << 4) + 5 .if MyValue >= 8 || OtherValue != 13 (code lines) .endif //Assuming IrqRoutine is at $087c lda #<IrqRoutine //Lower byte : $7c sta $0314 lda #>IrqRoutine //Higher byte: $08 sta $0315
Labels
Labels (also known as Symbols) are constants or memory addresses that can be used in directives and instructions as parts of expressions and operands.
A Label's name can only contain letters, numbers and the "_" character, and it can't begin with a number. Single character Labels can't match a register name in the selected CPU type, for example "x" is not going to work as a Label in 6502 mode. It's good practice to avoid using single character Labels if you can help it.
White spaces are usually ignored in the source code file. The only place where you must use a space or tab (or a colon) as separator is between the main Label of the code line and the directive/instruction after it.
To maintain compatibility with other assemblers, if the Label is followed by a ":" (colon) character, this character gets processed as white space. So "MyLabel:" is the same as "MyLabel", you can format them either way.
The assembler shows a Warning when you define a Label as a standalone value in a source code line, and the Label is up to 4 letter characters. This may be easily confused with a mistyped assembly instruction, so this is a help for the developer to detect such mistakes.
If you need to save the used Labels into a Debug file (without specifying breakpoints), use this in your code:
.setting "Debug", true
Normal Labels
The scope of a Normal Label (or a Global Label) is the entire source code, so they must be named uniquely. However when they are defined inside a Macro's code block, their Namespace Context is limited for the Macro's calling instance.
This means that multiple Macros can reuse the same Normal Label "MyLabel", and a Macro can call another Macro from inside its own code block without running into Label reusing issues. See more about this under Macros.
Labels can be defined the following ways:
MyLabel .equ $73 //MyLabel gets the constant value $73 MemAddress lda #$00 //MemAddress gets the value of the current memory address, eg $0813
Then the value of these labels can be used in directives and instructions with ease. Keep in mind that only those labels can be used in directives, that have been defined before the directive's code line.
MyLabel .equ $73 //MyLabel gets the constant value $73 lda #MyLabel+2 //lda #$75 sta WhoKnows //sta $0000 because WhoKnows is unknown //and the therefore assembler assumes a 16 bit value. MyNewLabel .equ MyLabel+4 //$77 MyNewLabel2 .equ WhoKnows+4 //ERROR: The label WhoKnows is not defined yet. MyLabel .equ 55 //ERROR: The label "MyLabel" is already defined. MyVariable .var 10 //Create a variable with the initial value 10 MyVariable .equ MyVariable+1 //The variable is updated to 10 + 1 = 11 MyVariable = MyVariable+1 //The variable is updated to 11 + 1 = 12
You can use Labels almost anywhere. They will get replaced by the number value they hold (constant or memory address) and this value will be used in calculations, or in determining the addressing type of certain instructions.
If the label's value is not known during the time the assembler gets to an instruction that uses it (in the 1st Pass), the assembler assumes that it's 16 bit memory address for those instruction addressing types that work with memory addresses.
For example an "sta MyLabel,y" instruction placed before MyLabel's definition will be handled as "sta $0000,y" (3 bytes) instead of "sta $73,y" (2 bytes), then in the 2nd Pass the code will be entered as "sta $0073,y" (3 bytes) because the worst case was already assumed in 1st Pass, in order to get the number of bytes that the instruction will take in memory.
So if you want to use zero page values for a faster code execution, define those labels before the code lines that try to use them.
Local Labels
There is a Local Label type, that can be used with a limited scope. Defining a new Normal Label and using certain directives close down the currently "open" Local Labels, by setting a range of start and end line numbers in the merged source code where the Local Label can be addressed.
By having this automatic closure, the Local Label names can be redefined in various sections of the code. Like small loop branches can simply utilize the same "@Loop" Label at most places, without running into any "label exists" errors.
Macro calls don't close down the previously created Local Labels, so you can define Locals before and after the Macro call, then reference these from before and after the Macro's generated code block. See more about this under Macros.
The name must start with the "@" character, then any letter, number or the "_" character can be used in any combination. This means that even the really simple "@1" is accepted as a Local Label name.
Example
FillMem ldx #$00 lda #$ff @Loop sta MemAddress,x //Define a new Local Label to "use it, then forget about it". inx cpx #$28 bne @Loop //Branches back to "sta MemAddress,x" as it should. NewLabel lda #$00 beq @Loop //ERROR: The @Loop Label can't be found, because the //definition of NewLabel closed its range.
The Local Label ranges are closed by the directives .function, .loop, .while and their .end* counterparts.
It's recommended to use Local Labels inside Macro code lines, Functions, Ifs, Loops and Whiles, and wherever else you need labels with a short life span.
Current Memory Address
Another kind of label that's worth mentioning is the "*" (asterisk) character.
"*" gets replaced by the current memory address, like $0813 during expression evaluation.
Please note that the "*" character is also used in multiplications, so the assembler tries to determine the context where the "*" character is used in, and acts accordingly.
Examples
ldx #$07 dex bne *-3 //Branches back to "ldx #$07" jmp * //Infinite loop to the memory address where the "jmp" instruction is. MyLabel = *+$20 //The current memory address +$20 will be set as value for MyLabel. MyLabel2 = 5 * 6 //MyLabel2 will get the value 30 due to the multiplication.
Comments
Comments can be placed at the end of any directive or instruction, or they can be the only content in a code line. They are ignored by the assembler, so a code line that has been commented out is not processed at all. The comment markers are // and ; that work equally.
Block comments are also supported, where multiple code lines can be commented out, or the source code can contain a bigger block of text without prefixing each line with the comment marker. The block comment markers are /* for opening and */ for closing the block.
Examples
MyLabel lda #$0e //This is a comment for the instruction. MyLabel2 ldx #$06 ;This is also a comment, with the alternative comment marker ";". MyLabel3 //ldy #$00 //Now this is just a line with "MyLabel3" in it, the instruction is ignored. /* Some optional comment text, and the encapsulated code lines are ignored. sta $d020 //Ignored. stx $d021 //Ignored. */ nop //The "nop" instruction is actually processed as valid code content.
Directives
Directives are control commands for the assembler. The generally accepted format is:
[label] .directive parameter(s) [comment]
Specific parameters and formatting exceptions will be explained in the description of each directive. Certain directives have alternative names (aliases), they are interchangeable with the official name.
Please note that Labels used as directive parameters must have prior definitions, meaning their value (usually a constant or a memory address) must be defined before the directive code line in the source code.
.target
Sets the target architecture by specifying the CPU type. It's a good practice to put this directive to the top of every source file you have, so the assembler will know what kind of assembly language you used.
In theory one could make a project which compiles different source files, or even different sections of the same file to different CPU types, but that should be avoided, and in the context of this assembler it wouldn't make much sense.
See the supported CPU types listed above. They are case insensitive.
Change! This directive used to also set the memory size (max 128 MB) of the virtual memory buffer where the project's output bytes are generated. This is no longer necessary, now a project can use the a full 32 bit address space. The directive still takes a second parameter to preserve compatibility with existing source code files, but this memory size parameter is ignored.
Format
.target "CPU Type"
Examples
.target "6502" .target "65C02" .target "Gameboy"
Alternative.cpu
.format
The assembler can output the compiled code and data into a plain stream of bytes, but it also can handle various, more or less complex file formats. You can pick one, depending on your project, ranging from plain Binary to a Gameboy ROM cartridge format with complex headers and checksum calculation.
You can only choose one. If you enter multiple selections, the last one will be used to generate the output file.
Read about the supported Output File Formats here.
Format
.format "Output Format"
Examples
.format "bin" .format "prg" .format "gb"
.org
Instructions and data bytes are always placed in the currently selected memory segment, right at the Current Memory Address, which is also called as Program Counter. The Originate directive sets this pointer to a defined memory address, to control where the program will be compiled in memory.
Format
[label] .org MemoryAddress
Optionally you can put a label in front of .org, then this label will get the selected address as value.
Examples
.org $2000 .pc $2000 *= $2000
Alternatives.pc*=
.equ
Assigns a constant value to a Label, which later can be used as directive parameter, instruction operand, part of an expression etc. The value may be a number, another Label with previous definition, or an expression that evaluates to a number. Label values can be assigned only once, unless you use the .var directive.
If the Label is an existing entry marked as Variable, then this directive updates its value.
Format
Label .equ Value
The = character also can be used instead of .equ to make programming easier.
Examples
MyValue .equ 123 //Works only for the first time, unless it's a Variable. MyValue = Start + $0200 //Works only for the first time, unless it's a Variable. Counter = Counter + 1 //This is a Variable in our example, so this works anytime.
Alternative=
.var
Creates a Label marked as Variable. It has to be a Label that doesn't exist yet (neither as a Constant or a Variable), and then it can be updated in the code with the .equ directive. Which has the shortcut "=", so it can be just updated as VariableName = NewValue.
Format
Label .var Constant
Example
Counter .var 10 //Create a Variable with the initial value 10. Counter = Counter - 1 //Update the Variable's value, even by using expressions.
.random
Creates a Variable (see the .var directive) with a random value, between 0 to 255 by default, but the limits can be customized. Afterwards this value can be used in the code, or can be modified the same way Variables can be modified.
The "seed value" for the random number generator is an important thing to mention. Each time the assembler runs, the random number generator gets a new seed value, calculated from the current time of the computer's clock. This value gets saved and restored between compiler passes, so the same random numbers will be generated in both passes for consistency.
If you need more control over the seed value before a random number is generated, it can be set by .setting using "RandomSeed". For most projects the default seed would be just fine. But if you need your code to generate the same random numbers every time it gets compiled, you need to set your own Random Seed value.
If you need to generate one or more random bytes as data, use .generate "random"
Format
Label .random [Min], [Max] Label .random [Max]
Example
Choice1 .random 1, 10 //Create a Variable with a random value between 1 to 10. .setting "RandomSeed", 1933 //Initialize the random number generator with a custom seed. Choice2 .random 55 //Create a Variable with a random value between 0 to 55.
Alternative.rnd
.setting
Updates a setting value in the default/current settings of Retro Assembler. The defaults can be changed in retroassembler-settings.xml and then some of them can be overridden by command line arguments when the assembler is launched. A few of these settings can't be changed using this directive because they are needed during initialization.
The setting has to exist by name, and the value type has to be the same what the chosen setting expects. The value types may be string, integer or boolean.
See the configurable setting values for details.
Format
.setting "SettingName", Value
Alternative format
.setting "SettingName" = Value
Examples
.setting "SomethingWithString", "Example value" .setting "SomethingWithInteger", 123 .setting "SomethingWithInteger", myLabel .setting "SomethingWithBoolean", true .setting "SomethingWithBoolean", false
.breakpoint
Creates a breakpoint, that will be saved into the Debug file.
If there is at least one breakpoint in the source code, the Debug file will be saved.
Note: This function doesn't work well with source code using multiple memory banks, for obvious reasons.
Format
.breakpoint [IF Condition]
Examples
nop .breakpoint lda CurrentColor .breakpoint "A == $01" sta $d021The output in the Debug file will be something like this:
BREAKPOINT $0821 BREAKPOINT $0824 IF A == $01 NtscSystemFlag $0070 NtscPlayCounter $0071 irq $0855 CurrentColor $08b1
If all you need is saving the Debug file with the used Labels without specifying breakpoints, use this in your code:
.setting "Debug", true
.closelabels
This directive forcibly closes the range of currently opened (still addressable) Local and Regional labels. It's mainly for unconventional use cases of Regional labels, should you decide to utilize them outside of a Macro or Function for some reason. Then by using .closelabels you can reset these labels for reusability reasons.
Change! With the removal of Regional labels, this directive is no longer useful.
Format
.closelabels
Prints the debug text on the console while compiling the code in the 2nd pass. The parameters can be combinations of strings, numbers, labels and expressions. Value separating commas are optional.
Format
.print parameter(s)
Example
.print "The current memory address is " * ", how cool is that!"
Alternatives.debug
.error
Prints a user raiser error message on the console while compiling the code in the 2nd pass and stops the compiler. The parameters can be combinations of strings, numbers, labels and expressions. Value separating commas are optional.
Typically it would be placed into an .if block.
Format
.error parameter(s)
Example
.if (* > $2010) .error "The current memory address is " * ", which is too high..." .endif
.end
The loading and therefore processing of source code lines stop at the line where this directive is used.
This can't be used in conditional code like in an .if block because it gets processed in the source code loading stage, so it will just leave an open .if as the last line of code.
Format
.end
.include
Includes the content of a source code file at the directive's code line, as if it was part of the main source code file's contents. Include files may use the .include directive to load other files, up to 16 levels of depth.
If the file is without a full path, the assembler tries to find it in known include directories. The defaults are the input source code file's directory, the assembler application's base directory and the include directory under these two. The rest of the lookup directories can be set up in the retroassembler-settings.xml file.
Format
.include "filename.ext"
Example
.include "C64_Registers.inc"
.incbin
Loads a binary file into the current memory segment, either at the current memory address (that you can control with a prior .org command), or at the memory location specified by the file's 2-byte load address header in auto mode. It's good for loading graphics assets, music and other data content, if you want to use the assembler as a linker.
If the file is without a full path, the assembler tries to find it in known Include directories. The defaults are the input source code file's directory, the assembler application's base directory and the include directory under these two. The rest of the lookup directories can be set up in the retroassembler-settings.xml file.
Unless you use the auto property, you can optionally set an Offset and even a Length, to control what sections to load from a more complex binary file. But in this mode the file is loaded at the current memory address, so make sure you set that up correctly beforehand.
Format
.incbin "filename.ext", [Offset], [Length] .incbin "filename.ext", auto
Example
.incbin "music.bin" auto //Load the file at $1000 where it belongs.
.segment
Segments are handled as separate virtual memory buffers within the assembler. They can be used to separate parts of the code, data blocks, memory banks on systems where they can be paged in, etc. Each segment belongs to a chosen memory bank.
When a segment is first mentioned, it gets created. In further cases the assembler simply switches to the existing segment by the same name and continutes to put instructions and data to the selected segment's current memory address.
If the bank number is missing from the first mention of a new segment, it gets created under the current bank. The current bank is where the last used segment is hosted, or if a new bank was just created, then it's the new, empty bank.
The referenced bank number must exist already, set up by a previous .bank directive, otherwise the assembler returns with an error message.
When a new segment is created in a memory Bank, the first segment of the Bank always gets a specific start address set, to the Bank's mapping address. Subsequent segments in the same bank are created as relocateable segments without a specific start address.
If the source code file doesn't specify a memory address (with .org) in a relocateable segment before the first instruction, the segment's contents will be relocated during compilation time, right behind the previous segment's last used memory address.
If you put at least one instruction into a relocateable segment, you are no longer allowed to set a specific memory address for the rest of the data in the segment. You have to create a new segment for those instructions or data bytes.
The assembler creates four standard segments by default: Code,Lib, Data and BSS, in this order, all in the default Bank 0. These can be accessed with directive shortcuts. The Code segment has a default start address of $0800 (or $0150 in a Gameboy project), the others are created without a specific start address, meaning they can be relocated during code compilation.
At the end of code compilation, if there are multiple segments in actual use, the assembler saves a merged file for each Bank. Optionally it can save each segment into individual files, by the setting "OutputSaveIndividualSegments".
The assembler also can save a "[CodeFileName]-Info.txt" file with information about each Bank and Segment used, with lowest and highest memory addresses, so the saved individual segments can be loaded to the correct memory address in your choice of linking method.
Format
.segment "Name", [BankNumber]
Examples
.segment "Scroll", 0 //New segment "Scroll" into Bank 0. .segment "Code" //Choose existing segment "Code" .bank 1, 16, $4000 //Create the new Bank 1 .segment "Tiles" //Create a new segment "Tiles" in Bank 1, because Bank 1 is the current bank. //Save individual segments in Bin/Prg/Txt formats .setting "OutputSaveIndividualSegments", true
You may do rapid switching between segments to separate certain data types. For example:
.segment "Code" //".code" would do the same, as shortcut. (Scroller subroutine code lines) .segment "Data" //".data" would do the same, as shortcut. .stext "hello, this is my scroll text!" .byte ' ', $ff //End of the scroll text with an additional space before repeat. .segment "Code" //".code" would do the same, as shortcut. (other subroutines)
This way the scroller code and the scroll text can be kept near each other in the source code itself (they may even come from an include file), but the Code and Data would still be separated in memory during code compilation. In this example the scroll text bytes get placed after the (other subroutines) instructions in the output binary file.
.code, .lib, .data, .bss
Shortcut to the default Code, Lib, Data and BSS segments, respectively. It's the same as using the .segment directive with the selected segment's name, such as:
.segment "Data"
Format
.code .lib .data .bss
Note that these segments are created in memory Bank 0.
.bank
Creates a new memory bank, or updates an existing bank's properties.
Memory banks act as a storage container for one or multiple memory segments. They don't do much for the typical project for a computer using the 6502 CPU, because usually the maximum it can address is 64 KB. But for example the Commodore 128 has 128 KB RAM and switchable memory banks, or a cartrige ROM can use switchable banks as well.
The real use for banks is with Gameboy projects, where anything larger than 32 KB needs to utilize ROM bank switching on the cartridge.
Each project gets a Bank 0 created by default, with 64KB size, mapped to $0000. The Code, Lib, Data and BSS segments are all created for Bank 0. If necessary, you can update this Bank 0's properties with the .bank directive, to change its size and mapping address for special projects.
Optionally an Info string can be set for each bank. This is used in special cases, like the NES ROM format builder identifies bank types and numbers by this value. You can read more about the NES output format here.
It's mainly up to the developer to stay within the Bank's limits. Saving full banks can be enabled by the setting "OutputSaveEntireBanks" (always on for Gameboy ROM output format) if that's what the project needs. Since the size and mapping address is set for each Bank, the assembler, after code compilation, saves the banks clipped down to the specific bank size. Code and data that's entered in the bank's segments outside the bank's boundaries are thrown away. So if you have a bank that's 16KB in size and maps to $4000, then even if you have code or data compiled to $8000+ in the virtual memory, those bytes will be ignored on save.
If necessary, you can opt in to save individual segments beside the banks, without clipping applied.
Bank numbers are zero based, and you may create gaps in the number of banks if you need to. The missing banks will be filled in with $00 in a Gameboy ROM and in other possible file formats that rely on saving multiple banks. The NES ROM builder doesn't use the bank numbers, only the Info strings.
If there is only Bank 0 created (which is likely for most assembly projects), the assembler doesn't deal with banks, unless it's enforced in settings, or the output file format requires it. The output file names will not be marked with the bank's number like "MyCode-Bank0.bin", it will be saved as "MyCode.bin"
Format
.bank BankNumber, SizeKB, MapAddress, [Info]
Examples
.bank 0, 16, $0000 //Update the existing Bank 0 for a Gameboy ROM .bank 1, 16, $4000 //Create Bank 1 for a Gameboy ROM .bank 0, 16, $c000, "NES_PRG0" //Create PRG Bank 0 for a NES ROM //Save full banks with clipping in Bin/Prg/Txt formats .setting "OutputSaveEntireBanks", true //Save individual segments (without clipping) in Bin/Prg/Txt formats .setting "OutputSaveIndividualSegments", true
.namespace
Labels are created in the Global Namespace by default. This one doesn't have a visible Namespace Name when the Labels are printed out. You can change the current Namespace manually, if a section of your code needs to use a specific Namespace.
Macros create their own unique Namespaces, so you don't need to use this within Macros. But if you know what you're doing, the assembler won't stop you. You can use this to access Labels and Values created outside the Macro in a specific Namespace. But it's important to note that labels created within a Macro will always use the auto-generated Namespace that the Macro gets when it's called from your code. If you don't understand this statement, you better don't use .namespace inside a Macro to avoid confusion.
When a Label is referenced, the system tries to find it inside the currently called Macro, or in the chosen Namespace (if applies), and if it's still not found, it tries to find it in the Global Namespace. This way Global Label override and fallback can be achieved in expert mode.
The directive can be called with a specific name which should be unique. If somehow you specify a Namespace that a Macro uses when it's being called, the outcome is undetermined. Best of luck to you!
When the name "Global" is used (it's checked in a case-insensitive way) then the assembler switches back to the default Global namespace. The same thing happens when the directive is called without any Name string set, to provide an easy shortcut.
Format
.namespace "MyCustomNamespace" .namespace "Global" //Case insensitive lookup for "global", switches to the Global Namespace. .namespace //Switches to the Global Namespace as a shortcut.
.region
Regions are logical blocks that encapsulate one or more source code lines. This directive is ignored by the assembler, but can be used in certain text editors to fold regions on demand. It's just like the #region directive in Visual Studio, purely a visual element.
This directive must be closed by using .endregion
Format
.region [Region name as free text] (Code lines) .endregion
.endregion
Closes the previously opened .region directive, so the encapsulated source code lines can be folded in certain text editors. It's just like the #endregion directive in Visual Studio, purely a visual element.
Format
.endregion
.function
Functions are logical blocks that encapsulate one or more source code lines, that are meant to be called as a subroutine. They don't have calling parameters, and internally this directive just converts the function's name into a Label.
Functions can be called in the code using "FunctionName()". The assembler replaces this with the target CPU's "call subroutine" instruction, for example "jsr FunctionName" for 6502 code, or with the appropriate instruction for other CPU types.
This directive must be closed by using .endfunction
Important change is the addition of the Setting OmitUnusedFunctions. This signals to the assembler that unused (unreferenced, not called) Functions should be removed during compilation time. This is helpful if you want to use Functions to build a Library that you can include in several projects, and only the parts that are actually used will be added to the project. Usage is detected in the main source code, in Macros that are actually called, and in Functions that are actually called, with recursive lookup.
This behavior can be turned on by using .setting "OmitUnusedFunctions", true anywhere in the source code.
Format
.function FunctionName() //Or as a friendly alternative... .function FunctionName
Example
.function Scroller() lda #$07 sta $d016 (other code lines) .endfunction //Serves as the "return from subroutine" instruction, eg "rts".
Calling example
lda #$0f sta $d020 Scroller() //Same as "jsr Scroller". Lda #$00 sta $d020
Please note that you can't open a new .function or .macro inside a Function, but you are free to use .loop, .if and .while directives.
.endfunction
Closes the previously opened .function directive. The assembler replaces this with the target CPU's "return from subroutine" instruction, for example the "rts" instruction for 6502 code, or with the appropriate instruction for other CPU types, so the subroutine can return automatically after the last code line. You may put your own "rts" instruction into the function at the point of your chosen return, but at the end the assembler will always add an "rts" as closure.
Format
[Label] .endfunction
Examples
.endfunction Return .endfunction //Marks the "return from subroutine" instruction with the Global Label "Return".
Alternative.endf
.macro
Macros are logical blocks that encapsulate one or more source code lines, that are compiled into the segment memory at the place of the macro call, using the arguments set by the macro call.
A Macro can have zero, one or more parameters. Each parameter can optionally get a default value, in case the parameter is not specified in the macro call itself. If you don't set a default value for a parameter, it will be handled as number 0 and you better set a value for that parameter during the actual macro call, unless you happen to need 0 there.
The parameter names don't actually get created as Global Labels, so you can reuse parameter names or you can use names that you defined as a Label elsewhere.
Macros can access Global Labels that were defined outside of the Macro. But when Normal Labels and Local Labels are defined inside the Macro, those will be stored under the Macro's own Namespace Context. This means that more than one Macro can define the same Label like "MyLabel". These won't create Global Labels and "MyLabel" will be unique for each Macro, and also for each call of that Macro in the source code.
As mentioned, you can and should use Local Labels like @Loop, @1 etc for those throwaway Labels. When a Macro is called, the assembler keeps the Local Labels open (defined) and you can define a Local Label after a Macro call, then for example conditionally jump to it from in front of the Macro call, it will work.
Due to the Namespace Context that Macros and their Labels use, it's not an issue to call a Macro from inside a Macro, and so on and on, up to a level you are comfortable with.
For Experts, the Normal Labels defined under a Macro can be reached from outside of the Macro call, by something like this: "MyMacro_2.MyLabel" Since these are somewhat unique for each and every Macro call, you can find your desired Label in the Debug text file or in the optional Label printer (calling option "-x") but you only need this if you really want to jump back into the middle of a Macro's generated code from the rest of the source code for some hacky reason.
As this might be a bit complicated, so pay attention to the example below where I'll try to highlight the features.
This directive must be closed by using .endmacro
Format
.macro MacroName( [Parameters=[DefaultValues]] )
Example
.macro SetColors(BackgroundColor=$06, BorderColor=$0e, MemAddress) lda #BackgroundColor sta $d021 lda #BorderColor sta $d020 ldx MemAddress stx $3300 .endmacro
Calling examples
MyColor .equ $09 SetColors($0b, $0f) //BackgroundColor is $0b, BorderColor is $0f. SetColors(, MyColor+1) //BackgroundColor is the default $06, BorderColor is $0a ($09 + 1). SetColors() //Use the default values $06 and $0e for the colors.
Note that we never set a value for MemAddress, so it keeps reading the value from memory address $0000.
A more complex macro example using Normal labels (such as Red) as variables.
//SetColor() macro for SNES which enters an RGB value into the selected color. //The color palette index is selected in $2121 ($00 is for the background) //and the color value is entered through $2122 as a 16 bit value, meaning //it has to be entered as a series of two 8 bit values. //Sets the currently selected SNES color using a 24 bit RGB value. .macro SetColor(RGB, Address=$2122) //Get the separated RGB colors out of the 24 bit RGB value. //Example: RGB = $000f1f (R=$00, G=$0f, B=$1f) for a nice Blue //Each color value can be between $00-$1f (5 bits), but keep //all 8 bits for correction below. Red .var (RGB & $00ff0000) >> 16 Green .var (RGB & $0000ff00) >> 8 Blue .var (RGB & $000000ff) >> 0 //Repair the color values if they are outside the 5 bit limit. //Instead of just cropping the bits, use the highest intensity. //NOTE: This macro easily could be converted to something that //takes real 24 bit color values with $00-$ff intensity and //converts them to 5-bit intensity by bit shifting ($ff >> 3 = $1f) .if Red > $1f Red = $1f .endif .if Green > $1f Green = $1f .endif .if Blue > $1f Blue = $1f .endif //Combine these potentially repaired values into //15 bit RGB colors for the SNES (the highest bit is unused) Color .var (Blue << 10) | (Green << 5) | (Red << 0) //Set the lower byte of the 16 bit color value... lda #<Color sta Address //Then set the higher byte of the value to enter the full color. lda #>Color sta Address .endmacro
Please note that you can't open a new .macro or .function inside a macro, but you are free to use .loop, .if and .while directives, that may be controlled by the calling arguments of the macro.
.endmacro
Closes the previously opened .macro directive.
Format
.endmacro
Alternative.endm
.loop
Loop blocks are logical blocks that encapsulate one or more source code lines, that get compiled into the segment memory LoopCount times in a row. The maximum limit is 100,000.
This directive must be closed by using .endloop
Further .loop directives can be nested under a .loop, just indent the code to make it easier to follow.
Format
.loop LoopCount
Example
.Loop 8 nop .endloop
This example is intentionally simplistic, but you can do some clever things with loops, especially if you keep modifying a variable value inside the loop, and use that value as a code modifier.
.endloop
Closes the previously opened .loop directive.
Format
.endloop
Alternative.endl
.if
If blocks are logical blocks that encapsulate one or more source code lines, that get compiled into the segment memory only if the conditional value or expression evaluates to 1 (true).
Since it works with expressions, arithmetic and logical comparisons etc, it can be a rather powerful tool.
This directive must be closed by using .endif
Further .if directives can be nested under an .if, just indent the code to make it easier to follow.
The .if directive block does not close @Local labels, so the code can reference local labels created before and after the block. You can define @Local labels inside an .if block, but if you want to reference it from outside the block, it will only compile if the .if block's condition was true, therefore it was compiled into the code.
Format
.if Condition
Example
.if (SomeValue >= 2) || (OtherValue == 13) lda #$00 sta $d020 .endif
.endif
Closes the previously opened .if directive.
Format
.endif
.while
While blocks are logical blocks that encapsulate one or more source code lines, that get compiled into the segment memory in a loop, as long as the conditional value or expression keeps evaluating to 1 (true) during each iteration.
Since it works with expressions, arithmetic and logical comparisons etc, it can be a rather powerful tool.
Given how easy it is to get into an endless loop with a badly set condition, you must be careful with this. Having some variable like a counter or other value that is constantly (or just sometimes) updated inside the block is key.
If the loop cycle reaches 100000, the parser will take it as an infinite loop detection and will return with an error message. If you need more loop cycles to achieve what you need, you will need to find a different solution.
You may use the .break directive to terminate the loop on a chosen condition set by the .if directive.
This directive must be closed by using .endwhile
Further .while directives can be nested under a .while, just indent the code to make it easier to follow.
Format
.while Condition
Example
MyCounter .var 0 .while MyCounter != 20 sta $3200 + MyCounter MyCounter = MyCounter+1 .endwhile
.endwhile
Closes the previously opened .while directive.
Format
.endwhile
Alternative.endw
.break
Terminates the .while loop cycle where it's placed into. It can only be used in .while blocks.
Format
.break
Example
MyCounter .var 0 .while MyCounter != 20 //Run for 20 loop cycles! .if MyCounter == 5 //Actually I changed my mind, 5 is enough. //(Silly example but you get it.) .break .endif sta $3200 + MyCounter MyCounter = MyCounter+1 .endwhile
.align
Aligns the upcoming instructions or data to the next "round" memory address. The alignment value must be the power of 2, such as 2, 4, 8, 16, 32, 64 etc, up to 1 megabyte.
If the filler byte is not set, the bytes only get allocated in memory without setting a byte value for them. This makes it possible to align purely allocated bytes too.
Alignment works in relocatable memory segments as well.
Format
[label] .align Alignment, [Filler byte]
Examples
//Align code/data to the next round $xx00 memory address, only allocate the skipped bytes. .align $100 //Align code with 6502 NOP instructions .align $80, $ea
.storage
Preserves the following Length number of bytes in the memory to be used as storage bytes.
If the filler byte is not set, the bytes only get allocated in memory without setting a byte value for them. This can be used to mark an array in memory, outside the compiled binary file's saved bytes, to be used by the program. For example in a Gameboy game the compiled code is in ROM banks, but the RAM from $c000 can be preserved using allocated bytes. It's better to use this to allocate an array, instead of just making a label for it, especially if the array is followed by similarly allocated bytes and words by using ButtonState .byte ? etc.
Format
[label] .storage Length, [Filler byte]
Examples
//Allocate $20 bytes in memory, without setting a byte value for those bytes. .storage $20 //Set $20 bytes in memory, filled with byte value $ff .storage $20, $ff
Alternatives.ds.fill
.byte
Puts one or more bytes at the current memory address.
The accepted, comma separated value types are 8-bit numbers ($00-$ff), characters and strings. Characters and strings are converted to ASCII bytes, just like the .text directive does it.
Negative numbers are also accepted, with the limitation of max 7 bits.
The ? value can be used for memory allocation, without setting any value at the current memory address.
Format
[label] .byte Value, [Values]
Examples
.byte $12, %1001, <MyLabel, '\t', "My string value" //Byte allocation MyAllocatedValue .byte ? MyAllocatedValues .byte ?, ?, ?
Alternative.b
.word
Puts one or more words (16 bit values) at the current memory address.
The accepted, comma separated value types are 16-bit numbers ($00-$ffff).
Negative numbers are also accepted, with the limitation of max 15 bits.
The word's two bytes are put into the memory buffer in the order of the target CPU's endianness. For the 6502 family it means that $1234 is entered as "$34, $12".
The ? value can be used for memory allocation, without setting any value at the current memory address.
Format
[label] .word Value, [Values]
Examples
.word $1234, $12, %1001, MyLabel //Word allocation MyAllocatedValue .word ? MyAllocatedValues .word ?, ?, ?
Alternative.w
.dword
Puts one or more double words (32 bit values) at the current memory address.
The accepted, comma separated value types are 32-bit numbers ($00-$ffffffff).
Negative numbers are also accepted, with the limitation of max 31 bits.
The word's two bytes are put into the memory buffer in the order of the target CPU's endianness. For the 6502 family it means that $12345678 is entered as "$78, $56, $34, $12".
The ? value can be used for memory allocation, without setting any value at the current memory address.
Format
[label] .dword Value, [Values]
Examples
.dword $12345678, $1234, $12, %1001, MyLabel //Double word allocation MyAllocatedValue .dword ? MyAllocatedValues .dword ?, ?, ?
Alternative.dw
.lobyte
Puts one or more bytes at the current memory address, using the entered value's Low byte (bits 1-8).
The accepted, comma separated value types are 16-bit numbers (0-65535), that also include 8-bit numbers (0-255).
Negative numbers are also accepted, with the limitation of max 15 bits.
Format
[label] .lobyte Value, [Values]
Example
.lobyte $1234, MyLabel
This is the same as
.byte <$1234, <MyLabel
.hibyte
Puts one or more bytes at the current memory address, using the entered value's High byte (bits 9-16).
The accepted, comma separated value types are 16-bit numbers (0-65535), that also include 8-bit numbers (0-255).
Negative numbers are also accepted, with the limitation of max 15 bits.
Format
[label] .hibyte Value, [Values]
Example
.hibyte $1234, MyLabel
This is the same as
.byte >$1234, >MyLabel
.loword
Puts two or more bytes at the current memory address, using the entered 32 bit value's Low word (bits 1-16).
The accepted, comma separated value types are 32-bit numbers ($00-$ffffffff).
Negative numbers are also accepted, with the limitation of max 31 bits.
Format
[label] .loword Value, [Values]
Example
.loword $12345678, MyLabel
.hiword
Puts two or more bytes at the current memory address, using the entered value's High word (bits 17-32).
The accepted, comma separated value types are 32-bit numbers ($00-$ffffffff).
Negative numbers are also accepted, with the limitation of max 31 bits.
Format
[label] .hiword Value, [Values]
Example
.hiword $12345678, MyLabel
.encoding
Sets the text encoding type and case, used by directives .text and .textz
When a string or character is saved in the target memory, it can be encoded in various ways. Standard ASCII characters are simple, stored as bytes between $00-$7f. Others are more complicated.
The default settings are Latin1 encoding type, mixed case.
Format
.encoding "Type", "Case"
Type | Description |
---|---|
latin1 | Latin1 encoding for Western European text. Each character is stored as one byte. (Default setting.) |
latin2 | Latin2 encoding for Eastern European text. Each character is stored as one byte. |
ascii | ASCII encoding, only the bytes between $00-$7f will be kept, others will be ignored. |
atascii | ATASCII encoding for Atari. Same as ASCII, but with modified values for the \t, \n, \a, \b control codes. |
screencode | The same as ScreencodeCommodore. |
screencodecommodore | Screencode conversion for Commodore computers, where lower case characters are between $00-$1f instead of $60-$7f. |
screencodeatari | Screencode conversion for Atari computers. |
utf8 | UTF-8 text. ASCII characters are stored as one byte, others may take up two or more bytes. |
unicode | 16 bit Unicode text (little endian). Each character is stored as two bytes. |
Case | Description |
---|---|
mixed | Mixed case, the characters are Not Modified. (Default setting.) |
lower | The characters are modified to be lower case. |
upper | The characters are modified to be UPPER CASE. |
If the "Case" parameter is not provided, it defaults to "mixed".
Optionally you can enter a Codepage Identifier value as integer to use a specific encoding understood by .NET Core.
.encoding Codepage, "Case"
Examples
.encoding "latin1", "mixed" //Latin1, mixed case (Default settings) .encoding "ascii", "mixed" //ASCII, mixed case, same as .ascii .encoding "ascii" //ASCII, case not provided -> mixed case .encoding "ascii", "upper" //ASCII, UPPER CASE .encoding "screencode", "lower" //Screencode, lower case, same as .stext .encoding 28595, "mixed" //ISO 8859-5 Cyrillic, mixed case
.text
Puts one or more bytes at the current memory address, by converting strings to text bytes, using the selected text encoding and case. See the .encoding directive.
The accepted, comma separated value types are characters and strings.
Format
[label] .text Value, [Values]
Example
.text "My String Value", 'c'
Alternatives.t
.textz
Puts one or more bytes at the current memory address, by converting strings to zero-terminated text bytes, using the selected text encoding and case. See the .encoding directive.
The accepted, comma separated value types are characters and strings.
In case of multiple parameters have been used, the string gets terminated by $00 only after the last parameter.
Format
[label] .textz Value, [Values]
Example
.textz "My String Value", 'c'
.stext
Puts one or more bytes at the current memory address, by converting strings typically to simplified ASCII bytes used in scroll texts and other, more compact text displays.
The DefaultScreencode setting value is used to set the Text Encoding Type and Case for this directive. See the .encoding directive for the available types and cases.
.setting "DefaultScreencode", "ScreencodeCommodore,Lower" .setting "DefaultScreencode", "ScreencodeAtari,Mixed"
Example: If the Default Screencode is set to "ScreencodeCommodore,Lower", then the strings are first converted to lowercase, then the ASCII bytes are rearranged, so $60-$7f (@abc...) are at $00-$1f, while the symbols and numbers are left intact in the $20-$3f range. The end result is text that fits into a character set of 64 characters, which is typical in demos and games. This is the same as screencode, lowercase encoding.
The accepted, comma separated value types are characters and strings.
Format
[label] .text Value, [Values]
Example
.stext "my scroll text!", 'c'
.ascii
Puts one or more bytes at the current memory address, by converting strings to ASCII text bytes in mixed case.
The accepted, comma separated value types are characters and strings.
Format
[label] .ascii Value, [Values]
Example
.ascii "My String Value", 'c'
.asciiz
Puts one or more bytes at the current memory address, by converting strings to zero-terminated ASCII text bytes in mixed case.
The accepted, comma separated value types are characters and strings.
In case of multiple parameters have been used, the string gets terminated by $00 only after the last parameter.
Format
[label] .asciiz Value, [Values]
Example
.asciiz "My String Value", 'c'
.generate
This directive generates byte values at the current memory address, depending on the selected Mode, and on the Parameter(s) that the Mode expects. Usually these are data tables that can be utilized for demo effects.
Each Mode has default parameters that they can fall back to if a parameter is not provided, but it wouldn't hurt to set each parameter to your liking to avoid strange results.
Depending on what the generator does, if the resulting data values can't be saved in a single byte when an 8-bit CPU is targeted (for example making a sinwave between $00 and $03ff values with the 6502 CPU selected), the generator creates two identically sized data outputs in a row. First the Low Bytes, then the High Bytes of each corresponding data value.
Format
[label] .generate "Mode", [Parameter(s)]
Modes with examples
//Generates a Sine Wave data table. .generate "sinwave", MinValue, MaxValue, Length, RotationDegrees .generate "sinwave", $00, $7f, $100, 270 //Make a wave starting with $00 by rotating it 270 degrees. .generate "sinwave", $00, $7f, $100 //No rotation requested. //Generates a Cosine Wave data table. .generate "coswave", MinValue, MaxValue, Length, RotationDegrees .generate "coswave", $20, $bf, $80, 180 //Start from the middle of the wave by rotating it 180 degrees. .generate "coswave", $20, $bf, $80 //No rotation requested. //Generates a Bounce Wave data table, an arch between Min-Max-Min. .generate "bouncewave", MinValue, MaxValue, Length, Flip .generate "bouncewave", $00, $7f, $100 //Bounce through $00-$7f-$00 .generate "bouncewave", $00, $7f, $100, 1 //Flip the data to be through $7f-$00-$7f //Generates a Random data table, with random numbers between Min-Max. .generate "random", MinValue, MaxValue, Length .generate "random", $00, $7f, $100 //Random bytes between $00-$7f
.memory
Executes various memory operations in the current memory segment.
The current memory location for new code/data doesn't get changed by this directive.
Format
.memory "Mode", StartAddress, Length, [Parameter(s)]
Mode | Parameter 1 | Parameter 2 | Description |
---|---|---|---|
fill | Byte value to fill with. | - | Fills the selected memory fragment with the selected byte. |
copy | Destination address. | - | Copies the selected memory fragment to the selected destination address. |
move | Destination address. | - | Moves the selected memory fragment to the selected destination address, then zeroes out the bytes at the original memory location. |
replace | Original byte value. | Replacement byte value. | Replaces a selected byte value to another in the selected memory fragment. |
add | Byte value to add. | - | Adds the selected byte to each byte in the selected memory fragment. |
subtract, sub | Byte value to subtract. | - | Subtracts the selected byte from each byte in the selected memory fragment. |
shiftleft, left | Number of bits to shift. | - | Shifts each byte in the selected memory fragment to the left by the selected number of bits. |
shiftright, right | Number of bits to shift. | - | Shifts each byte in the selected memory fragment to the right by the selected number of bits. |
negate, neg | - | - | Negates (inverts) the bits in each byte in the selected memory fragment. |
xor, eor | Byte value to use. | - | Performs a bit-wise XOR on each byte in the selected memory fragment. |
or | Byte value to use. | - | Performs a bit-wise OR on each byte in the selected memory fragment. |
and | Byte value to use. | - | Performs a bit-wise AND on each byte in the selected memory fragment. |
Example
.org $2000 .generate "sinwave", $00, $7f, $100 .memory "copy", $2000, $100, $2100 .memory "add", $2100, $100, $80
.memorydump
Saves the selected number of bytes from the target's virtual memory buffer (from the chosen memory bank) into a text file, as .byte source code lines. There will be up to 8 bytes in each line and a separator after every 256 bytes in the generated source code text file.
This can be useful if you need to convert an existing binary file into a source code insert, or if you generate something inside the assembler that you want to save and then manage manually.
Optionally the bytes can be saved into a binary file, just in case that's a better way for the user.
Format
.memorydump "filename.ext", BankNumber, StartAddress, Length, [Binary mode]
Example
.org $2000 .generate "sinwave", 0, $00, $7f, $100 .memorydump "MyWaveData.s", 0, $2000, $100 .memorydump "MyWaveData.bin", 0, $2000, $100, true //And the output in the file is like this: //Memory Dump - Start Address $2000 .byte $3f, $41, $42, $44, $45, $47, $48, $4a .byte $4b, $4d, $4e, $50, $51, $53, $54, $56 (...)
Instructions
Instructions are the actual assembly code that will be converted to an instruction opcode and operand bytes. The generally accepted format is:
[label] mnemonic [operand with chosen addressing type] [comment]
Example
BackgroundColor sta $d020 //In memory this looks like $8d $20 $d0
MOS 6502 Family
On the MOS 6502 family the assembler uses the standard instructions and addressing types, like it's described on 6502 opcodes on 6502.org.
Instructions, including aliases:
adc, and, asl, bcc, bcs, beq, bge, bit, blt, bmi bne, bpl, brk, bvc, bvs, clc, cld, cli, clv, cmp cpx, cpy, dec, dex, dey, eor, inc, inx, iny, jmp jsr, lda, ldx, ldy, lsr, nop, ora, pha, php, pla plp, rol, ror, rti, rts, sbc, sec, sed, sei, sta stx, sty, tax, tay, tsx, txa, txs, tya, xor
The accepted aliases:
Instruction | Alias |
---|---|
bcc | blt |
bcs | bge |
eor | xor |
Some undocumented (also called as illegal) instructions are also supported using the optional -u switch. They are described on oxyron.de (which is an awesome demo scene group, check out their work) but as a brief recap, here are the supported instructions and their aliases:
ahx, alr, anc, arr, aso, asr, axs, dcp, hlt, isb isc, jam, kil, lar, las, lax, lse, nop, rla, rra sax, shx, shy, slo, sre, tas, xaa
Instead of using dnp or tnp or other variants for double and triple nop, the assembler uses the actual nop mnemonic with various addressing types. These are actually useful in demos, the other undocumented instructions not so much, and those can be unstable on different CPUs.
nop #$nn //$80, uses 2 CPU cycles nop $nn //$03, uses 3 CPU cycles nop $nn,x //$14, uses 4 CPU cycles nop $nnnn //$0c, uses 4 CPU cycles nop $nnnn,x //$1c, uses 4 CPU cycles, +1 for crossing a page boundary
These nops can be useful in precise timing, or the nop $nnnn variant is a good way to temporarily comment out a jsr or jmp instruction, by replacing their opcode with $0c in a self-modifying code. Then the memory address they refer to remains intact, the instruction gets ignored by the CPU, and it can be restored when needed.
Mind you, 65C02, 65SC02 and 65C816 use these opcodes to implement new instructions, so if you want 100% compatibility with those CPUs, just refrain from using illegal instructions, including the above mentioned NOPs. You can just use "bit $1234" to temporarily disable a jsr or jmp instruction.
WDC 65C02 / 65SC02
The WDC 65C02 / 65SC02 CPU is an extension over the standard MOS 6502, that came with three new addressing modes and several new instructions. Undocumented (illegal) instructions are not allowed for this CPU type. For information about the changes see 65C02 opcodes on 6502.org.
Instructions, including aliases:
adc, and, asl, bcc, bcs, beq, bge, bit, blt, bmi bne, bpl, bra, brk, bvc, bvs, clc, cld, cli, clv cmp, cpx, cpy, dec, dex, dey, eor, inc, inx, iny jmp, jsr, lda, ldx, ldy, lsr, nop, ora, pha, php phx, phy, pla, plp, plx, ply, rol, ror, rti, rts sbc, sec, sed, sei, sta, stx, sty, stz, tax, tay trb, tsb, tsx, txa, txs, tya, xor
Vendor-specific instructions:
bbr0, bbr1, bbr2, bbr3, bbr4, bbr5, bbr6, bbr7 bbs0, bbs1, bbs2, bbs3, bbs4, bbs5, bbs6, bbs7 rmb0, rmb1, rmb2, rmb3, rmb4, rmb5, rmb6, rmb7 smb0, smb1, smb2, smb3, smb4, smb5, smb6, smb7 stp, wai
The accepted aliases:
Instruction | Alias |
---|---|
bcc | blt |
bcs | bge |
eor | xor |
WDC 65816
The WDC 65816 CPU is an extension over the standard 6502 (and partially over WDC 65C02), that came with several new addressing modes and instructions, 16 bit registers etc. Undocumented (illegal) instructions are not allowed for this CPU type. For information about the changes see 65816 opcodes on 6502.org.
Instructions, including aliases:
adc, and, asl, bcc, bcs, beq, bge, bit, blt, bmi bne, bpl, bra, brk, brl, bvc, bvs, clc, cld, cli clv, cmp, cop, cpx, cpy, dec, dex, dey, eor, inc inx, iny, jml, jmp, jsl, jsr, lda, ldx, ldy, lsr mvn, mvp, nop, ora, pea, pei, per, pha, phb, phd phk, php, phx, phy, pla, plb, pld, plp, plx, ply rep, rol, ror, rti, rtl, rts, sbc, sec, sed, sei sep, sta, stp, stx, sty, stz, tax, tay, tcd, tcs tdc, trb, tsb, tsc, tsx, txa, txs, txy, tya, tyx wai, xba, xce, xor
The accepted aliases:
Instruction | Alias |
---|---|
bcc | blt |
bcs | bge |
eor | xor |
jmp | jml (for long addressing) |
jsr | jsl (for long addressing) |
Due to the changes in this CPU, some instructions may be saved a way you don't expect it:
brk $12 --> $00 $12 brk --> $00 $00 cop $12 --> $02 $12 cop --> $02 $00
The 65816 deals with some unfortunate ambiguity when it comes to instruction opcodes. For example the instructions
The disassembler attempts to deal with this by following the changes in the M and X flags in sequential code.
The assembler gives the control to you. If you enter
In cases like the CPU is set to 16 bit Accumulator mode and your value happens to be an 8-bit number
(or anything smaller than what you need), you may enter it with a Preferred Number Size,
like
If you don't want to manage this so closely, in case of the 65816 you can use two setting values that you can modify via
the .setting directive. RegA16 and RegXY16
can tell the compiler that from now on, until the setting is set to false again, all immediate addressings should be handled
in 16 bit mode. For example
- RegA16 controls the instructions that work with the accumulator (lda, and, adc, cmp etc)
- RegXY16 controls ldx, ldy, cpx, cpy
You may set up macros that turn the CPU flags and these settings on and off to make the code less error-prone.
MEGA65 45GS02 / CSC 4510
The MEGA65 Project attempts to recreate a modernized version of the unreleased Commodore 65 with extended capabilities to build the ultimate 8-bit computer. It uses an FPGA with multiple cores to implement various computer environments and CPUs. It can use the CSC 4510 that was about to go into the Commodore 65, and also their own 32-bit extension, the 45GS02 which should be the default when developing for the MEGA65. Due to this it's best to cover these CPUs in the same section.
Instructions, including aliases:
adc, and, asl, asr, asw, bcc, bcs, beq, bge, bit blt, bmi, bne, bpl, bra, brk, bsr, bvc, bvs, clc cld, cle, cli, clv, cmp, cpx, cpy, cpz, dec, dew dex, dey, dez, eom, eor, inc, inw, inx, iny, inz jmp, jsr, lda, ldx, ldy, ldz, lsr, map, neg, nop ora, pha, php, phw, phx, phy, phz, pla, plp, plx ply, plz, rol, ror, row, rti, rts, sbc, sec, sed see, sei, sta, stx, sty, stz, tab, tax, tay, taz tba, trb, tsb, tsx, tsy, txa, txs, tya, tys, tza, xor bbr0, bbr1, bbr2, bbr3, bbr4, bbr5, bbr6, bbr7 bbs0, bbs1, bbs2, bbs3, bbs4, bbs5, bbs6, bbs7 rmb0, rmb1, rmb2, rmb3, rmb4, rmb5, rmb6, rmb7 smb0, smb1, smb2, smb3, smb4, smb5, smb6, smb7
45GS02-specific instructions:
adcq, andq, aslq, asrq, bitq, cmpq, deq, eorq, xorq inq, ldq, lsrq, orq, rolq, rorq, sbcq, stq
The accepted aliases:
Instruction | Alias |
---|---|
bcc | blt |
bcs | bge |
eor | xor |
eorq | xorq |
eom | nop |
The instruction NOP doesn't really exist in this CPU with that mnemonic. The EOM instruction (End of Map) is used as the counterpart of the MAP instruction, but in other cases it simply operates as a NOP, using the same $EA opcode that the 6502 uses. Therefore you can use NOP in your code if you need to, but the disassembler will see those as EOM.
This CPU adds the Z register, which unlike in the 65C02 is an actual register and doesn't just mean Zero when you use STZ. A few instructions also use the SP stack pointer register. In 45GS02 mode the CPU can use the Q pseudo-register which is the combination of the A, X, Y and Z register values to form a 32-bit value.
Some instructions in 45GS02 mode have a [ZEROPAGE_ADDRESS],Z addressing mode, which allows for referencing a 32-bit memory address indirectly. In the example below, imagine we put 4 bytes from to $72-$75: $80, $c0, $01, $00. This would correspond to the memory address $0001c080 which is clearly outside the 16-bit address space that could use the maximum value of $FFFF. By using this addressing mode, and using the Z register as an index value, we can access any part of the memory without memory bank mapping.
//Values at $72-$75: $80, $c0, $01, $00 for $0001c080 //Put $ff into the memory at $001c083 ldz #$03 lda #$ff sta [$72],z //Note the [] square brackets! //The instruction STA [$72],Z gets compiled like this: eom //$ea -- The same as NOP, used as an opcode prefix. sta ($72),z //$92, $72 -- Note the () round brackets!
The 45GS02-specific Q pseudo-register instructions and these 32-bit addressing modes are accessed by using NOP and/or NEG as opcode prefixes, so even just the opcode of certain instructions can end up being a combination of 2-4 bytes. The disassembler handles these nicely, but you can look at your 45GS02 code disassembled in 4510 mode to see what's going on under the cover.
Intel 4004
The Intel 4004 CPU was Intel's first microprocessor launched in 1971, and with its 4 bit data bus and simplified instructions it's severely limited compared to the better equipped 8 bit CPUs that followed in it's path.
Instructions:
add, adm, bbl, clb, clc, cma, cmc, daa, dac dcl, fim, fin, iac, inc, isz, jc, jcn, jin jms, jnc, jnt, jnz, jt, jun, jz, kbp, ld ldm, nop, ral, rar, rd0, rd1, rd2, rd3 rdm, rdr, sbm, src, stc, sub, tcc, tcs wmp, wpm, wr0, wr1, wr2, wr3, wrm, wrr, xch
Intel 4040
The Intel 4040 is a small extension over the Intel 4004.
Additional instructions over the Intel 4004:
bbs, din, ein, lcr, rpm, hlt or4, or5, an6, an7 db0, db1, sb0, sb1
Intel 8008
The Intel 8008 CPU is an early 8 bit CPU launched in 1972.
Although it has several registers, they are addressed within the instruction mnemonic itself like ADA, ADB, ADC etc. Some instructions have strict numerical parameters, some take 8 bit data or 16 bit address values.
Instructions:
nop, hlt, ret, rlc, rrc, ral, rar rfc, rfs, rtc, rts, rfz, rfp, rtz, rtp inb, inc, ind, ine, inh, inl dcb, dcc, dcd, dce, dch, dcl ada, adb, adc, add, ade, adh, adl, adm aca, acb, acc, acd, ace, ach, acl, acm sua, sub, suc, sud, sue, suh, sul, sum sba, sbb, sbc, sbd, sbe, sbh, sbl, sbm nda, ndb, ndc, ndd, nde, ndh, ndl, ndm xra, xrb, xrc, xrd, xre, xrh, xrl, xrm ora, orb, orc, ord, ore, orh, orl, orm cpa, cpb, cpc, cpd, cpe, cph, cpl, cpm lab, lac, lad, lae, lah, lal, lam lba, lbb, lbc, lbd, lbe, lbh, lbl, lbm lca, lcb, lcc, lcd, lce, lch, lcl, lcm lda, ldb, ldc, ldd, lde, ldh, ldl, ldm lea, leb, lec, led, lee, leh, lel, lem lha, lhb, lhc, lhd, lhe, lhh, lhl, lhm lla, llb, llc, lld, lle, llh, lll, llm lma, lmb, lmc, lmd, lme, lmh, lml adi, sui, ndi, ori, aci, sbi, xri, cpi lai, lbi, lci, ldi, lei, lhi, lli, lmi jmp, jfc, jfz, jfs, jfp, jtc, jtz, jts, jtp cal, cfc, cfz, cfs, cfp, ctc, ctz, cts, ctp rst, inp, out
Intel 8080
The Intel 8080 CPU the successor of the Intel 8008 from 1974. It's somewhat similar to the Zilog Z80 in instruction parameter styling.
Instructions:
aci, adc, add, adi, ana, ani, call, cc, cm, cma cmc, cmp, cnc, cnz, cp, cpe, cpi, cpo, cz, daa dad, dcr, dcx, di, ei, hlt, in, inr, inx, jc jm, jmp, jnc, jnz, jp, jpe, jpo, jz, lda, ldax lhld, lxi, mov, mvi, nop, ora, ori, out, pchl pop, push, ral, rar, rc, ret, rlc, rm, rnc, rnz rp, rpe, rpo, rrc, rst, rz, sbb, sbi, shld, sphl sta, stax, stc, sub, sui, xchg, xra, xri, xthl
Intel 8085
The Intel 8085 is a small extension over the Intel 8080.
Additional instructions over the Intel 8080:
rim, sim
Undocumented instructions:
arhl, dsub, jk, jnk, jnui, jui, ldhi, ldsi lhlx, rdel, rstv, shlx
Nintendo Gameboy
The Gameboy has a custom CPU which is based on the Z80, but has some different instructions and registers.
Instructions, including aliases:
adc, add, and, bit, call, ccf, cp, cpl, daa, dec di, ei, halt, inc, jp, jr, ld, ldd, ldh, ldhl ldi, nop, or, pop, push, res, ret, reti, rl, rla rlc, rlca, rr, rra, rrc, rrca, rst, sbc, scf, set sla, sra, srl, stop, sub, swap, xor, ex
If you're familiar with Gameboy assembly programming, these are alternative ways to do certain things:
//Alternatives for dealing with "HL, decreased" ld a,(hl-) ld a,(hld) ldd a,(hl) ld (hl-),a ld (hld),a ldd (hl),a //Alternatives for dealing with "HL, increased" ld a,(hl+) ld a,(hli) ldi a,(hl) ld (hl+),a ld (hli),a ldi (hl),a //Alternatives for dealing with the High RAM where the hardware registers are ld ($ff00+c),a ld (c),a ld a,($ff00+c) ld a,(c) ld ($ff12),a ldh ($12),a ld a,($ff12) ldh a,($12)
Most instructions that normally just imply working with the "a" register can be written as showing the "a" register, just in case that's how you like it. Some examples:
daa = daa a cpl = cpl a and a = and a,a and b = and a,b and c = and a,c
The assembler tries to be compatible with existing RGBDS source code files, so the brackets in the addressing modes
that use them are accepted with square brackets
A "nop" is placed behind each "halt" and "stop" instruction during code compilation to avoid hardware issues. This behavior can be changed in the Settings Xml file, but it's recommended keep it this way.
Z80
The Z80 CPU is a classic with a wide range of instructions and addressing modes, as it's described on Z80 opcodes on clrhome.org.
Instructions:
adc, add, and, bit, call, ccf, cp, cpd, cpdr, cpi cpir, cpl, daa, dec, di, djnz, ei, ex, exx, halt im, in, inc, ind, indr, ini, inir, jp, jr, ld ldd, lddr, ldi, ldir, neg, nop, or, otdr, otir, out outd, outi, pop, push, res, ret, reti, retn, rl, rla rlc, rlca, rld, rr, rra, rrc, rrca, rrd, rst, sbc scf, set, sla, sra, srl, sub, xor
Most instructions that normally just imply working with the "a" register can be written as showing the "a" register, just in case that's how you like it. Some examples:
daa = daa a cpl = cpl a and a = and a,a and b = and a,b and c = and a,c
Another notable alias is for the ex af,af' instruction, which would swap the contents of the af register pair with its background counterparts. This apostrophe (') character at the end may cause your text editor go haywire due to an unclosed character literal, so it's recommended to write this instruction without that. The assembler handles it both ways.
//This instruction... ex af,af' //Is the same as... ex af,af
Some undocumented (also called as illegal) instructions are also supported using the optional -u switch. The only unique instruction by mnemonic is sll, but there are a bunch of otherwise standard instructions with undocumented addressing modes that you can utilize, if needed.
This usually may not apply to pure Z80 source code, but similarly to the Nintendo Gameboy CPU mentioned above,
the assembler tries to be compatible with existing RGBDS source code files, so the brackets in the addressing modes
that use them are accepted with square brackets
Output File Formats
The assembler is capable of saving the compiled bytes from the target memory banks in various formats. Some are generic, others are system specific. For example it makes no sense to save 65c02 code in Gameboy ROM format, or NES code in a T64 format, even though the NES uses the 6502 CPU similar to Commodore computers.
The output file format can be selected from the command line eg -O=prg, or using the .format directive eg .format "prg"
BIN – Binary file
Binary file with .bin extension. It contains the compiled memory bytes, from the first used memory address to the last used one. If it's chosen in Settings, it may be an entire memory bank. The unset bytes and memory gaps are always filled with $00.
This file contains no meta information about the load address, it's always up to the user to load the file to the correct memory location.
PRG – Binary file with load address header
Program file with .prg extension. It's the same as a Binary file, but the load address is saved as the first few (typically 2) bytes of the file to identify the correct memory location. This is the format that Commodore computers use.
The load address is stored using the target CPU's endianness and address width. Meaning, 8-bit CPUs like the 6502 in Commodore computers store the address on the first 2 bytes, where an address like $0801 is stored as bytes $01, $08.
Projects for other CPUs may use this format as well, it's not limited to the 6502. For a 16 or 32 bit CPU the address header takes up 4 bytes because the load address is stored on 32 bits.
SBIN – Binary file with load address and data length header
Binary file with .sbin extension. It's the same as a Binary file, but the load address is saved as the first few (typically 2) bytes of the file to identify the correct memory location, along with typically 2 bytes of file data length.
The load address is stored using the target CPU's endianness and address width. Meaning, 8-bit CPUs like the 6502 in Commodore computers store the address on the first 2 bytes, where an address like $0801 is stored as bytes $01, $08.
The following data length is stored in a similar fashion, and on a typical 8-bit CPU it takes 2 bytes. If the file length would be bigger than 64 kilobytes, the length value is capped at $ffff.
For a 16 or 32 bit CPU the addresses in the header will take up 4 bytes each because the load address is stored on 32 bits and the files are typically bigger than 64 kilobytes.
T64 – Tape image format for Commodore computers
Tape image file with .t64 extension. Used by Commodore computers (emulators), it's basically a tape with one or more files on it, up to maximum 30 files. The files are saved as PRG files with load address header on each of them. Use this only for 6502 and perhaps for 65C02 / 65816 projects.
D64 – Disk image format for Commodore computers
Disk image file with .d64 extension. Initially it's created as a T64 file so it can hold up to maximum 30 files. Then it's converted to D64 using the VICE emulator package's program "c1541". The path to this VICE directory has to be set up in the retroassembler-settings.xml file, in the VicePath value.
TXT – Configurable text file format
ASCII text file with .txt extension. It contains the compiled memory bytes from the first used memory address to the last used one. If there are gaps in the memory, the unused bytes are skipped and the continuous bytes are organized into Areas. The output for a file may look like this:
c000 78 d8 a2 40 8e 17 40 a2 c008 ff 9a e8 8e 00 20 8e 01 c010 20 8e 10 40 2c 02 20 10 c018 fb a9 00 95 00 9d 00 01 c020 9d 00 02 9d 00 04 9d 00 c028 05 9d 00 06 9d 00 07 a9 c030 fe 9d 00 03 e8 d0 e2 2c c038 02 20 10 fb a9 80 8d 01 c040 20 4c 41 c0 40 40 fffa 44 c0 00 c0 45 c0
Nearly every aspect of the text file format can be customized via the OutputTxt Settings. The values are printed using the .Net string.Format() function, but if you're not familiar with it, here is a primer.
A string value like "{0:x04} " means that the Parameter 0 (which is the number being printed) should be in hexadecimal format, padded up to 4 characters with 0s, in lower case, with a space after it. For example $1ab would be shown as "01ab ".
"{0:X02}" would mean the number should be in hexadecimal format, padded up to 2 characters with 0s, in upper case (mind the upper case X in the string). For example $0d would be shown as "0D".
"{0}" would mean the number should be in decimal format, without any padding. For example $0d would be shown as "13".
The rest of the settings are fairly obvious, they are about where to put spacing, where to put newline characters and what they should be like. I chose the Windows standard "\r\n" for default values. You may also control how many bytes should be printed into a single line.
XEX – Binary file with data chunks and launcher for Atari DOS
Binary file with .xex extension. It contains one or more Chunks of data, and also an optional Launcher to auto start the program after loading. The Atari DOS has the capability of running code in certain chunks, depending on where they are loaded in the memory, while the file is being loaded. This way the program can do a system compatibility check, display a hero image and continue loading afterwards, so the user is not bored during long load times.
This separation of Chunks is achieved by using Segments. By default the code is entered into the "Code" segment (shortcut: .code) so in case of smaller programs, the code can be written without any extra setup, and it just needs the optional Launcher segment to enable auto start after loading.
.target "6502" .setting "OutputFileType", "AtariDOS" //or "XEX" //Start writing the program itself. It will start at $0600. .org $0600 //Create a "Start" label that points to the first line of the executable. Start lda #$00 nop rts //Create a "Launcher" segment which will load this auto start address //where the "Start" label points to, into the memory at $02e0 //Then the computer will start the program there, at $0600 in this example //after the file is loaded. .segment "Launcher" .word Start //Note that the label "Start" is just an example. It can be named freely.
If you need to use multiple Chunks at different memory addresses, create multiple Segments and specify the starting memory address in each using the .org directive.
The Segment named "Launcher" gets special handling. The memory address of it doesn't matter and only the first 2 bytes get loaded, even if you specify more in it.
You can of course opt not to use the "Launcher" segment, you can skip it altogether, or implement it yourself manually, by a code like this:
.segment "My Own Auto Start" .org $02e0 .word Start
Like with any Segment, the assembler will use the starting memory address of the code or data written into the Segment, and set the data length according to the byte content length of the Segment itself.
It's important to know that the Segments are processed in the order of creation, which means that the default Code, Lib, Data, BSS segments are created first, if they contain any bytes of code or data. If you must start your program file by loading a certain segment that comes before your main program needs to be loaded, do it accordingly. Either put the very first segment's data into the default "Code" segment, or skip using these segments altogether and just create your own ones like "Initializer" etc from the beginning. Empty segments are not saved, and the "Launcher" segment is always saved as the last Chunk in the file.
Hint: If you want to place the "Launcher" segment on top of your code for clarity or for code standardizing, make sure you put a ".code" at the end of those lines of code, before any other parts of your program, so it switches back to the default "Code" segment for subsequent source code lines. You can also choose to put the whole Launcher segment creator into a Macro.
.macro Launcher(StartAddress) .segment "Launcher" .word StartAddress .code .endmacro
Of course your mileage may vary here with the segment creation and switching, but I'm sure you will figure this one out.
GB – Nintendo Gameboy ROM format
Nintendo Gameboy ROM file with .gb extension. This format uses a special header and memory banks. The scope of this document doesn't allow for a Gameboy development tutorial, but you may consult the attached example.
In a nutshell, the Gameboy uses a custom CPU with some RAM and switchable memory banks that are 16KB each. Bank 0 is always mapped at $0000, other banks are mapped at $4000.
The Gameboy ROM builder identifies the banks by their bank number, the Info string is not in use (unlike for NES ROMs). If all you want to build is a 32KB ROM where you don't use bank switching, just use the default Bank 0 and the linker will handle it as a 32KB bank between $0000-$7fff.
Multiple banks should be used like this:
//The Main bank 0, mapped to $0000 .bank 0, 16, $0000 //Other banks, mapped to $4000 .bank 1, 16, $4000 .bank 2, 16, $4000 .bank 3, 16, $4000
The ROM format requires having a special header between $0100-$014f, which includes the Nintendo logo, creator info, cartridge descriptors and calculated checksum values. The ROM builder handles all that for you. The number of banks is detected and missing banks are padded up for a valid ROM size. You will need to set up the Reset and Interrupt vectors on the zero page, but the jump at $100 to the starting point of your program is generated by the linker. A typical Gameboy program starts at $0150, the assembler automatically sets this address for the entered code in Bank 0 by default.
You may choose your own Memory Bank Controller (MBC) type, but realistically, look at the calendar... Unless you have some super special needs, just set the other GameboyCartridge... setting values and the linker will figure out which MBC5 cartridge type you need. Just write your bank switching code according to the MBC5 specification and call it a day.
See the Gameboy ROM format Settings that you can set up for your project, it covers everything you need to build a valid Gameboy ROM file with header. Set these with the .setting directive from your main source code file.
NES – Nintendo Entertainment System ROM format
Nintendo Entertainment System ROM file with .nes extension. This format uses a special header and memory banks. The scope of this document doesn't allow for a NES development tutorial, but you may consult the attached example.
In a nutshell, the NES uses a 6502 CPU with minimal RAM and switchable Program (PRG) and Character (CHR) banks. The latter is specifically for graphics, tiles and sprites. PRG banks are 16KB each, CHR banks are 8KB each.
The NES ROM builder identifies the bank types and their sequential numbers solely from the Info string parameter of the .bank directive.
Examples:
//The Main PRG bank, mapped to $c000 .bank 0, 16, $c000, "NES_PRG0" //Another PRG bank, mapped to $8000 .bank 1, 16, $8000, "NES_PRG1" //This PRG bank will be ignored by the linker because NES_PRG2 is missing, //so it stops collecting PRG banks and goes ahead to collect CHR banks. .bank 2, 16, $8000, "NES_PRG3" //The first CHR bank, mapped to $0000 (doesn't matter, it's not code) .bank 3, 8, 0, "NES_CHR0" //The second CHR bank, mapped to $0000 (doesn't matter, it's not code) .bank 4, 8, 0, "NES_CHR1" //Special, completely optional "Trainer" bank, mapped between $7000-$71ff //Just set 1 KB size, only the first 512 bytes will be taken from it. .bank 5, 1, $7000, "NES_Trainer"
The assembler bank numbers don't matter for NES ROMs, only the numbers in the Info string. So you can even make PRG banks use 0-99, CHR banks use 100-199, Trainer use 200... It's all up to you. But when it comes to the bank numbers in the Info strings, they have to start from 0 and be continuous. The linker will start looking for "Does NES_PRG[number] exist?" and increment the number. If it doesn't find the PRG bank by the currently checked number, it goes ahead to look for CHR banks and stops at a missing bank number there as well.
You can use a lot of PRG and CHR banks, even more than 255 each if you need to. The linker utilizes some of the NES 2.0 ROM format extensions so you can build mega ROMs.
See the NES ROM format Settings that you can set up for your project, it covers everything you need to build a valid NES ROM file with header. Set these with the .setting directive from your main source code file.
SNES – Super Nintendo Entertainment System ROM format
Super Nintendo Entertainment System ROM file with .sfc extension. This format uses a special header and memory banks. The scope of this document doesn't allow for a SNES development tutorial, but you may consult the attached example.
In a nutshell, the SNES uses a 65816 CPU with minimal RAM and switchable memory banks that may contain code, graphics or other data. There are two versions of the memory bank layout:
- LoROM: The banks are 32 KB in size, mapped between $8000-$ffff. There are 128 banks to use. (4 MB)
- HiROM: The banks are 64 KB in size, mapped between $0000-$ffff. There are 64 banks to use. (4 MB)
The ROM builder supports all possible SNES ROM settings in theory, they can be set up in the header, but the non-standard ROM types and sizes will require additional work on the user's side to make the ROM file valid. The standard (or accepted) ROM sizes are handled well and the checksum values are calculated correctly for those. The SNESPadding setting is True by default, meaning it will pad the ROM with extra empty banks at the end of the file to reach the (next) valid ROM size, which are the following:
The memory banks are used by their bank number (not by the Info string parameter, like in the NES ROM format), starting from 0 and collected sequentially. Gaps between banks are filled with empty banks. The bank size is determined by the SNESHiROM setting, which is False by default, meaning the LoROM format is in use with 32 KB banks by default.
The ROM header is generated in Bank 0, between $ffc0-$ffdf, and (as far as I know) the interrupt vectors are always in Bank 0, from $ffe0 (technically from $ffe4).
LoROM setup example (32 KB banks)
.setting "SNESPadding", true //Create a valid ROM size by padding with extra, empty banks .setting "SNESHiROM", false //32 KB banks between $8000-$ffff (LoROM format) .setting "SNESCartridgeType", 2 //ROM and SRAM .setting "SNESSRAMSize", 3 //8 KB SRAM .setting "SNESFastROM", true //Fast ROM with 120ns access speed .setting "SNESCountry", 1 //USA (NTSC) .setting "SNESLicenseeCode", 1 //Nintendo's licensee code .setting "SNESTitle", "My test ROM" //ROM title in max 21 characters //The Main bank 0, mapped to $8000 .bank 0, 32, $8000 //Other banks, mapped to $8000 .bank 1, 32, $8000 .bank 2, 32, $8000 .bank 3, 32, $8000
HiROM setup example (64 KB banks)
.setting "SNESPadding", true //Create a valid ROM size by padding with extra, empty banks .setting "SNESHiROM", true //64 KB banks between $0000-$ffff (note the TRUE value!) .setting "SNESCartridgeType", 2 //ROM and SRAM .setting "SNESSRAMSize", 3 //8 KB SRAM .setting "SNESFastROM", true //Fast ROM with 120ns access speed .setting "SNESCountry", 1 //USA (NTSC) .setting "SNESLicenseeCode", 1 //Nintendo's licensee code .setting "SNESTitle", "My test ROM" //ROM title in max 21 characters //The Main bank 0, mapped to $0000 .bank 0, 64, $0000 //Other banks, mapped to $0000 .bank 1, 64, $0000 .bank 2, 64, $0000 .bank 3, 64, $0000
Of course these banks need .segment entries and everything else, and the ROM settings are just examples. Substitute your own needs for a cartridge type, SRAM quantity (may be 0) and so on. See the SNES settings and the SNES Kart document for more info.
SMS – Sega Master System ROM format
Sega Master System ROM file with .sms extension. This format uses a special header and memory banks. This is the same as the Sega Game Gear format. The scope of this document doesn't allow for a Sega development tutorial, but you may consult the attached example.
In a nutshell, the SMS and GG use a Z80 CPU with some RAM and switchable memory banks that are 16KB each. Bank 0 is always mapped at $0000, other banks are mapped at $4000 or $8000, mostly the latter. By convention, Bank 0 and Bank 1 (or just a 32KB Bank 0) are mapped in by default in the first 32KB, and the other banks, if available, should be mapped at $8000.
The Gameboy ROM builder identifies the banks by their bank number, the Info string is not in use (unlike for NES ROMs). If all you want to build is a 32KB ROM where you don't use bank switching, just use the default Bank 0 and set it to 32KB in size.
Multiple banks should be used like this:
//The Main bank 0, mapped to $0000 .bank 0, 16, $0000 //The (other) Main bank 1, mapped to $4000 .bank 1, 16, $4000 //Other banks that will be mapped to $8000 .bank 2, 16, $8000 .bank 3, 16, $8000
Or like this, which is essentially the same:
//The Main bank 0 in 32KB size, mapped to $0000 .bank 0, 32, $0000 //Other banks that will be mapped to $8000 .bank 1, 16, $8000 .bank 2, 16, $8000
The ROM format requires having a special header between $7ff0-$7fff, which includes an identifier string, product code, country code, ROM size and a calculated checksum value. The ROM builder handles all that for you. The number of banks is detected and missing banks are padded up for a valid ROM size. The valid ROM sizes are 32KB, 64KB, 128KB, 256KB and 512KB.
See the SMS & GG ROM format Settings that you can set up for your project, it covers everything you need to build a valid SMS/GG ROM file with header. Set these with the .setting directive from your main source code file.
There is another semi-standard header, called SDSC Header placed between $7fe0-$7fef with information about the ROM and its developer. The ROM builder doesn't create this, but you can do so manually with ease in the source code, if you want to support this in your ROM file.
GG – Sega Game Gear ROM format
Sega Game Gear ROM file with .gg extension.
This is exactly the same as the Sega Master System ROM format, but with a .gg extension that helps emulators differentiate between system types. Both use the Z80 CPU, ROM banks and the same header format.
See the SMS ROM format above and use the SMS & GG ROM format Settings in your source code.
TAP – ZX Spectrum 48K Tape format
ZX Spectrum 48K Tape file with .tap extension. This format is limited to 48 KB of data, between $4000-$ffff. The scope of this document doesn't allow for a ZX Spectrum development tutorial, but you may consult the attached example.
The ZX Spectrum uses the Z80 CPU and its 48K version has 48 KB of RAM, mapped between $4000-$ffff. The first 16 KB is occupied by the ROM, which contains a BASIC interpreter and system routines. While there is a 128K version of this computer, the TAP file format is restricted to 48 KB in this case. Future expansions are possible, if there is demand for it.
The Tape file has multiple headers and data blocks. To make it work in an emulator (or on a real ZX Spectrum), it has to start with a BASIC loader. First this program gets loaded into the memory and when it's executed, it loads and runs the compiled binary file.
BASIC loader example
10 REM Retro Assembler 20 BORDER VAL "0": PAPER VAL "0": INK VAL "7" 30 CLEAR VAL "24575" 50 LOAD "code"CODE 60 RANDOMIZE USR VAL "32768"
The assembler enters the TapClear setting value into line 30 (defaults to $5fff), and the TapStart setting value into line 60 (defaults to $8000) to customize the BASIC loader for the compiled code's needs.
The ZX Spectrum's graphics memory is allocated between $4000-$5fff (roughly), so programs should be loaded from $6000, and in many cases programs start at $8000. This is up to the developer. According to my tests, even programs loaded to $4000 start up fine in the Speccy emulator, but the screen gets messy due to writing into the graphics memory. The Spectrum loads the BASIC program from about $5c00 (just outside the graphics memory) so loading files from $6000 is safe, without the risk of overwriting this small BASIC loader's program. The TAP file builder ignores data you may put below $4000 and above $ffff.
If you are wondering how the Spectrum games load a picture first, and then the game itself, it's done with multiple data blocks in the TAP file. First a BASIC loader is entered, which loads an image file from $4000, then the code/data file from $6000 or higher. The TAP file builder doesn't support this, it's for testing code, so if you are planning to write a game with a hero image, you will need some other utility to link up your final code.
Integration with Text Editors
Source code for Retro Assembler can be written by using any text editor, but it's best to use something that supports user defined languages and syntax highlighting. I made support for two, these might be the most popular code editors today.
If you create a syntax highlighter for your favorite editor, I would be happy to hear from you. It can be added to the assembler package and its documentation with proper crediting.
Visual Studio Code
Visual Studio Code is a powerful, multi-platform IDE from Microsoft. If you don't have a chosen IDE for assembly development, just go with this one.
I created an Extension for it which makes VS Code work with Retro Assembler quite well. It can be downloaded from the Marketplace inside VS Code, just search for "retro assembler" or visit this page: VS Code Extension for Retro Assembler
Make sure you read the Readme of the extension itself in VS Code about how to set it up.
You can set up keyboard shortcuts and the assembler path to compile (and even auto-start) your code directly from the IDE.
VS Code has a bit of an issue with identifying the correct CPU type for the viewed source code file, but you can use the MyFile.6502.asm automatic CPU detection from file name hack explained above, or you can set a default language syntax for the assembly files. This is also covered in the Readme file.
You should consider using the Retro Assembler Light or Dark theme included with the extension, because it will do correct colorization of normal and undocumented instructions, directives etc. I've done what I could to make it look OK with other themes, but the mess of VS Code theme styles is a story for another day. The extension will be updated with new features and coverage for future assembler versions.
Notepad++
Notepad++ is a small but powerful text editor. It was the first editor I made support for to turn it into an IDE. It can use User Defined Languages with syntax highlighting by colorization, folding of comments, functions and macros, and also regions.
You can set this all up by clicking at the menu item Language -> Define your language... and import one (or any/all) of these files that came with the assembler's package, using the Import... button.
File name | Description |
---|---|
RetroAssembler_6502.xml | Syntax highlighting for the MOS 6502 CPU family's instructions and registers. |
RetroAssembler_65C02.xml | Syntax highlighting for the WDC 65C02 CPU family's instructions and registers. |
RetroAssembler_65816.xml | Syntax highlighting for the WDC 65816 CPU's instructions and registers. |
RetroAssembler_4510.xml | Syntax highlighting for the CSC 4510 CPU's instructions and registers. |
RetroAssembler_45GS02.xml | Syntax highlighting for the MEGA65 45GS02 CPU's instructions and registers. |
RetroAssembler_4004.xml | Syntax highlighting for the Intel 4004 CPU's instructions and registers. |
RetroAssembler_4040.xml | Syntax highlighting for the Intel 4040 CPU's instructions and registers. |
RetroAssembler_8008.xml | Syntax highlighting for the Intel 8008 CPU's instructions and registers. |
RetroAssembler_8080.xml | Syntax highlighting for the Intel 8080 CPU's instructions and registers. |
RetroAssembler_8085.xml | Syntax highlighting for the Intel 8085 CPU's instructions and registers. |
RetroAssembler_Gameboy.xml | Syntax highlighting for the Nintendo Gameboy CPU's instructions and registers. |
RetroAssembler_Z80.xml | Syntax highlighting for the Zilog Z80 CPU's instructions and registers. |
After this you must restart Notepad++ so it will utilize the imported file(s) correctly.
The automatically recognized source code file extensions within Notepad++ will be ".asm, .s, .inc", like "musicplayer.asm", but you can change these or add more by going back to the language editor. Select a Retro Asm CPU language and edit the Ext. field on the top.
Whether Notepad++ can recognize the file by extension is questionable, especially if you install language files for more than one CPU. You may have to choose the edited file's language manually in the Language menu. There is no support for the MyFile.6502.asm automatic CPU detection from file name hack explained above – I tried to add it to the extension list and it doesn't work. Maybe in a future version of Notepad++.
Change Log
1/1/2021
- Version 2021.1 released
- The assembler is now built for .NET 5.0 and it runs on Windows, macOS and Linux as a single package.
- Support for the MEGA65 CPUs, the 45GS02 and the 4510 have been added.
- The 65816 instructions that use the Stack Pointer now accept SP as register name, along with S. The disassembler uses SP to show those instructions.
6/16/2020
- Version 2020.12 released
- Empty Macros get removed from the compiled code automatically.
- When the Setting OmitUnusedFunctions is Enabled, empty Functions and their standard calls get removed from the compiled code.
- When the Setting Debug is Enabled, a text representation of the compiled code is saved alongside with the normal debug information. You can change its default filename in the Setting DebugCodeFile or in the Settings Xml file.
- When the Setting OutputSaveInfoFile is Enabled, now Atari DOS (.xex) files create the Info file about memory usage.
6/11/2020
- Version 2020.11 released
- Bug fixed in the parser where an lda <Label or lda >Label (and similar zero page instructions) would process the memory address of Label incorrectly, when Label is defined after the instruction's code line.
5/29/2020
- Version 2020.10 released
- Setting DefaultScreencode added to control the encoding Type and Case that the ".stext" directive uses for text conversion.
- When a file is included using the ".include" or the ".incbin" directive, the file's Directory is automatically added to the list of Known Include Directories. This way a file loaded with full path can include subsequent files without specifying the full path of those nested files.
- The Setting OmitUnusedFunctions is no longer Enabled by default to avoid issues with existing source code. It can be enabled on demand.
- Fixed the issue of Long Branch Handling not working in Macros.
5/17/2020
- Version 2020.9 released
- Serious code generating bug fixed in the Long Branch handler.
- ATASCII and ScreencodeAtari text encodings added in the ".encoding" directive.
5/16/2020
- Version 2020.8 released
- New directive ".lib" added as a shortcut to the new default "Lib" Segment. Short for Library where included Functions may be stored. This is placed between the Code and Data Segments.
- Setting OmitUnusedFunctions added to control whether unused Functions should be omitted from the compiled binary. It's enabled by default which may break some existing projects.
- If OmitUnusedFunctions is enabled, the assembler identifies those Functions that have not been actually called anywhere in the source code and removes them from the compiled binary. This is helpful if you want to build and include a large Function and Macro library, but only use a few of those in your project. If this is an unwanted behavior, the feature can be turned off in Settings.
5/13/2020
- Version 2020.7 released
- New directive ".format" added to replace Setting OutputFormat. The setting itself still works, but shows a warning.
- The ".if" directive block no longer closes @Local labels, so you can reference @Local labels created before and after the ".if" block.
- Setting HandleLongBranch added. When enabled, the assembler automatically fixes Branch Out of Range errors by replacing the branch instruction with its counterpart instruction and an absolute jump to the branch address. In Z80/Gameboy mode jr instructions get replaced by jp instructions. It's not enabled by default.
- Setting AllowUndocumentedInstructions added and set to True by default. This makes undocumented instructions enabled for participating CPUs, but you can disable this manually in your source code (or in the Settings Xml file) if you want to turn them off.
- The -u command line switch for undocumented instructions is now used only in Disassembler mode, where it matters to select it from the command line.
- The -L command line switch has been replaced by -l, but the old switch option still works.
- Setting LaunchEnabled has been renamed to Launch. The old name still works, but shows a warning.
5/9/2020
- Version 2020.6 released
- New directive ".namespace" added. This can change the currently used Namespace for Labels and Values to a custom one, or switch back to the default Global Namespace.
- Random mode added for the ".generate" directive to generate a set of random bytes.
- The Disassembler processes Atari DOS .XEX files to utilize the Chunk load addresses.
- For developers: If you need to use Retro Assembler in your project that needs it, check out the section Integration into Projects.
5/4/2020
- Version 2020.5 released
- New output file type added: Atari DOS binary format with load data chunks and launcher. It uses the .xex file extension.
4/25/2020
- Version 2020.4 released
- Labels now utilize Namespaces, so Macros can simply use Normal labels without running into duplication issues or generating Globally accessible Labels.
- Each Macro creates its unique Namespace when called from the source code, with a unique "_INSTANCENUMBER" modifier at the end.
- When Labels are printed out, or added to the Debug text file, they are shown as Namespace.LabelName, with unique instance number modifiers. (This is best to be checked out in the Debug output, and many users may never need to see or use them.)
- Calling Macros from within Macros work much better due to using Namespaces.
- For experts, Normal Labels within Macros can be referenced from outside, like "MyMacro.InnerLabel" for the first, or for example "MyMacro_2.InnerLabel" for the 2nd instance of MyMacro's usage.
- The scope of Local labels is no longer closed down automatically after a Macro call. You can refer to a Local label defined before or after the Macro call.
- Regional labels (eg "@@MyLabel") are now deprecated because they are no longer needed inside Macros. The compiler shows a Warning for each, so the source code can be fixed. The Regional labels feature will be completely removed in a later release.
- Setting DebugAddLocalLabels added to enable adding Local labels into the Debug text file. This is enabled by default.
- Setting ShowLocalLabelsAfterCompiling added to enable printing Local labels too, along with the Normal labels.
4/22/2020
- Version 2020.3 released
- Support for the following Intel CPUs has been added: 4004, 4040, 8008, 8080, 8085
- Visual Studio Code extension and Notepad++ files updated to support the new CPUs.
- Launch your compiled program automatically using the debugger of your choice via the settings DebugCommand and Debug.
- VS Code's Retro Assembler: Build & Debug command works by overriding the Debug=false setting using the -g switch.
4/17/2020
- Version 2020.2 released
- A Debug file containing Labels, Memory Addresses and Breakpoints can be saved on demand by using .setting "Debug", true
- This Debug file format can be customized in the User Settings XML file because every debugger is different.
- The ".breakpoint" directive now uses the Debug file and its format settings. If you miss the old VICE Monitor output settings, you can customize the file to work like that.
2/20/2020
- Version 2020.1 released
- The assembler is now built exclusively for .NET Core 3.1 and it runs on Windows, macOS and Linux as a single package.
- The .NET Framework 4.7 version is discontinued, therefore the Mono version is discontinued, too.
- The version number style has been changed to Year.Release for simplicity.
10/19/2019
- Version 2.4.2 released
- Fix for the case of calling a Macro with more parameters than what it has in its definition.
- The "[CodeFileName]-Info.txt" file is no longer saved by default. You can turn it on by the OutputSaveInfoFile setting either in the User Settings XML file, or in your project using the ".setting" directive.
5/31/2019
- Version 2.4.1 released
- Errors and warnings are printed with a clickable link that allows Visual Studio Code to open the referenced source code file at the problematic line number.
- New output file type added: SBin with load address and data length header.
4/4/2019
- Version 2.4 released
- New directive ".encoding" added. It controls how the directives .text and .textz turn strings into bytes.
- New directives ".textz", ".ascii" and ".asciiz" added for finer control over text strings.
- Directive aliases ".txt", ".st" and ".stxt" dropped to make things clearer.
- The directive ".debug" has been renamed to ".print", but ".debug" is still an accepted alias. Alias ".out" dropped.
- New directive ".error" added. It prints a user raiser error message on the console and stops the compiler.
- Error handling has been improved in source code processing.
- Standalone labels now produce warnings if they look similar to instructions, which may be a typo in the source code.
2/13/2019
- Version 2.3.1 released
- Fix for Mono on Ubuntu Linux and other operating systems.
11/13/2018
- Version 2.3 released
- The directives ".align" and ".storage" now only allocate bytes in memory, if the "filler byte" parameter is not set.
- The directives ".byte", ".word" and ".dword" can allocate bytes in memory, when the value is set to "?"
- New directive ".end" added. It terminates the source code loading at that line, the rest of the lines are ignored.
- Boolean values "true" and "false" now work everywhere where you expect them to work (directives equ, var, if, while, macro arguments)
10/15/2018
- Version 2.2.1 released
- Using of single character Labels is allowed (used to be forbidden), as long as it's not a match to a Register name in the selected CPU type. For example you can't use a / x / y as label names in 6502 code, but b / c / d are allowed.
- Functions made by the .function directive no longer require brackets for the function definition, but they are still needed for the function call. It's just for alternative formatting.
- Bug fix: The -L command line option for LaunchCommand broke earlier, it's been fixed. VS Code can launch the compiled files again.
10/1/2018
- Version 2.2 released
- Z80 CPU support added with standard and undocumented instructions.
- Sega Master System ROM (.sms) output format implemented.
- Sega Game Gear ROM (.gg) output format implemented.
- ZX Spectrum 48K Tape (.tap) output format implemented.
- The expression evaluator has been improved, now it can handle complex expressions (using parentheses) in Instructions and Macro calls too.
- Bitwise-NOT number manipulation implemented in the expression evaluator. It flips the bits of the number value that follows the '~' character. The number is analyzed and only the occupied bit width in use gets manipulated, so it works for 8, 16, 24 and 32 bit numbers as you would expect it to work. For example
Label = ~$1000 sets value $efff for the label, and ~$01 turns into $fe - BeforeBuild and AfterBuild added into Settings, to execute one or more commands before and after the build. See the Settings documentation for details because these are special.
- OutputFormat is now an alias of OutputFileType in Settings.
- VS Code Extension updated, Register color tweaked.
- Notepad++ support files updated, Register color tweaked.
8/23/2018
- Version 2.1.1 released
- Check for updates implemented. The assembler checks for a newer available version periodically and will notify you when there's something new to download. It's useful, but you can be disable it in settings.
- Launch your compiled program automatically using the emulator or utility of your choice via the settings LaunchCommand and LaunchEnabled.
- VS Code's Retro Assembler: Build & Start command now works by overriding the LaunchEnabled=false setting using the -L switch.
- Now you can create the file retroassembler-usersettings.xml based on the retroassembler-settings.xml file, where you can keep the settings you changed from their default values. Updated assembler packages are not going to overwrite this new Xml file, but will always load the final settings from it. This way you'll never risk losing your custom settings by installing an update.
- The automatically mapped include directories now are also mapped as Include for case-sensitive file systems, such as on Linux.
- VS Code Extension updated. The extension's files are no longer included in the assembler package, it can be downloaded from the Marketplace.
8/20/2018
- Version 2.1 released
- 65816 CPU support added with new addressing types and mnemonics.
- New boolean settings RegA16 and RegXY16 to force the compiler to use 16 bit immediate values in 65816 mode, for example
lda #$12 will be compiled aslda #$0012 , ifRegA16 = true - Semi-intelligent disassembler support for 65816 with Memory and Index flag state following to correctly decode 16 bit values (it only works for sequentially placed instructions). For example:
lda #$1234 vslda #$34 + misidentified instruction with $12 - The disassembler's -D option got advanced values support for code offset and length settings.
- New number formats: 0b11110000 for binary (alternative), 0o12 for octal numbers.
- Binary and hexadecimal numbers can use optional section separators for readability purposes: %1111_0000, 0b11_11_0000, $aa_bb etc
- Hexadecimal numbers can get a "preferred number size" by prefixing them with "0" characters, for example
lda $0012,x will be compiled as$9d $12 $00 instead of the normal$95 $12 forlda $12,x - New instruction aliases for 6502-based CPUs: bcc = blt, bcs = bge
- The .target directive now has an alternative directive: .cpu
- Super Nintendo Entertainment System ROM (.sfc) output format implemented with LoROM/HiROM support.
- VS Code Extension and Notepad++ support files updated.
7/24/2018
- Version 2.0 released after complete refactoring.
- The assembler has both .Net Framework 4.7 and .Net Core 2.1 versions.
- The command line options and configuration files changed, be aware of that.
- New directive ".setting" added to update the default Settings values (in retroassembler-settings.xml) from the source code.
- Updated output file types: Bin, Prg, T64, D64, Gb, Nes, Txt (formerly H6X)
- The Txt output format is fully configurable in Settings.
- The disassembler uses the load address header from a Prg file, for other types the address can be set by option -D
- New keywords added: True and False, these are always case-insensitive.
- The keyword Auto is now always case-insensitive.
- The Target memory management has been improved, there is no practical limit in usable memory size (32 bit address space, ~4GB)
- Values for labels and instruction parameters are now stored as Unsigned 32 bit integers (or as 31 bit signed integers in case of negative numbers), to maximize 32 bit capabilities.
- New value directives: .dword, .loword, .hiword
- New directive: .random to create variables with random values, optionally with user configurable random seed value.
- The Breakpoint directive has been renamed to .breakpoint (used to be .break)
- New directive: .break to terminate a .while loop cycle on demand.
- The .while directive now has protection from infinite loops. Tops out at 100K, just like .loop does.
- Improved Segment handling with memory Banks. New directive: .bank
- Individual Segment saving improved, info text file saved with bank and segment information after compiling.
- The .memorydump directive now needs the Bank Number selected.
- 65C02 and 65SC02 CPUs merged into a single 65C02 CPU, with all vendor-specific instructions added.
- Gameboy CPU support fully implemented.
- Gameboy ROM (.gb) output format implemented, builds a valid Gameboy ROM file that runs on real Gameboys and emulators alike.
- Nintendo Entertainment System ROM (.nes) output format implemented.
- Visual Studio Code extension implemented with syntax highlighter and light/dark themes.
- Notepad++ editor support files improved.
- Various improvements and bug fixes.
2/5/2018
- Silent update before more changes.
- Bugs fixed in handling the addressing modes of the instruction JMP in 6502, 65C02 and 65SC02 CPUs.
- The Notepad++ syntax highlighting file for All CPUs (RetroAssembler-npp.xml) is discontinued and removed from the package.
12/12/2017
- Version 1.2 released
- 65C02 and 65SC02 CPU support added with new addressing types and mnemonics.
- New directive ".break" added to create breakpoints for debugging in VICE. (Changed to .breakpoint)
- New Notepad++ syntax highlighting for 65C02.
- The supported CPU naming convention changed a little, 6502 and 6510 lost the MOS prefix. You should update the .config file.
- The D64 converter now works on Linux too, by calling "c1541" instead of "c1541.exe".
12/7/2017
- Version number updated to 1.1.1 for this quick fix release.
- Linux support added in form of making the application Mono compatible. File paths are handled correctly, using the system-native directory path separator.
- The default Include directories are located both as "Include" and "include" (lowercase) to support case-sensitive file systems.
- The default output directory of the ".memorydump" directive is now the input source code file's directory.
12/6/2017
- Version 1.1 released
- The .memorydump directive is now capable of saving binary files, too.
- New directive ".memory" added to manipulate bytes in a selected memory segment.
- For clarity, the following CPU types have been removed: 6502C, 7501, 8500, 8501, 8502.
You can always refer to 6502 or 6510 instead, they are the same opcode-wise anyway. - Bug fixes.
8/3/2017
- The .align directive became more advanced, now it works in relocatable segments too.
- Bugs fixed in the .storage directive, in the expression evaluator and in the label handler.
7/21/2017
- Initial release of Version 1.0
6/18/2017
- Retro Assembler development milestones in the beginning:
- June 18, 2017 - Development started, source code loader, partial tokenizer implemented
- June 21, 2017 - Tokenizer finished, target architecture memory handling implemented
- June 22, 2017 - Parser working OK, basic directives implemented
- June 23, 2017 - Parser is solid, 6502 instruction database built
- June 24, 2017 - The assembler compiled the first working Commodore 64 code: Music player
- June 26, 2017 - Loop directive implemented, error handling code prettied up
- June 29, 2017 - Macro directive implemented
- July 1, 2017 - Segment directive implemented, memory handing and file saving updated
- July 3, 2017 - More complex expression evaluator implemented, brackets supported
- July 4, 2017 - File includes improved by the usage of known include paths
- July 5, 2017 - If and While directives implemented, expression evaluator handles comparers
- July 6, 2017 - Disassembler implemented
- July 7, 2017 - Notepad++ syntax highlighter (User-Defined Language) created
- July 8, 2017 - Function directive implemented based on Macro
- July 11, 2017 - Documentation is nearly finished
- July 12, 2017 - T64, D64 and H6X output file formats implemented
- July 13, 2017 - Generate and Memorydump directives implemented, first samples coded
- July 15, 2017 - Var directive replaced .redefine, Regional labels implemented