/* * (C) 2008 - 2015 Dirk W. Hoffmann. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "C64.h" // -------------------------------------------------------------------------------- // Construction and Destruction // -------------------------------------------------------------------------------- C64Memory::C64Memory() { name ="C64 memory"; debug (2, " Creating main memory at address %p...\n", this); charRomFile = NULL; kernelRomFile = NULL; basicRomFile = NULL; // Register snapshot items SnapshotItem items[] = { { ram, sizeof(ram), KEEP_ON_RESET }, { colorRam, sizeof(colorRam), KEEP_ON_RESET }, { &rom[0xA000], 0x2000, KEEP_ON_RESET }, /* Basic ROM */ { &rom[0xD000], 0x1000, KEEP_ON_RESET }, /* Character ROM */ { &rom[0xE000], 0x2000, KEEP_ON_RESET }, /* Kernel ROM */ { &peekSrc, sizeof(peekSrc), KEEP_ON_RESET }, { &pokeTarget, sizeof(pokeTarget), KEEP_ON_RESET }, { NULL, 0, 0 }}; registerSnapshotItems(items, sizeof(items)); } C64Memory::~C64Memory() { debug(2, " Releasing main memory at address %p...\n", this); } void C64Memory::reset() { VirtualComponent::reset(); // Establish bindings vic = c64->vic; sid = c64->sid; cia1 = c64->cia1; cia2 = c64->cia2; cpu = c64->cpu; // Initialize RAM (powerup pattern similar to Frodo and VICE) for (unsigned i = 0; i < sizeof(ram); i++) ram[i] = (i & 0x40) ? 0xFF : 0x00; // Clear out initially visible screen memory to make it look nicer on startup for (unsigned i = 0; i < 1000; i++) ram[0x400+i] = 0x00; // Initialize peek source lookup table for (unsigned i = 0x1; i <= 0xF; i++) peekSrc[i] = M_RAM; peekSrc[0x0] = M_PP; // Initialize poke source lookup table for (unsigned i = 0x1; i <= 0xF; i++) pokeTarget[i] = M_RAM; pokeTarget[0x0] = M_PP; // Initialize color memory with random numbers for (unsigned i = 0; i < sizeof(colorRam); i++) { colorRam[i] = (rand() & 0xFF); } // Initialize processor port data direction register and processor port poke(0x0000, 0x2F); // Data direction poke(0x0001, 0x1F); // IO port, set default memory layout } // -------------------------------------------------------------------------------- // Input / Output // -------------------------------------------------------------------------------- void C64Memory::dumpState() { msg("C64 Memory:\n"); msg("-----------\n"); msg(" Basic ROM :%s loaded\n", basicRomIsLoaded() ? "" : " not"); msg("Character ROM :%s loaded\n", charRomIsLoaded() ? "" : " not"); msg(" Kernel ROM :%s loaded\n", kernelRomIsLoaded() ? "" : " not"); for (uint16_t i = 0; i < 0xFFFF; i++) { uint8_t tag = cpu->getBreakpointTag(i); if (tag != CPU::NO_BREAKPOINT) { msg("Breakpoint at %0x4X %s\n", i, tag == CPU::SOFT_BREAKPOINT ? "(soft)" : ""); } } msg("\n"); } // -------------------------------------------------------------------------------- // Rom Handling // -------------------------------------------------------------------------------- bool C64Memory::isBasicRom(const char *filename) { int magic_bytes[] = { 0x94, 0xE3, 0x7B, EOF }; if (filename == NULL) return false; if (!checkFileSize(filename, 0x2000, 0x2000)) return false; if (!checkFileHeader(filename, magic_bytes)) return false; return true; } bool C64Memory::isCharRom(const char *filename) { int magic_bytes[] = { 0x3C, 0x66, 0x6E, EOF }; if (filename == NULL) return false; if (!checkFileSize(filename, 0x1000, 0x1000)) return false; if (!checkFileHeader(filename, magic_bytes)) return false; return true; } bool C64Memory::isKernelRom(const char *filename) { int magic_bytes[] = { 0x85, 0x56, 0x20, EOF }; if (filename == NULL) return false; if (!checkFileSize(filename, 0x2000, 0x2000)) return false; if (!checkFileHeader(filename, magic_bytes)) return false; return true; } bool C64Memory::isRom(const char *filename) { return isBasicRom(filename) || isCharRom(filename) || isKernelRom(filename); } bool C64Memory::loadBasicRom(const char *filename) { if (isBasicRom(filename)) { basicRomFile = strdup(filename); flashRom(filename, 0xA000); return true; } return false; } bool C64Memory::loadCharRom(const char *filename) { if (isCharRom(filename)) { charRomFile = strdup(filename); flashRom(filename, 0xD000); return true; } return false; } bool C64Memory::loadKernelRom(const char *filename) { if (isKernelRom(filename)) { kernelRomFile = strdup(filename); flashRom(filename, 0xE000); return true; } return false; } // -------------------------------------------------------------------------------- // Memory access methods // -------------------------------------------------------------------------------- bool C64Memory::isValidAddr(uint16_t addr, MemoryType type) { switch (type) { case MEM_RAM: return true; case MEM_ROM: return isRomAddr(addr); case MEM_IO: return isCharRomAddr(addr); default: assert(false); return false; } } uint8_t C64Memory::peekRam(uint16_t addr) { return ram[addr]; } uint8_t C64Memory::peekRom(uint16_t addr) { return rom[addr]; } void C64Memory::updatePeekPokeLookupTables() { uint8_t EXROM = c64->expansionport->getExromLine() ? 0x10 : 0x00; uint8_t GAME = c64->expansionport->getGameLine() ? 0x08 : 0x00; uint8_t index = (cpu->getPortLines() & 0x07) | EXROM | GAME; MemorySource source; // 0x1000 - 0x7FFF (RAM or unmapped) source = BankMap[index][0]; assert(source == M_RAM || source == M_NONE); peekSrc[0x1] = source; peekSrc[0x2] = source; peekSrc[0x3] = source; peekSrc[0x4] = source; peekSrc[0x5] = source; peekSrc[0x6] = source; peekSrc[0x7] = source; // 0x8000 - 0x9FFF (Cartridge ROM or RAM) source = BankMap[index][1]; assert(source == M_CRTLO || source == M_CRTHI || source == M_RAM); peekSrc[0x8] = source; peekSrc[0x9] = source; // 0xA000 - 0xBFFF (Cartridge ROM, basic ROM, RAM, or unmapped) source = BankMap[index][2]; assert(source == M_CRTLO || source == M_CRTHI || source == M_BASIC || source == M_RAM || source == M_NONE); peekSrc[0xA] = source; peekSrc[0xB] = source; // 0xC000 - 0xCFFF (RAM or unmapped) source = BankMap[index][3]; assert(source == M_RAM || source == M_NONE); peekSrc[0xC] = source; // 0xD000 - 0xDFFF (IO space, character ROM, or RAM) source = BankMap[index][4]; assert(source == M_IO || source == M_CHAR || source == M_RAM); peekSrc[0xD] = source; // 0xE000 - 0xFFFF (Cartridge Rom, Kernel ROM, or RAM) source = BankMap[index][5]; assert(source == M_CRTLO || source == M_CRTHI || source == M_KERNEL || source == M_RAM); peekSrc[0xE] = source; peekSrc[0xF] = source; MemorySource target; // 0xD000 - 0xDFFF (IO space, Character ROM, or RAM) target = BankMap[index][4]; assert(target == M_IO || target == M_CHAR || target == M_RAM); pokeTarget[0xD] = (target == M_IO ? M_IO : M_RAM); } uint8_t C64Memory::peekIO(uint16_t addr) { // 0xD000 - 0xD3FF (VIC) if (addr <= 0xD3FF) { // Note: Only the lower 6 bits are used for adressing the VIC I/O space // Therefore, the VIC I/O memory repeats every 64 bytes return vic->peek(addr & 0x003F); } // 0xD400 - 0xD7FF (SID) if (addr <= 0xD7FF) { // Note: Only the lower 5 bits are used for adressing the SID I/O space // Therefore, the SID I/O memory repeats every 32 bytes return sid->peek(addr & 0x001F); } // 0xD800 - 0xDBFF (Color RAM) if (addr <= 0xDBFF) { return (colorRam[addr - 0xD800] & 0x0F) | (vic->getDataBus() & 0xF0); } // 0xDC00 - 0xDCFF (CIA 1) if (addr <= 0xDCFF) { // Note: Only the lower 4 bits are used for adressing the CIA I/O space // Therefore, the CIA I/O memory repeats every 16 bytes return cia1->peek(addr & 0x000F); } // 0xDD00 - 0xDDFF (CIA 2) if (addr <= 0xDDFF) { // Note: Only the lower 4 bits are used for adressing the CIA I/O space // Therefore, the CIA I/O memory repeats every 16 bytes return cia2->peek(addr & 0x000F); } // 0xDE00 - 0xDEFF (I/O area 1) // 0xDF00 - 0xDFFF (I/O area 2) if (addr <= 0xDFFF) { /* "Die beiden mit "I/O 1" und "I/O 2" bezeichneten Bereiche sind für Erweiterungskarten reserviert und normalerweise ebenfalls offen, ein Lesezugriff liefert auch hier "zufällige" Daten (dass diese Daten gar nicht so zufällig sind, wird in Kapitel 4 noch ausführlich erklärt. Ein Lesen von offenen Adressen liefert nämlich auf vielen C64 das zuletzt vom VIC gelesene Byte zurück!)" [C.B.] */ return vic->getDataBus(); } assert(false); return 0; } uint8_t C64Memory::peek(uint16_t addr) { MemorySource src = peekSrc[addr >> 12]; switch(src) { case M_RAM: return ram[addr]; case M_ROM: return rom[addr]; case M_IO: return peekIO(addr); case M_CRTLO: case M_CRTHI: if (c64->expansionport->romIsBlendedIn(addr)) return c64->expansionport->peek(addr); else return ram[addr]; case M_PP: uint8_t dir, ext, result; if (addr > 0x0001) return ram[addr]; // Processor port dir = cpu->getPortDirection(); ext = cpu->getExternalPortBits(); // Datasette (move to getExternalPortBits(?) after cleanup) if (c64->datasette.getPlayKey()) ext &= 0xEF; else ext |= 0x10; result = (addr == 0x0000) ? dir : (dir & cpu->getPort()) | (~dir & ext); return result; case M_NONE: // what happens if RAM is unmapped? return ram[addr]; default: assert(0); return 0; } } // -------------------------------------------------------------------------------- // Poke // -------------------------------------------------------------------------------- void C64Memory::pokeRam(uint16_t addr, uint8_t value) { ram[addr] = value; } void C64Memory::pokeRom(uint16_t addr, uint8_t value) { #if 0 if (cartridge != NULL && cartridge->isRomAddr(addr)) { cartridge->poke(addr, value); } else { rom[addr] = value; } #endif rom[addr] = value; } void C64Memory::pokeIO(uint16_t addr, uint8_t value) { // 0xD000 - 0xD3FF (VIC) if (addr < 0xD400) { // Note: Only the lower 6 bits are used for adressing the VIC I/O space // Therefore, the VIC I/O memory repeats every 64 bytes vic->poke(addr & 0x003F, value); return; } // 0xD400 - 0xD7FF (SID) if (addr < 0xD800) { // Note: Only the lower 5 bits are used for adressing the SID I/O space // Therefore, the SID I/O memory repeats every 32 bytes sid->poke(addr & 0x001F, value); return; } // 0xD800 - 0xDBFF (Color RAM) if (addr < 0xDC00) { // Note: The color RAM only saves 4 Bit per address (one nibble) // When reading the color RAM, the upper 4 bits will contain random values colorRam[addr - 0xD800] = (value & 0x0F) | (rand() & 0xF0); return; } // 0xDC00 - 0xDCFF (CIA 1) if (addr < 0xDD00) { // Note: Only the lower 4 bits are used for adressing the CIA I/O space // Therefore, the VIC I/O memory repeats every 16 bytes cia1->poke(addr & 0x000F, value); return; } // 0xDD00 - 0xDDFF (CIA 2) if (addr < 0xDE00) { // Note: Only the lower 4 bits are used for adressing the CIA I/O space // Therefore, the VIC I/O memory repeats every 16 bytes cia2->poke(addr & 0x000F, value); return; } // 0xDE00 - 0xDEFF (I/O area 1) // 0xDF00 - 0xDFFF (I/O area 2) if (addr < 0xE000) { // Some registers in this area trigger a bank switch in the // attached cartridge module. So we pass the value there... c64->expansionport->poke(addr, value); return; } assert(false); } void C64Memory::poke(uint16_t addr, uint8_t value) { MemorySource target = pokeTarget[addr >> 12]; switch(target) { case M_RAM: ram[addr] = value; return; case M_IO: pokeIO(addr, value); return; case M_PP: if (addr > 0x0001) { ram[addr] = value; return; } // Processor port if (addr == 0x0000) { cpu->setPortDirection(value); updatePeekPokeLookupTables(); } else { cpu->setPort(value); updatePeekPokeLookupTables(); } return; default: assert(0); return; } }