Brackets:
Brackets, { and }, may be used anywhere within the code to help organizeit structurally, they are converted to whitespace and then removed bythe assembler.
Labels / Sublabels / +/- Labels:
A label is used to represent a position in code, and allows one to codewithout having to constantly update branches and jumps/calls. A labelshould be able to be used in any opcode, but was specifically added tobe used with branches, jumps, and calls. Labels can contain A-Za-z0-9_.They must end with : or ()
A sublabel is used to declare labels within labels that will share itsaddress space only, and can contain the same characters as a label, butmust start with a period. A sublabel must not end with a : or ().Here's an example:
nop
.l1
bra .l1
proc2:
nop
.l1
bra .l1
;The two opcodes below will branch back and forth forever.
- bra +
+ bra -
Sublabels allow you to reuse redundantly named labels such as loop, end,etc. without causing duplicate label conflicts. A new sublabel group isstarted immediately after a label is declared automatically. A +/- labelcan be up to 3 levels deep, e.g. +, ++, +++, -, --, ---. They overwritetheir pc offsets immediately after being redefined. Useful for very shortloops, when even something like .loop would become redundant in a longroutine.
Lastly, there are labels specifically for macros. They are identical toreal labels, and begin with a ?
Example: ?label:
Do not use these outside of macros!!
Defines:
xkas uses a pretty powerful define system, which does quite a bit morethan a typical define. To start, they are prefixed with !
Syntax to declare a define is as follows, both are identical:
!x = *
!y equ *
You must have a space on both side of the separator, e.g. ' = ', or' equ '. !x=* will not work.A define can be anything, a label, another define, a math formula, itcan include the formatting for the opcode or not, etc.Here are some examples:
!x = $00
lda !x ;lda $00
lda #!x ;lda #$00
!x equ [$00],y
lda !x ;lda [$00],y
!y = $12
!x = !y$34
lda !x ;lda $1234
!phr = "pha : phx : phy"
!phr()
You can end defines with () if you like, it helps to clarify that thedefine is used as a function, instead of as an argument. But is completelyoptional. Also, if you need to use spaces within your define, then youmust use quotes around the right hand side of the operand, as shown abovewith !phr.
Macros:
Macros can be nested (macros with macros) up to 512 levels deep, thelimitation is there only to prevent macro a calling macro b, and viceversa, resulting in a dead-lock of the assembler. incsrc, below, has thesame 512 level deep limit, for exactly the same reason. Syntax of a macrois:
?loop:
lda <arg1>
sta.l $<arg2>
bra ?loop
endmacro
Things to note: The first and last line from the above example must beon their own lines, you cannot use the : command to concatenate thesecommands. You can use as many arguments as you like. Yes, infinitearguments, if you like. Or none at all. To use the argument within themacro, simply use <> around the argument name. To use the macrowithin your code, use %macroname(arg1, ...).This accepts quotes, as well.
This example would be valid: %writestr(3, "Hello", "pha : plb")
Opcode size specifications:You can specify the size of an opcode with .b, .w, and .l
This is needed with decimal, e.g. lda #0.. is that lda #$00, orlda #$0000? You would have to use lda.b #0, or lda.w #0, or use assume.See assume's usage below if you're interested.
I do not use the standard ! < > as size specifiers, namely because Ialready use these for defines, shifts, and macro arguments. Sorry if youdon't like not having these, but there isn't an easy workaround,I'm afraid.
: Separator
The : is used to allow multiple opcodes on the same line. It requiresa space before and after usage, to differentiate it from the : usedto end labels.
Example: lda #$00 : sta $00
This is useful for making each line define an action, rather than justan opcode. Very useful when combined with { } to indicate a structuralflow of assembly code, rather than a straight descending list. But you'refree to code however you like.
# psuedo-opcodes:
For convenience, I have added psuedo-opcodes to commonly used opcodes,such as nop, asl, lsr, inc, inx, iny, dec, dex, dey, etc. You can specifyasl #4, which will print the equivalent of asl : asl : asl : asl to thefile, the same goes for the other opcodes.
Math:
Math is supported in all opcodes, and all functions, however, it hasone major variation from traditional math. It does not resolve mathin order if symbols, it instead resolves from left to right, and alsodoes not use parinthesis to denote order. An example:
Standard Math:
mov a,5+6*2 ;=17
Left-to-right Math:
mov a,6*2+5 ;=17
mov a,5+6*2 ;=22
I do apologize for this limitation, it was just far easier and fasterto implement left-to-right math, and for an assembler, I haven't reallycome across anything that could not be represented in left-to-right math.To use left-to-right math, just reorder your multiplication and divisionto the start, and use addition and subtraction at the end. The math systemsupports the following commands:
+ : Addition
- : Subtraction
* : Multiplication
/ : Division
<< : Left-shift ( x<
& : Bitwise AND
| : Bitwise OR
^ : Bitwise XOR (Note: Not exponentials)
~ : Bitwise NOT
Math also works with labels. Example:
mov a,label2-label1+2
hirom:
Use hirom addressing.
lorom:
Use lorom addressing.
header:
Assume the rom has a header, the default assumes there is no header. I dothis because roms without a header are far easier to work with in hexeditors, and since 95% of work is done through emulators, why use aheader that is used for copiers only?
assume:
This is experimental.
Use this to optimize code, and avoid specifying opcodes with sizes.assume accepts mx (a and x/y bits), db (bank byte), and d. I do notsupport actually using d, yet. So you won't get any optimizations byusing it. I did say this was experimental, right? I hope to add d, oneday...
mx supports the following arguments:
%00, %01, %10, %11, %0-, %1-, %-0, %-1
The - just means to leave that bit alone. 0 means 16 bit, and 1 means8 bit. The left is for a's size, the right is for x/y's size.
db supports $xx, an 8-bit byte.
d supports $xxxx, a 16-bit byte.
All support off as an argument, which will turn off assumes for saidmode.
mx will automatically update, when enabled, when it sees rep/sepcommands. It will not be able to update between functions, whenit sees plp, etc. The same holds true for db, and d. You must updatethese manually!! It would be ideal to declare the assumes at the startof a function, and turn them off at the end. Here's an example:
lda #0 ;lda #$00
ldx #0 ;ldx #$0000
lda $7e8000 ;lda $8000
lda $7f8000 ;lda $7f8000
lda #$7f : pha : pld ;you need to update assume now
assume db:$7f
lda $7f8000 ;lda $8000
rep #$20
lda #0 ;lda #$0000
assume mx:off,db:off,d:off }
Again, declaring d will do nothing at present... hence why I did not givean example of using it.
rep:
Repeat the following opcode x times. Does not work with macros!!
Example: rep 5 : nop ;write nop 5 times
incbin:
The incbin command will let you insert a binary file into the outputbinary image. Usage:
incbin filename.bin
fill / fillbyte:
fillbyte will allow you to select the byte used by fill, and fill can beused to fill x number of bytes with the fillbyte. Example:
fillbyte $ff : fill 16
This will write 16 $ff's to the file, and will increment the pc counterautomatically.
pad / padbyte:
This is identical to fill / fillbyte, except that pad takes an offsetinstead of a number of bytes to write. It will continue writing bytesuntil the offset is reached. Example:
org $8000 : padbyte $ff : pad $8010
This will write 16 $ff's to the file, and will increment the pc counterautomatically.
db / dw / dl / dd:
These are used to write binary data to the file.
Multiple arguments can be passed to these commands by means of a comma.Example:
db $01,$02,$03,$04
dw $0001,$0002,$0003,$0004
This command also accepts labels and text as arguments, example:
dw label : db "This is a test",$00
Text is written using the ascii table by default, however, thereis table support, see below.
table / cleartable:
cleartable will reset the ascii lookup-table to the ascii charactermap.
table filename will load in a file, which contains a character map,and format it using left-to-right mode.
table filename,ltr will load the file in left-to-right mode.
table filename,rtl will load the file in right-to-left mode.
left-to-right mode table example:
A=00
B=01
C=02
...
right-to-left mode table example:
00=A
01=B
02=C
...
Use whichever you prefer.
skip:
Skip ahead x bytes.
Example: skip 5 ;skips ahead 5 bytes in the file.
namespace:
namespaces can be used to declare labels with the same name, useful toprefix all labels with a certain extension, e.g. for a library.
Example:
main: ;this will become "lf1_main" when referenced with namespaces off
namespace off ;turn namespace functionality off
main: ;no conflicts with identical labels due to namespaces
jsr lf1_main ;call our library function
Somewhat useful, I suppose...
imports / exports:
You can import and export key labels/defines (not macros) between files,when you are assembling each file individually, and do not wish to buildthe entire project at once by using incsrc within your files.
import filename.exp ;Will import the labels/defines from filename.exp
In reality, import is a mirror if incsrc, export writes in xkas formatassembly code.
export.open filename.exp ;open filename.exp in write mode
export.open >filename.exp ;open filename.exp in write mode
export.open >>filename.exp ;open filename.exp in append mode
Modeled after perl, or something.
export.close ;close open export file, you cannot open more than oneat the same time.
export.label labelname ;export labelname
export.define definename ;export definename
You're probably wondering why I didn't use export label, andexport !define. Well, xkas would see the ! as a define, and then try toresolve it... and well, that wouldn't work out too well. Rather thanslow things down (e.g. put in effort), I just used .label and .define...Remember to take off the ! if you use these functions.
print:
print allows you to print text to the console window, useful fordebugging.
Example: "main() is at pc : ",pc
That will print "main() is at pc : xxxxxx", where x is the PC location.It accepts infinite arguments, comma separated, all should be strings,there are a few special commands, however. They are:
pc - print current pc location
bytes - print # of bytes written to the file, use to determine the sizeof routines, for size optimizations and so forth.
opcodes - print # of opcodes assembled, you could probably use this foroptimizing, too.
I wanted to add cycles, but unfortunately, it's too unreliable. The cyclecount will change depending on the p register, if branches are taken,if writing to the dp has the d with an even value, etc. Too many variablesto be viable, sorry.
You can use: reset bytes, or reset opcodes, to reset said counters.
incsrc:
Assemble included file. There is a 512-deep recursion limit. (e.g. incsrcinside an incsrc inside an incsrc...) - See the macro definitions forwhy this is here. It will assemble the included file immediately when hit.So if your incsrc is in the middle of the file, that's where it's going.All labels/sublabels/etc. are in the global-scope. So if you have label1:defined in file1.asm, then incsrc file2.asm, file2 will be able to uselabel1:, and subsequently not be able to redefine it.
org:
org is used to set the file position. It will update pc, as well. You canseek forward and backward into a file.
loadpc / savepc / warnpc:
These are used for spanning multiple files, and warning when you exceedcertain ranges. loadpc will load in a file, which is an index of pcaddresses. You can specify just loadpc filename.xpc, or you can specifyan index into this file, such as loadpc filename.xpc,2. If no index isspecified, 0 is assumed. It will seek to index * 4, and read in threebytes, (the file is padded by 4),then set pc to this value. savepc works the same way, except it will savethe pc value to specified index location. You can use these two inconjunction to assemble multiple files without having to space the codeapart due to fears of the first files code size growing too large andcutting into the second files code, then the second file eating the firstfiles code. warnpc just takes an address, it will compare the current pcto the address you specified, if the current address is >= the addressspecified, it will give you an error. Useful if you want your code tobe between x and y, and data to be between y and z. You can make sureyour code isn't so large that it exceeds y this way.
base:
base is used to set the pc position without affecting the filelocation.
Example: org $c00000 : base $7f8000 : ... : base off
This is useful for assembling code at $c00000, that you plan to executein RAM, at $7f80000. You can turn the base address translation off atany time, as seen above. Note that I've never actually seen code run inRAM before, and I don't even know if it would be possible to execute codein RAM or not, but the function is here either way...
You may have noticed that I lacked a lot of limitations, such as max. #of labels, defines, etc. That's because the only limitations truly arethe macro recursions, and incsrc recursions. I wrote classes in c++ thatwill automatically increase the RAM reserved for labels, defines, etc.when needed, so you are literally only limited by the amount of free ramyou have available. This will hopefully make xkas ideal for very largetranslations, or even writing games in their entirity.
xkas will open files in read/write mode, if they already exist, this isso that you can 'patch' existing translations. If the file does not exist,it will be created, this is for making your own games.Opcodes:
I pretty much followed the accepted standards for opcodes, this isn'tthe SPC-700, where there are 40 variations between programmers for nearlyevery single opcode. You shouldn't have any trouble here.