Skip to content
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
18 changes: 6 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ An Octo compatible XO Chip, Super Chip, and Chip 8 emulator.
2. [Starting the Emulator](#starting-the-emulator)
3. [Running a ROM](#running-a-rom)
4. [Screen Scale](#screen-scale)
5. [Execution Delay](#execution-delay)
5. [Instructions Per Second](#instructions-per-second)
6. [Quirks Modes](#quirks-modes)
1. [Shift Quirks](#shift-quirks)
2. [Index Quirks](#index-quirks)
Expand Down Expand Up @@ -125,18 +125,12 @@ at 1x scale is 64 x 32):
The command above will scale the window so that it is 10 times the normal
size.

### Execution Delay
### Instructions Per Second

You may also wish to experiment with the `--delay` switch, which instructs
the emulator to add a delay to every operation that is executed. For example,

java -jar emulator-2.0.0-all.jar /path/to/rom/filename --delay 10

The command above will add a 10 ms delay to every opcode that is executed.
This is useful for very fast computers (note that it is difficult to find
information regarding opcode execution times, as such, I have not attempted
any fancy timing mechanisms to ensure that instructions are executed in a
set amount of time).
The `--ticks` switch will limit the number of instructions per second that the
emulator is allowed to run. By default, the value is set to 1,000. Minimum values
are 200. Use this switch to adjust the running time of ROMs that execute too quickly.
For simplicity, each instruction is assumed to take the same amount of time.

### Quirks Modes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2024 Craig Thomas
* Copyright (C) 2013-2025 Craig Thomas
* This project uses an MIT style license - see LICENSE for details.
*/
package ca.craigthomas.chip8java.emulator.components;
Expand All @@ -9,7 +9,6 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import javax.sound.midi.*;
import javax.sound.sampled.*;

/**
Expand All @@ -34,9 +33,6 @@
// The logger for the class
private final static Logger LOGGER = Logger.getLogger(Emulator.class.getName());

// The number of milliseconds for the delay timer
private static final long DELAY_INTERVAL = 17;

// The total number of registers in the Chip 8 CPU
private static final int NUM_REGISTERS = 16;

Expand All @@ -63,6 +59,9 @@
*/
private static final int MIN_AUDIO_SAMPLES = 3200;

// The maximum number of cycles per second allowed
public static final int DEFAULT_MAX_TICKS = 1000;

// The internal 8-bit registers
protected short[] v;

Expand Down Expand Up @@ -111,17 +110,9 @@
// A description of the last operation
protected String lastOpDesc;

// A Midi device for simple tone generation
private Synthesizer synthesizer;

// The Midi channel to perform playback on
private MidiChannel midiChannel;

// The current operating mode for the CPU
protected int mode;

public static final int DEFAULT_CPU_CYCLE_TIME = 1;

// Whether the CPU is waiting for a keypress
private boolean awaitingKeypress = false;

Expand Down Expand Up @@ -149,6 +140,12 @@
// Stores the generated sound clip
Clip generatedClip = null;

// How many ticks have passed
private int tickCounter = 0;

// The maximum number of ticks allowed per cycle
private int maxTicks = 1000;

CentralProcessingUnit(Memory memory, Keyboard keyboard, Screen screen) {
this.random = new Random();
this.memory = memory;
Expand All @@ -159,19 +156,22 @@
@Override
public void run() {
decrementTimers();
tickCounter = 0;
}
}, DELAY_INTERVAL, DELAY_INTERVAL);
}, 0, 17L);
mode = MODE_NORMAL;
reset();
}

try {
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
midiChannel = synthesizer.getChannels()[0];
} catch (MidiUnavailableException e) {
LOGGER.warning("Midi device not available for sound playback");
/**
* Sets the maximum allowed number of operations allowed per second
*/
public void setMaxTicks(int maxTicksAllowed) {
if (maxTicksAllowed < 200) {
maxTicksAllowed = 200;

Check warning on line 171 in src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java#L171

Added line #L171 was not covered by tests
}

reset();
maxTicks = maxTicksAllowed / 60;

Check warning on line 174 in src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java#L174

Added line #L174 was not covered by tests
}

/**
Expand Down Expand Up @@ -224,13 +224,16 @@
* to the next instruction, and execute the instruction.
*/
public void fetchIncrementExecute() {
operand = memory.read(pc);
operand = operand << 8;
operand += memory.read(pc + 1);
operand = operand & 0x0FFFF;
pc += 2;
int opcode = (operand & 0x0F000) >> 12;
executeInstruction(opcode);
if (tickCounter < maxTicks) {
operand = memory.read(pc);
operand = operand << 8;
operand += memory.read(pc + 1);
operand = operand & 0x0FFFF;
pc += 2;
int opcode = (operand & 0x0F000) >> 12;
executeInstruction(opcode);
tickCounter++;
}
}

/**
Expand Down Expand Up @@ -1252,6 +1255,7 @@
awaitingKeypress = false;
audioPatternBuffer = new int[16];
soundPlaying = false;
tickCounter = 0;
}

/**
Expand Down Expand Up @@ -1458,6 +1462,5 @@
* Stops CPU execution.
*/
public void kill() {
synthesizer.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2024 Craig Thomas
* Copyright (C) 2013-2025 Craig Thomas
* This project uses an MIT style license - see LICENSE for details.
*/
package ca.craigthomas.chip8java.emulator.components;
Expand Down Expand Up @@ -61,7 +61,7 @@
* Initializes an Emulator based on the parameters passed.
*
* @param scale the screen scaling to apply to the emulator window
* @param cycleTime the cycle time delay for the emulator
* @param maxTicks the maximum number of operations per second to execute
* @param rom the rom filename to load
* @param memSize4k whether to set memory size to 4k
* @param color0 the bitplane 0 color
Expand All @@ -75,7 +75,7 @@
*/
public Emulator(
int scale,
int cycleTime,
int maxTicks,
String rom,
boolean memSize4k,
String color0,
Expand Down Expand Up @@ -140,7 +140,6 @@
System.exit(1);
}

cpuCycleTime = cycleTime;
keyboard = new Keyboard();
memory = new Memory(memSize4k);
screen = new Screen(scale, converted_color0, converted_color1, converted_color2, converted_color3);
Expand All @@ -150,6 +149,7 @@
cpu.setJumpQuirks(jumpQuirks);
cpu.setIndexQuirks(indexQuirks);
cpu.setClipQuirks(clipQuirks);
cpu.setMaxTicks(maxTicks);

Check warning on line 152 in src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java#L152

Added line #L152 was not covered by tests

// Load the font file into memory
InputStream fontFileStream = IO.openInputStreamFromResource(FONT_FILE);
Expand Down Expand Up @@ -188,17 +188,12 @@
refreshScreen();
}
};
timer.scheduleAtFixedRate(timerTask, 0L, 33L);
timer.scheduleAtFixedRate(timerTask, 0L, 17L);

Check warning on line 191 in src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java#L191

Added line #L191 was not covered by tests

while (state != EmulatorState.KILLED) {
if (state != EmulatorState.PAUSED) {
if (!cpu.isAwaitingKeypress()) {
cpu.fetchIncrementExecute();
try {
Thread.sleep(cpuCycleTime);
} catch (InterruptedException e) {
LOGGER.warning("CPU sleep interrupted");
}
} else {
cpu.decodeKeypressAndContinue();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2024 Craig Thomas
* Copyright (C) 2013-2025 Craig Thomas
* This project uses an MIT style license - see LICENSE for details.
*/
package ca.craigthomas.chip8java.emulator.runner;
Expand All @@ -18,9 +18,6 @@
@Parameter(names={"--scale"}, description="scale factor")
public Integer scale = 7;

@Parameter(names={"--delay"}, description="delay factor")
public Integer delay = (int) CentralProcessingUnit.DEFAULT_CPU_CYCLE_TIME;

@Parameter(names={"--mem_size_4k"}, description="sets memory size to 4K (defaults to 64K)")
public Boolean memSize4k = false;

Expand Down Expand Up @@ -50,4 +47,7 @@

@Parameter(names={"--clip_quirks"}, description="enable clip quirks")
public Boolean clipQuirks = false;

@Parameter(names={"--ticks"}, description="how many instructions per seconds are allowed")

Check warning on line 51 in src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java#L51

Added line #L51 was not covered by tests
public int maxTicks = CentralProcessingUnit.DEFAULT_MAX_TICKS;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2024 Craig Thomas
* Copyright (C) 2013-2025 Craig Thomas
* This project uses an MIT style license - see LICENSE for details.
*/
package ca.craigthomas.chip8java.emulator.runner;
Expand Down Expand Up @@ -29,7 +29,7 @@ public static void main(String[] argv) {
/* Create the emulator and start it running */
Emulator emulator = new Emulator(
args.scale,
args.delay,
args.maxTicks,
args.romFile,
args.memSize4k,
args.color0,
Expand Down