ac6502 1.0.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/CPU.js +1170 -0
  4. package/dist/components/CPU.js.map +1 -0
  5. package/dist/components/Cart.js +23 -0
  6. package/dist/components/Cart.js.map +1 -0
  7. package/dist/components/IO/Empty.js +19 -0
  8. package/dist/components/IO/Empty.js.map +1 -0
  9. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
  10. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
  11. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
  12. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
  13. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
  14. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
  15. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/GPIOCard.js +597 -0
  18. package/dist/components/IO/GPIOCard.js.map +1 -0
  19. package/dist/components/IO/InputBoard.js +19 -0
  20. package/dist/components/IO/InputBoard.js.map +1 -0
  21. package/dist/components/IO/LCDCard.js +19 -0
  22. package/dist/components/IO/LCDCard.js.map +1 -0
  23. package/dist/components/IO/RAMCard.js +63 -0
  24. package/dist/components/IO/RAMCard.js.map +1 -0
  25. package/dist/components/IO/RTCCard.js +483 -0
  26. package/dist/components/IO/RTCCard.js.map +1 -0
  27. package/dist/components/IO/SerialCard.js +282 -0
  28. package/dist/components/IO/SerialCard.js.map +1 -0
  29. package/dist/components/IO/SoundCard.js +620 -0
  30. package/dist/components/IO/SoundCard.js.map +1 -0
  31. package/dist/components/IO/StorageCard.js +428 -0
  32. package/dist/components/IO/StorageCard.js.map +1 -0
  33. package/dist/components/IO/VGACard.js +9 -0
  34. package/dist/components/IO/VGACard.js.map +1 -0
  35. package/dist/components/IO/VideoCard.js +623 -0
  36. package/dist/components/IO/VideoCard.js.map +1 -0
  37. package/dist/components/IO.js +3 -0
  38. package/dist/components/IO.js.map +1 -0
  39. package/dist/components/Machine.js +310 -0
  40. package/dist/components/Machine.js.map +1 -0
  41. package/dist/components/RAM.js +24 -0
  42. package/dist/components/RAM.js.map +1 -0
  43. package/dist/components/ROM.js +23 -0
  44. package/dist/components/ROM.js.map +1 -0
  45. package/dist/index.js +441 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tests/CPU.test.js +1626 -0
  48. package/dist/tests/CPU.test.js.map +1 -0
  49. package/dist/tests/Cart.test.js +119 -0
  50. package/dist/tests/Cart.test.js.map +1 -0
  51. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
  52. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
  53. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
  54. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
  55. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
  56. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
  57. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
  58. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
  59. package/dist/tests/IO/GPIOCard.test.js +503 -0
  60. package/dist/tests/IO/GPIOCard.test.js.map +1 -0
  61. package/dist/tests/IO/RAMCard.test.js +229 -0
  62. package/dist/tests/IO/RAMCard.test.js.map +1 -0
  63. package/dist/tests/IO/RTCCard.test.js +177 -0
  64. package/dist/tests/IO/RTCCard.test.js.map +1 -0
  65. package/dist/tests/IO/SerialCard.test.js +423 -0
  66. package/dist/tests/IO/SerialCard.test.js.map +1 -0
  67. package/dist/tests/IO/SoundCard.test.js +528 -0
  68. package/dist/tests/IO/SoundCard.test.js.map +1 -0
  69. package/dist/tests/IO/StorageCard.test.js +647 -0
  70. package/dist/tests/IO/StorageCard.test.js.map +1 -0
  71. package/dist/tests/IO/VideoCard.test.js +549 -0
  72. package/dist/tests/IO/VideoCard.test.js.map +1 -0
  73. package/dist/tests/Machine.test.js +383 -0
  74. package/dist/tests/Machine.test.js.map +1 -0
  75. package/dist/tests/RAM.test.js +160 -0
  76. package/dist/tests/RAM.test.js.map +1 -0
  77. package/dist/tests/ROM.test.js +123 -0
  78. package/dist/tests/ROM.test.js.map +1 -0
  79. package/jest.config.cjs +9 -0
  80. package/package.json +43 -0
  81. package/src/components/CPU.ts +1371 -0
  82. package/src/components/Cart.ts +20 -0
  83. package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
  84. package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
  85. package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
  86. package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
  87. package/src/components/IO/GPIOCard.ts +677 -0
  88. package/src/components/IO/RAMCard.ts +68 -0
  89. package/src/components/IO/RTCCard.ts +518 -0
  90. package/src/components/IO/SerialCard.ts +335 -0
  91. package/src/components/IO/SoundCard.ts +711 -0
  92. package/src/components/IO/StorageCard.ts +473 -0
  93. package/src/components/IO/VideoCard.ts +730 -0
  94. package/src/components/IO.ts +11 -0
  95. package/src/components/Machine.ts +364 -0
  96. package/src/components/RAM.ts +23 -0
  97. package/src/components/ROM.ts +19 -0
  98. package/src/index.ts +474 -0
  99. package/src/tests/CPU.test.ts +2045 -0
  100. package/src/tests/Cart.test.ts +149 -0
  101. package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
  102. package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
  103. package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
  104. package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
  105. package/src/tests/IO/GPIOCard.test.ts +644 -0
  106. package/src/tests/IO/RAMCard.test.ts +284 -0
  107. package/src/tests/IO/RTCCard.test.ts +222 -0
  108. package/src/tests/IO/SerialCard.test.ts +530 -0
  109. package/src/tests/IO/SoundCard.test.ts +659 -0
  110. package/src/tests/IO/StorageCard.test.ts +787 -0
  111. package/src/tests/IO/VideoCard.test.ts +668 -0
  112. package/src/tests/Machine.test.ts +437 -0
  113. package/src/tests/RAM.test.ts +196 -0
  114. package/src/tests/ROM.test.ts +154 -0
  115. package/tsconfig.json +12 -0
@@ -0,0 +1,644 @@
1
+ import { GPIOCard } from '../../components/IO/GPIOCard'
2
+ import { GPIOAttachment } from '../../components/IO/GPIOAttachments/GPIOAttachment'
3
+
4
+ /**
5
+ * Helper function to create a mock GPIO attachment
6
+ */
7
+ const createMockAttachment = (options: {
8
+ priority?: number
9
+ enabled?: boolean
10
+ portAValue?: number
11
+ portBValue?: number
12
+ ca1Interrupt?: boolean
13
+ ca2Interrupt?: boolean
14
+ cb1Interrupt?: boolean
15
+ cb2Interrupt?: boolean
16
+ } = {}): GPIOAttachment => {
17
+ const {
18
+ priority = 0,
19
+ enabled = true,
20
+ portAValue = 0xFF,
21
+ portBValue = 0xFF,
22
+ ca1Interrupt = false,
23
+ ca2Interrupt = false,
24
+ cb1Interrupt = false,
25
+ cb2Interrupt = false,
26
+ } = options
27
+
28
+ let currentPortAValue = portAValue
29
+ let currentPortBValue = portBValue
30
+
31
+ return {
32
+ reset: jest.fn(),
33
+ tick: jest.fn(),
34
+ readPortA: jest.fn(() => currentPortAValue),
35
+ readPortB: jest.fn(() => currentPortBValue),
36
+ writePortA: jest.fn(),
37
+ writePortB: jest.fn(),
38
+ isEnabled: jest.fn(() => enabled),
39
+ getPriority: jest.fn(() => priority),
40
+ clearInterrupts: jest.fn(),
41
+ updateControlLines: jest.fn(),
42
+ hasCA1Interrupt: jest.fn(() => ca1Interrupt),
43
+ hasCA2Interrupt: jest.fn(() => ca2Interrupt),
44
+ hasCB1Interrupt: jest.fn(() => cb1Interrupt),
45
+ hasCB2Interrupt: jest.fn(() => cb2Interrupt),
46
+ // Helper method to update values (not part of interface)
47
+ setPortAValue: (value: number) => { currentPortAValue = value },
48
+ setPortBValue: (value: number) => { currentPortBValue = value },
49
+ } as GPIOAttachment & { setPortAValue: (v: number) => void; setPortBValue: (v: number) => void }
50
+ }
51
+
52
+ describe('GPIOCard (65C22 VIA)', () => {
53
+ let gpio: GPIOCard
54
+
55
+ beforeEach(() => {
56
+ gpio = new GPIOCard()
57
+ })
58
+
59
+ describe('Initialization', () => {
60
+ it('should initialize with all registers reset', () => {
61
+ expect(gpio.read(0x00)).toBe(0xFF) // ORB - all inputs default to 1
62
+ expect(gpio.read(0x01)).toBe(0xFF) // ORA - all inputs default to 1
63
+ expect(gpio.read(0x02)).toBe(0x00) // DDRB - all inputs
64
+ expect(gpio.read(0x03)).toBe(0x00) // DDRA - all inputs
65
+ expect(gpio.read(0x0A)).toBe(0x00) // SR
66
+ expect(gpio.read(0x0B)).toBe(0x00) // ACR
67
+ expect(gpio.read(0x0C)).toBe(0x00) // PCR
68
+ expect(gpio.read(0x0D)).toBe(0x00) // IFR
69
+ expect(gpio.read(0x0E)).toBe(0x80) // IER - bit 7 always reads as 1
70
+ })
71
+
72
+ it('should initialize timers to max values', () => {
73
+ const t1cl = gpio.read(0x04)
74
+ const t1ch = gpio.read(0x05)
75
+ expect(t1cl).toBe(0xFF)
76
+ expect(t1ch).toBe(0xFF)
77
+ })
78
+ })
79
+
80
+ describe('Reset', () => {
81
+ it('should reset all registers to default state', () => {
82
+ gpio.write(0x00, 0x55)
83
+ gpio.write(0x02, 0xFF)
84
+ gpio.write(0x0B, 0xFF)
85
+
86
+ gpio.reset(true)
87
+
88
+ expect(gpio.read(0x00)).toBe(0xFF)
89
+ expect(gpio.read(0x02)).toBe(0x00)
90
+ expect(gpio.read(0x0B)).toBe(0x00)
91
+ })
92
+ })
93
+
94
+ describe('Data Direction Registers', () => {
95
+ it('should write and read DDRB', () => {
96
+ gpio.write(0x02, 0xAA)
97
+ expect(gpio.read(0x02)).toBe(0xAA)
98
+ })
99
+
100
+ it('should write and read DDRA', () => {
101
+ gpio.write(0x03, 0x55)
102
+ expect(gpio.read(0x03)).toBe(0x55)
103
+ })
104
+
105
+ it('should affect port reading behavior', () => {
106
+ // Set DDRA bits 0-3 as outputs, 4-7 as inputs
107
+ gpio.write(0x03, 0x0F)
108
+ gpio.write(0x01, 0x5A) // Write to ORA
109
+
110
+ const value = gpio.read(0x01)
111
+ // Bits 0-3 should read as 0xA (from ORA), bits 4-7 as 0xF (inputs default to 1)
112
+ expect(value & 0x0F).toBe(0x0A)
113
+ expect(value & 0xF0).toBe(0xF0)
114
+ })
115
+ })
116
+
117
+ describe('Output Registers', () => {
118
+ it('should write and read ORB', () => {
119
+ gpio.write(0x02, 0xFF) // Set all as outputs
120
+ gpio.write(0x00, 0x42)
121
+ expect(gpio.read(0x00)).toBe(0x42)
122
+ })
123
+
124
+ it('should write and read ORA', () => {
125
+ gpio.write(0x03, 0xFF) // Set all as outputs
126
+ gpio.write(0x01, 0x24)
127
+ expect(gpio.read(0x01)).toBe(0x24)
128
+ })
129
+
130
+ it('should write and read ORA without handshake', () => {
131
+ gpio.write(0x03, 0xFF) // Set all as outputs
132
+ gpio.write(0x0F, 0x88) // Write to ORA_NH
133
+ expect(gpio.read(0x0F)).toBe(0x88)
134
+ })
135
+ })
136
+
137
+ describe('Timer 1', () => {
138
+ it('should write to T1 low latch via T1CL', () => {
139
+ gpio.write(0x04, 0x34)
140
+ gpio.write(0x05, 0x12) // T1CH starts timer
141
+ expect(gpio.read(0x06)).toBe(0x34) // Read T1LL
142
+ })
143
+
144
+ it('should write to T1 high latch via T1LH', () => {
145
+ gpio.write(0x07, 0x56)
146
+ expect(gpio.read(0x07)).toBe(0x56)
147
+ })
148
+
149
+ it('should load latch into counter when writing T1CH', () => {
150
+ gpio.write(0x04, 0x10) // T1CL
151
+ gpio.write(0x05, 0x00) // T1CH - loads counter and starts
152
+
153
+ const low = gpio.read(0x04)
154
+ const high = gpio.read(0x05)
155
+ expect(low).toBe(0x10)
156
+ expect(high).toBe(0x00)
157
+ })
158
+
159
+ it('should countdown Timer 1', () => {
160
+ gpio.write(0x04, 0x05) // Low = 5
161
+ gpio.write(0x05, 0x00) // High = 0, starts timer
162
+
163
+ // Tick 5 times
164
+ for (let i = 0; i < 5; i++) {
165
+ gpio.tick(1000000)
166
+ }
167
+
168
+ expect(gpio.read(0x04)).toBe(0x00)
169
+ })
170
+
171
+ it('should set T1 interrupt flag when counter reaches zero', () => {
172
+ gpio.write(0x04, 0x02)
173
+ gpio.write(0x05, 0x00)
174
+
175
+ gpio.tick(1000000)
176
+ gpio.tick(1000000)
177
+ gpio.tick(1000000) // Counter reaches 0
178
+
179
+ const ifr = gpio.read(0x0D)
180
+ expect(ifr & 0x40).toBe(0x40) // T1 interrupt flag
181
+ })
182
+
183
+ it('should clear T1 interrupt flag when reading T1CL', () => {
184
+ gpio.write(0x04, 0x01)
185
+ gpio.write(0x05, 0x00)
186
+ gpio.tick(1000000)
187
+ gpio.tick(1000000)
188
+
189
+ gpio.read(0x04) // Clear flag by reading T1CL
190
+
191
+ const ifr = gpio.read(0x0D)
192
+ expect(ifr & 0x40).toBe(0x00)
193
+ })
194
+
195
+ it('should stop in one-shot mode after timeout', () => {
196
+ gpio.write(0x04, 0x02)
197
+ gpio.write(0x05, 0x00)
198
+
199
+ // Countdown to 0
200
+ gpio.tick(1000000)
201
+ gpio.tick(1000000)
202
+ gpio.tick(1000000)
203
+
204
+ // Additional ticks shouldn't change counter
205
+ gpio.tick(1000000)
206
+ expect(gpio.read(0x04)).toBe(0x00)
207
+ })
208
+
209
+ it('should reload in free-run mode (ACR bit 6 set)', () => {
210
+ gpio.write(0x0B, 0x40) // ACR - enable free-run mode
211
+ gpio.write(0x04, 0x04) // Latch = 4
212
+ gpio.write(0x05, 0x00)
213
+
214
+ // Tick sequence: 4->3, 3->2, 2->1, 1->0 (triggers reload to 4), 4->3, 3->2
215
+ for (let i = 0; i < 6; i++) {
216
+ gpio.tick(1000000)
217
+ }
218
+
219
+ // After 6 ticks in free-run mode, should be at 2
220
+ expect(gpio.read(0x04)).toBe(0x02)
221
+ })
222
+
223
+ it('should toggle PB7 when ACR bit 7 is set', () => {
224
+ gpio.write(0x02, 0xFF) // DDRB all outputs
225
+ gpio.write(0x00, 0x00) // ORB = 0
226
+ gpio.write(0x0B, 0x80) // ACR - enable PB7 toggle
227
+ gpio.write(0x04, 0x01)
228
+ gpio.write(0x05, 0x00)
229
+
230
+ gpio.tick(1000000)
231
+ gpio.tick(1000000)
232
+
233
+ const orb = gpio.read(0x00)
234
+ expect(orb & 0x80).toBe(0x80) // PB7 should be toggled
235
+ })
236
+ })
237
+
238
+ describe('Timer 2', () => {
239
+ it('should write to T2 low latch', () => {
240
+ gpio.write(0x08, 0x42)
241
+ gpio.write(0x09, 0x00) // Start timer
242
+ expect(gpio.read(0x08)).toBe(0x42)
243
+ })
244
+
245
+ it('should countdown Timer 2', () => {
246
+ gpio.write(0x08, 0x05)
247
+ gpio.write(0x09, 0x00)
248
+
249
+ for (let i = 0; i < 5; i++) {
250
+ gpio.tick(1000000)
251
+ }
252
+
253
+ expect(gpio.read(0x08)).toBe(0x00)
254
+ })
255
+
256
+ it('should set T2 interrupt flag when counter reaches zero', () => {
257
+ gpio.write(0x08, 0x02)
258
+ gpio.write(0x09, 0x00)
259
+
260
+ gpio.tick(1000000)
261
+ gpio.tick(1000000)
262
+ gpio.tick(1000000)
263
+
264
+ const ifr = gpio.read(0x0D)
265
+ expect(ifr & 0x20).toBe(0x20) // T2 interrupt flag
266
+ })
267
+
268
+ it('should clear T2 interrupt flag when reading T2CL', () => {
269
+ gpio.write(0x08, 0x01)
270
+ gpio.write(0x09, 0x00)
271
+ gpio.tick(1000000)
272
+ gpio.tick(1000000)
273
+
274
+ gpio.read(0x08) // Clear flag
275
+
276
+ const ifr = gpio.read(0x0D)
277
+ expect(ifr & 0x20).toBe(0x00)
278
+ })
279
+
280
+ it('should stop after timeout (one-shot mode)', () => {
281
+ gpio.write(0x08, 0x02)
282
+ gpio.write(0x09, 0x00)
283
+
284
+ for (let i = 0; i < 4; i++) {
285
+ gpio.tick(1000000)
286
+ }
287
+
288
+ // Should stay at 0
289
+ expect(gpio.read(0x08)).toBe(0x00)
290
+ })
291
+ })
292
+
293
+ describe('Shift Register', () => {
294
+ it('should write and read shift register', () => {
295
+ gpio.write(0x0A, 0xA5)
296
+ expect(gpio.read(0x0A)).toBe(0xA5)
297
+ })
298
+
299
+ it('should clear SR interrupt flag when writing to SR', () => {
300
+ // Manually set SR interrupt flag
301
+ gpio.write(0x0E, 0x84) // Enable SR interrupt
302
+ gpio.write(0x0D, 0x04) // Won't actually set, but let's test the read behavior
303
+
304
+ gpio.write(0x0A, 0x00) // Writing SR should clear flag
305
+ const ifr = gpio.read(0x0D)
306
+ expect(ifr & 0x04).toBe(0x00)
307
+ })
308
+
309
+ it('should clear SR interrupt flag when reading SR', () => {
310
+ gpio.write(0x0A, 0xFF)
311
+ gpio.read(0x0A) // Should clear flag
312
+
313
+ const ifr = gpio.read(0x0D)
314
+ expect(ifr & 0x04).toBe(0x00)
315
+ })
316
+ })
317
+
318
+ describe('Interrupt Flag Register (IFR)', () => {
319
+ it('should read IFR with bit 7 always 0 when no interrupts', () => {
320
+ const ifr = gpio.read(0x0D)
321
+ expect(ifr & 0x80).toBe(0x00)
322
+ })
323
+
324
+ it('should set bit 7 when any enabled interrupt is active', () => {
325
+ gpio.write(0x0E, 0xC0) // Enable T1 interrupt (IER)
326
+ gpio.write(0x04, 0x01)
327
+ gpio.write(0x05, 0x00)
328
+ gpio.tick(1000000)
329
+ gpio.tick(1000000)
330
+
331
+ const ifr = gpio.read(0x0D)
332
+ expect(ifr & 0x80).toBe(0x80) // Bit 7 set
333
+ })
334
+
335
+ it('should clear specific interrupt flags when writing to IFR', () => {
336
+ gpio.write(0x0E, 0xC0) // Enable T1
337
+ gpio.write(0x04, 0x01)
338
+ gpio.write(0x05, 0x00)
339
+ gpio.tick(1000000)
340
+ gpio.tick(1000000)
341
+
342
+ gpio.write(0x0D, 0x40) // Clear T1 flag
343
+
344
+ const ifr = gpio.read(0x0D)
345
+ expect(ifr & 0x40).toBe(0x00)
346
+ })
347
+ })
348
+
349
+ describe('Interrupt Enable Register (IER)', () => {
350
+ it('should read IER with bit 7 always set', () => {
351
+ gpio.write(0x0E, 0x00)
352
+ const ier = gpio.read(0x0E)
353
+ expect(ier & 0x80).toBe(0x80)
354
+ })
355
+
356
+ it('should set interrupt enable bits when bit 7 is 1', () => {
357
+ gpio.write(0x0E, 0xC0) // Set T1 interrupt enable
358
+ const ier = gpio.read(0x0E)
359
+ expect(ier & 0x40).toBe(0x40)
360
+ })
361
+
362
+ it('should clear interrupt enable bits when bit 7 is 0', () => {
363
+ gpio.write(0x0E, 0xC0) // Set T1
364
+ gpio.write(0x0E, 0x40) // Clear T1 (bit 7 = 0)
365
+ const ier = gpio.read(0x0E)
366
+ expect(ier & 0x40).toBe(0x00)
367
+ })
368
+
369
+ it('should enable multiple interrupts', () => {
370
+ gpio.write(0x0E, 0xFF) // Enable all
371
+ const ier = gpio.read(0x0E)
372
+ expect(ier & 0x7F).toBe(0x7F)
373
+ })
374
+ })
375
+
376
+ describe('IRQ Generation', () => {
377
+ it('should call raiseIRQ when enabled interrupt is triggered', () => {
378
+ const mockIRQ = jest.fn()
379
+ gpio.raiseIRQ = mockIRQ
380
+
381
+ gpio.write(0x0E, 0xC0) // Enable T1 interrupt
382
+ gpio.write(0x04, 0x01)
383
+ gpio.write(0x05, 0x00)
384
+
385
+ gpio.tick(1000000)
386
+ gpio.tick(1000000)
387
+
388
+ expect(mockIRQ).toHaveBeenCalled()
389
+ })
390
+
391
+ it('should not call raiseIRQ when interrupt is not enabled', () => {
392
+ const mockIRQ = jest.fn()
393
+ gpio.raiseIRQ = mockIRQ
394
+
395
+ gpio.write(0x04, 0x01)
396
+ gpio.write(0x05, 0x00)
397
+
398
+ gpio.tick(1000000)
399
+ gpio.tick(1000000)
400
+
401
+ expect(mockIRQ).not.toHaveBeenCalled()
402
+ })
403
+ })
404
+
405
+ describe('Auxiliary Control Register (ACR)', () => {
406
+ it('should write and read ACR', () => {
407
+ gpio.write(0x0B, 0x55)
408
+ expect(gpio.read(0x0B)).toBe(0x55)
409
+ })
410
+
411
+ it('should control Timer 1 free-run mode (bit 6)', () => {
412
+ gpio.write(0x0B, 0x00) // One-shot
413
+ gpio.write(0x04, 0x02)
414
+ gpio.write(0x05, 0x00)
415
+
416
+ for (let i = 0; i < 4; i++) {
417
+ gpio.tick(1000000)
418
+ }
419
+
420
+ const t1 = gpio.read(0x04)
421
+ expect(t1).toBe(0x00) // Should stay at 0
422
+ })
423
+
424
+ it('should control PB7 output (bit 7)', () => {
425
+ gpio.write(0x02, 0xFF) // Set all Port B as outputs
426
+ gpio.write(0x00, 0x00) // Set ORB to 0
427
+ gpio.write(0x0B, 0xC0) // Free-run + PB7 toggle enabled
428
+ gpio.write(0x04, 0x02) // Latch = 2
429
+ gpio.write(0x05, 0x00)
430
+
431
+ const before = gpio.read(0x00) & 0x80 // Should be 0
432
+
433
+ // Tick until timer expires (3 ticks: 2, 1, 0)
434
+ gpio.tick(1000000)
435
+ gpio.tick(1000000)
436
+ gpio.tick(1000000)
437
+
438
+ const after = gpio.read(0x00) & 0x80 // Should be toggled (0x80)
439
+
440
+ expect(before).toBe(0x00)
441
+ expect(after).toBe(0x80)
442
+ })
443
+ })
444
+
445
+ describe('Peripheral Control Register (PCR)', () => {
446
+ it('should write and read PCR', () => {
447
+ gpio.write(0x0C, 0xAA)
448
+ expect(gpio.read(0x0C)).toBe(0xAA)
449
+ })
450
+
451
+ it('should update control lines when PCR is written', () => {
452
+ // This is hard to test without access to private fields,
453
+ // but we can verify the write doesn't crash
454
+ gpio.write(0x0C, 0xEE) // CA2 and CB2 manual outputs high
455
+ expect(gpio.read(0x0C)).toBe(0xEE)
456
+ })
457
+ })
458
+
459
+ describe('Port A/B Interrupt Clearing', () => {
460
+ it('should clear CA1/CA2 interrupts when reading ORA', () => {
461
+ // Manually would need attachment to trigger, but reading should clear
462
+ gpio.read(0x01)
463
+ const ifr = gpio.read(0x0D)
464
+ expect(ifr & 0x03).toBe(0x00)
465
+ })
466
+
467
+ it('should clear CA1/CA2 interrupts when writing ORA', () => {
468
+ gpio.write(0x01, 0x00)
469
+ const ifr = gpio.read(0x0D)
470
+ expect(ifr & 0x03).toBe(0x00)
471
+ })
472
+
473
+ it('should clear CB1/CB2 interrupts when reading ORB', () => {
474
+ gpio.read(0x00)
475
+ const ifr = gpio.read(0x0D)
476
+ expect(ifr & 0x18).toBe(0x00)
477
+ })
478
+
479
+ it('should clear CB1/CB2 interrupts when writing ORB', () => {
480
+ gpio.write(0x00, 0x00)
481
+ const ifr = gpio.read(0x0D)
482
+ expect(ifr & 0x18).toBe(0x00)
483
+ })
484
+
485
+ it('should NOT clear interrupts when using no-handshake register', () => {
486
+ // This test verifies that ORA_NH doesn't clear flags
487
+ // Since we can't easily set the flags without attachments, we just verify it works
488
+ gpio.write(0x0F, 0x42)
489
+ expect(gpio.read(0x0F)).toBe(0xFF) // All inputs
490
+ })
491
+ })
492
+
493
+ describe('GPIO Attachments', () => {
494
+ it('should attach a device to Port A', () => {
495
+ const mockAttachment = createMockAttachment({ priority: 0 })
496
+ gpio.attachToPortA(mockAttachment)
497
+ expect(gpio.getPortAAttachment(0)).toBe(mockAttachment)
498
+ })
499
+
500
+ it('should attach a device to Port B', () => {
501
+ const mockAttachment = createMockAttachment({ priority: 0 })
502
+ gpio.attachToPortB(mockAttachment)
503
+ expect(gpio.getPortBAttachment(0)).toBe(mockAttachment)
504
+ })
505
+
506
+ it('should read input from attached device on Port A', () => {
507
+ const mockAttachment = createMockAttachment({ portAValue: 0x00 })
508
+ gpio.attachToPortA(mockAttachment)
509
+
510
+ const value = gpio.read(0x01)
511
+ expect(value).toBe(0x00)
512
+ })
513
+
514
+ it('should read input from attached device on Port B', () => {
515
+ const mockAttachment = createMockAttachment({ portBValue: 0xAA })
516
+ gpio.attachToPortB(mockAttachment)
517
+
518
+ const value = gpio.read(0x00)
519
+ expect(value).toBe(0xAA)
520
+ })
521
+
522
+ it('should sort attachments by priority', () => {
523
+ const attachment1 = createMockAttachment({ priority: 5 })
524
+ const attachment2 = createMockAttachment({ priority: 2 })
525
+ const attachment3 = createMockAttachment({ priority: 8 })
526
+
527
+ gpio.attachToPortA(attachment1)
528
+ gpio.attachToPortA(attachment2)
529
+ gpio.attachToPortA(attachment3)
530
+
531
+ expect(gpio.getPortAAttachment(0)?.getPriority()).toBe(2) // Highest priority (lowest number)
532
+ expect(gpio.getPortAAttachment(1)?.getPriority()).toBe(5)
533
+ expect(gpio.getPortAAttachment(2)?.getPriority()).toBe(8)
534
+ })
535
+
536
+ it('should notify attachments when control lines change', () => {
537
+ const mockAttachment = createMockAttachment()
538
+
539
+ gpio.attachToPortA(mockAttachment)
540
+ expect(mockAttachment.updateControlLines).toHaveBeenCalled() // Called during attach
541
+ })
542
+
543
+ it('should tick all attachments', () => {
544
+ const mockAttachment = createMockAttachment()
545
+
546
+ gpio.attachToPortA(mockAttachment)
547
+ gpio.tick(1000000)
548
+
549
+ expect(mockAttachment.tick).toHaveBeenCalledWith(1000000)
550
+ })
551
+
552
+ it('should check attachment interrupts and set IFR flags', () => {
553
+ const mockAttachment = createMockAttachment({ ca1Interrupt: true })
554
+
555
+ gpio.attachToPortA(mockAttachment)
556
+ gpio.tick(1000000)
557
+
558
+ const ifr = gpio.read(0x0D)
559
+ expect(ifr & 0x02).toBe(0x02) // CA1 interrupt flag
560
+ })
561
+
562
+ it('should return null for invalid attachment index', () => {
563
+ expect(gpio.getPortAAttachment(0)).toBeNull()
564
+ expect(gpio.getPortBAttachment(10)).toBeNull()
565
+ })
566
+
567
+ it('should handle multiple attachments reading from same port', () => {
568
+ const attachment1 = createMockAttachment({ priority: 0, portAValue: 0x0F })
569
+ const attachment2 = createMockAttachment({ priority: 1, portAValue: 0xF0 })
570
+
571
+ gpio.attachToPortA(attachment1)
572
+ gpio.attachToPortA(attachment2)
573
+
574
+ const value = gpio.read(0x01)
575
+ // Values are ANDed together: 0x0F & 0xF0 = 0x00
576
+ expect(value).toBe(0x00)
577
+ })
578
+ })
579
+
580
+ describe('Port Direction Behavior', () => {
581
+ it('should read outputs from OR when DDR bit is 1', () => {
582
+ gpio.write(0x03, 0xFF) // All outputs
583
+ gpio.write(0x01, 0xA5)
584
+ expect(gpio.read(0x01)).toBe(0xA5)
585
+ })
586
+
587
+ it('should read external input when DDR bit is 0', () => {
588
+ const mockAttachment = createMockAttachment({ portAValue: 0xFF })
589
+ gpio.attachToPortA(mockAttachment)
590
+
591
+ gpio.write(0x03, 0x00) // All inputs
592
+ expect(gpio.read(0x01)).toBe(0xFF) // All high
593
+ })
594
+
595
+ it('should mix input and output based on DDR', () => {
596
+ const mockAttachment = createMockAttachment({ portAValue: 0x00 })
597
+ gpio.attachToPortA(mockAttachment)
598
+
599
+ gpio.write(0x03, 0x0F) // Lower 4 bits output, upper 4 input
600
+ gpio.write(0x01, 0x55)
601
+
602
+ const value = gpio.read(0x01)
603
+ // Lower 4 bits from ORA: 0x05
604
+ // Upper 4 bits from attachment: 0x00
605
+ expect(value & 0x0F).toBe(0x05)
606
+ expect(value & 0xF0).toBe(0x00)
607
+ })
608
+ })
609
+
610
+ describe('Edge Cases', () => {
611
+ it('should mask register addresses to 4 bits', () => {
612
+ gpio.write(0x03, 0xFF)
613
+ gpio.write(0x11, 0x42) // 0x11 & 0x0F = 0x01 (ORA)
614
+
615
+ expect(gpio.read(0x11)).toBe(0x42)
616
+ })
617
+
618
+ it('should mask data values to 8 bits', () => {
619
+ gpio.write(0x03, 0xFF)
620
+ gpio.write(0x01, 0x1FF)
621
+ expect(gpio.read(0x01)).toBe(0xFF)
622
+ })
623
+
624
+ it('should handle timer countdown to exactly zero', () => {
625
+ // Timer starts at 1, counts down to 0 and triggers interrupt
626
+ gpio.write(0x04, 0x01)
627
+ gpio.write(0x05, 0x00)
628
+
629
+ gpio.tick(1000000) // 1 -> 0
630
+ gpio.tick(1000000) // Reaches 0, interrupt triggered
631
+
632
+ const ifr = gpio.read(0x0D)
633
+ expect(ifr & 0x40).toBe(0x40)
634
+ })
635
+
636
+ it('should not countdown timers when not running', () => {
637
+ const t1Before = gpio.read(0x04)
638
+ gpio.tick(1000000)
639
+ const t1After = gpio.read(0x04)
640
+
641
+ expect(t1Before).toBe(t1After)
642
+ })
643
+ })
644
+ })