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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/components/CPU.js +1170 -0
- package/dist/components/CPU.js.map +1 -0
- package/dist/components/Cart.js +23 -0
- package/dist/components/Cart.js.map +1 -0
- package/dist/components/IO/Empty.js +19 -0
- package/dist/components/IO/Empty.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/GPIOCard.js +597 -0
- package/dist/components/IO/GPIOCard.js.map +1 -0
- package/dist/components/IO/InputBoard.js +19 -0
- package/dist/components/IO/InputBoard.js.map +1 -0
- package/dist/components/IO/LCDCard.js +19 -0
- package/dist/components/IO/LCDCard.js.map +1 -0
- package/dist/components/IO/RAMCard.js +63 -0
- package/dist/components/IO/RAMCard.js.map +1 -0
- package/dist/components/IO/RTCCard.js +483 -0
- package/dist/components/IO/RTCCard.js.map +1 -0
- package/dist/components/IO/SerialCard.js +282 -0
- package/dist/components/IO/SerialCard.js.map +1 -0
- package/dist/components/IO/SoundCard.js +620 -0
- package/dist/components/IO/SoundCard.js.map +1 -0
- package/dist/components/IO/StorageCard.js +428 -0
- package/dist/components/IO/StorageCard.js.map +1 -0
- package/dist/components/IO/VGACard.js +9 -0
- package/dist/components/IO/VGACard.js.map +1 -0
- package/dist/components/IO/VideoCard.js +623 -0
- package/dist/components/IO/VideoCard.js.map +1 -0
- package/dist/components/IO.js +3 -0
- package/dist/components/IO.js.map +1 -0
- package/dist/components/Machine.js +310 -0
- package/dist/components/Machine.js.map +1 -0
- package/dist/components/RAM.js +24 -0
- package/dist/components/RAM.js.map +1 -0
- package/dist/components/ROM.js +23 -0
- package/dist/components/ROM.js.map +1 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/CPU.test.js +1626 -0
- package/dist/tests/CPU.test.js.map +1 -0
- package/dist/tests/Cart.test.js +119 -0
- package/dist/tests/Cart.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js +503 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -0
- package/dist/tests/IO/RAMCard.test.js +229 -0
- package/dist/tests/IO/RAMCard.test.js.map +1 -0
- package/dist/tests/IO/RTCCard.test.js +177 -0
- package/dist/tests/IO/RTCCard.test.js.map +1 -0
- package/dist/tests/IO/SerialCard.test.js +423 -0
- package/dist/tests/IO/SerialCard.test.js.map +1 -0
- package/dist/tests/IO/SoundCard.test.js +528 -0
- package/dist/tests/IO/SoundCard.test.js.map +1 -0
- package/dist/tests/IO/StorageCard.test.js +647 -0
- package/dist/tests/IO/StorageCard.test.js.map +1 -0
- package/dist/tests/IO/VideoCard.test.js +549 -0
- package/dist/tests/IO/VideoCard.test.js.map +1 -0
- package/dist/tests/Machine.test.js +383 -0
- package/dist/tests/Machine.test.js.map +1 -0
- package/dist/tests/RAM.test.js +160 -0
- package/dist/tests/RAM.test.js.map +1 -0
- package/dist/tests/ROM.test.js +123 -0
- package/dist/tests/ROM.test.js.map +1 -0
- package/jest.config.cjs +9 -0
- package/package.json +43 -0
- package/src/components/CPU.ts +1371 -0
- package/src/components/Cart.ts +20 -0
- package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
- package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
- package/src/components/IO/GPIOCard.ts +677 -0
- package/src/components/IO/RAMCard.ts +68 -0
- package/src/components/IO/RTCCard.ts +518 -0
- package/src/components/IO/SerialCard.ts +335 -0
- package/src/components/IO/SoundCard.ts +711 -0
- package/src/components/IO/StorageCard.ts +473 -0
- package/src/components/IO/VideoCard.ts +730 -0
- package/src/components/IO.ts +11 -0
- package/src/components/Machine.ts +364 -0
- package/src/components/RAM.ts +23 -0
- package/src/components/ROM.ts +19 -0
- package/src/index.ts +474 -0
- package/src/tests/CPU.test.ts +2045 -0
- package/src/tests/Cart.test.ts +149 -0
- package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
- package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
- package/src/tests/IO/GPIOCard.test.ts +644 -0
- package/src/tests/IO/RAMCard.test.ts +284 -0
- package/src/tests/IO/RTCCard.test.ts +222 -0
- package/src/tests/IO/SerialCard.test.ts +530 -0
- package/src/tests/IO/SoundCard.test.ts +659 -0
- package/src/tests/IO/StorageCard.test.ts +787 -0
- package/src/tests/IO/VideoCard.test.ts +668 -0
- package/src/tests/Machine.test.ts +437 -0
- package/src/tests/RAM.test.ts +196 -0
- package/src/tests/ROM.test.ts +154 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { IO } from '../IO'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RAMCard - Emulates banked RAM with 256KB total capacity
|
|
5
|
+
*
|
|
6
|
+
* Provides 256KB of banked RAM divided into 256 banks of 1KB each.
|
|
7
|
+
* A bank control register at address 0x3FF selects which bank is currently visible.
|
|
8
|
+
*
|
|
9
|
+
* Address Map:
|
|
10
|
+
* $000-$3FE: Bank data (1K window into selected bank)
|
|
11
|
+
* $3FF: Bank control register (read/write)
|
|
12
|
+
*/
|
|
13
|
+
export class RAMCard implements IO {
|
|
14
|
+
|
|
15
|
+
static TOTAL_SIZE: number = 256 * 1024 // 256k bytes
|
|
16
|
+
static BANK_SIZE: number = 1024 // 1k per bank
|
|
17
|
+
static NUM_BANKS: number = RAMCard.TOTAL_SIZE / RAMCard.BANK_SIZE // 256 banks
|
|
18
|
+
static BANK_CONTROL_REGISTER: number = 0x3FF // Last byte in 1k window
|
|
19
|
+
|
|
20
|
+
data: number[] = [...Array(RAMCard.TOTAL_SIZE)].fill(0x00)
|
|
21
|
+
currentBank: number = 0
|
|
22
|
+
|
|
23
|
+
raiseIRQ = () => {}
|
|
24
|
+
raiseNMI = () => {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read from RAM or bank control register
|
|
28
|
+
*/
|
|
29
|
+
read(address: number): number {
|
|
30
|
+
// Reading from bank control register returns current bank number
|
|
31
|
+
if (address === RAMCard.BANK_CONTROL_REGISTER) {
|
|
32
|
+
return this.currentBank
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Calculate actual address in RAM: bank * bank_size + offset and return data
|
|
36
|
+
return this.data[this.currentBank * RAMCard.BANK_SIZE + address]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Write to RAM or bank control register
|
|
41
|
+
*/
|
|
42
|
+
write(address: number, data: number): void {
|
|
43
|
+
// Writing to bank control register switches banks
|
|
44
|
+
if (address === RAMCard.BANK_CONTROL_REGISTER) {
|
|
45
|
+
this.currentBank = data & 0xFF // Ensure 0-255 range
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Calculate actual address in RAM: bank * bank_size + offset and store data
|
|
50
|
+
this.data[this.currentBank * RAMCard.BANK_SIZE + address] = data & 0xFF
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Tick - no timing behavior for RAM
|
|
55
|
+
*/
|
|
56
|
+
tick(frequency: number): void {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reset the RAM card
|
|
60
|
+
*/
|
|
61
|
+
reset(coldStart: boolean): void {
|
|
62
|
+
if (coldStart) {
|
|
63
|
+
this.currentBank = 0
|
|
64
|
+
this.data.fill(0x00)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { IO } from '../IO'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DS1511Y Real-Time Clock IC Emulation
|
|
5
|
+
*
|
|
6
|
+
* Register Map (0x00-0x1F):
|
|
7
|
+
* 0x00: Seconds (BCD, 00-59)
|
|
8
|
+
* 0x01: Minutes (BCD, 00-59)
|
|
9
|
+
* 0x02: Hours (BCD, 00-23)
|
|
10
|
+
* 0x03: Day of Week (1-7, 1=Sunday)
|
|
11
|
+
* 0x04: Date (BCD, 01-31)
|
|
12
|
+
* 0x05: Month (BCD, 01-12) + Control bits (EOSC, E32K)
|
|
13
|
+
* 0x06: Year (BCD, 00-99)
|
|
14
|
+
* 0x07: Century (BCD, 00-39)
|
|
15
|
+
* 0x08: Alarm Seconds (BCD, 00-59) + AM1 bit
|
|
16
|
+
* 0x09: Alarm Minutes (BCD, 00-59) + AM2 bit
|
|
17
|
+
* 0x0A: Alarm Hours (BCD, 00-23) + AM3 bit
|
|
18
|
+
* 0x0B: Alarm Day/Date + AM4, DY/DT bits
|
|
19
|
+
* 0x0C: Watchdog (0.1 Second and 0.01 Second)
|
|
20
|
+
* 0x0D: Watchdog (0.1 Second and Second)
|
|
21
|
+
* 0x0E: Control A (BLF1, BLF2, PBS, PAB, TDF, KSF, WDF, IRQF)
|
|
22
|
+
* 0x0F: Control B (TE, CS, BME, TPE, TIE, KIE, WDE, WDS)
|
|
23
|
+
* 0x10: RAM Address (Extended RAM Address pointer)
|
|
24
|
+
* 0x11: Reserved
|
|
25
|
+
* 0x12: Reserved
|
|
26
|
+
* 0x13: RAM Data (Extended RAM Data at address pointed to by 0x10)
|
|
27
|
+
*/
|
|
28
|
+
export class RTCCard implements IO {
|
|
29
|
+
raiseIRQ = () => {}
|
|
30
|
+
raiseNMI = () => {}
|
|
31
|
+
|
|
32
|
+
// RTC Registers (user-visible)
|
|
33
|
+
private userSeconds: number = 0 // 0x00
|
|
34
|
+
private userMinutes: number = 0 // 0x01
|
|
35
|
+
private userHours: number = 0 // 0x02
|
|
36
|
+
private userDayOfWeek: number = 1 // 0x03 (1=Sunday)
|
|
37
|
+
private userDate: number = 1 // 0x04
|
|
38
|
+
private userMonth: number = 1 // 0x05 (bits 0-4)
|
|
39
|
+
private monthControl: number = 0 // 0x05 (bits 5-7: EOSC, E32K)
|
|
40
|
+
private userYear: number = 0 // 0x06 (00-99)
|
|
41
|
+
private userCentury: number = 20 // 0x07 (00-39)
|
|
42
|
+
|
|
43
|
+
// Internal timekeeping registers
|
|
44
|
+
private internalSeconds: number = 0
|
|
45
|
+
private internalMinutes: number = 0
|
|
46
|
+
private internalHours: number = 0
|
|
47
|
+
private internalDayOfWeek: number = 1
|
|
48
|
+
private internalDate: number = 1
|
|
49
|
+
private internalMonth: number = 1
|
|
50
|
+
private internalYear: number = 0
|
|
51
|
+
private internalCentury: number = 20
|
|
52
|
+
|
|
53
|
+
// Alarm registers
|
|
54
|
+
private alarmSeconds: number = 0 // 0x08 (bits 0-6, bit 7 = AM1)
|
|
55
|
+
private alarmMinutes: number = 0 // 0x09 (bits 0-6, bit 7 = AM2)
|
|
56
|
+
private alarmHours: number = 0 // 0x0A (bits 0-6, bit 7 = AM3)
|
|
57
|
+
private alarmDayDate: number = 0 // 0x0B (bits 0-5, bit 6 = DY/DT, bit 7 = AM4)
|
|
58
|
+
|
|
59
|
+
// Watchdog
|
|
60
|
+
private watchdog1: number = 0 // 0x0C (0.1 Second and 0.01 Second)
|
|
61
|
+
private watchdog2: number = 0 // 0x0D (0.1 Second and Second)
|
|
62
|
+
private watchdogCounterCentis: number = 0
|
|
63
|
+
private watchdogCycleCounter: number = 0
|
|
64
|
+
|
|
65
|
+
// Control registers
|
|
66
|
+
private controlA: number = 0 // 0x0E
|
|
67
|
+
private controlB: number = 0 // 0x0F
|
|
68
|
+
|
|
69
|
+
// Extended RAM
|
|
70
|
+
private ramAddress: number = 0 // 0x10
|
|
71
|
+
private ramData: Uint8Array = new Uint8Array(256) // 256 bytes of extended RAM
|
|
72
|
+
|
|
73
|
+
// Time tracking for incrementing
|
|
74
|
+
private cycleCounter: number = 0 // Accumulator for CPU cycles
|
|
75
|
+
private cpuFrequency: number = 2000000 // Default 2MHz
|
|
76
|
+
private transferCycleCounter: number = 0
|
|
77
|
+
private pendingUserToInternal: boolean = false
|
|
78
|
+
private userSyncNeeded: boolean = false
|
|
79
|
+
private lastTEEnabled: boolean = false
|
|
80
|
+
|
|
81
|
+
constructor() {
|
|
82
|
+
this.initializeWithCurrentTime()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Initialize RTC with current system time
|
|
87
|
+
*/
|
|
88
|
+
private initializeWithCurrentTime(): void {
|
|
89
|
+
const now = new Date()
|
|
90
|
+
this.internalSeconds = this.decimalToBCD(now.getSeconds())
|
|
91
|
+
this.internalMinutes = this.decimalToBCD(now.getMinutes())
|
|
92
|
+
this.internalHours = this.decimalToBCD(now.getHours())
|
|
93
|
+
this.internalDayOfWeek = now.getDay() === 0 ? 1 : now.getDay() // 1=Sunday
|
|
94
|
+
this.internalDate = this.decimalToBCD(now.getDate())
|
|
95
|
+
this.internalMonth = this.decimalToBCD(now.getMonth() + 1)
|
|
96
|
+
this.internalYear = this.decimalToBCD(now.getFullYear() % 100)
|
|
97
|
+
this.internalCentury = this.decimalToBCD(Math.floor(now.getFullYear() / 100))
|
|
98
|
+
this.monthControl = 0x80 // EOSC enabled by default (bit 7)
|
|
99
|
+
this.copyInternalToUser()
|
|
100
|
+
this.pendingUserToInternal = false
|
|
101
|
+
this.userSyncNeeded = false
|
|
102
|
+
this.transferCycleCounter = 0
|
|
103
|
+
this.lastTEEnabled = (this.controlB & 0x80) !== 0
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Convert decimal to BCD (Binary Coded Decimal)
|
|
108
|
+
*/
|
|
109
|
+
private decimalToBCD(decimal: number): number {
|
|
110
|
+
return ((Math.floor(decimal / 10) << 4) | (decimal % 10)) & 0xFF
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Convert BCD to decimal
|
|
115
|
+
*/
|
|
116
|
+
private bcdToDecimal(bcd: number): number {
|
|
117
|
+
return (((bcd >> 4) & 0x0F) * 10) + (bcd & 0x0F)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the number of days in a month
|
|
122
|
+
*/
|
|
123
|
+
private getDaysInMonth(month: number, year: number, century: number): number {
|
|
124
|
+
const fullYear = (century * 100) + year
|
|
125
|
+
|
|
126
|
+
if ([1, 3, 5, 7, 8, 10, 12].includes(month)) return 31
|
|
127
|
+
if ([4, 6, 9, 11].includes(month)) return 30
|
|
128
|
+
|
|
129
|
+
// February
|
|
130
|
+
if ((fullYear % 4 === 0 && fullYear % 100 !== 0) || fullYear % 400 === 0) {
|
|
131
|
+
return 29
|
|
132
|
+
}
|
|
133
|
+
return 28
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get next day of week
|
|
138
|
+
*/
|
|
139
|
+
private getNextDayOfWeek(currentDay: number): number {
|
|
140
|
+
return currentDay === 7 ? 1 : currentDay + 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private copyInternalToUser(): void {
|
|
144
|
+
this.userSeconds = this.internalSeconds
|
|
145
|
+
this.userMinutes = this.internalMinutes
|
|
146
|
+
this.userHours = this.internalHours
|
|
147
|
+
this.userDayOfWeek = this.internalDayOfWeek
|
|
148
|
+
this.userDate = this.internalDate
|
|
149
|
+
this.userMonth = this.internalMonth
|
|
150
|
+
this.userYear = this.internalYear
|
|
151
|
+
this.userCentury = this.internalCentury
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private copyUserToInternal(): void {
|
|
155
|
+
this.internalSeconds = this.userSeconds
|
|
156
|
+
this.internalMinutes = this.userMinutes
|
|
157
|
+
this.internalHours = this.userHours
|
|
158
|
+
this.internalDayOfWeek = this.userDayOfWeek
|
|
159
|
+
this.internalDate = this.userDate
|
|
160
|
+
this.internalMonth = this.userMonth
|
|
161
|
+
this.internalYear = this.userYear
|
|
162
|
+
this.internalCentury = this.userCentury
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private getTransferCyclesRequired(): number {
|
|
166
|
+
return Math.max(1, Math.ceil(this.cpuFrequency * 366 / 1000000))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private markUserTimeWrite(): void {
|
|
170
|
+
this.pendingUserToInternal = true
|
|
171
|
+
if ((this.controlB & 0x80) !== 0 &&
|
|
172
|
+
this.transferCycleCounter >= this.getTransferCyclesRequired()) {
|
|
173
|
+
this.copyUserToInternal()
|
|
174
|
+
this.pendingUserToInternal = false
|
|
175
|
+
this.userSyncNeeded = false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Increment time by one second
|
|
181
|
+
*/
|
|
182
|
+
private incrementTime(): void {
|
|
183
|
+
let sec = this.bcdToDecimal(this.internalSeconds)
|
|
184
|
+
let min = this.bcdToDecimal(this.internalMinutes)
|
|
185
|
+
let hour = this.bcdToDecimal(this.internalHours)
|
|
186
|
+
let date = this.bcdToDecimal(this.internalDate)
|
|
187
|
+
let month = this.bcdToDecimal(this.internalMonth)
|
|
188
|
+
let year = this.bcdToDecimal(this.internalYear)
|
|
189
|
+
const century = this.bcdToDecimal(this.internalCentury)
|
|
190
|
+
|
|
191
|
+
sec++
|
|
192
|
+
if (sec >= 60) {
|
|
193
|
+
sec = 0
|
|
194
|
+
min++
|
|
195
|
+
if (min >= 60) {
|
|
196
|
+
min = 0
|
|
197
|
+
hour++
|
|
198
|
+
if (hour >= 24) {
|
|
199
|
+
hour = 0
|
|
200
|
+
this.internalDayOfWeek = this.getNextDayOfWeek(this.internalDayOfWeek)
|
|
201
|
+
date++
|
|
202
|
+
|
|
203
|
+
const daysInMonth = this.getDaysInMonth(month, year, century)
|
|
204
|
+
if (date > daysInMonth) {
|
|
205
|
+
date = 1
|
|
206
|
+
month++
|
|
207
|
+
if (month > 12) {
|
|
208
|
+
month = 1
|
|
209
|
+
year++
|
|
210
|
+
if (year > 99) {
|
|
211
|
+
year = 0
|
|
212
|
+
let cent = this.bcdToDecimal(this.internalCentury)
|
|
213
|
+
cent++
|
|
214
|
+
if (cent > 39) cent = 0
|
|
215
|
+
this.internalCentury = this.decimalToBCD(cent)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.internalSeconds = this.decimalToBCD(sec)
|
|
224
|
+
this.internalMinutes = this.decimalToBCD(min)
|
|
225
|
+
this.internalHours = this.decimalToBCD(hour)
|
|
226
|
+
this.internalDate = this.decimalToBCD(date)
|
|
227
|
+
this.internalMonth = this.decimalToBCD(month)
|
|
228
|
+
this.internalYear = this.decimalToBCD(year)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if alarm should trigger
|
|
233
|
+
*/
|
|
234
|
+
private checkAlarm(): void {
|
|
235
|
+
const am1 = (this.alarmSeconds & 0x80) !== 0
|
|
236
|
+
const am2 = (this.alarmMinutes & 0x80) !== 0
|
|
237
|
+
const am3 = (this.alarmHours & 0x80) !== 0
|
|
238
|
+
const am4 = (this.alarmDayDate & 0x80) !== 0
|
|
239
|
+
|
|
240
|
+
// If all AM bits are set, alarm is disabled
|
|
241
|
+
if (am1 && am2 && am3 && am4) return
|
|
242
|
+
|
|
243
|
+
// Check matching based on AM bits
|
|
244
|
+
const secondsMatch = am1 || (this.internalSeconds === (this.alarmSeconds & 0x7F))
|
|
245
|
+
const minutesMatch = am2 || (this.internalMinutes === (this.alarmMinutes & 0x7F))
|
|
246
|
+
const hoursMatch = am3 || (this.internalHours === (this.alarmHours & 0x7F))
|
|
247
|
+
|
|
248
|
+
let dayDateMatch = true
|
|
249
|
+
if (!am4) {
|
|
250
|
+
const dyDt = (this.alarmDayDate & 0x40) !== 0
|
|
251
|
+
const alarmValue = this.alarmDayDate & 0x3F
|
|
252
|
+
|
|
253
|
+
if (dyDt) {
|
|
254
|
+
// Day of week match
|
|
255
|
+
dayDateMatch = this.internalDayOfWeek === alarmValue
|
|
256
|
+
} else {
|
|
257
|
+
// Date match
|
|
258
|
+
dayDateMatch = this.internalDate === alarmValue
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (secondsMatch && minutesMatch && hoursMatch && dayDateMatch) {
|
|
263
|
+
this.controlA |= 0x08 // Set TDF flag (bit 3)
|
|
264
|
+
this.raiseInterruptIfEnabled(0x08, 0x08)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private raiseInterruptIfEnabled(flagMask: number, enableMask: number): void {
|
|
269
|
+
if ((this.controlA & flagMask) === 0) return
|
|
270
|
+
if ((this.controlB & enableMask) === 0) return
|
|
271
|
+
this.controlA |= 0x01 // Set IRQF flag (bit 0)
|
|
272
|
+
this.raiseIRQ()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private setKickstartFlag(): void {
|
|
276
|
+
this.controlA |= 0x04 // Set KSF flag (bit 2)
|
|
277
|
+
this.raiseInterruptIfEnabled(0x04, 0x04)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private decodeWatchdogCentis(): number {
|
|
281
|
+
const hundredths = this.watchdog1 & 0x0F
|
|
282
|
+
const tenths = (this.watchdog1 >> 4) & 0x0F
|
|
283
|
+
const seconds = this.watchdog2 & 0x0F
|
|
284
|
+
const tensSeconds = (this.watchdog2 >> 4) & 0x0F
|
|
285
|
+
|
|
286
|
+
const totalSeconds = (tensSeconds * 10) + seconds
|
|
287
|
+
const totalCentis = (tenths * 10) + hundredths
|
|
288
|
+
return (totalSeconds * 100) + totalCentis
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private reloadWatchdog(): void {
|
|
292
|
+
this.watchdogCounterCentis = this.decodeWatchdogCentis()
|
|
293
|
+
this.watchdogCycleCounter = 0
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private stepWatchdog(): void {
|
|
297
|
+
if ((this.controlB & 0x02) === 0) return // WDE disabled
|
|
298
|
+
if (this.watchdogCounterCentis <= 0) return
|
|
299
|
+
|
|
300
|
+
const cyclesPerCentisecond = Math.max(1, Math.floor(this.cpuFrequency / 100))
|
|
301
|
+
this.watchdogCycleCounter++
|
|
302
|
+
if (this.watchdogCycleCounter < cyclesPerCentisecond) return
|
|
303
|
+
|
|
304
|
+
this.watchdogCycleCounter = 0
|
|
305
|
+
this.watchdogCounterCentis -= 1
|
|
306
|
+
if (this.watchdogCounterCentis > 0) return
|
|
307
|
+
|
|
308
|
+
this.controlA |= 0x02 // Set WDF flag (bit 1)
|
|
309
|
+
|
|
310
|
+
if ((this.controlB & 0x01) === 0) {
|
|
311
|
+
this.raiseInterruptIfEnabled(0x02, 0x02)
|
|
312
|
+
} else {
|
|
313
|
+
// WDS=1 steers watchdog to reset; emulate by clearing WDE
|
|
314
|
+
this.controlB &= ~0x02
|
|
315
|
+
this.raiseNMI()
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
read(address: number): number {
|
|
320
|
+
address &= 0x1F
|
|
321
|
+
|
|
322
|
+
switch (address) {
|
|
323
|
+
case 0x00: return this.userSeconds
|
|
324
|
+
case 0x01: return this.userMinutes
|
|
325
|
+
case 0x02: return this.userHours
|
|
326
|
+
case 0x03: return this.userDayOfWeek & 0x07
|
|
327
|
+
case 0x04: return this.userDate
|
|
328
|
+
case 0x05: return this.userMonth | this.monthControl
|
|
329
|
+
case 0x06: return this.userYear
|
|
330
|
+
case 0x07: return this.userCentury
|
|
331
|
+
case 0x08: return this.alarmSeconds
|
|
332
|
+
case 0x09: return this.alarmMinutes
|
|
333
|
+
case 0x0A: return this.alarmHours
|
|
334
|
+
case 0x0B: return this.alarmDayDate
|
|
335
|
+
case 0x0C: return this.watchdog1
|
|
336
|
+
case 0x0D: return this.watchdog2
|
|
337
|
+
case 0x0E:
|
|
338
|
+
// Reading Control A clears the interrupt flags: IRQF, WDF, KSF, TDF
|
|
339
|
+
const result = this.controlA
|
|
340
|
+
this.controlA &= 0xF0 // Clear bits 0-3 (IRQF, WDF, KSF, TDF)
|
|
341
|
+
return result
|
|
342
|
+
case 0x0F: return this.controlB
|
|
343
|
+
case 0x10: return this.ramAddress
|
|
344
|
+
case 0x11: return 0 // Reserved
|
|
345
|
+
case 0x12: return 0 // Reserved
|
|
346
|
+
case 0x13: {
|
|
347
|
+
const value = this.ramData[this.ramAddress]
|
|
348
|
+
if ((this.controlB & 0x20) !== 0) {
|
|
349
|
+
this.ramAddress = (this.ramAddress + 1) & 0xFF
|
|
350
|
+
}
|
|
351
|
+
return value
|
|
352
|
+
}
|
|
353
|
+
default: return 0
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
write(address: number, data: number): void {
|
|
358
|
+
data &= 0xFF
|
|
359
|
+
address &= 0x1F
|
|
360
|
+
|
|
361
|
+
switch (address) {
|
|
362
|
+
case 0x00:
|
|
363
|
+
this.userSeconds = data
|
|
364
|
+
this.markUserTimeWrite()
|
|
365
|
+
break
|
|
366
|
+
case 0x01:
|
|
367
|
+
this.userMinutes = data
|
|
368
|
+
this.markUserTimeWrite()
|
|
369
|
+
break
|
|
370
|
+
case 0x02:
|
|
371
|
+
this.userHours = data
|
|
372
|
+
this.markUserTimeWrite()
|
|
373
|
+
break
|
|
374
|
+
case 0x03:
|
|
375
|
+
this.userDayOfWeek = data & 0x07
|
|
376
|
+
this.markUserTimeWrite()
|
|
377
|
+
break
|
|
378
|
+
case 0x04:
|
|
379
|
+
this.userDate = data
|
|
380
|
+
this.markUserTimeWrite()
|
|
381
|
+
break
|
|
382
|
+
case 0x05:
|
|
383
|
+
this.userMonth = data & 0x1F
|
|
384
|
+
this.monthControl = data & 0xE0
|
|
385
|
+
this.markUserTimeWrite()
|
|
386
|
+
break
|
|
387
|
+
case 0x06:
|
|
388
|
+
this.userYear = data
|
|
389
|
+
this.markUserTimeWrite()
|
|
390
|
+
break
|
|
391
|
+
case 0x07:
|
|
392
|
+
this.userCentury = data
|
|
393
|
+
this.markUserTimeWrite()
|
|
394
|
+
break
|
|
395
|
+
case 0x08:
|
|
396
|
+
this.alarmSeconds = data
|
|
397
|
+
break
|
|
398
|
+
case 0x09:
|
|
399
|
+
this.alarmMinutes = data
|
|
400
|
+
break
|
|
401
|
+
case 0x0A:
|
|
402
|
+
this.alarmHours = data
|
|
403
|
+
break
|
|
404
|
+
case 0x0B:
|
|
405
|
+
this.alarmDayDate = data
|
|
406
|
+
break
|
|
407
|
+
case 0x0C:
|
|
408
|
+
this.watchdog1 = data
|
|
409
|
+
this.reloadWatchdog()
|
|
410
|
+
break
|
|
411
|
+
case 0x0D:
|
|
412
|
+
this.watchdog2 = data
|
|
413
|
+
this.reloadWatchdog()
|
|
414
|
+
break
|
|
415
|
+
case 0x0E:
|
|
416
|
+
// Writing 1 to flag bits (0-3) clears them; control bits (4-7) are written normally
|
|
417
|
+
this.controlA = (data & 0xF0) | ((this.controlA & 0x0F) & ~(data & 0x0F))
|
|
418
|
+
break
|
|
419
|
+
case 0x0F:
|
|
420
|
+
this.controlB = data
|
|
421
|
+
this.raiseInterruptIfEnabled(0x04, 0x04)
|
|
422
|
+
if ((this.controlB & 0x02) !== 0) {
|
|
423
|
+
this.reloadWatchdog()
|
|
424
|
+
}
|
|
425
|
+
break
|
|
426
|
+
case 0x10:
|
|
427
|
+
this.ramAddress = data // Set RAM address pointer
|
|
428
|
+
break
|
|
429
|
+
case 0x11:
|
|
430
|
+
case 0x12:
|
|
431
|
+
// Reserved, ignore writes
|
|
432
|
+
break
|
|
433
|
+
case 0x13:
|
|
434
|
+
this.ramData[this.ramAddress] = data // Write to RAM at current address
|
|
435
|
+
if ((this.controlB & 0x20) !== 0) {
|
|
436
|
+
this.ramAddress = (this.ramAddress + 1) & 0xFF
|
|
437
|
+
}
|
|
438
|
+
break
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
tick(frequency: number): void {
|
|
443
|
+
// Advance RTC based on CPU frequency
|
|
444
|
+
// Store the frequency for use in time calculations
|
|
445
|
+
this.cpuFrequency = frequency > 0 ? frequency : 2000000
|
|
446
|
+
|
|
447
|
+
const teEnabled = (this.controlB & 0x80) !== 0
|
|
448
|
+
if (teEnabled !== this.lastTEEnabled) {
|
|
449
|
+
this.lastTEEnabled = teEnabled
|
|
450
|
+
this.transferCycleCounter = 0
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (teEnabled) {
|
|
454
|
+
this.transferCycleCounter++
|
|
455
|
+
} else {
|
|
456
|
+
this.transferCycleCounter = 0
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const transferReady = teEnabled &&
|
|
460
|
+
this.transferCycleCounter >= this.getTransferCyclesRequired()
|
|
461
|
+
|
|
462
|
+
if (transferReady && this.pendingUserToInternal) {
|
|
463
|
+
this.copyUserToInternal()
|
|
464
|
+
this.pendingUserToInternal = false
|
|
465
|
+
this.userSyncNeeded = false
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if ((this.monthControl & 0x80) === 0) {
|
|
469
|
+
// Oscillator disabled
|
|
470
|
+
this.stepWatchdog()
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.cycleCounter++
|
|
475
|
+
|
|
476
|
+
// Advance time when we've accumulated enough cycles for 1 second
|
|
477
|
+
if (this.cycleCounter >= this.cpuFrequency) {
|
|
478
|
+
this.cycleCounter = 0
|
|
479
|
+
this.incrementTime()
|
|
480
|
+
this.checkAlarm()
|
|
481
|
+
|
|
482
|
+
if (transferReady) {
|
|
483
|
+
this.copyInternalToUser()
|
|
484
|
+
this.userSyncNeeded = false
|
|
485
|
+
} else {
|
|
486
|
+
this.userSyncNeeded = true
|
|
487
|
+
}
|
|
488
|
+
} else if (transferReady && this.userSyncNeeded) {
|
|
489
|
+
this.copyInternalToUser()
|
|
490
|
+
this.userSyncNeeded = false
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.stepWatchdog()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
reset(coldStart: boolean): void {
|
|
497
|
+
if (coldStart) {
|
|
498
|
+
// Cold start: Initialize with current time
|
|
499
|
+
this.initializeWithCurrentTime()
|
|
500
|
+
this.cycleCounter = 0
|
|
501
|
+
this.watchdogCounterCentis = 0
|
|
502
|
+
this.watchdogCycleCounter = 0
|
|
503
|
+
this.transferCycleCounter = 0
|
|
504
|
+
this.pendingUserToInternal = false
|
|
505
|
+
this.userSyncNeeded = false
|
|
506
|
+
this.setKickstartFlag()
|
|
507
|
+
} else {
|
|
508
|
+
// Warm start: Keep time, reset some control flags but preserve settings
|
|
509
|
+
this.controlA &= 0xF0 // Clear interrupt flags
|
|
510
|
+
this.cycleCounter = 0
|
|
511
|
+
this.watchdogCounterCentis = 0
|
|
512
|
+
this.watchdogCycleCounter = 0
|
|
513
|
+
this.transferCycleCounter = 0
|
|
514
|
+
this.pendingUserToInternal = false
|
|
515
|
+
this.userSyncNeeded = false
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|