libsidplayfp  1.1.0
ZeroRAMBank.h
1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2012-2013 Leandro Nini <drfiemost@users.sourceforge.net>
5  * Copyright 2010 Antti Lankila
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifndef ZERORAMBANK_H
23 #define ZERORAMBANK_H
24 
25 #include "Bank.h"
26 #include "sidplayfp/event.h"
27 
28 #include <stdint.h>
29 
33 class PLA
34 {
35 public:
36  virtual void setCpuPort(int state) =0;
37  virtual uint8_t getLastReadByte() const =0;
38  virtual event_clock_t getPhi2Time() const =0;
39 };
40 
55 class ZeroRAMBank : public Bank
56 {
57 private:
58 /*
59  NOTE: fall-off cycles are heavily chip- and temperature dependent. as a
60  consequence it is very hard to find suitable realistic values that
61  always work and we can only tweak them based on testcases. (unless we
62  want to make it configurable or emulate temperature over time =))
63 
64  it probably makes sense to tweak the values for a warmed up CPU, since
65  this is likely how (old) programs were coded and tested :)
66 */
67 
68 /* $01 bits 6 and 7 fall-off cycles (1->0), average is about 350 msec for a 6510
69  and about 1500 msec for a 8500 */
70 /* NOTE: the unused bits of the 6510 seem to be much more temperature dependant
71  and the fall-off time decreases quicker and more drastically than on a
72  8500
73 */
74  static const event_clock_t C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES = 350000;
75  //static const event_clock_t C64_CPU8500_DATA_PORT_FALL_OFF_CYCLES = 1500000;
76 /*
77  cpuports.prg from the lorenz testsuite will fail when the falloff takes more
78  than 1373 cycles. this suggests that he tested on a well warmed up c64 :)
79  he explicitly delays by ~1280 cycles and mentions capacitance, so he probably
80  even was aware of what happens.
81 */
82 
83  static const bool tape_sense = false;
84 
85 private:
86  PLA* pla;
87 
89  Bank* ramBank;
90 
93  event_clock_t dataSetClkBit6;
94  event_clock_t dataSetClkBit7;
96 
101  bool dataFalloffBit6;
102  bool dataFalloffBit7;
104 
107  uint8_t dataSetBit6;
108  uint8_t dataSetBit7;
110 
113  uint8_t dir;
114  uint8_t data;
116 
118  uint8_t dataRead;
119 
121  uint8_t procPortPins;
122 
123 private:
124  void updateCpuPort()
125  {
126  // Update data pins for which direction is OUTPUT
127  procPortPins = (procPortPins & ~dir) | (data & dir);
128 
129  dataRead = (data | ~dir) & (procPortPins | 0x17);
130 
131  pla->setCpuPort((data | ~dir) & 0x07);
132 
133  if ((dir & 0x20) == 0)
134  {
135  dataRead &= ~0x20;
136  }
137  if (tape_sense && (dir & 0x10) == 0)
138  {
139  dataRead &= ~0x10;
140  }
141  }
142 
143 private: // prevent copying
144  ZeroRAMBank(const ZeroRAMBank&);
145  ZeroRAMBank& operator=(const ZeroRAMBank&);
146 
147 public:
148  ZeroRAMBank(PLA* pla, Bank* ramBank) :
149  pla(pla),
150  ramBank(ramBank) {}
151 
152  void reset()
153  {
154  dataFalloffBit6 = false;
155  dataFalloffBit7 = false;
156  dir = 0;
157  data = 0x3f;
158  dataRead = 0x3f;
159  procPortPins = 0x3f;
160  updateCpuPort();
161  }
162 
163 /*
164  $00/$01 unused bits emulation, as investigated by groepaz:
165 
166  - There are 2 different unused bits, 1) the output bits, 2) the input bits
167  - The output bits can be (re)set when the data-direction is set to output
168  for those bits and the output bits will not drop-off to 0.
169  - When the data-direction for the unused bits is set to output then the
170  unused input bits can be (re)set by writing to them, when set to 1 the
171  drop-off timer will start which will cause the unused input bits to drop
172  down to 0 in a certain amount of time.
173  - When an unused input bit already had the drop-off timer running, and is
174  set to 1 again, the drop-off timer will restart.
175  - when a an unused bit changes from output to input, and the current output
176  bit is 1, the drop-off timer will restart again
177 */
178 
179  uint8_t peek(uint_least16_t address)
180  {
181  switch (address)
182  {
183  case 0:
184  return dir;
185  case 1:
186  {
187  /* discharge the "capacitor" */
188  if (dataFalloffBit6 || dataFalloffBit7)
189  {
190  const event_clock_t phi2time = pla->getPhi2Time();
191 
192  /* set real value of read bit 6 */
193  if (dataFalloffBit6 && dataSetClkBit6 < phi2time)
194  {
195  dataFalloffBit6 = false;
196  dataSetBit6 = 0;
197  }
198 
199  /* set real value of read bit 7 */
200  if (dataFalloffBit7 && dataSetClkBit7 < phi2time)
201  {
202  dataFalloffBit7 = false;
203  dataSetBit7 = 0;
204  }
205  }
206 
207  uint8_t retval = dataRead;
208 
209  /* for unused bits in input mode, the value comes from the "capacitor" */
210 
211  /* set real value of bit 6 */
212  if (!(dir & 0x40))
213  {
214  retval &= ~0x40;
215  retval |= dataSetBit6;
216  }
217 
218  /* set real value of bit 7 */
219  if (!(dir & 0x80))
220  {
221  retval &= ~0x80;
222  retval |= dataSetBit7;
223  }
224 
225  return retval;
226  }
227  default:
228  return ramBank->peek(address);
229  }
230  }
231 
232  void poke(uint_least16_t address, uint8_t value)
233  {
234  switch (address)
235  {
236  case 0:
237  /* when switching an unused bit from output (where it contained a
238  * stable value) to input mode (where the input is floating), some
239  * of the charge is transferred to the floating input */
240 
241  /* check if bit 6 has flipped from 1 to 0 */
242  if ((dir & 0x40) && !(value & 0x40))
243  {
244  dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
245  dataSetBit6 = data & 0x40;
246  dataFalloffBit6 = true;
247  }
248 
249  /* check if bit 7 has flipped from 1 to 0 */
250  if ((dir & 0x80) && !(value & 0x80))
251  {
252  dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
253  dataSetBit7 = data & 0x80;
254  dataFalloffBit7 = true;
255  }
256 
257  if (dir != value)
258  {
259  dir = value;
260  updateCpuPort();
261  }
262  value = pla->getLastReadByte();
263  break;
264  case 1:
265  /* when writing to an unused bit that is output, charge the "capacitor",
266  * otherwise don't touch it */
267 
268  if (dir & 0x40)
269  {
270  dataSetBit6 = value & 0x40;
271  dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
272  dataFalloffBit6 = true;
273  }
274 
275  if (dir & 0x80)
276  {
277  dataSetBit7 = value & 0x80;
278  dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
279  dataFalloffBit7 = true;
280  }
281 
282  if (data != value)
283  {
284  data = value;
285  updateCpuPort();
286  }
287  value = pla->getLastReadByte();
288  break;
289  default:
290  break;
291  }
292 
293  ramBank->poke(address, value);
294  }
295 };
296 
297 #endif