Skip to content

Commit 996e8ad

Browse files
authored
1.0 phase 2 (#201)
* npm audit fix * Fixed up benchmark and preact watch port clash * Fixed up Halt and Interrupt Handling * Fixed more small stuff * Added some new scripts They measure performance of asc and tsc * Fixed graphics timing, and passed another halt test * More passed tests, removed a hexLog * Passing the halt bug * Added screenshots for all new test ROMs * Added better speed switch support * Fixed LCD R/W * Found what broke Pokemon Yellow :) * Finished Cycle Tracking and some Cleanup * Some more execution function types * Exposed a new execute function * Finished the stepping and cycle tracking * Finished another wasm build * Added more tests to the README
1 parent 051a324 commit 996e8ad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+21909
-20914
lines changed

README.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,19 @@ Try to test and aim for support on all major browsers (Chrome, Firefox, and Safa
9393

9494
# Tests
9595

96-
These are all currently known passing tests (by me), there may be more test roms out there that pass. Feel free to open an issue or PR to add the tests to this list 😄 . **The test names are listed from left to right, top to bottom**.
96+
These are all currently known passing tests (by me), there may be more test roms out there that pass. Some tests may not pass, and that can either be because of the component it is testing is actually incorrect, or another component that the test is testing is not yet implemented, or is incorrect (e.g a lot of mooneye tests rely on Serial Interrupts, which this emulator has yet to implement). Feel free to open an issue or PR to add any more passing tests to this list 😄 . **The test names are listed from left to right, top to bottom**.
9797

9898
### Blarrg
9999

100100
[Repo with all blargg's tests and source](https://github.com/retrio/gb-test-roms)
101101

102-
cpu_instrs, instr_timing, mem_timing, mem_timing-2
102+
cpu_instrs, instr_timing, mem_timing, mem_timing-2, halt_bug
103103

104104
![Cpu Instructions all tests passing](./test/accuracy/testroms/blargg/cpu_instrs/cpu_instrs.golden.png)
105105
![Instruction timing all tests passing](./test/accuracy/testroms/blargg/instr_timing/instr_timing.golden.png)
106106
![Memory timing all tests passing](./test/accuracy/testroms/blargg/mem_timing/mem_timing.golden.png)
107107
![Memory timing 2 all tests passing](./test/accuracy/testroms/blargg/mem_timing-2/mem_timing-2.golden.png)
108+
![halt bug all tests passing](./test/accuracy/testroms/blargg/halt_bug/halt_bug.golden.png)
108109

109110
### Mooneye
110111

@@ -128,6 +129,14 @@ div_write, rapid_toggle, tim00, tim00_div_trigger, tim01, tim01_div_trigger, tim
128129
![tima write reloading test passing](./test/accuracy/testroms/mooneye/timer/tima_write_reloading/tima_write_reloading.golden.png)
129130
![tma write reloading test passing](./test/accuracy/testroms/mooneye/timer/tma_write_reloading/tma_write_reloading.golden.png)
130131

132+
#### Halt
133+
134+
halt_ime0_ei, halt_ime0_nointr_timing, halt_ime1_timing
135+
136+
![halt_ime0_ei test passing](./test/accuracy/testroms/mooneye/halt/halt_ime0_ei/halt_ime0_ei.golden.png)
137+
![halt_ime0_nointr_timing test passing](./test/accuracy/testroms/mooneye/halt/halt_ime0_nointr_timing/halt_ime0_nointr_timing.golden.png)
138+
![halt_ime1_timing test passing](./test/accuracy/testroms/mooneye/halt/halt_ime1_timing/halt_ime1_timing.golden.png)
139+
131140
# Contributing
132141

133142
Feel free to fork and submit PRs! Any help is much appreciated, and would be a ton of fun!

core/core.ts

+25-226
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
// Imports
2-
import { WASMBOY_STATE_LOCATION } from './constants';
3-
import { Cpu, initializeCpu, executeOpcode } from './cpu/index';
4-
import { Graphics, initializeGraphics, initializePalette, updateGraphics, batchProcessGraphics } from './graphics/index';
5-
import { Interrupts, checkInterrupts } from './interrupts/index';
2+
import { WASMBOY_WASM_PAGES, WASMBOY_STATE_LOCATION } from './constants';
3+
import { Config } from './config';
4+
import { resetCycles } from './cycles';
5+
import { resetSteps } from './execute';
6+
import { Cpu, initializeCpu } from './cpu/index';
7+
import { Graphics, initializeGraphics, initializePalette } from './graphics/index';
8+
import { Interrupts, initializeInterrupts } from './interrupts/index';
69
import { Joypad } from './joypad/index';
710
import { Memory, initializeCartridge, initializeDma, eightBitStoreIntoGBMemory, eightBitLoadFromGBMemory } from './memory/index';
8-
import { Timers, initializeTimers, updateTimers, batchProcessTimers } from './timers/index';
9-
import {
10-
Sound,
11-
initializeSound,
12-
Channel1,
13-
Channel2,
14-
Channel3,
15-
Channel4,
16-
updateSound,
17-
getNumberOfSamplesInAudioBuffer
18-
} from './sound/index';
19-
import { WASMBOY_WASM_PAGES } from './constants';
20-
import { Config } from './config';
11+
import { Timers, initializeTimers } from './timers/index';
12+
import { Sound, initializeSound, Channel1, Channel2, Channel3, Channel4 } from './sound/index';
2113
import { hexLog, log } from './helpers/index';
2214
import { u16Portable } from './portable/portable';
2315

@@ -28,6 +20,9 @@ if (memory.size() < WASMBOY_WASM_PAGES) {
2820

2921
// Function to track if the core has started
3022
let hasStarted: boolean = false;
23+
export function setHasCoreStarted(value: boolean): void {
24+
hasStarted = value;
25+
}
3126
export function hasCoreStarted(): i32 {
3227
if (hasStarted) {
3328
return 1;
@@ -133,6 +128,7 @@ function initialize(): void {
133128
initializeGraphics();
134129
initializePalette();
135130
initializeSound();
131+
initializeInterrupts();
136132
initializeTimers();
137133

138134
// Various Other Registers
@@ -164,214 +160,11 @@ function initialize(): void {
164160
}
165161

166162
// Reset hasStarted, since we are now reset
167-
hasStarted = false;
168-
}
169-
170-
// Public funciton to run opcodes until,
171-
// a frame is ready, or error.
172-
// Return values:
173-
// -1 = error
174-
// 0 = render a frame
175-
export function executeFrame(): i32 {
176-
let error: boolean = false;
177-
let numberOfCycles: i32 = -1;
178-
179-
while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME()) {
180-
numberOfCycles = executeStep();
181-
if (numberOfCycles < 0) {
182-
error = true;
183-
}
184-
}
185-
186-
// Find our exit reason
187-
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
188-
// Render a frame
189-
190-
// Reset our currentCycles
191-
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();
192-
193-
return 0;
194-
}
195-
// TODO: Boot ROM handling
196-
197-
// There was an error, return -1, and push the program counter back to grab the error opcode
198-
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
199-
return -1;
200-
}
201-
202-
// Public Function to run opcodes until,
203-
// a frame is ready, audio bufer is filled, or error
204-
// -1 = error
205-
// 0 = render a frame
206-
// 1 = output audio
207-
export function executeFrameAndCheckAudio(maxAudioBuffer: i32): i32 {
208-
let error: boolean = false;
209-
let numberOfCycles: i32 = -1;
210-
let audioBufferSize: i32 = 1024;
211-
212-
if (maxAudioBuffer && maxAudioBuffer > 0) {
213-
audioBufferSize = maxAudioBuffer;
214-
}
163+
setHasCoreStarted(false);
215164

216-
while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME() && getNumberOfSamplesInAudioBuffer() < audioBufferSize) {
217-
numberOfCycles = executeStep();
218-
if (numberOfCycles < 0) {
219-
error = true;
220-
}
221-
}
222-
223-
// Find our exit reason
224-
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
225-
// Render a frame
226-
227-
// Reset our currentCycles
228-
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();
229-
230-
return 0;
231-
}
232-
if (getNumberOfSamplesInAudioBuffer() >= audioBufferSize) {
233-
// Output Audio
234-
return 1;
235-
}
236-
237-
// TODO: Boot ROM handling
238-
239-
// There was an error, return -1, and push the program counter back to grab the error opcode
240-
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
241-
return -1;
242-
}
243-
244-
// Public function to run opcodes until,
245-
// a breakpoint is reached
246-
// -1 = error
247-
// 0 = frame executed
248-
// 1 = reached breakpoint
249-
export function executeFrameUntilBreakpoint(breakpoint: i32): i32 {
250-
let error: boolean = false;
251-
let numberOfCycles: i32 = -1;
252-
253-
while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME() && Cpu.programCounter !== breakpoint) {
254-
numberOfCycles = executeStep();
255-
if (numberOfCycles < 0) {
256-
error = true;
257-
}
258-
}
259-
260-
// Find our exit reason
261-
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
262-
// Render a frame
263-
264-
// Reset our currentCycles
265-
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();
266-
267-
return 0;
268-
}
269-
if (Cpu.programCounter === breakpoint) {
270-
// breakpoint
271-
return 1;
272-
}
273-
274-
// TODO: Boot ROM handling
275-
276-
// There was an error, return -1, and push the program counter back to grab the error opcode
277-
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
278-
return -1;
279-
}
280-
281-
// Function to execute an opcode, and update other gameboy hardware.
282-
// http://www.codeslinger.co.uk/pages/projects/gameboy/beginning.html
283-
export function executeStep(): i32 {
284-
// Set has started to 1 since we ran a emulation step
285-
hasStarted = true;
286-
287-
// Get the opcode, and additional bytes to be handled
288-
// Number of cycles defaults to 4, because while we're halted, we run 4 cycles (according to matt :))
289-
let numberOfCycles: i32 = 4;
290-
let opcode: i32 = 0;
291-
292-
// Cpu Halting best explained: https://www.reddit.com/r/EmuDev/comments/5ie3k7/infinite_loop_trying_to_pass_blarggs_interrupt/db7xnbe/
293-
if (!Cpu.isHalted && !Cpu.isStopped) {
294-
opcode = <u8>eightBitLoadFromGBMemory(Cpu.programCounter);
295-
numberOfCycles = executeOpcode(opcode);
296-
} else {
297-
// if we were halted, and interrupts were disabled but interrupts are pending, stop waiting
298-
if (Cpu.isHalted && !Interrupts.masterInterruptSwitch && Interrupts.areInterruptsPending()) {
299-
Cpu.isHalted = false;
300-
Cpu.isStopped = false;
301-
302-
// Need to run the next opcode twice, it's a bug menitoned in
303-
// The reddit comment mentioned above
304-
305-
// CTRL+F "low-power" on gameboy cpu manual
306-
// http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
307-
// E.g
308-
// 0x76 - halt
309-
// FA 34 12 - ld a,(1234)
310-
// Becomes
311-
// FA FA 34 ld a,(34FA)
312-
// 12 ld (de),a
313-
opcode = <u8>eightBitLoadFromGBMemory(Cpu.programCounter);
314-
numberOfCycles = executeOpcode(opcode);
315-
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
316-
}
317-
}
318-
319-
// blarggFixes, don't allow register F to have the bottom nibble
320-
Cpu.registerF = Cpu.registerF & 0xf0;
321-
322-
// Check if there was an error decoding the opcode
323-
if (numberOfCycles <= 0) {
324-
return numberOfCycles;
325-
}
326-
327-
// Interrupt Handling requires 20 cycles
328-
// https://github.com/Gekkio/mooneye-gb/blob/master/docs/accuracy.markdown#what-is-the-exact-timing-of-cpu-servicing-an-interrupt
329-
// Only check interrupts after an opcode is executed
330-
// Since we don't want to mess up our PC as we are executing
331-
numberOfCycles += checkInterrupts();
332-
333-
// Sync other GB Components with the number of cycles
334-
syncCycles(numberOfCycles);
335-
336-
return numberOfCycles;
337-
}
338-
339-
// Sync other GB Components with the number of cycles
340-
export function syncCycles(numberOfCycles: i32): void {
341-
// Check if we did a DMA TRansfer, if we did add the cycles
342-
if (Memory.DMACycles > 0) {
343-
numberOfCycles += Memory.DMACycles;
344-
Memory.DMACycles = 0;
345-
}
346-
347-
// Finally, Add our number of cycles to the CPU Cycles
348-
Cpu.currentCycles += numberOfCycles;
349-
350-
// Check other Gameboy components
351-
if (!Cpu.isStopped) {
352-
if (Config.graphicsBatchProcessing) {
353-
// Need to do this, since a lot of things depend on the scanline
354-
// Batch processing will simply return if the number of cycles is too low
355-
Graphics.currentCycles += numberOfCycles;
356-
batchProcessGraphics();
357-
} else {
358-
updateGraphics(numberOfCycles);
359-
}
360-
361-
if (Config.audioBatchProcessing) {
362-
Sound.currentCycles += numberOfCycles;
363-
} else {
364-
updateSound(numberOfCycles);
365-
}
366-
}
367-
368-
if (Config.timersBatchProcessing) {
369-
// Batch processing will simply return if the number of cycles is too low
370-
Timers.currentCycles += numberOfCycles;
371-
batchProcessTimers();
372-
} else {
373-
updateTimers(numberOfCycles);
374-
}
165+
// Reset our cycles ran
166+
resetCycles();
167+
resetSteps();
375168
}
376169

377170
// Function to return an address to store into save state memory
@@ -397,7 +190,9 @@ export function saveState(): void {
397190
Channel4.saveState();
398191

399192
// Reset hasStarted, since we are now reset
400-
hasStarted = false;
193+
setHasCoreStarted(false);
194+
195+
// Don't want to reset cycles here, as this does not reset the emulator
401196
}
402197

403198
// Function to load state from memory for all of our classes
@@ -415,5 +210,9 @@ export function loadState(): void {
415210
Channel4.loadState();
416211

417212
// Reset hasStarted, since we are now reset
418-
hasStarted = false;
213+
setHasCoreStarted(false);
214+
215+
// Reset our cycles ran
216+
resetCycles();
217+
resetSteps();
419218
}

0 commit comments

Comments
 (0)