Skip to content

(VERA 0.8) Getting started

Stephen Horn edited this page Mar 22, 2020 · 1 revision

This page refers to the VERA 0.8 and needs to be updated for emulator versions r37 and later.

Getting Started

The VERA's configuration and its VRAM are not mapped directly into the memory address space of the Commander X16. Instead, only a small handful of ports are mapped, through which we communicate with the VERA and can transfer data. These memory addresses are:

Address Name Description
$9F20 VERA_ADDR_LO Contains the least-significant byte of VRAM we want to access
$9F21 VERA_ADDR_MID Contains the middle byte of VRAM we want to access
$9F22 VERA_ADDR_HI Contains the most-significant byte of VRAM we want to access, and sets the memory access increment
$9F23 VERA_DATA0 Data port 0
$9F24 VERA_DATA1 Data port 1
$9F25 VERA_CTRL Selects which VERA_DATA port to configure on $9F20-$9F22, can also reset the VERA to its defaults
$9F26 VERA_IEN Enables various interrupts from the VERA
$9F27 VERA_ISR Shows which interrupts have fired, and can clear interrupt flags

This page will focus on $9F20-$9F23, the basics of communicating with the VERA.

For experienced 8-bit and 16-bit programmers accustomed to thinking about 24-bit addresses, it's tempting to think of VERA_ADDR_LO, VERA_ADDR_MID, and VERA_ADDR_HI as the low, "high", and "bank" bytes of an address, and many assemblers would follow this nomenclature as well. Technically, the VERA has no concept of "banks", it just has a 20-bit wide VRAM address space.

VRAM

The VERA has 128KiB of VRAM, starting at address $00000, running through $1FFFF. Also, similar to how the interface for communicating with the VERA is mapped to the X16's memory space, a number of configuration registers have been mapped to the VRAM address space:

Start address End address Description
$F0000 $F001F Display composer registers
$F1000 $F11FF Palette
$F2000 $F200F Layer 0 registers
$F3000 $F300F Layer 1 registers
$F4000 $F400F Sprite registers
$F5000 $F53FF Sprite attributes
$F6000 $F6xxx Audio (TBD)
$F7000 $F7001 SPI
$F8000 $F8003 UART

We'll leave the details of these registers for other pages.

Writing and reading with the VERA

At system startup, the kernal maps a layer to start at VRAM address $00000, and leaves the VERA configured to use VERA_DATA0 for reading and writing. $00000 corresponds to the top-left corner of the screen, so we can write a character to it through VERA_DATA0 with the following examples:

BASIC

10 POKE$9F20,0
20 POKE$9F21,0
30 POKE$9F22,0
40 POKE$9F23,1

This is identical to doing VPOKE0,0,1.

6502

stz $9F20
stz $9F21
stz $9F22
lda #1
sta $9F23

After setting the low, mid, and high bytes of our desired VRAM address to $00, this places a purple "A" in the top-left corner of the screen by writing $01 to VERA_DATA0.

We can read from the VERA exactly the same way as we wrote to it, but reading from VERA_DATA0 instead of writing to it:

BASIC

10 POKE$9F20,0
20 POKE$9F21,0
30 POKE$9F22,0
40 PRINT PEEK($9F23)

This is identical to doing PRINT VPEEK(0,0).

6502

stz $9F20
stz $9F21
stz $9F22
lda $9F23

Reading or writing a sequence: The memory access increment

The upper 4 bits of register $9F22 contains the "memory access increment" value, which causes VERA_DATA0 to automatically increment its address after each read or write.

Increment value Increment amount
0 0
1 1
2 2
3 4
4 8
5 16
6 32
7 64
8 128
9 256
10 512
11 1024
12 2048
13 4096
14 8192
15 16384

This can be useful when we want to quickly read or write a series of bytes to the VERA without needing to setup the VRAM address in-between each byte. For instance, if we wanted to write "HELLO WORLD" to the console after startup, we might do the following:

BASIC

10 REM "HELLO WORLD"
20 DATA 8,5,12,12,15,$60,23,15,18,12,4
30 POKE$9F20,0 : POKE$9F21,8 : POKE$9F22,$20
40 REM PRINT HELLO WORLD TO SCREEN
50 FOR X=1 TO 11 : READ A : POKE$9F23,A : NEXT

6502

hello: 
    !byte $08, $05, $0C, $0C, $0F, $60, $17, $0F, $12, $0C, $04 ; "HELLO WORLD"

print_hello:
    stz $9F20
    stz $9F21
    lda #$20
    sta $9F22
    ldy #0
loop:
    lda hello,y
    sta $9F23
    iny
    cpy #11
    bne loop
    rts

This chooses a memory access increment value of "2", because the VERA's default settings use a character size that is 2 bytes wide: The first byte is the character, the second is coloring information. By skipping every other byte this way, we can write text without changing the color information.

If we'd wanted to change the color information as well, we could have done so easily with a different increment value:

BASIC

10 REM "HELLO WORLD"
20 DATA 8,5,12,12,15,$60,23,15,18,12,4
30 POKE$9F20,0 : POKE$9F21,8 : POKE$9F22,$10
40 REM PRINT HELLO WORLD TO SCREEN
50 FOR X=1 TO 11 : READ A : POKE$9F23,A : POKE$9F23,33 : NEXT

6502

hello: 
    !byte $08, $05, $0C, $0C, $0F, $60, $17, $0F, $12, $0C, $04 ; "HELLO WORLD"

print_hello:
    stz $9F20
    stz $9F21
    lda #$10 ; Increment of 1 instead
    sta $9F22
    ldy #0
loop:
    lda hello,y
    sta $9F23
    lda #33
    sta $9F23
    iny
    cpy #11
    bne loop
    rts

Why use POKE instead of VPOKE?

In truth, when writing BASIC programs, there is relatively little reason to use POKE over VPOKE when communicating with the VERA. The overhead from using BASIC in either case vastly exceeds almost any performance gains you could expect from POKE.

Assembly programmers don't have VPOKE, however, and so must rely on the memory-mapped registers to interact with the VERA. But when you consider any VPOKE macro they might write will expand to 3 extra pairs of lda/sta instructions for each byte written, setting aside any additional math that may be required to determine the VRAM address, it quickly becomes obvious just how much faster it is to setup VERA_DATA0's automatic address increment once and then quickly stream a block of data to it.