Programming the Commodore REUs C=Hacking Issue 8.txt

From ReplayResources
Jump to navigationJump to search

Taken from The Fridge C=Hacking Section C=Hacking Issue #8

This issue of C=Hacking #8 was stripped down to just the Programming the Commodore RAM Expansion Units (REUs) article by Richard Hable.


Programming the Commodore RAM Expansion Units (REUs)
by Richard Hable (Richard.Hable@jk.uni-linz.ac.at)

The following article, initially written for a mailing list, describes
the Commodore REUs and explanes how to program them.

Contents:

 1) External RAM Access With REUs
 2) RAM Expansion Controller (REC) Registers
 3) How To Recognize The REU
 4) Simple RAM Transfer
 5) Additional Features
 6) Transfer Speed
 7) Interrupts
 8) Executing Code In Expanded Memory
 9) Other Useful Applications Of The REU
10) Comparision Of Bank Switching and DMA


1) _External RAM Access With REUs_

The REUs provide additional RAM for the C64/128.  Three types of REUs have
been produced by Commodore.  These are the 1700, 1764 and 1750 with 128, 256
and 512 KBytes built in RAM.  However, they can be extended up to several
MBytes.

The external memory can not be directly addressed by the C64 with its 16 bit
address space--it has to be transferred from and to the main memory of the
C64.  For that purpose, there is a built in RAM Expansion Controller (REC)
which transfers memory between the C64 and the REU using Direct Memory Access
(DMA).  It can also be used for other purposes.


2) _RAM Expansion Controller (REC) Registers_

The REC is programmed by accessing its registers.  When a REU is connected
through the expansion port, these registers appear memory mapped in the
I/O-area between $DF00 and $DF0A.  They can be read and written to like VIC-
and SID-registers.

$DF00: STATUS REGISTER
    Various information can be obtained (read only).

  Bit 7:     INTERRUPT PENDING  (1 = interrupt waiting to be served)
               unnecessary
  Bit 6:     END OF BLOCK  (1 = transfer complete)
               unnecessary
  Bit 5:     FAULT  (1 = block verify error)
               set if a difference between C64 and REU memory areas
               was found during a compare command
  Bit 4:     SIZE  (1 = 256 KB)
               seems to indicate the size of the RAM-chips;
               set on 1764 and 1750, clear on 1700.
  Bits 3..0: VERSION
               contains 0 on my REU.

$DF01: COMMAND REGISTER
     By writing to this register, RAM transfer or comparision can be
     executed.

  Bit 7:     EXECUTE  (1 = transfer per current configuration)
               must be set to execute a command
  Bit 6:     reserved  (normally 0)
  Bit 5:     LOAD  (1 = enable autoload option)
               With autoload enabled, the address and length registers (see
               below) will be unchanged after a command execution.
               Otherwise, the address registers will be counted up to the
               address of the last accessed byte of a DMA + 1
               and the length register will be changed (normally to 1).
  Bit 4:     FF00
               If this bit is set, command execution starts immediately
               after setting the command register.
               Otherwise, command execution is delayed until write access to
               memory position $FF00.
  Bits 3..2: reserved  (normally 0)
  Bits 1..0: TRANSFER TYPE
               00 = transfer C64 -> REU
               01 = transfer REU -> C64
               10 = swap C64 <-> REU
               11 = compare C64 - REU

$DF02..$DF03: C64 BASE ADDRESS
    16-bit C64 base address in low/high order

$DF04..$DF06: REU BASE ADDRESS
    This is a three byte address, consisting of a low and
    high byte and an expansion bank number.
    Normally, only bits 2..0 of the expansion bank are valid
    (for a maximum of 512 KByte), the other bits are always
    set.

$DF07..$DF08: TRANSFER LENGTH
    This is a 16 bit value containing the number of bytes to
    transfer or compare.
    The value 0 stands for 64 KBytes.
    If the transfer length plus the C64 base address exceeds
    64K, the C64 address will overflow and cause C64 memory
    from 0 on to be accessed.
    If the transfer length plus the REU base address exceeds
    512K, the REU address will overflow and cause REU memory
    from 0 on to be accessed.

$DF09: INTERRUPT MASK REGISTER
    unnecessary

  Bit 7:     INTERRUPT ENABLE  (1 = interrupt enabled)
  Bit 6:     END OF BLOCK MASK  (1 = interrupt on end)
  Bit 5:     VERIFY ERROR  (1 = interrupt on verify error)
  Bits 4..0: unused (normally all set)

$DF0A: ADDRESS CONTROL REGISTER
    With this register, address counting during DMA can be controlled.
    If a base address is fixed, the same byte is used repeatedly.

  Bit 7:     C64 ADDRESS CONTROL  (1 = fix C64 address)
  Bit 6:     REU ADDRESS CONTROL  (1 = fix REU address)
  Bits 5..0: unused (normally all set)


In order to access the REU registers in assembly language, it is convenient
to define labels something like this:

  status   = $DF00
  command  = $DF01
  c64base  = $DF02
  reubase  = $DF04
  translen = $DF07
  irqmask  = $DF09
  control  = $DF0A


3) _How To Recognize The REU_

Normally, the addresses between $DF02 and $DF05 are unused, values stored
there get lost.  Therefore, if e.g. the values 1,2,3,4 are written to
$DF02..$DF05 and do not stay there, no REU can be connected.  However, if the
values are there, it could be caused by another kind of module connected that
also uses these addresses.

Another problem is the recognition of the number of RAM banks (64 KByte
units) installed.  The SIZE bit only tells if there are at least 2 (1700) or
4 (1764, 1750) banks installed.  By trying to access and verify bytes in as
many RAM banks as possible, the real size can be determined.  This can be
seen in the source to "Dynamic memory allocation for the 128" in Commodore
Hacking Issue 2.

In any way, the user of a program should be able to choose, if and which REU
banks are to be used.


4) _Simple RAM Transfer_

Very little options of the REU are necessary for the main purposes of RAM
expanding.  Just set the base addresses, transfer length, and then the
command register.

The following code transfers one KByte containing the screen memory
($0400..$07FF) to address 0 in the REU:

  lda #0
  sta control ; to make sure both addresses are counted up
  lda #<$0400
  sta c64base
  lda #>$0400
  sta c64base + 1
  lda #0
  sta reubase
  sta reubase + 1
  sta reubase + 2
  lda #<$0400
  sta translen
  lda #>$0400
  sta translen + 1
  lda #%10010000;  c64 -> REU with immediate execution
  sta command

In order to transfer the memory back to the C64, replace "lda #%10010000" by
"lda #%10010001".

I think, this subset of 17xx functions would be enough for a reasonable RAM
expansion.  However, if full compatibility with 17xx REUs is desired, also
the more complicated functions have to be implemented.

5) _Additional Features_

Swapping Memory

With the swap-command, memory between 17xx and C64 can be exchanged. The
programming is the same as in simple RAM transfer.


Comparing Memory

No RAM is transferred. Instead, the number of bytes specified in the transfer
length register is compared.  If there are differences, the FAULT bit of the
status register is set.  In order to get valid information, this bit has to
be cleared before comparing.  This is possible by reading the status
register.


Using All C64 Memory

Normally, C64 memory is accessed in the memory configuration selected during
writing to the command register.  In order to be able to write to the command
register, the I/O-area has to be active.  If RAM between $D000 and $DFFF or
character ROM shall be used, it is possible to delay the execution of the
command by using a command byte with bit 4 ("FF00") cleared.  The command
will then be executed when an arbitrary value is written to address $FF00.

Example:

  < Set base addresses and transfer length >
  lda #%10000000 ; transfer C64 RAM -> REU delayed
  sta command
  sei
  lda $01
  and #$30
  sta $01 ; switch on 64 KByte RAM
  lda $FF00 ; do not change the contents of $FF00
  sta $FF00 ; execute DMA
  lda $01
  ora #$37
  sta $01 ; switch on normal configuration
  cli


6) _Transfer Speed_

During DMA the CPU is halted--the memory access cycles normally available for
the CPU are now used to access one byte each cycle. Therefore, with screen
and sprites switched off, in every clock cycle (985248 per second on PAL
machines) one byte is transferred.  If screen is on or sprites are enabled,
transfer is a bit slower, as the VIC sometimes accesses RAM exclusively. 
Comparing memory areas is as fast as transfering.  (Comparison is stopped
once the first difference is found.)  Swapping memory is only half as fast,
because two C64 memory accesses per byte (read & write) are necessary.


7) _Interrupts_

By setting certain bits in the interrupt mask register, IRQs at the end of a
DMA can be selected.  However, as the CPU is halted during DMA, a transfer or
comparision will always be finished after the store instruction into the
command register or $FF00.  Therefore, there is no need to check for an "END
OF BLOCK" (bit 6 of status register) or to enable an interrupt.


8) _Executing Code In Expanded Memory_

Code in expanded memory has to be copied into C64 memory before execution. 
This is a disadvantage against bank switching systems. However, bank
switching can be simulated by the SWAP command.  This is done e.g. in RAMDOS. 
There, only 256 bytes of C64 memory are occupied, the 8 KByte RAM disk driver
is swapped in whenever needed.  Too much swapping is one reason for RAMDOS to
be relatively slow at sequential file access.


9) _Other Useful Applications Of The REU_

The REC is not only useful for RAM transfer and comparison.

One other application (used in GEOS) is copying C64 RAM areas by first
transferring them to the REU and then transferring them back into the desired
position in C64 memory.  Due to the fast DMA, this is about 5 times faster
than copying memory with machine language instructions.

Interesting things can be done by fixing base addresses:  By fixing the REU
base address, large C64 areas can be fast filled with a single byte value. 
It is also possible to find the end of an area containing equal bytes very
fast, e.g. for data compression.

Fixing the C64 base address is interesting if it points to an I/O-port. 
Then, data can be written out faster than normally possible.  It would be
possible to use real bitmap graphics in the upper and lower screen border by
changing the "magic byte" (byte with the highest address accessable by the
VIC) in every clock cycle. Therefore, of course, the vertical border would
have to be switched off.

Generally the REC could be used as graphics accelerator, e.g. to copy bitmap
areas or other display data fast into the VIC-addressable 16 KByte area.


10) _Comparision Of Bank Switching and DMA_

When comparing bank switching and DMA for memory expansion, I think, DMA is
the more comfortable method to program. It is also faster in most cases. 
The disadvantage of code execution not possible in external memory can be
minimized by always copying only the necessary parts into C64 memory. 
Executing the code will then take much more time than copying it into C64
memory.