Intro to the bass (badass) 6502 assembler

Part 1

bass is a new 6502 assembler.It is designed to be powerful (like Kickassembler) but with a less complex syntax, and more unified, functional design.

Let’s start with a minimal C64 program, a 3 opcode routine to make a rainbow border:

    !org $c000
loop:
    lda $d012
    sta $d020
    jmp loop

You should be able to load this into a C64 emulator and start it with sys 49152.

Now let’s add a basic start snippet so we can just run the prg instead:

    !section "main", $801
    !byte $0b,$08,$01,$00,$9e,str(start)," BY SASQ",$00,$00,$00
start:
    lda #3
    sta $d020

What happens here? Well first we use the !section command instead of !org!. Sections are more powerful in that you can control layout of memory in different ways (but we wont get into that here).

Then, the !byte meta command accepts both numbers and strings, and for strings it inserts each character one by one.

The str() function takes a value and converts it to a string. So this is how we insert the correct sys address into the basic, regardless of where that start: label is placed.

Let’s add some more code;

    !section "main", $801
    !byte $0b,$08,$01,$00,$9e,str(start)," BY SASQ",$00,$00,$00
start:
    jsr $1000
.loop
    lda $d012
    cmp #100
    bne .loop
    lda #3
    sta $d020
    jsr $1003
    lda #0
    sta $d020
    jmp .loop

    !section "music", $1000
    !incbin "music.raw"

This is a short program that waits for a certain scan line, calls a music routine, and loops. The music is expected to reside in the file music.raw and must be placed at address $1000.

But what if music is a PRG with 2 bytes load address first. Or a sid file?

The functional approach of bass lets us manipulate not only numeric data, but also byte arrays. So to strip out the 2 first byte of a file and place it in memory we can do:

    data = load("music.bin")
    !fill data[2:]

Now we no longer use !incbin to include data, instead we load it into a byte array and store it in the symbol table, just like any symbol.

Then to actually place it in memory, we use the !fill meta command.

The [2:] part is called slicing. To extract a range from a byte array you use it like: data[start:end] (where start is inclusive and end exclusive). So in this case we skip the first 2 bytes (the load address) and keep the rest of the array.

So what about the load address, can we actually use it? Sure;

    loadAdr = word(data[0:2])
    !section "music", loadAdr
music:
    !fill data[2:]

As you may have figured out, the word() function decodes a little endian 16-bit word from an array. We could have also done this using just math operations; data[0] | (data[1]<<8).

Except what if the load address is not compatible with our layout? If we load another song at a different address it will probably break our code.

So instead of blindly trusting the load address we can verify it:

    musicStart = $1000
    !assert word(data[0:2]) == musicStart

Let’s put it all together:

    musicStart = $1000

    !section "main", $801
    !byte $0b, $08,$01,$00,$9e,str(start)," BY SASQ", $00,$00,$00
start:
    jsr musicStart
.loop
    lda $d012
    cmp #100
    bne .loop
    lda #3
    sta $d020
    jsr musicStart+3
    lda #0
    sta $d020
    jmp .loop

    data = load("music.prg")
    !assert  word(data[0:2]) == musicStart

    !section "music", musicStart
    !fill data[2:]