libsidplayfp  1.7.1
ZeroRAMBank.h
1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2012-2015 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 <stdint.h>
26 
27 #include "Bank.h"
28 #include "SystemRAMBank.h"
29 
30 #include "sidplayfp/event.h"
31 
35 class PLA
36 {
37 public:
38  virtual void setCpuPort(int state) =0;
39  virtual uint8_t getLastReadByte() const =0;
40  virtual event_clock_t getPhi2Time() const =0;
41 };
42 
57 class ZeroRAMBank : public Bank
58 {
59 private:
60 /*
61  NOTE: fall-off cycles are heavily chip- and temperature dependent. as a
62  consequence it is very hard to find suitable realistic values that
63  always work and we can only tweak them based on testcases. (unless we
64  want to make it configurable or emulate temperature over time =))
65 
66  it probably makes sense to tweak the values for a warmed up CPU, since
67  this is likely how (old) programs were coded and tested :)
68 */
69 
70 /* $01 bits 6 and 7 fall-off cycles (1->0), average is about 350 msec for a 6510
71  and about 1500 msec for a 8500 */
72 /* NOTE: the unused bits of the 6510 seem to be much more temperature dependant
73  and the fall-off time decreases quicker and more drastically than on a
74  8500
75 */
76  static const event_clock_t C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES = 350000;
77  //static const event_clock_t C64_CPU8500_DATA_PORT_FALL_OFF_CYCLES = 1500000;
78 /*
79  cpuports.prg from the lorenz testsuite will fail when the falloff takes more
80  than 1373 cycles. this suggests that he tested on a well warmed up c64 :)
81  he explicitly delays by ~1280 cycles and mentions capacitance, so he probably
82  even was aware of what happens.
83 */
84 
85  static const bool tape_sense = false;
86 
87 private:
88  PLA* pla;
89 
91  SystemRAMBank* ramBank;
92 
94 
95  event_clock_t dataSetClkBit6;
96  event_clock_t dataSetClkBit7;
98 
100 
101  bool dataFalloffBit6;
102  bool dataFalloffBit7;
104 
106 
107  uint8_t dataSetBit6;
108  uint8_t dataSetBit7;
110 
112 
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:
144  // prevent copying
145  ZeroRAMBank(const ZeroRAMBank&);
146  ZeroRAMBank& operator=(const ZeroRAMBank&);
147 
148 public:
149  ZeroRAMBank(PLA* pla, SystemRAMBank* ramBank) :
150  pla(pla),
151  ramBank(ramBank) {}
152 
153  void reset()
154  {
155  dataFalloffBit6 = false;
156  dataFalloffBit7 = false;
157  dir = 0;
158  data = 0x3f;
159  dataRead = 0x3f;
160  procPortPins = 0x3f;
161  updateCpuPort();
162  }
163 
164 /*
165  $00/$01 unused bits emulation, as investigated by groepaz:
166 
167  - There are 2 different unused bits, 1) the output bits, 2) the input bits
168  - The output bits can be (re)set when the data-direction is set to output
169  for those bits and the output bits will not drop-off to 0.
170  - When the data-direction for the unused bits is set to output then the
171  unused input bits can be (re)set by writing to them, when set to 1 the
172  drop-off timer will start which will cause the unused input bits to drop
173  down to 0 in a certain amount of time.
174  - When an unused input bit already had the drop-off timer running, and is
175  set to 1 again, the drop-off timer will restart.
176  - when a an unused bit changes from output to input, and the current output
177  bit is 1, the drop-off timer will restart again
178 */
179 
180  uint8_t peek(uint_least16_t address)
181  {
182  switch (address)
183  {
184  case 0:
185  return dir;
186  case 1:
187  {
188  /* discharge the "capacitor" */
189  if (dataFalloffBit6 || dataFalloffBit7)
190  {
191  const event_clock_t phi2time = pla->getPhi2Time();
192 
193  /* set real value of read bit 6 */
194  if (dataFalloffBit6 && dataSetClkBit6 < phi2time)
195  {
196  dataFalloffBit6 = false;
197  dataSetBit6 = 0;
198  }
199 
200  /* set real value of read bit 7 */
201  if (dataFalloffBit7 && dataSetClkBit7 < phi2time)
202  {
203  dataFalloffBit7 = false;
204  dataSetBit7 = 0;
205  }
206  }
207 
208  uint8_t retval = dataRead;
209 
210  /* for unused bits in input mode, the value comes from the "capacitor" */
211 
212  /* set real value of bit 6 */
213  if (!(dir & 0x40))
214  {
215  retval &= ~0x40;
216  retval |= dataSetBit6;
217  }
218 
219  /* set real value of bit 7 */
220  if (!(dir & 0x80))
221  {
222  retval &= ~0x80;
223  retval |= dataSetBit7;
224  }
225 
226  return retval;
227  }
228  default:
229  return ramBank->peek(address);
230  }
231  }
232 
233  void poke(uint_least16_t address, uint8_t value)
234  {
235  switch (address)
236  {
237  case 0:
238  /* when switching an unused bit from output (where it contained a
239  * stable value) to input mode (where the input is floating), some
240  * of the charge is transferred to the floating input */
241 
242  /* check if bit 6 has flipped from 1 to 0 */
243  if ((dir & 0x40) && !(value & 0x40))
244  {
245  dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
246  dataSetBit6 = data & 0x40;
247  dataFalloffBit6 = true;
248  }
249 
250  /* check if bit 7 has flipped from 1 to 0 */
251  if ((dir & 0x80) && !(value & 0x80))
252  {
253  dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
254  dataSetBit7 = data & 0x80;
255  dataFalloffBit7 = true;
256  }
257 
258  if (dir != value)
259  {
260  dir = value;
261  updateCpuPort();
262  }
263  value = pla->getLastReadByte();
264  break;
265  case 1:
266  /* when writing to an unused bit that is output, charge the "capacitor",
267  * otherwise don't touch it */
268 
269  if (dir & 0x40)
270  {
271  dataSetBit6 = value & 0x40;
272  dataSetClkBit6 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
273  dataFalloffBit6 = true;
274  }
275 
276  if (dir & 0x80)
277  {
278  dataSetBit7 = value & 0x80;
279  dataSetClkBit7 = pla->getPhi2Time() + C64_CPU6510_DATA_PORT_FALL_OFF_CYCLES;
280  dataFalloffBit7 = true;
281  }
282 
283  if (data != value)
284  {
285  data = value;
286  updateCpuPort();
287  }
288  value = pla->getLastReadByte();
289  break;
290  default:
291  break;
292  }
293 
294  ramBank->poke(address, value);
295  }
296 };
297 
298 #endif
uint8_t peek(uint_least16_t address)
Definition: SystemRAMBank.h:56
void poke(uint_least16_t address, uint8_t value)
Definition: ZeroRAMBank.h:233
Definition: Bank.h:32
Definition: SystemRAMBank.h:35
Definition: ZeroRAMBank.h:35
uint8_t peek(uint_least16_t address)
Definition: ZeroRAMBank.h:180
Definition: ZeroRAMBank.h:57
void poke(uint_least16_t address, uint8_t value)
Definition: SystemRAMBank.h:61