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