openMSX
I8254.cc
Go to the documentation of this file.
1 #include "I8254.hh"
2 #include "EmuTime.hh"
3 #include "ClockPin.hh"
4 #include "one_of.hh"
5 #include "serialize.hh"
6 #include "unreachable.hh"
7 #include <cassert>
8 #include <memory>
9 
10 namespace openmsx {
11 
12 constexpr byte READ_BACK = 0xC0;
13 constexpr byte RB_CNTR0 = 0x02;
14 constexpr byte RB_CNTR1 = 0x04;
15 constexpr byte RB_CNTR2 = 0x08;
16 constexpr byte RB_STATUS = 0x10;
17 constexpr byte RB_COUNT = 0x20;
18 
19 
20 class Counter {
21 public:
22  Counter(Scheduler& scheduler, ClockPinListener* listener,
23  EmuTime::param time);
24  void reset(EmuTime::param time);
25  byte readIO(EmuTime::param time);
26  byte peekIO(EmuTime::param time) const;
27  void writeIO(byte value, EmuTime::param time);
28  void setGateStatus(bool status, EmuTime::param time);
29  void writeControlWord(byte value, EmuTime::param time);
30  void latchStatus(EmuTime::param time);
31  void latchCounter(EmuTime::param time);
32 
33  template<typename Archive>
34  void serialize(Archive& ar, unsigned version);
35 
36 //private:
37  enum ByteOrder {LOW, HIGH};
38 
39 private:
40  static constexpr byte WRT_FRMT = 0x30;
41  static constexpr byte WF_LATCH = 0x00;
42  static constexpr byte WF_LOW = 0x10;
43  static constexpr byte WF_HIGH = 0x20;
44  static constexpr byte WF_BOTH = 0x30;
45  static constexpr byte CNTR_MODE = 0x0E;
46  static constexpr byte CNTR_M0 = 0x00;
47  static constexpr byte CNTR_M1 = 0x02;
48  static constexpr byte CNTR_M2 = 0x04;
49  static constexpr byte CNTR_M3 = 0x06;
50  static constexpr byte CNTR_M4 = 0x08;
51  static constexpr byte CNTR_M5 = 0x0A;
52  static constexpr byte CNTR_M2_ = 0x0C;
53  static constexpr byte CNTR_M3_ = 0x0E;
54 
55  void writeLoad(word value, EmuTime::param time);
56  void advance(EmuTime::param time);
57 
58  ClockPin clock;
59  ClockPin output;
60  EmuTime currentTime;
61  int counter;
62  word latchedCounter, counterLoad;
63  byte control, latchedControl;
64  bool ltchCtrl, ltchCntr;
65  ByteOrder readOrder, writeOrder;
66  byte writeLatch;
67  bool gate;
68  bool active, triggered, counting;
69 
70  friend class I8254;
71 };
72 
73 
74 // class I8254
75 
77  ClockPinListener* output1, ClockPinListener* output2,
78  EmuTime::param time)
79 {
80  counter[0] = std::make_unique<Counter>(scheduler, output0, time);
81  counter[1] = std::make_unique<Counter>(scheduler, output1, time);
82  counter[2] = std::make_unique<Counter>(scheduler, output2, time);
83 }
84 
85 I8254::~I8254() = default;
86 
87 void I8254::reset(EmuTime::param time)
88 {
89  for (auto& c : counter) {
90  c->reset(time);
91  }
92 }
93 
94 byte I8254::readIO(word port, EmuTime::param time)
95 {
96  port &= 3;
97  switch (port) {
98  case 0: case 1: case 2: // read counter 0, 1, 2
99  return counter[port]->readIO(time);
100  case 3: // read from control word, illegal
101  return 255; // TODO check value
102  default:
103  UNREACHABLE; return 0;
104  }
105 }
106 
107 byte I8254::peekIO(word port, EmuTime::param time) const
108 {
109  port &= 3;
110  switch (port) {
111  case 0: case 1: case 2:// read counter 0, 1, 2
112  return counter[port]->peekIO(time);
113  case 3: // read from control word, illegal
114  return 255; // TODO check value
115  default:
116  UNREACHABLE; return 0;
117  }
118 }
119 
120 void I8254::writeIO(word port, byte value, EmuTime::param time)
121 {
122  port &= 3;
123  switch (port) {
124  case 0: case 1: case 2: // write counter 0, 1, 2
125  counter[port]->writeIO(value, time);
126  break;
127  case 3:
128  // write to control register
129  if ((value & READ_BACK) != READ_BACK) {
130  // set control word of a counter
131  counter[value >> 6]->writeControlWord(
132  value & 0x3F, time);
133  } else {
134  // Read-Back-Command
135  if (value & RB_CNTR0) {
136  readBackHelper(value, 0, time);
137  }
138  if (value & RB_CNTR1) {
139  readBackHelper(value, 1, time);
140  }
141  if (value & RB_CNTR2) {
142  readBackHelper(value, 2, time);
143  }
144  }
145  break;
146  default:
147  UNREACHABLE;
148  }
149 }
150 
151 void I8254::readBackHelper(byte value, unsigned cntr, EmuTime::param time)
152 {
153  assert(cntr < 3);
154  if (!(value & RB_STATUS)) {
155  counter[cntr]->latchStatus(time);
156  }
157  if (!(value & RB_COUNT)) {
158  counter[cntr]->latchCounter(time);
159  }
160 }
161 
162 void I8254::setGate(unsigned cntr, bool status, EmuTime::param time)
163 {
164  assert(cntr < 3);
165  counter[cntr]->setGateStatus(status, time);
166 }
167 
169 {
170  assert(cntr < 3);
171  return counter[cntr]->clock;
172 }
173 
175 {
176  assert(cntr < 3);
177  return counter[cntr]->output;
178 }
179 
180 
181 // class Counter
182 
184  EmuTime::param time)
185  : clock(scheduler), output(scheduler, listener)
186  , currentTime(time)
187 {
188  gate = true;
189  counter = 0;
190  counterLoad = 0;
191  reset(time);
192 }
193 
194 void Counter::reset(EmuTime::param time)
195 {
196  currentTime = time;
197  ltchCtrl = false;
198  ltchCntr = false;
199  readOrder = LOW;
200  writeOrder = LOW;
201  control = 0x30; // Write BOTH / mode 0 / binary mode
202  active = false;
203  counting = true;
204  triggered = false;
205 
206  // avoid UMR on savestate
207  latchedCounter = 0;
208  latchedControl = 0;
209  writeLatch = 0;
210 }
211 
212 byte Counter::readIO(EmuTime::param time)
213 {
214  if (ltchCtrl) {
215  ltchCtrl = false;
216  return latchedControl;
217  }
218  advance(time);
219  word readData = ltchCntr ? latchedCounter : counter;
220  switch (control & WRT_FRMT) {
221  case WF_LATCH:
222  UNREACHABLE;
223  case WF_LOW:
224  ltchCntr = false;
225  return readData & 0x00FF;
226  case WF_HIGH:
227  ltchCntr = false;
228  return readData >> 8;
229  case WF_BOTH:
230  if (readOrder == LOW) {
231  readOrder = HIGH;
232  return readData & 0x00FF;
233  } else {
234  readOrder = LOW;
235  ltchCntr = false;
236  return readData >> 8;
237  }
238  default:
239  UNREACHABLE; return 0;
240  }
241 }
242 
243 byte Counter::peekIO(EmuTime::param time) const
244 {
245  if (ltchCtrl) {
246  return latchedControl;
247  }
248 
249  const_cast<Counter*>(this)->advance(time);
250 
251  word readData = ltchCntr ? latchedCounter : counter;
252  switch (control & WRT_FRMT) {
253  case WF_LATCH:
254  UNREACHABLE;
255  case WF_LOW:
256  return readData & 0x00FF;
257  case WF_HIGH:
258  return readData >> 8;
259  case WF_BOTH:
260  if (readOrder == LOW) {
261  return readData & 0x00FF;
262  } else {
263  return readData >> 8;
264  }
265  default:
266  UNREACHABLE; return 0;
267  }
268 }
269 
270 void Counter::writeIO(byte value, EmuTime::param time)
271 {
272  advance(time);
273  switch (control & WRT_FRMT) {
274  case WF_LATCH:
275  UNREACHABLE;
276  case WF_LOW:
277  writeLoad((counterLoad & 0xFF00) | value, time);
278  break;
279  case WF_HIGH:
280  writeLoad((counterLoad & 0x00FF) | (value << 8), time);
281  break;
282  case WF_BOTH:
283  if (writeOrder == LOW) {
284  writeOrder = HIGH;
285  writeLatch = value;
286  if ((control & CNTR_MODE) == CNTR_M0)
287  // pauze counting when in mode 0
288  counting = false;
289  } else {
290  writeOrder = LOW;
291  counting = true;
292  writeLoad((value << 8) | writeLatch, time);
293  }
294  break;
295  default:
296  UNREACHABLE;
297  }
298 }
299 void Counter::writeLoad(word value, EmuTime::param time)
300 {
301  counterLoad = value;
302  byte mode = control & CNTR_MODE;
303  if (mode == one_of(CNTR_M0, CNTR_M4)) {
304  counter = counterLoad;
305  }
306  if (!active && (mode == one_of(CNTR_M2, CNTR_M2_, CNTR_M3, 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 };
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
one_of.hh
openmsx::Counter::Counter
Counter(Scheduler &scheduler, ClockPinListener *listener, EmuTime::param time)
Definition: I8254.cc:183
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:174
openmsx::RB_COUNT
constexpr byte RB_COUNT
Definition: I8254.cc:17
openmsx::Scheduler
Definition: Scheduler.hh:34
openmsx::RB_CNTR0
constexpr byte RB_CNTR0
Definition: I8254.cc:13
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:37
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:87
openmsx::I8254::setGate
void setGate(unsigned cntr, bool status, EmuTime::param time)
Definition: I8254.cc:162
openmsx::I8254::peekIO
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:107
openmsx::I8254::I8254
I8254(Scheduler &scheduler, ClockPinListener *output0, ClockPinListener *output1, ClockPinListener *output2, EmuTime::param time)
Definition: I8254.cc:76
openmsx::ClockPin::getState
bool getState(EmuTime::param time) const
Definition: ClockPin.cc:58
openmsx::Counter::reset
void reset(EmuTime::param time)
Definition: I8254.cc:194
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::Counter::readIO
byte readIO(EmuTime::param time)
Definition: I8254.cc:212
openmsx::I8254::getClockPin
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:168
openmsx::I8254
Definition: I8254.hh:21
openmsx::READ_BACK
constexpr byte READ_BACK
Definition: I8254.cc:12
one_of
Definition: one_of.hh:7
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::Counter::HIGH
@ HIGH
Definition: I8254.cc:37
openmsx::Counter::peekIO
byte peekIO(EmuTime::param time) const
Definition: I8254.cc:243
openmsx::ClockPinListener
Definition: ClockPin.hh:13
openmsx::Counter::latchCounter
void latchCounter(EmuTime::param time)
Definition: I8254.cc:363
openmsx::I8254::serialize
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:538
openmsx::RB_CNTR2
constexpr byte RB_CNTR2
Definition: I8254.cc:15
EmuTime.hh
openmsx::Counter::setGateStatus
void setGateStatus(bool status, EmuTime::param time)
Definition: I8254.cc:373
openmsx::Counter::serialize
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:516
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:270
openmsx::ClockPin::isPeriodic
bool isPeriodic() const
Definition: ClockPin.hh:34
openmsx::Counter::LOW
@ LOW
Definition: I8254.cc:37
openmsx::I8254::~I8254
~I8254()
utf8::advance
void advance(octet_iterator &it, distance_type n, octet_iterator end)
Definition: utf8_checked.hh:186
unreachable.hh
openmsx::Counter::writeControlWord
void writeControlWord(byte value, EmuTime::param time)
Definition: I8254.cc:322
openmsx::RB_STATUS
constexpr byte RB_STATUS
Definition: I8254.cc:16
openmsx::Counter
Definition: I8254.cc:20
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:120
openmsx::Counter::latchStatus
void latchStatus(EmuTime::param time)
Definition: I8254.cc:353
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::RB_CNTR1
constexpr byte RB_CNTR1
Definition: I8254.cc:14
openmsx::I8254::readIO
byte readIO(word port, EmuTime::param time)
Definition: I8254.cc:94
openmsx::EmuDuration::param
const EmuDuration & param
Definition: EmuDuration.hh:27