libsidplayfp 1.0.3
ZeroRAMBank.h
00001 /*
00002  * This file is part of libsidplayfp, a SID player engine.
00003  *
00004  * Copyright 2012-2013 Leandro Nini <drfiemost@users.sourceforge.net>
00005  * Copyright 2010 Antti Lankila
00006  *
00007  * This program is free software; you can redistribute it and/or modify
00008  * it under the terms of the GNU General Public License as published by
00009  * the Free Software Foundation; either version 2 of the License, or
00010  * (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00020  */
00021 
00022 #ifndef ZERORAMBANK_H
00023 #define ZERORAMBANK_H
00024 
00025 #include "Bank.h"
00026 #include "sidplayfp/event.h"
00027 
00028 #include <stdint.h>
00029 
00033 class PLA
00034 {
00035 public:
00036     virtual void setCpuPort(int state) =0;
00037     virtual uint8_t getLastReadByte() const =0;
00038     virtual event_clock_t getPhi2Time() const =0;
00039 };
00040 
00055 class ZeroRAMBank : public Bank
00056 {
00057 private:
00058 /*
00059     NOTE: fall-off cycles are heavily chip- and temperature dependent. as a
00060           consequence it is very hard to find suitable realistic values that
00061           always work and we can only tweak them based on testcases. (unless we
00062           want to make it configurable or emulate temperature over time =))
00063 
00064           it probably makes sense to tweak the values for a warmed up CPU, since
00065           this is likely how (old) programs were coded and tested :)
00066 */
00067 
00068 /* $01 bits 6 and 7 fall-off cycles (1->0), average is about 350 msec for a 6510
00069    and about 1500 msec for a 8500 */
00070 /* NOTE: the unused bits of the 6510 seem to be much more temperature dependant
00071          and the fall-off time decreases quicker and more drastically than on a
00072          8500
00073 */
00074     static const event_clock_t C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES = 350000;
00075     //static const event_clock_t C64_CPU8500_DATA_PORT_FALL_OFF_CYCLES = 1500000;
00076 /*
00077    cpuports.prg from the lorenz testsuite will fail when the falloff takes more
00078    than 1373 cycles. this suggests that he tested on a well warmed up c64 :)
00079    he explicitly delays by ~1280 cycles and mentions capacitance, so he probably 
00080    even was aware of what happens.
00081 */
00082 
00083     static const bool tape_sense = false;
00084 
00085 private:
00086     PLA* pla;
00087 
00089     Bank* ramBank;
00090 
00093     event_clock_t dataSetClkBit6;
00094     event_clock_t dataSetClkBit7;
00096 
00101     bool dataFalloffBit6;
00102     bool dataFalloffBit7;
00104 
00107     uint8_t dataSetBit6;
00108     uint8_t dataSetBit7;
00110 
00113     uint8_t dir;
00114     uint8_t data;
00116 
00118     uint8_t dataRead;
00119 
00121     uint8_t procPortPins;
00122 
00123 private:
00124     void updateCpuPort()
00125     {
00126         // Update data pins for which direction is OUTPUT
00127         procPortPins = (procPortPins & ~dir) | (data & dir);
00128 
00129         dataRead = (data | ~dir) & (procPortPins | 0x17);
00130 
00131         pla->setCpuPort((data | ~dir) & 0x07);
00132 
00133         if ((dir & 0x20) == 0)
00134         {
00135             dataRead &= ~0x20;
00136         }
00137         if (tape_sense && (dir & 0x10) == 0)
00138         {
00139             dataRead &= ~0x10;
00140         }
00141     }
00142 
00143 private:    // prevent copying
00144     ZeroRAMBank(const ZeroRAMBank&);
00145     ZeroRAMBank& operator=(const ZeroRAMBank&);
00146 
00147 public:
00148     ZeroRAMBank(PLA* pla, Bank* ramBank) :
00149         pla(pla),
00150         ramBank(ramBank) {}
00151 
00152     void reset()
00153     {
00154         dataFalloffBit6 = false;
00155         dataFalloffBit7 = false;
00156         dir = 0;
00157         data = 0x3f;
00158         dataRead = 0x3f;
00159         procPortPins = 0x3f;
00160         updateCpuPort();
00161     }
00162 
00163 /*
00164     $00/$01 unused bits emulation, as investigated by groepaz:
00165 
00166     - There are 2 different unused bits, 1) the output bits, 2) the input bits
00167     - The output bits can be (re)set when the data-direction is set to output
00168       for those bits and the output bits will not drop-off to 0.
00169     - When the data-direction for the unused bits is set to output then the
00170       unused input bits can be (re)set by writing to them, when set to 1 the
00171       drop-off timer will start which will cause the unused input bits to drop
00172       down to 0 in a certain amount of time.
00173     - When an unused input bit already had the drop-off timer running, and is
00174       set to 1 again, the drop-off timer will restart.
00175     - when a an unused bit changes from output to input, and the current output
00176       bit is 1, the drop-off timer will restart again
00177 */
00178 
00179     uint8_t peek(uint_least16_t address)
00180     {
00181         switch (address)
00182         {
00183         case 0:
00184             return dir;
00185         case 1:
00186         {
00187             /* discharge the "capacitor" */
00188             if (dataFalloffBit6 || dataFalloffBit7)
00189             {
00190                 const event_clock_t phi2time = pla->getPhi2Time();
00191 
00192                 /* set real value of read bit 6 */
00193                 if (dataFalloffBit6 && dataSetClkBit6 < phi2time)
00194                 {
00195                     dataFalloffBit6 = false;
00196                     dataSetBit6 = 0;
00197                 }
00198 
00199                 /* set real value of read bit 7 */
00200                 if (dataFalloffBit7 && dataSetClkBit7 < phi2time)
00201                 {
00202                     dataFalloffBit7 = false;
00203                     dataSetBit7 = 0;
00204                 }
00205             }
00206 
00207             uint8_t retval = dataRead;
00208 
00209             /* for unused bits in input mode, the value comes from the "capacitor" */
00210 
00211             /* set real value of bit 6 */
00212             if (!(dir & 0x40))
00213             {
00214                 retval &= ~0x40;
00215                 retval |= dataSetBit6;
00216             }
00217 
00218             /* set real value of bit 7 */
00219             if (!(dir & 0x80))
00220             {
00221                 retval &= ~0x80;
00222                 retval |= dataSetBit7;
00223             }
00224 
00225             return retval;
00226         }
00227         default:
00228             return ramBank->peek(address);
00229         }
00230     }
00231 
00232     void poke(uint_least16_t address, uint8_t value)
00233     {
00234         switch (address)
00235         {
00236         case 0:
00237             /* when switching an unused bit from output (where it contained a
00238              * stable value) to input mode (where the input is floating), some
00239              * of the charge is transferred to the floating input */
00240 
00241             /* check if bit 6 has flipped from 1 to 0 */
00242             if ((dir & 0x40) && !(value & 0x40))
00243             {
00244                 dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
00245                 dataSetBit6 = data & 0x40;
00246                 dataFalloffBit6 = true;
00247             }
00248 
00249             /* check if bit 7 has flipped from 1 to 0 */
00250             if ((dir & 0x80) && !(value & 0x80))
00251             {
00252                 dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
00253                 dataSetBit7 = data & 0x80;
00254                 dataFalloffBit7 = true;
00255             }
00256 
00257             if (dir != value)
00258             {
00259                 dir = value;
00260                 updateCpuPort();
00261             }
00262             value = pla->getLastReadByte();
00263             break;
00264         case 1:
00265             /* when writing to an unused bit that is output, charge the "capacitor",
00266              * otherwise don't touch it */
00267 
00268             if (dir & 0x40)
00269             {
00270                 dataSetBit6 = value & 0x40;
00271                 dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
00272                 dataFalloffBit6 = true;
00273             }
00274 
00275             if (dir & 0x80)
00276             {
00277                 dataSetBit7 = value & 0x80;
00278                 dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
00279                 dataFalloffBit7 = true;
00280             }
00281 
00282             if (data != value)
00283             {
00284                 data = value;
00285                 updateCpuPort();
00286             }
00287             value = pla->getLastReadByte();
00288             break;
00289         default:
00290             break;
00291         }
00292 
00293         ramBank->poke(address, value);
00294     }
00295 };
00296 
00297 #endif