304 lines
8.9 KiB
Text
304 lines
8.9 KiB
Text
/*
|
|
* This file is part of libsidplayfp, a SID player engine.
|
|
*
|
|
* Copyright 2012-2014 Leandro Nini <drfiemost@users.sourceforge.net>
|
|
* Copyright 2009-2014 VICE project
|
|
* Copyright 2010 Antti Lankila
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef ZERORAMBANK_H
|
|
#define ZERORAMBANK_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "Bank.h"
|
|
|
|
#include "sidplayfp/event.h"
|
|
|
|
#include "sidcxx11.h"
|
|
|
|
namespace libsidplayfp
|
|
{
|
|
|
|
/**
|
|
* Interface to PLA functions.
|
|
*/
|
|
class PLA
|
|
{
|
|
public:
|
|
virtual void setCpuPort(int state) =0;
|
|
virtual uint8_t getLastReadByte() const =0;
|
|
virtual event_clock_t getPhi2Time() const =0;
|
|
};
|
|
|
|
/**
|
|
* Area backed by RAM, including cpu port addresses 0 and 1.
|
|
*
|
|
* This is bit of a fake. We know that the CPU port is an internal
|
|
* detail of the CPU, and therefore CPU should simply pay the price
|
|
* for reading/writing to 0/1.
|
|
*
|
|
* However, that would slow down all accesses, which is suboptimal. Therefore
|
|
* we install this little hook to the 4k 0 region to deal with this.
|
|
*
|
|
* Implementation based on VICE code.
|
|
*
|
|
* @author Antti Lankila
|
|
*/
|
|
class ZeroRAMBank : public Bank
|
|
{
|
|
private:
|
|
/**
|
|
* $01 bits 6 and 7 fall-off cycles (1->0), average is about 350 msec for a 6510
|
|
* and about 1500 msec for a 8500.
|
|
*
|
|
* NOTE: fall-off cycles are heavily chip- and temperature dependent. as a
|
|
* consequence it is very hard to find suitable realistic values that
|
|
* always work and we can only tweak them based on testcases. (unless we
|
|
* want to make it configurable or emulate temperature over time =))
|
|
*
|
|
* it probably makes sense to tweak the values for a warmed up CPU, since
|
|
* this is likely how (old) programs were coded and tested :)
|
|
*
|
|
* NOTE: the unused bits of the 6510 seem to be much more temperature dependant
|
|
* and the fall-off time decreases quicker and more drastically than on a
|
|
* 8500
|
|
*
|
|
* cpuports.prg from the lorenz testsuite will fail when the falloff takes more
|
|
* than 1373 cycles. this suggests that he tested on a well warmed up c64 :)
|
|
* he explicitly delays by ~1280 cycles and mentions capacitance, so he probably
|
|
* even was aware of what happens.
|
|
*/
|
|
//@{
|
|
static const event_clock_t C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES = 350000;
|
|
static const event_clock_t C64_CPU8500_DATA_PORT_FALL_OFF_CYCLES = 1500000; // Curently unused
|
|
//@}
|
|
|
|
static const bool tape_sense = false;
|
|
|
|
private:
|
|
PLA* pla;
|
|
|
|
/// C64 RAM area
|
|
Bank* ramBank;
|
|
|
|
/// Cycle that should invalidate the unused bits of the data port.
|
|
//@{
|
|
event_clock_t dataSetClkBit6;
|
|
event_clock_t dataSetClkBit7;
|
|
//@}
|
|
|
|
/// Indicates if the unused bits of the data port are in the process of falling off.
|
|
//@{
|
|
bool dataFalloffBit6;
|
|
bool dataFalloffBit7;
|
|
//@}
|
|
|
|
/// Value of the unused bit of the processor port.
|
|
//@{
|
|
uint8_t dataSetBit6;
|
|
uint8_t dataSetBit7;
|
|
//@}
|
|
|
|
/// Value written to processor port.
|
|
//@{
|
|
uint8_t dir;
|
|
uint8_t data;
|
|
//@}
|
|
|
|
/// Value read from processor port.
|
|
uint8_t dataRead;
|
|
|
|
/// State of processor port pins.
|
|
uint8_t procPortPins;
|
|
|
|
private:
|
|
void updateCpuPort()
|
|
{
|
|
// Update data pins for which direction is OUTPUT
|
|
procPortPins = (procPortPins & ~dir) | (data & dir);
|
|
|
|
dataRead = (data | ~dir) & (procPortPins | 0x17);
|
|
|
|
pla->setCpuPort((data | ~dir) & 0x07);
|
|
|
|
if ((dir & 0x20) == 0)
|
|
{
|
|
dataRead &= ~0x20;
|
|
}
|
|
if (tape_sense && (dir & 0x10) == 0)
|
|
{
|
|
dataRead &= ~0x10;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// prevent copying
|
|
ZeroRAMBank(const ZeroRAMBank&);
|
|
ZeroRAMBank& operator=(const ZeroRAMBank&);
|
|
|
|
public:
|
|
ZeroRAMBank(PLA* pla, Bank* ramBank) :
|
|
pla(pla),
|
|
ramBank(ramBank) {}
|
|
|
|
void reset()
|
|
{
|
|
dataFalloffBit6 = false;
|
|
dataFalloffBit7 = false;
|
|
dir = 0;
|
|
data = 0x3f;
|
|
dataRead = 0x3f;
|
|
procPortPins = 0x3f;
|
|
updateCpuPort();
|
|
}
|
|
|
|
// $00/$01 unused bits emulation, as investigated by groepaz:
|
|
//
|
|
// - There are 2 different unused bits, 1) the output bits, 2) the input bits
|
|
// - The output bits can be (re)set when the data-direction is set to output
|
|
// for those bits and the output bits will not drop-off to 0.
|
|
// - When the data-direction for the unused bits is set to output then the
|
|
// unused input bits can be (re)set by writing to them, when set to 1 the
|
|
// drop-off timer will start which will cause the unused input bits to drop
|
|
// down to 0 in a certain amount of time.
|
|
// - When an unused input bit already had the drop-off timer running, and is
|
|
// set to 1 again, the drop-off timer will restart.
|
|
// - when a an unused bit changes from output to input, and the current output
|
|
// bit is 1, the drop-off timer will restart again
|
|
|
|
uint8_t peek(uint_least16_t address) override
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0:
|
|
return dir;
|
|
case 1:
|
|
{
|
|
// discharge the "capacitor"
|
|
if (dataFalloffBit6 || dataFalloffBit7)
|
|
{
|
|
const event_clock_t phi2time = pla->getPhi2Time();
|
|
|
|
// set real value of read bit 6
|
|
if (dataFalloffBit6 && dataSetClkBit6 < phi2time)
|
|
{
|
|
dataFalloffBit6 = false;
|
|
dataSetBit6 = 0;
|
|
}
|
|
|
|
// set real value of read bit 7
|
|
if (dataFalloffBit7 && dataSetClkBit7 < phi2time)
|
|
{
|
|
dataFalloffBit7 = false;
|
|
dataSetBit7 = 0;
|
|
}
|
|
}
|
|
|
|
uint8_t retval = dataRead;
|
|
|
|
// for unused bits in input mode, the value comes from the "capacitor"
|
|
|
|
// set real value of bit 6
|
|
if (!(dir & 0x40))
|
|
{
|
|
retval &= ~0x40;
|
|
retval |= dataSetBit6;
|
|
}
|
|
|
|
// set real value of bit 7
|
|
if (!(dir & 0x80))
|
|
{
|
|
retval &= ~0x80;
|
|
retval |= dataSetBit7;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
default:
|
|
return ramBank->peek(address);
|
|
}
|
|
}
|
|
|
|
void poke(uint_least16_t address, uint8_t value) override
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0:
|
|
// when switching an unused bit from output (where it contained a
|
|
// stable value) to input mode (where the input is floating), some
|
|
// of the charge is transferred to the floating input
|
|
|
|
// check if bit 6 has flipped from 1 to 0
|
|
if ((dir & 0x40) && !(value & 0x40))
|
|
{
|
|
dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
|
|
dataSetBit6 = data & 0x40;
|
|
dataFalloffBit6 = true;
|
|
}
|
|
|
|
// check if bit 7 has flipped from 1 to 0
|
|
if ((dir & 0x80) && !(value & 0x80))
|
|
{
|
|
dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
|
|
dataSetBit7 = data & 0x80;
|
|
dataFalloffBit7 = true;
|
|
}
|
|
|
|
if (dir != value)
|
|
{
|
|
dir = value;
|
|
updateCpuPort();
|
|
}
|
|
value = pla->getLastReadByte();
|
|
break;
|
|
case 1:
|
|
// when writing to an unused bit that is output, charge the "capacitor",
|
|
// otherwise don't touch it
|
|
|
|
if (dir & 0x40)
|
|
{
|
|
dataSetBit6 = value & 0x40;
|
|
dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
|
|
dataFalloffBit6 = true;
|
|
}
|
|
|
|
if (dir & 0x80)
|
|
{
|
|
dataSetBit7 = value & 0x80;
|
|
dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
|
|
dataFalloffBit7 = true;
|
|
}
|
|
|
|
if (data != value)
|
|
{
|
|
data = value;
|
|
updateCpuPort();
|
|
}
|
|
value = pla->getLastReadByte();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ramBank->poke(address, value);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|