From 8994b6e44461916730b6bda429d4caa070134d15 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sun, 31 Dec 2023 09:42:05 +0100 Subject: [PATCH] [#314] Use AccurateFrequencyRunner --- .../plugins/cpu/intel8080/Breakpoint.java | 25 ------ .../plugins/cpu/intel8080/EmulatorEngine.java | 66 +++++---------- .../plugins/cpu/ssem/EmulatorEngine.java | 83 +++++++------------ .../plugins/cpu/ssem/gui/CpuPanel.java | 73 ++++++---------- .../plugins/cpu/zilogZ80/Breakpoint.java | 22 ----- .../plugins/cpu/zilogZ80/EmulatorEngine.java | 67 +++++---------- 6 files changed, 100 insertions(+), 236 deletions(-) delete mode 100644 plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/Breakpoint.java delete mode 100644 plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/Breakpoint.java diff --git a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/Breakpoint.java b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/Breakpoint.java deleted file mode 100644 index b50edfb39..000000000 --- a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/Breakpoint.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This file is part of emuStudio. - * - * Copyright (C) 2006-2023 Peter Jakubčo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.emustudio.plugins.cpu.intel8080; - -public class Breakpoint extends Exception { - - public Breakpoint() { - } -} diff --git a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/EmulatorEngine.java b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/EmulatorEngine.java index 499af41e1..aa77bbe4c 100644 --- a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/EmulatorEngine.java +++ b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/EmulatorEngine.java @@ -18,9 +18,9 @@ */ package net.emustudio.plugins.cpu.intel8080; +import net.emustudio.emulib.plugins.cpu.AccurateFrequencyRunner; import net.emustudio.emulib.plugins.cpu.CPU; import net.emustudio.emulib.plugins.memory.MemoryContext; -import net.emustudio.emulib.runtime.helpers.SleepUtils; import net.emustudio.plugins.cpu.intel8080.api.CpuEngine; import net.emustudio.plugins.cpu.intel8080.api.DispatchListener; import org.slf4j.Logger; @@ -29,7 +29,6 @@ import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.Map; -import java.util.concurrent.TimeUnit; import static net.emustudio.plugins.cpu.intel8080.DispatchTables.DISPATCH_TABLE; @@ -55,6 +54,8 @@ public class EmulatorEngine implements CpuEngine { ); private final MemoryContext memory; private final Context8080Impl context; + private final AccurateFrequencyRunner preciseRunner = new AccurateFrequencyRunner(); + public boolean INTE = false; // enabling / disabling of interrupts public int PC = 0; // program counter public int SP = 0; // stack pointer @@ -100,52 +101,29 @@ public CPU.RunState step() throws Exception { return currentRunState; } - @SuppressWarnings("BusyWait") public CPU.RunState run(CPU cpu) { - final long slotNanos = SleepUtils.SLEEP_PRECISION; - final double slotMicros = slotNanos / 1000.0; - final int cyclesPerSlot = (int) (slotMicros * context.getCPUFrequency() / 1000.0); // frequency in kHZ -> MHz - currentRunState = CPU.RunState.STATE_RUNNING; - long delayNanos = SleepUtils.SLEEP_PRECISION; - - long startTime = System.nanoTime(); - long executedCyclesPerSlot = 0; - while (!Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (delayNanos > 0) { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayNanos)); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - long endTime = System.nanoTime(); - long targetCycles = (endTime - startTime) / slotNanos * cyclesPerSlot; - - while ((executedCyclesPerSlot < targetCycles) && !Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (cpu.isBreakpointSet(PC)) { - throw new Breakpoint(); + return preciseRunner.run( + () -> (double) context.getCPUFrequency(), + () -> { + try { + if (cpu.isBreakpointSet(PC)) { + currentRunState = CPU.RunState.STATE_STOPPED_BREAK; + } else { + int cycles = dispatch(); + preciseRunner.addExecutedCycles(cycles); + context.passedCycles(cycles); + } + } catch (IndexOutOfBoundsException e) { + LOGGER.error("Unexpected error", e); + currentRunState = CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; + } catch (Throwable e) { + LOGGER.error("Unexpected error", e); + currentRunState = CPU.RunState.STATE_STOPPED_BAD_INSTR; } - int cycles = dispatch(); - executedCyclesPerSlot += cycles; - context.passedCycles(cycles); - } catch (Breakpoint e) { - return CPU.RunState.STATE_STOPPED_BREAK; - } catch (IndexOutOfBoundsException e) { - LOGGER.error("Unexpected error", e); - return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; - } catch (Throwable e) { - LOGGER.error("Unexpected error", e); - return CPU.RunState.STATE_STOPPED_BAD_INSTR; + return currentRunState; } - } - - long computationTime = System.nanoTime() - endTime; - delayNanos = slotNanos - computationTime; - } - return currentRunState; + ); } private int dispatch() throws Throwable { diff --git a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/EmulatorEngine.java b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/EmulatorEngine.java index d9f3d6355..ef2d03a4d 100644 --- a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/EmulatorEngine.java +++ b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/EmulatorEngine.java @@ -18,15 +18,11 @@ */ package net.emustudio.plugins.cpu.ssem; -import net.emustudio.emulib.plugins.cpu.CPU; -import net.emustudio.emulib.plugins.cpu.DecodedInstruction; -import net.emustudio.emulib.plugins.cpu.Decoder; -import net.emustudio.emulib.plugins.cpu.InvalidInstructionException; +import net.emustudio.emulib.plugins.cpu.*; import net.emustudio.emulib.plugins.memory.MemoryContext; import net.emustudio.emulib.runtime.helpers.Bits; import net.emustudio.emulib.runtime.helpers.NumberUtils; import net.emustudio.emulib.runtime.helpers.NumberUtils.Strategy; -import net.emustudio.emulib.runtime.helpers.SleepUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +30,6 @@ import java.lang.reflect.Method; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -43,6 +38,9 @@ public class EmulatorEngine { public final static double INSTRUCTION_TIME_MS = 1.44; public final static double INSTRUCTIONS_PER_SECOND = 1.0 / (INSTRUCTION_TIME_MS / 1000.0); + + private final static int INSTRUCTION_CYCLES = 100; // artificial number + public final static double FREQUENCY_KHZ = (INSTRUCTIONS_PER_SECOND / 1000.0) * INSTRUCTION_CYCLES; final static int LINE_MASK = 0b11111000; private final static Logger LOGGER = LoggerFactory.getLogger(EmulatorEngine.class); private static final Method[] DISPATCH_TABLE = new Method[8]; @@ -67,6 +65,7 @@ public class EmulatorEngine { private final Decoder decoder; private final Function isBreakpointSet; private final Bits emptyBits = new Bits(0, 0); + private final AccurateFrequencyRunner preciseRunner = new AccurateFrequencyRunner(); EmulatorEngine(MemoryContext memory, Function isBreakpointSet) { this.memory = Objects.requireNonNull(memory); @@ -154,55 +153,37 @@ private void writeInt(int lineAddress, int value) { memory.write(lineAddress, word); } - @SuppressWarnings("BusyWait") CPU.RunState run() { // 1 instruction takes 1.44 ms (~700 instructions per second) - // 1 / (INSTRUCTION_TIME_MS / 1000) = 694.4444444444444 Hz - - final long slotNanos = 2 * SleepUtils.SLEEP_PRECISION; // sleeping precision is usually ~1 ms - final double slotMillis = slotNanos / 1_000_000.0; - final int instructionsPerSlot = (int) (slotMillis / INSTRUCTION_TIME_MS); - - CPU.RunState currentRunState = CPU.RunState.STATE_RUNNING; - long delayNanos = SleepUtils.SLEEP_PRECISION; - - long startTime = System.nanoTime(); - long executedInstructionsPerSlot = 0; - - while (!Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (delayNanos > 0) { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayNanos)); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - long endTime = System.nanoTime(); - long targetInstructions = (endTime - startTime) / slotNanos * instructionsPerSlot; - - while ((executedInstructionsPerSlot < targetInstructions) && !Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (isBreakpointSet.apply(CI.get())) { - return CPU.RunState.STATE_STOPPED_BREAK; - } - currentRunState = step(); - executedInstructionsPerSlot += 1; - } catch (IllegalArgumentException e) { - LOGGER.debug("Unexpected error", e); - if (e.getCause() != null && e.getCause() instanceof IndexOutOfBoundsException) { + // frequency = 1 / (INSTRUCTION_TIME_MS / 1000) = 694.44 Hz = 0.69444 kHz + // We need integer number of kHZ here, so we need to adjust number of instruction cycles to be > 1. + // Let's say 1 instruction = 100 cycles. + // Then, frequency = 100 * 694.44 Hz = 69444 Hz = 69.444 kHz + + return preciseRunner.run( + () -> FREQUENCY_KHZ, + () -> { + try { + if (isBreakpointSet.apply(CI.get())) { + return CPU.RunState.STATE_STOPPED_BREAK; + } + preciseRunner.addExecutedCycles(INSTRUCTION_CYCLES); + return step(); + } catch (IndexOutOfBoundsException e) { + LOGGER.error("Unexpected error", e); return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; + } catch (IllegalArgumentException e) { + LOGGER.debug("Unexpected error", e); + if (e.getCause() != null && e.getCause() instanceof IndexOutOfBoundsException) { + return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; + } else { + return CPU.RunState.STATE_STOPPED_BAD_INSTR; + } + } catch (Throwable e) { + LOGGER.error("Unexpected error", e); + return CPU.RunState.STATE_STOPPED_BAD_INSTR; } - return CPU.RunState.STATE_STOPPED_BAD_INSTR; - } catch (IndexOutOfBoundsException e) { - LOGGER.debug("Unexpected error", e); - return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; } - } - - long computationTime = System.nanoTime() - endTime; - delayNanos = slotNanos - computationTime; - } - return currentRunState; + ); } } diff --git a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java index 8bcb311a6..2707c868c 100644 --- a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java +++ b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java @@ -50,13 +50,14 @@ public class CpuPanel extends JPanel { private JTextField txtDecMCI; private JTextField txtMLine; private JTextField txtDecMLine; + public CpuPanel(CPU cpu, EmulatorEngine engine, MemoryContext memory) { this.engine = Objects.requireNonNull(engine); this.memory = Objects.requireNonNull(memory); initComponents(); cpu.addCPUListener(new Updater()); - lblSpeed.setText(String.valueOf(EmulatorEngine.INSTRUCTIONS_PER_SECOND)); + lblSpeed.setText(String.format("%.2f", EmulatorEngine.INSTRUCTIONS_PER_SECOND)); } /** @@ -68,43 +69,40 @@ public CpuPanel(CPU cpu, EmulatorEngine engine, MemoryContext memory) { private void initComponents() { JPanel jPanel1 = new JPanel(); - lblRunState = new JLabel(); - JLabel jLabel7 = new JLabel(); - lblSpeed = new JLabel(); + lblRunState = new JLabel("BREAKPOINT"); + JLabel jLabel7 = new JLabel("ins/s"); + lblSpeed = new JLabel("0"); JPanel jPanel2 = new JPanel(); - JLabel jLabel2 = new JLabel(); - JLabel jLabel3 = new JLabel(); - txtCI = new JTextField(); - txtDecCI = new JTextField(); - txtA = new JTextField(); - txtDecA = new JTextField(); - txtBinA = new JTextField(); - txtBinCI = new JTextField(); + JLabel jLabel2 = new JLabel("A"); + JLabel jLabel3 = new JLabel("CI"); + txtCI = new JTextField("0"); + txtDecCI = new JTextField("0"); + txtA = new JTextField("0"); + txtDecA = new JTextField("0"); + txtBinA = new JTextField("0000 0000 0000 0000 0000 0000 0000 0000"); + txtBinCI = new JTextField("0000 0000 0000 0000 0000 0000 0000 0000"); JPanel jPanel3 = new JPanel(); - JLabel jLabel4 = new JLabel(); - JLabel jLabel5 = new JLabel(); - txtMLine = new JTextField(); - txtDecMLine = new JTextField(); - txtMCI = new JTextField(); - txtDecMCI = new JTextField(); - txtBinMCI = new JTextField(); - txtBinMLine = new JTextField(); - JLabel jLabel6 = new JLabel(); - txtLine = new JTextField(); - txtDecLine = new JTextField(); - txtBinLine = new JTextField(); + JLabel jLabel4 = new JLabel("M[CI]"); + JLabel jLabel5 = new JLabel("M[line]"); + txtMLine = new JTextField("0"); + txtDecMLine = new JTextField("0"); + txtMCI = new JTextField("0"); + txtDecMCI = new JTextField("0"); + txtBinMCI = new JTextField("0000 0000 0000 0000 0000 0000 0000 0000"); + txtBinMLine = new JTextField("0000 0000 0000 0000 0000 0000 0000 0000"); + JLabel jLabel6 = new JLabel("line"); + txtLine = new JTextField("0"); + txtDecLine = new JTextField("0"); + txtBinLine = new JTextField("0000 0000"); jPanel1.setBorder(BorderFactory.createTitledBorder("Run control")); lblRunState.setFont(FONT_MONOSPACED_BIG_BOLD); lblRunState.setForeground(CPU_RUN_STATE_COLOR); - lblRunState.setText("BREAKPOINT"); jLabel7.setFont(jLabel7.getFont().deriveFont(jLabel7.getFont().getStyle() | java.awt.Font.BOLD)); - jLabel7.setText("ins/s"); lblSpeed.setFont(FONT_MONOSPACED); - lblSpeed.setText("0"); lblSpeed.setToolTipText("Speed"); GroupLayout jPanel1Layout = new GroupLayout(jPanel1); @@ -134,42 +132,34 @@ private void initComponents() { jPanel2.setBorder(BorderFactory.createTitledBorder("Registers")); jLabel2.setFont(FONT_MONOSPACED); - jLabel2.setText("A"); jLabel2.setToolTipText("Accumulator"); jLabel3.setFont(FONT_MONOSPACED); - jLabel3.setText("CI"); jLabel3.setToolTipText("Control Instruction"); txtCI.setEditable(false); txtCI.setFont(FONT_MONOSPACED); txtCI.setHorizontalAlignment(JTextField.RIGHT); - txtCI.setText("0"); txtDecCI.setEditable(false); txtDecCI.setFont(FONT_MONOSPACED); txtDecCI.setHorizontalAlignment(JTextField.RIGHT); - txtDecCI.setText("0"); txtA.setEditable(false); txtA.setFont(FONT_MONOSPACED); txtA.setHorizontalAlignment(JTextField.RIGHT); - txtA.setText("0"); txtDecA.setEditable(false); txtDecA.setFont(FONT_MONOSPACED); txtDecA.setHorizontalAlignment(JTextField.RIGHT); - txtDecA.setText("0"); txtBinA.setEditable(false); txtBinA.setFont(FONT_MONOSPACED); txtBinA.setHorizontalAlignment(JTextField.RIGHT); - txtBinA.setText("0000 0000 0000 0000 0000 0000 0000 0000"); txtBinCI.setEditable(false); txtBinCI.setFont(FONT_MONOSPACED); txtBinCI.setHorizontalAlignment(JTextField.RIGHT); - txtBinCI.setText("0000 0000 0000 0000 0000 0000 0000 0000"); GroupLayout jPanel2Layout = new GroupLayout(jPanel2); jPanel2.setLayout(jPanel2Layout); @@ -215,61 +205,49 @@ private void initComponents() { jPanel3.setBorder(BorderFactory.createTitledBorder("Memory snippet")); jLabel4.setFont(FONT_MONOSPACED); - jLabel4.setText("M[CI]"); jLabel4.setToolTipText("Control Instruction"); jLabel5.setFont(FONT_MONOSPACED); - jLabel5.setText("M[line]"); jLabel5.setToolTipText("Control Instruction"); txtMLine.setEditable(false); txtMLine.setFont(FONT_MONOSPACED); txtMLine.setHorizontalAlignment(JTextField.RIGHT); - txtMLine.setText("0"); txtDecMLine.setEditable(false); txtDecMLine.setFont(FONT_MONOSPACED); txtDecMLine.setHorizontalAlignment(JTextField.RIGHT); - txtDecMLine.setText("0"); txtMCI.setEditable(false); txtMCI.setFont(FONT_MONOSPACED); txtMCI.setHorizontalAlignment(JTextField.RIGHT); - txtMCI.setText("0"); txtDecMCI.setEditable(false); txtDecMCI.setFont(FONT_MONOSPACED); txtDecMCI.setHorizontalAlignment(JTextField.RIGHT); - txtDecMCI.setText("0"); txtBinMCI.setEditable(false); txtBinMCI.setFont(FONT_MONOSPACED); txtBinMCI.setHorizontalAlignment(JTextField.RIGHT); - txtBinMCI.setText("0000 0000 0000 0000 0000 0000 0000 0000"); txtBinMLine.setEditable(false); txtBinMLine.setFont(FONT_MONOSPACED); txtBinMLine.setHorizontalAlignment(JTextField.RIGHT); - txtBinMLine.setText("0000 0000 0000 0000 0000 0000 0000 0000"); jLabel6.setFont(FONT_MONOSPACED); - jLabel6.setText("line"); jLabel6.setToolTipText("Control Instruction"); txtLine.setEditable(false); txtLine.setFont(FONT_MONOSPACED); txtLine.setHorizontalAlignment(JTextField.RIGHT); - txtLine.setText("0"); txtDecLine.setEditable(false); txtDecLine.setFont(FONT_MONOSPACED); txtDecLine.setHorizontalAlignment(JTextField.RIGHT); - txtDecLine.setText("0"); txtBinLine.setEditable(false); txtBinLine.setFont(FONT_MONOSPACED); txtBinLine.setHorizontalAlignment(JTextField.RIGHT); - txtBinLine.setText("0000 0000"); GroupLayout jPanel3Layout = new GroupLayout(jPanel3); jPanel3.setLayout(jPanel3Layout); @@ -412,5 +390,4 @@ private String formatBinary(int number, int length) { } } - // End of variables declaration//GEN-END:variables } diff --git a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/Breakpoint.java b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/Breakpoint.java deleted file mode 100644 index a2ce523de..000000000 --- a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/Breakpoint.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of emuStudio. - * - * Copyright (C) 2006-2023 Peter Jakubčo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.emustudio.plugins.cpu.zilogZ80; - -public class Breakpoint extends Exception { -} diff --git a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java index 64e094992..ff5cb66f7 100644 --- a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java +++ b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java @@ -18,6 +18,7 @@ */ package net.emustudio.plugins.cpu.zilogZ80; +import net.emustudio.emulib.plugins.cpu.AccurateFrequencyRunner; import net.emustudio.emulib.plugins.cpu.CPU; import net.emustudio.emulib.plugins.cpu.CPU.RunState; import net.emustudio.emulib.plugins.memory.MemoryContext; @@ -32,9 +33,7 @@ import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; import static net.emustudio.plugins.cpu.zilogZ80.DispatchTables.*; import static net.emustudio.plugins.cpu.zilogZ80.EmulatorTables.*; @@ -62,7 +61,7 @@ public class EmulatorEngine implements CpuEngine { private final ContextZ80Impl context; private final MemoryContext memory; - private final AtomicLong executedCyclesPerSlot = new AtomicLong(0); + private final AccurateFrequencyRunner preciseRunner = new AccurateFrequencyRunner(); public final int[] regs = new int[8]; public final int[] regs2 = new int[8]; @@ -132,7 +131,7 @@ public void setDispatchListener(DispatchListener dispatchListener) { } public void addExecutedCyclesPerTimeSlice(long tstates) { - executedCyclesPerSlot.addAndGet(tstates); + preciseRunner.addExecutedCycles(tstates); } public void requestMaskableInterrupt(byte[] data) { @@ -174,57 +173,33 @@ CPU.RunState step() throws Exception { return currentRunState; } - @SuppressWarnings("BusyWait") public CPU.RunState run(CPU cpu) { // In Z80, 1 t-state = 250 ns = 0.25 microseconds = 0.00025 milliseconds // in 1 millisecond time slot = 1 / 0.00025 = 4000 t-states are executed uncontrollably - - final long slotNanos = SleepUtils.SLEEP_PRECISION; - final double slotMicros = slotNanos / 1000.0; - final int cyclesPerSlot = (int) (slotMicros * context.getCPUFrequency() / 1000.0); // frequency in kHZ -> MHz - currentRunState = CPU.RunState.STATE_RUNNING; - long delayNanos = SleepUtils.SLEEP_PRECISION; - - long emulationStartTime = System.nanoTime(); - executedCyclesPerSlot.set(0); - while (!Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (delayNanos > 0) { - Thread.sleep(TimeUnit.NANOSECONDS.toMillis(delayNanos)); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - long computationStartTime = System.nanoTime(); - long targetCycles = (computationStartTime - emulationStartTime) / slotNanos * cyclesPerSlot; - - while ((executedCyclesPerSlot.get() < targetCycles) && !Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) { - try { - if (cpu.isBreakpointSet(PC)) { - throw new Breakpoint(); + return preciseRunner.run( + () -> (double) context.getCPUFrequency(), + () -> { + try { + if (cpu.isBreakpointSet(PC)) { + currentRunState = CPU.RunState.STATE_STOPPED_BREAK; + } else { + dispatch(); + } + } catch (IndexOutOfBoundsException e) { + LOGGER.error("Unexpected error", e); + currentRunState = CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; + } catch (Throwable e) { + LOGGER.error("Unexpected error", e); + currentRunState = CPU.RunState.STATE_STOPPED_BAD_INSTR; } - dispatch(); - } catch (Breakpoint e) { - return CPU.RunState.STATE_STOPPED_BREAK; - } catch (IndexOutOfBoundsException e) { - LOGGER.error("Unexpected error", e); - return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT; - } catch (Throwable e) { - LOGGER.error("Unexpected error", e); - return CPU.RunState.STATE_STOPPED_BAD_INSTR; + return currentRunState; } - } - - long computationTime = System.nanoTime() - computationStartTime; - delayNanos = slotNanos - computationTime; - } - return currentRunState; + ); } private void advanceCycles(int cycles) { - executedCyclesPerSlot.addAndGet(cycles); + preciseRunner.addExecutedCycles(cycles); for (int i = 0; i < cycles; i++) { context.passedCycles(1); // make it precise to the bones }