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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/components/CPU.js +1170 -0
- package/dist/components/CPU.js.map +1 -0
- package/dist/components/Cart.js +23 -0
- package/dist/components/Cart.js.map +1 -0
- package/dist/components/IO/Empty.js +19 -0
- package/dist/components/IO/Empty.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/GPIOCard.js +597 -0
- package/dist/components/IO/GPIOCard.js.map +1 -0
- package/dist/components/IO/InputBoard.js +19 -0
- package/dist/components/IO/InputBoard.js.map +1 -0
- package/dist/components/IO/LCDCard.js +19 -0
- package/dist/components/IO/LCDCard.js.map +1 -0
- package/dist/components/IO/RAMCard.js +63 -0
- package/dist/components/IO/RAMCard.js.map +1 -0
- package/dist/components/IO/RTCCard.js +483 -0
- package/dist/components/IO/RTCCard.js.map +1 -0
- package/dist/components/IO/SerialCard.js +282 -0
- package/dist/components/IO/SerialCard.js.map +1 -0
- package/dist/components/IO/SoundCard.js +620 -0
- package/dist/components/IO/SoundCard.js.map +1 -0
- package/dist/components/IO/StorageCard.js +428 -0
- package/dist/components/IO/StorageCard.js.map +1 -0
- package/dist/components/IO/VGACard.js +9 -0
- package/dist/components/IO/VGACard.js.map +1 -0
- package/dist/components/IO/VideoCard.js +623 -0
- package/dist/components/IO/VideoCard.js.map +1 -0
- package/dist/components/IO.js +3 -0
- package/dist/components/IO.js.map +1 -0
- package/dist/components/Machine.js +310 -0
- package/dist/components/Machine.js.map +1 -0
- package/dist/components/RAM.js +24 -0
- package/dist/components/RAM.js.map +1 -0
- package/dist/components/ROM.js +23 -0
- package/dist/components/ROM.js.map +1 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/CPU.test.js +1626 -0
- package/dist/tests/CPU.test.js.map +1 -0
- package/dist/tests/Cart.test.js +119 -0
- package/dist/tests/Cart.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js +503 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -0
- package/dist/tests/IO/RAMCard.test.js +229 -0
- package/dist/tests/IO/RAMCard.test.js.map +1 -0
- package/dist/tests/IO/RTCCard.test.js +177 -0
- package/dist/tests/IO/RTCCard.test.js.map +1 -0
- package/dist/tests/IO/SerialCard.test.js +423 -0
- package/dist/tests/IO/SerialCard.test.js.map +1 -0
- package/dist/tests/IO/SoundCard.test.js +528 -0
- package/dist/tests/IO/SoundCard.test.js.map +1 -0
- package/dist/tests/IO/StorageCard.test.js +647 -0
- package/dist/tests/IO/StorageCard.test.js.map +1 -0
- package/dist/tests/IO/VideoCard.test.js +549 -0
- package/dist/tests/IO/VideoCard.test.js.map +1 -0
- package/dist/tests/Machine.test.js +383 -0
- package/dist/tests/Machine.test.js.map +1 -0
- package/dist/tests/RAM.test.js +160 -0
- package/dist/tests/RAM.test.js.map +1 -0
- package/dist/tests/ROM.test.js +123 -0
- package/dist/tests/ROM.test.js.map +1 -0
- package/jest.config.cjs +9 -0
- package/package.json +43 -0
- package/src/components/CPU.ts +1371 -0
- package/src/components/Cart.ts +20 -0
- package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
- package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
- package/src/components/IO/GPIOCard.ts +677 -0
- package/src/components/IO/RAMCard.ts +68 -0
- package/src/components/IO/RTCCard.ts +518 -0
- package/src/components/IO/SerialCard.ts +335 -0
- package/src/components/IO/SoundCard.ts +711 -0
- package/src/components/IO/StorageCard.ts +473 -0
- package/src/components/IO/VideoCard.ts +730 -0
- package/src/components/IO.ts +11 -0
- package/src/components/Machine.ts +364 -0
- package/src/components/RAM.ts +23 -0
- package/src/components/ROM.ts +19 -0
- package/src/index.ts +474 -0
- package/src/tests/CPU.test.ts +2045 -0
- package/src/tests/Cart.test.ts +149 -0
- package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
- package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
- package/src/tests/IO/GPIOCard.test.ts +644 -0
- package/src/tests/IO/RAMCard.test.ts +284 -0
- package/src/tests/IO/RTCCard.test.ts +222 -0
- package/src/tests/IO/SerialCard.test.ts +530 -0
- package/src/tests/IO/SoundCard.test.ts +659 -0
- package/src/tests/IO/StorageCard.test.ts +787 -0
- package/src/tests/IO/VideoCard.test.ts +668 -0
- package/src/tests/Machine.test.ts +437 -0
- package/src/tests/RAM.test.ts +196 -0
- package/src/tests/ROM.test.ts +154 -0
- 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
|
+
}
|