ac6502 1.3.0 → 1.4.1
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/README.md +139 -32
- package/dist/components/IO/ACIA.d.ts +76 -0
- package/dist/components/IO/ACIA.js +282 -0
- package/dist/components/IO/ACIA.js.map +1 -0
- package/dist/components/IO/Attachments/Attachment.d.ts +112 -0
- package/dist/components/IO/Attachments/Attachment.js +71 -0
- package/dist/components/IO/Attachments/Attachment.js.map +1 -0
- package/dist/components/IO/Attachments/JoystickAttachment.d.ts +53 -0
- package/dist/components/IO/Attachments/JoystickAttachment.js +90 -0
- package/dist/components/IO/Attachments/JoystickAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +63 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.d.ts +44 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/KeypadAttachment.d.ts +47 -0
- package/dist/components/IO/Attachments/KeypadAttachment.js +141 -0
- package/dist/components/IO/Attachments/KeypadAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/LCDAttachment.d.ts +110 -0
- package/dist/components/IO/Attachments/LCDAttachment.js +716 -0
- package/dist/components/IO/Attachments/LCDAttachment.js.map +1 -0
- package/dist/components/IO/Attachments/SNESAttachment.d.ts +85 -0
- package/dist/components/IO/Attachments/SNESAttachment.js +184 -0
- package/dist/components/IO/Attachments/SNESAttachment.js.map +1 -0
- package/dist/components/IO/Empty.d.ts +9 -0
- package/dist/components/IO/Empty.js +5 -7
- package/dist/components/IO/Empty.js.map +1 -1
- package/dist/components/IO/GPIOCard.d.ts +5 -5
- package/dist/components/IO/GPIOCard.js.map +1 -1
- package/dist/components/IO/RAMBank.d.ts +37 -0
- package/dist/components/IO/RAMBank.js +63 -0
- package/dist/components/IO/RAMBank.js.map +1 -0
- package/dist/components/IO/RTC.d.ts +107 -0
- package/dist/components/IO/RTC.js +483 -0
- package/dist/components/IO/RTC.js.map +1 -0
- package/dist/components/IO/Sound.d.ts +120 -0
- package/dist/components/IO/Sound.js +622 -0
- package/dist/components/IO/Sound.js.map +1 -0
- package/dist/components/IO/Storage.d.ts +74 -0
- package/dist/components/IO/Storage.js +409 -0
- package/dist/components/IO/Storage.js.map +1 -0
- package/dist/components/IO/Terminal.d.ts +19 -0
- package/dist/components/IO/Terminal.js +33 -0
- package/dist/components/IO/Terminal.js.map +1 -0
- package/dist/components/IO/VIA.d.ts +105 -0
- package/dist/components/IO/VIA.js +597 -0
- package/dist/components/IO/VIA.js.map +1 -0
- package/dist/components/IO/Video.d.ts +141 -0
- package/dist/components/IO/Video.js +630 -0
- package/dist/components/IO/Video.js.map +1 -0
- package/dist/components/Machine.d.ts +20 -24
- package/dist/components/Machine.js +249 -166
- package/dist/components/Machine.js.map +1 -1
- package/dist/index.js +28 -14
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +16 -16
- package/dist/lib.js +32 -32
- package/dist/lib.js.map +1 -1
- package/dist/tests/IO/ACIA.test.d.ts +1 -0
- package/dist/tests/IO/ACIA.test.js +423 -0
- package/dist/tests/IO/ACIA.test.js.map +1 -0
- package/dist/tests/IO/Attachments/Attachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/Attachment.test.js +339 -0
- package/dist/tests/IO/Attachments/Attachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.js +126 -0
- package/dist/tests/IO/Attachments/JoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.js +323 -0
- package/dist/tests/IO/Attachments/KeypadAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.js +627 -0
- package/dist/tests/IO/Attachments/LCDAttachment.test.js.map +1 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.d.ts +1 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.js +331 -0
- package/dist/tests/IO/Attachments/SNESAttachment.test.js.map +1 -0
- package/dist/tests/IO/Empty.test.d.ts +1 -0
- package/dist/tests/IO/Empty.test.js +121 -0
- package/dist/tests/IO/Empty.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -1
- package/dist/tests/IO/RAMBank.test.d.ts +1 -0
- package/dist/tests/IO/RAMBank.test.js +229 -0
- package/dist/tests/IO/RAMBank.test.js.map +1 -0
- package/dist/tests/IO/RTC.test.d.ts +1 -0
- package/dist/tests/IO/RTC.test.js +177 -0
- package/dist/tests/IO/RTC.test.js.map +1 -0
- package/dist/tests/IO/Sound.test.d.ts +1 -0
- package/dist/tests/IO/Sound.test.js +528 -0
- package/dist/tests/IO/Sound.test.js.map +1 -0
- package/dist/tests/IO/Storage.test.d.ts +1 -0
- package/dist/tests/IO/Storage.test.js +656 -0
- package/dist/tests/IO/Storage.test.js.map +1 -0
- package/dist/tests/IO/VIA.test.d.ts +1 -0
- package/dist/tests/IO/VIA.test.js +503 -0
- package/dist/tests/IO/VIA.test.js.map +1 -0
- package/dist/tests/IO/Video.test.d.ts +1 -0
- package/dist/tests/IO/Video.test.js +549 -0
- package/dist/tests/IO/Video.test.js.map +1 -0
- package/dist/tests/Machine.test.js +27 -42
- package/dist/tests/Machine.test.js.map +1 -1
- package/package.json +1 -1
- package/src/components/IO/{SerialCard.ts → ACIA.ts} +2 -2
- package/src/components/IO/{GPIOAttachments/GPIOAttachment.ts → Attachments/Attachment.ts} +2 -2
- package/src/components/IO/{GPIOAttachments/GPIOJoystickAttachment.ts → Attachments/JoystickAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.ts → Attachments/KeyboardEncoderAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.ts → Attachments/KeyboardMatrixAttachment.ts} +5 -5
- package/src/components/IO/{GPIOAttachments/GPIOKeypadAttachment.ts → Attachments/KeypadAttachment.ts} +3 -3
- package/src/components/IO/{GPIOAttachments/GPIOLCDAttachment.ts → Attachments/LCDAttachment.ts} +7 -7
- package/src/components/IO/{EmptyCard.ts → Empty.ts} +1 -1
- package/src/components/IO/{RAMCard.ts → RAMBank.ts} +8 -8
- package/src/components/IO/{RTCCard.ts → RTC.ts} +1 -1
- package/src/components/IO/{SoundCard.ts → Sound.ts} +2 -2
- package/src/components/IO/{StorageCard.ts → Storage.ts} +70 -73
- package/src/components/IO/{DevOutputBoard.ts → Terminal.ts} +2 -2
- package/src/components/IO/{GPIOCard.ts → VIA.ts} +64 -64
- package/src/components/IO/{VideoCard.ts → Video.ts} +1 -1
- package/src/components/Machine.ts +276 -176
- package/src/index.ts +34 -21
- package/src/lib.ts +16 -16
- package/src/tests/IO/{SerialCard.test.ts → ACIA.test.ts} +5 -5
- package/src/tests/IO/{GPIOAttachments/GPIOAttachment.test.ts → Attachments/Attachment.test.ts} +12 -12
- package/src/tests/IO/{GPIOAttachments/GPIOJoystickAttachment.test.ts → Attachments/JoystickAttachment.test.ts} +23 -23
- package/src/tests/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts → Attachments/KeyboardEncoderAttachment.test.ts} +4 -4
- package/src/tests/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts → Attachments/KeyboardMatrixAttachment.test.ts} +5 -5
- package/src/tests/IO/{GPIOAttachments/GPIOKeypadAttachment.test.ts → Attachments/KeypadAttachment.test.ts} +38 -38
- package/src/tests/IO/{GPIOAttachments/GPIOLCDAttachment.test.ts → Attachments/LCDAttachment.test.ts} +12 -12
- package/src/tests/IO/Empty.test.ts +143 -0
- package/src/tests/IO/{RAMCard.test.ts → RAMBank.test.ts} +33 -33
- package/src/tests/IO/{RTCCard.test.ts → RTC.test.ts} +6 -6
- package/src/tests/IO/{SoundCard.test.ts → Sound.test.ts} +6 -6
- package/src/tests/IO/{StorageCard.test.ts → Storage.test.ts} +34 -25
- package/src/tests/IO/{GPIOCard.test.ts → VIA.test.ts} +7 -7
- package/src/tests/IO/{VideoCard.test.ts → Video.test.ts} +13 -13
- package/src/tests/Machine.test.ts +31 -38
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { IO } from '../IO'
|
|
2
|
-
import { readFile, writeFile } from 'fs/promises'
|
|
3
|
-
import { existsSync } from 'fs'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
4
|
+
* Storage - Emulates a Compact Flash card in 8-bit IDE mode
|
|
7
5
|
*
|
|
8
6
|
* Emulates a 128MB CF card with ATA-style register interface.
|
|
9
7
|
* Uses LBA (Logical Block Addressing) for sector access.
|
|
@@ -25,12 +23,12 @@ import { existsSync } from 'fs'
|
|
|
25
23
|
* 0xEC: Identify Drive
|
|
26
24
|
* 0xEF: Set Features (accepted but not implemented)
|
|
27
25
|
*/
|
|
28
|
-
export class
|
|
26
|
+
export class Storage implements IO {
|
|
29
27
|
|
|
30
28
|
// Constants
|
|
31
29
|
private static readonly STORAGE_SIZE = 128 * 1024 * 1024 // 128MB
|
|
32
30
|
private static readonly SECTOR_SIZE = 512
|
|
33
|
-
private static readonly SECTOR_COUNT =
|
|
31
|
+
private static readonly SECTOR_COUNT = Storage.STORAGE_SIZE / Storage.SECTOR_SIZE // 262144 sectors
|
|
34
32
|
|
|
35
33
|
// Status Register Flags
|
|
36
34
|
private static readonly STATUS_ERR = 0x01 // Error
|
|
@@ -43,13 +41,13 @@ export class StorageCard implements IO {
|
|
|
43
41
|
private static readonly ERR_IDNF = 0x10 // ID Not Found
|
|
44
42
|
|
|
45
43
|
// Storage and Identity data (in-memory simulation)
|
|
46
|
-
private storage:
|
|
47
|
-
private identity:
|
|
44
|
+
private storage: Uint8Array
|
|
45
|
+
private identity: Uint8Array
|
|
48
46
|
|
|
49
47
|
// Data buffer (512 bytes)
|
|
50
|
-
private buffer:
|
|
48
|
+
private buffer: Uint8Array = new Uint8Array(Storage.SECTOR_SIZE)
|
|
51
49
|
private bufferIndex: number = 0
|
|
52
|
-
private commandDataSize: number =
|
|
50
|
+
private commandDataSize: number = Storage.SECTOR_SIZE
|
|
53
51
|
private sectorOffset: number = 0
|
|
54
52
|
|
|
55
53
|
// Registers
|
|
@@ -72,8 +70,8 @@ export class StorageCard implements IO {
|
|
|
72
70
|
|
|
73
71
|
constructor() {
|
|
74
72
|
// Initialize storage and identity buffers
|
|
75
|
-
this.storage =
|
|
76
|
-
this.identity =
|
|
73
|
+
this.storage = new Uint8Array(Storage.STORAGE_SIZE)
|
|
74
|
+
this.identity = new Uint8Array(Storage.SECTOR_SIZE)
|
|
77
75
|
this.generateIdentity()
|
|
78
76
|
this.reset(true)
|
|
79
77
|
}
|
|
@@ -137,7 +135,7 @@ export class StorageCard implements IO {
|
|
|
137
135
|
|
|
138
136
|
reset(coldStart: boolean): void {
|
|
139
137
|
this.bufferIndex = 0x0000
|
|
140
|
-
this.commandDataSize =
|
|
138
|
+
this.commandDataSize = Storage.SECTOR_SIZE
|
|
141
139
|
this.sectorOffset = 0
|
|
142
140
|
|
|
143
141
|
this.error = 0x00
|
|
@@ -147,7 +145,7 @@ export class StorageCard implements IO {
|
|
|
147
145
|
this.lba1 = 0x00
|
|
148
146
|
this.lba2 = 0x00
|
|
149
147
|
this.lba3 = 0xE0
|
|
150
|
-
this.status = 0x00 |
|
|
148
|
+
this.status = 0x00 | Storage.STATUS_RDY
|
|
151
149
|
this.command = 0x00
|
|
152
150
|
|
|
153
151
|
this.isIdentifying = false
|
|
@@ -162,36 +160,36 @@ export class StorageCard implements IO {
|
|
|
162
160
|
|
|
163
161
|
private executeCommand(): void {
|
|
164
162
|
// New command so clear errors and flags
|
|
165
|
-
this.status &= ~
|
|
166
|
-
this.status &= ~
|
|
163
|
+
this.status &= ~Storage.STATUS_ERR
|
|
164
|
+
this.status &= ~Storage.STATUS_DRQ
|
|
167
165
|
this.error = 0x00
|
|
168
|
-
this.commandDataSize =
|
|
166
|
+
this.commandDataSize = Storage.SECTOR_SIZE * this.sectorCount
|
|
169
167
|
this.bufferIndex = 0
|
|
170
168
|
this.sectorOffset = 0
|
|
171
169
|
|
|
172
170
|
// Check if already executing a command
|
|
173
171
|
if (this.isTransferring || this.isIdentifying) {
|
|
174
|
-
this.status |=
|
|
175
|
-
this.error |=
|
|
172
|
+
this.status |= Storage.STATUS_ERR
|
|
173
|
+
this.error |= Storage.ERR_ABRT
|
|
176
174
|
return
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
switch (this.command) {
|
|
180
178
|
case 0xC0: { // Erase sector
|
|
181
179
|
if (!this.sectorValid()) {
|
|
182
|
-
this.status |=
|
|
183
|
-
this.error |=
|
|
180
|
+
this.status |= Storage.STATUS_ERR
|
|
181
|
+
this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
|
|
184
182
|
} else {
|
|
185
|
-
const offset = this.sectorIndex() *
|
|
186
|
-
this.storage.fill(0x00, offset, offset +
|
|
183
|
+
const offset = this.sectorIndex() * Storage.SECTOR_SIZE
|
|
184
|
+
this.storage.fill(0x00, offset, offset + Storage.SECTOR_SIZE)
|
|
187
185
|
}
|
|
188
186
|
break
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
case 0xEC: { // Identify drive
|
|
192
|
-
this.
|
|
193
|
-
this.commandDataSize =
|
|
194
|
-
this.status |=
|
|
190
|
+
this.buffer.set(this.identity.subarray(0, Storage.SECTOR_SIZE))
|
|
191
|
+
this.commandDataSize = Storage.SECTOR_SIZE
|
|
192
|
+
this.status |= Storage.STATUS_DRQ
|
|
195
193
|
this.isIdentifying = true
|
|
196
194
|
break
|
|
197
195
|
}
|
|
@@ -199,13 +197,13 @@ export class StorageCard implements IO {
|
|
|
199
197
|
case 0x20: // Read sector
|
|
200
198
|
case 0x21:
|
|
201
199
|
if (!this.sectorValid()) {
|
|
202
|
-
this.status |=
|
|
203
|
-
this.error |=
|
|
200
|
+
this.status |= Storage.STATUS_ERR
|
|
201
|
+
this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
|
|
204
202
|
} else {
|
|
205
203
|
// Load first sector into buffer
|
|
206
|
-
const offset = this.sectorIndex() *
|
|
207
|
-
this.
|
|
208
|
-
this.status |=
|
|
204
|
+
const offset = this.sectorIndex() * Storage.SECTOR_SIZE
|
|
205
|
+
this.buffer.set(this.storage.subarray(offset, offset + Storage.SECTOR_SIZE))
|
|
206
|
+
this.status |= Storage.STATUS_DRQ
|
|
209
207
|
this.isTransferring = true
|
|
210
208
|
}
|
|
211
209
|
break
|
|
@@ -217,18 +215,18 @@ export class StorageCard implements IO {
|
|
|
217
215
|
case 0x30: // Write sector
|
|
218
216
|
case 0x31:
|
|
219
217
|
if (!this.sectorValid()) {
|
|
220
|
-
this.status |=
|
|
221
|
-
this.error |=
|
|
218
|
+
this.status |= Storage.STATUS_ERR
|
|
219
|
+
this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
|
|
222
220
|
} else {
|
|
223
|
-
this.status |=
|
|
221
|
+
this.status |= Storage.STATUS_DRQ
|
|
224
222
|
this.isTransferring = true
|
|
225
223
|
}
|
|
226
224
|
break
|
|
227
225
|
|
|
228
226
|
default:
|
|
229
227
|
// Unsupported command
|
|
230
|
-
this.status |=
|
|
231
|
-
this.error |=
|
|
228
|
+
this.status |= Storage.STATUS_ERR
|
|
229
|
+
this.error |= Storage.ERR_ABRT
|
|
232
230
|
break
|
|
233
231
|
}
|
|
234
232
|
}
|
|
@@ -242,14 +240,14 @@ export class StorageCard implements IO {
|
|
|
242
240
|
} else {
|
|
243
241
|
this.bufferIndex = 0
|
|
244
242
|
this.isIdentifying = false
|
|
245
|
-
this.status &= ~
|
|
243
|
+
this.status &= ~Storage.STATUS_DRQ
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
return data
|
|
249
247
|
} else if (this.isTransferring) {
|
|
250
248
|
const data = this.buffer[this.bufferIndex]
|
|
251
249
|
|
|
252
|
-
if (this.bufferIndex <
|
|
250
|
+
if (this.bufferIndex < Storage.SECTOR_SIZE - 1) {
|
|
253
251
|
this.bufferIndex++
|
|
254
252
|
} else {
|
|
255
253
|
this.bufferIndex = 0
|
|
@@ -257,11 +255,11 @@ export class StorageCard implements IO {
|
|
|
257
255
|
|
|
258
256
|
if (this.sectorOffset < this.sectorCount) {
|
|
259
257
|
// Load the next sector
|
|
260
|
-
const offset = (this.sectorIndex() + this.sectorOffset) *
|
|
261
|
-
this.
|
|
258
|
+
const offset = (this.sectorIndex() + this.sectorOffset) * Storage.SECTOR_SIZE
|
|
259
|
+
this.buffer.set(this.storage.subarray(offset, offset + Storage.SECTOR_SIZE))
|
|
262
260
|
} else {
|
|
263
261
|
this.isTransferring = false
|
|
264
|
-
this.status &= ~
|
|
262
|
+
this.status &= ~Storage.STATUS_DRQ
|
|
265
263
|
}
|
|
266
264
|
}
|
|
267
265
|
|
|
@@ -274,21 +272,21 @@ export class StorageCard implements IO {
|
|
|
274
272
|
private writeBuffer(value: number): void {
|
|
275
273
|
this.buffer[this.bufferIndex] = value
|
|
276
274
|
|
|
277
|
-
if (this.bufferIndex <
|
|
275
|
+
if (this.bufferIndex < Storage.SECTOR_SIZE - 1) {
|
|
278
276
|
this.bufferIndex++
|
|
279
277
|
} else {
|
|
280
278
|
this.bufferIndex = 0
|
|
281
279
|
|
|
282
280
|
// Write the current sector to storage
|
|
283
|
-
const offset = (this.sectorIndex() + this.sectorOffset) *
|
|
284
|
-
this.
|
|
281
|
+
const offset = (this.sectorIndex() + this.sectorOffset) * Storage.SECTOR_SIZE
|
|
282
|
+
this.storage.set(this.buffer.subarray(0, Storage.SECTOR_SIZE), offset)
|
|
285
283
|
|
|
286
284
|
this.sectorOffset++
|
|
287
285
|
|
|
288
286
|
// Check if all sectors have been written
|
|
289
287
|
if (this.sectorOffset >= this.sectorCount) {
|
|
290
288
|
this.isTransferring = false
|
|
291
|
-
this.status &= ~
|
|
289
|
+
this.status &= ~Storage.STATUS_DRQ
|
|
292
290
|
}
|
|
293
291
|
}
|
|
294
292
|
}
|
|
@@ -298,7 +296,7 @@ export class StorageCard implements IO {
|
|
|
298
296
|
}
|
|
299
297
|
|
|
300
298
|
private sectorValid(): boolean {
|
|
301
|
-
return this.sectorIndex() <
|
|
299
|
+
return this.sectorIndex() < Storage.SECTOR_COUNT
|
|
302
300
|
}
|
|
303
301
|
|
|
304
302
|
private generateIdentity(): void {
|
|
@@ -434,40 +432,39 @@ export class StorageCard implements IO {
|
|
|
434
432
|
}
|
|
435
433
|
|
|
436
434
|
/**
|
|
437
|
-
* Load storage data from a
|
|
438
|
-
* If
|
|
435
|
+
* Load storage data from a Uint8Array, ArrayBuffer, or number array
|
|
436
|
+
* If data is not provided or wrong size, storage remains empty
|
|
439
437
|
*/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
438
|
+
loadData(data: Uint8Array | ArrayBuffer | number[] | null): void {
|
|
439
|
+
if (!data) {
|
|
440
|
+
console.log('No storage data provided. Storage will remain empty.')
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let uint8Data: Uint8Array
|
|
445
|
+
if (data instanceof ArrayBuffer) {
|
|
446
|
+
uint8Data = new Uint8Array(data)
|
|
447
|
+
} else if (data instanceof Uint8Array) {
|
|
448
|
+
uint8Data = data
|
|
449
|
+
} else {
|
|
450
|
+
uint8Data = new Uint8Array(data)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Ensure the data is exactly the expected size
|
|
454
|
+
if (uint8Data.length === Storage.STORAGE_SIZE) {
|
|
455
|
+
this.storage.set(uint8Data)
|
|
456
|
+
console.log(`Storage loaded (${Storage.STORAGE_SIZE} bytes)`)
|
|
457
|
+
} else {
|
|
458
|
+
console.warn(`Warning: Storage data size mismatch. Expected ${Storage.STORAGE_SIZE} bytes, got ${uint8Data.length} bytes.`)
|
|
459
|
+
console.warn('Storage will remain empty.')
|
|
458
460
|
}
|
|
459
461
|
}
|
|
460
462
|
|
|
461
463
|
/**
|
|
462
|
-
*
|
|
464
|
+
* Get storage data as Uint8Array for saving
|
|
463
465
|
*/
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
await writeFile(filePath, this.storage)
|
|
467
|
-
console.log(`Storage saved to: ${filePath}`)
|
|
468
|
-
} catch (error) {
|
|
469
|
-
console.error('Error saving storage file:', error)
|
|
470
|
-
}
|
|
466
|
+
getData(): Uint8Array {
|
|
467
|
+
return new Uint8Array(this.storage)
|
|
471
468
|
}
|
|
472
469
|
|
|
473
470
|
}
|
|
@@ -2,14 +2,14 @@ import { IO } from '../IO'
|
|
|
2
2
|
import { VTAC } from 'vtac-terminal'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Terminal - Emulates the VTAC fantasy terminal
|
|
6
6
|
*
|
|
7
7
|
* Register Map:
|
|
8
8
|
* $00: Data / Status Register
|
|
9
9
|
* Write: sends byte to VTAC for processing
|
|
10
10
|
* Read: always returns 0 (bit 7 is a busy flag on the real device; busy is never set here)
|
|
11
11
|
*/
|
|
12
|
-
export class
|
|
12
|
+
export class Terminal implements IO {
|
|
13
13
|
|
|
14
14
|
raiseIRQ = () => {}
|
|
15
15
|
raiseNMI = () => {}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { IO } from '../IO'
|
|
2
|
-
import {
|
|
2
|
+
import { Attachment } from './Attachments/Attachment'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* VIA - Emulates the 65C22 VIA (Versatile Interface Adapter)
|
|
6
6
|
*
|
|
7
7
|
* The 65C22 VIA provides:
|
|
8
8
|
* - Two 8-bit bidirectional I/O ports (Port A and Port B)
|
|
@@ -10,7 +10,7 @@ import { GPIOAttachment } from './GPIOAttachments/GPIOAttachment'
|
|
|
10
10
|
* - Shift register for serial I/O
|
|
11
11
|
* - Handshaking lines for data transfer
|
|
12
12
|
*/
|
|
13
|
-
export class
|
|
13
|
+
export class VIA implements IO {
|
|
14
14
|
// VIA Register addresses (offset from base address)
|
|
15
15
|
private static readonly VIA_ORB = 0x00 // Output Register B
|
|
16
16
|
private static readonly VIA_ORA = 0x01 // Output Register A
|
|
@@ -73,8 +73,8 @@ export class GPIOCard implements IO {
|
|
|
73
73
|
private ticksPerMicrosecond: number = 1
|
|
74
74
|
|
|
75
75
|
// Attachments
|
|
76
|
-
private portA_attachments: (
|
|
77
|
-
private portB_attachments: (
|
|
76
|
+
private portA_attachments: (Attachment | null)[] = []
|
|
77
|
+
private portB_attachments: (Attachment | null)[] = []
|
|
78
78
|
private portA_attachmentCount: number = 0
|
|
79
79
|
private portB_attachmentCount: number = 0
|
|
80
80
|
|
|
@@ -116,7 +116,7 @@ export class GPIOCard implements IO {
|
|
|
116
116
|
// Initialize attachment arrays
|
|
117
117
|
this.portA_attachmentCount = 0
|
|
118
118
|
this.portB_attachmentCount = 0
|
|
119
|
-
for (let i = 0; i <
|
|
119
|
+
for (let i = 0; i < VIA.MAX_ATTACHMENTS_PER_PORT; i++) {
|
|
120
120
|
this.portA_attachments[i] = null
|
|
121
121
|
this.portB_attachments[i] = null
|
|
122
122
|
}
|
|
@@ -143,9 +143,9 @@ export class GPIOCard implements IO {
|
|
|
143
143
|
let value = 0x00
|
|
144
144
|
|
|
145
145
|
switch (reg) {
|
|
146
|
-
case
|
|
146
|
+
case VIA.VIA_ORB:
|
|
147
147
|
// Reading ORB clears CB1 and CB2 interrupt flags
|
|
148
|
-
this.clearIRQFlag(
|
|
148
|
+
this.clearIRQFlag(VIA.IRQ_CB1 | VIA.IRQ_CB2)
|
|
149
149
|
value = this.readPortB()
|
|
150
150
|
// Notify attachments that interrupts were cleared
|
|
151
151
|
for (let i = 0; i < this.portB_attachmentCount; i++) {
|
|
@@ -155,9 +155,9 @@ export class GPIOCard implements IO {
|
|
|
155
155
|
}
|
|
156
156
|
break
|
|
157
157
|
|
|
158
|
-
case
|
|
158
|
+
case VIA.VIA_ORA:
|
|
159
159
|
// Reading ORA clears CA1 and CA2 interrupt flags
|
|
160
|
-
this.clearIRQFlag(
|
|
160
|
+
this.clearIRQFlag(VIA.IRQ_CA1 | VIA.IRQ_CA2)
|
|
161
161
|
value = this.readPortA()
|
|
162
162
|
// Notify attachments that interrupts were cleared
|
|
163
163
|
for (let i = 0; i < this.portA_attachmentCount; i++) {
|
|
@@ -167,69 +167,69 @@ export class GPIOCard implements IO {
|
|
|
167
167
|
}
|
|
168
168
|
break
|
|
169
169
|
|
|
170
|
-
case
|
|
170
|
+
case VIA.VIA_DDRB:
|
|
171
171
|
value = this.regDDRB
|
|
172
172
|
break
|
|
173
173
|
|
|
174
|
-
case
|
|
174
|
+
case VIA.VIA_DDRA:
|
|
175
175
|
value = this.regDDRA
|
|
176
176
|
break
|
|
177
177
|
|
|
178
|
-
case
|
|
178
|
+
case VIA.VIA_T1CL:
|
|
179
179
|
// Reading T1CL clears T1 interrupt flag
|
|
180
|
-
this.clearIRQFlag(
|
|
180
|
+
this.clearIRQFlag(VIA.IRQ_T1)
|
|
181
181
|
value = this.regT1C & 0xFF
|
|
182
182
|
break
|
|
183
183
|
|
|
184
|
-
case
|
|
184
|
+
case VIA.VIA_T1CH:
|
|
185
185
|
value = (this.regT1C >> 8) & 0xFF
|
|
186
186
|
break
|
|
187
187
|
|
|
188
|
-
case
|
|
188
|
+
case VIA.VIA_T1LL:
|
|
189
189
|
value = this.regT1L & 0xFF
|
|
190
190
|
break
|
|
191
191
|
|
|
192
|
-
case
|
|
192
|
+
case VIA.VIA_T1LH:
|
|
193
193
|
value = (this.regT1L >> 8) & 0xFF
|
|
194
194
|
break
|
|
195
195
|
|
|
196
|
-
case
|
|
196
|
+
case VIA.VIA_T2CL:
|
|
197
197
|
// Reading T2CL clears T2 interrupt flag
|
|
198
|
-
this.clearIRQFlag(
|
|
198
|
+
this.clearIRQFlag(VIA.IRQ_T2)
|
|
199
199
|
value = this.regT2C & 0xFF
|
|
200
200
|
break
|
|
201
201
|
|
|
202
|
-
case
|
|
202
|
+
case VIA.VIA_T2CH:
|
|
203
203
|
value = (this.regT2C >> 8) & 0xFF
|
|
204
204
|
break
|
|
205
205
|
|
|
206
|
-
case
|
|
206
|
+
case VIA.VIA_SR:
|
|
207
207
|
// Reading SR clears SR interrupt flag
|
|
208
|
-
this.clearIRQFlag(
|
|
208
|
+
this.clearIRQFlag(VIA.IRQ_SR)
|
|
209
209
|
value = this.regSR
|
|
210
210
|
break
|
|
211
211
|
|
|
212
|
-
case
|
|
212
|
+
case VIA.VIA_ACR:
|
|
213
213
|
value = this.regACR
|
|
214
214
|
break
|
|
215
215
|
|
|
216
|
-
case
|
|
216
|
+
case VIA.VIA_PCR:
|
|
217
217
|
value = this.regPCR
|
|
218
218
|
break
|
|
219
219
|
|
|
220
|
-
case
|
|
220
|
+
case VIA.VIA_IFR:
|
|
221
221
|
value = this.regIFR
|
|
222
222
|
// Bit 7 is set if any enabled interrupt is active
|
|
223
223
|
if (this.regIFR & this.regIER & 0x7F) {
|
|
224
|
-
value |=
|
|
224
|
+
value |= VIA.IRQ_IRQ
|
|
225
225
|
}
|
|
226
226
|
break
|
|
227
227
|
|
|
228
|
-
case
|
|
228
|
+
case VIA.VIA_IER:
|
|
229
229
|
value = this.regIER | 0x80 // Bit 7 always reads as 1
|
|
230
230
|
break
|
|
231
231
|
|
|
232
|
-
case
|
|
232
|
+
case VIA.VIA_ORA_NH:
|
|
233
233
|
// Reading ORA without handshake (no interrupt flag clearing)
|
|
234
234
|
value = this.readPortA()
|
|
235
235
|
break
|
|
@@ -243,84 +243,84 @@ export class GPIOCard implements IO {
|
|
|
243
243
|
const value = data & 0xFF
|
|
244
244
|
|
|
245
245
|
switch (reg) {
|
|
246
|
-
case
|
|
246
|
+
case VIA.VIA_ORB:
|
|
247
247
|
// Writing ORB clears CB1 and CB2 interrupt flags
|
|
248
|
-
this.clearIRQFlag(
|
|
248
|
+
this.clearIRQFlag(VIA.IRQ_CB1 | VIA.IRQ_CB2)
|
|
249
249
|
this.regORB = value
|
|
250
250
|
this.writePortB(value)
|
|
251
251
|
break
|
|
252
252
|
|
|
253
|
-
case
|
|
253
|
+
case VIA.VIA_ORA:
|
|
254
254
|
// Writing ORA clears CA1 and CA2 interrupt flags
|
|
255
|
-
this.clearIRQFlag(
|
|
255
|
+
this.clearIRQFlag(VIA.IRQ_CA1 | VIA.IRQ_CA2)
|
|
256
256
|
this.regORA = value
|
|
257
257
|
this.writePortA(value)
|
|
258
258
|
break
|
|
259
259
|
|
|
260
|
-
case
|
|
260
|
+
case VIA.VIA_DDRB:
|
|
261
261
|
this.regDDRB = value
|
|
262
262
|
break
|
|
263
263
|
|
|
264
|
-
case
|
|
264
|
+
case VIA.VIA_DDRA:
|
|
265
265
|
this.regDDRA = value
|
|
266
266
|
break
|
|
267
267
|
|
|
268
|
-
case
|
|
269
|
-
case
|
|
268
|
+
case VIA.VIA_T1CL:
|
|
269
|
+
case VIA.VIA_T1LL:
|
|
270
270
|
// Write to T1 low latch
|
|
271
271
|
this.regT1L = (this.regT1L & 0xFF00) | value
|
|
272
272
|
break
|
|
273
273
|
|
|
274
|
-
case
|
|
274
|
+
case VIA.VIA_T1CH:
|
|
275
275
|
// Write to T1 high counter - loads latch into counter and starts timer
|
|
276
276
|
this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
|
|
277
277
|
this.regT1C = this.regT1L
|
|
278
|
-
this.clearIRQFlag(
|
|
278
|
+
this.clearIRQFlag(VIA.IRQ_T1)
|
|
279
279
|
this.T1_running = true
|
|
280
280
|
break
|
|
281
281
|
|
|
282
|
-
case
|
|
282
|
+
case VIA.VIA_T1LH:
|
|
283
283
|
// Write to T1 high latch
|
|
284
284
|
this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
|
|
285
|
-
this.clearIRQFlag(
|
|
285
|
+
this.clearIRQFlag(VIA.IRQ_T1)
|
|
286
286
|
break
|
|
287
287
|
|
|
288
|
-
case
|
|
288
|
+
case VIA.VIA_T2CL:
|
|
289
289
|
// Write to T2 low latch
|
|
290
290
|
this.regT2L = value
|
|
291
291
|
break
|
|
292
292
|
|
|
293
|
-
case
|
|
293
|
+
case VIA.VIA_T2CH:
|
|
294
294
|
// Write to T2 high counter - loads latch into counter and starts timer
|
|
295
295
|
this.regT2C = (value << 8) | this.regT2L
|
|
296
|
-
this.clearIRQFlag(
|
|
296
|
+
this.clearIRQFlag(VIA.IRQ_T2)
|
|
297
297
|
this.T2_running = true
|
|
298
298
|
break
|
|
299
299
|
|
|
300
|
-
case
|
|
300
|
+
case VIA.VIA_SR:
|
|
301
301
|
this.regSR = value
|
|
302
|
-
this.clearIRQFlag(
|
|
302
|
+
this.clearIRQFlag(VIA.IRQ_SR)
|
|
303
303
|
break
|
|
304
304
|
|
|
305
|
-
case
|
|
305
|
+
case VIA.VIA_ACR:
|
|
306
306
|
this.regACR = value
|
|
307
307
|
// ACR controls timer modes, shift register, and latching
|
|
308
308
|
break
|
|
309
309
|
|
|
310
|
-
case
|
|
310
|
+
case VIA.VIA_PCR:
|
|
311
311
|
this.regPCR = value
|
|
312
312
|
// PCR controls CA1, CA2, CB1, CB2 behavior
|
|
313
313
|
this.updateCA2()
|
|
314
314
|
this.updateCB2()
|
|
315
315
|
break
|
|
316
316
|
|
|
317
|
-
case
|
|
317
|
+
case VIA.VIA_IFR:
|
|
318
318
|
// Writing to IFR clears the corresponding interrupt flags
|
|
319
319
|
this.regIFR &= ~(value & 0x7F)
|
|
320
320
|
this.updateIRQ()
|
|
321
321
|
break
|
|
322
322
|
|
|
323
|
-
case
|
|
323
|
+
case VIA.VIA_IER:
|
|
324
324
|
// Bit 7 determines set (1) or clear (0)
|
|
325
325
|
if (value & 0x80) {
|
|
326
326
|
this.regIER |= (value & 0x7F)
|
|
@@ -330,7 +330,7 @@ export class GPIOCard implements IO {
|
|
|
330
330
|
this.updateIRQ()
|
|
331
331
|
break
|
|
332
332
|
|
|
333
|
-
case
|
|
333
|
+
case VIA.VIA_ORA_NH:
|
|
334
334
|
// Writing ORA without handshake (no interrupt flag clearing)
|
|
335
335
|
this.regORA = value
|
|
336
336
|
this.writePortA(value)
|
|
@@ -345,7 +345,7 @@ export class GPIOCard implements IO {
|
|
|
345
345
|
if (this.T1_running && this.regT1C > 0) {
|
|
346
346
|
this.regT1C--
|
|
347
347
|
if (this.regT1C === 0) {
|
|
348
|
-
this.setIRQFlag(
|
|
348
|
+
this.setIRQFlag(VIA.IRQ_T1)
|
|
349
349
|
|
|
350
350
|
// Check if timer is in free-run mode (ACR bit 6)
|
|
351
351
|
if (this.regACR & 0x40) {
|
|
@@ -365,7 +365,7 @@ export class GPIOCard implements IO {
|
|
|
365
365
|
if (this.T2_running && this.regT2C > 0) {
|
|
366
366
|
this.regT2C--
|
|
367
367
|
if (this.regT2C === 0) {
|
|
368
|
-
this.setIRQFlag(
|
|
368
|
+
this.setIRQFlag(VIA.IRQ_T2)
|
|
369
369
|
this.T2_running = false
|
|
370
370
|
}
|
|
371
371
|
}
|
|
@@ -386,20 +386,20 @@ export class GPIOCard implements IO {
|
|
|
386
386
|
for (let i = 0; i < this.portA_attachmentCount; i++) {
|
|
387
387
|
if (this.portA_attachments[i] !== null) {
|
|
388
388
|
if (this.portA_attachments[i]!.hasCA1Interrupt()) {
|
|
389
|
-
this.setIRQFlag(
|
|
389
|
+
this.setIRQFlag(VIA.IRQ_CA1)
|
|
390
390
|
}
|
|
391
391
|
if (this.portA_attachments[i]!.hasCA2Interrupt()) {
|
|
392
|
-
this.setIRQFlag(
|
|
392
|
+
this.setIRQFlag(VIA.IRQ_CA2)
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
for (let i = 0; i < this.portB_attachmentCount; i++) {
|
|
397
397
|
if (this.portB_attachments[i] !== null) {
|
|
398
398
|
if (this.portB_attachments[i]!.hasCB1Interrupt()) {
|
|
399
|
-
this.setIRQFlag(
|
|
399
|
+
this.setIRQFlag(VIA.IRQ_CB1)
|
|
400
400
|
}
|
|
401
401
|
if (this.portB_attachments[i]!.hasCB2Interrupt()) {
|
|
402
|
-
this.setIRQFlag(
|
|
402
|
+
this.setIRQFlag(VIA.IRQ_CB2)
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
}
|
|
@@ -413,9 +413,9 @@ export class GPIOCard implements IO {
|
|
|
413
413
|
private updateIRQ(): void {
|
|
414
414
|
// Update bit 7 of IFR based on enabled interrupts
|
|
415
415
|
if (this.regIFR & this.regIER & 0x7F) {
|
|
416
|
-
this.regIFR |=
|
|
416
|
+
this.regIFR |= VIA.IRQ_IRQ
|
|
417
417
|
} else {
|
|
418
|
-
this.regIFR &= ~
|
|
418
|
+
this.regIFR &= ~VIA.IRQ_IRQ
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
|
|
@@ -629,8 +629,8 @@ export class GPIOCard implements IO {
|
|
|
629
629
|
* Attach a GPIO device to Port A
|
|
630
630
|
* @param attachment - The attachment to add
|
|
631
631
|
*/
|
|
632
|
-
attachToPortA(attachment:
|
|
633
|
-
if (attachment !== null && this.portA_attachmentCount <
|
|
632
|
+
attachToPortA(attachment: Attachment): void {
|
|
633
|
+
if (attachment !== null && this.portA_attachmentCount < VIA.MAX_ATTACHMENTS_PER_PORT) {
|
|
634
634
|
this.portA_attachments[this.portA_attachmentCount++] = attachment
|
|
635
635
|
this.sortAttachmentsByPriority()
|
|
636
636
|
// Notify the attachment of current control line states
|
|
@@ -642,8 +642,8 @@ export class GPIOCard implements IO {
|
|
|
642
642
|
* Attach a GPIO device to Port B
|
|
643
643
|
* @param attachment - The attachment to add
|
|
644
644
|
*/
|
|
645
|
-
attachToPortB(attachment:
|
|
646
|
-
if (attachment !== null && this.portB_attachmentCount <
|
|
645
|
+
attachToPortB(attachment: Attachment): void {
|
|
646
|
+
if (attachment !== null && this.portB_attachmentCount < VIA.MAX_ATTACHMENTS_PER_PORT) {
|
|
647
647
|
this.portB_attachments[this.portB_attachmentCount++] = attachment
|
|
648
648
|
this.sortAttachmentsByPriority()
|
|
649
649
|
// Notify the attachment of current control line states
|
|
@@ -656,7 +656,7 @@ export class GPIOCard implements IO {
|
|
|
656
656
|
* @param index - The attachment index
|
|
657
657
|
* @returns The attachment or null if not found
|
|
658
658
|
*/
|
|
659
|
-
getPortAAttachment(index: number):
|
|
659
|
+
getPortAAttachment(index: number): Attachment | null {
|
|
660
660
|
if (index < this.portA_attachmentCount) {
|
|
661
661
|
return this.portA_attachments[index]
|
|
662
662
|
}
|
|
@@ -668,7 +668,7 @@ export class GPIOCard implements IO {
|
|
|
668
668
|
* @param index - The attachment index
|
|
669
669
|
* @returns The attachment or null if not found
|
|
670
670
|
*/
|
|
671
|
-
getPortBAttachment(index: number):
|
|
671
|
+
getPortBAttachment(index: number): Attachment | null {
|
|
672
672
|
if (index < this.portB_attachmentCount) {
|
|
673
673
|
return this.portB_attachments[index]
|
|
674
674
|
}
|
|
@@ -136,7 +136,7 @@ const CYCLES_PER_TICK = 128 // Must match Machine.ts ioTickInterval
|
|
|
136
136
|
const BORDER_X = (DISPLAY_WIDTH - TMS_PIXELS_X) / 2 // 32
|
|
137
137
|
const BORDER_Y = (DISPLAY_HEIGHT - TMS_PIXELS_Y) / 2 // 24
|
|
138
138
|
|
|
139
|
-
export class
|
|
139
|
+
export class Video implements IO {
|
|
140
140
|
|
|
141
141
|
raiseIRQ = () => {}
|
|
142
142
|
raiseNMI = () => {}
|