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,530 @@
1
+ import { SerialCard } from '../../components/IO/SerialCard'
2
+
3
+ describe('SerialCard (6551 ACIA)', () => {
4
+ let serialCard: SerialCard
5
+
6
+ beforeEach(() => {
7
+ serialCard = new SerialCard()
8
+ })
9
+
10
+ describe('Initialization', () => {
11
+ it('should initialize with correct default values', () => {
12
+ // Status register should have Transmit Data Register Empty flag set
13
+ const status = serialCard.read(0x01)
14
+ expect(status & 0x10).toBe(0x10) // TDRE bit set
15
+ })
16
+
17
+ it('should have empty transmit and receive buffers', () => {
18
+ // Write should succeed, indicating buffers are empty
19
+ serialCard.write(0x00, 0x42)
20
+ expect(serialCard.read(0x01) & 0x10).toBe(0) // TDRE should be clear after write
21
+ })
22
+ })
23
+
24
+ describe('Register Operations', () => {
25
+ describe('Data Register (0x00)', () => {
26
+ it('should write data to transmit buffer', () => {
27
+ const initialStatus = serialCard.read(0x01)
28
+ serialCard.write(0x00, 0x55)
29
+ const statusAfter = serialCard.read(0x01)
30
+
31
+ // TDRE should be clear after writing data
32
+ expect((statusAfter & 0x10)).toBe(0)
33
+ })
34
+
35
+ it('should read data from receive buffer', () => {
36
+ serialCard.onData(0x42)
37
+ const data = serialCard.read(0x00)
38
+ expect(data).toBe(0x42)
39
+ })
40
+
41
+ it('should mask data to 8 bits', () => {
42
+ serialCard.write(0x00, 0x1FF) // More than 8 bits
43
+ serialCard.onData(0x1FF)
44
+ const data = serialCard.read(0x00)
45
+ expect(data).toBe(0xFF)
46
+ })
47
+
48
+ it('should return last received data if no new data available', () => {
49
+ serialCard.onData(0x42)
50
+ let data = serialCard.read(0x00) // Read the data
51
+ data = serialCard.read(0x00) // Read again with empty buffer
52
+ // Should return last value received
53
+ expect(data).toBe(0x42)
54
+ })
55
+ })
56
+
57
+ describe('Status Register (0x01)', () => {
58
+ it('should report Receive Data Register Full when data available', () => {
59
+ serialCard.onData(0x50)
60
+ const status = serialCard.read(0x01)
61
+ expect(status & 0x08).toBe(0x08) // RDRF bit set
62
+ })
63
+
64
+ it('should clear RDRF after reading data', () => {
65
+ serialCard.onData(0x50)
66
+ serialCard.read(0x00) // Read the data
67
+ const status = serialCard.read(0x01)
68
+ expect(status & 0x08).toBe(0) // RDRF bit clear
69
+ })
70
+
71
+ it('should report Transmit Data Register Empty when buffer empty', () => {
72
+ const status = serialCard.read(0x01)
73
+ expect(status & 0x10).toBe(0x10) // TDRE bit set
74
+ })
75
+
76
+ it('should clear TDRE after writing data', () => {
77
+ serialCard.write(0x00, 0x42)
78
+ const status = serialCard.read(0x01)
79
+ expect(status & 0x10).toBe(0) // TDRE bit clear
80
+ })
81
+
82
+ it('should report Data Set Ready (DSR) always set', () => {
83
+ const status = serialCard.read(0x01)
84
+ expect(status & 0x40).toBe(0x40) // DSR bit set
85
+ })
86
+
87
+ it('should report Data Carrier Detect (DCD) always clear', () => {
88
+ const status = serialCard.read(0x01)
89
+ expect(status & 0x20).toBe(0) // DCD bit clear
90
+ })
91
+
92
+ it('should report parity error flag', () => {
93
+ // Trigger parity error by writing then reading programmed reset
94
+ serialCard.onData(0x50)
95
+ serialCard.write(0x01, 0x00) // Programmed reset clears errors
96
+ let status = serialCard.read(0x01)
97
+ expect(status & 0x01).toBe(0) // Parity error cleared
98
+
99
+ // Note: This test verifies the flag can be cleared
100
+ })
101
+
102
+ it('should report framing error flag', () => {
103
+ const status = serialCard.read(0x01)
104
+ expect(status & 0x02).toBe(0) // Framing error not set initially
105
+ })
106
+
107
+ it('should report overrun flag', () => {
108
+ const status = serialCard.read(0x01)
109
+ expect(status & 0x04).toBe(0) // Overrun not set initially
110
+ })
111
+
112
+ it('should report IRQ flag', () => {
113
+ const status = serialCard.read(0x01)
114
+ expect(status & 0x80).toBe(0) // IRQ not set initially
115
+ })
116
+ })
117
+
118
+ describe('Command Register (0x02)', () => {
119
+ it('should return 0 on read (write-only)', () => {
120
+ serialCard.write(0x02, 0xFF)
121
+ const data = serialCard.read(0x02)
122
+ expect(data).toBe(0)
123
+ })
124
+
125
+ it('should mask command data to 8 bits', () => {
126
+ serialCard.write(0x02, 0x1FF)
127
+ // Should not throw and should process command
128
+ })
129
+
130
+ it('should enable receive IRQ when bit 2 is set', () => {
131
+ const mockIRQ = jest.fn()
132
+ serialCard.raiseIRQ = mockIRQ
133
+
134
+ serialCard.write(0x02, 0x04) // Enable receive IRQ
135
+ serialCard.onData(0x42)
136
+
137
+ expect(mockIRQ).toHaveBeenCalled()
138
+ })
139
+
140
+ it('should disable receive IRQ when bit 2 is clear', () => {
141
+ const mockIRQ = jest.fn()
142
+ serialCard.raiseIRQ = mockIRQ
143
+
144
+ serialCard.write(0x02, 0x00) // Disable receive IRQ
145
+ serialCard.onData(0x42)
146
+
147
+ expect(mockIRQ).not.toHaveBeenCalled()
148
+ })
149
+
150
+ it('should enable echo mode when bit 5 is set', () => {
151
+ serialCard.write(0x02, 0x20) // Enable echo mode
152
+ serialCard.onData(0x42)
153
+
154
+ // In echo mode, received data is added to transmit buffer
155
+ // Verify the transmit buffer will send the echoed byte
156
+ const statusBeforeTick = serialCard.read(0x01)
157
+ expect(statusBeforeTick & 0x10).toBe(0) // TDRE should be clear (data in transmit buffer)
158
+ })
159
+ })
160
+
161
+ describe('Control Register (0x03)', () => {
162
+ it('should return 0 on read (write-only)', () => {
163
+ serialCard.write(0x03, 0xFF)
164
+ const data = serialCard.read(0x03)
165
+ expect(data).toBe(0)
166
+ })
167
+
168
+ it('should mask control data to 8 bits', () => {
169
+ serialCard.write(0x03, 0x1FF)
170
+ // Should not throw and should process control
171
+ })
172
+
173
+ it('should set default baud rate to 115200', () => {
174
+ serialCard.write(0x03, 0x00) // Code 0000
175
+ serialCard.write(0x00, 0x42)
176
+
177
+ // Baud rate affects tick behavior; we'll test tick timing later
178
+ })
179
+ })
180
+ })
181
+
182
+ describe('Data Transmission', () => {
183
+ it('should transmit data byte via callback', () => {
184
+ const mockTransmit = jest.fn()
185
+ serialCard.transmit = mockTransmit
186
+
187
+ serialCard.write(0x00, 0x42)
188
+
189
+ // Need to tick enough times for byte transmission
190
+ // With default baud of 115200 and 1MHz clock: ~87 cycles per byte
191
+ for (let i = 0; i < 100; i++) {
192
+ serialCard.tick(1000000)
193
+ }
194
+
195
+ expect(mockTransmit).toHaveBeenCalledWith(0x42)
196
+ })
197
+
198
+ it('should handle multiple bytes in transmission', () => {
199
+ const mockTransmit = jest.fn()
200
+ serialCard.transmit = mockTransmit
201
+
202
+ serialCard.write(0x00, 0x42)
203
+ serialCard.write(0x00, 0x43)
204
+ serialCard.write(0x00, 0x44)
205
+
206
+ for (let i = 0; i < 300; i++) {
207
+ serialCard.tick(1000000)
208
+ }
209
+
210
+ expect(mockTransmit).toHaveBeenNthCalledWith(1, 0x42)
211
+ expect(mockTransmit).toHaveBeenNthCalledWith(2, 0x43)
212
+ expect(mockTransmit).toHaveBeenNthCalledWith(3, 0x44)
213
+ })
214
+
215
+ it('should set TDRE flag after transmission complete', () => {
216
+ const mockTransmit = jest.fn()
217
+ serialCard.transmit = mockTransmit
218
+
219
+ serialCard.write(0x00, 0x42)
220
+ expect(serialCard.read(0x01) & 0x10).toBe(0) // TDRE clear
221
+
222
+ for (let i = 0; i < 100; i++) {
223
+ serialCard.tick(1000000)
224
+ }
225
+
226
+ expect(serialCard.read(0x01) & 0x10).toBe(0x10) // TDRE set
227
+ })
228
+ })
229
+
230
+ describe('Data Reception', () => {
231
+ it('should receive data from external source', () => {
232
+ serialCard.onData(0x55)
233
+ const data = serialCard.read(0x00)
234
+ expect(data).toBe(0x55)
235
+ })
236
+
237
+ it('should set RDRF flag when data received', () => {
238
+ serialCard.onData(0x55)
239
+ const status = serialCard.read(0x01)
240
+ expect(status & 0x08).toBe(0x08)
241
+ })
242
+
243
+ it('should handle multiple received bytes', () => {
244
+ serialCard.onData(0x41) // 'A'
245
+ serialCard.onData(0x42) // 'B'
246
+ serialCard.onData(0x43) // 'C'
247
+
248
+ expect(serialCard.read(0x00)).toBe(0x41)
249
+ expect(serialCard.read(0x00)).toBe(0x42)
250
+ expect(serialCard.read(0x00)).toBe(0x43)
251
+ })
252
+
253
+ it('should mask received data to 8 bits', () => {
254
+ serialCard.onData(0x1FF)
255
+ const data = serialCard.read(0x00)
256
+ expect(data).toBe(0xFF)
257
+ })
258
+ })
259
+
260
+ describe('Interrupt Handling', () => {
261
+ it('should trigger IRQ on receive interrupt enabled', () => {
262
+ const mockIRQ = jest.fn()
263
+ serialCard.raiseIRQ = mockIRQ
264
+
265
+ serialCard.write(0x02, 0x04) // Enable receive IRQ
266
+ serialCard.onData(0x42)
267
+
268
+ expect(mockIRQ).toHaveBeenCalled()
269
+ expect(serialCard.read(0x01) & 0x80).toBe(0x80) // IRQ flag set
270
+ })
271
+
272
+ it('should trigger IRQ on transmit complete when enabled', () => {
273
+ const mockIRQ = jest.fn()
274
+ const mockTransmit = jest.fn()
275
+ serialCard.raiseIRQ = mockIRQ
276
+ serialCard.transmit = mockTransmit
277
+
278
+ serialCard.write(0x03, 0x00) // Set control register
279
+ serialCard.write(0x02, 0x08) // Enable transmit IRQ
280
+ serialCard.write(0x00, 0x42)
281
+
282
+ for (let i = 0; i < 100; i++) {
283
+ serialCard.tick(1000000)
284
+ }
285
+
286
+ // Should trigger IRQ when transmission complete
287
+ expect(serialCard.read(0x01) & 0x80).toBe(0x80)
288
+ })
289
+
290
+ it('should not trigger receive IRQ when disabled', () => {
291
+ const mockIRQ = jest.fn()
292
+ serialCard.raiseIRQ = mockIRQ
293
+
294
+ serialCard.write(0x02, 0x00) // Disable receive IRQ
295
+ serialCard.onData(0x42)
296
+
297
+ expect(mockIRQ).not.toHaveBeenCalled()
298
+ })
299
+
300
+ it('should clear IRQ flag when data is read', () => {
301
+ const mockIRQ = jest.fn()
302
+ serialCard.raiseIRQ = mockIRQ
303
+
304
+ serialCard.write(0x02, 0x04) // Enable receive IRQ
305
+ serialCard.onData(0x42)
306
+
307
+ expect(serialCard.read(0x01) & 0x80).toBe(0x80) // IRQ set
308
+
309
+ serialCard.read(0x00) // Read data
310
+
311
+ expect(serialCard.read(0x01) & 0x80).toBe(0) // IRQ cleared
312
+ })
313
+ })
314
+
315
+ describe('Overrun Handling', () => {
316
+ it('should detect overrun condition', () => {
317
+ serialCard.onData(0x42)
318
+ const statusBefore = serialCard.read(0x01)
319
+
320
+ // Send another byte before first is read
321
+ serialCard.onData(0x43)
322
+ const statusAfter = serialCard.read(0x01)
323
+
324
+ expect(statusAfter & 0x04).toBe(0x04) // Overrun flag set
325
+ })
326
+
327
+ it('should clear overrun after all buffered data is read', () => {
328
+ serialCard.onData(0x42)
329
+ serialCard.onData(0x43) // Cause overrun
330
+
331
+ serialCard.read(0x00) // Read first byte (0x42)
332
+ let status = serialCard.read(0x01)
333
+ expect(status & 0x04).toBe(0x04) // Overrun still set (0x43 in buffer)
334
+
335
+ serialCard.read(0x00) // Read second byte (0x43)
336
+ status = serialCard.read(0x01)
337
+ expect(status & 0x04).toBe(0) // Overrun cleared now (buffer empty)
338
+ })
339
+ })
340
+
341
+ describe('Echo Mode', () => {
342
+ it('should echo received data when echo mode enabled', () => {
343
+ const mockTransmit = jest.fn()
344
+ serialCard.transmit = mockTransmit
345
+
346
+ serialCard.write(0x02, 0x20) // Enable echo mode
347
+ serialCard.onData(0x42)
348
+
349
+ // Tick to allow transmission
350
+ for (let i = 0; i < 100; i++) {
351
+ serialCard.tick(1000000)
352
+ }
353
+
354
+ expect(mockTransmit).toHaveBeenCalledWith(0x42)
355
+ })
356
+
357
+ it('should not echo when echo mode disabled', () => {
358
+ const mockTransmit = jest.fn()
359
+ serialCard.transmit = mockTransmit
360
+
361
+ serialCard.write(0x02, 0x00) // Echo mode disabled
362
+ serialCard.onData(0x42)
363
+
364
+ for (let i = 0; i < 100; i++) {
365
+ serialCard.tick(1000000)
366
+ }
367
+
368
+ expect(mockTransmit).not.toHaveBeenCalled()
369
+ })
370
+
371
+ it('should clear TDRE flag in echo mode', () => {
372
+ serialCard.write(0x02, 0x20) // Enable echo mode
373
+ serialCard.onData(0x42)
374
+
375
+ const status = serialCard.read(0x01)
376
+ expect(status & 0x10).toBe(0) // TDRE should be clear
377
+ })
378
+ })
379
+
380
+ describe('Baud Rate Configuration', () => {
381
+ it('should support multiple baud rates', () => {
382
+ const baudRates = [
383
+ [0x00, 115200],
384
+ [0x01, 50],
385
+ [0x02, 75],
386
+ [0x03, 110],
387
+ [0x04, 135],
388
+ [0x05, 150],
389
+ [0x06, 300],
390
+ [0x07, 600],
391
+ [0x08, 1200],
392
+ [0x09, 1800],
393
+ [0x0A, 2400],
394
+ [0x0B, 3600],
395
+ [0x0C, 4800],
396
+ [0x0D, 7200],
397
+ [0x0E, 9600],
398
+ [0x0F, 19200]
399
+ ]
400
+
401
+ baudRates.forEach(([code, rate]) => {
402
+ const card = new SerialCard()
403
+ card.write(0x03, code as number) // Control register with baud rate code
404
+ // Baud rate affects tick timing - verify no errors occur
405
+ })
406
+ })
407
+ })
408
+
409
+ describe('Reset Operations', () => {
410
+ it('should perform programmed reset', () => {
411
+ // First setup: send some data so transmit buffer is not empty
412
+ serialCard.write(0x00, 0x42)
413
+ serialCard.write(0x00, 0x43)
414
+ serialCard.write(0x02, 0xFF) // Set command register
415
+ serialCard.onData(0x44)
416
+
417
+ // Now perform programmed reset
418
+ serialCard.write(0x01, 0x00) // Programmed reset via status register write
419
+
420
+ const status = serialCard.read(0x01)
421
+ // Programmed reset clears status flags and IRQ, but does not clear buffers
422
+ expect(status & 0x80).toBe(0) // IRQ flag cleared
423
+ expect(status & 0x60).toBe(0x40) // DSR set, DCD clear
424
+ })
425
+
426
+ it('should reset all registers on cold start', () => {
427
+ serialCard.write(0x00, 0x42)
428
+ serialCard.write(0x02, 0xFF)
429
+ serialCard.write(0x03, 0xFF)
430
+ serialCard.onData(0x42)
431
+
432
+ serialCard.reset(true)
433
+
434
+ expect(serialCard.read(0x01) & 0x10).toBe(0x10) // TDRE set
435
+ expect(serialCard.read(0x01) & 0x08).toBe(0) // RDRF clear
436
+ expect(serialCard.read(0x01) & 0x80).toBe(0) // IRQ clear
437
+ })
438
+
439
+ it('should clear transmit buffer on reset', () => {
440
+ serialCard.write(0x00, 0x42)
441
+ serialCard.write(0x00, 0x43)
442
+
443
+ serialCard.reset(true)
444
+
445
+ expect(serialCard.read(0x01) & 0x10).toBe(0x10) // TDRE should be set (buffer empty)
446
+ })
447
+
448
+ it('should clear receive buffer on reset', () => {
449
+ serialCard.onData(0x42)
450
+
451
+ serialCard.reset(true)
452
+
453
+ expect(serialCard.read(0x01) & 0x08).toBe(0) // RDRF should be clear (buffer empty)
454
+ })
455
+ })
456
+
457
+ describe('Register Address Masking', () => {
458
+ it('should mask address to lower 2 bits', () => {
459
+ // Test with different address values that map to same register
460
+ serialCard.write(0x00, 0x42)
461
+ serialCard.write(0x04, 0x43) // Should write to register 0
462
+ const status = serialCard.read(0x01)
463
+ expect(status & 0x10).toBe(0) // TDRE clear, so there's data
464
+ })
465
+ })
466
+
467
+ describe('Callback Functions', () => {
468
+ it('should support custom transmit callback', () => {
469
+ const mockTransmit = jest.fn()
470
+ serialCard.transmit = mockTransmit
471
+
472
+ serialCard.write(0x00, 0x42)
473
+ for (let i = 0; i < 100; i++) {
474
+ serialCard.tick(1000000)
475
+ }
476
+
477
+ expect(mockTransmit).toHaveBeenCalled()
478
+ })
479
+
480
+ it('should support custom IRQ callback', () => {
481
+ const mockIRQ = jest.fn()
482
+ serialCard.raiseIRQ = mockIRQ
483
+
484
+ serialCard.write(0x02, 0x04)
485
+ serialCard.onData(0x42)
486
+
487
+ expect(mockIRQ).toHaveBeenCalled()
488
+ })
489
+
490
+ it('should support custom NMI callback', () => {
491
+ const mockNMI = jest.fn()
492
+ serialCard.raiseNMI = mockNMI
493
+
494
+ // NMI not currently triggered in implementation, but callback exists
495
+ expect(typeof serialCard.raiseNMI).toBe('function')
496
+ })
497
+ })
498
+
499
+ describe('Edge Cases', () => {
500
+ it('should handle zero-length tick', () => {
501
+ serialCard.write(0x00, 0x42)
502
+ serialCard.tick(0)
503
+ // Should not crash
504
+ })
505
+
506
+ it('should handle rapid consecutive writes', () => {
507
+ for (let i = 0; i < 100; i++) {
508
+ serialCard.write(0x00, i & 0xFF)
509
+ }
510
+ // Should queue all data without error
511
+ })
512
+
513
+ it('should handle rapid consecutive reads from empty receive buffer', () => {
514
+ for (let i = 0; i < 100; i++) {
515
+ const data = serialCard.read(0x00)
516
+ expect(typeof data).toBe('number')
517
+ }
518
+ })
519
+
520
+ it('should handle interleaved reads and writes', () => {
521
+ serialCard.write(0x00, 0x42)
522
+ const data1 = serialCard.read(0x01) // Read status
523
+ serialCard.write(0x00, 0x43)
524
+ const data2 = serialCard.read(0x01) // Read status again
525
+
526
+ expect(typeof data1).toBe('number')
527
+ expect(typeof data2).toBe('number')
528
+ })
529
+ })
530
+ })