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 
75 I8254::I8254(Scheduler& scheduler, ClockPinListener* output0,
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 
167 ClockPin& I8254::getClockPin(unsigned cntr)
168 {
169  assert(cntr < 3);
170  return counter[cntr]->clock;
171 }
172 
173 ClockPin& I8254::getOutputPin(unsigned cntr)
174 {
175  assert(cntr < 3);
176  return counter[cntr]->output;
177 }
178 
179 
180 // class Counter
181 
182 Counter::Counter(Scheduler& scheduler, ClockPinListener* listener,
183  EmuTime::param time)
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  "output", output,
520  "currentTime", currentTime,
521  "counter", counter,
522  "latchedCounter", latchedCounter,
523  "counterLoad", counterLoad,
524  "control", control,
525  "latchedControl", latchedControl,
526  "ltchCtrl", ltchCtrl,
527  "ltchCntr", ltchCntr,
528  "readOrder", readOrder,
529  "writeOrder", writeOrder,
530  "writeLatch", writeLatch,
531  "gate", gate,
532  "active", active,
533  "triggered", triggered,
534  "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
bool getEnum() const noexcept
Definition: EnumSetting.hh:96
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 writeControlWord(byte value, EmuTime::param time)
Definition: I8254.cc:322
void reset(EmuTime::param time)
Definition: I8254.cc:86
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8254.cc:119
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:106
void latchStatus(EmuTime::param time)
Definition: I8254.cc:353
friend class I8254
Definition: I8254.cc:69
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:538
constexpr EmuDuration operator*(unsigned fact) const
Definition: EmuDuration.hh:71
byte peekIO(EmuTime::param time) const
Definition: I8254.cc:242
#define SERIALIZE_ENUM(TYPE, INFO)
void setGateStatus(bool status, EmuTime::param time)
Definition: I8254.cc:373
void reset(EmuTime::param time)
Definition: I8254.cc:193
byte readIO(EmuTime::param time)
Definition: I8254.cc:211
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:167
void latchCounter(EmuTime::param time)
Definition: I8254.cc:363
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