NAME

xasm - 6502 cross-assembler

SYNOPSIS

xasm source [options]

DESCRIPTION

xasm is a cross-assembler which generates code for the 6502 processor.

source is the name of the source file. If no filename extension is given, .asx is appended. The default action (when invoked without options) is to assembly source, writing the result to a file with the .obx extension.

OPTIONS

/c
List lines skipped due to a false condition.
/d:label=value
Define a label. label should be a valid label name. value may be any expression (it may use forward references to labels defined in the source file). You may use several /d options to define many labels from the command line.
/i
Do not list included files. Only main source file will be listed.
/l[:filename]
Generate a listing. If no filename is given, the listing is written to source.lst, where source is the name of the source file without the extension.
/o:filename
Set object file name. The default is source.obx. You may use the null device (/o:nul) to generate no object file.
/p
Print fully qualified file names in listing and error messages. This option works only on Windows and is silently ignored on other platforms.
/q
Suppress info messages. Quiet mode. Prevents xasm from printing the logo and the summary.
/t[:filename]
List label table. If filename is omitted then the table is appended to the listing.
/u
Issue a warning message for each label whose value is unused.

Alternatively, you may use Unix-style options, for example:

xasm -i -d DEBUG=1 -l listing.lst source.asx

SYNTAX

Source files should be plain ASCII files. LF, CR, CR/LF and Atari line terminators are supported. Labels and instructions are case-insensitive.

xasm is backward compatible with Quick Assembler. To compile QA sources with xasm, simply replace ATASCII-specific characters with their integer codes. You also have to change all OPT directives, but usually you can simply remove them.

A label is a symbol that represents a 32-bit signed integer. You can define a label by putting its name at the beginning of a line (with no spaces before). Unless you use the EQU directive, the label is assigned the current value of the origin counter.

Instructions and directives must be preceded with some whitespace. Note that in xasm you can use instruction and directive names as label names. For example

nop

defines a label called nop, whereas

 nop

is a 6502 instruction.

Full comment lines must start with a semicolon, a pipe or an asterisk, with optional label definition and spaces before. Here are examples of full comment lines:

; this is a comment
 * so it is
label | and this too

Lines with instructions (and some directives) may be repeated. A single line may be assembled several times, for example:

:4 asl @
table :32*5 dta 5

In lines with instructions or directives, a comment starts immediately after the instruction/directive has been successfully parsed. That is, xasm does not require a special character to start a comment.

 lda foo ; this is a comment
 sta bar so it is
 tax #0  tax has no operand, therefore #0 starts this comment

You may put two instructions in one line so they share the operand. For example:

 eor:sta foo

is equivalent to

 eor foo
 sta foo

Note that

 lda:tax #0

is allowed because #0 is treated as a comment for tax.

EXPRESSIONS

Expressions are numbers combined with operators and brackets. You should use square brackets, because parentheses are reserved for 6502 indirect addressing.

Numbers can be expressed as:

Abbreviations of Atari hardware registers are provided to save you the trouble of typing two extra characters (^4e vs $d40e) and to ease porting software between Atari 8-bit computers and the Atari 5200 console. These are very similar machines, one of the biggest differences is the location of hardware registers.

SyntaxChip Value in the Atari 8-bit
computer mode (opt g-)
Value in the Atari 5200
game console mode (opt g+)
^0xGTIA $D00x$C00x
^1xGTIA $D01x$C01x
^2xPOKEY $D20x$E80x
^3xPIA $D30xerror (there's no PIA chip)
^4xANTIC $D40x$D40x

An op-code is the single-byte op-code of the instruction inside braces. The operand of the instruction is discarded and is necessary only to recognize the addressing mode. The instruction should begin right after the left brace and the right brace should immediately follow the operand or the instruction. You can skip the operand if the addressing mode is fixed. Examples: {lda #}, {jsr}, {bne}, {jmp ()}, {sta a:,x}.

You can use the line repeat counter (#) in the repeated lines. It counts the iterations starting from zero. Examples:

:3 dta # ; generates three bytes: 0, 1, 2.
line_lo :192 dta l(screen+40*#)
line_hi :192 dta h(screen+40*#)
dl :59 dta $4f,a(screen+40*#),0,$4f,a(screen+40*#),0

The follownig binary operators are supported:

The following unary operators are supported:

The operator precedence is following:

first[] (brackets)
+ - ~ < > (unary)
* / % & << >> (binary)
+ - | ^ (binary)
= == <> != < > <= >= (binary)
! (unary)
&& (binary)
last || (binary)

Note that although the operators are similar to those used in C, C++ and Java, their priorities are different than in these languages.

Compare and logical operators assume that zero is false and a non-zero is true. They return 1 for true.

While calculating an expression, signed 32-bit arithmetic is used. When range of 32 bits is exceeded, an 'Arithmetic overflow' error occurs.

DIRECTIVES

EQU - assign a value of an expression to the label
Examples:
five equ 5
here equ *
OPT - set assembly options
Five options are available:
  • F - fill the space between ORGs with $FF
  • G - Atari 5200 mode for hardware register abbreviations
  • H - generate Atari executable headers
  • L - write to the listing
  • O - write to the object file
You can turn any of these on or off.
The default (if no OPT specified) is opt f-g-h+l+o+.
Examples:
 opt l-     listing off
 opt l+o-   listing on, object file off
 opt f+g+h- useful for Atari 5200 cartridges - raw output format, 5200 hw regs
ORG - change value of the origin counter
If Atari executable headers are enabled, you can use a prefix:
  • a: starts a new block even if it is not necessary because the new address equals the current address.
  • f: is same as a:, but additionally generates a $FF,$FF prefix before the new header. The prefix is automatically generated at the beginning of the file.
Examples:
 org $600
 org f:$700
table org *+100
In the latter example table points to 100 bytes of uninitialized data (label is assigned to * before the ORG directive is executed).

Starting with version 2.6.0, xasm supports code that is relocated in the memory at runtime. Let's say you want your code to be located on the zero page. You can't normally load it directly into this place, so you load it at a different address and then move in your program. org r: changes the address that it used for code generation but not the address used for generating Atari executable headers. Example:

 org $8000
 ldx #code_length-1
 mva:rpl code_loaded,x z:code_zpage,x-
 jmp code_zpage

code_loaded
 org r:$30
code_zpage
 jmp * ; ... or something more sensible
code_length equ *-code_zpage

Note that both * and label definitions use the counter used for code generation. There is no direct access to the other counter, because I think this is not useful. If you really need it, you can always type something like:

where_am_i equ *-code_zpage+code_loaded
DTA - define data
  • integers
    • bytes: b(200) or simply 200
    • words: a(10000)
    • low bytes of words: l(511) defines byte 255
    • high bytes of words: h(511) defines byte 1
    You may enter many expressions in parentheses and combine different types of data in a single line, separating things with commas.
    You may also define a sine lookup table. The syntax is:
    sin(center,amp,size,first,last)
    where:
    • center is a number which is added to every sine value
    • amp is the sine amplitude
    • size is the sine period
    • first,last define the range of sine arguments. They are optional. The default are 0,size-1.
    Example: dta a(sin(0,1000,256,0,63)) defines a table of 64 words representing a quarter of sine with the amplitude of 1000.
  • real numbers: r(-1.23456e12)
    Real numbers are stored in the 6-byte Atari Floating-Point format.
  • text strings
    • ASCII strings: c'Text' or c"Text"
    • ANTIC strings: d'Text' or d"Text"
    A character string consists of any of characters surrounded by quotation marks. You can include the quotation marks in the string by doubling them.
    Placing a * character after a string inverts the highest bit in every byte of the string.
Examples of DTA:
 dta b(1,2),3,a(1000,-1),l(12345,sin(0,127,256))
 dta d"ANTIC"*,c'It''s a string',$9b
ICL - include another source file
Specifies another file to be included in the assembly as if the contents of the referenced file appeared in place of the ICL statement. The included file may contain other ICL statements. The .asx extension is added if none given.
Examples:
 icl 'macros.asx'
 icl 'lib/fileio'
Note: for portability, you should use only relative paths and slash as the separator. This assures that your sources will compile under Windows and Linux.
END - end assembling file
Remaining part of the file is not assembled. If this statement does not occur, the assembler stops assembling when it encounters the end of the file.
Example:
 end
INS - insert contents of file
Copies every byte of the specified file into the object file and updates the origin counter, as if these bytes were defined with DTA.
You may specify a range of the file to insert. The syntax is:
 ins 'file'[,offset[,length]]
The first byte in a file has the offset of zero.
If the offset is negative, it counts from the end of the file.
Examples:
 ins 'picture.raw'
 ins 'file',-256  insert last 256 bytes of file
 ins 'file',10,10 insert bytes 10..19 of file
RUN - set run address in the Atari executable format
 run addr
is equivalent to:
 org $2e0
 dta a(addr)
Example:
 run main
INI - set init address in the Atari executable format
Example:
 ini showpic
ERT - generate error if expression evaluates to true
Examples:
 ert *>$c000
 ert len1>$ff||len2>$ff
IFT - assemble if expression is true
ELI - else if
ELS - else
EIF - end if
With these directives you can construct fragments which are assembled when a condition is met. Conditional constructions can be nested.
Example:
noscr equ 1
widescr equ 1
 ift noscr
 lda #0
 eli widescr
 lda #$23
 els
 lda #$22
 eif
 sta $22f
The above example can be rewritten using the line repeating feature:
noscr equ 1
widescr equ 1
:noscr lda #0
:!noscr&&widescr lda #$23
:!noscr&&!widescr lda #$22
 sta $22f

PSEUDO-COMMANDS

Pseudo-commands are built-in macros.

ADD - addition without carry
If you have ever programmed a 6502, you must have noticed that you had to use a CLC before ADC for every simple addition.
xasm can do it for you. ADD replaces two instructions: CLC and ADC.
SUB - subtraction
It is SEC and SBC.
RCC, RCS, REQ, RMI, RNE, RPL, RVC, RVS - conditional repeat
These are branches to the previous instruction. They take no operand, because the branch target is the address of the previously assembled instruction.
Example:
 ldx #0
 mva:rne $500,x $600,x+
The example code copies memory $500-$5ff to $600-$6ff. Here is the same written with standard 6502 commands only:
 ldx #0
loop lda $500,x
 sta $600,x
 inx
 bne loop
SCC, SCS, SEQ, SMI, SNE, SPL, SVC, SVS - conditional skip
These are branches over the next instructions. No operand is required, because the target is the address of the instruction following the next instruction.
Example:
 lda #40
 add:sta $80
 scc:inc $81
In the above example the word-sized variable $80 is incremented by 40.
JCC, JCS, JEQ, JMI, JNE, JPL, JVC, JVS - conditional jumps
These are a kind of 'long' branches. While standard branches (such as BNE) have range of -128..+127, these jumps have range of 64 kB.
Example:
 jne dest
is equivalent to:
 seq:jmp dest
INW - increment word
Increments a 16-bit word in the memory.
Example:
 inw dest
is equivalent to:
 inc dest
 sne:inc dest+1
MVA, MVX, MVY - move byte using accumulator, X or Y
Each of these pseudo-commands requires two operands and substitutes two commands:
 mva source dest = lda source : sta dest
 mvx source dest = ldx source : stx dest
 mvy source dest = ldy source : sty dest
MWA, MWX, MWY - move word using accumulator, X or Y
These pseudo-commands require two operands and are combinations of two MV*'s: one to move the low byte, and the other to move the high byte.
You can't use indirect nor pseudo addressing mode with MW*. Destination must be an absolute address, optionally indexed.
When source is also an absolute address, an mw* source dest expands to:
 mv* source  dest
 mv* source+1 dest+1
When source is an immediate value, an mw* #immed dest expands to:
 mv* <immed dest
 mv* >immed dest+1
When <immed equals >immed and immed is not forward-referenced, xasm skips the second LD*:
 mv* <immed dest
 st* dest+1
If possible, MWX and MWY use increment/decrement commands. For example, mwx #1 dest expands to:
 ldx #1
 stx dest
 dex
 stx dest+1

ADDRESSING MODES

All addressing modes are entered in the standard 6502 convention except for the accumulator addressing mode, which should be marked with the @ character (as in Quick Assembler).

There are two extra immediate addressing modes: < and >, which use the low/high byte of a 16-bit word constant. They are for Quick Assembler compatibility. You can use traditional #< and #>. Note that lda >$ff+5 loads 1 (>$104), while lda #>$ff+5 loads 5 (0+5) to the accumulator, because the unary operator > has a higher priority than the binary plus.

You can explicitly choose absolute (a:) and zero-page (z:) addressing modes.

Examples:

 nop
 asl @
 lda >$1234  assembles to lda #$12
 lda $100,x
 lda 0       zero-page (8-bit address)
 lda a:0     absolute (16-bit address)
 jmp ($0a)
 lda ($80),y

There are pseudo addressing modes, which are similar to pseudo-commands. You may use them just like standard addressing modes in all 6502 commands and pseudo-commands, except for MWA, MWX and MWY:

 cmd a,x+   =  cmd a,x   : inx
 cmd a,x-   =  cmd a,x   : dex
 cmd a,y+   =  cmd a,y   : iny
 cmd a,y-   =  cmd a,y   : dey
 cmd (z),y+ =  cmd (z),y : iny
 cmd (z),y- =  cmd (z),y : dey
 cmd (z,0)  =  ldx #0    : cmd (z,x)
 cmd (z),0  =  ldy #0    : cmd (z),y
 cmd (z),0+ =  ldy #0    : cmd (z),y : iny
 cmd (z),0- =  ldy #0    : cmd (z),y : dey

HISTORY

Version 3.0.1 (2007-04-22)

Version 3.0.0 (2005-05-22)

Version 2.6.1 (2005-05-21)

Version 2.6.0 (2005-02-07)

Version 2.5.2 (2002-10-03)

Version 2.5.1 (2002-08-21)

Version 2.5 (2002-07-08)

Version 2.4.1 (2002-06-27)

Version 2.4 (2002-05-22)

Version 2.3 (2002-02-10)

Version 2.2 (1999-09-10)

Version 2.0 (1998-11-12)

Version 1.2 (1998-08-14)

AUTHOR

Piotr Fusik (fox@scene.pl)

SEE ALSO

xasm home page (http://xasm.atari.org)