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,787 @@
1
+ import { StorageCard } from '../../components/IO/StorageCard'
2
+ import { writeFile, unlink, readFile } from 'fs/promises'
3
+ import { existsSync } from 'fs'
4
+ import { tmpdir } from 'os'
5
+ import { join } from 'path'
6
+
7
+ describe('StorageCard (Compact Flash in IDE Mode)', () => {
8
+ let storageCard: StorageCard
9
+
10
+ beforeEach(() => {
11
+ storageCard = new StorageCard()
12
+ })
13
+
14
+ describe('Initialization', () => {
15
+ it('should initialize with correct default register values', () => {
16
+ expect(storageCard.read(0x01)).toBe(0x00) // Error Register
17
+ expect(storageCard.read(0x02)).toBe(0x00) // Sector Count
18
+ expect(storageCard.read(0x03)).toBe(0x00) // LBA0
19
+ expect(storageCard.read(0x04)).toBe(0x00) // LBA1
20
+ expect(storageCard.read(0x05)).toBe(0x00) // LBA2
21
+ expect(storageCard.read(0x06)).toBe(0xE0) // LBA3 (with mode bits)
22
+ expect(storageCard.read(0x07) & 0x40).toBe(0x40) // Status (RDY bit)
23
+ })
24
+
25
+ it('should have IRQ and NMI callbacks', () => {
26
+ expect(typeof storageCard.raiseIRQ).toBe('function')
27
+ expect(typeof storageCard.raiseNMI).toBe('function')
28
+ })
29
+
30
+ it('should initialize with all storage zeroed', () => {
31
+ // Verify first sector is all zeros
32
+ storageCard.write(0x02, 1) // Sector count = 1
33
+ storageCard.write(0x03, 0) // LBA = 0
34
+ storageCard.write(0x07, 0x20) // Read sector command
35
+
36
+ for (let i = 0; i < 512; i++) {
37
+ expect(storageCard.read(0x00)).toBe(0x00)
38
+ }
39
+ })
40
+ })
41
+
42
+ describe('Register Operations', () => {
43
+ describe('Address Masking', () => {
44
+ it('should mask address to lower 3 bits', () => {
45
+ storageCard.write(0x02, 0x42) // Sector count register
46
+ expect(storageCard.read(0x0A)).toBe(0x42) // 0x0A & 0x07 = 0x02
47
+ expect(storageCard.read(0x12)).toBe(0x42) // 0x12 & 0x07 = 0x02
48
+ expect(storageCard.read(0x1A)).toBe(0x42) // 0x1A & 0x07 = 0x02
49
+ })
50
+ })
51
+
52
+ describe('LBA Registers', () => {
53
+ it('should write and read LBA0', () => {
54
+ storageCard.write(0x03, 0xAB)
55
+ expect(storageCard.read(0x03)).toBe(0xAB)
56
+ })
57
+
58
+ it('should write and read LBA1', () => {
59
+ storageCard.write(0x04, 0xCD)
60
+ expect(storageCard.read(0x04)).toBe(0xCD)
61
+ })
62
+
63
+ it('should write and read LBA2', () => {
64
+ storageCard.write(0x05, 0xEF)
65
+ expect(storageCard.read(0x05)).toBe(0xEF)
66
+ })
67
+
68
+ it('should mask LBA3 to lower 4 bits and set mode bits', () => {
69
+ storageCard.write(0x06, 0xFF)
70
+ expect(storageCard.read(0x06)).toBe(0xEF) // 0xFF & 0x0F | 0xE0
71
+
72
+ storageCard.write(0x06, 0x05)
73
+ expect(storageCard.read(0x06)).toBe(0xE5) // 0x05 & 0x0F | 0xE0
74
+ })
75
+ })
76
+
77
+ describe('Sector Count Register', () => {
78
+ it('should write and read sector count', () => {
79
+ storageCard.write(0x02, 1)
80
+ expect(storageCard.read(0x02)).toBe(1)
81
+
82
+ storageCard.write(0x02, 10)
83
+ expect(storageCard.read(0x02)).toBe(10)
84
+ })
85
+ })
86
+
87
+ describe('Feature/Error Register', () => {
88
+ it('should read error register', () => {
89
+ const error = storageCard.read(0x01)
90
+ expect(error).toBe(0x00) // No error initially
91
+ })
92
+ })
93
+
94
+ describe('Status Register', () => {
95
+ it('should have RDY bit set initially', () => {
96
+ const status = storageCard.read(0x07)
97
+ expect(status & 0x40).toBe(0x40) // RDY bit
98
+ })
99
+
100
+ it('should not have ERR bit set initially', () => {
101
+ const status = storageCard.read(0x07)
102
+ expect(status & 0x01).toBe(0x00) // ERR bit
103
+ })
104
+
105
+ it('should not have DRQ bit set initially', () => {
106
+ const status = storageCard.read(0x07)
107
+ expect(status & 0x08).toBe(0x00) // DRQ bit
108
+ })
109
+ })
110
+ })
111
+
112
+ describe('Identify Drive Command (0xEC)', () => {
113
+ it('should set DRQ flag after identify command', () => {
114
+ storageCard.write(0x07, 0xEC)
115
+ const status = storageCard.read(0x07)
116
+ expect(status & 0x08).toBe(0x08) // DRQ set
117
+ })
118
+
119
+ it('should return 512 bytes of identity data', () => {
120
+ storageCard.write(0x07, 0xEC)
121
+
122
+ let byteCount = 0
123
+ while (storageCard.read(0x07) & 0x08) { // While DRQ is set
124
+ storageCard.read(0x00)
125
+ byteCount++
126
+ }
127
+
128
+ expect(byteCount).toBe(512)
129
+ })
130
+
131
+ it('should clear DRQ flag after reading all identity data', () => {
132
+ storageCard.write(0x07, 0xEC)
133
+
134
+ // Read all 512 bytes
135
+ for (let i = 0; i < 512; i++) {
136
+ storageCard.read(0x00)
137
+ }
138
+
139
+ const status = storageCard.read(0x07)
140
+ expect(status & 0x08).toBe(0x00) // DRQ cleared
141
+ })
142
+
143
+ it('should contain valid identity data', () => {
144
+ storageCard.write(0x07, 0xEC)
145
+
146
+ const identity: number[] = []
147
+ for (let i = 0; i < 512; i++) {
148
+ identity.push(storageCard.read(0x00))
149
+ }
150
+
151
+ // Check general configuration (word 0)
152
+ expect(identity[0]).toBe(0x84)
153
+ expect(identity[1]).toBe(0x8A)
154
+
155
+ // Check serial number starts at byte 20
156
+ const serial = String.fromCharCode(...identity.slice(20, 40))
157
+ expect(serial).toBe('ACWD6502EMUCF1010101')
158
+
159
+ // Check firmware revision starts at byte 46
160
+ const firmware = String.fromCharCode(...identity.slice(46, 54))
161
+ expect(firmware).toBe('1.0 ')
162
+
163
+ // Check model number starts at byte 54
164
+ const model = String.fromCharCode(...identity.slice(54, 94))
165
+ expect(model).toContain('ACWD6502EMUCF')
166
+ })
167
+ })
168
+
169
+ describe('Read Sector Command (0x20/0x21)', () => {
170
+ it('should set DRQ flag after read sector command', () => {
171
+ storageCard.write(0x02, 1) // 1 sector
172
+ storageCard.write(0x03, 0) // LBA = 0
173
+ storageCard.write(0x07, 0x20) // Read sector
174
+
175
+ const status = storageCard.read(0x07)
176
+ expect(status & 0x08).toBe(0x08) // DRQ set
177
+ })
178
+
179
+ it('should read a single sector', () => {
180
+ storageCard.write(0x02, 1)
181
+ storageCard.write(0x03, 0)
182
+ storageCard.write(0x07, 0x20)
183
+
184
+ let byteCount = 0
185
+ while (storageCard.read(0x07) & 0x08) {
186
+ storageCard.read(0x00)
187
+ byteCount++
188
+ }
189
+
190
+ expect(byteCount).toBe(512)
191
+ })
192
+
193
+ it('should clear DRQ after reading all sector data', () => {
194
+ storageCard.write(0x02, 1)
195
+ storageCard.write(0x03, 0)
196
+ storageCard.write(0x07, 0x20)
197
+
198
+ for (let i = 0; i < 512; i++) {
199
+ storageCard.read(0x00)
200
+ }
201
+
202
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00)
203
+ })
204
+
205
+ it('should report error for invalid sector (too high)', () => {
206
+ storageCard.write(0x02, 1)
207
+ storageCard.write(0x03, 0xFF) // LBA = 0x0FFFFFFF (invalid)
208
+ storageCard.write(0x04, 0xFF)
209
+ storageCard.write(0x05, 0xFF)
210
+ storageCard.write(0x06, 0xFF)
211
+ storageCard.write(0x07, 0x20)
212
+
213
+ const status = storageCard.read(0x07)
214
+ const error = storageCard.read(0x01)
215
+
216
+ expect(status & 0x01).toBe(0x01) // ERR bit set
217
+ expect(error & 0x10).toBe(0x10) // IDNF error
218
+ })
219
+
220
+ it('should read multiple sectors', () => {
221
+ storageCard.write(0x02, 3) // 3 sectors
222
+ storageCard.write(0x03, 0)
223
+ storageCard.write(0x07, 0x20)
224
+
225
+ let byteCount = 0
226
+ while (storageCard.read(0x07) & 0x08) {
227
+ storageCard.read(0x00)
228
+ byteCount++
229
+ }
230
+
231
+ expect(byteCount).toBe(512 * 3)
232
+ })
233
+
234
+ it('should work with command 0x21', () => {
235
+ storageCard.write(0x02, 1)
236
+ storageCard.write(0x03, 0)
237
+ storageCard.write(0x07, 0x21) // Alternate read command
238
+
239
+ expect(storageCard.read(0x07) & 0x08).toBe(0x08) // DRQ set
240
+ })
241
+ })
242
+
243
+ describe('Write Sector Command (0x30/0x31)', () => {
244
+ it('should set DRQ flag after write sector command', () => {
245
+ storageCard.write(0x02, 1)
246
+ storageCard.write(0x03, 0)
247
+ storageCard.write(0x07, 0x30) // Write sector
248
+
249
+ const status = storageCard.read(0x07)
250
+ expect(status & 0x08).toBe(0x08) // DRQ set
251
+ })
252
+
253
+ it('should write and read back a single sector', () => {
254
+ // Write sector 0
255
+ storageCard.write(0x02, 1)
256
+ storageCard.write(0x03, 0)
257
+ storageCard.write(0x07, 0x30)
258
+
259
+ for (let i = 0; i < 512; i++) {
260
+ storageCard.write(0x00, i & 0xFF)
261
+ }
262
+
263
+ // Read sector 0
264
+ storageCard.write(0x02, 1)
265
+ storageCard.write(0x03, 0)
266
+ storageCard.write(0x07, 0x20)
267
+
268
+ for (let i = 0; i < 512; i++) {
269
+ expect(storageCard.read(0x00)).toBe(i & 0xFF)
270
+ }
271
+ })
272
+
273
+ it('should clear DRQ after writing all sector data', () => {
274
+ storageCard.write(0x02, 1)
275
+ storageCard.write(0x03, 0)
276
+ storageCard.write(0x07, 0x30)
277
+
278
+ for (let i = 0; i < 512; i++) {
279
+ storageCard.write(0x00, 0xFF)
280
+ }
281
+
282
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00)
283
+ })
284
+
285
+ it('should report error for invalid sector', () => {
286
+ storageCard.write(0x02, 1)
287
+ storageCard.write(0x03, 0xFF)
288
+ storageCard.write(0x04, 0xFF)
289
+ storageCard.write(0x05, 0xFF)
290
+ storageCard.write(0x06, 0xFF)
291
+ storageCard.write(0x07, 0x30)
292
+
293
+ const status = storageCard.read(0x07)
294
+ expect(status & 0x01).toBe(0x01) // ERR bit
295
+ })
296
+
297
+ it('should write multiple sectors', () => {
298
+ storageCard.write(0x02, 2)
299
+ storageCard.write(0x03, 0)
300
+ storageCard.write(0x07, 0x30)
301
+
302
+ for (let i = 0; i < 512 * 2; i++) {
303
+ storageCard.write(0x00, 0xAA)
304
+ }
305
+
306
+ // Verify data was written
307
+ storageCard.write(0x02, 2)
308
+ storageCard.write(0x03, 0)
309
+ storageCard.write(0x07, 0x20)
310
+
311
+ for (let i = 0; i < 512 * 2; i++) {
312
+ expect(storageCard.read(0x00)).toBe(0xAA)
313
+ }
314
+ })
315
+
316
+ it('should work with command 0x31', () => {
317
+ storageCard.write(0x02, 1)
318
+ storageCard.write(0x03, 0)
319
+ storageCard.write(0x07, 0x31) // Alternate write command
320
+
321
+ expect(storageCard.read(0x07) & 0x08).toBe(0x08) // DRQ set
322
+ })
323
+ })
324
+
325
+ describe('Erase Sector Command (0xC0)', () => {
326
+ it('should erase a sector', () => {
327
+ // First write some data
328
+ storageCard.write(0x02, 1)
329
+ storageCard.write(0x03, 5) // Sector 5
330
+ storageCard.write(0x07, 0x30)
331
+
332
+ for (let i = 0; i < 512; i++) {
333
+ storageCard.write(0x00, 0xFF)
334
+ }
335
+
336
+ // Erase the sector
337
+ storageCard.write(0x02, 1)
338
+ storageCard.write(0x03, 5)
339
+ storageCard.write(0x07, 0xC0)
340
+
341
+ // Read back and verify zeros
342
+ storageCard.write(0x02, 1)
343
+ storageCard.write(0x03, 5)
344
+ storageCard.write(0x07, 0x20)
345
+
346
+ for (let i = 0; i < 512; i++) {
347
+ expect(storageCard.read(0x00)).toBe(0x00)
348
+ }
349
+ })
350
+
351
+ it('should report error for invalid sector', () => {
352
+ storageCard.write(0x02, 1)
353
+ storageCard.write(0x03, 0xFF)
354
+ storageCard.write(0x04, 0xFF)
355
+ storageCard.write(0x05, 0xFF)
356
+ storageCard.write(0x06, 0xFF)
357
+ storageCard.write(0x07, 0xC0)
358
+
359
+ const status = storageCard.read(0x07)
360
+ expect(status & 0x01).toBe(0x01) // ERR bit
361
+ })
362
+
363
+ it('should not set DRQ flag', () => {
364
+ storageCard.write(0x02, 1)
365
+ storageCard.write(0x03, 0)
366
+ storageCard.write(0x07, 0xC0)
367
+
368
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00) // DRQ not set
369
+ })
370
+ })
371
+
372
+ describe('Set Features Command (0xEF)', () => {
373
+ it('should accept command without error', () => {
374
+ storageCard.write(0x07, 0xEF)
375
+
376
+ const status = storageCard.read(0x07)
377
+ expect(status & 0x01).toBe(0x00) // No error
378
+ })
379
+
380
+ it('should not set DRQ flag', () => {
381
+ storageCard.write(0x07, 0xEF)
382
+
383
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00)
384
+ })
385
+ })
386
+
387
+ describe('Unsupported Commands', () => {
388
+ it('should report error for unsupported command', () => {
389
+ storageCard.write(0x07, 0xFF) // Invalid command
390
+
391
+ const status = storageCard.read(0x07)
392
+ const error = storageCard.read(0x01)
393
+
394
+ expect(status & 0x01).toBe(0x01) // ERR bit
395
+ expect(error & 0x04).toBe(0x04) // ABRT error
396
+ })
397
+
398
+ it('should not set DRQ for unsupported command', () => {
399
+ storageCard.write(0x07, 0x99)
400
+
401
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00)
402
+ })
403
+ })
404
+
405
+ describe('LBA Addressing', () => {
406
+ it('should correctly calculate 28-bit LBA address', () => {
407
+ // Write pattern to sector at LBA 0x00000045 (using valid low address)
408
+ storageCard.write(0x02, 1)
409
+ storageCard.write(0x03, 0x45) // LBA0
410
+ storageCard.write(0x04, 0x00) // LBA1
411
+ storageCard.write(0x05, 0x00) // LBA2
412
+ storageCard.write(0x06, 0x00) // LBA3 (0x00 & 0x0F | 0xE0 = 0xE0)
413
+ storageCard.write(0x07, 0x30)
414
+
415
+ for (let i = 0; i < 512; i++) {
416
+ storageCard.write(0x00, 0xCC)
417
+ }
418
+
419
+ // Read back
420
+ storageCard.write(0x02, 1)
421
+ storageCard.write(0x03, 0x45)
422
+ storageCard.write(0x04, 0x00)
423
+ storageCard.write(0x05, 0x00)
424
+ storageCard.write(0x06, 0x00)
425
+ storageCard.write(0x07, 0x20)
426
+
427
+ expect(storageCard.read(0x00)).toBe(0xCC)
428
+ })
429
+
430
+ it('should isolate different sectors', () => {
431
+ // Write to sector 0
432
+ storageCard.write(0x02, 1)
433
+ storageCard.write(0x03, 0)
434
+ storageCard.write(0x07, 0x30)
435
+ for (let i = 0; i < 512; i++) {
436
+ storageCard.write(0x00, 0x11)
437
+ }
438
+
439
+ // Write to sector 1
440
+ storageCard.write(0x02, 1)
441
+ storageCard.write(0x03, 1)
442
+ storageCard.write(0x07, 0x30)
443
+ for (let i = 0; i < 512; i++) {
444
+ storageCard.write(0x00, 0x22)
445
+ }
446
+
447
+ // Read sector 0 (must read all bytes to complete transfer)
448
+ storageCard.write(0x02, 1)
449
+ storageCard.write(0x03, 0)
450
+ storageCard.write(0x07, 0x20)
451
+ const firstByte0 = storageCard.read(0x00)
452
+ for (let i = 1; i < 512; i++) {
453
+ storageCard.read(0x00)
454
+ }
455
+ expect(firstByte0).toBe(0x11)
456
+
457
+ // Read sector 1 (must read all bytes to complete transfer)
458
+ storageCard.write(0x02, 1)
459
+ storageCard.write(0x03, 1)
460
+ storageCard.write(0x07, 0x20)
461
+ const firstByte1 = storageCard.read(0x00)
462
+ for (let i = 1; i < 512; i++) {
463
+ storageCard.read(0x00)
464
+ }
465
+ expect(firstByte1).toBe(0x22)
466
+ })
467
+ })
468
+
469
+ describe('Error Conditions', () => {
470
+ it('should abort if command issued while transferring', () => {
471
+ storageCard.write(0x02, 1)
472
+ storageCard.write(0x03, 0)
473
+ storageCard.write(0x07, 0x20) // Start read
474
+
475
+ // Issue another command while first is active
476
+ storageCard.write(0x07, 0x20)
477
+
478
+ const status = storageCard.read(0x07)
479
+ const error = storageCard.read(0x01)
480
+
481
+ expect(status & 0x01).toBe(0x01) // ERR bit
482
+ expect(error & 0x04).toBe(0x04) // ABRT error
483
+ })
484
+
485
+ it('should abort if command issued while identifying', () => {
486
+ storageCard.write(0x07, 0xEC) // Start identify
487
+
488
+ // Issue another command
489
+ storageCard.write(0x07, 0x20)
490
+
491
+ const status = storageCard.read(0x07)
492
+ const error = storageCard.read(0x01)
493
+
494
+ expect(status & 0x01).toBe(0x01) // ERR bit
495
+ expect(error & 0x04).toBe(0x04) // ABRT error
496
+ })
497
+
498
+ it('should clear error flags on new valid command', () => {
499
+ // Trigger an error
500
+ storageCard.write(0x07, 0xFF) // Invalid command
501
+ expect(storageCard.read(0x07) & 0x01).toBe(0x01)
502
+
503
+ // Issue valid command
504
+ storageCard.write(0x02, 1)
505
+ storageCard.write(0x03, 0)
506
+ storageCard.write(0x07, 0x20)
507
+
508
+ const status = storageCard.read(0x07)
509
+ expect(status & 0x01).toBe(0x00) // ERR cleared
510
+ })
511
+ })
512
+
513
+ describe('Reset', () => {
514
+ it('should reset all registers to default values', () => {
515
+ // Modify registers
516
+ storageCard.write(0x02, 0x42)
517
+ storageCard.write(0x03, 0x11)
518
+ storageCard.write(0x04, 0x22)
519
+ storageCard.write(0x05, 0x33)
520
+
521
+ storageCard.reset(true)
522
+
523
+ expect(storageCard.read(0x01)).toBe(0x00) // Error
524
+ expect(storageCard.read(0x02)).toBe(0x00) // Sector Count
525
+ expect(storageCard.read(0x03)).toBe(0x00) // LBA0
526
+ expect(storageCard.read(0x04)).toBe(0x00) // LBA1
527
+ expect(storageCard.read(0x05)).toBe(0x00) // LBA2
528
+ expect(storageCard.read(0x06)).toBe(0xE0) // LBA3
529
+ expect(storageCard.read(0x07) & 0x40).toBe(0x40) // Status RDY
530
+ })
531
+
532
+ it('should clear transfer state', () => {
533
+ storageCard.write(0x02, 1)
534
+ storageCard.write(0x03, 0)
535
+ storageCard.write(0x07, 0x20) // Start transfer
536
+
537
+ storageCard.reset(true)
538
+
539
+ expect(storageCard.read(0x07) & 0x08).toBe(0x00) // DRQ cleared
540
+ })
541
+ })
542
+
543
+ describe('Tick', () => {
544
+ it('should have tick method that does nothing', () => {
545
+ expect(() => {
546
+ storageCard.tick(1000000)
547
+ }).not.toThrow()
548
+ })
549
+ })
550
+
551
+ describe('IO Interface Implementation', () => {
552
+ it('should implement IO interface methods', () => {
553
+ expect(typeof storageCard.read).toBe('function')
554
+ expect(typeof storageCard.write).toBe('function')
555
+ expect(typeof storageCard.tick).toBe('function')
556
+ expect(typeof storageCard.reset).toBe('function')
557
+ expect(typeof storageCard.raiseIRQ).toBe('function')
558
+ expect(typeof storageCard.raiseNMI).toBe('function')
559
+ })
560
+ })
561
+
562
+ describe('Storage Persistence', () => {
563
+ const testDir = tmpdir()
564
+ const testFile = join(testDir, `storage-test-${Date.now()}.bin`)
565
+ const invalidSizeFile = join(testDir, `storage-invalid-${Date.now()}.bin`)
566
+ const nonExistentFile = join(testDir, `storage-nonexistent-${Date.now()}.bin`)
567
+
568
+ afterEach(async () => {
569
+ // Cleanup test files
570
+ const filesToClean = [testFile, invalidSizeFile, nonExistentFile]
571
+ for (const file of filesToClean) {
572
+ if (existsSync(file)) {
573
+ await unlink(file)
574
+ }
575
+ }
576
+ })
577
+
578
+ describe('saveToFile', () => {
579
+ it('should save storage data to a file', async () => {
580
+ // Write some known data to storage
581
+ storageCard.write(0x02, 1) // 1 sector
582
+ storageCard.write(0x03, 0) // LBA = 0
583
+ storageCard.write(0x07, 0x30) // Write sector command
584
+
585
+ for (let i = 0; i < 512; i++) {
586
+ storageCard.write(0x00, i & 0xFF)
587
+ }
588
+
589
+ // Save to file
590
+ await storageCard.saveToFile(testFile)
591
+
592
+ // Verify file exists
593
+ expect(existsSync(testFile)).toBe(true)
594
+
595
+ // Verify file size is 128MB
596
+ const fileData = await readFile(testFile)
597
+ expect(fileData.length).toBe(128 * 1024 * 1024)
598
+ })
599
+
600
+ it('should save complete storage contents', async () => {
601
+ // Write to multiple sectors
602
+ for (let sector = 0; sector < 5; sector++) {
603
+ storageCard.write(0x02, 1)
604
+ storageCard.write(0x03, sector)
605
+ storageCard.write(0x07, 0x30)
606
+
607
+ for (let i = 0; i < 512; i++) {
608
+ storageCard.write(0x00, (sector + i) & 0xFF)
609
+ }
610
+ }
611
+
612
+ await storageCard.saveToFile(testFile)
613
+
614
+ // Read file directly and verify
615
+ const fileData = await readFile(testFile)
616
+
617
+ // Check first sector
618
+ for (let i = 0; i < 512; i++) {
619
+ expect(fileData[i]).toBe(i & 0xFF)
620
+ }
621
+
622
+ // Check second sector
623
+ for (let i = 0; i < 512; i++) {
624
+ expect(fileData[512 + i]).toBe((1 + i) & 0xFF)
625
+ }
626
+ })
627
+ })
628
+
629
+ describe('loadFromFile', () => {
630
+ it('should load storage data from an existing file', async () => {
631
+ // Create a test file with known data
632
+ const testData = Buffer.alloc(128 * 1024 * 1024, 0x00)
633
+
634
+ // Fill first sector with pattern
635
+ for (let i = 0; i < 512; i++) {
636
+ testData[i] = (0xAA + i) & 0xFF
637
+ }
638
+
639
+ await writeFile(testFile, testData)
640
+
641
+ // Load into storage card
642
+ await storageCard.loadFromFile(testFile)
643
+
644
+ // Verify data was loaded
645
+ storageCard.write(0x02, 1)
646
+ storageCard.write(0x03, 0)
647
+ storageCard.write(0x07, 0x20) // Read sector
648
+
649
+ for (let i = 0; i < 512; i++) {
650
+ expect(storageCard.read(0x00)).toBe((0xAA + i) & 0xFF)
651
+ }
652
+ })
653
+
654
+ it('should handle non-existent file gracefully', async () => {
655
+ // Try to load from a file that doesn't exist
656
+ await expect(storageCard.loadFromFile(nonExistentFile)).resolves.not.toThrow()
657
+
658
+ // Storage should remain empty (zeros)
659
+ storageCard.write(0x02, 1)
660
+ storageCard.write(0x03, 0)
661
+ storageCard.write(0x07, 0x20)
662
+
663
+ for (let i = 0; i < 512; i++) {
664
+ expect(storageCard.read(0x00)).toBe(0x00)
665
+ }
666
+ })
667
+
668
+ it('should reject file with incorrect size', async () => {
669
+ // Create a file that's too small
670
+ const smallData = Buffer.alloc(1024, 0xFF) // Only 1KB
671
+ await writeFile(invalidSizeFile, smallData)
672
+
673
+ await storageCard.loadFromFile(invalidSizeFile)
674
+
675
+ // Storage should remain empty (zeros)
676
+ storageCard.write(0x02, 1)
677
+ storageCard.write(0x03, 0)
678
+ storageCard.write(0x07, 0x20)
679
+
680
+ for (let i = 0; i < 512; i++) {
681
+ expect(storageCard.read(0x00)).toBe(0x00)
682
+ }
683
+ })
684
+
685
+ it('should load multiple sectors correctly', async () => {
686
+ const testData = Buffer.alloc(128 * 1024 * 1024, 0x00)
687
+
688
+ // Fill sectors with different patterns
689
+ for (let sector = 0; sector < 10; sector++) {
690
+ for (let i = 0; i < 512; i++) {
691
+ testData[sector * 512 + i] = (sector * 16 + i) & 0xFF
692
+ }
693
+ }
694
+
695
+ await writeFile(testFile, testData)
696
+ await storageCard.loadFromFile(testFile)
697
+
698
+ // Verify each sector
699
+ for (let sector = 0; sector < 10; sector++) {
700
+ storageCard.write(0x02, 1)
701
+ storageCard.write(0x03, sector)
702
+ storageCard.write(0x07, 0x20)
703
+
704
+ for (let i = 0; i < 512; i++) {
705
+ expect(storageCard.read(0x00)).toBe((sector * 16 + i) & 0xFF)
706
+ }
707
+ }
708
+ })
709
+ })
710
+
711
+ describe('Round-trip persistence', () => {
712
+ it('should save and load data without loss', async () => {
713
+ // Write unique pattern to storage
714
+ for (let sector = 0; sector < 100; sector++) {
715
+ storageCard.write(0x02, 1)
716
+ storageCard.write(0x03, sector)
717
+ storageCard.write(0x07, 0x30)
718
+
719
+ for (let i = 0; i < 512; i++) {
720
+ storageCard.write(0x00, ((sector * 7 + i * 3) ^ 0x55) & 0xFF)
721
+ }
722
+ }
723
+
724
+ // Save to file
725
+ await storageCard.saveToFile(testFile)
726
+
727
+ // Create new storage card and load
728
+ const newStorageCard = new StorageCard()
729
+ await newStorageCard.loadFromFile(testFile)
730
+
731
+ // Verify all sectors match
732
+ for (let sector = 0; sector < 100; sector++) {
733
+ newStorageCard.write(0x02, 1)
734
+ newStorageCard.write(0x03, sector)
735
+ newStorageCard.write(0x07, 0x20)
736
+
737
+ for (let i = 0; i < 512; i++) {
738
+ expect(newStorageCard.read(0x00)).toBe(((sector * 7 + i * 3) ^ 0x55) & 0xFF)
739
+ }
740
+ }
741
+ })
742
+
743
+ it('should preserve data across multiple save/load cycles', async () => {
744
+ // Initial write
745
+ storageCard.write(0x02, 1)
746
+ storageCard.write(0x03, 42)
747
+ storageCard.write(0x07, 0x30)
748
+ for (let i = 0; i < 512; i++) {
749
+ storageCard.write(0x00, 0xCC)
750
+ }
751
+ await storageCard.saveToFile(testFile)
752
+
753
+ // Load and modify
754
+ const card2 = new StorageCard()
755
+ await card2.loadFromFile(testFile)
756
+ card2.write(0x02, 1)
757
+ card2.write(0x03, 43)
758
+ card2.write(0x07, 0x30)
759
+ for (let i = 0; i < 512; i++) {
760
+ card2.write(0x00, 0xDD)
761
+ }
762
+ await card2.saveToFile(testFile)
763
+
764
+ // Load again and verify both sectors
765
+ const card3 = new StorageCard()
766
+ await card3.loadFromFile(testFile)
767
+
768
+ // Check sector 42
769
+ card3.write(0x02, 1)
770
+ card3.write(0x03, 42)
771
+ card3.write(0x07, 0x20)
772
+ expect(card3.read(0x00)).toBe(0xCC)
773
+
774
+ // Complete reading sector 42
775
+ for (let i = 1; i < 512; i++) {
776
+ card3.read(0x00)
777
+ }
778
+
779
+ // Check sector 43
780
+ card3.write(0x02, 1)
781
+ card3.write(0x03, 43)
782
+ card3.write(0x07, 0x20)
783
+ expect(card3.read(0x00)).toBe(0xDD)
784
+ })
785
+ })
786
+ })
787
+ })