openMSX
I8254.cc
Go to the documentation of this file.
1 #include "I8254.hh"
2 #include "EmuTime.hh"
3 #include "ClockPin.hh"
4 #include "serialize.hh"
5 #include "unreachable.hh"
6 #include <cassert>
7 #include <memory>
8 
9 namespace openmsx {
10 
11 static const byte READ_BACK = 0xC0;
12 static const byte RB_CNTR0 = 0x02;
13 static const byte RB_CNTR1 = 0x04;
14 static const byte RB_CNTR2 = 0x08;
15 static const byte RB_STATUS = 0x10;
16 static const byte RB_COUNT = 0x20;
17 
18 
19 class Counter {
20 public:
21  Counter(Scheduler& scheduler, ClockPinListener* listener,
22  EmuTime::param time);
23  void reset(EmuTime::param time);
24  byte readIO(EmuTime::param time);
25  byte peekIO(EmuTime::param time) const;
26  void writeIO(byte value, EmuTime::param time);
27  void setGateStatus(bool status, EmuTime::param time);
28  void writeControlWord(byte value, EmuTime::param time);
29  void latchStatus(EmuTime::param time);
30  void latchCounter(EmuTime::param time);
31 
32  template<typename Archive>
33  void serialize(Archive& ar, unsigned version);
34 
35 //private:
36  enum ByteOrder {LOW, HIGH};
37 
38 private:
39  static const byte WRT_FRMT = 0x30;
40  static const byte WF_LATCH = 0x00;
41  static const byte WF_LOW = 0x10;
42  static const byte WF_HIGH = 0x20;
43  static const byte WF_BOTH = 0x30;
44  static const byte CNTR_MODE = 0x0E;
45  static const byte CNTR_M0 = 0x00;
46  static const byte CNTR_M1 = 0x02;
47  static const byte CNTR_M2 = 0x04;
48  static const byte CNTR_M3 = 0x06;
49  static const byte CNTR_M4 = 0x08;
50  static const byte CNTR_M5 = 0x0A;
51  static const byte CNTR_M2_ = 0x0C;
52  static const byte CNTR_M3_ = 0x0E;
53 
54  void writeLoad(word value, EmuTime::param time);
55  void advance(EmuTime::param time);
56 
57  ClockPin clock;
58  ClockPin output;
59  EmuTime currentTime;
60  int counter;
61  word latchedCounter, counterLoad;
62  byte control, latchedControl;
63  bool ltchCtrl, ltchCntr;
64  ByteOrder readOrder, writeOrder;
65  byte writeLatch;
66  bool gate;
67  bool active, triggered, counting;
68 
69  friend class I8254;
70 };
71 
72 
73 // class I8254
74 
76  ClockPinListener* output1, ClockPinListener* output2,
77  EmuTime::param time)
78 {
79  counter[0] = std::make_unique<Counter>(scheduler, output0, time);
80  counter[1] = std::make_unique<Counter>(scheduler, output1, time);
81  counter[2] = std::make_unique<Counter>(scheduler, output2, time);
82 }
83 
84 I8254::~I8254() = default;
85 
86 void I8254::reset(EmuTime::param time)
87 {
88  for (auto& c : counter) {
89  c->reset(time);
90  }
91 }
92 
93 byte I8254::readIO(word port, EmuTime::param time)
94 {
95  port &= 3;
96  switch (port) {
97  case 0: case 1: case 2: // read counter 0, 1, 2
98  return counter[port]->readIO(time);
99  case 3: // read from control word, illegal
100  return 255; // TODO check value
101  default:
102  UNREACHABLE; return 0;
103  }
104 }
105 
106 byte I8254::peekIO(word port, EmuTime::param time) const
107 {
108  port &= 3;
109  switch (port) {
110  case 0: case 1: case 2:// read counter 0, 1, 2
111  return counter[port]->peekIO(time);
112  case 3: // read from control word, illegal
113  return 255; // TODO check value
114  default:
115  UNREACHABLE; return 0;
116  }
117 }
118 
119 void I8254::writeIO(word port, byte value, EmuTime::param time)
120 {
121  port &= 3;
122  switch (port) {
123  case 0: case 1: case 2: // write counter 0, 1, 2
124  counter[port]->writeIO(value, time);
125  break;
126  case 3:
127  // write to control register
128  if ((value & READ_BACK) != READ_BACK) {
129  // set control word of a counter
130  counter[value >> 6]->writeControlWord(
131  value & 0x3F, time);
132  } else {
133  // Read-Back-Command
134  if (value & RB_CNTR0) {
135  readBackHelper(value, 0, time);
136  }
137  if (value & RB_CNTR1) {
138  readBackHelper(value, 1, time);
139  }
140  if (value & RB_CNTR2) {
141  readBackHelper(value, 2, time);
142  }
143  }
144  break;
145  default:
146  UNREACHABLE;
147  }
148 }
149 
150 void I8254::readBackHelper(byte value, unsigned cntr, EmuTime::param time)
151 {
152  assert(cntr < 3);
153  if (!(value & RB_STATUS)) {
154  counter[cntr]->latchStatus(time);
155  }
156  if (!(value & RB_COUNT)) {
157  counter[cntr]->latchCounter(time);
158  }
159 }
160 
161 void I8254::setGate(unsigned cntr, bool status, EmuTime::param time)
162 {
163  assert(cntr < 3);
164  counter[cntr]->setGateStatus(status, time);
165 }
166 
168 {
169  assert(cntr < 3);
170  return counter[cntr]->clock;
171 }
172 
174 {
175  assert(cntr < 3);
176  return counter[cntr]->output;
177 }
178 
179 
180 // class Counter
181 
183  EmuTime::param time)
184  : clock(scheduler), output(scheduler, listener)
185  , currentTime(time)
186 {
187  gate = true;
188  counter = 0;
189  counterLoad = 0;
190  reset(time);
191 }
192 
193 void Counter::reset(EmuTime::param time)
194 {
195  currentTime = time;
196  ltchCtrl = false;
197  ltchCntr = false;
198  readOrder = LOW;
199  writeOrder = LOW;
200  control = 0x30; // Write BOTH / mode 0 / binary mode
201  active = false;
202  counting = true;
203  triggered = false;
204 
205  // avoid UMR on savestate
206  latchedCounter = 0;
207  latchedControl = 0;
208  writeLatch = 0;
209 }
210 
211 byte Counter::readIO(EmuTime::param time)
212 {
213  if (ltchCtrl) {
214  ltchCtrl = false;
215  return latchedControl;
216  }
217  advance(time);
218  word readData = ltchCntr ? latchedCounter : counter;
219  switch (control & WRT_FRMT) {
220  case WF_LATCH:
221  UNREACHABLE;
222  case WF_LOW:
223  ltchCntr = false;
224  return readData & 0x00FF;
225  case WF_HIGH:
226  ltchCntr = false;
227  return readData >> 8;
228  case WF_BOTH:
229  if (readOrder == LOW) {
230  readOrder = HIGH;
231  return readData & 0x00FF;
232  } else {
233  readOrder = LOW;
234  ltchCntr = false;
235  return readData >> 8;
236  }
237  default:
238  UNREACHABLE; return 0;
239  }
240 }
241 
242 byte Counter::peekIO(EmuTime::param time) const
243 {
244  if (ltchCtrl) {
245  return latchedControl;
246  }
247 
248  const_cast<Counter*>(this)->advance(time);
249 
250  word readData = ltchCntr ? latchedCounter : counter;
251  switch (control & WRT_FRMT) {
252  case WF_LATCH:
253  UNREACHABLE;
254  case WF_LOW:
255  return readData & 0x00FF;
256  case WF_HIGH:
257  return readData >> 8;
258  case WF_BOTH:
259  if (readOrder == LOW) {
260  return readData & 0x00FF;
261  } else {
262  return readData >> 8;
263  }
264  default:
265  UNREACHABLE; return 0;
266  }
267 }
268 
269 void Counter::writeIO(byte value, EmuTime::param time)
270 {
271  advance(time);
272  switch (control & WRT_FRMT) {
273  case WF_LATCH:
274  UNREACHABLE;
275  case WF_LOW:
276  writeLoad((counterLoad & 0xFF00) | value, time);
277  break;
278  case WF_HIGH:
279  writeLoad((counterLoad & 0x00FF) | (value << 8), time);
280  break;
281  case WF_BOTH:
282  if (writeOrder == LOW) {
283  writeOrder = HIGH;
284  writeLatch = value;
285  if ((control & CNTR_MODE) == CNTR_M0)
286  // pauze counting when in mode 0
287  counting = false;
288  } else {
289  writeOrder = LOW;
290  counting = true;
291  writeLoad((value << 8) | writeLatch, time);
292  }
293  break;
294  default:
295  UNREACHABLE;
296  }
297 }
298 void Counter::writeLoad(word value, EmuTime::param time)
299 {
300  counterLoad = value;
301  byte mode = control & CNTR_MODE;
302  if ((mode==CNTR_M0) || (mode==CNTR_M4)) {
303  counter = counterLoad;
304  }
305  if (!active && ((mode == CNTR_M2) || (mode == CNTR_M2_) ||
306  (mode == CNTR_M3) || (mode == CNTR_M3_))) {
307  if (clock.isPeriodic()) {
308  counter = counterLoad;
309  EmuDuration::param high = clock.getTotalDuration();
310  EmuDuration total = high * counter;
311  output.setPeriodicState(total, high, time);
312  } else {
313  // TODO ??
314  }
315  }
316  if (mode == CNTR_M0) {
317  output.setState(false, time);
318  }
319  active = true; // counter is (re)armed after counter is initialized
320 }
321 
322 void Counter::writeControlWord(byte value, EmuTime::param time)
323 {
324  advance(time);
325  if ((value & WRT_FRMT) == 0) {
326  // counter latch command
327  latchCounter(time);
328  return;
329  } else {
330  // new control mode
331  control = value;
332  writeOrder = LOW;
333  counting = true;
334  active = false;
335  triggered = false;
336  switch (control & CNTR_MODE) {
337  case CNTR_M0:
338  output.setState(false, time);
339  break;
340  case CNTR_M1:
341  case CNTR_M2: case CNTR_M2_:
342  case CNTR_M3: case CNTR_M3_:
343  case CNTR_M4:
344  case CNTR_M5:
345  output.setState(true, time);
346  break;
347  default:
348  UNREACHABLE;
349  }
350  }
351 }
352 
353 void Counter::latchStatus(EmuTime::param time)
354 {
355  advance(time);
356  if (!ltchCtrl) {
357  ltchCtrl = true;
358  byte out = output.getState(time) ? 0x80 : 0;
359  latchedControl = out | control; // TODO bit 6 null-count
360  }
361 }
362 
363 void Counter::latchCounter(EmuTime::param time)
364 {
365  advance(time);
366  if (!ltchCntr) {
367  ltchCntr = true;
368  readOrder = LOW;
369  latchedCounter = counter;
370  }
371 }
372 
373 void Counter::setGateStatus(bool newStatus, EmuTime::param time)
374 {
375  advance(time);
376  if (gate != newStatus) {
377  gate = newStatus;
378  switch (control & CNTR_MODE) {
379  case CNTR_M0:
380  case CNTR_M4:
381  // Gate does not influence output, it just acts as a count enable
382  // nothing needs to be done
383  break;
384  case CNTR_M1:
385  if (gate && active) {
386  // rising edge
387  counter = counterLoad;
388  output.setState(false, time);
389  triggered = true;
390  }
391  break;
392  case CNTR_M2: case CNTR_M2_:
393  case CNTR_M3: case CNTR_M3_:
394  if (gate) {
395  if (clock.isPeriodic()) {
396  counter = counterLoad;
397  EmuDuration::param high = clock.getTotalDuration();
398  EmuDuration total = high * counter;
399  output.setPeriodicState(total, high, time);
400  } else {
401  // TODO ???
402  }
403  } else {
404  output.setState(true, time);
405  }
406  break;
407  case CNTR_M5:
408  if (gate && active) {
409  // rising edge
410  counter = counterLoad;
411  triggered = true;
412  }
413  break;
414  default:
415  UNREACHABLE;
416  }
417  }
418 }
419 
420 void Counter::advance(EmuTime::param time)
421 {
422  // TODO !!!! Set SP !!!!
423  // TODO BCD counting
424  int ticks = int(clock.getTicksBetween(currentTime, time));
425  currentTime = time;
426  switch (control & CNTR_MODE) {
427  case CNTR_M0:
428  if (gate && counting) {
429  counter -= ticks;
430  if (counter < 0) {
431  counter &= 0xFFFF;
432  if (active) {
433  output.setState(false, time);
434  active = false; // not periodic
435  }
436  }
437  }
438  break;
439  case CNTR_M1:
440  counter -= ticks;
441  if (triggered) {
442  if (counter < 0) {
443  output.setState(true, time);
444  triggered = false; // not periodic
445  }
446  }
447  counter &= 0xFFFF;
448  break;
449  case CNTR_M2:
450  case CNTR_M2_:
451  if (gate) {
452  counter -= ticks;
453  if (active) {
454  // TODO not completely correct
455  if (counterLoad != 0) {
456  counter %= counterLoad;
457  } else {
458  counter = 0;
459  }
460  }
461  }
462  break;
463  case CNTR_M3:
464  case CNTR_M3_:
465  if (gate) {
466  counter -= 2 * ticks;
467  if (active) {
468  // TODO not correct
469  if (counterLoad != 0) {
470  counter %= counterLoad;
471  } else {
472  counter = 0;
473  }
474  }
475  }
476  break;
477  case CNTR_M4:
478  if (gate) {
479  counter -= ticks;
480  if (active) {
481  if (counter == 0) {
482  output.setState(false, time);
483  } else if (counter < 0) {
484  output.setState(true, time);
485  active = false; // not periodic
486  }
487  }
488  counter &= 0xFFFF;
489  }
490  break;
491  case CNTR_M5:
492  counter -= ticks;
493  if (triggered) {
494  if (counter == 0)
495  output.setState(false, time);
496  if (counter < 0) {
497  output.setState(true, time);
498  triggered = false; // not periodic
499  }
500  }
501  counter &= 0xFFFF;
502  break;
503  default:
504  UNREACHABLE;
505  }
506 }
507 
508 
509 static std::initializer_list<enum_string<Counter::ByteOrder>> byteOrderInfo = {
510  { "LOW", Counter::LOW },
511  { "HIGH", Counter::HIGH }
512 };
513 SERIALIZE_ENUM(Counter::ByteOrder, byteOrderInfo);
514 
515 template<typename Archive>
516 void Counter::serialize(Archive& ar, unsigned /*version*/)
517 {
518  ar.serialize("clock", clock);
519  ar.serialize("output", output);
520  ar.serialize("currentTime", currentTime);
521  ar.serialize("counter", counter);
522  ar.serialize("latchedCounter", latchedCounter);
523  ar.serialize("counterLoad", counterLoad);
524  ar.serialize("control", control);
525  ar.serialize("latchedControl", latchedControl);
526  ar.serialize("ltchCtrl", ltchCtrl);
527  ar.serialize("ltchCntr", ltchCntr);
528  ar.serialize("readOrder", readOrder);
529  ar.serialize("writeOrder", writeOrder);
530  ar.serialize("writeLatch", writeLatch);
531  ar.serialize("gate", gate);
532  ar.serialize("active", active);
533  ar.serialize("triggered", triggered);
534  ar.serialize("counting", counting);
535 }
536 
537 template<typename Archive>
538 void I8254::serialize(Archive& ar, unsigned /*version*/)
539 {
540  char tag[9] = { 'c', 'o', 'u', 'n', 't', 'e', 'r', 'X', 0 };
541  for (int i = 0; i < 3; ++i) {
542  tag[7] = char('0' + i);
543  ar.serialize(tag, *counter[i]);
544  }
545 }
547 
548 } // namespace openmsx
byte readIO(word port, EmuTime::param time)
Definition: I8254.cc:93
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
Counter(Scheduler &scheduler, ClockPinListener *listener, EmuTime::param time)
Definition: I8254.cc:182
void setGate(unsigned cntr, bool status, EmuTime::param time)
Definition: I8254.cc:161
ClockPin & getOutputPin(unsigned cntr)
Definition: I8254.cc:173
void setState(bool status, EmuTime::param time)
Definition: ClockPin.cc:16
void writeControlWord(byte value, EmuTime::param time)
Definition: I8254.cc:322
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
void reset(EmuTime::param time)
Definition: I8254.cc:86
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8254.cc:119
EmuDuration::param getTotalDuration() const
Definition: ClockPin.cc:67
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:106
void latchStatus(EmuTime::param time)
Definition: I8254.cc:353
void advance(octet_iterator &it, distance_type n, octet_iterator end)
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:538
byte peekIO(EmuTime::param time) const
Definition: I8254.cc:242
void setGateStatus(bool status, EmuTime::param time)
Definition: I8254.cc:373
bool isPeriodic() const
Definition: ClockPin.hh:34
int getTicksBetween(EmuTime::param begin, EmuTime::param end) const
Definition: ClockPin.cc:79
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void reset(EmuTime::param time)
Definition: I8254.cc:193
byte readIO(EmuTime::param time)
Definition: I8254.cc:211
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
bool getState(EmuTime::param time) const
Definition: ClockPin.cc:58
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:167
void latchCounter(EmuTime::param time)
Definition: I8254.cc:363
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition: ClockPin.cc:36
void writeIO(byte value, EmuTime::param time)
Definition: I8254.cc:269
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:516
I8254(Scheduler &scheduler, ClockPinListener *output0, ClockPinListener *output1, ClockPinListener *output2, EmuTime::param time)
Definition: I8254.cc:75
#define UNREACHABLE
Definition: unreachable.hh:38