OXYGENHUB
◆ main enhanced-x16-emulator / src / via.c
9.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2019 Michael Steil
3
// All rights reserved. License: 2-clause BSD
4
5
#include "via.h"
6
#include "i2c.h"
7
#include "memory.h"
8
#include "serial.h"
9
#include <stdio.h>
10
#include <stdlib.h>
11
#include <time.h>
12
#include <string.h>
13
//XXX
14
#include "glue.h"
15
#include "joystick.h"
16
17
typedef struct {
18
	unsigned timer_count[2];
19
	unsigned pb6_pulse_counts;
20
	uint8_t registers[15];
21
	bool timer1_m1;
22
	bool timer_running[2];
23
	bool pb7_output;
24
} via_t;
25
26
static via_t via[2];
27
28
// only internal logic is handled here, see via1/2 calls for external
29
// operations specific to each unit
30
31
static void
32
via_init(via_t *via)
33
{
34
	// timer latches, timer counters and SR are not cleared
35
	for (int i = 0; i < 4; i++) via->registers[i] = 0;
36
	for (int i = 11; i < 15; i++) via->registers[i] = 0;
37
	via->timer_running[0] = false;
38
	via->timer_running[1] = false;
39
	via->timer1_m1 = false;
40
	via->pb7_output = true;
41
}
42
43
static void
44
via_clear_pra_irqs(via_t *via)
45
{
46
	via->registers[13] &= ~0x02;
47
	if ((via->registers[12] & 0b00001010) != 0b00000010) {
48
		via->registers[13] &= ~0x01;
49
	}
50
}
51
52
static void
53
via_clear_prb_irqs(via_t *via)
54
{
55
	via->registers[13] &= ~0x10;
56
	if ((via->registers[12] & 0b10100000) != 0b00100000) {
57
		via->registers[13] &= ~0x08;
58
	}
59
}
60
61
static uint8_t
62
via_read(via_t *via, uint8_t reg, bool debug)
63
{
64
	uint8_t ifr;
65
	bool    irq;
66
	switch (reg) {
67
		case 0: // IRB
68
			if (!debug) via_clear_prb_irqs(via);
69
			return via->registers[0];
70
		case 1: // IRA
71
		case 15:
72
			if (!debug) via_clear_pra_irqs(via);
73
			return via->registers[1];
74
		case 4: // T1L
75
			if (!debug) via->registers[13] &= ~0x40;
76
			return (uint8_t)(via->timer_count[0] & 0xff);
77
		case 5: // T1H
78
			return (uint8_t)(via->timer_count[0] >> 8);
79
		case 8: // T2L
80
			if (!debug) via->registers[13] &= ~0x20;
81
			return (uint8_t)(via->timer_count[1] & 0xff);
82
		case 9: // T2H
83
			return (uint8_t)(via->timer_count[1] >> 8);
84
		case 10: // SR
85
			if (!debug) via->registers[13] &= ~0x04;
86
			return via->registers[10];
87
		case 13: // IFR
88
			ifr = via->registers[13];
89
			irq = (ifr & via->registers[14]) != 0;
90
			return ((uint8_t)irq << 7) | ifr;
91
		case 14: // IER
92
			return via->registers[14] | 0x80;
93
		default:
94
			return via->registers[reg];
95
	}
96
}
97
98
static void
99
via_write(via_t *via, uint8_t reg, uint8_t value)
100
{
101
	switch (reg) {
102
		case 0: // ORB
103
			via_clear_prb_irqs(via);
104
			via->registers[0] = value;
105
			break;
106
		case 1: // ORA
107
		case 15:
108
			via_clear_pra_irqs(via);
109
			via->registers[1] = value;
110
			break;
111
		case 4: // T1L
112
			via->registers[6] = value;
113
			break;
114
		case 5: // T1H
115
		case 7: // T1LH
116
			via->registers[13] &= ~0x40;
117
			via->registers[7] = value;
118
			if (reg == 5) {
119
				via->timer_count[0] = ((unsigned)value << 8) | via->registers[6];
120
				via->timer_running[0] = true;
121
				via->pb7_output = false;
122
			}
123
			break;
124
		case 9: // T2H
125
			via->registers[13] &= ~0x20;
126
			via->timer_count[1] = ((unsigned)value << 8) | via->registers[8];
127
			via->timer_running[1] = true;
128
			break;
129
		case 10: // SR
130
			via->registers[13] &= ~0x04;
131
			via->registers[10] = value;
132
			break;
133
		case 13: // IFR
134
			via->registers[13] &= ~(value & 0x7f);
135
			break;
136
		case 14: // IER
137
			if (value & 0x80) {
138
				via->registers[14] |= value & 0x7f;
139
			} else {
140
				via->registers[14] &= ~value & 0x7f;
141
			}
142
			break;
143
		default:
144
			via->registers[reg] = value;
145
	}
146
}
147
148
static void
149
via_step(via_t *via, unsigned clocks)
150
{
151
	// TODO there's currently no timestamp mechanism to mark exact transition
152
	// times, since there's currently no peripherals that require those
153
	uint8_t acr = via->registers[11];
154
	uint8_t ifr = via->registers[13];
155
	// handle timers
156
	unsigned cnt;
157
	uint32_t tclk, tclk_s, reload;
158
	// counter always update even if it's not "running"
159
	cnt = via->timer_count[0];
160
	tclk = clocks;
161
	while (tclk > 0) {
162
		if (via->timer1_m1) {
163
			reload = (((uint32_t)via->registers[7] << 8) | via->registers[6]);
164
			tclk_s = reload + 1;
165
			if (tclk < tclk_s) tclk_s = tclk;
166
			cnt = reload - tclk_s + 1;
167
			via->timer1_m1 = false;
168
		} else if (cnt < tclk) {
169
			if (via->timer_running[0]) {
170
				ifr |= 0x40;
171
				via->pb7_output ^= true;
172
				if (!(acr & 0x40)) via->timer_running[0] = false;
173
			}
174
			if (tclk - cnt == 1) {
175
				// special, -1 state
176
				cnt = 0xffff;
177
				via->timer1_m1 = true;
178
				tclk_s = 1;
179
			} else {
180
				reload = (((uint32_t)via->registers[7] << 8) | via->registers[6]);
181
				tclk_s = cnt + reload + 2;
182
				if (tclk < tclk_s) tclk_s = tclk;
183
				cnt += reload - tclk_s + 2;
184
			}
185
		} else {
186
			cnt -= tclk;
187
			break;
188
		}
189
		tclk -= tclk_s;
190
	}
191
	via->timer_count[0] = (unsigned)cnt;
192
193
	cnt = via->timer_count[1];
194
	tclk = (acr & 0x20) ? via->pb6_pulse_counts : clocks;
195
	via->pb6_pulse_counts = 0;
196
	if (cnt < tclk) {
197
		if (via->timer_running[1]) {
198
			ifr |= 0x20;
199
			via->timer_running[1] = false;
200
		}
201
		via->timer_count[1] = 0x10000 + cnt - tclk;
202
	} else {
203
		via->timer_count[1] -= tclk;
204
	}
205
206
	// TODO Cxx pin and shift register handling
207
	
208
	via->registers[13] = ifr;
209
}
210
211
//
212
// VIA#1
213
//
214
// PA0: I2CDATA   I2C DATA
215
// PA1: I2CCLK    I2C CLK
216
// PA2: NESLATCH  NES LATCH (for all controllers)
217
// PA3: NESCLK    NES CLK   (for all controllers)
218
// PA4: NESDAT3   NES DATA  (controller 3)
219
// PA5: NESDAT2   NES DATA  (controller 2)
220
// PA6: NESDAT1   NES DATA  (controller 1)
221
// PA7: NESDAT0   NES DATA  (controller 0)
222
// PB0: -         Unused
223
// PB1: -         Unused
224
// PB2: -         Unused
225
// PB3: IECATTO   Serial ATN  out
226
// PB4: IECCLKO   Serial CLK  out
227
// PB5: IECDATAO  Serial DATA out
228
// PB6: IECCLKI   Serial CLK  in
229
// PB7: IECDATAI  Serial DATA in
230
// CA1: -         Unused
231
// CA2: -         Unused
232
// CB1: IECSRQ
233
// CB2: -         Unused
234
235
void
236
via1_init()
237
{
238
	via_init(&via[0]);
239
	i2c_port.clk_in = 1;
240
	serial_port.in.atn = 0;
241
	serial_port.in.clk = 0;
242
	serial_port.in.data = 0;
243
	serial_port.out.clk = 1;
244
	serial_port.out.data = 1;
245
}
246
247
uint8_t
248
via1_read(uint8_t reg, bool debug)
249
{
250
	// DDR=0 (input)  -> take input bit
251
	// DDR=1 (output) -> take output bit
252
	// For now, just assume that I2C peripherals always drive all lines and VIA
253
	// overrides them with their input values based on current DDR bits
254
	switch (reg) {
255
		case 0: // PB
256
			if (!debug) via_clear_prb_irqs(&via[0]);
257
			if (via[0].registers[11] & 2) {
258
				// TODO latching mechanism (requires IEC implementation)
259
				return 0;
260
			} else {
261
				return
262
					(~via[0].registers[2] & (
263
						(serial_port_read_clk() << 6) |
264
						(serial_port_read_data() << 7)
265
					)) |
266
					(via[0].registers[2] & (
267
						(serial_port.in.atn << 3) |
268
						((!serial_port.in.clk) << 4) |
269
						((!serial_port.in.data) << 5)
270
					));
271
			}
272
			
273
		case 1: // PA
274
		case 15:
275
			// i2c_step() advances the bus state machine, so it has to sit behind
276
			// the debug check: reading $9F01 in a memory view shouldn't disturb
277
			// a transfer in progress. That bus talks to the SMC and the RTC.
278
			if (!debug) {
279
				i2c_step();
280
				via_clear_pra_irqs(&via[0]);
281
			}
282
			if (via[0].registers[11] & 1) {
283
				// CA1 is currently not connected to anything (?)
284
				return 0;
285
			} else {
286
				return (~via[0].registers[3] & i2c_port.data_out) |					//I2C Data: PA0=1 if DDR bit is 0 (input) and data_out is 1; usage of data_out and data_in is a bit confusing...
287
					(via[0].registers[3] & i2c_port.data_in) |						//I2C Data: PA0=1 if DDR bit is 1 (output) and data_in is 1
288
					(~via[0].registers[3] & I2C_CLK_MASK ) |						//I2C Clock: PA1=1 if DDR bit is 0 (input), simulating an input pull-up
289
					(via[0].registers[3] & i2c_port.clk_in) |						//I2C Clock: PA1=1 if DDR bit is 1 (output) and clk_in is 1, simulating a pin driven by the VIA 
290
					Joystick_data;
291
			}
292
293
		default:
294
			return via_read(&via[0], reg, debug);
295
	}
296
}
297
298
void
299
via1_write(uint8_t reg, uint8_t value)
300
{
301
	via_write(&via[0], reg, value);
302
	if (reg == 0 || reg == 2) {
303
		// PB
304
		const uint8_t pb = via[0].registers[0] | ~via[0].registers[2];
305
		serial_port.in.atn = (pb & SERIAL_ATNIN_MASK) != 0;
306
		serial_port.in.clk = (pb & SERIAL_CLOCKIN_MASK) == 0;
307
		serial_port.in.data = (pb & SERIAL_DATAIN_MASK) == 0;
308
309
	} else if (reg == 1 || reg == 3) {
310
		i2c_step();
311
		// PA
312
		const uint8_t pa = via[0].registers[1] | ~via[0].registers[3];
313
		i2c_port.data_in = pa & I2C_DATA_MASK;										//Sets data_in = 1 if the corresponding DDR bit is 0 (input), simulates a pull-up
314
		i2c_port.clk_in = (pa & I2C_CLK_MASK) >> 1;									//Sets clk_in = 1 if pin is an input, simulates a pull-up
315
		joystick_set_latch(via[0].registers[1] & JOY_LATCH_MASK);
316
		joystick_set_clock(via[0].registers[1] & JOY_CLK_MASK);
317
	}
318
}
319
320
void
321
via1_step(unsigned clocks)
322
{
323
	via_step(&via[0], clocks);
324
}
325
326
bool
327
via1_irq()
328
{
329
	return (via[0].registers[13] & via[0].registers[14]) != 0;
330
}
331
332
//
333
// VIA#2
334
//
335
// PA/PB: user port
336
// for now, just assume that all user ports are not connected
337
// and reads return output register (open bus behavior)
338
339
void
340
via2_init()
341
{
342
	via_init(&via[1]);
343
}
344
345
uint8_t
346
via2_read(uint8_t reg, bool debug)
347
{
348
	return via_read(&via[1], reg, debug);
349
}
350
351
void
352
via2_write(uint8_t reg, uint8_t value)
353
{
354
	via_write(&via[1], reg, value);
355
}
356
357
void
358
via2_step(unsigned clocks)
359
{
360
	via_step(&via[1], clocks);
361
}
362
363
bool
364
via2_irq()
365
{
366
	return (via[1].registers[13] & via[1].registers[14]) != 0;
367
}
368
369
// Side-effect-free snapshot of one VIA's state for the ImGui debugger.
370
// `which` is 0 for VIA1, 1 for VIA2.
371
void
372
via_debug_get_state(int which, via_debug_state_t *out)
373
{
374
	if (which < 0 || which > 1) {
375
		return;
376
	}
377
	const via_t *v = &via[which];
378
	for (int i = 0; i < 15; i++) {
379
		out->regs[i] = v->registers[i];
380
	}
381
	out->timer_count[0]   = (uint16_t)v->timer_count[0];
382
	out->timer_count[1]   = (uint16_t)v->timer_count[1];
383
	out->timer_latch[0]   = ((uint16_t)v->registers[7] << 8) | v->registers[6];
384
	out->timer_latch[1]   = v->registers[8]; // T2 only has latch low byte
385
	out->timer_running[0] = v->timer_running[0];
386
	out->timer_running[1] = v->timer_running[1];
387
	out->timer1_m1        = v->timer1_m1;
388
	out->pb7_output       = v->pb7_output;
389
}