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