ED64 - HOW TO WRITE A
COMMODORE 64 EMULATOR
By ir. Marc Dendooven
Chapter 8: The VIC –
part 1: memory management
Introduction
Our emulator uses for the moment a very simple text output: the memory range from 1024 to 2023 is monitored, and an access to this memory range results in a copy from character rom to the screen. Now we will extend the functionality of the VIC (Video Interface Chip). In this chapter we will provide IO access to the VIC registers, and use the information in these registers to let access the VIC other ranges in memory. (for text and for characterset data)
Communication with the VIC
First we will define the VIC registers in the VIC unit:
reg : array[$D000..$D02E] of byte;
and two methods for accessing them :
function vicRead(address : word) :
byte;
begin
case address of
$D012 : vicRead := $00;
//raster
else
vicRead := reg[address]
end
end;
procedure vicWrite(address : word ;
val : byte);
begin
reg[address] := val;
end;
reading $D012 (the raster register) will return 0 for the reason explained in chapter 3.
these methods will be called by ioIn and
ioOut in the memio unit:
function ioIn(address : word) :
byte;
begin
case address of
$D000..$D02E : ioIn :=
vicRead(address);
else
ioIn := $FF
end
end;
procedure ioOut(address : word ;
value : byte);
begin
case address of
$D000..$D02E :
vicWrite(address,value);
end
end;
Getting the addresses
VIC register $D018 contains the screen
and characterset addresses. But the VIC can only address a range of
16k. The two most significant address lines are provided by one of the
CIA chips, and controlled by CIA register $DD00.
In our VIC emulation, we will use tree address variables:
bankAddress : word;
screenAddress : word;
charRomAddress:
word;
bankAddress provides an
offset for the selected 16k bank, and the others point to an address
inside that bank.
Since the bankaddress is selected by a CIA register and not by a VIC
register, we will provide a method to set it (because it will be set
from the memio package and not from inside the vic package) :
procedure vicBank(val : byte);
begin
bankAddress := not(val and 3) *
$4000;
end;
The addresses are
represented by some bits in the register. a bit operation is necessary
to calculate the address.
This method is called from the ioOut procedure in
memio. (see memio.pas)
the othes variabels are VIC registers and are modified in vicWrite:
procedure vicWrite(address : word
; val : byte);
begin
reg[address] := val;
case address of
$D018 : begin
screenAddress
:= (val shr 4)*$400;
charRomAddress
:= (val and $0E)*$400;
end
end
end;
Now vicText can be changed to use the
correct character set address :
procedure vicText(pos: word; val:
byte);
var x0,y0,x,y:integer;
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
for x:=0 to 7 do
for y := 0 to 7 do
if
boolean(vicPeek(bankAddress+charRomAddress+val*8+y)
and ($80 shr x))
then
setPixel(x0+x,y0+y,14) //lightblue
else
setPixel(x0+x,y0+y,6) //blue
end;
Monitoring the text area
Until now, text is written when a poke occurs in
the range 1000 – 1999. But now the textarea is variable. To resolve
this we will create in memio to variables and a write method :
watchVicFrom,watchVicTo : word;
procedure
setVicWatchRange(startAddress,endAddress : word);
begin
watchVicFrom := startAddress;
watchVicTo := endAddress;
end;
Since a case instruction can't select on variables, an if then construction will be used :
procedure poke(address : word ;
value : byte);
begin
ram[address] := value;
if (address >=
watchVicFrom)
and (address <= watchVicTo)
then
VicText(address-watchVicFrom,value);
case address of
$0001 :
ioport(value);
$D000..$DFFF : if hiORlo
then ioOut(address,value);
end
end;
setVicWartchRange is
called everytime bankAddress
or screenAddress is changed. (in the vicBank and in the vicWrite
procedures - see vic.pas)
Conclusion
Now we can use the complete memory range to
store our text- and character data. Observe that 'PRINT CHR$(14)' now
switches to characterset 2. Also user defined charactersets can now be
used.
In the following chapter we will introduce color.