Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0 phase 2 #201

Merged
merged 18 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,19 @@ Try to test and aim for support on all major browsers (Chrome, Firefox, and Safa

# Tests

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**.
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**.

### Blarrg

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

cpu_instrs, instr_timing, mem_timing, mem_timing-2
cpu_instrs, instr_timing, mem_timing, mem_timing-2, halt_bug

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

### Mooneye

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

#### Halt

halt_ime0_ei, halt_ime0_nointr_timing, halt_ime1_timing

![halt_ime0_ei test passing](./test/accuracy/testroms/mooneye/halt/halt_ime0_ei/halt_ime0_ei.golden.png)
![halt_ime0_nointr_timing test passing](./test/accuracy/testroms/mooneye/halt/halt_ime0_nointr_timing/halt_ime0_nointr_timing.golden.png)
![halt_ime1_timing test passing](./test/accuracy/testroms/mooneye/halt/halt_ime1_timing/halt_ime1_timing.golden.png)

# Contributing

Feel free to fork and submit PRs! Any help is much appreciated, and would be a ton of fun!
Expand Down
251 changes: 25 additions & 226 deletions core/core.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
// Imports
import { WASMBOY_STATE_LOCATION } from './constants';
import { Cpu, initializeCpu, executeOpcode } from './cpu/index';
import { Graphics, initializeGraphics, initializePalette, updateGraphics, batchProcessGraphics } from './graphics/index';
import { Interrupts, checkInterrupts } from './interrupts/index';
import { WASMBOY_WASM_PAGES, WASMBOY_STATE_LOCATION } from './constants';
import { Config } from './config';
import { resetCycles } from './cycles';
import { resetSteps } from './execute';
import { Cpu, initializeCpu } from './cpu/index';
import { Graphics, initializeGraphics, initializePalette } from './graphics/index';
import { Interrupts, initializeInterrupts } from './interrupts/index';
import { Joypad } from './joypad/index';
import { Memory, initializeCartridge, initializeDma, eightBitStoreIntoGBMemory, eightBitLoadFromGBMemory } from './memory/index';
import { Timers, initializeTimers, updateTimers, batchProcessTimers } from './timers/index';
import {
Sound,
initializeSound,
Channel1,
Channel2,
Channel3,
Channel4,
updateSound,
getNumberOfSamplesInAudioBuffer
} from './sound/index';
import { WASMBOY_WASM_PAGES } from './constants';
import { Config } from './config';
import { Timers, initializeTimers } from './timers/index';
import { Sound, initializeSound, Channel1, Channel2, Channel3, Channel4 } from './sound/index';
import { hexLog, log } from './helpers/index';
import { u16Portable } from './portable/portable';

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

// Function to track if the core has started
let hasStarted: boolean = false;
export function setHasCoreStarted(value: boolean): void {
hasStarted = value;
}
export function hasCoreStarted(): i32 {
if (hasStarted) {
return 1;
Expand Down Expand Up @@ -133,6 +128,7 @@ function initialize(): void {
initializeGraphics();
initializePalette();
initializeSound();
initializeInterrupts();
initializeTimers();

// Various Other Registers
Expand Down Expand Up @@ -164,214 +160,11 @@ function initialize(): void {
}

// Reset hasStarted, since we are now reset
hasStarted = false;
}

// Public funciton to run opcodes until,
// a frame is ready, or error.
// Return values:
// -1 = error
// 0 = render a frame
export function executeFrame(): i32 {
let error: boolean = false;
let numberOfCycles: i32 = -1;

while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME()) {
numberOfCycles = executeStep();
if (numberOfCycles < 0) {
error = true;
}
}

// Find our exit reason
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
// Render a frame

// Reset our currentCycles
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();

return 0;
}
// TODO: Boot ROM handling

// There was an error, return -1, and push the program counter back to grab the error opcode
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
return -1;
}

// Public Function to run opcodes until,
// a frame is ready, audio bufer is filled, or error
// -1 = error
// 0 = render a frame
// 1 = output audio
export function executeFrameAndCheckAudio(maxAudioBuffer: i32): i32 {
let error: boolean = false;
let numberOfCycles: i32 = -1;
let audioBufferSize: i32 = 1024;

if (maxAudioBuffer && maxAudioBuffer > 0) {
audioBufferSize = maxAudioBuffer;
}
setHasCoreStarted(false);

while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME() && getNumberOfSamplesInAudioBuffer() < audioBufferSize) {
numberOfCycles = executeStep();
if (numberOfCycles < 0) {
error = true;
}
}

// Find our exit reason
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
// Render a frame

// Reset our currentCycles
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();

return 0;
}
if (getNumberOfSamplesInAudioBuffer() >= audioBufferSize) {
// Output Audio
return 1;
}

// TODO: Boot ROM handling

// There was an error, return -1, and push the program counter back to grab the error opcode
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
return -1;
}

// Public function to run opcodes until,
// a breakpoint is reached
// -1 = error
// 0 = frame executed
// 1 = reached breakpoint
export function executeFrameUntilBreakpoint(breakpoint: i32): i32 {
let error: boolean = false;
let numberOfCycles: i32 = -1;

while (!error && Cpu.currentCycles < Cpu.MAX_CYCLES_PER_FRAME() && Cpu.programCounter !== breakpoint) {
numberOfCycles = executeStep();
if (numberOfCycles < 0) {
error = true;
}
}

// Find our exit reason
if (Cpu.currentCycles >= Cpu.MAX_CYCLES_PER_FRAME()) {
// Render a frame

// Reset our currentCycles
Cpu.currentCycles -= Cpu.MAX_CYCLES_PER_FRAME();

return 0;
}
if (Cpu.programCounter === breakpoint) {
// breakpoint
return 1;
}

// TODO: Boot ROM handling

// There was an error, return -1, and push the program counter back to grab the error opcode
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
return -1;
}

// Function to execute an opcode, and update other gameboy hardware.
// http://www.codeslinger.co.uk/pages/projects/gameboy/beginning.html
export function executeStep(): i32 {
// Set has started to 1 since we ran a emulation step
hasStarted = true;

// Get the opcode, and additional bytes to be handled
// Number of cycles defaults to 4, because while we're halted, we run 4 cycles (according to matt :))
let numberOfCycles: i32 = 4;
let opcode: i32 = 0;

// Cpu Halting best explained: https://www.reddit.com/r/EmuDev/comments/5ie3k7/infinite_loop_trying_to_pass_blarggs_interrupt/db7xnbe/
if (!Cpu.isHalted && !Cpu.isStopped) {
opcode = <u8>eightBitLoadFromGBMemory(Cpu.programCounter);
numberOfCycles = executeOpcode(opcode);
} else {
// if we were halted, and interrupts were disabled but interrupts are pending, stop waiting
if (Cpu.isHalted && !Interrupts.masterInterruptSwitch && Interrupts.areInterruptsPending()) {
Cpu.isHalted = false;
Cpu.isStopped = false;

// Need to run the next opcode twice, it's a bug menitoned in
// The reddit comment mentioned above

// CTRL+F "low-power" on gameboy cpu manual
// http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
// E.g
// 0x76 - halt
// FA 34 12 - ld a,(1234)
// Becomes
// FA FA 34 ld a,(34FA)
// 12 ld (de),a
opcode = <u8>eightBitLoadFromGBMemory(Cpu.programCounter);
numberOfCycles = executeOpcode(opcode);
Cpu.programCounter = u16Portable(Cpu.programCounter - 1);
}
}

// blarggFixes, don't allow register F to have the bottom nibble
Cpu.registerF = Cpu.registerF & 0xf0;

// Check if there was an error decoding the opcode
if (numberOfCycles <= 0) {
return numberOfCycles;
}

// Interrupt Handling requires 20 cycles
// https://github.com/Gekkio/mooneye-gb/blob/master/docs/accuracy.markdown#what-is-the-exact-timing-of-cpu-servicing-an-interrupt
// Only check interrupts after an opcode is executed
// Since we don't want to mess up our PC as we are executing
numberOfCycles += checkInterrupts();

// Sync other GB Components with the number of cycles
syncCycles(numberOfCycles);

return numberOfCycles;
}

// Sync other GB Components with the number of cycles
export function syncCycles(numberOfCycles: i32): void {
// Check if we did a DMA TRansfer, if we did add the cycles
if (Memory.DMACycles > 0) {
numberOfCycles += Memory.DMACycles;
Memory.DMACycles = 0;
}

// Finally, Add our number of cycles to the CPU Cycles
Cpu.currentCycles += numberOfCycles;

// Check other Gameboy components
if (!Cpu.isStopped) {
if (Config.graphicsBatchProcessing) {
// Need to do this, since a lot of things depend on the scanline
// Batch processing will simply return if the number of cycles is too low
Graphics.currentCycles += numberOfCycles;
batchProcessGraphics();
} else {
updateGraphics(numberOfCycles);
}

if (Config.audioBatchProcessing) {
Sound.currentCycles += numberOfCycles;
} else {
updateSound(numberOfCycles);
}
}

if (Config.timersBatchProcessing) {
// Batch processing will simply return if the number of cycles is too low
Timers.currentCycles += numberOfCycles;
batchProcessTimers();
} else {
updateTimers(numberOfCycles);
}
// Reset our cycles ran
resetCycles();
resetSteps();
}

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

// Reset hasStarted, since we are now reset
hasStarted = false;
setHasCoreStarted(false);

// Don't want to reset cycles here, as this does not reset the emulator
}

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

// Reset hasStarted, since we are now reset
hasStarted = false;
setHasCoreStarted(false);

// Reset our cycles ran
resetCycles();
resetSteps();
}
Loading