-
Notifications
You must be signed in to change notification settings - Fork 60
(VERA 0.8) Getting started
This page refers to the VERA 0.8 and needs to be updated for emulator versions r37 and later.
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.
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.
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:
10 POKE$9F20,0
20 POKE$9F21,0
30 POKE$9F22,0
40 POKE$9F23,1
This is identical to doing VPOKE0,0,1
.
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:
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)
.
stz $9F20
stz $9F21
stz $9F22
lda $9F23
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:
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
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:
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
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
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.