OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_via_irq.c
9.5 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// The 65C22's interrupt flags and enables: IFR ($0D) and IER ($0E).
2
//
3
// Two VIAs sit at $9F00 and $9F10. The interrupt logic is shared between them
4
// and is entirely internal to the chip -- no timing model, no peripherals --
5
// which makes it the part of via.c that can be pinned exactly.
6
//
7
// The tests drive VIA 2. via2_read() and via2_write() pass straight through to
8
// the generic implementation both VIAs use (via.c:345-355), while VIA 1's
9
// ports are wired to I2C, the IEC bus and the NES controllers. Driving the
10
// user port exercises the same registers with none of that attached. VIA 1 is
11
// used only to show the two do not share state.
12
//
13
// ORACLE: C -- WDC W65C22S datasheet, pages 26-27, quoted inline. Fetch it
14
// with `python tests/refs/fetch_refs.py`; the text extraction carries
15
// `===== page N =====` markers so a citation can be found again.
16
//
17
// NOT COVERED, because via.c cannot produce these flags at all: IFR bits 0, 1,
18
// 3 and 4 (CA2, CA1, CB2, CB1) and bit 2 (shift register). via.c:206 leaves
19
// "Cxx pin and shift register handling" as a TODO, so nothing sets them and no
20
// test can arrange for them to be set. The code that clears them, including
21
// the PCR "independent input" exception at via.c:47 and via.c:56, is therefore
22
// unreachable from outside. That is a gap in the emulator, not in this file,
23
// and it is recorded rather than asserted around.
24
25
#include "support/harness.h"
26
27
#include "via.h"
28
29
#include <stdint.h>
30
31
#define VIA_ORB   0
32
#define VIA_ORA   1
33
#define VIA_T1C_L 4
34
#define VIA_T1C_H 5
35
#define VIA_T1L_L 6
36
#define VIA_T1L_H 7
37
#define VIA_T2C_L 8
38
#define VIA_T2C_H 9
39
#define VIA_SR    10
40
#define VIA_ACR   11
41
#define VIA_PCR   12
42
#define VIA_IFR   13
43
#define VIA_IER   14
44
45
#define IFR_CA2  0x01
46
#define IFR_CA1  0x02
47
#define IFR_SR   0x04
48
#define IFR_CB2  0x08
49
#define IFR_CB1  0x10
50
#define IFR_T2   0x20
51
#define IFR_T1   0x40
52
#define IFR_IRQ  0x80
53
54
// IER bit 7 selects between setting and clearing, so it is written rather than
55
// stored. Named so the intent of a write is legible.
56
#define IER_SET   0x80
57
#define IER_CLEAR 0x00
58
59
static uint8_t
60
ifr(void)
61
{
62
	return via2_read(VIA_IFR, true);
63
}
64
65
static uint8_t
66
ier(void)
67
{
68
	return via2_read(VIA_IER, true);
69
}
70
71
// The only flags via.c can raise are the two timers. T1 in one-shot mode times
72
// out once and sets its flag; counting past it is enough.
73
static void
74
raise_t1(void)
75
{
76
	via2_write(VIA_ACR, 0x00);      // one-shot, not free-running
77
	via2_write(VIA_T1L_L, 0x10);
78
	via2_write(VIA_T1C_H, 0x00);    // starts T1 at 0x0010
79
	via2_step(0x40);
80
}
81
82
static void
83
raise_t2(void)
84
{
85
	via2_write(VIA_ACR, 0x00);      // count clocks, not PB6 pulses
86
	via2_write(VIA_T2C_L, 0x10);
87
	via2_write(VIA_T2C_H, 0x00);    // starts T2 at 0x0010
88
	via2_step(0x40);
89
}
90
91
// p26: "The microprocessor can set or clear selected bits within the IER. This
92
//       allows the control of individual interrupts without affecting others.
93
//       To set or clear a particular Interrupt Enable bit, the microprocessor
94
//       must write to the IER address. During this write operation, if IER7 is
95
//       a Logic 0, each Logic 1 in IER6 thru IER0 will clear the corresponding
96
//       bit in the IER. For each Logic 0 in IER6 thru IER0, the corresponding
97
//       bit in the IER will be unaffected."
98
//
99
// Table 2-12 note 3: "If a read of this register is done, bit 7 will be Logic 1
100
//       and all other bits will reflect their enable/disable state."
101
static void
102
test_ier_sets_and_clears_selected_bits(void)
103
{
104
	via2_init();
105
	check_eq(ier(), 0x80, "IER starts with nothing enabled and bit 7 reading one");
106
107
	via2_write(VIA_IER, IER_SET | IFR_T1 | IFR_CA1);
108
	check_eq(ier(), (uint8_t)(0x80 | IFR_T1 | IFR_CA1), "a write with bit 7 set enables those bits");
109
110
	// A zero in a position leaves that bit alone, which is the whole point of
111
	// the convention: one interrupt can be changed without reading first.
112
	via2_write(VIA_IER, IER_SET | IFR_T2);
113
	check_eq(ier(), (uint8_t)(0x80 | IFR_T1 | IFR_T2 | IFR_CA1), "and leaves the others alone");
114
115
	via2_write(VIA_IER, IER_CLEAR | IFR_CA1);
116
	check_eq(ier(), (uint8_t)(0x80 | IFR_T1 | IFR_T2), "a write with bit 7 clear disables those bits");
117
118
	via2_write(VIA_IER, IER_CLEAR | 0x00);
119
	check_eq(ier(), (uint8_t)(0x80 | IFR_T1 | IFR_T2), "a write of all zeroes changes nothing");
120
121
	via2_write(VIA_IER, IER_SET | 0x00);
122
	check_eq(ier(), (uint8_t)(0x80 | IFR_T1 | IFR_T2), "and neither does bit 7 alone");
123
124
	// Bit 7 is the set/clear selector, not storage, so it cannot be enabled.
125
	via2_write(VIA_IER, IER_SET | 0x7F);
126
	check_eq(ier(), 0xFF, "every enable can be set at once");
127
	via2_write(VIA_IER, IER_CLEAR | 0x7F);
128
	check_eq(ier(), 0x80, "and cleared again, with bit 7 still reading one");
129
}
130
131
// p26: "individual flag bits may be cleared by writing a Logic 1 into the
132
//       appropriate bit of the IFR."
133
static void
134
test_ifr_flags_clear_on_writing_one(void)
135
{
136
	via2_init();
137
	raise_t1();
138
	check_eq(ifr() & IFR_T1, IFR_T1, "T1 timing out raises its flag");
139
140
	via2_write(VIA_IFR, 0x00);
141
	check_eq(ifr() & IFR_T1, IFR_T1, "writing zero clears nothing");
142
143
	via2_write(VIA_IFR, IFR_T2);
144
	check_eq(ifr() & IFR_T1, IFR_T1, "and writing a one elsewhere leaves it set");
145
146
	via2_write(VIA_IFR, IFR_T1);
147
	check_eq(ifr() & IFR_T1, 0, "writing its own bit clears it");
148
}
149
150
// p26: "Bit 7 of the IFR indicates the status of the IRQB output. Bit 7
151
//       corresponds to the following logic function:
152
//       IRQ = IFR6 ∧ IER6 ∨ IFR5 ∧ IER5 ∨ IFR4 ∧ IER4 ∨ IFR3 ∧ IER3 ∨
153
//             IFR2 ∧ IER2 ∨ IFR1 ∧ IER1 ∨ IFR0 ∧ IER0."
154
static void
155
test_ifr_bit_7_is_the_irq_output(void)
156
{
157
	via2_init();
158
	raise_t1();
159
160
	// A flag with no enable behind it does not interrupt.
161
	check_eq(ifr() & IFR_IRQ, 0, "a raised flag alone does not assert IRQ");
162
	check(!via2_irq(), "and the interrupt line agrees");
163
164
	// An enable whose flag is clear does not either.
165
	via2_write(VIA_IER, IER_SET | IFR_T2);
166
	check_eq(ifr() & IFR_IRQ, 0, "nor does an enable with no flag behind it");
167
	check(!via2_irq(), "with the line still released");
168
169
	via2_write(VIA_IER, IER_SET | IFR_T1);
170
	check_eq(ifr() & IFR_IRQ, IFR_IRQ, "a flag against its own enable asserts IRQ");
171
	check(via2_irq(), "and raises the line");
172
}
173
174
// p26: "IFR7 is not a flag. Therefore, IFR7 is not directly cleared by writing
175
//       a Logic 1 into its bit position. It can be cleared, however, by
176
//       clearing all the flags within the register, or by disabling all active
177
//       interrupts as presented in the next section."
178
static void
179
test_ifr_bit_7_is_not_a_flag(void)
180
{
181
	via2_init();
182
	raise_t1();
183
	via2_write(VIA_IER, IER_SET | IFR_T1);
184
	check_eq(ifr() & IFR_IRQ, IFR_IRQ, "IRQ is asserted");
185
186
	via2_write(VIA_IFR, IFR_IRQ);
187
	check_eq(ifr() & IFR_IRQ, IFR_IRQ, "writing bit 7 does not clear it");
188
	check_eq(ifr() & IFR_T1, IFR_T1, "and does not disturb the flag beneath it");
189
190
	// The two routes the datasheet gives instead.
191
	via2_write(VIA_IFR, IFR_T1);
192
	check_eq(ifr() & IFR_IRQ, 0, "clearing the flag clears bit 7 with it");
193
194
	raise_t1();
195
	check_eq(ifr() & IFR_IRQ, IFR_IRQ, "raised again");
196
	via2_write(VIA_IER, IER_CLEAR | IFR_T1);
197
	check_eq(ifr() & IFR_IRQ, 0, "and disabling the interrupt clears bit 7 too");
198
	check_eq(ifr() & IFR_T1, IFR_T1, "while the flag itself stays raised");
199
}
200
201
// Table 2-11, the "CLEARED BY" column:
202
//     Time out of T1   Read T1C-L low or write T1L-H high
203
//     Time out of T2   Read T2 low or write T2 high
204
static void
205
test_the_timer_flags_clear_on_their_own_accesses(void)
206
{
207
	via2_init();
208
209
	raise_t1();
210
	via2_read(VIA_T1C_H, false);
211
	check_eq(ifr() & IFR_T1, IFR_T1, "reading T1C-H does not clear T1");
212
	via2_read(VIA_T1C_L, false);
213
	check_eq(ifr() & IFR_T1, 0, "reading T1C-L does");
214
215
	raise_t1();
216
	via2_write(VIA_T1L_L, 0x10);
217
	check_eq(ifr() & IFR_T1, IFR_T1, "writing T1L-L does not clear T1");
218
	via2_write(VIA_T1L_H, 0x00);
219
	check_eq(ifr() & IFR_T1, 0, "writing T1L-H does");
220
221
	raise_t2();
222
	via2_read(VIA_T2C_H, false);
223
	check_eq(ifr() & IFR_T2, IFR_T2, "reading T2C-H does not clear T2");
224
	via2_read(VIA_T2C_L, false);
225
	check_eq(ifr() & IFR_T2, 0, "reading T2C-L does");
226
227
	raise_t2();
228
	via2_write(VIA_T2C_H, 0x00);
229
	check_eq(ifr() & IFR_T2, 0, "writing T2C-H clears T2");
230
}
231
232
// A debug read is the debugger looking. The flag-clearing side effects above
233
// are exactly what it must not perform -- reading $9F14 in a memory view would
234
// otherwise acknowledge the guest's timer interrupt for it.
235
static void
236
test_a_debug_read_clears_no_flag(void)
237
{
238
	via2_init();
239
240
	raise_t1();
241
	via2_read(VIA_T1C_L, true);
242
	check_eq(ifr() & IFR_T1, IFR_T1, "a debug read of T1C-L leaves the flag raised");
243
	via2_read(VIA_T1C_L, false);
244
	check_eq(ifr() & IFR_T1, 0, "while a real read clears it");
245
246
	raise_t2();
247
	via2_read(VIA_T2C_L, true);
248
	check_eq(ifr() & IFR_T2, IFR_T2, "a debug read of T2C-L leaves the flag raised");
249
	via2_read(VIA_T2C_L, false);
250
	check_eq(ifr() & IFR_T2, 0, "while a real read clears it");
251
}
252
253
// Two chips, not one with two addresses.
254
static void
255
test_the_two_vias_keep_their_own_flags(void)
256
{
257
	via1_init();
258
	via2_init();
259
260
	raise_t1();
261
	via2_write(VIA_IER, IER_SET | IFR_T1);
262
263
	check(via2_irq(), "VIA 2 is interrupting");
264
	check(!via1_irq(), "VIA 1 is not");
265
	check_eq(via1_read(VIA_IFR, true) & IFR_T1, 0, "and holds no flag of its own");
266
	check_eq(via1_read(VIA_IER, true), 0x80, "nor any enable");
267
}
268
269
int
270
main(void)
271
{
272
	test_ier_sets_and_clears_selected_bits();
273
	test_ifr_flags_clear_on_writing_one();
274
	test_ifr_bit_7_is_the_irq_output();
275
	test_ifr_bit_7_is_not_a_flag();
276
	test_the_timer_flags_clear_on_their_own_accesses();
277
	test_a_debug_read_clears_no_flag();
278
	test_the_two_vias_keep_their_own_flags();
279
	return x16_test_summary("via_irq");
280
}