mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-26 15:49:18 +00:00
294fe25c36
Stack indexing can now be given either as ",s" or as ",sp" (only relevant for 65816 and 65CE02). git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@78 4df02467-bbd4-4a76-a152-e7ce94205b78
943 lines
34 KiB
Plaintext
943 lines
34 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: !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: !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 6502 CPU's NOP command).
|
|
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 first block of statements will be parsed;
|
|
if it isn't, the second block will be parsed instead
|
|
(if present).
|
|
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: !text "Black", 0 ; Choose wording according to
|
|
!if country = uk { ; content of "country" symbol.
|
|
!text "Grey"
|
|
} else {
|
|
!text "Gray"
|
|
}
|
|
!byte 0
|
|
!text "White", 0
|
|
|
|
; Insert debug commands if symbol "debug" is not zero:
|
|
!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.
|
|
}
|
|
|
|
|
|
Call: !for SYMBOL, START, END { BLOCK }
|
|
Purpose: Looping assembly. The block of statements will be
|
|
parsed a fixed number of times, as specified by the
|
|
values of START and END. For a more flexible
|
|
possibility, have a look at "!do" 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.
|
|
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
|
|
}
|
|
|
|
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 "-Wno-old-for" switch, but then you will get
|
|
warnings for using the *new* syntax.
|
|
When migrating your sources, 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", 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: !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 STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Show a warning during assembly.
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Numbers will be output in decimal _and_ hex format.
|
|
Example: !if * > $a000 {
|
|
!warn "Program reached ROM: ", * - $a000, " bytes overlap."
|
|
}
|
|
|
|
|
|
Call: !error STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Generate an error during assembly (therefore, no
|
|
output file will be generated).
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Numbers will be output in decimal _and_ hex format.
|
|
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 STRING_VALUE [, STRING_VALUE]*
|
|
Purpose: Generate a serious error, immediately stopping
|
|
assembly.
|
|
Parameters: STRING_VALUE: Can be either a string given in double
|
|
quotes or any formula the value parser accepts.
|
|
Numbers will be output in decimal _and_ hex format.
|
|
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 dot), 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.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
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.
|
|
Future versions of ACME may issue errors instead of
|
|
warnings.
|
|
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.
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
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
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
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 commands
|
|
the chosen CPU does not have. 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
|
|
6510 6502 plus undocumented opcodes
|
|
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
|
|
c64dtv2 6502 plus BRA/SAC/SIR plus some of the
|
|
undocumented opcodes
|
|
See "docs/cputypes.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 ; allow 65816 commands from here on
|
|
|
|
|
|
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.
|
|
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
|
|
|
|
|
|
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"
|
|
}
|
|
|
|
|
|
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...
|
|
}
|