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