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.
Files changed (32) hide show
  1. package/README.md +2 -2
  2. package/dist/components/IO/EmptyCard.js +17 -0
  3. package/dist/components/IO/EmptyCard.js.map +1 -0
  4. package/dist/components/IO/GPIOAttachments/GPIOKeypadAttachment.js +141 -0
  5. package/dist/components/IO/GPIOAttachments/GPIOKeypadAttachment.js.map +1 -0
  6. package/dist/components/IO/GPIOAttachments/GPIOLCDAttachment.js +716 -0
  7. package/dist/components/IO/GPIOAttachments/GPIOLCDAttachment.js.map +1 -0
  8. package/dist/components/IO/SerialCard.js +4 -4
  9. package/dist/components/IO/SerialCard.js.map +1 -1
  10. package/dist/components/Machine.js +84 -45
  11. package/dist/components/Machine.js.map +1 -1
  12. package/dist/index.js +175 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.js +323 -0
  15. package/dist/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.js.map +1 -0
  16. package/dist/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.js +627 -0
  17. package/dist/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.js.map +1 -0
  18. package/dist/tests/IO/SerialCard.test.js +4 -4
  19. package/dist/tests/IO/SerialCard.test.js.map +1 -1
  20. package/dist/tests/Machine.test.js +9 -3
  21. package/dist/tests/Machine.test.js.map +1 -1
  22. package/package.json +3 -3
  23. package/src/components/IO/EmptyCard.ts +16 -0
  24. package/src/components/IO/GPIOAttachments/GPIOKeypadAttachment.ts +153 -0
  25. package/src/components/IO/GPIOAttachments/GPIOLCDAttachment.ts +791 -0
  26. package/src/components/IO/SerialCard.ts +4 -4
  27. package/src/components/Machine.ts +107 -61
  28. package/src/index.ts +179 -87
  29. package/src/tests/IO/GPIOAttachments/GPIOKeypadAttachment.test.ts +389 -0
  30. package/src/tests/IO/GPIOAttachments/GPIOLCDAttachment.test.ts +795 -0
  31. package/src/tests/IO/SerialCard.test.ts +4 -4
  32. 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
+ })