So, to make our C64 boot, we need some of the original software that ran on it, of course. First we need to take a look at the memory map.
$FFFF = 65535 ┌───────────────┬───────────────┐ │---------------│|||||||||||||||│ ||| = read by e.g. PEEK │---------------│|||||||||||||||│ --- = written by e.g. POKE │---------------│|||||||||||||||│ +++ = read + write │---------------│||| KERNAL ||||│ sonst = no access with BASIC │---------------│|||||||||||||||│ │---------------│|||||||||||||||│ │---------------│|||||||||||||||│ $E000 = 57344 ├───────────────┼───────────────┼───────────────┐ │ │ │+++++++++++++++│ │ │ CHAR │+++++ I/O +++++│ │ │ │+++++++++++++++│ $D000 = 53248 ├───────────────┼───────────────┴───────────────┘ │+++++++++++++++│ │+++++++++++++++│ │+++++++++++++++│ $C000 = 49152 ├───────────────┼───────────────┐ │---------------│|||||||||||||||│ │---------------│|||||||||||||||│ │---------------│||| BASIC- ||||│ │---------------│||| ROM ||||│ │---------------│|||||||||||||||│ │---------------│|||||||||||||||│ │---------------│|||||||||||||||│ $A000 = 40960 ├───────────────┼───────────────┘ │+++++++++++++++│ │+++ BASIC- ++++│ │+++ RAM ++++│ $0800 = 2048 │+++++++++++++++│-┐ $0400 = 1024 │+++++++++++++++│-┘Video-RAM $0000 └───────────────┘-┘Zeropage
As you can see, we need to load the KERNAL, the CHAR (-ROM) and the BASIC-ROM into our 64k adress space we have available.
These files can be obtained from your original C64, or can be downloaded from the Zimmers page.
Please respect copyright laws where they might apply.
So, with the roms at our hands, we load them and make them adressable.
void loadFirmware(string filename) {
// load fw
unsigned char cartridge[0x4000];
FILE* file = fopen(filename.c_str(), "rb");
int pos = 0;
while (fread(&cartridge[pos], 1, 1, file)) {
pos++;
}
fclose(file);
// map BASIC interpreter (first 8kb) to 0xa000 - 0xbfff
for (int i = 0; i < 0x2000; i++) {
basic[i] = cartridge[i];
}
// map KERNAL
for (int i = 0; i < 0x2000; i++) {
kernal[i] = cartridge[0x2000 + i];
}
}
void loadCHRROM(string filename) {
// load file
unsigned char cartridge[0x4000];
FILE* file = fopen(filename.c_str(), "rb");
int pos = 0;
while (fread(&cartridge[pos], 1, 1, file)) {
pos++;
}
fclose(file);
// map first character ROM
for (int i = 0; i < 0x800; i++) {
chr1[i] = cartridge[i];
}
// map second character ROM
for (int i = 0; i < 0x800; i++) {
chr2[i] = cartridge[i + 0x800];
}
}
This all happens in our MMU class, where we control and direct memory writes and reads. So we can prevent access to memory, where the code shouldn’t be allowed to read from / write to.
uint8_t readFromMem(uint16_t adr) {
switch (adr)
{
...
default:
if (adr >= 0x1000 && adr < 0x1800) { // CHR1
return chr1[adr % 0x1000];
}
if (adr >= 0x1800 && adr < 0x2000) { // CHR2
return chr2[adr % 0x1800];
}
if (adr >= 0xa000 && adr < 0xc000) { // BASIC
return basic[adr % 0xa000];
}
if (adr >= 0xe000 && adr < 0x10000) { // KERNAL
return kernal[adr % 0xe000];
}
return memory[adr];
break;
}
}
void writeToMem(uint16_t adr, uint8_t val) {
...
So, what are we loading here exactly?
- KERNAL – The basic software, close to a BIOS, that talks to the hardware and peripherals
- BASIC-ROM – The BASIC interpreter that can run the routines, programmed for this machine
- CHR1/CHR2 – The 2 character sets that the C64 contains
With the ROMs in place, it’s time to set our PC to the reset vector, 0xfffc for the 6502, and let it run. But now, we will need to have graphical output, so it’s time to implement the VIC next.
Comments