ED64 - HOW TO WRITE A COMMODORE 64 EMULATOR
By ir. Marc Dendooven


Chapter 8: The VIC – part 1: memory management

  1. 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)

  2. 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;

  1. 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;

  1. 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)

  1. 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.