Futaba

Futaba is a low-level assembler specifically targetting the Super Famicom (SFC) and Super Nintendo Entertainment System (SNES). Futaba is an open-source project with its source code hosted on GitHub.

Futaba is a single-pass assembler, prioritizing fast assembly and a smaller memory footprint. One of this project's goals is also to move away from the archaic and C-flavored syntax prevalent in other assemblers. Currently, Futaba also aims to be as low-level as possible. Higher level program flow such as while statements are left out in favor of encouraging source generators.

As a .NET application, Futaba is cross-platform. The only prerequisite is having the .NET (10.0+) runtime. The core library is separate from the command-line interface, allowing it to be cleanly imported into other projects. Futaba is also AOT-compatible, enabling direct compilation to a native executable, negating the need for the runtime entirely.

Release timeline

  1. 2026-04-06 – private, closed-source development
  2. 2026-09-15 – public beta; open source
  3. TBD – preview
  4. TBD – release

Futaba is currently in the public beta stage. Not every feature has been fully implemented or tested, but its source is available for review and contribution. Bugs definitely exist and no guarantees are yet made. Once the core library has reached a stable state, we will enter a year-long preview stage during which syntax and naming may be adjusted, and features might be removed or modified. After the preview stage all of these aforementioned components will be locked and permanent. Until the full release, guarantees about determinism across release should not be considered valid. Expect potentially breaking changes.

Quickstart

All versions of Futaba require the .NET (10.0+) runtime, which can be downloaded from Microsoft.com.

Windows

  1. Download the file futaba-win-x64.zip from the latest release of Futaba.
  2. Extract the contents of the archive to a permanent home for the application.
  3. Navigate to that folder in File Explorer.
  4. Click the address bar and type cmd. This will open the command prompt.
  5. Type futaba register in the command prompt then press ENTER.

Linux

  1. Download the file futaba-linux-x64.zip from the latest release of Futaba.
  2. Extract the contents of the archive to a permanent home for the application.
  3. Open the command prompt.
  4. cd to that folder.
  5. Type futaba register in the command prompt then press ENTER.

Features and Syntax

Numbers

Number literals can be written in decimal, hexadecimal, or binary.

print 16 ; no prefix - decimal print $10 ; $ prefix - hexadecimal print %10000 ; % prefix - binary

Number literals can be split with underscores (_) as digit separators to improve readability.

print 2_000_000 print $10_BEBE print %1111_0000
NOTE: To avoid ambiguity with sublabels, numbers of magnitude less than 1 must be written with a leading 0; e.g., 0.9 not .9.

Strings

Strings are an important data type, but they may be intepreted in multiple ways. In normal expressions, a string either behaves as a string object or, when a numeric value is expected, it will behave as either 0 (empty string) or the encoding of its first character.

A single character can be inserted by enclosing it in apostrophes (').

Note: Futaba supports source files encoded in ASCII, UTF-8, and UTF-16, but does not support codepoints beyond the Basic Multilingual Plane (U+0000–U+FFFF) outside of comments. Surrogates are treated as separate characters.

Escape sequences

In some cases, a character may need to be escaped in a string or character:

Encoders

The default encoding is ASCII, which maps the unprintable characters and characters outside of the ASCII code block to space (U+0020). One other built-in encoder exists for unicode, which maps characters directly to their code point. Different encodings are selected with the encoder directive.

encoder ascii encoder unicode

Additional encoders can be declared by using the encoder directive followed by an identifier and a file path. User-defined encoders can be selected the same way as the built-in encoders. The identifier must be a unique name among encoders and cannot be the reserved names ascii or unicode.

encoder «IDENTIFIER» «"FILE"»

The format of an encoding file is one definition per line, with the first character of that line containing the character to encode followed by an equals sign (=) then a prefix-less hexadecimal value. Any character not defined by the encoder will be mapped to 0.

A = 20 B = 21 C = 22 D = 23 E = 24 F = 25 Z = 3A

For contiguous mappings, use an ellipsis (...) followed by the last character in the range. Optionally, put a character in front to specify the beginning of the range. The example below encodes the uppercase letters beginning at $20 followed by the lowercase letters, all consecutively:

A = 20 ...Z a...z

If a character is by itself on a line, it will be inserted as the next code point. The example below encodes the first 6 uppercase letters as $20 through $25:

A = 20 B C D E F

String functions

When used in a data statement, functions can be invoked on a string by appending a triple-slash (///). Multiple function can be provided for a single string by separating each function with a single-slash (/).

The enc function selects an encoder for a string without changing the default encoder.

encoder ascii db "Hello world!" ; uses the ascii encoder db "I am encoded!"///enc:unicode ; uses the unicode encoder db "I too am that." ; uses the ascii encoder

The upper and lower functions transform a string to upper- and lowercase, respectively.

db "hEllO WoRlD!"///upper ; "HELLO WORLD!" db "HelLo WOrLd!"///lower ; "hello world!"

The pad function pads a string to a given number of characters with a specified alignment. By default, the string will be padded with spaces. Enclose a character in apostrophes to specify it as the padding character.

Padding only lengthens strings that are too short. To force an exact length instead of a minimum, use the len function with the same arguments.

db "Hello world!"///pad:l20 ; "Hello world! " db "Hello world!"///pad:l20'!' ; "Hello world!!!!!!!!!" db "Hello world!"///pad:r20'!' ; "!!!!!!!!Hello world!" db "Hello world!"///pad:c20 ; " Hello world " db "Hello world!"///pad:cr20 ; " Hello world " db "Hello world!"///pad:cl20 ; " Hello world " db "Hello world!"///pad:r5 ; "Hello world!" db "Hello world!"///len:l20 ; "Hello world! " db "Hello world!"///len:l5 ; "Hello" db "Hello world!"///len:r5 ; "Hello"

Commands

Each line of source code ends when it reaches the end of the line, a comment, or a single-line token.

Comments are denoted with a semicolon (;) and continue to the end of the line.

Multiple statements can be written on a single line by using a colon surrounded by white space on both sides ( : ).

print 1 print 2 ; This is a comment. print 1 : print 2 print 1 :print 2 ; error! missing space on the right

Languages

Three languages are supported, selected by using the lang or arch directive:

Language Names Mnemonics
WDC 65c816
wdc 65816 65c816 wdc65816 wdc65c816
ADC AND ASL BCC BCS BEQ BIT 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 WDM XBA XCE
SPC700
spc spc700
ADC ADDW AND AND0 AND1 AND2 AND3 AND4 AND5 AND6 AND7 ASL BBC0 BBC1 BBC2 BBC3 BBC4 BBC5 BBC6 BBC7 BBS0 BBS1 BBS2 BBS3 BBS4 BBS5 BBS6 BBS7 BCC BCS BEQ BMI BNE BPL BRA BRK BVC BVS CALL CBNE CLR0 CLR1 CLR2 CLR3 CLR4 CLR5 CLR6 CLR7 CLRC CLRP CLRV CMP CMPW DAA DAS DBNZ DEC DECW DI DIV EI EOR EOR0 EOR1 EOR2 EOR3 EOR4 EOR5 EOR6 EOR7 INC INCW JMP LSR MOV MOV0 MOV1 MOV2 MOV3 MOV4 MOV5 MOV6 MOV7 MOVW MUL NOP NOT0 NOT1 NOT2 NOT3 NOT4 NOT5 NOT6 NOT7 NOTC OR OR0 OR1 OR2 OR3 OR4 OR5 OR6 OR7 PCALL POP PUSH RET RETI ROL ROR SBC SET0 SET1 SET2 SET3 SET4 SET5 SET6 SET7 SETC SETP SLEEP STOP SUBW TCALL TCLR TSET XCN
Super FX
sfx superfx
ADC ADD ALT1 ALT2 ALT3 AND ASR BCC BCS BEQ BGE BIC BLT BMI BNE BPL BRA BVC BVS CACHE CMODE CMP COLOR DEC DIV2 FMULT FROM GETB GETBH GETBL GETBS GETC HIB IBT INC IWT JMP LDB LDW LEA LINK LJMP LM LMS LMULT LOB LOOP LSR MERGE MOVE MOVEB MOVES MOVEW MULT NOP NOT OR PLOT RAMB ROL ROMB ROR RPIX SBC SBK SEX SM SMS STB STOP STW SUB SWAP TO UMULT WITH XOR

Differences from official documentation

Official syntax Futaba syntax
mov1 C, arg.n movn C, arg
mov1 arg.n, C movn arg, C
and1 C, arg.n andn C, arg
and1 C, /arg.n andn C, /arg
or1 C, arg.n orn C, arg
or1 C, /arg.n orn C, /arg
eor1 C, arg.n eorn C, arg
not1 arg.n notn arg
set1 arg.n setn arg
clr1 arg.n clrn arg
Mnemonics with aliases
Official syntax Alias
tclr1 arg tclr arg
tset1 arg tset arg
daa A daa
das A das
xcn A xcn
mov A, (X)+ mov A, (X+)
mov (X)+, A mov (X+), A

For the SPC700, the 1-bit instructions use mnemonics that address the bit directly rather than at the end of the operand.

Using ! for absolute/16-bit addressing is not supported, as it conflicts with the syntax for variables. Use .w instead.

Miscellaneous

When it is safe to do so, dummy operands will be assembled to help keep code aligned even with invalid syntax. For example, JSR #1 will assemble as 20 00 00. An error will still be emitted, because this is not a valid addressing mode; however, the given mnemonic can only ever assemble with a 16-bit operand, so a placeholder instruction is added to help maintain the address of code that follows.

Program counter

Futaba tracks three different positions as it assembles:

The program counter or pc of code or data is the physical location as it appears on the SNES system bus.

The provenanceAfter much deliberation, I chose "provenance" as the nomenclature because it was a synonym for "origin", and I wanted something that was similar to the org directive—org being short for origin. or site of code or data is the location in memory that it should be interpreted as being in. For most program code, provenance and program counter are identical, but for content that is accessed elsewhere, such as sound engine code on the APU, provenance will differ.

The binary offset of code or data is the physical location of it in the final .sfc file. This can be vastly different from the program counter or provenance, depending on the mapper mode.

Controlling the program counter

The org directive moves the program counter (and thus assembly) to a specific address. This address is affected by the mapping mode of the ROM.

The site directive stages assembly elsewhere without changing the actual program counter. This is useful for writing source code that is relocated, such as to WRAM or ARAM. To reset provenance back to being the same as the program counter, use SPLAT (*) as its argument.

The org and site directives both require an expression to be immediately resolved. The default symbol context of an org expression is ROM address. Likewise, the default symbol context of site is provenance.

The current program counter can be saved and recovered using the pushpc and pullpc directives. The current provenance can be saved and recovered using the pushsite and pullsite directives.

Assembly can be advanced an arbitrary number of bytes without writing using the skip directive.

The align and arrange directives can be used to skip ahead the necessary number of bytes for the program counter and provenance, respectively, to be an exact multiple of the value supplied.

The skipto directive advances forward until the given provenance is reached. If this results in a negative change, an error will be emitted.

The warnpc and warnsite directive can be used to emit errors if the current program counter or provenance, respectively, is beyond a given address. Warning and relocating the program counter can be performed together with the safeorg directive.

The rebank directive can be used to change only the bank of the current program counter. This directive includes special syntax to control paging: prefixing the expression with the > token will force bit 15 of the address to be set, while the < token will force it to be reset.

site $998123 ; provenance: $99:8123 rebank $BE ; provenance: $BE:8123 rebank <$7E ; provenance: $7E:0123 rebank >$7E ; provenance: $7E:8123

The bankguard directive can be used to change when errors are emitted if certain boundaries are crossed by the program counter:

Data statements

Four directives are supplied for adding arbitrary data directly into a program:

These directives take a comma separated list of expressions, variables, and/or strings. Expressions will insert the value they resolve to. Strings will be treated as an array of characters and insert each encoded character separately. All values will be padded or truncated to the element size.

Individual elements can be skipped with a SPLAT (*). This is useful for patching larger tables with partial edits.

Symbols

A symbol is a read-only token that represents an integer. Symbols can be used to represent memory addresses or program constants.

All symbols must have a unique name; however, special syntax allows symbols with similar names to be created as part of larger hierarchies.

User-created symbol names must begin with a letter or underscore and may be followed by zero or more letters, numbers, and/or underscores.

Dragon: ; valid! Wyvern: ; valid! Dragon2: ; valid! _Dragon: ; valid! 3LeggedWyvern: ; invalid! Three-LeggedWyvern: ; invalid! A: ; invalid! see below

A handful of keywords are reserved and cannot be used as a symbol name:

  • A
  • X
  • Y
  • a
  • x
  • y
  • C
  • c
  • SP
  • Sp
  • sp
  • sP
  • R0
  • R1
  • R2
  • R3
  • R4
  • R5
  • R6
  • R7
  • R8
  • R9
  • R10
  • R11
  • R12
  • R13
  • R14
  • R15
  • r0
  • r1
  • r2
  • r3
  • r4
  • r5
  • r6
  • r7
  • r8
  • r9
  • r10
  • r11
  • r12
  • r13
  • r14
  • r15

Labels

Labels are symbols declared inline to define the location of code or data. Labels are declared by affixing a colon (:) to an identifier with no space in between.

Sublabels are declared by prefixing an alphanumeric name with a dot (.). Sublabels can be accessed within their scope by dot-prefix-name alone. Sublabels can also be accessed explicitly through their hierarchy. Nested sublabels can be declared by prepending additional dots, up to 20 levels deep.

Dragon: .main ; Dragon.main LDA.b #$04 ..exit ; Dragon.main.exit BRA .draw ; becomes Dragon.draw .draw ; Dragon.draw STA.b $00 ..exit ; Dragon.draw.exit RTS Wyrm: LDA.b #$05 BRA Dragon.draw Dog: JMP Dragon.draw.exit ..exit ; invalid! no single-dot sublabel exists to be its parent

Each declaration of a top-level label creates a new hierarchy for sublabels. An extrinsic label that exists outside any hierarchy can be declared by prefixing a label with the pound (#) token.

Dragon: .draw #Wyrm: BRA .draw ; valid, uses Dragon.draw Dog: BRA .draw ; invalid!

Assignments

A basic symbol can be declared and assigned by placing an equals sign (=) followed by an expression after an identifier.

MAXHP = 128

The assignment expression may include other symbols, but only if they have already been declared and assigned.

DRAGONHP = MAXHP ; invalid! MAXHP has not been declared yet MAXHP = 128 WYVERNHP = MAXHP ; valid

Relative labels

Relative labels can be created by stringing together any number of repeating + or - to create forward- and backward-relative labels, respectively. These labels can then be used in other expressions to find the nearest matching label in the respective direction.

-- ; let's call this location A BCS -- ; branches to A BCC -- ; branches to A -- ; let's call this location B BCS ++ ; branches to C BCC -- ; branches to B ++ ; let's call this location C

To be used in any larger expression, relative labels must be enclosed in parentheses.

LDA.l ++++ ; valid LDA.l (++++)+2 ; valid JMP.w (----)+2 ; valid JMP.w ----+2 ; invalid! this is interpreted as 5 separate unary operators

Pools

Pools are special groups declared with the pool directive followed by one or more top-level label names. Pools can be used to declare sublabels under a hierarchy before that hierarchy exists. Pools can also be used to create sublabels that belong to multiple distinct hierarchies. Pools are closed using the endpool directive.

pool Dragon Wyvern .health ; Creates both Dragon.health and Wyvern.health .attack ; Creates both Dragon.attack and Wyvern.attack endpool Dragon: LDA.w .health LDX.w .attack Wyvern: LDA.w .health LDX.w .attack

New top-level hierarchies cannot be created inside pools.

Buoys

Buoys are labels used to reference relative offsets and are declared by prefixing a sublabel declaration with one or more carets (^). Each caret denotes a reference to an ancestor one level earlier in the hierarchy; thus, the maximum number of carets on a given sublabel is the same as the number of dots. The default value of a buoy is the distance from it to its parent.

Label: .sublabel db 1 ^.buoy1 ; relative offset is 1 (distance from "Label") db 1 ^..buoy1a ; relative offset is 1 (distance from ".buoy1") ^^..buoy1b ; relative offset is 2 (distance from "Label") ^^^..buoy1c ; invalid declaration
Tip: Use ^.size at the end of a block to create an accessible size symbol under any circumstances.

Docks

Docks are reusable labels representing a growing region that can be optionally protected. Declaring a dock automatically enters it as well. To unmount a dock, use the undock directive.

dock «IDENTIFIER» dock «IDENTIFIER» :: «SIZE»

Attempting to add code or data to a protected dock under normal circumstances will trigger an error. Content may be added to a docked region in one of two ways: using the org directive with the dock's name as its only operand.

dock Sprites :: $2000 ; 8kb protected; dock position is 0 ... org Sprites ; valid! the region is entered at Sprites+0 Dragon: JSR DragonStuff org somewhereelse org Sprites ; valid! the region is entered at Sprites+3 JSR MoreDragonStuff org Sprites ; valid! the region is entered at Sprites+6 org Sprites+2 ; invalid! the region is not entered properly

Pilings

The piling directive can be used to create an anchor point for a self-incrementing data region.

piling «IDENTIFIER» piling «IDENTIFIER» :: «CAPACITY»

Pilings can then be added to by using the pile directive to assemble a single data statement by placing an allocator token (::) followed by the statement.

piling Health :: $20 ; 32 bytes capacity; piling position is 0 ... Dragon: pile Health :: db 100 ; writes 1 byte to piling; position becomes 1 JSR DragonStuff JSR MoreDragonStuff RTL Wyvern: pile Health :: db 75; writes 1 byte to piling; position becomes 2 JSR DragonStuff JSR WyvernStuff RTL

Plopping

The plop directive behaves similarly to pile with the difference that its first operand is an expression for a SNES address.

plop «ADDRESS» :: «DATA STATEMENT»

Plopping allows completely arbitrary data writes with a simplified syntax that is useful for single data entries.

Dragon: plop Health+0 :: db 100 Wyvern: plop Health+1 :: db 75
Note: The plop and pile directives do not affect the position being assembled. For example, the internal variable !:pc will return the current program counter and not the position of the data. This is intentional behavior that facilitates the creation of vector tables.

Allocations

The allocate directive can be used to create blocks of allocated address space using the following syntaxes:

allocate «ADDRESS» allocate «ADDRESS» :: «SIZE»

Inside an allocation block, symbols may be declared in 3 ways:

To close the current allocation block, use the endallocate directive.

Allocation blocks can be chained by reusing the allocate directive. To continue a block where the previous block ends, use a SPLAT (*) for the address operand. This address is remembered across invocations.

allocate $7E0000 :: $0100 ; block begins at $7E0000 256 bytes allocate * :: $0100 ; block begins at $7E0100 allocate * ; block begins at $7E0200 SpriteHP :: 16 allocate * ; block begins at $7E0210

Allocation blocks are more restricted than code blocks, only allowing the following keywords:

Allocation blocks use a separate program counter from code. All allocated symbols return the same value for their provenance, program counter, and binary offset.

Post-label white space

Inline label declarations that are not created with keywords automatically begin a new command after their declaration; however, they must be followed by white space, a line break, a comment, or an end of file.

LabelA: ; valid! followed by a newline .sublabel ; valid! ditto LabelB:;comment ; valid! followed by a comment .sublabel;comment ; valid! ditto LabelC: NOP ; valid! followed by a space .sublabel NOP; valid! ditto LabelD: : NOP ; valid! .sublabel : NOP ; valid! LabelE:NOP ; invalid! followed by normal text .sublabel!VAR = 1 ; invalid! ditto .sublabel: ; invalid! colons don't belong on sublabels ++ -- ; two on the same line? perfectly valid!

Symbol properties and context

All symbols represent at least 3 values: a binary offset, a ROM address, and a provenance.

By default, labels and sublabels return their provenance when used in an expression; however, certain directives force a different context to be used. For example, the org directive evaluates expressions with the "ROM address" context, causing all symbols to default to the ROM address they hold.

The expression context can be overridden on individual symbols by appending a property accessor (:) and a property name. No spaces should separate the symbol name, specifier token, and property name.

print Dragon ; provenance print Dragon:offset ; binary offset

Expressions

Futaba supports expressions for nearly all operands and arguments. Expressions use standard programmer syntax for mathematical operations with C-style function calls, and several assembly-specific operators.

Expressions follow C#'s operator precedence with custom operators given the precedence that makes the most sense under those rules.

The value of all expressions and subexpressions are stored as a decimal type value. This offers higher precision than double with negligible performance impact for Futaba's usecase.

Operator precedence
(a)
func(a)
a?
-a +a <a >a ^a <&a >&a ^&a
a*b a/b a//b a%b
a+b a-b
a<<b a>>b a>>>b
a&b
a^b
a|b a][b
a&&b
a||b
a>b a<b a>=b a<=b
a==b a!=b
a??b
Binary operators
a+b addition
a-b subtraction
a*b multiplication
a/b divison
a//b integer division
a%b modulo
a<<b arithmetic left shift
a>>b logical right shift
a>>>b arithmetic right shift
a&b bitwise AND
a|b bitwise OR
a^b bitwise EOR
Logical operators
a==b equal
a!=b not equal
a>b greater than
a<b less than
a>=b greater than or equal
a<=b less than or equal
a&&b logical AND
a||b logical OR
Unary operators
+a self
-a additive inverse
~a bitwise NOT
New binary operators
a][b little endian merge (a & $FF) | ((b & $FF) << 8)
a??b null coalescence a if resolved; otherwise b
New unary operators
<a low byte a & $FF
>a high byte (a >> 8) & $FF
^a bank byte (a >> 16) & $FF
<&a absolute a & $FFFF
>&a high 16 (a >> 8) & $FFFF
^&a isolate bank a & $FF0000
a? null coalescence a if resolved; otherwise 0

White space

Any number of space or tab characters may appear between most elements in an expression; however, inidivual elements cannot have white space within them.

Truthiness

Any value or expression that evaluates to exactly 0 is considered false. All other results are considered true.

The logical AND (&&) and logical OR (||) operators are not short-circuiting. Both operands will always be evaluated (unless the left operand fails to resolve, in which case the failure will immediately propagate upwards).

Null coalescence

The null coalescence operators can be used to provide fallbacks for invalid subexpressions in addition to undeclared symbols and variables.

print (1 / 0) ; error print (1 / 0)? ; evaluates to 0 print (1 / 0) ?? 5 ; evaluates to 5

Bitwise restrictions

The bitwise operators only function on integral types. The fractional part of any operand will be automatically discarded.

print 1 & 3 ; evaluates to 1 print 1 & 3.14 ; evaluates to 1

Variables

Variables in Futaba are dynamically typed and can be either numbers or strings. Variables are declared with the following syntax. A variable's identifier can only contain letters, numbers, and underscores; however, unlike symbols, there are no restrictions on the first character.

!«IDENTIFIER» = «EXPRESSION» !«IDENTIFIER» = "«STRING»"

A variable's type is dictated by the operand of its assignment. String variables must enclose their contents in quotation marks ("). All other operands will be treated as mathematical expressions and must resolve to some value. A variable can be reassigned at any point, with subsequent dereferences using the new value.

!VAR1 = 3 ; value type !VAR2 = "3" ; string type !VAR3 = !VAR2 ; !VAR3 is a string type !VAR4 = !VAR3 + 3 ; !VAR3 is treated as a string inside an expression

Variables inside strings are automatically interpolated. To prevent interpolation, escape the variable with a backslash (\).

!VAR1 = 3 print !VAR1 ; prints 3 print "!VAR1" ; prints 3 print "\!VAR1" ; prints !VAR1

The ?= assignment operator can be used to assign a variable only when it does not yet exist.

!VAR1 = 3 print !VAR1 ; prints 3 !VAR1 ?= 15 print !VAR1 ; prints 3

The following compound assignments are available for numeric variables: +=, -=, *=, /=. String variables only support the += compound assignment, which appends the operand to the end of the string. No other string concatenation operator is supported. To concatenate string variables to a new variable, use string interpolation; e.g.: !C="!A!B".

Note: Using the += operator on a string variable when the operand is an expression will interpret the expression as a naked string without parsing it. Conversely, using the += operator on a numeric variable with a string will first cast the value to a string then append it.

Internal variables

A handful of internal variables are provided to expose properties of the assembler or useful constants. These may be accessed by including a colon (:) before the variable's name. Internal variables are read-only.

NameValue
!:version Assembler version
!:pc Current program counter
!:site Current provenance
!:here Alias for site
!:offset Current binary offset
!:line Current source line
!:rand Random 64-bit value
!:pi 3.14159265358979323846264
!:e 2.71828182845904523536028
!:rad 0.01745329251994329576923 (π/180)
!:name Tile name mask: $03FF

Functions

Functions are declared by using the function keyword with the following syntax:

function «IDENTIFIER»( [PARAMETERS, ...] ) => «EXPRESSION»

Parameters in the function body are dereferenced by enclosing their names with curly braces ({name}).

function SquareHalf(a, b) => ({a}*{a} + {b}*{b}) / 2 print SquareHalf(10, 8) ; calculates (10*10 + 8*8) / 2

On declaration, functions create a syntax tree in place and capture the current state of any variables.

!VAR = 3 function GetVar() => !VAR ; !VAR is captured in its current state print GetVar() ; prints 3 !VAR = 5 print GetVar() ; prints 3

Built-in functions

A number of built-in functions are provided. These functions' names are case-sensitive.

NameUsage
sin(θ) Returns the sine of θ
cos(θ) Returns the cosine of θ
tan(θ) Returns the tangent of θ
asin(θ) Returns the arcsine of θ
acos(θ) Returns the arccosine of θ
atan(θ) Returns the arctangent of θ
log(x) Returns log10(x)
log2(x) Returns log2(x)
logb(x,b) Returns logb(x)
pow(x,p) Returns x to the power of p: xp
sqrt(x) Returns the square root of x: 2√x
root(x,n) Returns the nth root of x: n√x
max(a,b) Returns the larger value of a and b
min(a,b) Returns the smaller value of a and b
clamp(a,x,y) Returns a such that it is no smaller than x and no larger than y
round(x,n) Returns x rounded to n digits of precision; negative values remove precision to the left of the decimal point
select(p,a,b) Returns b when p evaluates to zero; otherwise a
vram(a) Returns VRAM address of a as (a&$FFFF)>>1
obsel(a,n,s) Returns a value for the PPU register OBSEL for VRAM address a with n $1000×16-bit words between namespaces and size selector s.
bgnba(a,b) Returns a value for the PPU registers BG[12/34]NGA that places background characters at the given addresses a for 1/3 and b for 2/4
bgsc(a,v,h) Returns a value for the PPU registers BG[1/2/3/4]SC that places the tilemap at VRAM address a and treats v and h as boolean values for vertical and horizontal mirroring, respectively
rtn(a) Returns a−1; useful for self-documenting stack return addresses
rebank(a,b) Returns a new 24-bit address where the bank byte of a has been replaced with b
floor(n) Returns n rounded to the nearest whole number less-than-or-equal to n
ceil(n) Returns n rounded to the nearest whole number greater-than-or-equal to n
int(n) Returns n with its fractional part truncated
abs(n) Returns the absolute value of n
bcd(n) Returns the value that in hexadecimal represents n in binary-coded decimal
col(c) Returns the 15-bit SNES color that approximates the given HEX color c = $RRGGBB
col(r,g,b) Returns the 15-bit SNES color that approximates the given RGB color
rand(a,b) Returns a random integer n such that a≤n<b
bitn(n) Returns bit n: 1<<n
hash(s) Returns a hash on string s; this hash is guaranteed to be deterministic across releases
len(s) Returns the length of string s in characters
streq(s,c) Returns 1 if strings s and c contain the same contents; otherwise 0
exists(v) Returns 1 if variable v exists when this expression is encountered; otherwise 0
read(file) Returns the 8-bit value from position 0 of file
read(file,i) Returns the 8-bit value from position i of file
read(file,i,s) Returns the s-byte value from position i of file; 1≤s≤8
Note: Future releases may potentially add more functions; however, these functions will always have lowercase identifiers.

Code/data management directives

Macros

Macros provide robust, parameterized text substitution in source code. Macros are declared with the macro directive followed by a unique alphanumeric identifier then zero or more parameters enclosed in parentheses. After the macro header is any number of valid assembly source lines, with the macro body terminated by the endmacro directive.

macro «IDENTIFIER»( [PARAMETERS, ...] ) «...» endmacro

Parameters in the macro body are dereferenced by enclosing their names with curly braces ({name}). The special variable {?} can be used to insert an invocation-unique string which may be used for macro-scoped labels or other similar purposes.

macro Sequence(a) db {a}, {a}+3, {a}+6 endmacro %Sequence(2) ; assembles db 2, 5, 8
macro MakeLabel() {?}Label: db {?}Label endmacro %MakeLabel() %MakeLabel() ; no conflict

The arguments to a macro call are raw strings that are inserted directly into the macro body. To prevent trimming of whitespace or early comma detection, wrap indivudual arguments in quotes.

%SomeMacro( Stuff ) ; arg0 is Stuff %SomeMacro( "Stuff" ) ; arg0 is Stuff %SomeMacro( ""Stuff"" ) ; arg0 is "Stuff" %SomeMacro(" Stuff ") ; arg0 is Stuff %SomeMacro( "" Stuff "" ) ; arg0 is " Stuff "
Weirdness: Due to how macro variables are inserted, currently only the closing brace can be escaped to prevent a parameter dereference from being parsed.

incsrc

The incsrc directive can be used to assemble source from other files. This directive takes a comma-separated list of strings containing file paths to other source code.

incsrc «"FILE"» incsrc «"FILE1"», «"FILE2"»

Files may be nested and/or chained up to 512 levels deep. For the purposes of this limit, files listed together in a single directive are considered nested inside each other. Macros also count towards this limit, as they are replaced with ephemeral source objects during assembly.

incbin

The incbin directive can be used to insert raw data by copying it from another file. This directive takes a comma-separated list of strings containing file paths to data. There is no hard limit to the number of files that can be listed with a single directive.

incbin «"FILE"» incbin «"FILE1"», «"FILE2"»

Slices of data can be inserted by specifying a range after the file path string, enclosed in brackets. This range is an inclusive start to an exclusive end, with both operands taking an expression.

incbin «"FILE"»[«START»::«END»] incbin «"FILE"»[::«END»] ; start is implicitly 0 incbin «"FILE"»[«START»::] ; end is implicitly the size of the file

A label can be attached to a data block by including a bracket-enclosed identifier before the file list:

incbin [«IDENTIFIER»] «"FILE"»

This label will include a size property that can be accessed using the :size property. If multiple files are assembled together in a list, this size will be the length of all data inserted by the directive.

incbin [HealthData] "health_data.bin" printf "Health data contains {0} bytes!", HealthData:size

fill

The fill directive can be used to fill a span of data with a repeated value using the following syntax:

fill «TYPE» «MODE» «AMOUNT» :: «VALUE»

The fill value is an expression or, in the case of random, an optional seed. If a value is omitted for random fills, the block will consume values from the assembler's shared instance RNG; otherwise, the operand will be treated as a string and its contents will be hashed to seed a new sequence.

Note that the fill value is only calculated once—when it is parsed in this command. This is still the case even when that value must be resolved later.

fill byte count 200 :: $00 ; 200 bytes fill word size 200 :: $1234 ; 200 bytes fill word count 200 :: $1234 ; 400 bytes

Conditional logic

Simple conditional logic can be checked using the if, else, and endif directives. If-statements evaluate an expression for truthiness.

Output and Diagnostics

Printing

Four directives are provided for printing to a text stream. The print directive prints a comma-separated list of tokens, strings, or expressions on a single line. The warn and error directives behave similarly, but they also emit a warning or error, respectively.

The printf directive can be used to print a formatted string with the first argument being the format specifier and the remaining arguments being the objects passed to the formatter. This directive uses the .NET string format syntax.

Futaba includes a special format specifier for numbers: A, which can be used to print a number as a 6-digit hexadecimal string in the form $XX:XXXX. Futaba also reroutes the X and B specifiers on decimal values by truncating the fractional part and converting the value to a 64-bit integer.

All print commands include special shorthands to facilitate printing. These are not reserved words, but they will take priority over symbols with the same name. To work around this, prefix those symbols with a plus sign (+), which is effectively a null operation.

TokenPrints
pc Current program counter as a 6-digit hexadecimal string
site Current provenance as a 6-digit hexadecimal string
offset Current binary file offset as a 6-digit hexadecimal string
bar 100 hypens; useful as a delimiter

Breakpoints

Breakpoints can be created for a program by inserting a tick mark (`) optionally followed by other arguments:

`[TRIGGERS][+INSERT]

Any combination of flags can be combined to declare what actions the breakpoint should break on. If none are specified, then the default triggers will be used.

Zero or one items may be specified as an inserted instruction. The insert is delimited with a plus sign (+); used by itself, this is treated as "no insert". If not specified at all, then the default instruction will be inserted.

The same arguments above can be used with the breakpoints directive to declare the default behavior of naked breakpoints. The default behavior is breakpoints rwx+.

` ; break on Read/Write/Execute, inserts nothing ` ; break on Read/Write/Execute, inserts nothing breakpoints rw+n ; default breakpoint triggers are Read/Write; inserts NOP ` ; break on Read/Write, inserts NOP `x ; break on Execute, inserts NOP `+b ; break on Read/Write, inserts BRK `+ ; break on Read/Write, inserts nothing
Tip: Use breakpoints kill at the beginning of your entry file to cleanly produce release builds.

Special markup

Address markers

Address markers are beginning-of-the-line annotations that identify where in memory code or data is expected to assemble. These are primarily designed for disassemblies but are not restricted from use in projects of other types.

|AAAAAA| ; program counter |AAAAAA:PPPP| ; program counter and provenance

Address markers behave as their own command; a single-line token is not required to put content on the same line.

In addition to providing useful annotation for readers, address markers also emit warnings when they are incorrect.

org $008000 |10BEBE| db 0 ; warning! this annotation is wrong site $8000 |10BEBE| db 0 ; warning! provenance is missing site * |10BEBE:9000| db 0 ; warning! provenance is not necessary

Syntactic sugar

Processor flags

Using square brackets, the processor flags can be referred to by initial in the REP and SEP instructions.

REP #[MXC] ; assembles as REP #$31

brop

Branch instructions can be assembled as brops (BRanch OPcodes) by using SPLAT (*) as their operand. This allows for simpler patches where the type of branch instruction should be changed but the location it branches to should not.

org $01D4DB BCS * ; assembles as B0 and skips the next byte

Branch literals

Branch instructions can be assembled with literal values for their operands by using the # token. This may have some usecases, but generally, branch operands should be left to the assembler.

No-op jumps

A no-op jump can be assembled by supplying & as the operand to branch and jump instructions. These will assemble with the necessary value to move to the next instruction. This is useful for blanking out code without inefficient NOP usage or for jumping to fastrom after an interrupt.

NMI: JML & ; jumps to the next instruction REP #$30 BCS & ; assembles as B0 00

BTYS

The Branch To YourSelf—invoked with a single less-than (<)—can be used to point a branch instruction to itself. This is primarily intended for use with the complex branching instructions in the SPC700 architecture, but it is available for any branching instruction in any language.

mov A, #$FC cbne $F4, < ; assembles as 2E F4 FE

Repeat pseudo-instructions

The following instructions can be repeated an arbitrary number of times by using the # token before an expression. This expression must be immediately resolvable and evaluate to a positive number:

Command line interface

Manifest files

The recommended method of building projects is with a .futaba manifest file. These manifests dictate and control complex settings in a human-readable format, and, after registering the application, enable double-click assembly for easy building.

Comments may be added to manifest files using a semicolon (;); however, they may only appear on their own lines.

Options

Below is a table of options available for manifest files. Keys marked with an asterisk (*) are required.

Key Description
assembly:
*entry The main entry point where assembly begins
type The type of project being assembled
Accepted values: new, homebrew, patch, hack, disassembly, other
output Relative path to the desired output file
addsymbols Preload symbols into the symbols table for use during assembly
Accepted values:
ppu PPU registers on page $21
cpu S-CPU registers on page $42
joypad Joypad registers on page $40
dma DMA and HDMA registers on page $43
spc SPC registers on page $00 in ARAM
dsp Sound DSP registers
sa1 SA-1 registers on pages $22 and $23
sfx GSU registers on page $30
standard ppu, cpu, joypad, dma (+aliases), spc, dsp
snes ppu, cpu, joypad, dma
apu spc, dsp
nullfill Pre-initialize the output buffer before assembly
Accepted values:
zero (default) filled with 0s
byte, «val», «...» filled the given pattern of bytes
string, «text» UTF-8-encoded text
random random data
random, «string» deterministic random data, using a string for seeding
This option only initializes the buffer before assembly. If the file grows in size, the expanded region will be initialized to all 0.
randomseed Seeds the assembler for deterministic random numbers using the given string
base Relative path to a file to use as a base binary
header:
*mapmode Mapper mode used by the program
Accepted values: lorom, hirom, exlorom, exhirom, sa1, none
autofill Automatically populates the ROM header using the data supplied in this block.
Accepted values: true, false
checksum Calculate and insert a checksum at the end of assembly.
This option has lower priority than autofill.
fastrom Indicates whether this program utilizes FastROM
Accepted values: true, false
title ASCII title inserted into the header
The title will be trimmed of leading white space and padded to 21 characters.
romsize Initial program size in bytes
Accepted values: 32kb, 64kb, 128kb, 256kb, 512kb, 1mb, 1.5mb, 2mb, 3mb, 4mb, 6mb, 8mb
This value may be limited by the mapper mode chosen.
ramsize Amount of on-board RAM in bytes
Accepted values: none, 2kb, 4kb, 8kb, 16kb, 32kb, 64kb, 128kb, 256kb
version ROM version (1 byte value)
region Target distribution region/language
Accepted values: japan, north america, europe, france, dutch, spain, germany, italy, china, korea, quebec, brazil, australia, scandinavia, common
extended Indicates this program uses the extended header
Accepted values: true, false
coprocessor What coprocessor, if any is present on board
Accepted values: none, sa1, sfx, dsp, obc1, sdd1, srtc, custom, other
gamecode ASCII 4-character game code
makercode ASCII 2-character game code
debug:
symbols Output format for symbols found during assembly
Accepted values: mlb, wla
stdout Relative path to text file for print directives. If unspecified, everything is written to the console.
errorout Relative path to text file for warnings and errors. If unspecified, everything is written to the console.
overflow Action to take if assembly occurs outside the buffer.
Accepted values: error, grow, expand
tokens Set the warning level for instructions without a size token.
Accepted values: off, ambiguous, aggressive, absolute, all
warnaserror Treat warnings as errors during assembly.
Accepted values: true, false
maxerrors Set the maximum number of errors. If this limit is exceeded, assembly immediately halts.
disassembly:
crc A string literal to display and compare against for CRC32 hashing over the output file.
md5 A string literal to display and compare against for MD5 hashing over the output file.
sha1 A string literal to display and compare against for SHA-1 hashing over the output file.
diff Relative path to a nominal file to compare output against.

Template

[FUTABA:assemble]

assembly:
	entry              =
	type               =
	output             =
	addsymbols         =
	nullfill           =
	randomseed         =
	base               =

header:
	mapmode            =
	autofill           =
	checksum           =
	fastrom            =
	title              =
	romsize            =
	ramsize            =
	version            =
	region             =
	extended           =
	coprocessor        =
	gamecode           =
	makercode          =

debug:
	symbols            =
	stdout             =
	errorout           =
	overflow           =
	tokens             =
	warnaserror        =
	maxerrors          =

disassembly:
	crc                =
	md5                =
	sha1               =
	diff               =

!VARIABLE1 = "STRING"
!VARIABLE2 = 1.2823

#SYMBOL1 = 1234
#SYMBOL2 = $1ABC
#SYMBOL3 = %01011

.NET package

Futaba's core library is available as a nuget package from nuget.org for .NET projects. It can be found under the name Spannerisms.futaba.