Skip to content

Commit bfc8b68

Browse files
authored
Add CPU instruction limiter (#60)
1 parent 796038d commit bfc8b68

File tree

5 files changed

+49
-57
lines changed

5 files changed

+49
-57
lines changed

README.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ An Octo compatible XO Chip, Super Chip, and Chip 8 emulator.
1919
2. [Starting the Emulator](#starting-the-emulator)
2020
3. [Running a ROM](#running-a-rom)
2121
4. [Screen Scale](#screen-scale)
22-
5. [Execution Delay](#execution-delay)
22+
5. [Instructions Per Second](#instructions-per-second)
2323
6. [Quirks Modes](#quirks-modes)
2424
1. [Shift Quirks](#shift-quirks)
2525
2. [Index Quirks](#index-quirks)
@@ -125,18 +125,12 @@ at 1x scale is 64 x 32):
125125
The command above will scale the window so that it is 10 times the normal
126126
size.
127127

128-
### Execution Delay
128+
### Instructions Per Second
129129

130-
You may also wish to experiment with the `--delay` switch, which instructs
131-
the emulator to add a delay to every operation that is executed. For example,
132-
133-
java -jar emulator-2.0.0-all.jar /path/to/rom/filename --delay 10
134-
135-
The command above will add a 10 ms delay to every opcode that is executed.
136-
This is useful for very fast computers (note that it is difficult to find
137-
information regarding opcode execution times, as such, I have not attempted
138-
any fancy timing mechanisms to ensure that instructions are executed in a
139-
set amount of time).
130+
The `--ticks` switch will limit the number of instructions per second that the
131+
emulator is allowed to run. By default, the value is set to 1,000. Minimum values
132+
are 200. Use this switch to adjust the running time of ROMs that execute too quickly.
133+
For simplicity, each instruction is assumed to take the same amount of time.
140134

141135
### Quirks Modes
142136

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

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013-2024 Craig Thomas
2+
* Copyright (C) 2013-2025 Craig Thomas
33
* This project uses an MIT style license - see LICENSE for details.
44
*/
55
package ca.craigthomas.chip8java.emulator.components;
@@ -9,7 +9,6 @@
99
import java.util.Timer;
1010
import java.util.TimerTask;
1111
import java.util.logging.Logger;
12-
import javax.sound.midi.*;
1312
import javax.sound.sampled.*;
1413

1514
/**
@@ -34,9 +33,6 @@ public class CentralProcessingUnit extends Thread
3433
// The logger for the class
3534
private final static Logger LOGGER = Logger.getLogger(Emulator.class.getName());
3635

37-
// The number of milliseconds for the delay timer
38-
private static final long DELAY_INTERVAL = 17;
39-
4036
// The total number of registers in the Chip 8 CPU
4137
private static final int NUM_REGISTERS = 16;
4238

@@ -63,6 +59,9 @@ public class CentralProcessingUnit extends Thread
6359
*/
6460
private static final int MIN_AUDIO_SAMPLES = 3200;
6561

62+
// The maximum number of cycles per second allowed
63+
public static final int DEFAULT_MAX_TICKS = 1000;
64+
6665
// The internal 8-bit registers
6766
protected short[] v;
6867

@@ -111,17 +110,9 @@ public class CentralProcessingUnit extends Thread
111110
// A description of the last operation
112111
protected String lastOpDesc;
113112

114-
// A Midi device for simple tone generation
115-
private Synthesizer synthesizer;
116-
117-
// The Midi channel to perform playback on
118-
private MidiChannel midiChannel;
119-
120113
// The current operating mode for the CPU
121114
protected int mode;
122115

123-
public static final int DEFAULT_CPU_CYCLE_TIME = 1;
124-
125116
// Whether the CPU is waiting for a keypress
126117
private boolean awaitingKeypress = false;
127118

@@ -149,6 +140,12 @@ public class CentralProcessingUnit extends Thread
149140
// Stores the generated sound clip
150141
Clip generatedClip = null;
151142

143+
// How many ticks have passed
144+
private int tickCounter = 0;
145+
146+
// The maximum number of ticks allowed per cycle
147+
private int maxTicks = 1000;
148+
152149
CentralProcessingUnit(Memory memory, Keyboard keyboard, Screen screen) {
153150
this.random = new Random();
154151
this.memory = memory;
@@ -159,19 +156,22 @@ public class CentralProcessingUnit extends Thread
159156
@Override
160157
public void run() {
161158
decrementTimers();
159+
tickCounter = 0;
162160
}
163-
}, DELAY_INTERVAL, DELAY_INTERVAL);
161+
}, 0, 17L);
164162
mode = MODE_NORMAL;
163+
reset();
164+
}
165165

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

174-
reset();
174+
maxTicks = maxTicksAllowed / 60;
175175
}
176176

177177
/**
@@ -224,13 +224,16 @@ public void setClipQuirks(boolean enableQuirk) {
224224
* to the next instruction, and execute the instruction.
225225
*/
226226
public void fetchIncrementExecute() {
227-
operand = memory.read(pc);
228-
operand = operand << 8;
229-
operand += memory.read(pc + 1);
230-
operand = operand & 0x0FFFF;
231-
pc += 2;
232-
int opcode = (operand & 0x0F000) >> 12;
233-
executeInstruction(opcode);
227+
if (tickCounter < maxTicks) {
228+
operand = memory.read(pc);
229+
operand = operand << 8;
230+
operand += memory.read(pc + 1);
231+
operand = operand & 0x0FFFF;
232+
pc += 2;
233+
int opcode = (operand & 0x0F000) >> 12;
234+
executeInstruction(opcode);
235+
tickCounter++;
236+
}
234237
}
235238

236239
/**
@@ -1252,6 +1255,7 @@ public void reset() {
12521255
awaitingKeypress = false;
12531256
audioPatternBuffer = new int[16];
12541257
soundPlaying = false;
1258+
tickCounter = 0;
12551259
}
12561260

12571261
/**
@@ -1458,6 +1462,5 @@ public String cpuStatusLine3() {
14581462
* Stops CPU execution.
14591463
*/
14601464
public void kill() {
1461-
synthesizer.close();
14621465
}
14631466
}

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013-2024 Craig Thomas
2+
* Copyright (C) 2013-2025 Craig Thomas
33
* This project uses an MIT style license - see LICENSE for details.
44
*/
55
package ca.craigthomas.chip8java.emulator.components;
@@ -61,7 +61,7 @@ public Emulator() {
6161
* Initializes an Emulator based on the parameters passed.
6262
*
6363
* @param scale the screen scaling to apply to the emulator window
64-
* @param cycleTime the cycle time delay for the emulator
64+
* @param maxTicks the maximum number of operations per second to execute
6565
* @param rom the rom filename to load
6666
* @param memSize4k whether to set memory size to 4k
6767
* @param color0 the bitplane 0 color
@@ -75,7 +75,7 @@ public Emulator() {
7575
*/
7676
public Emulator(
7777
int scale,
78-
int cycleTime,
78+
int maxTicks,
7979
String rom,
8080
boolean memSize4k,
8181
String color0,
@@ -140,7 +140,6 @@ public Emulator(
140140
System.exit(1);
141141
}
142142

143-
cpuCycleTime = cycleTime;
144143
keyboard = new Keyboard();
145144
memory = new Memory(memSize4k);
146145
screen = new Screen(scale, converted_color0, converted_color1, converted_color2, converted_color3);
@@ -150,6 +149,7 @@ public Emulator(
150149
cpu.setJumpQuirks(jumpQuirks);
151150
cpu.setIndexQuirks(indexQuirks);
152151
cpu.setClipQuirks(clipQuirks);
152+
cpu.setMaxTicks(maxTicks);
153153

154154
// Load the font file into memory
155155
InputStream fontFileStream = IO.openInputStreamFromResource(FONT_FILE);
@@ -188,17 +188,12 @@ public void run() {
188188
refreshScreen();
189189
}
190190
};
191-
timer.scheduleAtFixedRate(timerTask, 0L, 33L);
191+
timer.scheduleAtFixedRate(timerTask, 0L, 17L);
192192

193193
while (state != EmulatorState.KILLED) {
194194
if (state != EmulatorState.PAUSED) {
195195
if (!cpu.isAwaitingKeypress()) {
196196
cpu.fetchIncrementExecute();
197-
try {
198-
Thread.sleep(cpuCycleTime);
199-
} catch (InterruptedException e) {
200-
LOGGER.warning("CPU sleep interrupted");
201-
}
202197
} else {
203198
cpu.decodeKeypressAndContinue();
204199
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013-2024 Craig Thomas
2+
* Copyright (C) 2013-2025 Craig Thomas
33
* This project uses an MIT style license - see LICENSE for details.
44
*/
55
package ca.craigthomas.chip8java.emulator.runner;
@@ -18,9 +18,6 @@ public class Arguments
1818
@Parameter(names={"--scale"}, description="scale factor")
1919
public Integer scale = 7;
2020

21-
@Parameter(names={"--delay"}, description="delay factor")
22-
public Integer delay = (int) CentralProcessingUnit.DEFAULT_CPU_CYCLE_TIME;
23-
2421
@Parameter(names={"--mem_size_4k"}, description="sets memory size to 4K (defaults to 64K)")
2522
public Boolean memSize4k = false;
2623

@@ -50,4 +47,7 @@ public class Arguments
5047

5148
@Parameter(names={"--clip_quirks"}, description="enable clip quirks")
5249
public Boolean clipQuirks = false;
50+
51+
@Parameter(names={"--ticks"}, description="how many instructions per seconds are allowed")
52+
public int maxTicks = CentralProcessingUnit.DEFAULT_MAX_TICKS;
5353
}

src/main/java/ca/craigthomas/chip8java/emulator/runner/Runner.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013-2024 Craig Thomas
2+
* Copyright (C) 2013-2025 Craig Thomas
33
* This project uses an MIT style license - see LICENSE for details.
44
*/
55
package ca.craigthomas.chip8java.emulator.runner;
@@ -29,7 +29,7 @@ public static void main(String[] argv) {
2929
/* Create the emulator and start it running */
3030
Emulator emulator = new Emulator(
3131
args.scale,
32-
args.delay,
32+
args.maxTicks,
3333
args.romFile,
3434
args.memSize4k,
3535
args.color0,

0 commit comments

Comments
 (0)