Intro to the bass (badass) 6502 assembler

Part 4 - Unit Testing

(NOTE: This part still needs work)

To demonstrate unit testing I will leave the wonderful world of C64 for a while and show bits of code targeting the Commander X16 computer.

But let’s begin with the basic structure of a unit test:

!test
my_code:
    lda #$ef
    sta $1
    lda #$70
    adc $1
    rts

!assert tests.my_test.A == (0x70 + 0xef) & 0xff

Tests works like this:

Any !test clause like the one above will cause that program location to be saved as a test.

Each test will be executed in the internal bass 6502 emulator, and the state of the emulator will be saved to a set of symbols for the specific test.

!assert commands can then be used to verify the result, both by checking registers and reading RAM.

Now let’s dive into the example targeting the X16 Commander:

    png = load_png("../data/face.png")

copy_indexes:

    ldy #80
    ldx #0
    clc
.loop
    lda indexes,x
    sta DATA0
    dey
    bne +

    ldy #128-80
    lda #0
.x  sta DATA0
    dey
    bne .x
    ldy #80
$
    inx
    bne .loop
    inc .loop+2
    lda .loop+2
    cmp  #((indexes_end+128)>>8)
    bne .loop

    rts

indexes:
    !fill png.indexes
indexes_end:

; ---------------------------------------------------------------------------

!test index_copy {
    SetVReg(0)
    SetVAdr(tileMem | INC_1)
    jsr copy_indexes
}
; Check first lines of screen tiles
vram = get_vram()
!rept 10 {
    !assert vram[tileMem+i*128:tileMem+i*128+80] = png.indexes[i*80:(i+1)*80])
}

So a lot of things happening here.

First we load a png image using the png_load() function.The result of this call is a structure of several fields, which includes the actual bitmap and colors, but also a tiled version of the image, where the bitmap has been split up into 8x8 characters, and each character is assigned a tile index.

Tiled data is used in a lot of older graphics hardware, and the graphics hardware of the X16 (called Vera) also has a tiled graphics mode which we are using.

After that we have the actual "function under test".Its job is to copy the tile indexes to the VRAM of the Vera.But the X16 does not have VRAM directly mapped, so you need to set the offset in VRAM you want to write to, and then write the values to the special Vera DATA0 register.

Then we have the actual test invocation, and the asserts.

But what is get_vram() ?And how can the emulator deal with the specific way of copying data to VRAM unique to the X16/Vera hardware:

It works because we load this LUA script into the Assembler:

-- Vera state
vram = { 0 }
vregs = { 0 }
vsel = 1
vadr = { 0, 0 }
vinc = { 0, 0 }

increments = {
    0, 0, 1, -1, 2, -2, 4, -4, 8, -8, 16, -16, 32, -32,
    64, -64, 128, -128, 256, -256, 512, -512,
    40, -40, 80, -80, 160, -160, 320, -320, 640, -640,
}


-- The Vera Interface are mapped to registers between $9f20-$9f40

map_bank_read(0x9f, 1, function(adr)
    offset = adr & 0xff
    -- print("Read", offset)
    if offset >= 0x20 and offset < 0x40 then
        if offset == 0x20 then
            return vadr[vsel] & 0xff
        elseif offset == 0x21 then
            return (vadr[vsel] >> 8) & 0xff
        elseif offset == 0x22 then
            return (vadr[vsel] >> 16) | (vinc[vsel]<<3)
        elseif offset == 0x23 then
            res = vram[vadr[1]+1]
            vadr[1] = vadr[1] + increments[vinc[1]+1]
            return res
        elseif offset == 0x24 then
            res = vram[vadr[2]+1]
            vadr[2] = vadr[2] + increments[vinc[2]+1]
            return res
        end
        res =  vregs[offset-0x20+1]
        return res
    else
        return mem_read(adr)
    end
end)

map_bank_write(0x9f, 1, function(adr, val)
    offset = adr & 0xff
    if offset >= 0x20 and offset < 0x40 then
        if offset == 0x20 then
            vadr[vsel] = (vadr[vsel] & 0x1ff00) | val
        elseif offset == 0x21 then
            vadr[vsel] = (vadr[vsel] & 0x100ff) | (val<<8)
        elseif offset == 0x22 then
            vadr[vsel] = (vadr[vsel] & 0xffff) | ((val&1)<<16)
            vinc[vsel] = val>>3
        elseif offset == 0x23 then
            -- print(string.format("Vram write %x to %x", val, vadr[1]))
            vram[vadr[1]+1] = val
            vadr[1] = vadr[1] + increments[vinc[1]+1]
        elseif offset == 0x24 then
            -- print(string.format("Vram write %x to %x", val, vadr[2]))
            vram[vadr[2]+1] = val
            vadr[2] = vadr[2] + increments[vinc[2]+1]
        end
        -- print(string.format("Write %x to %x", val, offset))
        vregs[offset-0x20+1] = val
    else
        mem_write(adr, val)
    end
end)

function get_vram()
    return vram
end

So this is a lot of code.If you know and/or are interested in how the Graphics access works in the X16, you can probably tell that this implements parts of the Vera access, and in fact you can find code similar to this in the official X16 emulator.

It is an example on how you can extend the internal emulator, by intercepting reads and write to certain memory areas.This way you can actually emulate those parts of your target system that you need for testing.

And at the end of this LUA code you also see the get_vram() function used in the asserts above. This function returns the data written to VRAM, so we can verify that it contains what we want.