mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-25 07:31:52 +00:00
3bb7fce2f0
git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@317 4df02467-bbd4-4a76-a152-e7ce94205b78
1102 lines
40 KiB
Plaintext
1102 lines
40 KiB
Plaintext
|
|
|
|
ACME
|
|
|
|
...the ACME Crossassembler for Multiple Environments
|
|
|
|
--- pseudo opcodes ---
|
|
|
|
|
|
This is a list of all the pseudo opcodes currently implemented.
|
|
Stuff in square brackets is optional, stuff followed by "*" may be
|
|
given more than once. This list is not sorted alphabetically, the
|
|
pseudo opcodes are grouped together according to their usage.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: How to insert values
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !8 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 8-bit values.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: "!08", "!by", "!byte"
|
|
Examples: !08 127, symbol, -128 ; output some values
|
|
!by 14, $3d, %0110, &304, <*, 'c'
|
|
!byte 3 - 4, symbol1 XOR symbol2, 2 ^ tz, (3+4)*7
|
|
|
|
|
|
Call: !16 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 16-bit values in chosen CPU's byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: "!wo", "!word" (and because all currently supported
|
|
CPUs are little-endian, "!le16" is in fact another
|
|
alias)
|
|
Examples: !16 65535, symbol, -32768 ; output some values
|
|
!wo 14, $4f35, %100101010010110, &36304, *, 'c'
|
|
!word 3000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !le16 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 16-bit values in little-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None (but because all currently supported CPUs are
|
|
little-endian, "!16/!wo/!word" are in fact aliases)
|
|
Examples: !le16 65535, symbol, -32768 ; output some values
|
|
!le16 14, $4f35, %100101010010110, &36304, *, 'c'
|
|
!le16 3000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !be16 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 16-bit values in big-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None
|
|
Examples: !be16 65535, symbol, -32768 ; output some values
|
|
!be16 14, $4f35, %100101010010110, &36304, *, 'c'
|
|
!be16 3000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
|
|
Call: !24 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 24-bit values in chosen CPU's byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None (but because all currently supported CPUs are
|
|
little-endian, "!le24" is in fact an alias)
|
|
Examples: !24 16777215, symbol, -8388608, 14, $6a4f35
|
|
!24 %10010110100101010010110, &47336304, *, 'c'
|
|
!24 300000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !le24 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 24-bit values in little-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None (but because all currently supported CPUs are
|
|
little-endian, "!24" is in fact an alias)
|
|
Examples: !le24 16777215, symbol, -8388608, 14, $6a4f35
|
|
!le24 %10010110100101010010110, &47336304, *, 'c'
|
|
!le24 300000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !be24 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 24-bit values in big-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None
|
|
Examples: !be24 16777215, symbol, -8388608, 14, $6a4f35
|
|
!be24 %10010110100101010010110, &47336304, *, 'c'
|
|
!be24 300000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
|
|
Call: !32 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 32-bit values in chosen CPU's byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None (but because all currently supported CPUs are
|
|
little-endian, "!le32" is in fact an alias)
|
|
Examples: !32 $7fffffff, symbol, -$80000000, 14, $46a4f35
|
|
!32 %1001011010010101001011010010, &4733630435, *, 'c'
|
|
!32 300000 - 4, a AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !le32 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 32-bit values in little-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None (but because all currently supported CPUs are
|
|
little-endian, "!32" is in fact an alias)
|
|
Examples: !le32 $7fffffff, symbol, -$80000000, 14, $46a4f35
|
|
!le32 %1001011010010101001011010010, &4733630435, *, 'c'
|
|
!le32 300000 - 4, a AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
Call: !be32 EXPRESSION [, EXPRESSION]*
|
|
Purpose: Insert 32-bit values in big-endian byte order.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
Aliases: None
|
|
Examples: !be32 $7fffffff, symbol, -$80000000, 14, $46a4f35
|
|
!be32 %1001011010010101001011010010, &4733630435, *, 'c'
|
|
!be32 300000 - 4, a AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
|
|
|
|
|
|
Call: !hex PAIRS_OF_HEX_DIGITS
|
|
Purpose: Insert byte values with a minimum of additional
|
|
syntax. This pseudo opcode was added for easier
|
|
writing of external source code generator tools.
|
|
Parameters: PAIRS_OF_HEX_DIGITS: Just hexadecimal digits, without
|
|
any "0x" or "$" prefix. Spaces and TABs are allowed,
|
|
but not needed to separate the byte values.
|
|
Aliases: "!h"
|
|
Examples: !h f0 f1 f2 f3 f4 f5 f6 f7 ; insert values 0xf0..0xf7
|
|
!h f0f1f2f3 f4f5f6f7 ; insert values 0xf0..0xf7
|
|
!h f0f1f2f3f4f5f6f7 ; insert values 0xf0..0xf7
|
|
!h f0f 1f2 ; ERROR: space inside pair!
|
|
!h 0x00, $00 ; ERROR: "0x", "," and "$" are forbidden!
|
|
!h SOME_SYMBOL ; ERROR: symbols are forbidden!
|
|
!h ABCD ; insert value 0xAB, then 0xCD (CAUTION, big-endian)
|
|
|
|
|
|
Call: !fill AMOUNT [, VALUE]
|
|
Purpose: Fill amount of memory with value.
|
|
Parameters: AMOUNT: Any formula the value parser accepts, but it
|
|
must be solvable even in the first pass.
|
|
VALUE: Any formula the value parser accepts. If
|
|
omitted, a default value is used (currently zero).
|
|
Aliases: "!fi"
|
|
Examples: !fi 256, $ff ; reserve 256 bytes
|
|
!fill 2 ; reserve two bytes
|
|
|
|
|
|
Call: !skip AMOUNT
|
|
Purpose: Advance in output buffer without starting a new
|
|
segment.
|
|
Parameters: AMOUNT: Any formula the value parser accepts, but it
|
|
must be solvable even in the first pass (this
|
|
limitation will hopefully be lifted in a future
|
|
release).
|
|
Aliases: None
|
|
Examples: !skip BUFSIZE ; reserve some bytes
|
|
!skip 5 ; reserve five bytes
|
|
|
|
|
|
Call: !align ANDVALUE, EQUALVALUE [, FILLVALUE]
|
|
Purpose: Fill memory until a matching address is reached. ACME
|
|
outputs FILLVALUE until "program counter AND ANDVALUE"
|
|
equals EQUALVALUE.
|
|
Parameters: ANDVALUE: Any formula the value parser accepts, but it
|
|
must be solvable even in the first pass.
|
|
EQUALVALUE: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
FILLVALUE: Any formula the value parser accepts. If it
|
|
is omitted, a default value is used (currently 234,
|
|
that's the opcode of the 6502 CPU's NOP instruction).
|
|
Examples: !align 255, 0 ; align to page (256 bytes)
|
|
!align 63, 0 ; align to C64 sprite block (64 bytes)
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: How to insert text strings
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !convtab KEYWORD [ { BLOCK } ]
|
|
or: !convtab FILENAME [ { BLOCK } ]
|
|
Purpose: Choose text conversion table.
|
|
Parameters: KEYWORD: Name of conversion table. Valid names are:
|
|
pet converts to PetSCII
|
|
raw doesn't convert at all
|
|
scr converts to C64 screencode
|
|
FILENAME: File name of conversion table, given in
|
|
"..." quoting (load from current directory) or in
|
|
<...> quoting (load from library). The file must hold
|
|
exactly 256 bytes.
|
|
BLOCK: A block of assembler statements
|
|
Before encountering this PO, ACME defaults to "raw".
|
|
This PO supersedes the now deprecated "!cbm".
|
|
Aliases: "!ct"
|
|
Examples: !convtab raw
|
|
!text "Test" ; outputs $54 $65 $73 $74
|
|
!ct pet
|
|
!tx "Test" ; outputs $d4 $45 $53 $54
|
|
!ct scr {
|
|
!tx "Test" ; outputs $54 $05 $13 $14
|
|
!ct "my_own_table_file"
|
|
!tx "abcdefg" ; whatever... :)
|
|
}
|
|
!tx "Test" ; outputs $d4 $45 $53 $54 again
|
|
Hint: If you don't want to fiddle with a hex editor to create a
|
|
conversion table file, try using ACME:
|
|
!to "asc2pet.ct", plain ; no load address
|
|
* = 0 ; pc = table index
|
|
; first create "as-is" table
|
|
!for i, 0, 255 {!byte i}
|
|
; now exchange upper and lower case characters
|
|
* = 65, overlay
|
|
!for i, 1, 26 {!byte * + 128}
|
|
* = 97, overlay
|
|
!for i, 1, 26 {!byte * - 32}
|
|
The resulting file can be used as a conversion table to convert to
|
|
PetSCII (which is useless, because ACME can do so anyway. But you get
|
|
the idea).
|
|
|
|
|
|
Call: !text STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Output the given string(s) using the current
|
|
conversion table.
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Please note that formula results won't be converted,
|
|
but single characters involved in calculations will.
|
|
Aliases: "!tx"
|
|
Examples: !text "Loading...", Char_NewLine, "Filename:", 0
|
|
!tx "Offset character is ", offset - 1 + 'a', 0
|
|
|
|
|
|
Call: !pet STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Output the given string(s) using the PetSCII
|
|
conversion table (This means to exchange the upper-
|
|
and lowercase characters; useful for C64 programs).
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Please note that formula results won't be converted,
|
|
but single characters involved in calculations will.
|
|
Examples: !pet "Loading...", Char_NewLine, "Filename:", 0
|
|
!pet "Offset character is ", offset - 1 + 'a', 0
|
|
|
|
|
|
Call: !raw STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Output the given string(s) without any conversion at
|
|
all.
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Examples: !raw "Loading...", Char_NewLine, "Filename:", 0
|
|
!raw "Offset character is ", offset - 1 + 'a', 0
|
|
|
|
|
|
Call: !scr STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Output the given string(s) using the C64 screen code
|
|
conversion table (useful for C64 programs, as you will
|
|
have guessed).
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Please note that formula results won't be converted,
|
|
but single characters involved in calculations will.
|
|
Examples: !scr "Loading...", Char_NewLine, "Filename:", 0
|
|
!scr "Offset character is ", offset - 1 + 'a', 0
|
|
|
|
|
|
Call: !scrxor XOR_VALUE, STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Output the given string(s) using the C64 screen code
|
|
conversion table and exclusive-OR-ing the results with
|
|
the given value (useful for C64 programs when inverse
|
|
video is needed, or EBC mode, etc.).
|
|
Parameters: XOR_VALUE: Any formula the value parser accepts.
|
|
STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Please note that formula results will be neither
|
|
converted nor exclusive-OR-d.
|
|
Single characters involved in calculations will be
|
|
converted, but not exclusive-OR-d.
|
|
Examples: !scrxor $80, "Loading..."
|
|
!scrxor $a0, "Offset char is ", (offset-1+'a') XOR $a0
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: File stuff
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !to FILENAME, FILEFORMAT
|
|
Purpose: Define the output file name and file type. If this
|
|
opcode isn't used, ACME still fully processes the
|
|
source code - as the resulting binary isn't stored,
|
|
this only serves to check for errors. Instead of using
|
|
this pseudo opcode, you can also use the command line
|
|
options "--outfile" and "--format".
|
|
Parameters: FILENAME: A file name given in "..." quoting.
|
|
FILEFORMAT: Name of file format. Valid names are:
|
|
cbm with load address (Commodore format)
|
|
plain without load address
|
|
apple with load address and length (Apple II)
|
|
If FILEFORMAT is omitted, ACME gives a warning and
|
|
then defaults to "cbm" (this can be changed using the
|
|
command line option "--format").
|
|
Examples: !to "eprom.p", plain ; don't add a load address
|
|
!to "demo.o", cbm ; add c64-style load address
|
|
|
|
|
|
Call: !source FILENAME
|
|
Purpose: Assemble another source code file. After having
|
|
processed the new file, ACME continues processing the
|
|
old one.
|
|
Parameters: FILENAME: A file name given in "..." quoting (load
|
|
from current directory) or in <...> quoting (load from
|
|
library).
|
|
Aliases: "!src"
|
|
Examples: !source <6502/std.a> ; Read library file
|
|
!src "Macros.a" ; Read file from current dir
|
|
|
|
|
|
Call: !binary FILENAME [, [SIZE] [, [SKIP]]]
|
|
Purpose: Insert binary file directly into output file.
|
|
Parameters: FILENAME: A file name given in "..." quoting (load
|
|
from current directory) or in <...> quoting (load from
|
|
library).
|
|
SIZE: Any formula the value parser accepts, but it
|
|
must be solvable even in the first pass. If SIZE is
|
|
given, it is used: If the file is longer, only SIZE
|
|
bytes are read; if it is shorter, ACME will use
|
|
padding until SIZE is reached. If SIZE is omitted,
|
|
ACME will include the file until EOF.
|
|
SKIP: Any formula the value parser accepts. If SKIP is
|
|
omitted, it defaults to zero. ACME will start loading
|
|
the file from file offset SKIP. So C64 coders wanting
|
|
to include C64 files without their load addresses
|
|
should use a SKIP value of 2.
|
|
Aliases: "!bin"
|
|
Examples: !binary <Own/menudata.b> ; insert library file
|
|
!bin "asc2pet.b", 256, 2 ; insert 256 bytes
|
|
; from file offset 2.
|
|
!bin "table", 2, 9 ; insert 2 bytes from offset 9
|
|
!bin "list",, 9 ; insert from offset 9 to EOF
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Symbols
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !zone [TITLE] [ { BLOCK } ]
|
|
Purpose: Switch to new zone of local symbols. Zones can either
|
|
be nested or used sequentially.
|
|
Parameters: TITLE: May consist of letters and digits. Its only
|
|
purpose is to be displayed in error messages, so it'll
|
|
be omitted in most cases.
|
|
BLOCK: A block of assembler statements
|
|
If no block is given, the previous zone is terminated
|
|
and the new zone is started.
|
|
If a block is given, the old zone continues after the
|
|
block.
|
|
Aliases: "!zn"
|
|
Examples: .backgroundcolor = 0 ; some local symbol
|
|
!zone File_IO ; new zone begins here, so
|
|
.backgroundcolor = 1 ; this is a different symbol
|
|
!zn LinkedList_Init
|
|
.backgroundcolor = 2
|
|
!zone LinkedList { ; start of nested zone
|
|
; imagine some code here...
|
|
!zone LinkedList_Init
|
|
; imagine some more code here...
|
|
!zone LinkedList_Body {
|
|
; imagine yet some more code here...
|
|
!zone LinkedList_SecondPart
|
|
; imagine still some more code here...
|
|
}
|
|
!zone LinkedList_End
|
|
; you know what to imagine here...
|
|
}
|
|
.backgroundcolor = 3 ; -> "Symbol already defined."
|
|
|
|
|
|
Call: !symbollist FILENAME
|
|
Purpose: Write a symbol list to the given file after assembly
|
|
is finished. The list will contain all global symbols.
|
|
This table could be loaded during another assembly
|
|
session using the "!source" pseudo opcode.
|
|
Parameters: FILENAME: A file name given in "..." quoting.
|
|
Aliases: "!sl"
|
|
Examples: !sl "Symbols.a" ; produce symbol list after assembly
|
|
!sl "global" ; produce symbol list after assembly
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Flow control
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !if CONDITION { BLOCK } [ else { BLOCK } ]
|
|
Purpose: Conditional assembly. If the given condition is true,
|
|
the matching block of statements will be parsed;
|
|
if no condition is true, the ELSE block (if present)
|
|
will be parsed.
|
|
Parameters: CONDITION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ; Choose word according to "country" symbol:
|
|
!if country == uk {
|
|
!text "Grey"
|
|
} else if country == fr {
|
|
!text "Gris"
|
|
} else if country == de {
|
|
!text "Grau"
|
|
} else {
|
|
!text "Gray"
|
|
}
|
|
|
|
; Insert debug code depending on symbol "debug":
|
|
!if debug { lda #'z':jsr char_output }
|
|
|
|
|
|
Call: !ifdef SYMBOL { BLOCK } [ else { BLOCK } ]
|
|
or: !ifdef SYMBOL STATEMENT
|
|
Call: !ifndef SYMBOL { BLOCK } [ else { BLOCK } ]
|
|
or: !ifndef SYMBOL STATEMENT
|
|
Purpose: Conditional assembly, depending on whether a symbol is
|
|
already defined or not.
|
|
With "ifdef", if the symbol is defined, the first
|
|
block of statements will be parsed; if it isn't, the
|
|
second block will be parsed instead (if present).
|
|
With "ifndef", it's the other way around: If the
|
|
symbol isn't defined, the first block of statements
|
|
will be parsed; if it is defined, the second block
|
|
will be parsed instead (if present).
|
|
CAUTION: These opcodes were added to speed up parsing
|
|
of library files (see example below). They can be used
|
|
to tell passes apart, therefore only use them in your
|
|
own files if you're sure you *really* know what you
|
|
are doing - using them in the wrong way will result in
|
|
loads of error messages.
|
|
Parameters: SYMBOL: Any valid symbol name.
|
|
BLOCK: A block of assembler statements.
|
|
STATEMENT: Any assembler statement.
|
|
Examples: ; this was taken from <6502/std.a>:
|
|
!ifdef Lib_6502_std_a !eof ; in later passes,
|
|
Lib_6502_std_a = 1 ; skip this file.
|
|
; During the first pass, the symbol is not defined,
|
|
; therefore the file will get parsed. During all
|
|
; further passes, the symbol is already defined,
|
|
; therefore the file will be skipped.
|
|
|
|
; if the following code gets included several times,
|
|
; only assemble it at the first location:
|
|
!ifndef my_label {my_label} ; only define if undefined
|
|
!if * == my_label {
|
|
; imagine some code here...
|
|
; this block will only be assembled at the
|
|
; first location where it is included. all
|
|
; further instances will be skipped.
|
|
}
|
|
|
|
; include at most one driver source code:
|
|
!ifdef RAM_REU {
|
|
!src "driver_reu.a"
|
|
} else ifdef RAM_GEORAM {
|
|
!src "driver_georam.a"
|
|
} else ifdef RAM_VDCRAM {
|
|
!src "driver_vdcram.a"
|
|
} else ifdef RAM_SUPERRAM {
|
|
!src "driver_superram.a"
|
|
} else {
|
|
!src "driver_noram.a"
|
|
}
|
|
|
|
|
|
Call: !for SYMBOL, START, END { BLOCK }
|
|
or: !for SYMBOL in ITERABLE { BLOCK }
|
|
Purpose: Looping assembly. The block of statements will be
|
|
parsed a fixed number of times, as specified by the
|
|
arguments:
|
|
When using the first syntax, SYMBOL will simply count
|
|
from START to END.
|
|
When using the second syntax, SYMBOL will iterate over
|
|
the contents of the ITERABLE, which must be a string
|
|
or a list.
|
|
For more flexible loop constructs, have a look at
|
|
"!do" and "!while" below.
|
|
Parameters: SYMBOL: Any valid symbol name.
|
|
START: Any formula the value parser accepts, but it
|
|
must be solvable even in the first pass. SYMBOL will
|
|
have this value during the first loop cycle.
|
|
END: Any formula the value parser accepts, but it must
|
|
be solvable even in the first pass. SYMBOL will have
|
|
this value during the last loop cycle.
|
|
ITERABLE: This must be a string or a list, but its
|
|
length must be defined even in the first pass (and of
|
|
course it should stay the same during all subsequent
|
|
passes).
|
|
If ITERABLE is a list, its _items_ are allowed
|
|
to be undefined.
|
|
If ITERABLE is a string, SYMBOL will be set to
|
|
each of its character codes in turn, using the
|
|
currently chosen conversion table.
|
|
BLOCK: A block of assembler statements.
|
|
If START or END are floats, they will be converted to
|
|
integers (never use floats for loop counters). If
|
|
START is less than or equal to END, SYMBOL will get
|
|
incremented at the end of each cycle; if START is
|
|
greater than END, SYMBOL will get decremented at the
|
|
end of each cycle. So after leaving the loop, SYMBOL
|
|
will have an "illegal" value (END + 1 if counting up,
|
|
END - 1 if counting down).
|
|
Please note that it is impossible to change the number
|
|
of loop cycles "inside" the loop by fiddling with the
|
|
counter using the "!set" pseudo opcode: The "!for"
|
|
routine keeps its own copy of the counter value and
|
|
only sets the symbol value, it never reads it back.
|
|
This was done to eliminate a possibility to hang ACME.
|
|
Examples:
|
|
int2BCD ; conversion table: integer to BCD
|
|
!for Outer, 0, 9 {
|
|
!for Inner, 0, 9 {
|
|
!byte (Outer << 4) OR Inner
|
|
}
|
|
}
|
|
!fill 156, $ff ; values above 99 give 255 (invalid)
|
|
|
|
BCD2int ; conversion table: BCD to integer
|
|
!for Outer, 0, 9 {
|
|
!for Inner, 0, 9 {
|
|
!byte 10 * Outer + Inner
|
|
}
|
|
!fill 6, $ff ; invalid BCD values give 255
|
|
}
|
|
!fill 96, $ff ; invalid BCD values give 255
|
|
|
|
quickclear ; generate speedcode to clear C64 screen
|
|
lda #' '
|
|
!for i, 0, 999 {
|
|
sta $0400 + i
|
|
}
|
|
|
|
split_table_lo ; generate two tables from one list
|
|
!for h in my_handler_list {
|
|
!by <h
|
|
}
|
|
split_table_hi
|
|
!for h in my_handler_list {
|
|
!by >h
|
|
}
|
|
|
|
hidden_string ; "encrypt" a string by XORing with address
|
|
!ct scr { ; use screen codes
|
|
!for c in "very secret message" {
|
|
!by c XOR <*
|
|
}
|
|
}
|
|
|
|
Miscellaneous: The old syntax
|
|
!for SYMBOL, END { BLOCK }
|
|
where START was always implied to be 1 is still fully
|
|
supported, but gives a warning to get people to change
|
|
to the new syntax.
|
|
You can disable this warning using the "--dialect" or
|
|
the "-Wno-old-for" switches, but then you will get
|
|
warnings for using the *new* syntax.
|
|
When migrating your sources to the current syntax,
|
|
bear in mind that it is no longer possible to skip the
|
|
block completely by specifying a loop count of zero.
|
|
Also note that with the new algorithm, SYMBOL has a
|
|
different value after the block than during the last
|
|
loop cycle, while the old algorithm kept that last
|
|
value.
|
|
|
|
|
|
Call: !set SYMBOL = VALUE
|
|
Purpose: Assign given value to symbol even if the symbol
|
|
already has a different value. Needed for loop
|
|
counters when using "!do"or "!while", for example.
|
|
Only use this opcode for something else if you're sure
|
|
you *really* know what you are doing... :)
|
|
Parameters: SYMBOL: Any valid symbol name.
|
|
VALUE: Any formula the value parser accepts.
|
|
Example: see "!do" below
|
|
|
|
|
|
Call: !do [KEYWORD CONDITION] { BLOCK } [KEYWORD CONDITION]
|
|
Purpose: Looping assembly. The block of statements can be
|
|
parsed several times, depending on the given
|
|
condition(s).
|
|
Conditions may be placed before or after the block (or
|
|
even at both places), they are then parsed in every
|
|
repetition before or after the block respectively. If
|
|
there is a condition before the block and it isn't
|
|
met when first checked, the block will be skipped.
|
|
Parameters: KEYWORD: Either "until" or "while" (without quotes).
|
|
CONDITION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ; a loop with conditions at both start and end
|
|
!set a = 0 ; init loop counter
|
|
!do while loop_flag == TRUE {
|
|
lda #a
|
|
sta label + a
|
|
!set a = a + 1
|
|
} until a > 6
|
|
|
|
; a loop with a condition at the start
|
|
!do while * < $c000 { nop }
|
|
|
|
; a loop with a condition at the end
|
|
!do { !wo * + base } while * < base + 345
|
|
|
|
; a never ending loop - this will cause an error
|
|
!do while 3 < 4 { nop } until 3 == 4
|
|
|
|
; an empty loop - this will hang ACME
|
|
!do until 3 == 4 { } while 3 < 4
|
|
|
|
|
|
Call: !while [CONDITION] { BLOCK }
|
|
Purpose: Looping assembly. The block of statements can be
|
|
parsed several times, depending on the given
|
|
condition.
|
|
The condition is parsed in every repetition before the
|
|
actual block. If it isn't met when first checked, the
|
|
block will be skipped.
|
|
Parameters: CONDITION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ; a loop with a counter
|
|
!set a = 0 ; init loop counter
|
|
!while a < 6 {
|
|
lda #a
|
|
sta label + a
|
|
!set a = a + 1
|
|
}
|
|
|
|
; a loop depending on program counter
|
|
!while * < $c000 { nop }
|
|
|
|
; a never ending loop - this will cause an error
|
|
!while 3 < 4 { nop }
|
|
|
|
; an empty loop - this will hang ACME
|
|
!while 3 != 4 { }
|
|
|
|
|
|
Call: !endoffile
|
|
Purpose: Stop processing the current source file. Using this
|
|
pseudo opcode you can add explanatory text inside your
|
|
source file without having to comment out every single
|
|
line of it.
|
|
Aliases: "!eof"
|
|
Example: rts ; some assembler mnemonic
|
|
!eof
|
|
Though this text isn't preceded by a semicolon, it is
|
|
treated as if it were a comment. In fact, ACME doesn't
|
|
even parse this anymore - the file gets closed when
|
|
"!eof" is reached.
|
|
|
|
|
|
Call: !warn VALUE [, VALUE]*
|
|
Purpose: Show a warning during assembly.
|
|
Parameters: VALUE: Can be either a string given in double quotes
|
|
or any formula the value parser accepts.
|
|
Integer numbers will be output in both decimal _and_
|
|
hex formats.
|
|
Example: !if * > $a000 {
|
|
!warn "Program reached ROM: ", * - $a000, " bytes overlap."
|
|
}
|
|
|
|
|
|
Call: !error VALUE [, VALUE]*
|
|
Purpose: Generate an error during assembly (therefore, no
|
|
output file will be generated).
|
|
Parameters: VALUE: Can be either a string given in double quotes
|
|
or any formula the value parser accepts.
|
|
Integer numbers will be output in both decimal _and_
|
|
hex formats.
|
|
Example: rts ; end of some function
|
|
start !source "colors.a"
|
|
end !if end - start > 256 {
|
|
!error "Color strings are ", end - start - 256, " bytes too long."
|
|
}
|
|
|
|
|
|
Call: !serious VALUE [, VALUE]*
|
|
Purpose: Generate a serious error, immediately stopping
|
|
assembly.
|
|
Parameters: VALUE: Can be either a string given in double quotes
|
|
or any formula the value parser accepts.
|
|
Integer numbers will be output in both decimal _and_
|
|
hex formats.
|
|
Example: !source "part1.a" ; sets part1_version
|
|
!source "part2.a" ; sets part2_version
|
|
!if part1_version != part2_version {
|
|
!serious "part1.a and part2.a don't match!"
|
|
}
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Macro usage
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !macro TITLE [[~]SYMBOL [, [~]SYMBOL]*] { BLOCK }
|
|
Purpose: Define a macro.
|
|
Parameters: TITLE: The macro's desired name (same rules as for
|
|
symbol names). If the title's first character is a dot
|
|
("."), the macro will be local (though why anyone
|
|
could want this is beyond me).
|
|
SYMBOL: The desired name for the parameter value at
|
|
call time. Normally, these parameter symbols should be
|
|
local (first character a '.' or a '@'), as different
|
|
macro calls will almost for sure have different
|
|
parameter values.
|
|
If you prefix SYMBOL with a '~' character, it will be
|
|
called by reference, not by value: Changing the value
|
|
inside the macro will result in the "outer" symbol to
|
|
be changed as well.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ; far branch, as defined in <6502/std.a>
|
|
!macro bne .target {
|
|
beq * + 5
|
|
jmp .target
|
|
}
|
|
|
|
; increase 16-bit counters
|
|
!macro dinc .target {
|
|
inc .target
|
|
bne + ; "bne * + 5" would not work in zp
|
|
inc .target + 1
|
|
+
|
|
}
|
|
; Yes, anonymous label references can be used with
|
|
; macros (unlike several other assemblers). That's
|
|
; because ACME's macros are implemented more like
|
|
; real functions.
|
|
|
|
; load A and X
|
|
!macro ldax .target {
|
|
lda .target
|
|
ldx .target + 1
|
|
}
|
|
|
|
; store A and X
|
|
!macro stax .target {
|
|
sta .target
|
|
stx .target + 1
|
|
}
|
|
|
|
; use call-by-reference for return value
|
|
!macro reserve ~.address, .amount {
|
|
.address = external_pc
|
|
!set external_pc = external_pc + .amount
|
|
}
|
|
|
|
; define a pixel row of a C64 hardware sprite
|
|
!macro SpriteLine .v {
|
|
!by .v >> 16, (.v >> 8) & 255, .v & 255
|
|
}
|
|
|
|
|
|
Call: +TITLE [ARGUMENT [, ARGUMENT]*]
|
|
Purpose: Call a macro, using the given parameter values.
|
|
Parameters: TITLE: The macro's name as given in its definition.
|
|
ARGUMENT: This is either any formula the value parser
|
|
accepts, or (new in release 0.86) a '~' character
|
|
followed by a symbol name. The '~'-prefix indicates
|
|
call-by-reference semantics, which means that when the
|
|
macro changes the symbol's value, the caller's
|
|
symbol's value will change as well.
|
|
Examples: inc label
|
|
bne mark ; "near" branch
|
|
inc label2
|
|
+bne mark2 ; "far" branch
|
|
|
|
inc $fa ; increase 8-bit counter
|
|
+dinc $fb ; increase 16-bit counter
|
|
|
|
ldy label ; get byte
|
|
+ldax label2 ; get two bytes
|
|
|
|
; using macro calls in a macro definition
|
|
!macro cp16 .source, .target {
|
|
+ldax .source
|
|
+stax .target
|
|
}
|
|
|
|
; use call-by-reference for return value
|
|
!set external_pc = $0400
|
|
+reserve ~.line_buffer, 80
|
|
+reserve ~.in_buffer, 256
|
|
+reserve ~.out_buffer, 256
|
|
+reserve ~.byte_var, 1
|
|
|
|
; define a C64 hardware sprite
|
|
; 765432107654321076543210
|
|
+SpriteLine %........................
|
|
+SpriteLine %.#......................
|
|
+SpriteLine %.##.....................
|
|
+SpriteLine %.###....................
|
|
+SpriteLine %.####...................
|
|
+SpriteLine %.#####..................
|
|
+SpriteLine %.######.................
|
|
+SpriteLine %.#######................
|
|
+SpriteLine %.########...............
|
|
+SpriteLine %.#########..............
|
|
+SpriteLine %.########...............
|
|
+SpriteLine %.######.................
|
|
+SpriteLine %.######.................
|
|
+SpriteLine %.##..##.................
|
|
+SpriteLine %.#....##................
|
|
+SpriteLine %......##................
|
|
+SpriteLine %.......##...............
|
|
+SpriteLine %.......##...............
|
|
+SpriteLine %........##..............
|
|
+SpriteLine %........##..............
|
|
+SpriteLine %........................
|
|
!byte 0 ; pad to 64-byte block
|
|
|
|
Since release 0.86, different macros are allowed to have the same name
|
|
as long as their parameter lists differ in size (number of arguments)
|
|
or type (call-by-value vs. call-by-reference). So
|
|
!macro process_bytes b1, b2 {...whatever...}
|
|
!macro process_bytes b1, b2, b3 {...whatever...}
|
|
!macro process_bytes b1, b2, ~b3 {...whatever...}
|
|
can *all* be used at the same time without any name clash.
|
|
|
|
Since release 0.97, lists are supported. This is useful for macros if
|
|
you want an arbitrary number of arguments: Just define the macro with
|
|
a single argument, then pass a list and have the macro iterate over
|
|
its contents.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Segment assembly
|
|
----------------------------------------------------------------------
|
|
|
|
Call: * = EXPRESSION [, MODIFIER]*
|
|
Purpose: Set program counter to given value and start new
|
|
segment. This opcode must be given at least once
|
|
(or the command line option "--setpc" must be used).
|
|
If segments overlap each other, warnings will be
|
|
issued. Because some people do this overlapping
|
|
on purpose, the warnings can be suppressed using
|
|
modifier keywords.
|
|
Using the "--strict-segments" CLI switch, these
|
|
warnings can be turned onto errors. Future versions of
|
|
ACME may do that by default - so if needed, use the
|
|
modifier keywords.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
MODIFIER: "overlay" or "invisible" (without quotes):
|
|
"overlay" suppresses the warning "Segment starts
|
|
inside another one, overwriting it".
|
|
"invisible" makes the new segment invisible, so that
|
|
_other_ segments will never raise the warning "Segment
|
|
reached another one, overwriting it".
|
|
Examples: !to "TinyDemo", cbm ; define output file + format
|
|
* = $0801 ; start at C64 BASIC start
|
|
!src "basicmacros.a" ; include macro definitions
|
|
+basic_header ; call program header macro
|
|
!src "main.a" ; include main program
|
|
* = $1000 ; jump to new segment
|
|
!bin "music.b" ; load music to $1000
|
|
* = $8000 ; jump to new segment
|
|
!bin "pic.b" ; load graphics to $8000
|
|
* = $8010, overlay, invisible ; go back and patch
|
|
; the graphics, suppressing warnings
|
|
; After assembly, ACME will save everything from $0801
|
|
; up to the highest address written to. The resulting
|
|
; file will contain some big unused areas (zero'd),
|
|
; but demos will get compressed anyway... :)
|
|
|
|
|
|
Call: !initmem EXPRESSION
|
|
Purpose: Define "unchanged" memory. ACME will fill its output
|
|
buffer completely with the given value before storing
|
|
the assembled code. So gaps between segments will
|
|
contain the desired byte when writing the output file.
|
|
Instead of using this pseudo opcode, you can also use
|
|
the "--initmem" command line option. If neither is
|
|
used, the buffer is cleared.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass (because
|
|
this opcode will be ignored in all later passes).
|
|
Examples: !to "TinyDemo", cbm ; define output file + format
|
|
!initmem $ea ; default memory content $ea.
|
|
* = $0801 ; start at C64 BASIC start
|
|
!src "basicmacros.a" ; include macro definitions
|
|
+basic_header ; call program header macro
|
|
!src "main.a" ; include main program
|
|
* = $1000 ; jump to new segment
|
|
!bin "music.b" ; load music to $1000
|
|
* = $8000 ; jump to new segment
|
|
!bin "pic.b" ; load graphics to $8000
|
|
* = $8010, overlay, invisible ; go back and patch
|
|
; the graphics, suppressing warnings
|
|
; This is the same example as before, but now the big
|
|
; unused areas will contain the value $ea instead of
|
|
; zero.
|
|
|
|
!initmem $ff ; Default memory content is now $ff.
|
|
; Useful if you want to store your code in an EPROM.
|
|
|
|
|
|
Call: !xor EXPRESSION [ { BLOCK } ]
|
|
Purpose: Change the value to XOR all output bytes with (the
|
|
value defaults to zero on startup). This "encryption"
|
|
facility was added to compensate for the shortcomings
|
|
of the "!scrxor" pseudo opcode, which only XORs
|
|
strings and characters, but not numbers.
|
|
When used with block syntax, the previously chosen
|
|
value is restored afterwards.
|
|
Parameters: EXPRESSION: Any formula the value parser accepts.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ; first as normal screencodes:
|
|
!scr "Hello everybody...", GROUPLOGOCHAR
|
|
; and now as inverted screencodes:
|
|
!xor $80 {
|
|
!scr "Hello everybody...", GROUPLOGOCHAR
|
|
}
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Offset assembly
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !pseudopc EXPRESSION { BLOCK }
|
|
Purpose: Assemble code as if the program counter had the given
|
|
value, effectively producing a program that has to be
|
|
copied to a different address space before being run.
|
|
After having processed the block of statements with
|
|
the new program counter, the updated (!) old program
|
|
counter is used again.
|
|
Thanks to the block syntax, offset assembly can now be
|
|
nested. Then the old program counter would not
|
|
necessarily be the *real* program counter, but could
|
|
be a pseudopc as well. ;)
|
|
Parameters: EXPRESSION: Any formula the value parser accepts, but
|
|
it must be solvable even in the first pass.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: ldx #.shifted_end - .shifted_start
|
|
- lda .shifted_start - 1, x
|
|
sta .target - 1, x
|
|
dex
|
|
bne -
|
|
jmp .target
|
|
.shifted_start
|
|
!pseudopc $0400 {
|
|
.target ; imagine some code here...
|
|
; it should be copied to $0400 and executed *there*
|
|
}
|
|
.shifted_end
|
|
|
|
Miscellaneous: If you need to convert a label or the program counter
|
|
from its "pseudopc" to its "real" value, you can do
|
|
that using the "&" operator. Given the example above,
|
|
the symbol ".target" will evaluate to the value $0400,
|
|
but "&.target" will evaluate to the same value as
|
|
".shifted_start" will.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: CPU support pseudo opcodes (especially 65816 support)
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !cpu KEYWORD [ { BLOCK } ]
|
|
Purpose: Select the processor to produce code for. If this PO
|
|
isn't used, ACME defaults to the 6502 CPU (or to the
|
|
one selected by the "--cpu" command line option).
|
|
ACME will give errors if you try to assemble
|
|
instructions the chosen CPU does not support. You can
|
|
change the chosen CPU at any time. When used with
|
|
block syntax, the previously chosen CPU value is
|
|
restored afterwards.
|
|
Parameters: KEYWORD: Currently valid keywords are:
|
|
6502 for the original MOS 6502
|
|
nmos6502 6502 plus undocumented opcodes
|
|
6510 (alias for "nmos6502")
|
|
65c02 6502 plus BRA,PHX/Y,PLX/Y,STZ,TRB/TSB
|
|
r65c02 65c02 plus BBRx, BBSx, RMBx, SMBx
|
|
w65c02 r65c02 plus STP/WAI
|
|
65816 65c02 plus 16/24-bit extensions
|
|
65ce02 r65c02 plus Z reg, long branches, ...
|
|
4502 65ce02 with MAP instead of AUG
|
|
m65 4502 plus 32-bit extensions
|
|
c64dtv2 6502 plus BRA/SAC/SIR plus some of the
|
|
undocumented opcodes
|
|
See "docs/cputypes/all.txt" for more info.
|
|
BLOCK: A block of assembler statements.
|
|
Examples: !if cputype == $65c02 {
|
|
!cpu 65c02 { ; temporarily allow 65c02 stuff
|
|
stz .todelete
|
|
}
|
|
} else {
|
|
pha
|
|
lda #0
|
|
sta .todelete
|
|
pla
|
|
}
|
|
rts
|
|
!cpu 65816 ; now allow instructions of 65816 cpu
|
|
|
|
|
|
Call: !al [ { BLOCK } ]
|
|
Purpose: Assume long (16 bits) accumulator. Only allowed when
|
|
producing code for the 65816 CPU. When used with block
|
|
syntax, the previous configuration is restored
|
|
afterwards.
|
|
|
|
|
|
Call: !as [ { BLOCK } ]
|
|
Purpose: Assume short (8 bits) accumulator. Only needed when
|
|
producing code for the 65816 CPU. When used with block
|
|
syntax, the previous configuration is restored
|
|
afterwards. Short accumulator is the default in every
|
|
pass.
|
|
|
|
|
|
Call: !rl [ { BLOCK } ]
|
|
Purpose: Assume long (16 bits) index registers. Only allowed
|
|
when producing code for the 65816 CPU. When used with
|
|
block syntax, the previous configuration is restored
|
|
afterwards.
|
|
|
|
|
|
Call: !rs [ { BLOCK } ]
|
|
Purpose: Assume short (8 bits) index registers. Only needed
|
|
when producing code for the 65816 CPU. When used with
|
|
block syntax, the previous configuration is restored
|
|
afterwards. Short registers are the default in every
|
|
pass.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Type system
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !address [ { BLOCK } ]
|
|
or: !address SYMBOL = VALUE
|
|
Purpose: Mark a block or a statement as "explicitly defined
|
|
symbols are holding addresses".
|
|
Parameters: BLOCK: A block of assembler statements
|
|
Everything inside the block will be parsed as usual,
|
|
but explicitly defined symbols will be marked as
|
|
referencing memory.
|
|
If no block is given, only the current statement will
|
|
be affected, which should then be an explicit symbol
|
|
definition.
|
|
To make use of this feature, you need to use the
|
|
"-Wtype-mismatch" CLI switch.
|
|
Aliases: "!addr"
|
|
Examples: !addr k_chrout = $ffd2 ; this is an address
|
|
CLEAR = 147 ; but this is not
|
|
!addr {
|
|
; these are addresses:
|
|
sid_v1_control = $d404
|
|
sid_v2_control = $d40b
|
|
sid_v3_control = $d412
|
|
}
|
|
; these are not:
|
|
sid_VOICECONTROL_NOISE = %#.......
|
|
sid_VOICECONTROL_RECTANGLE = %.#......
|
|
sid_VOICECONTROL_SAWTOOTH = %..#.....
|
|
sid_VOICECONTROL_TRIANGLE = %...#....
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
Section: Obsolete pseudo opcodes (they will throw errors if used)
|
|
----------------------------------------------------------------------
|
|
|
|
Call: !cbm
|
|
Purpose: Use PetSCII as the text conversion table. Now
|
|
superseded by the "!convtab" pseudo opcode.
|
|
Old usage: !cbm ; gives "use !ct pet instead" error
|
|
Now use: !convtab pet ; does the same without error
|
|
If you just want to assemble an old source code
|
|
without touching it, use the "--dialect" CLI switch:
|
|
Using "--dialect 0.94.6" or earlier will assemble this
|
|
pseudo opcode without throwing an error.
|
|
|
|
|
|
Call: !subzone [TITLE] { BLOCK }
|
|
Purpose: Allows nesting of zones. Now superseded by "!zone"
|
|
because that allows nesting as well.
|
|
Parameters: TITLE: May consist of letters and digits.
|
|
BLOCK: A block of assembler statements.
|
|
Aliases: "!sz"
|
|
Old usage: !subzone graphics {
|
|
!source "graphics.a"
|
|
}
|
|
Now use: !zone graphics {
|
|
!source "graphics.a"
|
|
}
|
|
If you just want to assemble an old source code
|
|
without touching it, use the "--dialect" CLI switch:
|
|
Using "--dialect 0.94.6" or earlier will assemble this
|
|
pseudo opcode without throwing an error.
|
|
|
|
|
|
Call: !realpc
|
|
Purpose: Restore the program counter to its real value,
|
|
therefore finishing offset assembly. Because
|
|
"!pseudopc" now knows block syntax and can be nested,
|
|
there's no reason to use "!realpc" any more.
|
|
Old usage: !pseudopc $0400
|
|
; imagine some code here...
|
|
!realpc
|
|
Now use: !pseudopc $0400 {
|
|
; imagine some code here...
|
|
}
|
|
If you just want to assemble an old source code
|
|
without touching it, use the "--dialect" CLI switch:
|
|
Using "--dialect 0.94.6" or earlier will assemble this
|
|
pseudo opcode without throwing an error.
|
|
Using "--dialect 0.85", not even a warning is thrown.
|