xkas v0.06 documentation
Author: byuu
Release Date: 08/01/2004

   xkas (acronym for: cross-knight assembler) is a cross-assembler for the WDC G65816 processor, specifically tailored for use with SFC/SNES programming and translations.

  • Supports all 256 opcodes
  • Supports labels / sublabels / +/- labels
  • Supports math
  • Supports ascii-tables
  • Supports defines
  • Supports macros

    Brackets, { and }, may be used anywhere within the code to help organize it structurally, they are converted to whitespace and then removed by the assembler.

    Labels / Sublabels / +/- Labels:
    A label is used to represent a position in code, and allows one to code without having to constantly update branches and jumps/calls. A label should be able to be used in any opcode, but was specifically added to be 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 its address space only, and can contain the same characters as a label, but must start with a period. A sublabel must not end with a : or (). Here's an example:

      bra .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 is started immediately after a label is declared automatically. A +/- label can be up to 3 levels deep, e.g. +, ++, +++, -, --, ---. They overwrite their pc offsets immediately after being redefined. Useful for very short loops, when even something like .loop would become redundant in a long routine.
    Lastly, there are labels specifically for macros. They are identical to real labels, and begin with a ?
    Example: ?label:
    Do not use these outside of macros!!

    xkas uses a pretty powerful define system, which does quite a bit more than 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, it can 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"
    You can end defines with () if you like, it helps to clarify that the define is used as a function, instead of as an argument. But is completely optional. Also, if you need to use spaces within your define, then you must use quotes around the right hand side of the operand, as shown above with !phr.

    Macros can be nested (macros with macros) up to 512 levels deep, the limitation is there only to prevent macro a calling macro b, and vice versa, resulting in a dead-lock of the assembler. incsrc, below, has the same 512 level deep limit, for exactly the same reason. Syntax of a macro is:

    macro name(arg1, arg2, ...)
    lda <arg1>
    sta.l $<arg2>
    bra ?loop

    Things to note: The first and last line from the above example must be on their own lines, you cannot use the : command to concatenate these commands. You can use as many arguments as you like. Yes, infinite arguments, if you like. Or none at all. To use the argument within the macro, simply use <> around the argument name. To use the macro within 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, or lda #$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 I already use these for defines, shifts, and macro arguments. Sorry if you don'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 requires a space before and after usage, to differentiate it from the : used to end labels.
    Example: lda #$00 : sta $00
    This is useful for making each line define an action, rather than just an opcode. Very useful when combined with { } to indicate a structural flow of assembly code, rather than a straight descending list. But you're free 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 specify asl #4, which will print the equivalent of asl : asl : asl : asl to the file, the same goes for the other opcodes.

    Math is supported in all opcodes, and all functions, however, it has one major variation from traditional math. It does not resolve math in order if symbols, it instead resolves from left to right, and also does 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 faster to implement left-to-right math, and for an assembler, I haven't really come across anything that could not be represented in left-to-right math. To use left-to-right math, just reorder your multiplication and division to the start, and use addition and subtraction at the end. The math system supports the following commands:
    + : Addition
    - : Subtraction
    * : Multiplication
    / : Division
    << : Left-shift ( x< >> : Right-shift ( x >> y formula: x = x / 2^y )
    & : Bitwise AND
    | : Bitwise OR
    ^ : Bitwise XOR (Note: Not exponentials)
    ~ : Bitwise NOT

    Math also works with labels. Example:
    mov a,label2-label1+2

    Additional Commands:

    Use hirom addressing.

    Use lorom addressing.

    Assume the rom has a header, the default assumes there is no header. I do this because roms without a header are far easier to work with in hex editors, and since 95% of work is done through emulators, why use a header that is used for copiers only?

    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 not support actually using d, yet. So you won't get any optimizations by using it. I did say this was experimental, right? I hope to add d, one day...
    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 means 8 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 said mode.
    mx will automatically update, when enabled, when it sees rep/sep commands. It will not be able to update between functions, when it sees plp, etc. The same holds true for db, and d. You must update these manually!! It would be ideal to declare the assumes at the start of a function, and turn them off at the end. Here's an example:

    main() assume mx:%10,db:$7e,d:$0000 {
      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 give an example of using it.

    Repeat the following opcode x times. Does not work with macros!!
    Example: rep 5 : nop ;write nop 5 times

    The incbin command will let you insert a binary file into the output binary image. Usage:
    incbin filename.bin

    fill / fillbyte:
    fillbyte will allow you to select the byte used by fill, and fill can be used 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 counter automatically.

    pad / padbyte:
    This is identical to fill / fillbyte, except that pad takes an offset instead of a number of bytes to write. It will continue writing bytes until 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 counter automatically.

    db / dw / dl / dd:
    These are used to write binary data to the file.
  • db - 8-bit byte write mode
  • dw - 16-bit word write mode
  • dl - 24-bit long write mode
  • dd - 32-bit dword write mode
    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, there is table support, see below.

    table / cleartable:
    cleartable will reset the ascii lookup-table to the ascii character map.
    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:
    right-to-left mode table example:
    Use whichever you prefer.

    Skip ahead x bytes.
    Example: skip 5 ;skips ahead 5 bytes in the file.

    namespaces can be used to declare labels with the same name, useful to prefix all labels with a certain extension, e.g. for a library.

    namespace "lf1_" ;set namespace to "lf1_"
    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 build the 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 format assembly 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 one at the same time.
    export.label labelname ;export labelname
    export.define definename ;export definename
    You're probably wondering why I didn't use export label, and export !define. Well, xkas would see the ! as a define, and then try to resolve it... and well, that wouldn't work out too well. Rather than slow things down (e.g. put in effort), I just used .label and .define... Remember to take off the ! if you use these functions.

    print allows you to print text to the console window, useful for debugging.
    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 size of routines, for size optimizations and so forth.
    opcodes - print # of opcodes assembled, you could probably use this for optimizing, too.
    I wanted to add cycles, but unfortunately, it's too unreliable. The cycle count 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 variables to be viable, sorry.
    You can use: reset bytes, or reset opcodes, to reset said counters.

    Assemble included file. There is a 512-deep recursion limit. (e.g. incsrc inside an incsrc inside an incsrc...) - See the macro definitions for why 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 use label1:, and subsequently not be able to redefine it.

    org is used to set the file position. It will update pc, as well. You can seek forward and backward into a file.

    loadpc / savepc / warnpc:
    These are used for spanning multiple files, and warning when you exceed certain ranges. loadpc will load in a file, which is an index of pc addresses. You can specify just loadpc filename.xpc, or you can specify an index into this file, such as loadpc filename.xpc,2. If no index is specified, 0 is assumed. It will seek to index * 4, and read in three bytes, (the file is padded by 4), then set pc to this value. savepc works the same way, except it will save the pc value to specified index location. You can use these two in conjunction to assemble multiple files without having to space the code apart due to fears of the first files code size growing too large and cutting into the second files code, then the second file eating the first files code. warnpc just takes an address, it will compare the current pc to the address you specified, if the current address is >= the address specified, it will give you an error. Useful if you want your code to be between x and y, and data to be between y and z. You can make sure your code isn't so large that it exceeds y this way.

    base is used to set the pc position without affecting the file location.
    Example: org $c00000 : base $7f8000 : ... : base off
    This is useful for assembling code at $c00000, that you plan to execute in RAM, at $7f80000. You can turn the base address translation off at any time, as seen above. Note that I've never actually seen code run in RAM before, and I don't even know if it would be possible to execute code in RAM or not, but the function is here either way...

    Additional Notes:
    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 are the macro recursions, and incsrc recursions. I wrote classes in c++ that will automatically increase the RAM reserved for labels, defines, etc. when needed, so you are literally only limited by the amount of free ram you have available. This will hopefully make xkas ideal for very large translations, or even writing games in their entirity.
    xkas will open files in read/write mode, if they already exist, this is so that you can 'patch' existing translations. If the file does not exist, it will be created, this is for making your own games.

    I pretty much followed the accepted standards for opcodes, this isn't the SPC-700, where there are 40 variations between programmers for nearly every single opcode. You shouldn't have any trouble here.