VERA Overview
VERA stands for Versatile Embedded Retro Adapter and consists of (copy from official documentation):
- Video generator featuring:
- Multiple output formats (VGA, NTSC Composite, NTSC S-Video, RGB video) at a fixed resolution of 640x480@60Hz
- Support for 2 layers, both supporting either tile or bitmap mode.
- Support for up to 128 sprites.
- Embedded video RAM of 128kB.
- Palette with 256 colors selected from a total range of 4096 colors.
- 16-channel Programmable Sound Generator with multiple waveforms (Pulse, Sawtooth, Triangle, Noise)
- High quality PCM audio playback from an 4kB FIFO buffer featuring up to 48kHz 16-bit stereo sound.
- SPI controller for SecureDigital storage.
In this short introduction post we will discuss access to VERA and write some very simple examples. We will explain the memory organization and test it with Basic and later translate it into Assembly program. At the end is breakdown with links to detailed descriptions of many of VERA features.
When programming in Basic we have few handy commands that make access to graphics simple and seamless. For example using VPOKE and VPEEK we can write and read to and from any address in video memory. With VLOAD we can load chunks of data directly into Video memory. So why would accessing it with machine code be any different?
To explain let’s look at the (simplified) memory map of
Commander X16. This illustration actually describes the most important concept in programming VERA, which is the fact that to do anything with it we can only access it through 32 bytes located in memory locations $9F20 - $9F3F:
The CPU is using 16 bit address bus and therefore can
directly access 64 Kbytes of memory. We call it CPU memory. Commander X16 actually has more memory which
CPU can access through “banking switching” however it can still only directly address 64K at any given time.
On the other hand we have 128K of Video memory (VRAM). That
memory is completely separated and isolated from CPU memory therefore CPU has
no direct access to it. The only way to manipulate the data in Video memory and
therefore the content of the display is through a fairly narrow window in the
CPU memory.
Currently 32 bytes are reserved for VERA communication and those 32 bytes contain 36 VERA registers. To be able to access 36 registers in only 32 addresses four of the registers have dual functionality that can be switched by changing the DCSEL bit in register 5.
Let’s look at those 36 bytes or registers that we can use:
Register | Address | Name | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|---|---|
0 | $9F20 | Address Lo | VRAM Memory address bits 0-7 | |||||||
1 | $9F21 | Address Mid | VRAM Memory address bits 8 - 15 | |||||||
2 | $9F22 | Address Hi | Address Increment | Dec | - | Bit 16 | ||||
3 | $9F23 | Data 0 | Data Register 0 | |||||||
4 | $9F24 | Data 1 | Data Register 1 | |||||||
5 | $9F25 | Control | Reset | - | DC | Address Select | ||||
6 | $9F26 | IEN | IRQ bit 8 | - | AFLOW | SPRCOL | LINE | VSYNC | ||
7 | $9F27 | ISR | Sprite Collissions | AFLOW | SPRCOL | LINE | VSYNC | |||
8 | $9F28 | IRQ Line | IRQ bits 0-7 | |||||||
9-0 | $9F29 | Video | Current Field | Sprite Enable | Layer 1 Enable | Layer 0 Enable | - | Chroma Disable | Output Mode | |
10-0 | $9F2A | Horizontal Scale | Active Display Horizonatl Scale | |||||||
11-0 | $9F2B | Vertical Scale | Active Display Vertical Scale | |||||||
12-0 | $9F2C | Border Color | Border Color | |||||||
9-1 | $9F29 | Horizontal Start | Active Display Horizontal Start Bits 2 - 9 | |||||||
10-1 | $9F2A | Horizontal Stop | Active Display Horizontal Stop Bits 2 - 9 | |||||||
11-1 | $9F2B | Vertical Start | Active Display Vertical Start Bits 1 - 8 | |||||||
12-1 | $9F2C | vertical Stop | Active Display Vertical Stop Bits 1 - 8 | |||||||
13 | $9F2D | Layer 0 - Config | Map Height | Map Width | T256C | Bitmap Mode | Color Depth | |||
14 | $9F2E | Layer 0 - Mapbase | Map Base Address Bits 9 - 16 | |||||||
15 | $9F2F | Layer 0 - Tilebase | Tile Base Address Bits 11 - 16 | Height | Width | |||||
16 | $9F30 | Layer 0 - H Scroll | Horizontal Scroll Bits 0 - 7 | |||||||
17 | $9F31 | Layer 0 H Scroll | - | Horizontal Scroll Bits 8 - 11 | ||||||
18 | $9F32 | Layer 0 - V Scroll | Vertical Scroll Bits 0 - 7 | |||||||
19 | $9F33 | Layer 0 - V Scroll | - | Vertical Scroll Bits 8 - 11 | ||||||
20 | $9F34 | Layer 1 - Config | Map Height | Map Width | T256C | Bitmap Mode | Color Depth | |||
21 | $9F35 | Layer 1 - Mapbase | Map Base Address Bits 9 - 16 | |||||||
22 | $9F36 | Layer 1 - Tilebase | Tile Base Address Bits 11 - 16 | Height | Width | |||||
23 | $9F37 | Layer 1 - H Scroll | Horizontal Scroll Bits 0 - 7 | |||||||
24 | $9F38 | Layer 1 H Scroll | - | Horizontal Scroll Bits 8 - 11 | ||||||
25 | $9F39 | Layer 1 - V Scroll | Vertical Scroll Bits 0 - 7 | |||||||
26 | $9F3A | Layer 1 - V Scroll | - | Vertical Scroll Bits 8 - 11 | ||||||
27 | $9F3B | Audio Control | FIFO Full/Reset | - | 16 Bit | Stereo | PCM Volume | |||
28 | $9F3C | Audio Rate | PCM Sample Rate | |||||||
29 | $9F3D | Audio Data | Audio FIFO Write Only Data Register | |||||||
30 | $9F3E | SPI Data | SPI (Serial Peripheral Interface) Data Register | |||||||
31 | $9F3F | SPI Control | Busy | - | Slow Clock | Select |
First three registers are used to set address into VRAM. We
have 17 bits at our disposal (0 – 16). That gives us a range from $00000 to
$1FFFF or 128 Kbytes. Of course it is no coincidence that VPOKE command has the
same range.
Not all VRAM memory is created equal. As already marked in the picture above there are three sections at the end of the memory space with special role:
Address Range | Description |
---|---|
$00000 - $1F9BF | Video RAM for Maps and Tiles |
$1F9C0 - $1F9FF | Programmable Sound Generator Registers (64 bytes) |
$1FA00 - $1FBFF | Color Palette |
$1FC00 - $1FFFF | Sprite Attributes (1024 bytes, 128 sprites - 8 bytes each) |
We see that video memory is between $00000 and $1FFFF and
that we have dedicated space for Palette, sprite registers and Programmable Music Generator from $1F9C0 on.
That means that to be able to manipulate VRAM we need 17 bits, which is exactly what we have in registers 0-2. Register 0 contains bits 0-7, Register1 contains bits 8-15 and Register 2 contains bit 16 and some other settings we look at later.
After setting the address we can use Data registers to write
or read from Video Memory. We can use Register 3 ($9F23) as Data Register 0 and
Register 4 ($9F24) as Data Register 1. We can choose which one we prefer by
setting Bit 0 in Register 5 ($9F25) with value 0 selecting Data Register 0 and
value 1 selecting Register 1. Note that Bit 7 in this register resets the VERA
settings but I recommend not to use it from BASIC.
Next we have to explain probably the most important concept
of using VERA and accessing Video Memory. Register 2 ($9F22) contains highest bit of address but also Increment and bit to switch between Increment and Decrement.
Increment is setting that defines automatic increment/decrement of
VERA address in registers 0-2 after every read or write. Since we have 4 bits
we have 16 different values but what does that actually mean.
The best way to describe how increment works let’s imagine
following example and test it. As you remember default video mode of Commander
X16 after start (or reset) is 80 column text mode. The displayed text starts in
Video memory $00000 which we can verify by VPOKE to that address. The memory is
organized in a way that first byte contains a character code. The second
character contains a color attribute, the third second character displayed,
fourth color attribute for that character and so on. So if we want to write
only characters we have to write to every other address starting with $00000,
then $00002 followed by $00004 and so on. Obviously the address has to be
incremented by two for each write to the screen. And that is exactly what the Increment
setting does automatically.
So let’s test this theory and write a simple BASIC program
to write to the video memory without using VPOKE but just POKE to the VERA
external registers and see if we can recreate above scenario.
First step is to decide which data register we will use.
Let’s just use Register 0. We do that with
POKE $9F25,0
Next we have to set screen address to $00000 and set
Increment to 2 so we can write to every other address. That means we set Low
and Mid byte to 0 and Hi Byte to $20 Hex.
POKE $9F20,0:POKE$9F21:POKE $9F22,$20
If we write some value to Data Register 0 ($9F23) it should
display the character in the top left corner and increment the address by two.
To make it more interesting let’s put all this into a program:
The Increment values are not simply represented by binary
value from 0 – 15 but they have the following values to increase the reach of
the Increment:
Increment Setting | Actual Increment Amount |
---|---|
0 or $0 | 0 |
1 or $1 | 1 |
2 or $2 | 2 |
3 or $3 | 4 |
4 or $4 | 8 |
5 or $5 | 16 |
6 or $6 | 32 |
7 or $7 | 64 |
8 or $8 | 128 |
9 or $9 | 256 |
10 or $A | 512 |
11 or $B | 40 |
12 or $C | 80 |
13 or $D | 160 |
14 or $E | 320 |
15 or $F | 640 |
Knowing this we can modify the above program and try to write vertically. If we recall in the default mode each line takes 256 bytes of memory and only part is visible. 80 out of 128 characters and attributes. To write vertically we therefore have to increment memory location pointer by 256 after every write. Based on a table above we have to use value 9 for increment and now our code looks like this:
No real surprises there. We could use what we learned in
Basic and in special cases we might be able to speed up the code a little but
not in significant way, especially not in real life scenarios.
Everything we did above we can also do in reverse by using the Decrement bit 3 in Register 2. If it is set the Increment works as Decrement for the same value.
Assembly
The logical next step is to use the above learned in assembly. Since POKE command is closest we get to hardware from Basic the translation is very simple. Let’s look at below Assembly code and walk through it. Commander X16 comes with Monitor built in which is very convenient. It allows us write simple assembly programs right there on the system without complicated setup and external tools or installing additional tools on a system itself.
First three lines of code simply write 0 into registers at $9F25,
$9F20 and $9F21. Mnemonic STZ (STore Zero) was added to 65C02 so if you try to
use it on Commodore 64 or any other 6502 computer it will not be recognized.
Lines 4 and 5 first stores value $20 hex into Accumulator,
which is the main register in the CPU and store it into $9F22 essentially
setting the Memory address to $00000 and setting increment to 2.
We will use Accumulator for two things, to store the value
of the next character to be written to screen and as a counter to write exactly
80 characters. Since we will start with A, which has screen code 1 (just like
in the Basic program) we initialize it by setting it to 1 in Line 6.
Lines 7-10 are our loop. We write 1 to first location in
video memory which in first iteration is $00000. Then in line 8 we increment
Accumulator by one. In line 9 we compare it with value $51 hex, which is 81 in
decimal. If we would compare with 80 the loop would exit too soon (we could use
BPL but let’s keep it simple for now). Then in line we check the result of
comparison and if the comparison was not equal we jump back to the beginning of
the loop to line 7 (address $4010).
During second iteration therefore we write 2 into address
$00002, then 3 into $00004 and so on until we write all 80 characters.
In line 11 we Return from Subroutine and go back to basic or
wherever we called this function from.
Like every piece of code this routine could be written in
many different ways but I feel this is the clearest way to do it.
Before we wrap up let’s quickly look at how to write it and
test it. At the end of this part our screen should look similar to this:
We have to start Monitor by writing command MON
Our cursor will be waiting after the prompt in the form of
dot.
Without any additional preparation we can start writing code
by telling Monitor we will write assembly and at what address by using command
A immediately followed by mnemonic of the first assembly command and parameter
(if any). In our case we just type:
A4000 STZ $9F25
After that monitor will expand the line and show the actual bytes stored in memory at locations $4000 - $4002 like we see on above screenshot and wait for next command in next line with new address $4003. So we just keep typing the whole program and when finished on memory location $4019 we simply press enter and we will be at dot prompt again. By typing X we can exit monitor.
All we have to do now is call the program by calling it from
Basic by:
SYS $4000
And we should see 80 characters magically appear in the first line of the screen.
Have fun experimenting with and changing your first assembly
program.
Comments
Post a Comment