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,437 @@
|
|
|
1
|
+
import { Machine } from '../components/Machine'
|
|
2
|
+
import { RAM } from '../components/RAM'
|
|
3
|
+
import { ROM } from '../components/ROM'
|
|
4
|
+
import { Cart } from '../components/Cart'
|
|
5
|
+
|
|
6
|
+
describe('Machine', () => {
|
|
7
|
+
let machine: Machine
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
machine = new Machine()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
// Ensure the machine loop is stopped after each test
|
|
15
|
+
machine.end()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('Initialization', () => {
|
|
19
|
+
test('Constructor creates a Machine instance', () => {
|
|
20
|
+
expect(machine).not.toBeNull()
|
|
21
|
+
expect(machine).toBeInstanceOf(Machine)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('Machine initializes with correct default properties', () => {
|
|
25
|
+
expect(machine.isAlive).toBe(false)
|
|
26
|
+
expect(machine.isRunning).toBe(false)
|
|
27
|
+
expect(machine.frequency).toBe(2000000)
|
|
28
|
+
expect(machine.scale).toBe(2)
|
|
29
|
+
expect(machine.frames).toBe(0)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('Machine creates CPU, RAM, ROM, and IO cards', () => {
|
|
33
|
+
expect(machine.cpu).not.toBeNull()
|
|
34
|
+
expect(machine.ram).not.toBeNull()
|
|
35
|
+
expect(machine.rom).not.toBeNull()
|
|
36
|
+
expect(machine.io1).not.toBeNull()
|
|
37
|
+
expect(machine.io2).not.toBeNull()
|
|
38
|
+
expect(machine.io3).not.toBeNull()
|
|
39
|
+
expect(machine.io4).not.toBeNull()
|
|
40
|
+
expect(machine.io5).not.toBeNull()
|
|
41
|
+
expect(machine.io6).not.toBeNull()
|
|
42
|
+
expect(machine.io7).not.toBeNull()
|
|
43
|
+
expect(machine.io8).not.toBeNull()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('Machine has no cart initially', () => {
|
|
47
|
+
expect(machine.cart).toBeUndefined()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('Machine has CPU reset on creation', () => {
|
|
51
|
+
// CPU should be reset, which sets up initial state
|
|
52
|
+
expect(machine.cpu).toBeDefined()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('State Management', () => {
|
|
57
|
+
test('start() sets isRunning and isAlive to true', () => {
|
|
58
|
+
expect(machine.isRunning).toBe(false)
|
|
59
|
+
expect(machine.isAlive).toBe(false)
|
|
60
|
+
machine.start()
|
|
61
|
+
expect(machine.isRunning).toBe(true)
|
|
62
|
+
expect(machine.isAlive).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('end() sets isRunning and isAlive to false', () => {
|
|
66
|
+
machine.start()
|
|
67
|
+
expect(machine.isRunning).toBe(true)
|
|
68
|
+
machine.end()
|
|
69
|
+
expect(machine.isRunning).toBe(false)
|
|
70
|
+
expect(machine.isAlive).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('run() sets isRunning to true', () => {
|
|
74
|
+
expect(machine.isRunning).toBe(false)
|
|
75
|
+
machine.run()
|
|
76
|
+
expect(machine.isRunning).toBe(true)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('stop() sets isRunning to false', () => {
|
|
80
|
+
machine.run()
|
|
81
|
+
expect(machine.isRunning).toBe(true)
|
|
82
|
+
machine.stop()
|
|
83
|
+
expect(machine.isRunning).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('Memory Access - Read Operations', () => {
|
|
88
|
+
test('Reading from RAM returns stored values', () => {
|
|
89
|
+
const address = 0x0100
|
|
90
|
+
machine.ram.write(address, 0xAB)
|
|
91
|
+
expect(machine.read(address)).toBe(0xAB)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('Reading from uninitialized RAM returns 0', () => {
|
|
95
|
+
expect(machine.read(0x0200)).toBe(0)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('Reading from IO1 address space', () => {
|
|
99
|
+
const ioAddress = 0x8000
|
|
100
|
+
machine.io1.write(0, 0x55)
|
|
101
|
+
expect(machine.read(ioAddress)).toBe(0x55)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('Reading from IO2 address space', () => {
|
|
105
|
+
const ioAddress = 0x8400
|
|
106
|
+
machine.io2.write(0, 0x66)
|
|
107
|
+
expect(machine.read(ioAddress)).toBe(0x66)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('Reading from IO3 (RTC) address space', () => {
|
|
111
|
+
const ioAddress = 0x8800
|
|
112
|
+
const result = machine.read(ioAddress)
|
|
113
|
+
expect(typeof result).toBe('number')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('Reading from IO4 (Storage) address space', () => {
|
|
117
|
+
const ioAddress = 0x8C00
|
|
118
|
+
const result = machine.read(ioAddress)
|
|
119
|
+
expect(typeof result).toBe('number')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('Reading from IO5 (Serial) address space', () => {
|
|
123
|
+
const ioAddress = 0x9000
|
|
124
|
+
const result = machine.read(ioAddress)
|
|
125
|
+
expect(typeof result).toBe('number')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('Reading from IO6 (GPIO) address space', () => {
|
|
129
|
+
const ioAddress = 0x9400
|
|
130
|
+
const result = machine.read(ioAddress)
|
|
131
|
+
expect(typeof result).toBe('number')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('Reading from IO7 (Sound) address space', () => {
|
|
135
|
+
const ioAddress = 0x9800
|
|
136
|
+
const result = machine.read(ioAddress)
|
|
137
|
+
expect(typeof result).toBe('number')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('Reading from IO8 (Video) address space', () => {
|
|
141
|
+
const ioAddress = 0x9C00
|
|
142
|
+
const result = machine.read(ioAddress)
|
|
143
|
+
expect(typeof result).toBe('number')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('Reading from invalid address returns 0', () => {
|
|
147
|
+
// Assuming unmapped space returns 0
|
|
148
|
+
expect(machine.read(0x10000)).toBe(0)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('Reading from ROM address space', () => {
|
|
152
|
+
const romAddress = 0xA000
|
|
153
|
+
const result = machine.read(romAddress)
|
|
154
|
+
expect(typeof result).toBe('number')
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('Memory Access - Write Operations', () => {
|
|
159
|
+
test('Writing to RAM stores values', () => {
|
|
160
|
+
const address = 0x0100
|
|
161
|
+
machine.write(address, 0xCD)
|
|
162
|
+
expect(machine.ram.read(address)).toBe(0xCD)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('Writing to IO1 address space', () => {
|
|
166
|
+
const ioAddress = 0x8000
|
|
167
|
+
machine.write(ioAddress, 0x42)
|
|
168
|
+
expect(machine.io1.read(0)).toBe(0x42)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('Writing to IO2 address space', () => {
|
|
172
|
+
const ioAddress = 0x8400
|
|
173
|
+
machine.write(ioAddress, 0x43)
|
|
174
|
+
expect(machine.io2.read(0)).toBe(0x43)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('Writing to IO3 address space', () => {
|
|
178
|
+
const ioAddress = 0x8800
|
|
179
|
+
expect(() => machine.write(ioAddress, 0x44)).not.toThrow()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('Writing to IO4 address space', () => {
|
|
183
|
+
const ioAddress = 0x8C00
|
|
184
|
+
expect(() => machine.write(ioAddress, 0x45)).not.toThrow()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('Writing to IO5 address space', () => {
|
|
188
|
+
const ioAddress = 0x9000
|
|
189
|
+
expect(() => machine.write(ioAddress, 0x46)).not.toThrow()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('Writing to IO6 address space', () => {
|
|
193
|
+
const ioAddress = 0x9400
|
|
194
|
+
expect(() => machine.write(ioAddress, 0x47)).not.toThrow()
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('Writing to IO7 address space', () => {
|
|
198
|
+
const ioAddress = 0x9800
|
|
199
|
+
expect(() => machine.write(ioAddress, 0x48)).not.toThrow()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('Writing to IO8 address space', () => {
|
|
203
|
+
const ioAddress = 0x9C00
|
|
204
|
+
expect(() => machine.write(ioAddress, 0x49)).not.toThrow()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('Writing to ROM address space does nothing', () => {
|
|
208
|
+
const romAddress = 0xA000
|
|
209
|
+
expect(() => machine.write(romAddress, 0xFF)).not.toThrow()
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('Cart Operations', () => {
|
|
214
|
+
test('Cart is initially undefined', () => {
|
|
215
|
+
expect(machine.cart).toBeUndefined()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('loadCart should load cart data', async () => {
|
|
219
|
+
// Create a mock cart file path - this will test the error handling
|
|
220
|
+
await machine.loadCart('/nonexistent/path.bin')
|
|
221
|
+
// The machine should handle the error gracefully
|
|
222
|
+
expect(machine.cart).toBeUndefined()
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('ROM Operations', () => {
|
|
227
|
+
test('loadROM should load ROM data', async () => {
|
|
228
|
+
// Create a mock ROM file path - this will test the error handling
|
|
229
|
+
await machine.loadROM('/nonexistent/path.bin')
|
|
230
|
+
// The machine should handle the error gracefully
|
|
231
|
+
expect(machine.rom).toBeDefined()
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
describe('CPU Execution', () => {
|
|
236
|
+
test('step() executes one instruction', () => {
|
|
237
|
+
const initialCycles = machine.cpu.cycles
|
|
238
|
+
machine.step()
|
|
239
|
+
// Step should execute at least one cycle
|
|
240
|
+
expect(machine.cpu.cycles).toBeGreaterThanOrEqual(initialCycles)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('tick() executes one CPU clock cycle', () => {
|
|
244
|
+
const initialCycles = machine.cpu.cycles
|
|
245
|
+
machine.tick()
|
|
246
|
+
// tick() increments CPU state; cycles may stay same if already counted
|
|
247
|
+
expect(machine.cpu.cycles).toBeGreaterThanOrEqual(initialCycles)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('multiple steps execute multiple instructions', () => {
|
|
251
|
+
const initialCycles = machine.cpu.cycles
|
|
252
|
+
machine.step()
|
|
253
|
+
machine.step()
|
|
254
|
+
machine.step()
|
|
255
|
+
expect(machine.cpu.cycles).toBeGreaterThan(initialCycles)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('multiple ticks increment cycle counter', () => {
|
|
259
|
+
const initialCycles = machine.cpu.cycles
|
|
260
|
+
for (let i = 0; i < 10; i++) {
|
|
261
|
+
machine.tick()
|
|
262
|
+
}
|
|
263
|
+
expect(machine.cpu.cycles).toBeGreaterThan(initialCycles)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe('Input Handling', () => {
|
|
268
|
+
test('onReceive() passes data to Serial card', () => {
|
|
269
|
+
const spy = jest.spyOn(machine.io5, 'onData')
|
|
270
|
+
machine.onReceive(0x41)
|
|
271
|
+
expect(spy).toHaveBeenCalledWith(0x41)
|
|
272
|
+
spy.mockRestore()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('onKeyDown() routes key to GPIO attachments', () => {
|
|
276
|
+
const matrixSpy = jest.spyOn(machine.keyboardMatrixAttachment, 'updateKey')
|
|
277
|
+
const encoderSpy = jest.spyOn(machine.keyboardEncoderAttachment, 'updateKey')
|
|
278
|
+
machine.onKeyDown(0x52) // Arrow Up USB HID keycode
|
|
279
|
+
expect(matrixSpy).toHaveBeenCalledWith(0x52, true)
|
|
280
|
+
expect(encoderSpy).toHaveBeenCalledWith(0x52, true)
|
|
281
|
+
matrixSpy.mockRestore()
|
|
282
|
+
encoderSpy.mockRestore()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('onKeyUp() routes key to GPIO attachments', () => {
|
|
286
|
+
const matrixSpy = jest.spyOn(machine.keyboardMatrixAttachment, 'updateKey')
|
|
287
|
+
const encoderSpy = jest.spyOn(machine.keyboardEncoderAttachment, 'updateKey')
|
|
288
|
+
machine.onKeyUp(0x52) // Arrow Up USB HID keycode
|
|
289
|
+
expect(matrixSpy).toHaveBeenCalledWith(0x52, false)
|
|
290
|
+
expect(encoderSpy).toHaveBeenCalledWith(0x52, false)
|
|
291
|
+
matrixSpy.mockRestore()
|
|
292
|
+
encoderSpy.mockRestore()
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('onJoystick() routes button state to joystick attachment', () => {
|
|
296
|
+
const spy = jest.spyOn(machine.joystickAttachment, 'updateJoystick')
|
|
297
|
+
machine.onJoystick(0xFF)
|
|
298
|
+
expect(spy).toHaveBeenCalledWith(0xFF)
|
|
299
|
+
spy.mockRestore()
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('Reset Operations', () => {
|
|
304
|
+
test('reset() resets CPU', () => {
|
|
305
|
+
const spy = jest.spyOn(machine.cpu, 'reset')
|
|
306
|
+
machine.reset(true)
|
|
307
|
+
expect(spy).toHaveBeenCalled()
|
|
308
|
+
spy.mockRestore()
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('reset(coldStart: true) performs full reset', () => {
|
|
312
|
+
machine.ram.write(0x0000, 0xFF)
|
|
313
|
+
expect(machine.ram.read(0x0000)).toBe(0xFF)
|
|
314
|
+
machine.reset(true)
|
|
315
|
+
expect(machine.ram.read(0x0000)).toBe(0x00)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
test('reset(coldStart: false) performs warm reset', () => {
|
|
319
|
+
machine.ram.write(0x0000, 0xFF)
|
|
320
|
+
machine.reset(false)
|
|
321
|
+
// Warm reset preserves RAM
|
|
322
|
+
expect(machine.ram.read(0x0000)).toBe(0xFF)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('reset() resets all IO cards', () => {
|
|
326
|
+
const io1Spy = jest.spyOn(machine.io1, 'reset')
|
|
327
|
+
const io2Spy = jest.spyOn(machine.io2, 'reset')
|
|
328
|
+
const io3Spy = jest.spyOn(machine.io3, 'reset')
|
|
329
|
+
const io4Spy = jest.spyOn(machine.io4, 'reset')
|
|
330
|
+
const io5Spy = jest.spyOn(machine.io5, 'reset')
|
|
331
|
+
const io6Spy = jest.spyOn(machine.io6, 'reset')
|
|
332
|
+
const io7Spy = jest.spyOn(machine.io7, 'reset')
|
|
333
|
+
const io8Spy = jest.spyOn(machine.io8, 'reset')
|
|
334
|
+
|
|
335
|
+
machine.reset(true)
|
|
336
|
+
|
|
337
|
+
expect(io1Spy).toHaveBeenCalled()
|
|
338
|
+
expect(io2Spy).toHaveBeenCalled()
|
|
339
|
+
expect(io3Spy).toHaveBeenCalled()
|
|
340
|
+
expect(io4Spy).toHaveBeenCalled()
|
|
341
|
+
expect(io5Spy).toHaveBeenCalled()
|
|
342
|
+
expect(io6Spy).toHaveBeenCalled()
|
|
343
|
+
expect(io7Spy).toHaveBeenCalled()
|
|
344
|
+
expect(io8Spy).toHaveBeenCalled()
|
|
345
|
+
|
|
346
|
+
io1Spy.mockRestore()
|
|
347
|
+
io2Spy.mockRestore()
|
|
348
|
+
io3Spy.mockRestore()
|
|
349
|
+
io4Spy.mockRestore()
|
|
350
|
+
io5Spy.mockRestore()
|
|
351
|
+
io6Spy.mockRestore()
|
|
352
|
+
io7Spy.mockRestore()
|
|
353
|
+
io8Spy.mockRestore()
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
describe('Callbacks', () => {
|
|
358
|
+
test('transmit callback can be set', () => {
|
|
359
|
+
const mockTransmit = jest.fn()
|
|
360
|
+
machine.transmit = mockTransmit
|
|
361
|
+
expect(machine.transmit).toBe(mockTransmit)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('render callback can be set', () => {
|
|
365
|
+
const mockRender = jest.fn()
|
|
366
|
+
machine.render = mockRender
|
|
367
|
+
expect(machine.render).toBe(mockRender)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
test('SerialCard uses transmit callback when set', () => {
|
|
371
|
+
const mockTransmit = jest.fn()
|
|
372
|
+
machine.transmit = mockTransmit
|
|
373
|
+
// Trigger SerialCard to transmit if possible
|
|
374
|
+
machine.io5.transmit?.(0x41)
|
|
375
|
+
expect(mockTransmit).toHaveBeenCalledWith(0x41)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
describe('Configuration', () => {
|
|
380
|
+
test('Machine has configurable frequency', () => {
|
|
381
|
+
const originalFreq = machine.frequency
|
|
382
|
+
machine.frequency = 1000000
|
|
383
|
+
expect(machine.frequency).toBe(1000000)
|
|
384
|
+
machine.frequency = originalFreq
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
test('Machine has configurable scale', () => {
|
|
388
|
+
machine.scale = 4
|
|
389
|
+
expect(machine.scale).toBe(4)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
test('Machine constants are defined', () => {
|
|
393
|
+
expect(Machine.MAX_FPS).toBe(60)
|
|
394
|
+
expect(Machine.FRAME_INTERVAL_MS).toBe(1000 / 60)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('Cart and ROM Interaction', () => {
|
|
399
|
+
test('Reading from cart address space when no cart is loaded returns 0', () => {
|
|
400
|
+
const cartAddress = 0xC000
|
|
401
|
+
expect(machine.read(cartAddress)).toBe(0)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
test('Reading from ROM takes precedence when no cart', () => {
|
|
405
|
+
const romAddress = 0xA000
|
|
406
|
+
machine.rom.load(Array(ROM.SIZE).fill(0x00))
|
|
407
|
+
const result = machine.read(romAddress)
|
|
408
|
+
expect(typeof result).toBe('number')
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('Memory Region Boundaries', () => {
|
|
413
|
+
test('Reading/writing at RAM boundaries', () => {
|
|
414
|
+
machine.write(RAM.START, 0x11)
|
|
415
|
+
expect(machine.read(RAM.START)).toBe(0x11)
|
|
416
|
+
|
|
417
|
+
machine.write(RAM.END, 0x22)
|
|
418
|
+
expect(machine.read(RAM.END)).toBe(0x22)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
test('IO1 address space boundaries (0x8000-0x83FF)', () => {
|
|
422
|
+
machine.write(0x8000, 0x33)
|
|
423
|
+
expect(machine.io1.read(0)).toBe(0x33)
|
|
424
|
+
|
|
425
|
+
machine.write(0x83FF, 0x44)
|
|
426
|
+
expect(machine.io1.read(0x3FF)).toBe(0x44)
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
test('IO2 address space boundaries (0x8400-0x87FF)', () => {
|
|
430
|
+
machine.write(0x8400, 0x55)
|
|
431
|
+
expect(machine.io2.read(0)).toBe(0x55)
|
|
432
|
+
|
|
433
|
+
machine.write(0x87FF, 0x66)
|
|
434
|
+
expect(machine.io2.read(0x3FF)).toBe(0x66)
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { RAM } from '../components/RAM'
|
|
2
|
+
|
|
3
|
+
describe('RAM', () => {
|
|
4
|
+
let ram: RAM
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
ram = new RAM()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('Initialization', () => {
|
|
11
|
+
test('can initialize', () => {
|
|
12
|
+
expect(ram).not.toBeNull()
|
|
13
|
+
expect(ram).toBeInstanceOf(RAM)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('initializes with correct size', () => {
|
|
17
|
+
expect(ram.data.length).toBe(RAM.SIZE)
|
|
18
|
+
expect(ram.data.length).toBe(0x8000) // 32KB
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('initializes all memory to zero', () => {
|
|
22
|
+
for (let i = 0; i < RAM.SIZE; i++) {
|
|
23
|
+
expect(ram.data[i]).toBe(0x00)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('Static Properties', () => {
|
|
29
|
+
test('START is 0x0000', () => {
|
|
30
|
+
expect(RAM.START).toBe(0x0000)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('END is 0x7FFF', () => {
|
|
34
|
+
expect(RAM.END).toBe(0x7FFF)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('SIZE is calculated correctly', () => {
|
|
38
|
+
expect(RAM.SIZE).toBe(0x8000)
|
|
39
|
+
expect(RAM.SIZE).toBe(RAM.END - RAM.START + 1)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('read()', () => {
|
|
44
|
+
test('reads zero from uninitialized address', () => {
|
|
45
|
+
expect(ram.read(0x0000)).toBe(0x00)
|
|
46
|
+
expect(ram.read(0x1234)).toBe(0x00)
|
|
47
|
+
expect(ram.read(0x7FFF)).toBe(0x00)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('reads data from address', () => {
|
|
51
|
+
ram.data[0x1234] = 0x42
|
|
52
|
+
expect(ram.read(0x1234)).toBe(0x42)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('reads from start address', () => {
|
|
56
|
+
ram.data[0x0000] = 0xFF
|
|
57
|
+
expect(ram.read(0x0000)).toBe(0xFF)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('reads from end address', () => {
|
|
61
|
+
ram.data[0x7FFF] = 0xAB
|
|
62
|
+
expect(ram.read(0x7FFF)).toBe(0xAB)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('reads different values from different addresses', () => {
|
|
66
|
+
ram.data[0x0100] = 0x11
|
|
67
|
+
ram.data[0x0200] = 0x22
|
|
68
|
+
ram.data[0x0300] = 0x33
|
|
69
|
+
|
|
70
|
+
expect(ram.read(0x0100)).toBe(0x11)
|
|
71
|
+
expect(ram.read(0x0200)).toBe(0x22)
|
|
72
|
+
expect(ram.read(0x0300)).toBe(0x33)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('write()', () => {
|
|
77
|
+
test('writes data to address', () => {
|
|
78
|
+
ram.write(0x1234, 0x42)
|
|
79
|
+
expect(ram.data[0x1234]).toBe(0x42)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('writes to start address', () => {
|
|
83
|
+
ram.write(0x0000, 0xFF)
|
|
84
|
+
expect(ram.data[0x0000]).toBe(0xFF)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('writes to end address', () => {
|
|
88
|
+
ram.write(0x7FFF, 0xAB)
|
|
89
|
+
expect(ram.data[0x7FFF]).toBe(0xAB)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('writes different values to different addresses', () => {
|
|
93
|
+
ram.write(0x0100, 0x11)
|
|
94
|
+
ram.write(0x0200, 0x22)
|
|
95
|
+
ram.write(0x0300, 0x33)
|
|
96
|
+
|
|
97
|
+
expect(ram.data[0x0100]).toBe(0x11)
|
|
98
|
+
expect(ram.data[0x0200]).toBe(0x22)
|
|
99
|
+
expect(ram.data[0x0300]).toBe(0x33)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('overwrites existing data', () => {
|
|
103
|
+
ram.write(0x1000, 0x11)
|
|
104
|
+
expect(ram.data[0x1000]).toBe(0x11)
|
|
105
|
+
|
|
106
|
+
ram.write(0x1000, 0x22)
|
|
107
|
+
expect(ram.data[0x1000]).toBe(0x22)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('does not affect other addresses', () => {
|
|
111
|
+
ram.write(0x1000, 0xFF)
|
|
112
|
+
|
|
113
|
+
expect(ram.data[0x0FFF]).toBe(0x00)
|
|
114
|
+
expect(ram.data[0x1001]).toBe(0x00)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('read/write integration', () => {
|
|
119
|
+
test('read returns written value', () => {
|
|
120
|
+
ram.write(0x1234, 0x42)
|
|
121
|
+
expect(ram.read(0x1234)).toBe(0x42)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('multiple read/write operations', () => {
|
|
125
|
+
ram.write(0x0100, 0x11)
|
|
126
|
+
ram.write(0x0200, 0x22)
|
|
127
|
+
ram.write(0x0300, 0x33)
|
|
128
|
+
|
|
129
|
+
expect(ram.read(0x0100)).toBe(0x11)
|
|
130
|
+
expect(ram.read(0x0200)).toBe(0x22)
|
|
131
|
+
expect(ram.read(0x0300)).toBe(0x33)
|
|
132
|
+
|
|
133
|
+
ram.write(0x0100, 0x44)
|
|
134
|
+
expect(ram.read(0x0100)).toBe(0x44)
|
|
135
|
+
expect(ram.read(0x0200)).toBe(0x22) // unchanged
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('reset()', () => {
|
|
140
|
+
beforeEach(() => {
|
|
141
|
+
// Write some data to memory
|
|
142
|
+
ram.write(0x0000, 0x11)
|
|
143
|
+
ram.write(0x1234, 0x42)
|
|
144
|
+
ram.write(0x7FFF, 0xFF)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('resets all memory to zero when coldStart is true', () => {
|
|
148
|
+
ram.reset(true)
|
|
149
|
+
|
|
150
|
+
expect(ram.read(0x0000)).toBe(0x00)
|
|
151
|
+
expect(ram.read(0x1234)).toBe(0x00)
|
|
152
|
+
expect(ram.read(0x7FFF)).toBe(0x00)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('does not reset memory when coldStart is false', () => {
|
|
156
|
+
ram.reset(false)
|
|
157
|
+
|
|
158
|
+
expect(ram.read(0x0000)).toBe(0x11)
|
|
159
|
+
expect(ram.read(0x1234)).toBe(0x42)
|
|
160
|
+
expect(ram.read(0x7FFF)).toBe(0xFF)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('all memory is zeroed after cold start', () => {
|
|
164
|
+
ram.reset(true)
|
|
165
|
+
|
|
166
|
+
for (let i = 0; i < RAM.SIZE; i++) {
|
|
167
|
+
expect(ram.data[i]).toBe(0x00)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('memory can be written after reset', () => {
|
|
172
|
+
ram.reset(true)
|
|
173
|
+
ram.write(0x5000, 0xAB)
|
|
174
|
+
|
|
175
|
+
expect(ram.read(0x5000)).toBe(0xAB)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe('Edge Cases', () => {
|
|
180
|
+
test('handles byte overflow (values > 0xFF)', () => {
|
|
181
|
+
ram.write(0x1000, 0x1FF)
|
|
182
|
+
expect(ram.data[0x1000]).toBe(0x1FF) // JavaScript allows this
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('handles zero value', () => {
|
|
186
|
+
ram.write(0x1000, 0xFF)
|
|
187
|
+
ram.write(0x1000, 0x00)
|
|
188
|
+
expect(ram.read(0x1000)).toBe(0x00)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('handles maximum byte value (0xFF)', () => {
|
|
192
|
+
ram.write(0x1000, 0xFF)
|
|
193
|
+
expect(ram.read(0x1000)).toBe(0xFF)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
})
|