OXYGENHUB
◆ main x16-emulator / src / via.c
8.7 KiB · C · last changed chore(): initial commit 15 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();
276
			if (!debug) via_clear_pra_irqs(&via[0]);
277
			if (via[0].registers[11] & 1) {
278
				// CA1 is currently not connected to anything (?)
279
				return 0;
280
			} else {
281
				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...
282
					(via[0].registers[3] & i2c_port.data_in) |						//I2C Data: PA0=1 if DDR bit is 1 (output) and data_in is 1
283
					(~via[0].registers[3] & I2C_CLK_MASK ) |						//I2C Clock: PA1=1 if DDR bit is 0 (input), simulating an input pull-up
284
					(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 
285
					Joystick_data;
286
			}
287
288
		default:
289
			return via_read(&via[0], reg, debug);
290
	}
291
}
292
293
void
294
via1_write(uint8_t reg, uint8_t value)
295
{
296
	via_write(&via[0], reg, value);
297
	if (reg == 0 || reg == 2) {
298
		// PB
299
		const uint8_t pb = via[0].registers[0] | ~via[0].registers[2];
300
		serial_port.in.atn = (pb & SERIAL_ATNIN_MASK) != 0;
301
		serial_port.in.clk = (pb & SERIAL_CLOCKIN_MASK) == 0;
302
		serial_port.in.data = (pb & SERIAL_DATAIN_MASK) == 0;
303
304
	} else if (reg == 1 || reg == 3) {
305
		i2c_step();
306
		// PA
307
		const uint8_t pa = via[0].registers[1] | ~via[0].registers[3];
308
		i2c_port.data_in = pa & I2C_DATA_MASK;										//Sets data_in = 1 if the corresponding DDR bit is 0 (input), simulates a pull-up
309
		i2c_port.clk_in = (pa & I2C_CLK_MASK) >> 1;									//Sets clk_in = 1 if pin is an input, simulates a pull-up
310
		joystick_set_latch(via[0].registers[1] & JOY_LATCH_MASK);
311
		joystick_set_clock(via[0].registers[1] & JOY_CLK_MASK);
312
	}
313
}
314
315
void
316
via1_step(unsigned clocks)
317
{
318
	via_step(&via[0], clocks);
319
}
320
321
bool
322
via1_irq()
323
{
324
	return (via[0].registers[13] & via[0].registers[14]) != 0;
325
}
326
327
//
328
// VIA#2
329
//
330
// PA/PB: user port
331
// for now, just assume that all user ports are not connected
332
// and reads return output register (open bus behavior)
333
334
void
335
via2_init()
336
{
337
	via_init(&via[1]);
338
}
339
340
uint8_t
341
via2_read(uint8_t reg, bool debug)
342
{
343
	return via_read(&via[1], reg, debug);
344
}
345
346
void
347
via2_write(uint8_t reg, uint8_t value)
348
{
349
	via_write(&via[1], reg, value);
350
}
351
352
void
353
via2_step(unsigned clocks)
354
{
355
	via_step(&via[1], clocks);
356
}
357
358
bool
359
via2_irq()
360
{
361
	return (via[1].registers[13] & via[1].registers[14]) != 0;
362
}