Direct VERA Access
*** Mostly obsolete, valid for Emulator versions before R.37 - Check the VERA Overview instead ***
In this short post we will discuss access to graphics using
direct access. We will explain the memory organization and test it with Basic and later translate it into Assembly program
When programing 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.
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 address 64
at any given time.
On the other hand we 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 16 bytes are reserved for VERA communication but
only 8 bytes are documented for our use and are located on memory locations
$9F20 - $9F27. That is fairly narrow pipe that we have to feed through all the
data for the graphics.
Let’s look at those 8 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 | Address (Bits 0 - 7) | |||||||
1 | $9F21 | Address MI | Address (Bits 8 - 15) | |||||||
2 | $9F22 | Address HI | Increment | Address (Bits 16 - 19) | ||||||
3 | $9F23 | Data 0 | Data Register 0 | |||||||
4 | $9F24 | Data 1 | Data Register 1 | |||||||
5 | $9F25 | VERA Control | Reset | Select | ||||||
6 | $9F26 | Interrupt Enable | UART | SPRCOL | LINE | VSYNC | ||||
7 | $9F27 | Interrup Status | UART | SPRCOL | LINE | VSYNC |
First three registers are used to set address into VRAM. We
have 20 bits at our disposal (0 – 19). That gives us a range from $00000 to
$FFFFF or 1M bytes. Of course it is no coincidence that VPOKE command has the
same range. Since we said that we only have 128K of VRAM clearly we can’t use
all the addresses but let’s remember important addresses from Sprites in Basic
I article:
Address Range | Description |
---|---|
$00000 - $1FFFF | Video RAM |
$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 |
$F7000 - $F7001 | SPI |
$F8000 - $F8003 | UART |
We see that video memory is between $00000 and $1FFFF and that we have Display Composer, Palette, sprite registers etc. from $F0000 on. That means that if we want to manipulate VRAM we can use following binary values of 00 or 01 in the High byte of address and %1111 or $F in hex for accessing internal VERA registers.
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 four highest bits of address but also Increment.
Increment is setting that defines automatic increment 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 the character code. The second
character contains the color attributes, 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.
With 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 POKE $9F25,$00:REM DCSEL=0 | |
20 POKE $9F22,$21:REM ADDR-H=$01 INCREMENT=2 | |
30 POKE $9F21,$B0:REM ADDR-M=$B0 | |
40 POKE $9F20,$00:REM ADDR-L=$00 | |
50 FOR N=1 TO 80:POKE $9F23,N:NEXT N |
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 | 1024 |
12 or $C | 2048 |
13 or $D | 4096 |
14 or $E | 8192 |
15 or $F | 16384 |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 POKE $9F25,$00:REM DCSEL=0 | |
20 POKE $9F22,$91:REM ADDR-H=$01 INCREMENT=256 | |
30 POKE $9F21,$B0:REM ADDR-M=$B0 | |
40 POKE $9F20,$00:REM ADDR-L=$00 | |
50 FOR N=1 TO 60:POKE $9F23,N:NEXT N |
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.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
STZ $9F25 | |
STZ $9F20 | |
LDA #$B0 | |
STA $9F21 | |
LDA #$21 | |
STA $9F22 | |
LDA #$01 | |
STA $9F23 | |
INC | |
CMP #$51 | |
BNE $4010 | |
RTS |
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