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:
50 UNREACHABLE; return 0;
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:
63 UNREACHABLE; return 0;
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 = LOW;
144 writeOrder = 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 == LOW) {
175 readOrder = HIGH;
176 return narrow_cast<uint8_t>(readData & 0x00FF);
177 } else {
178 readOrder = LOW;
179 ltchCntr = false;
180 return narrow_cast<uint8_t>(readData >> 8);
181 }
182 default:
183 UNREACHABLE; return 0;
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 == LOW) {
205 return narrow_cast<uint8_t>(readData & 0x00FF);
206 } else {
207 return narrow_cast<uint8_t>(readData >> 8);
208 }
209 default:
210 UNREACHABLE; return 0;
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 == LOW) {
228 writeOrder = 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 = 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_M2, CNTR_M2_, CNTR_M3, CNTR_M3_))) {
251 if (clock.isPeriodic()) {
252 counter = counterLoad;
254 EmuDuration total = high * counter;
255 output.setPeriodicState(total, high, time);
256 } else {
257 // TODO ??
258 }
259 }
260 if (mode == CNTR_M0) {
261 output.setState(false, time);
262 }
263 active = true; // counter is (re)armed after counter is initialized
264}
265
266void Counter::writeControlWord(uint8_t value, EmuTime::param time)
267{
268 advance(time);
269 if ((value & WRT_FRMT) == 0) {
270 // counter latch command
271 latchCounter(time);
272 return;
273 } else {
274 // new control mode
275 control = value;
276 writeOrder = LOW;
277 counting = true;
278 active = false;
279 triggered = false;
280 switch (control & CNTR_MODE) {
281 case CNTR_M0:
282 output.setState(false, time);
283 break;
284 case CNTR_M1:
285 case CNTR_M2: case CNTR_M2_:
286 case CNTR_M3: case CNTR_M3_:
287 case CNTR_M4:
288 case CNTR_M5:
289 output.setState(true, time);
290 break;
291 default:
293 }
294 }
295}
296
297void Counter::latchStatus(EmuTime::param time)
298{
299 advance(time);
300 if (!ltchCtrl) {
301 ltchCtrl = true;
302 uint8_t out = output.getState(time) ? 0x80 : 0;
303 latchedControl = out | control; // TODO bit 6 null-count
304 }
305}
306
307void Counter::latchCounter(EmuTime::param time)
308{
309 advance(time);
310 if (!ltchCntr) {
311 ltchCntr = true;
312 readOrder = LOW;
313 latchedCounter = narrow_cast<uint16_t>(counter);
314 }
315}
316
317void Counter::setGateStatus(bool newStatus, EmuTime::param time)
318{
319 advance(time);
320 if (gate != newStatus) {
321 gate = newStatus;
322 switch (control & CNTR_MODE) {
323 case CNTR_M0:
324 case CNTR_M4:
325 // Gate does not influence output, it just acts as a count enable
326 // nothing needs to be done
327 break;
328 case CNTR_M1:
329 if (gate && active) {
330 // rising edge
331 counter = counterLoad;
332 output.setState(false, time);
333 triggered = true;
334 }
335 break;
336 case CNTR_M2: case CNTR_M2_:
337 case CNTR_M3: case CNTR_M3_:
338 if (gate) {
339 if (clock.isPeriodic()) {
340 counter = counterLoad;
342 EmuDuration total = high * counter;
343 output.setPeriodicState(total, high, time);
344 } else {
345 // TODO ???
346 }
347 } else {
348 output.setState(true, time);
349 }
350 break;
351 case CNTR_M5:
352 if (gate && active) {
353 // rising edge
354 counter = counterLoad;
355 triggered = true;
356 }
357 break;
358 default:
360 }
361 }
362}
363
364void Counter::advance(EmuTime::param time)
365{
366 // TODO !!!! Set SP !!!!
367 // TODO BCD counting
368 int ticks = clock.getTicksBetween(currentTime, time);
369 currentTime = time;
370 switch (control & CNTR_MODE) {
371 case CNTR_M0:
372 if (gate && counting) {
373 counter -= ticks;
374 if (counter < 0) {
375 counter &= 0xFFFF;
376 if (active) {
377 output.setState(false, time);
378 active = false; // not periodic
379 }
380 }
381 }
382 break;
383 case CNTR_M1:
384 counter -= ticks;
385 if (triggered) {
386 if (counter < 0) {
387 output.setState(true, time);
388 triggered = false; // not periodic
389 }
390 }
391 counter &= 0xFFFF;
392 break;
393 case CNTR_M2:
394 case CNTR_M2_:
395 if (gate) {
396 counter -= ticks;
397 if (active) {
398 // TODO not completely correct
399 if (counterLoad != 0) {
400 counter %= counterLoad;
401 } else {
402 counter = 0;
403 }
404 }
405 }
406 break;
407 case CNTR_M3:
408 case CNTR_M3_:
409 if (gate) {
410 counter -= 2 * ticks;
411 if (active) {
412 // TODO not correct
413 if (counterLoad != 0) {
414 counter %= counterLoad;
415 } else {
416 counter = 0;
417 }
418 }
419 }
420 break;
421 case CNTR_M4:
422 if (gate) {
423 counter -= ticks;
424 if (active) {
425 if (counter == 0) {
426 output.setState(false, time);
427 } else if (counter < 0) {
428 output.setState(true, time);
429 active = false; // not periodic
430 }
431 }
432 counter &= 0xFFFF;
433 }
434 break;
435 case CNTR_M5:
436 counter -= ticks;
437 if (triggered) {
438 if (counter == 0)
439 output.setState(false, time);
440 if (counter < 0) {
441 output.setState(true, time);
442 triggered = false; // not periodic
443 }
444 }
445 counter &= 0xFFFF;
446 break;
447 default:
449 }
450}
451
452
453static constexpr std::initializer_list<enum_string<Counter::ByteOrder>> byteOrderInfo = {
454 { "LOW", Counter::LOW },
455 { "HIGH", Counter::HIGH }
456};
458
459template<typename Archive>
460void Counter::serialize(Archive& ar, unsigned /*version*/)
461{
462 ar.serialize("clock", clock,
463 "output", output,
464 "currentTime", currentTime,
465 "counter", counter,
466 "latchedCounter", latchedCounter,
467 "counterLoad", counterLoad,
468 "control", control,
469 "latchedControl", latchedControl,
470 "ltchCtrl", ltchCtrl,
471 "ltchCntr", ltchCntr,
472 "readOrder", readOrder,
473 "writeOrder", writeOrder,
474 "writeLatch", writeLatch,
475 "gate", gate,
476 "active", active,
477 "triggered", triggered,
478 "counting", counting);
479}
480
481template<typename Archive>
482void I8254::serialize(Archive& ar, unsigned /*version*/)
483{
484 std::array<char, 9> tag = {'c', 'o', 'u', 'n', 't', 'e', 'r', 'X', 0};
485 for (auto [i, cntr] : enumerate(counter)) {
486 tag[7] = char('0' + i);
487 ar.serialize(tag.data(), cntr);
488 }
489}
491
492} // namespace openmsx
Definition: one_of.hh:7
bool isPeriodic() const
Definition: ClockPin.hh:34
void setState(bool status, EmuTime::param time)
Definition: ClockPin.cc:13
int getTicksBetween(EmuTime::param begin, EmuTime::param end) const
Definition: ClockPin.cc:76
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:297
uint8_t peekIO(EmuTime::param time) const
Definition: I8254.cc:187
void serialize(Archive &ar, unsigned version)
Definition: I8254.cc:460
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:307
void writeControlWord(uint8_t value, EmuTime::param time)
Definition: I8254.cc:266
void setGateStatus(bool status, EmuTime::param time)
Definition: I8254.cc:317
Counter(Scheduler &scheduler, ClockPinListener *listener, EmuTime::param time)
Definition: I8254.cc:130
const EmuDuration & param
Definition: EmuDuration.hh:30
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:482
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:9
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1021
#define UNREACHABLE
Definition: unreachable.hh:38