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
10namespace openmsx {
11
12static constexpr uint8_t READ_BACK = 0xC0;
13static constexpr uint8_t RB_CNTR0 = 0x02;
14static constexpr uint8_t RB_CNTR1 = 0x04;
15static constexpr uint8_t RB_CNTR2 = 0x08;
16static constexpr uint8_t RB_STATUS = 0x10;
17static constexpr uint8_t 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
34void I8254::reset(EmuTime::param time)
35{
36 for (auto& c : counter) {
37 c.reset(time);
38 }
39}
40
41uint8_t I8254::readIO(uint16_t 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:
51 }
52}
53
54uint8_t I8254::peekIO(uint16_t 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:
64 }
65}
66
67void I8254::writeIO(uint16_t port, uint8_t 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
98void I8254::readBackHelper(uint8_t 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
109void 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 reset(time);
136}
137
138void Counter::reset(EmuTime::param time)
139{
140 currentTime = time;
141 ltchCtrl = false;
142 ltchCntr = false;
143 readOrder = ByteOrder::LOW;
144 writeOrder = ByteOrder::LOW;
145 control = 0x30; // Write BOTH / mode 0 / binary mode
146 active = false;
147 counting = true;
148 triggered = false;
149
150 // avoid UMR on savestate
151 latchedCounter = 0;
152 latchedControl = 0;
153 writeLatch = 0;
154}
155
156uint8_t Counter::readIO(EmuTime::param time)
157{
158 if (ltchCtrl) {
159 ltchCtrl = false;
160 return latchedControl;
161 }
162 advance(time);
163 uint16_t readData = ltchCntr ? latchedCounter : narrow_cast<uint16_t>(counter);
164 switch (control & WRT_FRMT) {
165 case WF_LATCH:
167 case WF_LOW:
168 ltchCntr = false;
169 return narrow_cast<uint8_t>(readData & 0x00FF);
170 case WF_HIGH:
171 ltchCntr = false;
172 return narrow_cast<uint8_t>(readData >> 8);
173 case WF_BOTH:
174 if (readOrder == ByteOrder::LOW) {
175 readOrder = ByteOrder::HIGH;
176 return narrow_cast<uint8_t>(readData & 0x00FF);
177 } else {
178 readOrder = ByteOrder::LOW;
179 ltchCntr = false;
180 return narrow_cast<uint8_t>(readData >> 8);
181 }
182 default:
184 }
185}
186
187uint8_t Counter::peekIO(EmuTime::param time) const
188{
189 if (ltchCtrl) {
190 return latchedControl;
191 }
192
193 const_cast<Counter*>(this)->advance(time);
194
195 uint16_t readData = ltchCntr ? latchedCounter : narrow_cast<uint16_t>(counter);
196 switch (control & WRT_FRMT) {
197 case WF_LATCH:
199 case WF_LOW:
200 return narrow_cast<uint8_t>(readData & 0x00FF);
201 case WF_HIGH:
202 return narrow_cast<uint8_t>(readData >> 8);
203 case WF_BOTH:
204 if (readOrder == ByteOrder::LOW) {
205 return narrow_cast<uint8_t>(readData & 0x00FF);
206 } else {
207 return narrow_cast<uint8_t>(readData >> 8);
208 }
209 default:
211 }
212}
213
214void Counter::writeIO(uint8_t value, EmuTime::param time)
215{
216 advance(time);
217 switch (control & WRT_FRMT) {
218 case WF_LATCH:
220 case WF_LOW:
221 writeLoad((counterLoad & 0xFF00) | uint16_t(value << 0), time);
222 break;
223 case WF_HIGH:
224 writeLoad((counterLoad & 0x00FF) | uint16_t(value << 8), time);
225 break;
226 case WF_BOTH:
227 if (writeOrder == ByteOrder::LOW) {
228 writeOrder = ByteOrder::HIGH;
229 writeLatch = value;
230 if ((control & CNTR_MODE) == CNTR_M0)
231 // pause counting when in mode 0
232 counting = false;
233 } else {
234 writeOrder = ByteOrder::LOW;
235 counting = true;
236 writeLoad(uint16_t((value << 8) | writeLatch), time);
237 }
238 break;
239 default:
241 }
242}
243void Counter::writeLoad(uint16_t value, EmuTime::param time)
244{
245 counterLoad = value;
246 uint8_t mode = control & CNTR_MODE;
247 if (mode == one_of(CNTR_M0, CNTR_M4)) {
248 counter = counterLoad;
249 }
250 if (!active && (mode == one_of(CNTR_M3, CNTR_M3_))) {
251 if (clock.isPeriodic()) {
252 counter = counterLoad;
253 int half = (counter + 1) / 2; // round up
254 EmuDuration tick = clock.getTotalDuration();
255 EmuDuration total = tick * counter;
256 EmuDuration high = tick * half;
257 output.setPeriodicState(total, high, time);
258 } else {
259 // TODO ??
260 }
261 }
262 if (!active && (mode == one_of(CNTR_M2, CNTR_M2_))) {
263 if (clock.isPeriodic()) {
264 counter = counterLoad;
265 EmuDuration high = clock.getTotalDuration();
266 EmuDuration total = high * counter;
267 output.setPeriodicState(total, high, time);
268 } else {
269 // TODO ??
270 }
271 }
272
273 if (mode == CNTR_M0) {
274 output.setState(false, time);
275 }
276 active = true; // counter is (re)armed after counter is initialized
277}
278
279void Counter::writeControlWord(uint8_t value, EmuTime::param time)
280{
281 advance(time);
282 if ((value & WRT_FRMT) == 0) {
283 // counter latch command
284 latchCounter(time);
285 return;
286 } else {
287 // new control mode
288 control = value;
289 writeOrder = ByteOrder::LOW;
290 counting = true;
291 active = false;
292 triggered = false;
293 switch (control & CNTR_MODE) {
294 case CNTR_M0:
295 output.setState(false, time);
296 break;
297 case CNTR_M1:
298 case CNTR_M2: case CNTR_M2_:
299 case CNTR_M3: case CNTR_M3_:
300 case CNTR_M4:
301 case CNTR_M5:
302 output.setState(true, time);
303 break;
304 default:
306 }
307 }
308}
309
310void Counter::latchStatus(EmuTime::param time)
311{
312 advance(time);
313 if (!ltchCtrl) {
314 ltchCtrl = true;
315 uint8_t out = output.getState(time) ? 0x80 : 0;
316 latchedControl = out | control; // TODO bit 6 null-count
317 }
318}
319
320void Counter::latchCounter(EmuTime::param time)
321{
322 advance(time);
323 if (!ltchCntr) {
324 ltchCntr = true;
325 readOrder = ByteOrder::LOW;
326 latchedCounter = narrow_cast<uint16_t>(counter);
327 }
328}
329
330void Counter::setGateStatus(bool newStatus, EmuTime::param time)
331{
332 advance(time);
333 if (gate != newStatus) {
334 gate = newStatus;
335 switch (control & CNTR_MODE) {
336 case CNTR_M0:
337 case CNTR_M4:
338 // Gate does not influence output, it just acts as a count enable
339 // nothing needs to be done
340 break;
341 case CNTR_M1:
342 if (gate && active) {
343 // rising edge
344 counter = counterLoad;
345 output.setState(false, time);
346 triggered = true;
347 }
348 break;
349 case CNTR_M2: case CNTR_M2_:
350 case CNTR_M3: case CNTR_M3_:
351 if (gate) {
352 if (clock.isPeriodic()) {
353 counter = counterLoad;
355 EmuDuration total = high * counter;
356 output.setPeriodicState(total, high, time);
357 } else {
358 // TODO ???
359 }
360 } else {
361 output.setState(true, time);
362 }
363 break;
364 case CNTR_M5:
365 if (gate && active) {
366 // rising edge
367 counter = counterLoad;
368 triggered = true;
369 }
370 break;
371 default:
373 }
374 }
375}
376
377void Counter::advance(EmuTime::param time)
378{
379 // TODO !!!! Set SP !!!!
380 // TODO BCD counting
381 unsigned ticks = clock.getTicksBetween(currentTime, time);
382 currentTime = time;
383 switch (control & CNTR_MODE) {
384 case CNTR_M0:
385 if (gate && counting) {
386 counter -= ticks;
387 if (counter < 0) {
388 counter &= 0xFFFF;
389 if (active) {
390 output.setState(false, time);
391 active = false; // not periodic
392 }
393 }
394 }
395 break;
396 case CNTR_M1:
397 counter -= ticks;
398 if (triggered && (counter < 0)) {
399 output.setState(true, time);
400 triggered = false; // not periodic
401 }
402 counter &= 0xFFFF;
403 break;
404 case CNTR_M2:
405 case CNTR_M2_:
406 if (gate) {
407 counter -= ticks;
408 if (active) {
409 // TODO not completely correct
410 if (counterLoad != 0) {
411 counter %= counterLoad;
412 } else {
413 counter = 0;
414 }
415 }
416 }
417 break;
418 case CNTR_M3:
419 case CNTR_M3_:
420 if (gate) {
421 counter -= 2 * ticks;
422 if (active) {
423 // TODO not correct
424 if (counterLoad != 0) {
425 counter %= counterLoad;
426 } else {
427 counter = 0;
428 }
429 }
430 }
431 break;
432 case CNTR_M4:
433 if (gate) {
434 counter -= ticks;
435 if (active) {
436 if (counter == 0) {
437 output.setState(false, time);
438 } else if (counter < 0) {
439 output.setState(true, time);
440 active = false; // not periodic
441 }
442 }
443 counter &= 0xFFFF;
444 }
445 break;
446 case CNTR_M5:
447 counter -= ticks;
448 if (triggered) {
449 if (counter == 0)
450 output.setState(false, time);
451 if (counter < 0) {
452 output.setState(true, time);
453 triggered = false; // not periodic
454 }
455 }
456 counter &= 0xFFFF;
457 break;
458 default:
460 }
461}
462
463
464static constexpr std::initializer_list<enum_string<Counter::ByteOrder>> byteOrderInfo = {
465 { "LOW", Counter::ByteOrder::LOW },
466 { "HIGH", Counter::ByteOrder::HIGH }
467};
469
470template<typename Archive>
471void Counter::serialize(Archive& ar, unsigned /*version*/)
472{
473 ar.serialize("clock", clock,
474 "output", output,
475 "currentTime", currentTime,
476 "counter", counter,
477 "latchedCounter", latchedCounter,
478 "counterLoad", counterLoad,
479 "control", control,
480 "latchedControl", latchedControl,
481 "ltchCtrl", ltchCtrl,
482 "ltchCntr", ltchCntr,
483 "readOrder", readOrder,
484 "writeOrder", writeOrder,
485 "writeLatch", writeLatch,
486 "gate", gate,
487 "active", active,
488 "triggered", triggered,
489 "counting", counting);
490}
491
492template<typename Archive>
493void I8254::serialize(Archive& ar, unsigned /*version*/)
494{
495 std::array<char, 9> tag = {'c', 'o', 'u', 'n', 't', 'e', 'r', 'X', 0};
496 for (auto [i, cntr] : enumerate(counter)) {
497 tag[7] = char('0' + i);
498 ar.serialize(tag.data(), cntr);
499 }
500}
502
503} // namespace openmsx
bool isPeriodic() const
Definition ClockPin.hh:34
unsigned getTicksBetween(EmuTime::param begin, EmuTime::param end) const
Definition ClockPin.cc:76
void setState(bool status, EmuTime::param time)
Definition ClockPin.cc:13
bool getState(EmuTime::param time) const
Definition ClockPin.cc:55
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition ClockPin.cc:33
EmuDuration::param getTotalDuration() const
Definition ClockPin.cc:64
void latchStatus(EmuTime::param time)
Definition I8254.cc:310
uint8_t peekIO(EmuTime::param time) const
Definition I8254.cc:187
void serialize(Archive &ar, unsigned version)
Definition I8254.cc:471
void reset(EmuTime::param time)
Definition I8254.cc:138
void writeIO(uint8_t value, EmuTime::param time)
Definition I8254.cc:214
uint8_t readIO(EmuTime::param time)
Definition I8254.cc:156
void latchCounter(EmuTime::param time)
Definition I8254.cc:320
void writeControlWord(uint8_t value, EmuTime::param time)
Definition I8254.cc:279
void setGateStatus(bool status, EmuTime::param time)
Definition I8254.cc:330
Counter(Scheduler &scheduler, ClockPinListener *listener, EmuTime::param time)
Definition I8254.cc:130
void reset(EmuTime::param time)
Definition I8254.cc:34
uint8_t peekIO(uint16_t port, EmuTime::param time) const
Definition I8254.cc:54
void writeIO(uint16_t port, uint8_t value, EmuTime::param time)
Definition I8254.cc:67
uint8_t readIO(uint16_t 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:493
void setGate(unsigned cntr, bool status, EmuTime::param time)
Definition I8254.cc:109
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:11
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
#define UNREACHABLE