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,677 @@
1
+ import { IO } from '../IO'
2
+ import { GPIOAttachment } from './GPIOAttachments/GPIOAttachment'
3
+
4
+ /**
5
+ * GPIOCard - Emulates the 65C22 VIA (Versatile Interface Adapter)
6
+ *
7
+ * The 65C22 VIA provides:
8
+ * - Two 8-bit bidirectional I/O ports (Port A and Port B)
9
+ * - Two 16-bit timers with interrupt generation
10
+ * - Shift register for serial I/O
11
+ * - Handshaking lines for data transfer
12
+ */
13
+ export class GPIOCard implements IO {
14
+ // VIA Register addresses (offset from base address)
15
+ private static readonly VIA_ORB = 0x00 // Output Register B
16
+ private static readonly VIA_ORA = 0x01 // Output Register A
17
+ private static readonly VIA_DDRB = 0x02 // Data Direction Register B
18
+ private static readonly VIA_DDRA = 0x03 // Data Direction Register A
19
+ private static readonly VIA_T1CL = 0x04 // Timer 1 Counter Low
20
+ private static readonly VIA_T1CH = 0x05 // Timer 1 Counter High
21
+ private static readonly VIA_T1LL = 0x06 // Timer 1 Latch Low
22
+ private static readonly VIA_T1LH = 0x07 // Timer 1 Latch High
23
+ private static readonly VIA_T2CL = 0x08 // Timer 2 Counter Low
24
+ private static readonly VIA_T2CH = 0x09 // Timer 2 Counter High
25
+ private static readonly VIA_SR = 0x0A // Shift Register
26
+ private static readonly VIA_ACR = 0x0B // Auxiliary Control Register
27
+ private static readonly VIA_PCR = 0x0C // Peripheral Control Register
28
+ private static readonly VIA_IFR = 0x0D // Interrupt Flag Register
29
+ private static readonly VIA_IER = 0x0E // Interrupt Enable Register
30
+ private static readonly VIA_ORA_NH = 0x0F // Output Register A (No Handshake)
31
+
32
+ // Interrupt flags
33
+ private static readonly IRQ_CA2 = 0x01
34
+ private static readonly IRQ_CA1 = 0x02
35
+ private static readonly IRQ_SR = 0x04
36
+ private static readonly IRQ_CB2 = 0x08
37
+ private static readonly IRQ_CB1 = 0x10
38
+ private static readonly IRQ_T2 = 0x20
39
+ private static readonly IRQ_T1 = 0x40
40
+ private static readonly IRQ_IRQ = 0x80 // Master IRQ flag
41
+
42
+ private static readonly MAX_ATTACHMENTS_PER_PORT = 8
43
+
44
+ // VIA Registers
45
+ private regORB: number = 0x00
46
+ private regORA: number = 0x00
47
+ private regDDRB: number = 0x00
48
+ private regDDRA: number = 0x00
49
+ private regT1C: number = 0xFFFF
50
+ private regT1L: number = 0xFFFF
51
+ private regT2C: number = 0xFFFF
52
+ private regT2L: number = 0xFF
53
+ private regSR: number = 0x00
54
+ private regACR: number = 0x00
55
+ private regPCR: number = 0x00
56
+ private regIFR: number = 0x00
57
+ private regIER: number = 0x00
58
+
59
+ // Control lines
60
+ private CA1: boolean = false
61
+ private CA2: boolean = false
62
+ private CB1: boolean = false
63
+ private CB2: boolean = false
64
+
65
+ // Timer states
66
+ private T1_running: boolean = false
67
+ private T2_running: boolean = false
68
+ private T1_IRQ_enabled: boolean = false
69
+ private T2_IRQ_enabled: boolean = false
70
+
71
+ // Timing
72
+ private tickCounter: number = 0
73
+ private ticksPerMicrosecond: number = 1
74
+
75
+ // Attachments
76
+ private portA_attachments: (GPIOAttachment | null)[] = []
77
+ private portB_attachments: (GPIOAttachment | null)[] = []
78
+ private portA_attachmentCount: number = 0
79
+ private portB_attachmentCount: number = 0
80
+
81
+ raiseIRQ = () => {}
82
+ raiseNMI = () => {}
83
+
84
+ constructor() {
85
+ this.reset(true)
86
+ }
87
+
88
+ reset(coldStart: boolean): void {
89
+ // Reset all VIA registers
90
+ this.regORB = 0x00
91
+ this.regORA = 0x00
92
+ this.regDDRB = 0x00
93
+ this.regDDRA = 0x00
94
+ this.regT1C = 0xFFFF
95
+ this.regT1L = 0xFFFF
96
+ this.regT2C = 0xFFFF
97
+ this.regT2L = 0xFF
98
+ this.regSR = 0x00
99
+ this.regACR = 0x00
100
+ this.regPCR = 0x00
101
+ this.regIFR = 0x00
102
+ this.regIER = 0x00
103
+
104
+ // Reset control lines
105
+ this.CA1 = false
106
+ this.CA2 = false
107
+ this.CB1 = false
108
+ this.CB2 = false
109
+
110
+ // Reset timer states
111
+ this.T1_running = false
112
+ this.T2_running = false
113
+ this.T1_IRQ_enabled = false
114
+ this.T2_IRQ_enabled = false
115
+
116
+ // Initialize attachment arrays
117
+ this.portA_attachmentCount = 0
118
+ this.portB_attachmentCount = 0
119
+ for (let i = 0; i < GPIOCard.MAX_ATTACHMENTS_PER_PORT; i++) {
120
+ this.portA_attachments[i] = null
121
+ this.portB_attachments[i] = null
122
+ }
123
+
124
+ // Reset all attachments
125
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
126
+ if (this.portA_attachments[i] !== null) {
127
+ this.portA_attachments[i]!.reset()
128
+ }
129
+ }
130
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
131
+ if (this.portB_attachments[i] !== null) {
132
+ this.portB_attachments[i]!.reset()
133
+ }
134
+ }
135
+
136
+ // Reset timing
137
+ this.tickCounter = 0
138
+ this.ticksPerMicrosecond = 1
139
+ }
140
+
141
+ read(address: number): number {
142
+ const reg = address & 0x0F
143
+ let value = 0x00
144
+
145
+ switch (reg) {
146
+ case GPIOCard.VIA_ORB:
147
+ // Reading ORB clears CB1 and CB2 interrupt flags
148
+ this.clearIRQFlag(GPIOCard.IRQ_CB1 | GPIOCard.IRQ_CB2)
149
+ value = this.readPortB()
150
+ // Notify attachments that interrupts were cleared
151
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
152
+ if (this.portB_attachments[i] !== null) {
153
+ this.portB_attachments[i]!.clearInterrupts(false, false, true, true)
154
+ }
155
+ }
156
+ break
157
+
158
+ case GPIOCard.VIA_ORA:
159
+ // Reading ORA clears CA1 and CA2 interrupt flags
160
+ this.clearIRQFlag(GPIOCard.IRQ_CA1 | GPIOCard.IRQ_CA2)
161
+ value = this.readPortA()
162
+ // Notify attachments that interrupts were cleared
163
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
164
+ if (this.portA_attachments[i] !== null) {
165
+ this.portA_attachments[i]!.clearInterrupts(true, true, false, false)
166
+ }
167
+ }
168
+ break
169
+
170
+ case GPIOCard.VIA_DDRB:
171
+ value = this.regDDRB
172
+ break
173
+
174
+ case GPIOCard.VIA_DDRA:
175
+ value = this.regDDRA
176
+ break
177
+
178
+ case GPIOCard.VIA_T1CL:
179
+ // Reading T1CL clears T1 interrupt flag
180
+ this.clearIRQFlag(GPIOCard.IRQ_T1)
181
+ value = this.regT1C & 0xFF
182
+ break
183
+
184
+ case GPIOCard.VIA_T1CH:
185
+ value = (this.regT1C >> 8) & 0xFF
186
+ break
187
+
188
+ case GPIOCard.VIA_T1LL:
189
+ value = this.regT1L & 0xFF
190
+ break
191
+
192
+ case GPIOCard.VIA_T1LH:
193
+ value = (this.regT1L >> 8) & 0xFF
194
+ break
195
+
196
+ case GPIOCard.VIA_T2CL:
197
+ // Reading T2CL clears T2 interrupt flag
198
+ this.clearIRQFlag(GPIOCard.IRQ_T2)
199
+ value = this.regT2C & 0xFF
200
+ break
201
+
202
+ case GPIOCard.VIA_T2CH:
203
+ value = (this.regT2C >> 8) & 0xFF
204
+ break
205
+
206
+ case GPIOCard.VIA_SR:
207
+ // Reading SR clears SR interrupt flag
208
+ this.clearIRQFlag(GPIOCard.IRQ_SR)
209
+ value = this.regSR
210
+ break
211
+
212
+ case GPIOCard.VIA_ACR:
213
+ value = this.regACR
214
+ break
215
+
216
+ case GPIOCard.VIA_PCR:
217
+ value = this.regPCR
218
+ break
219
+
220
+ case GPIOCard.VIA_IFR:
221
+ value = this.regIFR
222
+ // Bit 7 is set if any enabled interrupt is active
223
+ if (this.regIFR & this.regIER & 0x7F) {
224
+ value |= GPIOCard.IRQ_IRQ
225
+ }
226
+ break
227
+
228
+ case GPIOCard.VIA_IER:
229
+ value = this.regIER | 0x80 // Bit 7 always reads as 1
230
+ break
231
+
232
+ case GPIOCard.VIA_ORA_NH:
233
+ // Reading ORA without handshake (no interrupt flag clearing)
234
+ value = this.readPortA()
235
+ break
236
+ }
237
+
238
+ return value & 0xFF
239
+ }
240
+
241
+ write(address: number, data: number): void {
242
+ const reg = address & 0x0F
243
+ const value = data & 0xFF
244
+
245
+ switch (reg) {
246
+ case GPIOCard.VIA_ORB:
247
+ // Writing ORB clears CB1 and CB2 interrupt flags
248
+ this.clearIRQFlag(GPIOCard.IRQ_CB1 | GPIOCard.IRQ_CB2)
249
+ this.regORB = value
250
+ this.writePortB(value)
251
+ break
252
+
253
+ case GPIOCard.VIA_ORA:
254
+ // Writing ORA clears CA1 and CA2 interrupt flags
255
+ this.clearIRQFlag(GPIOCard.IRQ_CA1 | GPIOCard.IRQ_CA2)
256
+ this.regORA = value
257
+ this.writePortA(value)
258
+ break
259
+
260
+ case GPIOCard.VIA_DDRB:
261
+ this.regDDRB = value
262
+ break
263
+
264
+ case GPIOCard.VIA_DDRA:
265
+ this.regDDRA = value
266
+ break
267
+
268
+ case GPIOCard.VIA_T1CL:
269
+ case GPIOCard.VIA_T1LL:
270
+ // Write to T1 low latch
271
+ this.regT1L = (this.regT1L & 0xFF00) | value
272
+ break
273
+
274
+ case GPIOCard.VIA_T1CH:
275
+ // Write to T1 high counter - loads latch into counter and starts timer
276
+ this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
277
+ this.regT1C = this.regT1L
278
+ this.clearIRQFlag(GPIOCard.IRQ_T1)
279
+ this.T1_running = true
280
+ break
281
+
282
+ case GPIOCard.VIA_T1LH:
283
+ // Write to T1 high latch
284
+ this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
285
+ this.clearIRQFlag(GPIOCard.IRQ_T1)
286
+ break
287
+
288
+ case GPIOCard.VIA_T2CL:
289
+ // Write to T2 low latch
290
+ this.regT2L = value
291
+ break
292
+
293
+ case GPIOCard.VIA_T2CH:
294
+ // Write to T2 high counter - loads latch into counter and starts timer
295
+ this.regT2C = (value << 8) | this.regT2L
296
+ this.clearIRQFlag(GPIOCard.IRQ_T2)
297
+ this.T2_running = true
298
+ break
299
+
300
+ case GPIOCard.VIA_SR:
301
+ this.regSR = value
302
+ this.clearIRQFlag(GPIOCard.IRQ_SR)
303
+ break
304
+
305
+ case GPIOCard.VIA_ACR:
306
+ this.regACR = value
307
+ // ACR controls timer modes, shift register, and latching
308
+ break
309
+
310
+ case GPIOCard.VIA_PCR:
311
+ this.regPCR = value
312
+ // PCR controls CA1, CA2, CB1, CB2 behavior
313
+ this.updateCA2()
314
+ this.updateCB2()
315
+ break
316
+
317
+ case GPIOCard.VIA_IFR:
318
+ // Writing to IFR clears the corresponding interrupt flags
319
+ this.regIFR &= ~(value & 0x7F)
320
+ this.updateIRQ()
321
+ break
322
+
323
+ case GPIOCard.VIA_IER:
324
+ // Bit 7 determines set (1) or clear (0)
325
+ if (value & 0x80) {
326
+ this.regIER |= (value & 0x7F)
327
+ } else {
328
+ this.regIER &= ~(value & 0x7F)
329
+ }
330
+ this.updateIRQ()
331
+ break
332
+
333
+ case GPIOCard.VIA_ORA_NH:
334
+ // Writing ORA without handshake (no interrupt flag clearing)
335
+ this.regORA = value
336
+ this.writePortA(value)
337
+ break
338
+ }
339
+ }
340
+
341
+ tick(frequency: number): void {
342
+ this.tickCounter++
343
+
344
+ // Update Timer 1
345
+ if (this.T1_running && this.regT1C > 0) {
346
+ this.regT1C--
347
+ if (this.regT1C === 0) {
348
+ this.setIRQFlag(GPIOCard.IRQ_T1)
349
+
350
+ // Check if timer is in free-run mode (ACR bit 6)
351
+ if (this.regACR & 0x40) {
352
+ this.regT1C = this.regT1L // Reload from latch
353
+ } else {
354
+ this.T1_running = false
355
+ }
356
+
357
+ // Toggle PB7 if enabled (ACR bit 7)
358
+ if (this.regACR & 0x80) {
359
+ this.regORB ^= 0x80
360
+ }
361
+ }
362
+ }
363
+
364
+ // Update Timer 2
365
+ if (this.T2_running && this.regT2C > 0) {
366
+ this.regT2C--
367
+ if (this.regT2C === 0) {
368
+ this.setIRQFlag(GPIOCard.IRQ_T2)
369
+ this.T2_running = false
370
+ }
371
+ }
372
+
373
+ // Tick all attachments
374
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
375
+ if (this.portA_attachments[i] !== null) {
376
+ this.portA_attachments[i]!.tick(frequency)
377
+ }
378
+ }
379
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
380
+ if (this.portB_attachments[i] !== null) {
381
+ this.portB_attachments[i]!.tick(frequency)
382
+ }
383
+ }
384
+
385
+ // Check for attachment interrupts
386
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
387
+ if (this.portA_attachments[i] !== null) {
388
+ if (this.portA_attachments[i]!.hasCA1Interrupt()) {
389
+ this.setIRQFlag(GPIOCard.IRQ_CA1)
390
+ }
391
+ if (this.portA_attachments[i]!.hasCA2Interrupt()) {
392
+ this.setIRQFlag(GPIOCard.IRQ_CA2)
393
+ }
394
+ }
395
+ }
396
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
397
+ if (this.portB_attachments[i] !== null) {
398
+ if (this.portB_attachments[i]!.hasCB1Interrupt()) {
399
+ this.setIRQFlag(GPIOCard.IRQ_CB1)
400
+ }
401
+ if (this.portB_attachments[i]!.hasCB2Interrupt()) {
402
+ this.setIRQFlag(GPIOCard.IRQ_CB2)
403
+ }
404
+ }
405
+ }
406
+
407
+ // Raise IRQ if any enabled interrupt is active
408
+ if (this.regIFR & this.regIER & 0x7F) {
409
+ this.raiseIRQ()
410
+ }
411
+ }
412
+
413
+ private updateIRQ(): void {
414
+ // Update bit 7 of IFR based on enabled interrupts
415
+ if (this.regIFR & this.regIER & 0x7F) {
416
+ this.regIFR |= GPIOCard.IRQ_IRQ
417
+ } else {
418
+ this.regIFR &= ~GPIOCard.IRQ_IRQ
419
+ }
420
+ }
421
+
422
+ private setIRQFlag(flag: number): void {
423
+ this.regIFR |= flag
424
+ this.updateIRQ()
425
+ }
426
+
427
+ private clearIRQFlag(flag: number): void {
428
+ this.regIFR &= ~flag
429
+ this.updateIRQ()
430
+ }
431
+
432
+ private readPortA(): number {
433
+ let value = 0xFF
434
+
435
+ // Determine input sources from attachments (priority-based multiplexing)
436
+ let externalInput = 0xFF
437
+
438
+ // Query all Port A attachments in priority order
439
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
440
+ if (this.portA_attachments[i] !== null && this.portA_attachments[i]!.isEnabled()) {
441
+ const attachmentData = this.portA_attachments[i]!.readPortA(this.regDDRA, this.regORA)
442
+ // First enabled attachment with data (not 0xFF) wins, or AND all values together
443
+ externalInput &= attachmentData
444
+ }
445
+ }
446
+
447
+ // Apply DDR settings: output bits come from ORA, input bits from external
448
+ for (let bit = 0; bit < 8; bit++) {
449
+ if (this.regDDRA & (1 << bit)) {
450
+ // Output mode - read from register
451
+ if (this.regORA & (1 << bit)) {
452
+ value |= (1 << bit)
453
+ } else {
454
+ value &= ~(1 << bit)
455
+ }
456
+ } else {
457
+ // Input mode - read from external source
458
+ if (externalInput & (1 << bit)) {
459
+ value |= (1 << bit)
460
+ } else {
461
+ value &= ~(1 << bit)
462
+ }
463
+ }
464
+ }
465
+
466
+ return value & 0xFF
467
+ }
468
+
469
+ private readPortB(): number {
470
+ let value = 0xFF
471
+
472
+ // Determine input sources from attachments (priority-based multiplexing)
473
+ let externalInput = 0xFF
474
+
475
+ // Query all Port B attachments in priority order
476
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
477
+ if (this.portB_attachments[i] !== null && this.portB_attachments[i]!.isEnabled()) {
478
+ const attachmentData = this.portB_attachments[i]!.readPortB(this.regDDRB, this.regORB)
479
+ // First enabled attachment with data (not 0xFF) wins, or AND all values together
480
+ externalInput &= attachmentData
481
+ }
482
+ }
483
+
484
+ // Apply DDR settings: output bits come from ORB, input bits from external
485
+ for (let bit = 0; bit < 8; bit++) {
486
+ if (this.regDDRB & (1 << bit)) {
487
+ // Output mode - read from register
488
+ if (this.regORB & (1 << bit)) {
489
+ value |= (1 << bit)
490
+ } else {
491
+ value &= ~(1 << bit)
492
+ }
493
+ } else {
494
+ // Input mode - read from external source
495
+ if (externalInput & (1 << bit)) {
496
+ value |= (1 << bit)
497
+ } else {
498
+ value &= ~(1 << bit)
499
+ }
500
+ }
501
+ }
502
+
503
+ return value & 0xFF
504
+ }
505
+
506
+ private writePortA(value: number): void {
507
+ // Notify all Port A attachments of the write
508
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
509
+ if (this.portA_attachments[i] !== null) {
510
+ this.portA_attachments[i]!.writePortA(value, this.regDDRA)
511
+ }
512
+ }
513
+ }
514
+
515
+ private writePortB(value: number): void {
516
+ // Notify all Port B attachments of the write
517
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
518
+ if (this.portB_attachments[i] !== null) {
519
+ this.portB_attachments[i]!.writePortB(value, this.regDDRB)
520
+ }
521
+ }
522
+ }
523
+
524
+ private updateCA2(): void {
525
+ // CA2 control based on PCR bits 1-3
526
+ const ca2_control = (this.regPCR >> 1) & 0x07
527
+
528
+ switch (ca2_control) {
529
+ case 0x00: // Input mode - negative edge
530
+ case 0x01: // Independent interrupt input - negative edge
531
+ case 0x02: // Input mode - positive edge
532
+ case 0x03: // Independent interrupt input - positive edge
533
+ // Input modes
534
+ break
535
+
536
+ case 0x04: // Handshake output
537
+ case 0x05: // Pulse output
538
+ // Output modes
539
+ break
540
+
541
+ case 0x06: // Manual output LOW
542
+ this.CA2 = false
543
+ break
544
+
545
+ case 0x07: // Manual output HIGH
546
+ this.CA2 = true
547
+ break
548
+ }
549
+
550
+ // Notify all attachments of control line changes
551
+ this.notifyAttachmentsControlLines()
552
+ }
553
+
554
+ private updateCB2(): void {
555
+ // CB2 control based on PCR bits 5-7
556
+ const cb2_control = (this.regPCR >> 5) & 0x07
557
+
558
+ switch (cb2_control) {
559
+ case 0x00: // Input mode - negative edge
560
+ case 0x01: // Independent interrupt input - negative edge
561
+ case 0x02: // Input mode - positive edge
562
+ case 0x03: // Independent interrupt input - positive edge
563
+ // Input modes
564
+ break
565
+
566
+ case 0x04: // Handshake output
567
+ case 0x05: // Pulse output
568
+ // Output modes
569
+ break
570
+
571
+ case 0x06: // Manual output LOW
572
+ this.CB2 = false
573
+ break
574
+
575
+ case 0x07: // Manual output HIGH
576
+ this.CB2 = true
577
+ break
578
+ }
579
+
580
+ // Notify all attachments of control line changes
581
+ this.notifyAttachmentsControlLines()
582
+ }
583
+
584
+ private notifyAttachmentsControlLines(): void {
585
+ // Notify all attachments of control line state changes
586
+ for (let i = 0; i < this.portA_attachmentCount; i++) {
587
+ if (this.portA_attachments[i] !== null) {
588
+ this.portA_attachments[i]!.updateControlLines(this.CA1, this.CA2, this.CB1, this.CB2)
589
+ }
590
+ }
591
+ for (let i = 0; i < this.portB_attachmentCount; i++) {
592
+ if (this.portB_attachments[i] !== null) {
593
+ this.portB_attachments[i]!.updateControlLines(this.CA1, this.CA2, this.CB1, this.CB2)
594
+ }
595
+ }
596
+ }
597
+
598
+ private sortAttachmentsByPriority(): void {
599
+ // Simple bubble sort for Port A attachments by priority (lower = higher priority)
600
+ for (let i = 0; i < this.portA_attachmentCount - 1; i++) {
601
+ for (let j = 0; j < this.portA_attachmentCount - i - 1; j++) {
602
+ if (this.portA_attachments[j] !== null && this.portA_attachments[j + 1] !== null) {
603
+ if (this.portA_attachments[j]!.getPriority() > this.portA_attachments[j + 1]!.getPriority()) {
604
+ // Swap
605
+ const temp = this.portA_attachments[j]
606
+ this.portA_attachments[j] = this.portA_attachments[j + 1]
607
+ this.portA_attachments[j + 1] = temp
608
+ }
609
+ }
610
+ }
611
+ }
612
+
613
+ // Simple bubble sort for Port B attachments by priority
614
+ for (let i = 0; i < this.portB_attachmentCount - 1; i++) {
615
+ for (let j = 0; j < this.portB_attachmentCount - i - 1; j++) {
616
+ if (this.portB_attachments[j] !== null && this.portB_attachments[j + 1] !== null) {
617
+ if (this.portB_attachments[j]!.getPriority() > this.portB_attachments[j + 1]!.getPriority()) {
618
+ // Swap
619
+ const temp = this.portB_attachments[j]
620
+ this.portB_attachments[j] = this.portB_attachments[j + 1]
621
+ this.portB_attachments[j + 1] = temp
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Attach a GPIO device to Port A
630
+ * @param attachment - The attachment to add
631
+ */
632
+ attachToPortA(attachment: GPIOAttachment): void {
633
+ if (attachment !== null && this.portA_attachmentCount < GPIOCard.MAX_ATTACHMENTS_PER_PORT) {
634
+ this.portA_attachments[this.portA_attachmentCount++] = attachment
635
+ this.sortAttachmentsByPriority()
636
+ // Notify the attachment of current control line states
637
+ attachment.updateControlLines(this.CA1, this.CA2, this.CB1, this.CB2)
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Attach a GPIO device to Port B
643
+ * @param attachment - The attachment to add
644
+ */
645
+ attachToPortB(attachment: GPIOAttachment): void {
646
+ if (attachment !== null && this.portB_attachmentCount < GPIOCard.MAX_ATTACHMENTS_PER_PORT) {
647
+ this.portB_attachments[this.portB_attachmentCount++] = attachment
648
+ this.sortAttachmentsByPriority()
649
+ // Notify the attachment of current control line states
650
+ attachment.updateControlLines(this.CA1, this.CA2, this.CB1, this.CB2)
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Get a Port A attachment by index
656
+ * @param index - The attachment index
657
+ * @returns The attachment or null if not found
658
+ */
659
+ getPortAAttachment(index: number): GPIOAttachment | null {
660
+ if (index < this.portA_attachmentCount) {
661
+ return this.portA_attachments[index]
662
+ }
663
+ return null
664
+ }
665
+
666
+ /**
667
+ * Get a Port B attachment by index
668
+ * @param index - The attachment index
669
+ * @returns The attachment or null if not found
670
+ */
671
+ getPortBAttachment(index: number): GPIOAttachment | null {
672
+ if (index < this.portB_attachmentCount) {
673
+ return this.portB_attachments[index]
674
+ }
675
+ return null
676
+ }
677
+ }