ED64 - HOW TO WRITE A
COMMODORE 64 EMULATOR
Copyright 2006 by ir. Marc Dendooven
Chapter 3 : Adding C64 ROM’s
Introduction
In Chapter 1 we created a simple emulator model an implemented it. In Chapter 2 we added the instruction set of the processor and created a working emulator of a 6510 microprocessor.
Now we will add the kernal rom and the basic rom which contain the operating sytem and the basic interpreter.
Obtaining the rom images.
We can get the rom images from a real C64, or we can look for them on the internet.
Loading the rom images
We could load the rom images in our emulated ram memory. This works perfectly, but we risk that they will be overwritten. So we will change the memio unit to emulate rom.
var
basic_rom : array
[$A000..$BFFF] of byte;
kernal_rom : array
[$E000..$FFFF] of byte;
These arrays should be filled with the rom data from two files called BASIC.ROM and KERNAL.ROM. The following program does this:
procedure loadrom
(var rom:array
of byte;filename:string);
var
f : file of byte;
b : byte;
address: word;
begin
{$i-}
assign (f,filename);
reset(f);
{$i+}
if ioresult <> 0
then
begin
writeln('file
',filename,' is missing');
readln;
halt
end;
address := low(rom);
while not eof(f) do
begin
read(f,b);
rom[address] :=
b;
inc(address)
end;
close(f)
end;
and this procedure can be called from the initialization part of the unit.
initialization
begin
loadrom(kernal_rom,'kernal.rom');
loadrom(basic_rom,'basic.rom')
end;
In startup mode (like the c64 is started up), reading the positions $A000 to $BFFF and $E000 to $FFFF will access the rom. We can implement this by changing the implementation of the peek function:
function peek(address : word) :
byte;
begin
case address of
$A000..$BFFF :
peek :=
basic_rom[address];
$D000..$DFFF :
peek :=
io(address);
$E000..$FFFF :
peek :=
kernal_rom[address]
else
peek :=
ram[address]
end
end;
Since writing to this regions should alter underlaying ram and not the rom, the poke procedure should not be changed.
In startup mode an access to the region $D000 to $DFFF should access memory mapped hardware, so we will dispatch to a function called io. This function will return $FF (If an IO device is not present on a real c64, the physical lines should read a high value). If we don’t return $FF for not implemented hardware, we risk that the c64 OS interpretes this as a malfunction and hangs.
function io(address : word) :
byte;
begin
io := $FF;
end;
Running the emulator
When we compile memio.pas and ed64.pas we can run the program. Be sure that the rom files are present in the same directory. The emulator starts executing the OS. If trace is on you can follow the execution. You can use cntl-c to stop.
Remark: it is easier to study the trace if you execute the command ed64.exe > out.txt in a command line shell. Use cntl-c (as fast as possible – otherwise out.txt will be too big) to stop execution. Now you can examine out.txt in an editor (don’t use notepad for big files).
Examining the trace
If you have a documented ROM listing (like the book ‘What’s really inside the Commodore 64’ – Milton Bathurst) you can follow the trace. At first some initialization routines are executed. If you examine the trace you will see that the emulator enters and stays a long time in a loop. This is the memory checking routine done at startup. This routine ends at address $FD9A. You may wonder that, if this startup routine is that slow, our emulator will be unusable.
We will alter the trace routine now to start tracing from a specific address:
in ED64.pas we change:
trace := false;
and add a line in the main processor loop:
…
while true do
begin
IR := peek(PC);
if
PC=$fd9a then trace := true;
if trace then
dump1;
inc(PC);
case IR of
…
Now tracing starts only at the indicated point (after the memory check).
remark: this is only for demonstrating purposes: this line (and the other trace lines) should be deleted in our final version.
When we execute the emulator now we can remark that tracing almost starts immediately (at least on a recent computer) after executing the complete memory check. So it is not the emulator which is slow, it is the tracing routine (in fact the screen access) that is that slow.
Now the emulator starts an endless loop at $FF5E. At this position the emulator attempts to reed a value from the video chip (the raster count at memory mapped position $D012) and waits for this value to become zero (for video synchronization). Since we have no hardware emulated the value is and stays $FF. Here is an excerpt of the trace:
…
PC=FF5E IR=AD A=FF X=01 Y=84 S=FD
P=11000101
PC=FF61 IR=D0 A=FF X=01 Y=84 S=FD
P=11000101
PC=FF5E IR=AD A=FF X=01 Y=84 S=FD
P=11000101
PC=FF61 IR=D0 A=FF X=01 Y=84 S=FD
P=11000101
PC=FF5E IR=AD A=FF X=01 Y=84 S=FD
P=11000101
PC=FF61 IR=D0 A=FF X=01 Y=84 S=FD
P=11000101
PC=FF5E IR=AD A=FF X=01 Y=84 S=FD
P=11000101
…
So we will have to program a stub for this video register. We change the io function to return $00 for the address $D012.
function io(address : word) :
byte;
begin
case address of
$D012 : io := $00
else
io := $FF
end
end;
Now the emulator passes this point and enters again an endless loop at $E5CD. But this is what we expected. The emulator simply waits for someone or something to poke a character in the keyboard buffer.
Conclusion
In this chapter we added the c64 roms and executed the OS inside it.
In the next chapter we will
provide a minimal screen and keyboard emulation to provide a working
c64 emulator.