From e3f0a4de8921e67b6d350afaa635c202341ece7e Mon Sep 17 00:00:00 2001 From: Craig Thomas Date: Mon, 13 Jan 2025 15:03:18 -0500 Subject: [PATCH] Add CPU instruction limiter --- README.md | 18 ++---- .../components/CentralProcessingUnit.java | 61 ++++++++++--------- .../emulator/components/Emulator.java | 15 ++--- .../chip8java/emulator/runner/Arguments.java | 8 +-- .../chip8java/emulator/runner/Runner.java | 4 +- 5 files changed, 49 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index bf0f837..2f92349 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java b/src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java index 7c45a1e..763cf78 100644 --- a/src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java +++ b/src/main/java/ca/craigthomas/chip8java/emulator/components/CentralProcessingUnit.java @@ -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; @@ -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.*; /** @@ -34,9 +33,6 @@ public class CentralProcessingUnit extends Thread // 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; @@ -63,6 +59,9 @@ public class CentralProcessingUnit extends Thread */ 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; @@ -111,17 +110,9 @@ public class CentralProcessingUnit extends Thread // 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; @@ -149,6 +140,12 @@ public class CentralProcessingUnit extends Thread // 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; @@ -159,19 +156,22 @@ public class CentralProcessingUnit extends Thread @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; } - reset(); + maxTicks = maxTicksAllowed / 60; } /** @@ -224,13 +224,16 @@ public void setClipQuirks(boolean enableQuirk) { * 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++; + } } /** @@ -1252,6 +1255,7 @@ public void reset() { awaitingKeypress = false; audioPatternBuffer = new int[16]; soundPlaying = false; + tickCounter = 0; } /** @@ -1458,6 +1462,5 @@ public String cpuStatusLine3() { * Stops CPU execution. */ public void kill() { - synthesizer.close(); } } diff --git a/src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java b/src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java index 07cf431..f02a6b6 100644 --- a/src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java +++ b/src/main/java/ca/craigthomas/chip8java/emulator/components/Emulator.java @@ -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; @@ -61,7 +61,7 @@ public Emulator() { * 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 @@ -75,7 +75,7 @@ public Emulator() { */ public Emulator( int scale, - int cycleTime, + int maxTicks, String rom, boolean memSize4k, String color0, @@ -140,7 +140,6 @@ public Emulator( System.exit(1); } - cpuCycleTime = cycleTime; keyboard = new Keyboard(); memory = new Memory(memSize4k); screen = new Screen(scale, converted_color0, converted_color1, converted_color2, converted_color3); @@ -150,6 +149,7 @@ public Emulator( cpu.setJumpQuirks(jumpQuirks); cpu.setIndexQuirks(indexQuirks); cpu.setClipQuirks(clipQuirks); + cpu.setMaxTicks(maxTicks); // Load the font file into memory InputStream fontFileStream = IO.openInputStreamFromResource(FONT_FILE); @@ -188,17 +188,12 @@ public void run() { refreshScreen(); } }; - timer.scheduleAtFixedRate(timerTask, 0L, 33L); + timer.scheduleAtFixedRate(timerTask, 0L, 17L); 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(); } diff --git a/src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java b/src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java index 5df6f65..73b3e58 100644 --- a/src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java +++ b/src/main/java/ca/craigthomas/chip8java/emulator/runner/Arguments.java @@ -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; @@ -18,9 +18,6 @@ public class Arguments @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; @@ -50,4 +47,7 @@ public class Arguments @Parameter(names={"--clip_quirks"}, description="enable clip quirks") public Boolean clipQuirks = false; + + @Parameter(names={"--ticks"}, description="how many instructions per seconds are allowed") + public int maxTicks = CentralProcessingUnit.DEFAULT_MAX_TICKS; } \ No newline at end of file diff --git a/src/main/java/ca/craigthomas/chip8java/emulator/runner/Runner.java b/src/main/java/ca/craigthomas/chip8java/emulator/runner/Runner.java index 45be532..c966e13 100644 --- a/src/main/java/ca/craigthomas/chip8java/emulator/runner/Runner.java +++ b/src/main/java/ca/craigthomas/chip8java/emulator/runner/Runner.java @@ -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; @@ -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,