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
12constexpr byte READ_BACK = 0xC0;
13constexpr byte RB_CNTR0 = 0x02;
14constexpr byte RB_CNTR1 = 0x04;
15constexpr byte RB_CNTR2 = 0x08;
16constexpr byte RB_STATUS = 0x10;
17constexpr 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
34void I8254::reset(EmuTime::param time)
35{
36 for (auto& c : counter) {
37 c.reset(time);
38 }
39}
40
41byte 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
54byte 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
67void 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
98void 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
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 , counter(0)
135 , counterLoad(0)
136 , gate(true)
137{
138 reset(time);
139}
140
141void 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
159byte 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:
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
190byte 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:
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
217void Counter::writeIO(byte value, EmuTime::param time)
218{
219 advance(time);
220 switch (control & WRT_FRMT) {
221 case WF_LATCH:
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:
244 }
245}
246void 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;
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
269void 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:
296 }
297 }
298}
299
300void 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
310void Counter::latchCounter(EmuTime::param time)
311{
312 advance(time);
313 if (!ltchCntr) {
314 ltchCntr = true;
315 readOrder = LOW;
316 latchedCounter = counter;
317 }
318}
319
320void 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;
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:
363 }
364 }
365}
366
367void 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:
452 }
453}
454
455
456static constexpr std::initializer_list<enum_string<Counter::ByteOrder>> byteOrderInfo = {
457 { "LOW", Counter::LOW },
458 { "HIGH", Counter::HIGH }
459};
461
462template<typename Archive>
463void 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
484template<typename Archive>
485void 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:28
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:1009
#define UNREACHABLE
Definition: unreachable.hh:38