ac6502 1.10.0 → 1.11.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/dist/components/CPU.d.ts +4 -0
- package/dist/components/CPU.js +87 -30
- package/dist/components/CPU.js.map +1 -1
- package/dist/components/IO/ACIA.d.ts +13 -21
- package/dist/components/IO/ACIA.js +53 -151
- package/dist/components/IO/ACIA.js.map +1 -1
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +1 -1
- package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -1
- package/dist/components/IO/Empty.d.ts +1 -3
- package/dist/components/IO/Empty.js +1 -5
- package/dist/components/IO/Empty.js.map +1 -1
- package/dist/components/IO/RAMBank.d.ts +3 -4
- package/dist/components/IO/RAMBank.js +4 -13
- package/dist/components/IO/RAMBank.js.map +1 -1
- package/dist/components/IO/RTC.d.ts +2 -3
- package/dist/components/IO/RTC.js +17 -7
- package/dist/components/IO/RTC.js.map +1 -1
- package/dist/components/IO/Sound.d.ts +1 -3
- package/dist/components/IO/Sound.js +13 -23
- package/dist/components/IO/Sound.js.map +1 -1
- package/dist/components/IO/Storage.d.ts +1 -3
- package/dist/components/IO/Storage.js +1 -3
- package/dist/components/IO/Storage.js.map +1 -1
- package/dist/components/IO/VIA.d.ts +1 -3
- package/dist/components/IO/VIA.js +6 -7
- package/dist/components/IO/VIA.js.map +1 -1
- package/dist/components/IO/Video.d.ts +1 -3
- package/dist/components/IO/Video.js +3 -5
- package/dist/components/IO/Video.js.map +1 -1
- package/dist/components/IO.d.ts +1 -3
- package/dist/components/Machine.d.ts +1 -2
- package/dist/components/Machine.js +21 -74
- package/dist/components/Machine.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tests/IO/ACIA.test.js +57 -108
- package/dist/tests/IO/ACIA.test.js.map +1 -1
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +5 -0
- package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -1
- package/dist/tests/IO/Empty.test.js +2 -14
- package/dist/tests/IO/Empty.test.js.map +1 -1
- package/dist/tests/IO/RAMBank.test.js +5 -12
- package/dist/tests/IO/RAMBank.test.js.map +1 -1
- package/dist/tests/IO/RTC.test.js +7 -16
- package/dist/tests/IO/RTC.test.js.map +1 -1
- package/dist/tests/IO/Sound.test.js +6 -8
- package/dist/tests/IO/Sound.test.js.map +1 -1
- package/dist/tests/IO/Storage.test.js +0 -6
- package/dist/tests/IO/Storage.test.js.map +1 -1
- package/dist/tests/IO/VIA.test.js +6 -10
- package/dist/tests/IO/VIA.test.js.map +1 -1
- package/dist/tests/IO/Video.test.js +7 -7
- package/dist/tests/IO/Video.test.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CPU.ts +94 -31
- package/src/components/IO/ACIA.ts +57 -176
- package/src/components/IO/Attachments/KeyboardEncoderAttachment.ts +1 -1
- package/src/components/IO/Empty.ts +1 -4
- package/src/components/IO/RAMBank.ts +4 -15
- package/src/components/IO/RTC.ts +18 -7
- package/src/components/IO/Sound.ts +14 -27
- package/src/components/IO/Storage.ts +2 -5
- package/src/components/IO/VIA.ts +6 -8
- package/src/components/IO/Video.ts +5 -7
- package/src/components/IO.ts +1 -4
- package/src/components/Machine.ts +22 -90
- package/src/index.ts +1 -1
- package/src/tests/IO/ACIA.test.ts +60 -122
- package/src/tests/IO/Attachments/KeyboardEncoderAttachment.test.ts +6 -0
- package/src/tests/IO/Empty.test.ts +2 -17
- package/src/tests/IO/RAMBank.test.ts +5 -14
- package/src/tests/IO/RTC.test.ts +7 -20
- package/src/tests/IO/Sound.test.ts +6 -8
- package/src/tests/IO/Storage.test.ts +0 -7
- package/src/tests/IO/VIA.test.ts +6 -12
- package/src/tests/IO/Video.test.ts +7 -8
|
@@ -3,6 +3,9 @@ import { IO } from '../IO'
|
|
|
3
3
|
/**
|
|
4
4
|
* ACIA - Emulates a R6551 ACIA (Asynchronous Communications Interface Adapter)
|
|
5
5
|
*
|
|
6
|
+
* Simplified to match real R6551 hardware: single-byte TX/RX registers,
|
|
7
|
+
* no buffers, no baud rate timing (USB serial operates at USB speeds).
|
|
8
|
+
*
|
|
6
9
|
* Register Map:
|
|
7
10
|
* $00: Data Register (read/write)
|
|
8
11
|
* $01: Status Register (read) / Programmed Reset (write)
|
|
@@ -11,31 +14,24 @@ import { IO } from '../IO'
|
|
|
11
14
|
*/
|
|
12
15
|
export class ACIA implements IO {
|
|
13
16
|
|
|
14
|
-
raiseIRQ = () => {}
|
|
15
|
-
raiseNMI = () => {}
|
|
16
17
|
transmit?: (data: number) => void
|
|
17
18
|
|
|
18
19
|
// Registers
|
|
19
|
-
private
|
|
20
|
-
private
|
|
20
|
+
private txRegister: number = 0
|
|
21
|
+
private rxRegister: number = 0
|
|
21
22
|
private commandRegister: number = 0
|
|
22
23
|
private controlRegister: number = 0
|
|
23
24
|
|
|
24
|
-
// Buffers
|
|
25
|
-
private transmitBuffer: number[] = []
|
|
26
|
-
private receiveBuffer: number[] = []
|
|
27
|
-
|
|
28
25
|
// Status flags
|
|
26
|
+
private txRegEmpty: boolean = true
|
|
27
|
+
private rxRegFull: boolean = false
|
|
28
|
+
private txPending: boolean = false
|
|
29
|
+
private overrun: boolean = false
|
|
29
30
|
private parityError: boolean = false
|
|
30
31
|
private framingError: boolean = false
|
|
31
|
-
private overrun: boolean = false
|
|
32
32
|
private irqFlag: boolean = false
|
|
33
33
|
private echoMode: boolean = false
|
|
34
34
|
|
|
35
|
-
// Timing
|
|
36
|
-
private cycleCounter: number = 0
|
|
37
|
-
private baudRate: number = 115200
|
|
38
|
-
|
|
39
35
|
/**
|
|
40
36
|
* Read from ACIA register
|
|
41
37
|
*/
|
|
@@ -80,51 +76,32 @@ export class ACIA implements IO {
|
|
|
80
76
|
break
|
|
81
77
|
|
|
82
78
|
case 0x03: // Control Register
|
|
83
|
-
this.
|
|
79
|
+
this.controlRegister = data & 0xFF
|
|
84
80
|
break
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
/**
|
|
89
|
-
* Read data from receive
|
|
85
|
+
* Read data from receive register
|
|
90
86
|
*/
|
|
91
87
|
private readData(): number {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Update status: clear Receive Data Register Full
|
|
97
|
-
this.statusRegister &= ~0x08
|
|
98
|
-
|
|
99
|
-
// Check for overrun if more data arrives
|
|
100
|
-
if (this.receiveBuffer.length === 0) {
|
|
101
|
-
this.overrun = false
|
|
102
|
-
this.statusRegister &= ~0x04
|
|
103
|
-
}
|
|
88
|
+
// Clear Receive Data Register Full
|
|
89
|
+
this.rxRegFull = false
|
|
90
|
+
this.overrun = false
|
|
104
91
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.irqFlag = true
|
|
108
|
-
this.statusRegister |= 0x80
|
|
109
|
-
} else {
|
|
110
|
-
this.irqFlag = false
|
|
111
|
-
this.statusRegister &= ~0x80
|
|
112
|
-
}
|
|
92
|
+
// Clear IRQ if it was from RX
|
|
93
|
+
this.irqFlag = false
|
|
113
94
|
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return this.dataRegister
|
|
95
|
+
return this.rxRegister
|
|
118
96
|
}
|
|
119
97
|
|
|
120
98
|
/**
|
|
121
|
-
* Write data to transmit
|
|
99
|
+
* Write data to transmit register
|
|
122
100
|
*/
|
|
123
101
|
private writeData(data: number): void {
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.statusRegister &= ~0x10
|
|
102
|
+
this.txRegister = data & 0xFF
|
|
103
|
+
this.txRegEmpty = false
|
|
104
|
+
this.txPending = true
|
|
128
105
|
}
|
|
129
106
|
|
|
130
107
|
/**
|
|
@@ -148,27 +125,26 @@ export class ACIA implements IO {
|
|
|
148
125
|
if (this.overrun) status |= 0x04
|
|
149
126
|
|
|
150
127
|
// Bit 3: Receive Data Register Full
|
|
151
|
-
if (this.
|
|
128
|
+
if (this.rxRegFull) status |= 0x08
|
|
152
129
|
|
|
153
130
|
// Bit 4: Transmit Data Register Empty
|
|
154
|
-
if (this.
|
|
131
|
+
if (this.txRegEmpty) status |= 0x10
|
|
155
132
|
|
|
156
|
-
// Bit 5: Data Carrier Detect (DCD)
|
|
133
|
+
// Bit 5: Data Carrier Detect (DCD) - always connected
|
|
157
134
|
status &= ~0x20
|
|
158
135
|
|
|
159
|
-
// Bit 6: Data Set Ready (DSR)
|
|
136
|
+
// Bit 6: Data Set Ready (DSR) - always ready
|
|
160
137
|
status |= 0x40
|
|
161
138
|
|
|
162
139
|
// Bit 7: Interrupt (IRQ)
|
|
163
140
|
if (this.irqFlag) status |= 0x80
|
|
164
141
|
|
|
165
|
-
// Clear IRQ and error flags after
|
|
142
|
+
// Clear IRQ and error flags after reading (R6551 spec)
|
|
166
143
|
this.irqFlag = false
|
|
167
144
|
this.parityError = false
|
|
168
145
|
this.framingError = false
|
|
169
146
|
this.overrun = false
|
|
170
147
|
|
|
171
|
-
this.statusRegister = status
|
|
172
148
|
return status
|
|
173
149
|
}
|
|
174
150
|
|
|
@@ -178,82 +154,16 @@ export class ACIA implements IO {
|
|
|
178
154
|
private writeCommand(data: number): void {
|
|
179
155
|
this.commandRegister = data & 0xFF
|
|
180
156
|
|
|
181
|
-
// Bits 0-1: DTR control
|
|
182
|
-
// const dtrControl = data & 0x03
|
|
183
|
-
|
|
184
|
-
// Bit 1: Receiver Interrupt Request Disable (RIIE) — 0 = IRQ enabled, 1 = disabled (active low)
|
|
185
|
-
const receiveIRQEnabled = (data & 0x02) === 0
|
|
186
|
-
|
|
187
|
-
// Bits 3-2: Transmitter Interrupt Control (TIC)
|
|
188
|
-
// const transmitControl = (data >> 2) & 0x03
|
|
189
|
-
|
|
190
157
|
// Bit 4: Echo Mode Enable (EME)
|
|
191
158
|
this.echoMode = (data & 0x10) !== 0
|
|
192
|
-
|
|
193
|
-
// Bits 6-7: Parity control
|
|
194
|
-
// const parityControl = (data >> 6) & 0x03
|
|
195
|
-
|
|
196
|
-
// Handle receive IRQ
|
|
197
|
-
if (receiveIRQEnabled && this.receiveBuffer.length > 0) {
|
|
198
|
-
this.irqFlag = true
|
|
199
|
-
this.statusRegister |= 0x80
|
|
200
|
-
this.raiseIRQ()
|
|
201
|
-
} else if (!receiveIRQEnabled) {
|
|
202
|
-
this.irqFlag = false
|
|
203
|
-
this.statusRegister &= ~0x80
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Write to control register
|
|
209
|
-
*/
|
|
210
|
-
private writeControl(data: number): void {
|
|
211
|
-
this.controlRegister = data & 0xFF
|
|
212
|
-
|
|
213
|
-
// Bits 0-3: Baud rate
|
|
214
|
-
const baudRateCode = data & 0x0F
|
|
215
|
-
this.baudRate = this.getBaudRate(baudRateCode)
|
|
216
|
-
|
|
217
|
-
// Bit 4: Receiver clock source (internal/external)
|
|
218
|
-
const receiverClockSource = (data & 0x10) !== 0
|
|
219
|
-
|
|
220
|
-
// Bits 5-6: Word length (5, 6, 7, or 8 bits)
|
|
221
|
-
const wordLength = ((data >> 5) & 0x03) + 5
|
|
222
|
-
|
|
223
|
-
// Bit 7: Stop bits (1 or 2)
|
|
224
|
-
const stopBits = (data & 0x80) ? 2 : 1
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get baud rate from control register code
|
|
229
|
-
*/
|
|
230
|
-
private getBaudRate(code: number): number {
|
|
231
|
-
const baudRates = [
|
|
232
|
-
115200, // 0000 (actually 16x external clock, using 115200 as default)
|
|
233
|
-
50, // 0001
|
|
234
|
-
75, // 0010
|
|
235
|
-
110, // 0011
|
|
236
|
-
135, // 0100
|
|
237
|
-
150, // 0101
|
|
238
|
-
300, // 0110
|
|
239
|
-
600, // 0111
|
|
240
|
-
1200, // 1000
|
|
241
|
-
1800, // 1001
|
|
242
|
-
2400, // 1010
|
|
243
|
-
3600, // 1011
|
|
244
|
-
4800, // 1100
|
|
245
|
-
7200, // 1101
|
|
246
|
-
9600, // 1110
|
|
247
|
-
19200 // 1111
|
|
248
|
-
]
|
|
249
|
-
return baudRates[code] || 115200
|
|
250
159
|
}
|
|
251
160
|
|
|
252
161
|
/**
|
|
253
162
|
* Programmed reset
|
|
254
163
|
*/
|
|
255
164
|
private programmedReset(): void {
|
|
256
|
-
this.
|
|
165
|
+
this.txRegEmpty = true
|
|
166
|
+
this.txPending = false
|
|
257
167
|
this.parityError = false
|
|
258
168
|
this.framingError = false
|
|
259
169
|
this.overrun = false
|
|
@@ -261,100 +171,71 @@ export class ACIA implements IO {
|
|
|
261
171
|
}
|
|
262
172
|
|
|
263
173
|
/**
|
|
264
|
-
* Tick -
|
|
174
|
+
* Tick - process TX/RX each cycle, return interrupt status
|
|
265
175
|
*/
|
|
266
|
-
tick(frequency: number):
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (!(this.commandRegister & 0x02) && this.receiveBuffer.length > 0) {
|
|
271
|
-
this.irqFlag = true
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (this.irqFlag) {
|
|
275
|
-
this.raiseIRQ()
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
this.cycleCounter++
|
|
176
|
+
tick(frequency: number): number {
|
|
177
|
+
// Handle pending transmit - send immediately (no baud timing)
|
|
178
|
+
if (this.txPending) {
|
|
179
|
+
this.txPending = false
|
|
279
180
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const cyclesPerByte = Math.floor((frequency / this.baudRate) * 10)
|
|
283
|
-
|
|
284
|
-
// Simulate transmission based on actual baud rate
|
|
285
|
-
if (this.cycleCounter >= cyclesPerByte && this.transmitBuffer.length > 0) {
|
|
286
|
-
this.cycleCounter = 0
|
|
287
|
-
|
|
288
|
-
// Transmit one byte
|
|
289
|
-
const byte = this.transmitBuffer.shift()
|
|
290
|
-
|
|
291
|
-
if (byte !== undefined && this.transmit) {
|
|
292
|
-
this.transmit(byte)
|
|
181
|
+
if (this.transmit) {
|
|
182
|
+
this.transmit(this.txRegister)
|
|
293
183
|
}
|
|
294
184
|
|
|
295
|
-
|
|
296
|
-
if (this.transmitBuffer.length === 0) {
|
|
297
|
-
this.statusRegister |= 0x10
|
|
185
|
+
this.txRegEmpty = true
|
|
298
186
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.statusRegister |= 0x80
|
|
303
|
-
this.raiseIRQ()
|
|
304
|
-
}
|
|
187
|
+
// Trigger transmit complete IRQ if enabled (TIC bits 3-2 = 01)
|
|
188
|
+
if ((this.commandRegister & 0x0C) === 0x04) {
|
|
189
|
+
this.irqFlag = true
|
|
305
190
|
}
|
|
191
|
+
|
|
192
|
+
// Echo mode: received data echoed back
|
|
193
|
+
// (echo of transmitted data is handled in onData)
|
|
306
194
|
}
|
|
195
|
+
|
|
196
|
+
// Return IRQ status
|
|
197
|
+
return this.irqFlag ? 0x80 : 0
|
|
307
198
|
}
|
|
308
199
|
|
|
309
200
|
/**
|
|
310
201
|
* Reset the ACIA
|
|
311
202
|
*/
|
|
312
203
|
reset(coldStart: boolean): void {
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
204
|
+
this.txRegister = 0
|
|
205
|
+
this.rxRegister = 0
|
|
315
206
|
this.commandRegister = 0
|
|
316
207
|
this.controlRegister = 0
|
|
317
|
-
|
|
318
|
-
this.
|
|
319
|
-
this.
|
|
320
|
-
|
|
208
|
+
|
|
209
|
+
this.txRegEmpty = true
|
|
210
|
+
this.rxRegFull = false
|
|
211
|
+
this.txPending = false
|
|
212
|
+
this.overrun = false
|
|
321
213
|
this.parityError = false
|
|
322
214
|
this.framingError = false
|
|
323
|
-
this.overrun = false
|
|
324
215
|
this.irqFlag = false
|
|
325
216
|
this.echoMode = false
|
|
326
|
-
|
|
327
|
-
this.cycleCounter = 0
|
|
328
|
-
this.baudRate = 115200
|
|
329
217
|
}
|
|
330
218
|
|
|
331
219
|
/**
|
|
332
220
|
* Receive data from external source
|
|
333
221
|
*/
|
|
334
222
|
onData(data: number): void {
|
|
335
|
-
if (this.
|
|
223
|
+
if (this.rxRegFull) {
|
|
336
224
|
// Overrun: new data arrived before the previous byte was read
|
|
337
225
|
this.overrun = true
|
|
338
|
-
this.statusRegister |= 0x04
|
|
339
226
|
}
|
|
340
227
|
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
// Set Receive Data Register Full flag
|
|
344
|
-
this.statusRegister |= 0x08
|
|
228
|
+
this.rxRegister = data & 0xFF
|
|
229
|
+
this.rxRegFull = true
|
|
345
230
|
|
|
346
231
|
// Trigger receive IRQ if enabled (bit 1 = 0 means enabled, active low)
|
|
347
232
|
if (!(this.commandRegister & 0x02)) {
|
|
348
233
|
this.irqFlag = true
|
|
349
|
-
this.statusRegister |= 0x80
|
|
350
|
-
this.raiseIRQ()
|
|
351
234
|
}
|
|
352
235
|
|
|
353
236
|
// Echo mode: automatically transmit received data
|
|
354
|
-
if (this.echoMode) {
|
|
355
|
-
this.
|
|
356
|
-
// Clear Transmit Data Register Empty flag
|
|
357
|
-
this.statusRegister &= ~0x10
|
|
237
|
+
if (this.echoMode && this.transmit) {
|
|
238
|
+
this.transmit(data & 0xFF)
|
|
358
239
|
}
|
|
359
240
|
}
|
|
360
241
|
}
|
|
@@ -86,7 +86,7 @@ export class KeyboardEncoderAttachment extends AttachmentBase {
|
|
|
86
86
|
// 'A' = PS/2 encoder on Port A (CA1 IRQ only)
|
|
87
87
|
// 'B' = Matrix encoder on Port B (CB1 IRQ only)
|
|
88
88
|
// 'both' = both ports active (default)
|
|
89
|
-
activePort: 'A' | 'B' | 'both' = '
|
|
89
|
+
activePort: 'A' | 'B' | 'both' = 'B'
|
|
90
90
|
|
|
91
91
|
// Port A state
|
|
92
92
|
private asciiDataA: number = 0x00
|
|
@@ -2,15 +2,12 @@ import { IO } from '../IO'
|
|
|
2
2
|
|
|
3
3
|
export class Empty implements IO {
|
|
4
4
|
|
|
5
|
-
raiseIRQ = () => {}
|
|
6
|
-
raiseNMI = () => {}
|
|
7
|
-
|
|
8
5
|
read(address: number): number {
|
|
9
6
|
return 0
|
|
10
7
|
}
|
|
11
8
|
|
|
12
9
|
write(address: number, data: number): void {}
|
|
13
|
-
tick(frequency: number):
|
|
10
|
+
tick(frequency: number): number { return 0 }
|
|
14
11
|
reset(coldStart: boolean): void {}
|
|
15
12
|
|
|
16
13
|
}
|
|
@@ -20,40 +20,29 @@ export class RAMBank implements IO {
|
|
|
20
20
|
data: number[] = [...Array(RAMBank.TOTAL_SIZE)].fill(0x00)
|
|
21
21
|
currentBank: number = 0
|
|
22
22
|
|
|
23
|
-
raiseIRQ = () => {}
|
|
24
|
-
raiseNMI = () => {}
|
|
25
|
-
|
|
26
23
|
/**
|
|
27
|
-
* Read from RAM
|
|
24
|
+
* Read from RAM - all addresses read from the data array
|
|
28
25
|
*/
|
|
29
26
|
read(address: number): number {
|
|
30
|
-
// Reading from bank control register returns current bank number
|
|
31
|
-
if (address === RAMBank.BANK_CONTROL_REGISTER) {
|
|
32
|
-
return this.currentBank
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Calculate actual address in RAM: bank * bank_size + offset and return data
|
|
36
27
|
return this.data[this.currentBank * RAMBank.BANK_SIZE + address]
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
/**
|
|
40
31
|
* Write to RAM or bank control register
|
|
32
|
+
* Writing to $3FF sets the bank AND writes through to the new bank's data
|
|
41
33
|
*/
|
|
42
34
|
write(address: number, data: number): void {
|
|
43
|
-
// Writing to bank control register switches banks
|
|
44
35
|
if (address === RAMBank.BANK_CONTROL_REGISTER) {
|
|
45
|
-
this.currentBank = data & 0xFF
|
|
46
|
-
return
|
|
36
|
+
this.currentBank = data & 0xFF
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
// Calculate actual address in RAM: bank * bank_size + offset and store data
|
|
50
39
|
this.data[this.currentBank * RAMBank.BANK_SIZE + address] = data & 0xFF
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
/**
|
|
54
43
|
* Tick - no timing behavior for RAM
|
|
55
44
|
*/
|
|
56
|
-
tick(frequency: number):
|
|
45
|
+
tick(frequency: number): number { return 0 }
|
|
57
46
|
|
|
58
47
|
/**
|
|
59
48
|
* Reset the RAM card
|
package/src/components/IO/RTC.ts
CHANGED
|
@@ -26,8 +26,6 @@ import { IO } from '../IO'
|
|
|
26
26
|
* 0x13: RAM Data (Extended RAM Data at address pointed to by 0x10)
|
|
27
27
|
*/
|
|
28
28
|
export class RTC implements IO {
|
|
29
|
-
raiseIRQ = () => {}
|
|
30
|
-
raiseNMI = () => {}
|
|
31
29
|
|
|
32
30
|
// RTC Registers (user-visible)
|
|
33
31
|
private userSeconds: number = 0 // 0x00
|
|
@@ -61,6 +59,7 @@ export class RTC implements IO {
|
|
|
61
59
|
private watchdog2: number = 0 // 0x0D (0.1 Second and Second)
|
|
62
60
|
private watchdogCounterCentis: number = 0
|
|
63
61
|
private watchdogCycleCounter: number = 0
|
|
62
|
+
private nmiPending: boolean = false
|
|
64
63
|
|
|
65
64
|
// Control registers
|
|
66
65
|
private controlA: number = 0 // 0x0E
|
|
@@ -261,7 +260,6 @@ export class RTC implements IO {
|
|
|
261
260
|
if ((this.controlA & flagMask) === 0) return
|
|
262
261
|
if ((this.controlB & enableMask) === 0) return
|
|
263
262
|
this.controlA |= 0x01 // Set IRQF flag (bit 0)
|
|
264
|
-
this.raiseIRQ()
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
private setKickstartFlag(): void {
|
|
@@ -304,7 +302,7 @@ export class RTC implements IO {
|
|
|
304
302
|
} else {
|
|
305
303
|
// WDS=1 steers watchdog to reset; emulate by clearing WDE
|
|
306
304
|
this.controlB &= ~0x02
|
|
307
|
-
this.
|
|
305
|
+
this.nmiPending = true
|
|
308
306
|
}
|
|
309
307
|
}
|
|
310
308
|
|
|
@@ -449,7 +447,7 @@ export class RTC implements IO {
|
|
|
449
447
|
|
|
450
448
|
|
|
451
449
|
|
|
452
|
-
tick(frequency: number
|
|
450
|
+
tick(frequency: number): number {
|
|
453
451
|
this.cpuFrequency = frequency > 0 ? frequency : 1000000
|
|
454
452
|
|
|
455
453
|
const teEnabled = (this.controlB & 0x80) !== 0
|
|
@@ -457,12 +455,17 @@ export class RTC implements IO {
|
|
|
457
455
|
if ((this.monthControl & 0x80) !== 0) {
|
|
458
456
|
// EOSC=1: oscillator disabled (DS1511Y+ active-low enable)
|
|
459
457
|
this.stepWatchdog()
|
|
460
|
-
|
|
458
|
+
let status = (this.controlA & 0x01) ? 0x80 : 0
|
|
459
|
+
if (this.nmiPending) {
|
|
460
|
+
status |= 0x40
|
|
461
|
+
this.nmiPending = false
|
|
462
|
+
}
|
|
463
|
+
return status
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
// Advance the clock by counting CPU cycles.
|
|
464
467
|
// One emulated second = cpuFrequency cycles.
|
|
465
|
-
this.cycleAccumulator
|
|
468
|
+
this.cycleAccumulator++
|
|
466
469
|
while (this.cycleAccumulator >= this.cpuFrequency) {
|
|
467
470
|
this.cycleAccumulator -= this.cpuFrequency
|
|
468
471
|
this.incrementTime()
|
|
@@ -482,6 +485,14 @@ export class RTC implements IO {
|
|
|
482
485
|
}
|
|
483
486
|
|
|
484
487
|
this.stepWatchdog()
|
|
488
|
+
|
|
489
|
+
// Return interrupt status: bit 7 = IRQ, bit 6 = NMI
|
|
490
|
+
let status = (this.controlA & 0x01) ? 0x80 : 0
|
|
491
|
+
if (this.nmiPending) {
|
|
492
|
+
status |= 0x40
|
|
493
|
+
this.nmiPending = false
|
|
494
|
+
}
|
|
495
|
+
return status
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
reset(coldStart: boolean): void {
|
|
@@ -40,9 +40,6 @@ export const SID_CLOCK_PAL = 985248
|
|
|
40
40
|
/** Number of SID registers */
|
|
41
41
|
const NUM_REGISTERS = 29
|
|
42
42
|
|
|
43
|
-
/** Cycles per tick from Machine.ts ioTickInterval */
|
|
44
|
-
const CYCLES_PER_TICK = 128
|
|
45
|
-
|
|
46
43
|
// Register offsets within each voice (relative to voice base)
|
|
47
44
|
const REG_FREQ_LO = 0
|
|
48
45
|
const REG_FREQ_HI = 1
|
|
@@ -162,9 +159,6 @@ export class SIDVoice {
|
|
|
162
159
|
|
|
163
160
|
export class Sound implements IO {
|
|
164
161
|
|
|
165
|
-
raiseIRQ = () => {}
|
|
166
|
-
raiseNMI = () => {}
|
|
167
|
-
|
|
168
162
|
/** Callback to push audio samples to the host emulator */
|
|
169
163
|
pushSamples?: (samples: Float32Array) => void
|
|
170
164
|
|
|
@@ -202,7 +196,7 @@ export class Sound implements IO {
|
|
|
202
196
|
sidClock: number = SID_CLOCK_NTSC
|
|
203
197
|
|
|
204
198
|
/** Internal sample buffer for pushing to host */
|
|
205
|
-
private sampleBuffer: Float32Array = new Float32Array(
|
|
199
|
+
private sampleBuffer: Float32Array = new Float32Array(128)
|
|
206
200
|
private sampleBufferIndex: number = 0
|
|
207
201
|
|
|
208
202
|
// ================================================================
|
|
@@ -247,35 +241,28 @@ export class Sound implements IO {
|
|
|
247
241
|
}
|
|
248
242
|
}
|
|
249
243
|
|
|
250
|
-
tick(frequency: number):
|
|
244
|
+
tick(frequency: number): number {
|
|
251
245
|
// SID clock runs at the CPU clock rate
|
|
252
246
|
this.sidClock = frequency
|
|
253
247
|
|
|
254
|
-
// Each tick represents
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
for (let c = 0; c < cycles; c++) {
|
|
258
|
-
this.clockOneCycle()
|
|
248
|
+
// Each tick represents 1 SID clock cycle
|
|
249
|
+
this.clockOneCycle()
|
|
259
250
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
251
|
+
// Sample rate conversion: accumulate and downsample
|
|
252
|
+
this.cycleAccumulator += this.sampleRate
|
|
253
|
+
if (this.cycleAccumulator >= this.sidClock) {
|
|
254
|
+
this.cycleAccumulator -= this.sidClock
|
|
264
255
|
|
|
265
|
-
|
|
266
|
-
|
|
256
|
+
const sample = this.generateSample()
|
|
257
|
+
this.sampleBuffer[this.sampleBufferIndex++] = sample
|
|
267
258
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
259
|
+
// Buffer full - push to host
|
|
260
|
+
if (this.sampleBufferIndex >= this.sampleBuffer.length) {
|
|
261
|
+
this.flushSampleBuffer()
|
|
272
262
|
}
|
|
273
263
|
}
|
|
274
264
|
|
|
275
|
-
|
|
276
|
-
if (this.sampleBufferIndex > 0) {
|
|
277
|
-
this.flushSampleBuffer()
|
|
278
|
-
}
|
|
265
|
+
return 0
|
|
279
266
|
}
|
|
280
267
|
|
|
281
268
|
reset(coldStart: boolean): void {
|
|
@@ -65,9 +65,6 @@ export class Storage implements IO {
|
|
|
65
65
|
private isIdentifying: boolean = false
|
|
66
66
|
private isTransferring: boolean = false
|
|
67
67
|
|
|
68
|
-
raiseIRQ = () => {}
|
|
69
|
-
raiseNMI = () => {}
|
|
70
|
-
|
|
71
68
|
constructor() {
|
|
72
69
|
// Initialize storage and identity buffers
|
|
73
70
|
this.storage = new Uint8Array(Storage.STORAGE_SIZE)
|
|
@@ -129,8 +126,8 @@ export class Storage implements IO {
|
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
tick(frequency: number):
|
|
133
|
-
|
|
129
|
+
tick(frequency: number): number {
|
|
130
|
+
return 0
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
reset(coldStart: boolean): void {
|
package/src/components/IO/VIA.ts
CHANGED
|
@@ -78,9 +78,6 @@ export class VIA implements IO {
|
|
|
78
78
|
private portA_attachmentCount: number = 0
|
|
79
79
|
private portB_attachmentCount: number = 0
|
|
80
80
|
|
|
81
|
-
raiseIRQ = () => {}
|
|
82
|
-
raiseNMI = () => {}
|
|
83
|
-
|
|
84
81
|
constructor() {
|
|
85
82
|
this.reset(true)
|
|
86
83
|
}
|
|
@@ -333,12 +330,12 @@ export class VIA implements IO {
|
|
|
333
330
|
}
|
|
334
331
|
}
|
|
335
332
|
|
|
336
|
-
tick(frequency: number
|
|
333
|
+
tick(frequency: number): number {
|
|
337
334
|
this.tickCounter++
|
|
338
335
|
|
|
339
336
|
// Update Timer 1
|
|
340
337
|
if (this.T1_running && this.regT1C > 0) {
|
|
341
|
-
this.regT1C
|
|
338
|
+
this.regT1C--
|
|
342
339
|
if (this.regT1C <= 0) {
|
|
343
340
|
this.setIRQFlag(VIA.IRQ_T1)
|
|
344
341
|
|
|
@@ -359,7 +356,7 @@ export class VIA implements IO {
|
|
|
359
356
|
|
|
360
357
|
// Update Timer 2
|
|
361
358
|
if (this.T2_running && this.regT2C > 0) {
|
|
362
|
-
this.regT2C
|
|
359
|
+
this.regT2C--
|
|
363
360
|
if (this.regT2C <= 0) {
|
|
364
361
|
this.setIRQFlag(VIA.IRQ_T2)
|
|
365
362
|
this.regT2C = 0
|
|
@@ -401,10 +398,11 @@ export class VIA implements IO {
|
|
|
401
398
|
}
|
|
402
399
|
}
|
|
403
400
|
|
|
404
|
-
//
|
|
401
|
+
// Return IRQ status if any enabled interrupt is active
|
|
405
402
|
if (this.regIFR & this.regIER & 0x7F) {
|
|
406
|
-
|
|
403
|
+
return 0x80
|
|
407
404
|
}
|
|
405
|
+
return 0
|
|
408
406
|
}
|
|
409
407
|
|
|
410
408
|
private updateIRQ(): void {
|