ac6502 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/components/IO/EmptyCard.js +17 -0
- package/dist/components/IO/EmptyCard.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeypadAttachment.js +141 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeypadAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOLCDAttachment.js +716 -0
- package/dist/components/IO/GPIOAttachments/GPIOLCDAttachment.js.map +1 -0
- package/dist/components/IO/SerialCard.js +4 -4
- package/dist/components/IO/SerialCard.js.map +1 -1
- package/dist/components/Machine.js +84 -45
- package/dist/components/Machine.js.map +1 -1
- package/dist/index.js +175 -52
- package/dist/index.js.map +1 -1
- package/dist/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.js +323 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.js +627 -0
- package/dist/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.js.map +1 -0
- package/dist/tests/IO/SerialCard.test.js +4 -4
- package/dist/tests/IO/SerialCard.test.js.map +1 -1
- package/dist/tests/Machine.test.js +9 -3
- package/dist/tests/Machine.test.js.map +1 -1
- package/package.json +3 -3
- package/src/components/IO/EmptyCard.ts +16 -0
- package/src/components/IO/GPIOAttachments/GPIOKeypadAttachment.ts +153 -0
- package/src/components/IO/GPIOAttachments/GPIOLCDAttachment.ts +791 -0
- package/src/components/IO/SerialCard.ts +4 -4
- package/src/components/Machine.ts +107 -61
- package/src/index.ts +179 -87
- package/src/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.ts +389 -0
- package/src/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.ts +795 -0
- package/src/tests/IO/SerialCard.test.ts +4 -4
- package/src/tests/Machine.test.ts +10 -3
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { GPIOKeypadAttachment } from '../../../components/IO/GPIOAttachments/GPIOKeypadAttachment'
|
|
2
|
+
|
|
3
|
+
describe('GPIOKeypadAttachment', () => {
|
|
4
|
+
let keypadA: GPIOKeypadAttachment // attached to Port A
|
|
5
|
+
let keypadB: GPIOKeypadAttachment // attached to Port B
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
keypadA = new GPIOKeypadAttachment(true, 0)
|
|
9
|
+
keypadB = new GPIOKeypadAttachment(false, 0)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
describe('Initialization', () => {
|
|
14
|
+
it('should have no data ready after construction', () => {
|
|
15
|
+
expect(keypadA.hasDataReady()).toBe(false)
|
|
16
|
+
expect(keypadB.hasDataReady()).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should have no interrupt pending after construction', () => {
|
|
20
|
+
expect(keypadA.hasCA1Interrupt()).toBe(false)
|
|
21
|
+
expect(keypadA.hasCB1Interrupt()).toBe(false)
|
|
22
|
+
expect(keypadB.hasCA1Interrupt()).toBe(false)
|
|
23
|
+
expect(keypadB.hasCB1Interrupt()).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should return 0xFF on port reads when idle (Port A)', () => {
|
|
27
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should return 0xFF on port reads when idle (Port B)', () => {
|
|
31
|
+
expect(keypadB.readPortB(0x00, 0x00)).toBe(0xFF)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should be enabled by default', () => {
|
|
35
|
+
expect(keypadA.isEnabled()).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should report the correct priority', () => {
|
|
39
|
+
const kp = new GPIOKeypadAttachment(true, 7)
|
|
40
|
+
expect(kp.getPriority()).toBe(7)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('getCurrentKey should return 0xFF when no data is ready', () => {
|
|
44
|
+
expect(keypadA.getCurrentKey()).toBe(0xFF)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
describe('Reset', () => {
|
|
50
|
+
it('should clear data ready, interrupt, and keypad value', () => {
|
|
51
|
+
keypadA.updateKey(0x1E, true) // press '1'
|
|
52
|
+
expect(keypadA.hasDataReady()).toBe(true)
|
|
53
|
+
|
|
54
|
+
keypadA.reset()
|
|
55
|
+
|
|
56
|
+
expect(keypadA.hasDataReady()).toBe(false)
|
|
57
|
+
expect(keypadA.hasCA1Interrupt()).toBe(false)
|
|
58
|
+
expect(keypadA.getCurrentKey()).toBe(0xFF)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
describe('Key press → keypad value mapping', () => {
|
|
64
|
+
// Helpers: assert OE (CA2/CB2 LOW) then press and read
|
|
65
|
+
const pressAndReadA = (kp: GPIOKeypadAttachment, hid: number) => {
|
|
66
|
+
kp.updateControlLines(false, false, false, true) // CA2 LOW → OE asserted for Port A
|
|
67
|
+
kp.updateKey(hid, true)
|
|
68
|
+
return kp.readPortA(0x00, 0x00)
|
|
69
|
+
}
|
|
70
|
+
const pressAndReadB = (kp: GPIOKeypadAttachment, hid: number) => {
|
|
71
|
+
kp.updateControlLines(false, true, false, false) // CB2 LOW → OE asserted for Port B
|
|
72
|
+
kp.updateKey(hid, true)
|
|
73
|
+
return kp.readPortB(0x00, 0x00)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
it('Left Arrow (0x50) → $00 ◄', () => {
|
|
77
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x50)).toBe(0x00)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('Backspace is not on this keypad', () => {
|
|
81
|
+
const kp = new GPIOKeypadAttachment(true)
|
|
82
|
+
kp.updateControlLines(false, false, false, true)
|
|
83
|
+
kp.updateKey(0x2A, true) // Backspace – unmapped
|
|
84
|
+
expect(kp.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('1 (0x1E) → $01', () => {
|
|
88
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x1E)).toBe(0x01)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('2 (0x1F) → $02', () => {
|
|
92
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x1F)).toBe(0x02)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('3 (0x20) → $03', () => {
|
|
96
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x20)).toBe(0x03)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('4 (0x21) → $04', () => {
|
|
100
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x21)).toBe(0x04)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('5 (0x22) → $05', () => {
|
|
104
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x22)).toBe(0x05)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('6 (0x23) → $06', () => {
|
|
108
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x23)).toBe(0x06)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('7 (0x24) → $07', () => {
|
|
112
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x24)).toBe(0x07)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('8 (0x25) → $08', () => {
|
|
116
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x25)).toBe(0x08)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('9 (0x26) → $09', () => {
|
|
120
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x26)).toBe(0x09)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('0 (0x27) → $0A', () => {
|
|
124
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x27)).toBe(0x0A)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('Right Arrow (0x4F) → $0B ►', () => {
|
|
128
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x4F)).toBe(0x0B)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('Enter is not mapped to $0B (it maps to $14)', () => {
|
|
132
|
+
const kp = new GPIOKeypadAttachment(true)
|
|
133
|
+
kp.updateControlLines(false, false, false, true)
|
|
134
|
+
kp.updateKey(0x28, true) // Enter → $14
|
|
135
|
+
expect(kp.readPortA(0x00, 0x00)).toBe(0x14)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('f (0x09) → $0C F', () => {
|
|
139
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x09)).toBe(0x0C)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('e (0x08) → $0D E', () => {
|
|
143
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x08)).toBe(0x0D)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('d (0x07) → $0E D', () => {
|
|
147
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x07)).toBe(0x0E)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('c (0x06) → $0F C', () => {
|
|
151
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x06)).toBe(0x0F)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('Escape (0x29) → $10 ESC', () => {
|
|
155
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x29)).toBe(0x10)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('Insert (0x49) → $11 INS', () => {
|
|
159
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x49)).toBe(0x11)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('Page Up (0x4B) → $12 PGUP', () => {
|
|
163
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x4B)).toBe(0x12)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('a (0x04) → $13 A', () => {
|
|
167
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x04)).toBe(0x13)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('Up Arrow (0x52) → $14 ▲', () => {
|
|
171
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x52)).toBe(0x14)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('Enter (0x28) → $14 ▲', () => {
|
|
175
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x28)).toBe(0x14)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('Delete (0x4C) → $15 DEL', () => {
|
|
179
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x4C)).toBe(0x15)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('Page Down (0x4E) → $16 PGDN', () => {
|
|
183
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x4E)).toBe(0x16)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('b (0x05) → $17 B', () => {
|
|
187
|
+
expect(pressAndReadA(new GPIOKeypadAttachment(true), 0x05)).toBe(0x17)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should also map correctly on Port B', () => {
|
|
191
|
+
expect(pressAndReadB(new GPIOKeypadAttachment(false), 0x1E)).toBe(0x01) // '1' → $01
|
|
192
|
+
expect(pressAndReadB(new GPIOKeypadAttachment(false), 0x05)).toBe(0x17) // 'b' → $17
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
describe('Bits 5–7 are always 0 when data is present', () => {
|
|
198
|
+
it('should mask bits 5–7 to 0 on Port A reads', () => {
|
|
199
|
+
keypadA.updateControlLines(false, false, false, true) // CA2 LOW → OE asserted
|
|
200
|
+
keypadA.updateKey(0x05, true) // HID 0x05 (b) → keypad $17 = 0b10111 – highest valid code
|
|
201
|
+
const value = keypadA.readPortA(0x00, 0x00)
|
|
202
|
+
expect(value & 0xE0).toBe(0x00) // bits 5, 6, 7 must be 0
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should mask bits 5–7 to 0 on Port B reads', () => {
|
|
206
|
+
keypadB.updateControlLines(false, true, false, false) // CB2 LOW → OE asserted
|
|
207
|
+
keypadB.updateKey(0x05, true) // HID 0x05 (b) → keypad $17
|
|
208
|
+
const value = keypadB.readPortB(0x00, 0x00)
|
|
209
|
+
expect(value & 0xE0).toBe(0x00)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('getCurrentKey should never have bits 5–7 set', () => {
|
|
213
|
+
keypadA.updateKey(0x29, true) // HID 0x29 (Escape) → keypad $10; bit 4 set
|
|
214
|
+
expect(keypadA.getCurrentKey() & 0xE0).toBe(0x00) // getCurrentKey is independent of OE
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
describe('Port attachment routing', () => {
|
|
220
|
+
it('Port A attachment should not drive Port B', () => {
|
|
221
|
+
keypadA.updateKey(0x1E, true) // press '1'
|
|
222
|
+
expect(keypadA.readPortB(0x00, 0x00)).toBe(0xFF)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('Port B attachment should not drive Port A', () => {
|
|
226
|
+
keypadB.updateKey(0x1E, true) // press '1'
|
|
227
|
+
expect(keypadB.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
describe('Interrupt behaviour (Port A)', () => {
|
|
233
|
+
it('should assert CA1 after a key press', () => {
|
|
234
|
+
keypadA.updateKey(0x1E, true)
|
|
235
|
+
expect(keypadA.hasCA1Interrupt()).toBe(true)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should not assert CB1 when attached to Port A', () => {
|
|
239
|
+
keypadA.updateKey(0x1E, true)
|
|
240
|
+
expect(keypadA.hasCB1Interrupt()).toBe(false)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('clearInterrupts(ca1) should deassert CA1 and clear data ready', () => {
|
|
244
|
+
keypadA.updateKey(0x1E, true)
|
|
245
|
+
keypadA.clearInterrupts(true, false, false, false)
|
|
246
|
+
expect(keypadA.hasCA1Interrupt()).toBe(false)
|
|
247
|
+
expect(keypadA.hasDataReady()).toBe(false)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('clearInterrupts(cb1) should not affect CA1 keypad', () => {
|
|
251
|
+
keypadA.updateKey(0x1E, true)
|
|
252
|
+
keypadA.clearInterrupts(false, false, true, false) // wrong line
|
|
253
|
+
expect(keypadA.hasCA1Interrupt()).toBe(true)
|
|
254
|
+
expect(keypadA.hasDataReady()).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('port reads after clearInterrupts should return 0xFF', () => {
|
|
258
|
+
keypadA.updateKey(0x1E, true)
|
|
259
|
+
keypadA.clearInterrupts(true, false, false, false)
|
|
260
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
describe('Interrupt behaviour (Port B)', () => {
|
|
266
|
+
it('should assert CB1 after a key press', () => {
|
|
267
|
+
keypadB.updateKey(0x1E, true)
|
|
268
|
+
expect(keypadB.hasCB1Interrupt()).toBe(true)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should not assert CA1 when attached to Port B', () => {
|
|
272
|
+
keypadB.updateKey(0x1E, true)
|
|
273
|
+
expect(keypadB.hasCA1Interrupt()).toBe(false)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('clearInterrupts(cb1) should deassert CB1 and clear data ready', () => {
|
|
277
|
+
keypadB.updateKey(0x1E, true)
|
|
278
|
+
keypadB.clearInterrupts(false, false, true, false)
|
|
279
|
+
expect(keypadB.hasCB1Interrupt()).toBe(false)
|
|
280
|
+
expect(keypadB.hasDataReady()).toBe(false)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('clearInterrupts(ca1) should not affect CB1 keypad', () => {
|
|
284
|
+
keypadB.updateKey(0x1E, true)
|
|
285
|
+
keypadB.clearInterrupts(true, false, false, false) // wrong line
|
|
286
|
+
expect(keypadB.hasCB1Interrupt()).toBe(true)
|
|
287
|
+
expect(keypadB.hasDataReady()).toBe(true)
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
describe('Key release events', () => {
|
|
293
|
+
it('should not set dataReady on key release', () => {
|
|
294
|
+
keypadA.updateKey(0x1E, false)
|
|
295
|
+
expect(keypadA.hasDataReady()).toBe(false)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('should not change the port value on key release', () => {
|
|
299
|
+
// Press then read, then release – port stays at 0xFF (interrupt already fired)
|
|
300
|
+
keypadA.updateKey(0x1E, true)
|
|
301
|
+
keypadA.clearInterrupts(true, false, false, false)
|
|
302
|
+
keypadA.updateKey(0x1E, false) // release
|
|
303
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
describe('Unmapped keys', () => {
|
|
309
|
+
it('should not set dataReady for a key not on the keypad', () => {
|
|
310
|
+
keypadA.updateKey(0x3A, true) // F1 – not on this keypad
|
|
311
|
+
expect(keypadA.hasDataReady()).toBe(false)
|
|
312
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should not fire an interrupt for an unmapped key', () => {
|
|
316
|
+
keypadA.updateKey(0x3A, true)
|
|
317
|
+
expect(keypadA.hasCA1Interrupt()).toBe(false)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
describe('Successive key presses', () => {
|
|
323
|
+
it('should latch the latest key code when a second key is pressed', () => {
|
|
324
|
+
keypadA.updateControlLines(false, false, false, true) // CA2 LOW → OE asserted
|
|
325
|
+
keypadA.updateKey(0x1E, true) // '1' → $01
|
|
326
|
+
keypadA.clearInterrupts(true, false, false, false)
|
|
327
|
+
|
|
328
|
+
keypadA.updateKey(0x1F, true) // '2' → $02
|
|
329
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0x02)
|
|
330
|
+
expect(keypadA.hasCA1Interrupt()).toBe(true)
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
describe('OE (Output Enable) via CA2/CB2', () => {
|
|
336
|
+
it('Port A: data is not driven when CA2 is HIGH (OE disabled)', () => {
|
|
337
|
+
keypadA.updateControlLines(false, true, false, true) // CA2 HIGH → OE deasserted
|
|
338
|
+
keypadA.updateKey(0x1E, true) // '1'
|
|
339
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('Port A: data IS driven when CA2 is LOW (OE enabled)', () => {
|
|
343
|
+
keypadA.updateControlLines(false, false, false, true) // CA2 LOW → OE asserted
|
|
344
|
+
keypadA.updateKey(0x1E, true)
|
|
345
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0x01)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('Port B: data is not driven when CB2 is HIGH (OE disabled)', () => {
|
|
349
|
+
keypadB.updateControlLines(false, true, false, true) // CB2 HIGH → OE deasserted
|
|
350
|
+
keypadB.updateKey(0x1E, true)
|
|
351
|
+
expect(keypadB.readPortB(0x00, 0x00)).toBe(0xFF)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('Port B: data IS driven when CB2 is LOW (OE enabled)', () => {
|
|
355
|
+
keypadB.updateControlLines(false, true, false, false) // CB2 LOW → OE asserted
|
|
356
|
+
keypadB.updateKey(0x1E, true)
|
|
357
|
+
expect(keypadB.readPortB(0x00, 0x00)).toBe(0x01)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('CA1 interrupt fires regardless of OE state', () => {
|
|
361
|
+
keypadA.updateControlLines(false, true, false, true) // OE disabled
|
|
362
|
+
keypadA.updateKey(0x1E, true)
|
|
363
|
+
expect(keypadA.hasCA1Interrupt()).toBe(true) // DA line is independent of OE
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('CB1 interrupt fires regardless of OE state', () => {
|
|
367
|
+
keypadB.updateControlLines(false, true, false, true) // OE disabled
|
|
368
|
+
keypadB.updateKey(0x1E, true)
|
|
369
|
+
expect(keypadB.hasCB1Interrupt()).toBe(true)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('toggling OE HIGH then LOW reveals the latched value', () => {
|
|
373
|
+
keypadA.updateKey(0x20, true) // '3' → $03
|
|
374
|
+
// OE still disabled – bus should be high-Z
|
|
375
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
376
|
+
// Now the 6522 asserts CA2 LOW to enable OE
|
|
377
|
+
keypadA.updateControlLines(false, false, false, true)
|
|
378
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0x03)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('reset clears OE state to disabled', () => {
|
|
382
|
+
keypadA.updateControlLines(false, false, false, true) // OE asserted
|
|
383
|
+
keypadA.updateKey(0x1E, true)
|
|
384
|
+
keypadA.reset()
|
|
385
|
+
// After reset OE should be HIGH (disabled) and dataReady cleared
|
|
386
|
+
expect(keypadA.readPortA(0x00, 0x00)).toBe(0xFF)
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
})
|