ac6502 1.9.3 → 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.
Files changed (77) hide show
  1. package/dist/components/CPU.d.ts +4 -0
  2. package/dist/components/CPU.js +87 -30
  3. package/dist/components/CPU.js.map +1 -1
  4. package/dist/components/IO/ACIA.d.ts +13 -21
  5. package/dist/components/IO/ACIA.js +53 -151
  6. package/dist/components/IO/ACIA.js.map +1 -1
  7. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +5 -10
  8. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +43 -266
  9. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -1
  10. package/dist/components/IO/Empty.d.ts +1 -3
  11. package/dist/components/IO/Empty.js +1 -5
  12. package/dist/components/IO/Empty.js.map +1 -1
  13. package/dist/components/IO/RAMBank.d.ts +3 -4
  14. package/dist/components/IO/RAMBank.js +4 -13
  15. package/dist/components/IO/RAMBank.js.map +1 -1
  16. package/dist/components/IO/RTC.d.ts +2 -3
  17. package/dist/components/IO/RTC.js +17 -7
  18. package/dist/components/IO/RTC.js.map +1 -1
  19. package/dist/components/IO/Sound.d.ts +1 -3
  20. package/dist/components/IO/Sound.js +13 -23
  21. package/dist/components/IO/Sound.js.map +1 -1
  22. package/dist/components/IO/Storage.d.ts +1 -3
  23. package/dist/components/IO/Storage.js +1 -3
  24. package/dist/components/IO/Storage.js.map +1 -1
  25. package/dist/components/IO/VIA.d.ts +1 -3
  26. package/dist/components/IO/VIA.js +6 -7
  27. package/dist/components/IO/VIA.js.map +1 -1
  28. package/dist/components/IO/Video.d.ts +1 -3
  29. package/dist/components/IO/Video.js +3 -5
  30. package/dist/components/IO/Video.js.map +1 -1
  31. package/dist/components/IO.d.ts +1 -3
  32. package/dist/components/Machine.d.ts +1 -2
  33. package/dist/components/Machine.js +21 -74
  34. package/dist/components/Machine.js.map +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/tests/IO/ACIA.test.js +57 -108
  38. package/dist/tests/IO/ACIA.test.js.map +1 -1
  39. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +334 -574
  40. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -1
  41. package/dist/tests/IO/Empty.test.js +2 -14
  42. package/dist/tests/IO/Empty.test.js.map +1 -1
  43. package/dist/tests/IO/RAMBank.test.js +5 -12
  44. package/dist/tests/IO/RAMBank.test.js.map +1 -1
  45. package/dist/tests/IO/RTC.test.js +7 -16
  46. package/dist/tests/IO/RTC.test.js.map +1 -1
  47. package/dist/tests/IO/Sound.test.js +6 -8
  48. package/dist/tests/IO/Sound.test.js.map +1 -1
  49. package/dist/tests/IO/Storage.test.js +0 -6
  50. package/dist/tests/IO/Storage.test.js.map +1 -1
  51. package/dist/tests/IO/VIA.test.js +6 -10
  52. package/dist/tests/IO/VIA.test.js.map +1 -1
  53. package/dist/tests/IO/Video.test.js +7 -7
  54. package/dist/tests/IO/Video.test.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/components/CPU.ts +94 -31
  57. package/src/components/IO/ACIA.ts +57 -176
  58. package/src/components/IO/Attachments/KeyboardEncoderAttachment.ts +45 -217
  59. package/src/components/IO/Empty.ts +1 -4
  60. package/src/components/IO/RAMBank.ts +4 -15
  61. package/src/components/IO/RTC.ts +18 -7
  62. package/src/components/IO/Sound.ts +14 -27
  63. package/src/components/IO/Storage.ts +2 -5
  64. package/src/components/IO/VIA.ts +6 -8
  65. package/src/components/IO/Video.ts +5 -7
  66. package/src/components/IO.ts +1 -4
  67. package/src/components/Machine.ts +22 -90
  68. package/src/index.ts +1 -1
  69. package/src/tests/IO/ACIA.test.ts +60 -122
  70. package/src/tests/IO/Attachments/KeyboardEncoderAttachment.test.ts +342 -676
  71. package/src/tests/IO/Empty.test.ts +2 -17
  72. package/src/tests/IO/RAMBank.test.ts +5 -14
  73. package/src/tests/IO/RTC.test.ts +7 -20
  74. package/src/tests/IO/Sound.test.ts +6 -8
  75. package/src/tests/IO/Storage.test.ts +0 -7
  76. package/src/tests/IO/VIA.test.ts +6 -12
  77. package/src/tests/IO/Video.test.ts +7 -8
@@ -28,6 +28,8 @@ export class CPU {
28
28
  cyclesRem: number = 0 // Counts how many cycles the current instruction has remaining
29
29
  cycles: number = 0 // Counts the total number of cycles executed
30
30
 
31
+ private irqLine: boolean = false
32
+
31
33
  a: number = 0x00
32
34
  x: number = 0x00
33
35
  y: number = 0x00
@@ -103,6 +105,14 @@ export class CPU {
103
105
  }
104
106
  }
105
107
 
108
+ irqTrigger(): void {
109
+ this.irqLine = true
110
+ }
111
+
112
+ irqClear(): void {
113
+ this.irqLine = false
114
+ }
115
+
106
116
  nmi(): void {
107
117
  // Push the program counter onto the stack
108
118
  this.write(0x0100 + this.sp, (this.pc >> 8) & 0x00FF)
@@ -148,6 +158,11 @@ export class CPU {
148
158
  // addrMode() and opcode() return 1 or 0 if additional clock cycles are required
149
159
  this.cyclesRem += addCycleAddrMode & addCycleOpcode
150
160
  this.cycles += addCycleAddrMode & addCycleOpcode
161
+
162
+ // Level-triggered IRQ: check after instruction decode
163
+ if (this.irqLine) {
164
+ this.irq()
165
+ }
151
166
  }
152
167
 
153
168
  this.cyclesRem--
@@ -405,13 +420,33 @@ export class CPU {
405
420
  private ADC(): number {
406
421
  this.fetch()
407
422
 
408
- this.temp = this.a + this.fetched + this.getFlag(CPU.C)
409
- this.setFlag(CPU.C, (this.temp & 0xFF00) != 0)
410
- this.setFlag(CPU.Z, (this.temp & 0x00FF) == 0)
411
- this.setFlag(CPU.V, ((this.temp ^ this.a) & (this.temp ^ this.fetched) & 0x0080) != 0)
412
- this.setFlag(CPU.N, (this.temp & 0x0080) != 0)
423
+ if (this.getFlag(CPU.D)) {
424
+ // BCD decimal mode (65C02)
425
+ const c = this.getFlag(CPU.C)
426
+ const bin = this.a + this.fetched + c
427
+
428
+ let lo = (this.a & 0x0F) + (this.fetched & 0x0F) + c
429
+ if (lo > 0x09) lo += 0x06
430
+ let hi = (this.a >> 4) + (this.fetched >> 4) + (lo > 0x0F ? 1 : 0)
431
+ if (hi > 0x09) hi += 0x06
432
+
433
+ const result = (hi << 4) | (lo & 0x0F)
434
+
435
+ this.setFlag(CPU.Z, (bin & 0xFF) === 0)
436
+ this.setFlag(CPU.V, ((this.a ^ bin) & (this.fetched ^ bin) & 0x80) !== 0)
437
+ this.setFlag(CPU.N, (bin & 0x80) !== 0)
438
+ this.setFlag(CPU.C, hi > 0x0F)
413
439
 
414
- this.a = this.temp & 0x00FF
440
+ this.a = result & 0xFF
441
+ } else {
442
+ this.temp = this.a + this.fetched + this.getFlag(CPU.C)
443
+ this.setFlag(CPU.C, (this.temp & 0xFF00) != 0)
444
+ this.setFlag(CPU.Z, (this.temp & 0x00FF) == 0)
445
+ this.setFlag(CPU.V, ((this.temp ^ this.a) & (this.temp ^ this.fetched) & 0x0080) != 0)
446
+ this.setFlag(CPU.N, (this.temp & 0x0080) != 0)
447
+
448
+ this.a = this.temp & 0x00FF
449
+ }
415
450
 
416
451
  return 1
417
452
  }
@@ -441,11 +476,11 @@ export class CPU {
441
476
 
442
477
  private BCC(): number {
443
478
  if (this.getFlag(CPU.C) == 0) {
444
- this.cycles++
479
+ this.cyclesRem++
445
480
  this.addrAbs = this.pc + this.addrRel
446
481
 
447
482
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
448
- this.cycles++
483
+ this.cyclesRem++
449
484
  }
450
485
 
451
486
  this.pc = this.addrAbs
@@ -455,11 +490,11 @@ export class CPU {
455
490
 
456
491
  private BCS(): number {
457
492
  if (this.getFlag(CPU.C) == 1) {
458
- this.cycles++
493
+ this.cyclesRem++
459
494
  this.addrAbs = this.pc + this.addrRel
460
495
 
461
496
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
462
- this.cycles++
497
+ this.cyclesRem++
463
498
  }
464
499
 
465
500
  this.pc = this.addrAbs
@@ -469,10 +504,10 @@ export class CPU {
469
504
 
470
505
  private BEQ(): number {
471
506
  if (this.getFlag(CPU.Z) == 1) {
472
- this.cycles++
507
+ this.cyclesRem++
473
508
  this.addrAbs = this.pc + this.addrRel
474
509
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
475
- this.cycles++
510
+ this.cyclesRem++
476
511
  }
477
512
 
478
513
  this.pc = this.addrAbs
@@ -489,13 +524,21 @@ export class CPU {
489
524
  return 0
490
525
  }
491
526
 
527
+ private BIT_IMM(): number {
528
+ this.fetch()
529
+ this.temp = this.a & this.fetched
530
+ this.setFlag(CPU.Z, (this.temp & 0x00FF) == 0x00)
531
+ // N and V are NOT modified for BIT immediate (65C02)
532
+ return 0
533
+ }
534
+
492
535
  private BMI(): number {
493
536
  if (this.getFlag(CPU.N) == 1) {
494
- this.cycles++
537
+ this.cyclesRem++
495
538
  this.addrAbs = this.pc + this.addrRel
496
539
 
497
540
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
498
- this.cycles++
541
+ this.cyclesRem++
499
542
  }
500
543
 
501
544
  this.pc = this.addrAbs
@@ -505,11 +548,11 @@ export class CPU {
505
548
 
506
549
  private BNE(): number {
507
550
  if (this.getFlag(CPU.Z) == 0) {
508
- this.cycles++
551
+ this.cyclesRem++
509
552
  this.addrAbs = this.pc + this.addrRel
510
553
 
511
554
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
512
- this.cycles++
555
+ this.cyclesRem++
513
556
  }
514
557
 
515
558
  this.pc = this.addrAbs
@@ -519,11 +562,11 @@ export class CPU {
519
562
 
520
563
  private BPL(): number {
521
564
  if (this.getFlag(CPU.N) == 0) {
522
- this.cycles++
565
+ this.cyclesRem++
523
566
  this.addrAbs = this.pc + this.addrRel
524
567
 
525
568
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
526
- this.cycles++
569
+ this.cyclesRem++
527
570
  }
528
571
 
529
572
  this.pc = this.addrAbs
@@ -552,11 +595,11 @@ export class CPU {
552
595
 
553
596
  private BVC(): number {
554
597
  if (this.getFlag(CPU.V) == 0) {
555
- this.cycles++
598
+ this.cyclesRem++
556
599
  this.addrAbs = this.pc + this.addrRel
557
600
 
558
601
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
559
- this.cycles++
602
+ this.cyclesRem++
560
603
  }
561
604
 
562
605
  this.pc = this.addrAbs
@@ -566,11 +609,11 @@ export class CPU {
566
609
 
567
610
  private BVS(): number {
568
611
  if (this.getFlag(CPU.V) == 1) {
569
- this.cycles++
612
+ this.cyclesRem++
570
613
  this.addrAbs = this.pc + this.addrRel
571
614
 
572
615
  if ((this.addrAbs & 0xFF00) != (this.pc & 0xFF00)) {
573
- this.cycles++
616
+ this.cyclesRem++
574
617
  }
575
618
 
576
619
  this.pc = this.addrAbs
@@ -851,15 +894,35 @@ export class CPU {
851
894
  private SBC(): number {
852
895
  this.fetch()
853
896
 
854
- const value = this.fetched ^ 0x00FF
855
-
856
- this.temp = this.a + value + this.getFlag(CPU.C)
857
- this.setFlag(CPU.C, (this.temp & 0xFF00) != 0)
858
- this.setFlag(CPU.Z, (this.temp & 0x00FF) == 0)
859
- this.setFlag(CPU.V, ((this.temp ^ this.a) & (this.temp ^ value) & 0x0080) != 0)
860
- this.setFlag(CPU.N, (this.temp & 0x0080) != 0)
897
+ if (this.getFlag(CPU.D)) {
898
+ // BCD decimal mode (65C02)
899
+ const c = this.getFlag(CPU.C)
900
+ const bin = this.a - this.fetched - (1 - c)
901
+
902
+ let lo = (this.a & 0x0F) - (this.fetched & 0x0F) - (1 - c)
903
+ if (lo < 0) lo = ((lo - 0x06) & 0x0F) | ((this.a & 0xF0) - (this.fetched & 0xF0) - 0x10)
904
+ else lo = (lo & 0x0F) | ((this.a & 0xF0) - (this.fetched & 0xF0))
905
+ if (lo < 0) lo -= 0x60
906
+
907
+ const result = lo & 0xFF
861
908
 
862
- this.a = this.temp & 0x00FF
909
+ this.setFlag(CPU.Z, (bin & 0xFF) === 0)
910
+ this.setFlag(CPU.V, ((this.a ^ bin) & (~this.fetched ^ bin) & 0x80) !== 0)
911
+ this.setFlag(CPU.N, (bin & 0x80) !== 0)
912
+ this.setFlag(CPU.C, (bin & 0xFFFF) < 0x100)
913
+
914
+ this.a = result
915
+ } else {
916
+ const value = this.fetched ^ 0x00FF
917
+
918
+ this.temp = this.a + value + this.getFlag(CPU.C)
919
+ this.setFlag(CPU.C, (this.temp & 0xFF00) != 0)
920
+ this.setFlag(CPU.Z, (this.temp & 0x00FF) == 0)
921
+ this.setFlag(CPU.V, ((this.temp ^ this.a) & (this.temp ^ value) & 0x0080) != 0)
922
+ this.setFlag(CPU.N, (this.temp & 0x0080) != 0)
923
+
924
+ this.a = this.temp & 0x00FF
925
+ }
863
926
 
864
927
  return 1
865
928
  }
@@ -1254,7 +1317,7 @@ export class CPU {
1254
1317
  { name: 'STX', cycles: 3, opcode: this.STX.bind(this), addrMode: this.ZP0.bind(this) },
1255
1318
  { name: 'SMB0', cycles: 5, opcode: this.SMB0.bind(this), addrMode: this.ZP0.bind(this) },
1256
1319
  { name: 'DEY', cycles: 2, opcode: this.DEY.bind(this), addrMode: this.IMP.bind(this) },
1257
- { name: 'BIT', cycles: 2, opcode: this.BIT.bind(this), addrMode: this.IMM.bind(this) },
1320
+ { name: 'BIT', cycles: 2, opcode: this.BIT_IMM.bind(this), addrMode: this.IMM.bind(this) },
1258
1321
  { name: 'TXA', cycles: 2, opcode: this.TXA.bind(this), addrMode: this.IMP.bind(this) },
1259
1322
  { name: '???', cycles: 2, opcode: this.XXX.bind(this), addrMode: this.IMP.bind(this) },
1260
1323
  { name: 'STY', cycles: 4, opcode: this.STY.bind(this), addrMode: this.ABS.bind(this) },
@@ -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 dataRegister: number = 0
20
- private statusRegister: number = 0x10 // Transmit Data Register Empty
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.writeControl(data)
79
+ this.controlRegister = data & 0xFF
84
80
  break
85
81
  }
86
82
  }
87
83
 
88
84
  /**
89
- * Read data from receive buffer
85
+ * Read data from receive register
90
86
  */
91
87
  private readData(): number {
92
- if (this.receiveBuffer.length > 0) {
93
- const data = this.receiveBuffer.shift()!
94
- this.dataRegister = data
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
- // If more bytes remain in the buffer, re-assert IRQ so the BIOS services them
106
- if (this.receiveBuffer.length > 0 && !(this.commandRegister & 0x02)) {
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
- return data
115
- }
116
-
117
- return this.dataRegister
95
+ return this.rxRegister
118
96
  }
119
97
 
120
98
  /**
121
- * Write data to transmit buffer
99
+ * Write data to transmit register
122
100
  */
123
101
  private writeData(data: number): void {
124
- this.transmitBuffer.push(data & 0xFF)
125
-
126
- // Clear Transmit Data Register Empty flag
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.receiveBuffer.length > 0) status |= 0x08
128
+ if (this.rxRegFull) status |= 0x08
152
129
 
153
130
  // Bit 4: Transmit Data Register Empty
154
- if (this.transmitBuffer.length === 0) status |= 0x10
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 building the status byte (R6551 spec)
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.statusRegister = 0x10 // Transmit Data Register Empty
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 - emulate ACIA timing
174
+ * Tick - process TX/RX each cycle, return interrupt status
265
175
  */
266
- tick(frequency: number): void {
267
- // Re-evaluate receive interrupt condition.
268
- // After readStatus() clears irqFlag, the IRQ must be re-asserted if
269
- // the underlying condition (RDRF + receive IRQ enabled) is still active.
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
- // Calculate cycles per byte: (CPU_CLOCK / baud_rate) * bits_per_frame
281
- // Assuming 10 bits per frame (1 start + 8 data + 1 stop)
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
- // Set Transmit Data Register Empty if buffer is empty
296
- if (this.transmitBuffer.length === 0) {
297
- this.statusRegister |= 0x10
185
+ this.txRegEmpty = true
298
186
 
299
- // Trigger transmit complete IRQ if enabled (bits 3-2 = 01 means TxIRQ on TDRE)
300
- if ((this.commandRegister & 0x0C) === 0x04) {
301
- this.irqFlag = true
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.dataRegister = 0
314
- this.statusRegister = 0x10 // Transmit Data Register Empty
204
+ this.txRegister = 0
205
+ this.rxRegister = 0
315
206
  this.commandRegister = 0
316
207
  this.controlRegister = 0
317
-
318
- this.transmitBuffer = []
319
- this.receiveBuffer = []
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.receiveBuffer.length > 0) {
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.receiveBuffer.push(data & 0xFF)
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.transmitBuffer.push(data & 0xFF)
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
  }