Skip to content

Commit

Permalink
[#314] cassette-player,z80-cpu: remove tep
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed May 21, 2023
1 parent 87de500 commit cbac842
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public final class ContextZ80Impl extends AbstractCPUContext implements ContextZ
private final static Logger LOGGER = LoggerFactory.getLogger(ContextZ80Impl.class);

private final ConcurrentMap<Integer, CpuPortDevice> devices = new ConcurrentHashMap<>();
private final TimedEventsProcessor tep = new TimedEventsProcessor();

private volatile EmulatorEngine engine;
private volatile int clockFrequencyKHz = DEFAULT_FREQUENCY_KHZ;
Expand Down Expand Up @@ -122,16 +121,11 @@ public void addCycles(long tStates) {

@Override
public Optional<TimedEventsProcessor> getTimedEventsProcessor() {
return Optional.of(tep);
return Optional.empty();
}

@Override
public boolean passedCyclesSupported() {
return true;
}

public TimedEventsProcessor getTimedEventsProcessorNow() {
// bypassing optional
return tep;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import net.emustudio.emulib.plugins.cpu.CPU;
import net.emustudio.emulib.plugins.cpu.CPU.RunState;
import net.emustudio.emulib.plugins.cpu.TimedEventsProcessor;
import net.emustudio.emulib.plugins.memory.MemoryContext;
import net.emustudio.emulib.runtime.helpers.SleepUtils;
import net.emustudio.plugins.cpu.intel8080.api.CpuEngine;
Expand All @@ -30,11 +29,9 @@

import java.lang.invoke.MethodHandle;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

Expand Down Expand Up @@ -63,7 +60,6 @@ public class EmulatorEngine implements CpuEngine {
};

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

Expand Down Expand Up @@ -96,7 +92,6 @@ public class EmulatorEngine implements CpuEngine {
public EmulatorEngine(MemoryContext<Byte> memory, ContextZ80Impl context) {
this.memory = Objects.requireNonNull(memory);
this.context = Objects.requireNonNull(context);
this.tep = context.getTimedEventsProcessorNow();
LOGGER.info("Sleep precision: " + SleepUtils.SLEEP_PRECISION + " nanoseconds.");
}

Expand Down Expand Up @@ -220,8 +215,9 @@ public CPU.RunState run(CPU cpu) {

private void advanceCycles(int cycles) {
cyclesExecutedPerTimeSlice.addAndGet(cycles);
tep.advanceClock(cycles);
context.passedCycles(cycles);
for (int i = 0; i < cycles; i++) {
context.passedCycles(1); // make it precise to the bones
}
}

private void dispatch() throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import net.emustudio.emulib.plugins.annotations.PLUGIN_TYPE;
import net.emustudio.emulib.plugins.annotations.PluginRoot;
import net.emustudio.emulib.plugins.cpu.CPUContext;
import net.emustudio.emulib.plugins.cpu.TimedEventsProcessor;
import net.emustudio.emulib.plugins.device.AbstractDevice;
import net.emustudio.emulib.plugins.device.DeviceContext;
import net.emustudio.emulib.runtime.ApplicationApi;
Expand All @@ -35,7 +34,6 @@
import java.util.MissingResourceException;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Supplier;

@PluginRoot(type = PLUGIN_TYPE.DEVICE, title = "Cassette Player")
public class DeviceImpl extends AbstractDevice {
Expand All @@ -59,22 +57,19 @@ public void initialize() throws PluginInitializationException {
ContextPool contextPool = applicationApi.getContextPool();

// a cassette player needs a device to which it will write at its own pace
Supplier<TimedEventsProcessor> tep; // Line-in device initialization might be happening just now
DeviceContext<Byte> lineIn;
try {
CPUContext cpu = contextPool.getCPUContext(pluginID);
lineIn = contextPool.getDeviceContext(pluginID, DeviceContext.class);
DeviceContext<Byte> lineIn = contextPool.getDeviceContext(pluginID, DeviceContext.class);
if (lineIn.getDataType() != Byte.class) {
throw new PluginInitializationException("Could not find line-in device");
}
tep = cpu.getTimedEventsProcessor()::get;
this.cassetteListener = new TapePlaybackImpl(lineIn);
cpu.addPassedCyclesListener(this.cassetteListener);
} catch (PluginInitializationException ignored) {
ZxSpectrumBus bus = contextPool.getDeviceContext(pluginID, ZxSpectrumBus.class);
lineIn = bus;
tep = () -> bus.getTimedEventsProcessor().orElseThrow();
this.cassetteListener = new TapePlaybackImpl(bus);
bus.addPassedCyclesListener(this.cassetteListener);
}

this.cassetteListener = new TapePlaybackImpl(lineIn, tep);
this.controller = new CassetteController(cassetteListener);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@
*/
package net.emustudio.plugins.device.cassette_player;

import net.emustudio.emulib.plugins.cpu.TimedEventsProcessor;
import net.emustudio.emulib.plugins.cpu.CPUContext;
import net.emustudio.emulib.plugins.device.DeviceContext;
import net.emustudio.plugins.device.cassette_player.gui.CassettePlayerGui;
import net.emustudio.plugins.device.cassette_player.loaders.Loader;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

/**
* Tape playback.
Expand All @@ -49,7 +45,7 @@
* - <a href="https://softspectrum48.weebly.com/notes/tape-loading-routines">Tape loading routines</a>
* - <a href="https://sinclair.wiki.zxnet.co.uk/wiki/Spectrum_tape_interface">Spectrum tape interface</a>
*/
public class TapePlaybackImpl implements Loader.TapePlayback {
public class TapePlaybackImpl implements Loader.TapePlayback, CPUContext.PassedCyclesListener {
private final static int LEADER_PULSE_TSTATES = 2168;
private final static int SYNC1_PULSE_TSTATES = 667;
private final static int SYNC2_PULSE_TSTATES = 735;
Expand All @@ -62,17 +58,17 @@ public class TapePlaybackImpl implements Loader.TapePlayback {

private final DeviceContext<Byte> lineIn;
private final AtomicReference<CassettePlayerGui> gui = new AtomicReference<>();
private final Supplier<TimedEventsProcessor> tepSupplier;
private TimedEventsProcessor tep = null;

private final Map<Integer, Runnable> loaderSchedule = new HashMap<>();
private final NavigableMap<Integer, Runnable> loaderSchedule = new TreeMap<>();
private int currentTstates;
private boolean pulseUp;

private volatile boolean playing;
private int playingTstates;
private final CyclicBarrier barrier = new CyclicBarrier(2);

public TapePlaybackImpl(DeviceContext<Byte> lineIn, Supplier<TimedEventsProcessor> tepSupplier) {
public TapePlaybackImpl(DeviceContext<Byte> lineIn) {
this.lineIn = Objects.requireNonNull(lineIn);
this.tepSupplier = Objects.requireNonNull(tepSupplier);
}

public void setGui(CassettePlayerGui gui) {
Expand Down Expand Up @@ -157,10 +153,7 @@ public void onFileEnd() {
barrier.await();
} catch (InterruptedException e) {
// cancel playing
for (int cycles : loaderSchedule.keySet()) {
tep.removeAllScheduledOnce(cycles);
}

playing = false;
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
Expand Down Expand Up @@ -211,18 +204,29 @@ private void schedulePulse(int length, String msg) {
}

private void playPulses() {
if (tep == null) {
tep = tepSupplier.get();
}
loaderSchedule.put(currentTstates, () -> {
try {
barrier.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException ignored) {
playingTstates = 0;
playing = true;
}

@Override
public void passedCycles(long tstates) {
if (playing) {
playingTstates += tstates;
Map.Entry<Integer, Runnable> entry = loaderSchedule.floorEntry(playingTstates);
if (entry != null) {
loaderSchedule.remove(entry.getKey());
entry.getValue().run();
}
});
tep.scheduleOnceMultiple(loaderSchedule);
if (loaderSchedule.isEmpty()) {
playing = false;
try {
barrier.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException ignored) {

}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package net.emustudio.plugins.device.zxspectrum.bus;

import net.emustudio.emulib.plugins.cpu.CPUContext;
import net.emustudio.emulib.plugins.cpu.TimedEventsProcessor;
import net.emustudio.emulib.plugins.memory.AbstractMemoryContext;
import net.emustudio.emulib.plugins.memory.MemoryContext;
import net.emustudio.emulib.plugins.memory.annotations.MemoryContextAnnotations;
Expand All @@ -28,10 +27,7 @@
import net.emustudio.plugins.device.zxspectrum.bus.api.ZxSpectrumBus;
import net.jcip.annotations.NotThreadSafe;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;

/**
* ZX Spectrum bus (for 48K ZX spectrum).
Expand Down Expand Up @@ -119,6 +115,7 @@ public class ZxSpectrumBusImpl extends AbstractMemoryContext<Byte> implements Zx
private long contentionCycles;

private final Map<Integer, Context8080.CpuPortDevice> deferredAttachments = new HashMap<>();
private final Set<CPUContext.PassedCyclesListener> deferredListeners = new HashSet<>();

public void initialize(ContextZ80 cpu, MemoryContext<Byte> memory) {
this.cpu = Objects.requireNonNull(cpu);
Expand All @@ -129,6 +126,12 @@ public void initialize(ContextZ80 cpu, MemoryContext<Byte> memory) {
throw new RuntimeException("Could not attach device " + attachment.getValue().getName() + " to CPU");
}
}
for (CPUContext.PassedCyclesListener listener : deferredListeners) {
cpu.addPassedCyclesListener(listener);
}

deferredAttachments.clear();
deferredListeners.clear();
}


Expand All @@ -154,11 +157,6 @@ public void signalInterrupt(byte[] data) {
cpu.signalInterrupt(data);
}

@Override
public Optional<TimedEventsProcessor> getTimedEventsProcessor() {
return cpu.getTimedEventsProcessor();
}

@Override
public byte readMemoryNotContended(int location) {
return memory.read(location);
Expand All @@ -171,12 +169,20 @@ public void writeMemoryNotContended(int location, byte data) {

@Override
public void addPassedCyclesListener(CPUContext.PassedCyclesListener passedCyclesListener) {
cpu.addPassedCyclesListener(passedCyclesListener);
if (cpu == null) {
deferredListeners.add(passedCyclesListener);
} else {
cpu.addPassedCyclesListener(passedCyclesListener);
}
}

@Override
public void removePassedCyclesListener(CPUContext.PassedCyclesListener passedCyclesListener) {
cpu.removePassedCyclesListener(passedCyclesListener);
if (cpu == null) {
deferredListeners.remove(passedCyclesListener);
} else {
cpu.removePassedCyclesListener(passedCyclesListener);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,6 @@ public interface ZxSpectrumBus extends DeviceContext<Byte>, MemoryContext<Byte>
*/
void signalInterrupt(byte[] data);

/**
* For synchronizing with Z80 T-state cycles.
*
* @return CPU timed events processor if any
*/
Optional<TimedEventsProcessor> getTimedEventsProcessor();

/**
* Read data from memory, a non-contended variant.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ public class DisplayCanvas extends Canvas implements AutoCloseable, CPUContext.P
// border-lines; the others may be either border or vertical retrace.
//
//Then the 192 screen+border lines are displayed, followed by 56 border lines again. Note that this
// means that a frame is (64+192+56)*224=69888 T states long, which means that the '50 Hz' interrupt is actually a 3.5MHz/69888=50.08 Hz interrupt. This fact can be seen by taking a clock program, and running it for an hour, after which it will be the expected 6 seconds fast. However, on a real Spectrum, the frequency of the interrupt varies slightly as the Spectrum gets hot; the reason for this is unknown, but placing a cooler onto the ULA has been observed to remove this effect.
// means that a frame is (64+192+56)*224=69888 T states long, which means that the '50 Hz' interrupt is actually
// a 3.5MHz/69888=50.08 Hz interrupt. This fact can be seen by taking a clock program, and running it for an hour,
// after which it will be the expected 6 seconds fast. However, on a real Spectrum, the frequency of the interrupt
// varies slightly as the Spectrum gets hot; the reason for this is unknown, but placing a cooler onto the ULA has
// been observed to remove this effect.
private long frameCycleCounter = 0;
private long lineCycleCounter = 0;
private int lastLinePainted = 0;
Expand Down Expand Up @@ -92,7 +96,6 @@ public class DisplayCanvas extends Canvas implements AutoCloseable, CPUContext.P

private final ULA ula;
private final PaintCycle paintCycle = new PaintCycle();
private int interrupts = 0;

public DisplayCanvas(ULA ula) {
this.ula = Objects.requireNonNull(ula);
Expand All @@ -111,11 +114,8 @@ public void start() {
}

private void triggerCpuInterrupt() {
interrupts = (interrupts + 1) % 3; // 0 1 2
ula.triggerInterrupt();
if (interrupts == 0) {
paintCycle.run();
}
paintCycle.run();
}

private void drawNextLine(int line) {
Expand Down

0 comments on commit cbac842

Please sign in to comment.