REU Programming
REU Programming
by Robin Harbron.
Note: This article originally appeared in Loadstar Letter #46, published in 1997. Thanks to the folks at Loadstar for granting permission for me to post it on my website.
Want to know how to make use of a Ram Expansion Unit in your own programs? The following BASIC and assembly programs are examples of how to detect an REU, determine its size, and then store, retrieve or swap memory with it.
The REU is accessed through IO2, which is the memory locations from $DF00 to $DFFF. The REU actually has 7 registers, with some registers being multiple bytes, so all your PEEKing and POKEing is done in the addresses $DF00 to $DF0A (57088 to 57098).
How to detect an REU? If the REU is present, addresses $DF02 through $DF05 retain what is stored in them. I don’t know of any other device with that property, so the following routines exploit this. In the BASIC version (which only uses one variable, just to be unobtrusive), x will equal 0 if there is no REU, and x=1 if there is one present.
10 rem detect 20 forx=2to5:poke57088+x,x:nextx 30 forx=5to2step-1 40 ifpeek(57088+x)<>xthenx=1 50 nextx 60 ifx=0thenprint"no reu"
In the assembly version (sys 16384 to start it from BASIC), the accumulator will be 0 if there is no REU, and 1 if there is one. PEEK(780) will tell you the contents of the accumulator if you call this routine from BASIC.
*= $4000
; detect REU
ldx #2
loop1 txa
sta $df00,x
inx
cpx #6
bne loop1
ldx #2
loop2 txa
cmp $df00,x
bne noreu
inx
cpx #6
bne loop2
lda #1
rts
noreu lda #0
rts
Now, how to tell how big the REU is? This is my solution, although there seems to be no absolute best way. This routine takes a while to run, in the BASIC version (of course the assembly version takes no noticeable time at all). Simply speaking, the routine writes the bank number into the first byte of every bank (all 256 possible banks, which would be a 16 meg REU!). Banks that are not available seem to be shadows of the existing banks, so what you write into a shadow bank ends up overwriting what we had put in a previous bank. Then we just go through the banks again, and see how many consecutive, increasing values we can find. This number is the number of available banks. This routine is also non-destructive. It reads and stores the original contents of the REU, and then puts them back when it’s done. The routine requires a 257 byte buffer for this, which the
variable s/label temp points to. After running the BASIC version, the variable A contains the number of banks available. For example, A=8 after running the program on my 512K REU. The ML version returns the number of banks in the accumulator.
10 rem size 15 s=49152 20 poke57090,0:poke57091,192 30 poke57092,0:poke57093,0 40 poke57095,1:poke57096,0 50 poke57098,0 60 forb=0to255:poke57094,b 63 pokes,b:poke57089,178 65 pokes+1+b,peek(s):nextb 70 b=0:o=0 80 poke57094,b:poke57089,177 90 n=peek(s) 100 ifn>otheno=n:b=b+1:goto80 110 a=b 120 forb=255to0step-1:poke57094,b 130 pokes,peek(s+1+b):poke57089,176 140 nextb 150 printa
*= $4000
temp = $c000
;detect reu size
lda #0
sta $df04
sta $df05
sta $df08
sta $df0a
lda #1
sta $df07
lda #<temp
sta $df02
lda #>temp
sta $df03
ldx #0
loop1 stx $df06
stx temp
lda #178
sta $df01
lda temp
sta temp+1,x
inx
bne loop1
ldy #177
ldx #0
stx old
loop2 stx $df06
sty $df01
lda temp
cmp old
bcc next
sta old
inx
bne loop2
next stx size
ldy #176
ldx #255
loop3 stx $df06
lda temp+1,x
sta temp
sty $df01
dex
cpx #255
bne loop3
lda size
rts
old .byte 0
size .byte 0
Now we get to the primary function of the REU: Storing and retrieving data. The following routine is a model for you to use. It either stashes or retrieves a screen to/from the REU. Here’s the breakdown of the registers:
$DF02 & $DF03 (57090 & 57091) point to the base address in the computer’s memory that we want to deal with. It’s stored in standard lo/hi format. $400 is the address we stash in here to deal with the screen.
$DF04-$DF06 (57092-57094) point to the base address in the REU’s memory that we want to use. It’s stored in lo/hi/bank format. The bank number can be thought of as the “even higher” byte. We’ll just store the info in location $0, bank 0.
$DF07-$DF08 (57095-57096) determine the length in bytes of the transfer. It’s stored in lo/hi format as well. Storing a zero in this register causes the REU to transfer 64K at once, which is the limit for one move.
$DF0A allows you to fix either the C64 or REU memory address, to allow you to either write or read one particular memory location many times. If bit 7 is set, the C64 address is fixed, and if bit 6 is set, the REU address is fixed. This has many uses: You could fix the address in the REU (perhaps storing a 0 or 255 there) and then set the length to 8000 bytes. Then you could fill an entire bitmap screen with that byte in just 8000 cycles, by transferring from the REU to the C64! This is at least 4 times faster than doing it with the processor. Or how about sampling a particular location in memory? It’s been used to examine the random number generator in the SID chip.
$DF01 (57089) actually executes the move. Setting bit 7 is necessary, to tell the REU to execute the move. The other bits tell the REU how to do that move:
If bit 5 is set, the address and length registers are unchanged after the command is executed. This is useful if you are going to do many consecutive, identical transfers. If the bit is not set, the address registers will point to the address immediately after the last address accessed, and the length register will be set to 1.
If bit 4 is set, the command will be executed immediately. If it is not set, the command will not be executed until you write to location $FF00. This sounds weird, but is useful if you want to do transfers around the IO/ROM areas of memory. This gives you a chance to set up the transfer, then switch IO out (for example), and then execute the transfer. A simple LDA $FF00 : STA $FF00 will do the job.
Bits 1 and 0 define the transfer type. 00 is for a C64 to REU move, 01 is for a REU to C64 move, 10 is to swap between REU and C64 memory (this takes twice as long, as it has double the work to do), and 11 is for a compare between the REU and C64. To use the compare feature, clear bit 5 of $DF00, and execute the compare (just like doing any other transfer, but no bytes are actually moved). Now, if bit 5 of $DF00 has been set, then a difference was found between the REU and C64 memory.
Here’s the BASIC version. Just make sure you set x according to the comments in line 60.
10 poke57090,0:poke57091,4:rem c64 20 poke57092,0:poke57093,0:poke57094,0:rem reu 30 poke57095,232:poke57096,3:rem length 40 poke57098,0:rem not fixed addresses 50 poke57089,128+16+x 60 rem x=0 c64->reu, x=1 reu->c64, x=2 swap c64 and reu
In the assembly version, just set the labels c64 to the C64 base address, reu to the REU base address, bank to the bank number in the REU (I used 2, as my assembler makes use of 0 and 1), length to the number of bytes you want to transfer, and action equal to 0, 1, 2 or 3, corresponding to what type of transfer you’d like.
*= $4000
;reu store/swap/get
c64 = $0400
reu = $00
bank = $02
length = 1000
action = 0
lda #<c64
sta $df02
lda #>c64
sta $df03
lda #<reu
sta $df04
lda #>reu
sta $df05
lda #bank
sta $df06
lda #<length
sta $df07
lda #>length
sta $df08
lda #0
sta $df0a
lda #144+action
sta $df01
rts