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