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,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
|
+
})
|