Skip to content

Commit

Permalink
[#314] ZS spectrum: keyboard
Browse files Browse the repository at this point in the history
+ Improve CPU performance
  • Loading branch information
vbmacher committed Dec 28, 2023
1 parent 29069e5 commit 19022a4
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
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;

Expand Down Expand Up @@ -100,44 +100,50 @@ public CPU.RunState step() throws Exception {
return currentRunState;
}

@SuppressWarnings("BusyWait")
public CPU.RunState run(CPU cpu) {
long startTime, endTime;
int cyclesExecuted;
int checkTimeSlice = 100;
int cycles_to_execute = checkTimeSlice * context.getCPUFrequency();
int cycles;
long slice = checkTimeSlice * 1000000;
final long slotNanos = SleepUtils.SLEEP_PRECISION;
final double slotMicros = slotNanos / 1_000.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)) {
startTime = System.nanoTime();
cyclesExecuted = 0;
while ((cyclesExecuted < cycles_to_execute) && !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 {
cycles = dispatch();
cyclesExecuted += cycles;
int cycles = dispatch();
executedCyclesPerSlot += cycles;
context.passedCycles(cycles);
if (cpu.isBreakpointSet(PC)) {
throw new Breakpoint();
}
} catch (Breakpoint e) {
return CPU.RunState.STATE_STOPPED_BREAK;
} catch (IndexOutOfBoundsException e) {
LOGGER.debug("Unexpected error", e);
return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT;
} catch (IOException e) {
LOGGER.error("Unexpected error", e);
return CPU.RunState.STATE_STOPPED_BAD_INSTR;
return CPU.RunState.STATE_STOPPED_ADDR_FALLOUT;
} catch (Throwable e) {
LOGGER.debug("Unexpected error", e);
LOGGER.error("Unexpected error", e);
return CPU.RunState.STATE_STOPPED_BAD_INSTR;
}
}
endTime = System.nanoTime() - startTime;
if (endTime < slice) {
// time correction
SleepUtils.preciseSleepNanos(slice - endTime);
}

long computationTime = System.nanoTime() - endTime;
delayNanos = slotNanos - computationTime;
}
return currentRunState;
}
Expand Down Expand Up @@ -319,7 +325,7 @@ public int I_RLC() {
int temp = (regs[REG_A] & 0x80) >>> 7;

flags &= (~FLAG_C);
flags |= temp;
flags |= (short) temp;

regs[REG_A] = (regs[REG_A] << 1 | temp) & 0xFF;
return 4;
Expand All @@ -329,7 +335,7 @@ public int I_RRC() {
int temp = regs[REG_A] & 0x01;

flags &= (~FLAG_C);
flags |= temp;
flags |= (short) temp;

regs[REG_A] = ((regs[REG_A] >>> 1) | (temp << 7)) & 0xFF;
return 4;
Expand All @@ -352,7 +358,7 @@ public int I_RAR() {
regs[REG_A] |= 0x80;
}
flags &= (~FLAG_C);
flags |= newCarry;
flags |= (short) newCarry;

return 4;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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;

Expand Down Expand Up @@ -61,7 +62,7 @@ public class EmulatorEngine implements CpuEngine {

private final ContextZ80Impl context;
private final MemoryContext<Byte> memory;
private final AtomicLong cyclesExecutedPerTimeSlice = new AtomicLong(0);
private final AtomicLong executedCyclesPerSlot = new AtomicLong(0);

public final int[] regs = new int[8];
public final int[] regs2 = new int[8];
Expand Down Expand Up @@ -131,7 +132,7 @@ public void setDispatchListener(DispatchListener dispatchListener) {
}

public void addExecutedCyclesPerTimeSlice(long tstates) {
cyclesExecutedPerTimeSlice.addAndGet(tstates);
executedCyclesPerSlot.addAndGet(tstates);
}

public void requestMaskableInterrupt(byte[] data) {
Expand Down Expand Up @@ -173,22 +174,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 slice = 1 / 0.00025 = 4000 t-states are executed uncontrollably

long timeSliceNanos = SleepUtils.SLEEP_PRECISION;
double timeSliceMicros = timeSliceNanos / 1_000.0;
int cyclesPerTimeSlice = (int) (timeSliceMicros * context.getCPUFrequency() / 1000.0); // frequency in kHZ -> MHzq

//System.out.println("Time slice millis: " + timeSliceMillis);
//System.out.println("Cycles per time slice: " + cyclesPerTimeSlice);
final long slotNanos = SleepUtils.SLEEP_PRECISION;
final double slotMicros = slotNanos / 1_000.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();
executedCyclesPerSlot.set(0);
while (!Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) {
long startTime = System.nanoTime();
cyclesExecutedPerTimeSlice.set(0);
while ((cyclesExecutedPerTimeSlice.get() < cyclesPerTimeSlice) && !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.get() < targetCycles) && !Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) {
try {
dispatch();
if (cpu.isBreakpointSet(PC)) {
Expand All @@ -204,17 +216,15 @@ public CPU.RunState run(CPU cpu) {
return CPU.RunState.STATE_STOPPED_BAD_INSTR;
}
}
long endTime = System.nanoTime() - startTime;
if (endTime < timeSliceNanos) {
// time correction
SleepUtils.preciseSleepNanos(timeSliceNanos - endTime);
}

long computationTime = System.nanoTime() - endTime;
delayNanos = slotNanos - computationTime;
}
return currentRunState;
}

private void advanceCycles(int cycles) {
cyclesExecutedPerTimeSlice.addAndGet(cycles);
executedCyclesPerSlot.addAndGet(cycles);
for (int i = 0; i < cycles; i++) {
context.passedCycles(1); // make it precise to the bones
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import net.emustudio.emulib.runtime.settings.PluginSettings;
import net.emustudio.plugins.device.zxspectrum.bus.api.ZxSpectrumBus;
import net.emustudio.plugins.device.zxspectrum.ula.gui.Keyboard;
import net.emustudio.plugins.device.zxspectrum.ula.gui.TerminalWindow;
import net.emustudio.plugins.device.zxspectrum.ula.gui.DisplayWindow;

import javax.swing.*;
import java.util.MissingResourceException;
Expand All @@ -42,7 +42,7 @@ public class DeviceImpl extends AbstractDevice {
private boolean guiIOset = false;

private ULA ula;
private TerminalWindow gui;
private DisplayWindow gui;

public DeviceImpl(long pluginID, ApplicationApi applicationApi, PluginSettings settings) {
super(pluginID, applicationApi, settings);
Expand Down Expand Up @@ -87,7 +87,7 @@ public boolean isShowSettingsSupported() {
public void showGUI(JFrame parent) {
if (guiSupported) {
if (!guiIOset) {
this.gui = new TerminalWindow(parent, ula);
this.gui = new DisplayWindow(parent, ula, keyboard);
GuiUtils.addKeyListener(gui, keyboard);
guiIOset = true;
this.gui.setVisible(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener {
public final byte[][] attributeMemory = new byte[SCREEN_WIDTH][ATTRIBUTE_HEIGHT];
private final static int[] lineStartOffsets = computeLineStartOffsets();

private boolean hostControlDown = false;
private boolean hostShiftDown = false;

// maps host characters to ZX Spectrum key "commands" (keymap index, "zero" value)
private final static Map<Character, Byte[]> CHAR_MAPPING = new HashMap<>();

Expand Down Expand Up @@ -220,6 +217,8 @@ public byte read(int portAddress) {
// SPACE, SYM SHFT, M, N, B
result &= keymap[7];
}

// LINE IN?
if ((bus.readData() & 1) == 1) {
result |= 0x40;
}
Expand Down Expand Up @@ -248,14 +247,8 @@ public void onKeyUp(KeyEvent evt) {
int keyCode = evt.getExtendedKeyCode();

if (keyCode == KeyEvent.VK_CONTROL) {
hostControlDown = false;
} else if (evt.getKeyCode() == KeyEvent.VK_SHIFT) {
hostShiftDown = false;
}

if (hostControlDown) {
keymap[7] |= 0x2; // symshift
} else if (hostShiftDown) {
} else if (evt.getKeyCode() == KeyEvent.VK_SHIFT) {
keymap[0] |= 0x1; // shift
}

Expand All @@ -265,9 +258,6 @@ public void onKeyUp(KeyEvent evt) {
char c = (char) Character.toLowerCase(keyCode);
Byte[] command = CHAR_MAPPING.get(c);
if (command != null) {
if (command[2] == 1) {
keymap[7] |= 0x02; // symshift off
}
keymap[command[0]] |= command[1];
}
}
Expand All @@ -278,14 +268,8 @@ public void onKeyDown(KeyEvent evt) {
int keyCode = evt.getExtendedKeyCode();

if (keyCode == KeyEvent.VK_CONTROL) {
hostControlDown = true;
} else if (keyCode == KeyEvent.VK_SHIFT) {
hostShiftDown = true;
}

if (hostControlDown) {
keymap[7] &= (byte) 0xFD; // symshift
} else if (hostShiftDown) {
} else if (keyCode == KeyEvent.VK_SHIFT) {
keymap[0] &= (byte) 0xFE; // shift
}

Expand All @@ -295,9 +279,6 @@ public void onKeyDown(KeyEvent evt) {
char c = (char) Character.toLowerCase(keyCode);
Byte[] command = CHAR_MAPPING.get(c);
if (command != null) {
if (command[2] == 1) {
keymap[7] &= (byte) 0xFD; // symshift on
}
keymap[command[0]] &= (byte) ((~command[1]) & 0xFF);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import static net.emustudio.plugins.device.zxspectrum.ula.ULA.SCREEN_HEIGHT;
import static net.emustudio.plugins.device.zxspectrum.ula.ULA.SCREEN_WIDTH;
import static net.emustudio.plugins.device.zxspectrum.ula.gui.DisplayWindow.MARGIN;

// https://worldofspectrum.org/faq/reference/48kreference.htm

Expand Down Expand Up @@ -231,20 +232,25 @@ protected void paint() {
// Video RAM. This means that instead of keeping the image in the system memory with everything else,
// it is kept on the memory local to the graphics card. This allows for much faster drawing-to and
// copying-from operations.
do {
try {
do {
Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);

graphics.drawImage(
screenImage, 0, 0,
(int) (SCREEN_IMAGE_WIDTH * ZOOM), (int) (SCREEN_IMAGE_HEIGHT * ZOOM),
null);
graphics.dispose();
} while (strategy.contentsRestored());
strategy.show();
} while (strategy.contentsLost());
do {

Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);

graphics.drawImage(
screenImage, MARGIN, MARGIN,
(int) (SCREEN_IMAGE_WIDTH * ZOOM), (int) (SCREEN_IMAGE_HEIGHT * ZOOM), null);
graphics.dispose();

} while (strategy.contentsRestored());
strategy.show();
} while (strategy.contentsLost());
} catch (Exception ignored) {
repaint();
}
}
}
}
Loading

0 comments on commit 19022a4

Please sign in to comment.