515 lines
14 KiB
C++
Executable File
515 lines
14 KiB
C++
Executable File
/*
|
|
* (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;
|
|
}
|
|
}
|
|
|
|
|