Skip to content

Commit

Permalink
[#314] z80-cpu: fix mode 1 interrupt
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed Apr 30, 2023
1 parent d220ffd commit 0eae970
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void setDispatchListener(DispatchListener dispatchListener) {
}

@Override
public long getAndResetExecutedCycles() {
public long getAndResetGlobalExecutedCycles() {
long tmpExecutedCycles = executedCycles;
executedCycles = 0;
return tmpExecutedCycles;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

public interface CpuEngine {

long getAndResetExecutedCycles();
long getAndResetGlobalExecutedCycles();

void fireFrequencyChanged(float newFrequency);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void run() {
synchronized (this) {
long endTime = System.nanoTime();
long time = endTime - startTimeSaved;
long executedCycles = cpu.getAndResetExecutedCycles();
long executedCycles = cpu.getAndResetGlobalExecutedCycles();

if (executedCycles > 0) {
frequency = (float) (executedCycles / (time / 1000000.0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class FrequencyUpdaterTest {
@Test
public void testFrequencyNotChanged() throws Exception {
CpuEngine cpuEngine = createMock(CpuEngine.class);
expect(cpuEngine.getAndResetExecutedCycles()).andReturn(0L);
expect(cpuEngine.getAndResetGlobalExecutedCycles()).andReturn(0L);
replay(cpuEngine);

new FrequencyUpdater(cpuEngine).run();
Expand All @@ -39,7 +39,7 @@ public void testFrequencyNotChanged() throws Exception {
@Test
public void testFrequencyChanged() throws Exception {
CpuEngine cpuEngine = createMock(CpuEngine.class);
expect(cpuEngine.getAndResetExecutedCycles()).andReturn(2000L);
expect(cpuEngine.getAndResetGlobalExecutedCycles()).andReturn(2000L);
cpuEngine.fireFrequencyChanged(anyFloat());
expectLastCall().once();
replay(cpuEngine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public void signalNonMaskableInterrupt() {
engine.requestNonMaskableInterrupt();
}

@Override
public void addCycles(int tStates) {
engine.addExecutedCyclesPerTimeSlice(tStates);
}

@Override
public Optional<TimedEventsProcessor> getTimedEventsProcessor() {
return Optional.of(tep);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static net.emustudio.plugins.cpu.zilogZ80.DispatchTables.*;
Expand Down Expand Up @@ -67,7 +69,8 @@ public class EmulatorEngine implements CpuEngine {
private final TimedEventsProcessor tep;
private final MemoryContext<Byte> memory;
private final List<FrequencyChangedListener> frequencyChangedListeners = new CopyOnWriteArrayList<>();
private final AtomicLong executedCycles = new AtomicLong(0);
private final AtomicLong cyclesExecutedGlobal = new AtomicLong(0);
private final AtomicInteger cyclesExecutedPerTimeSlice = new AtomicInteger(0);

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

@Override
public long getAndResetExecutedCycles() {
return executedCycles.getAndSet(0);
public long getAndResetGlobalExecutedCycles() {
return cyclesExecutedGlobal.getAndSet(0);
}

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

public void addFrequencyChangedListener(FrequencyChangedListener listener) {
Expand Down Expand Up @@ -193,26 +200,22 @@ CPU.RunState step() throws Exception {
}

public CPU.RunState run(CPU cpu) {
long startTime, endTime;
int cycles_executed;
// In Z80, 1 t-state = 250 ns = 0.25 microseconds = 0.00025 milliseconds
// in 10 milliseconds = 10 / 0.00025 = 40000 t-states are executed uncontrollably
// in 1 millisecond = 1 / 0.00025 = 4000 t-states :(
// in 1 millisecond time slice = 1 / 0.00025 = 4000 t-states are executed uncontrollably

int checkTimeSlice = (int) Math.ceil(SleepUtils.SLEEP_PRECISION / 1000000.0); // milliseconds
int cycles_to_execute = checkTimeSlice * context.getCPUFrequency();
int cycles;
long slice = checkTimeSlice * 1000000L; // nanoseconds
long timeSliceNanos = SleepUtils.SLEEP_PRECISION;
int timeSliceMillis = (int) Math.ceil(timeSliceNanos / 1000000.0);
int cyclesPerTimeSlice = timeSliceMillis * context.getCPUFrequency();

currentRunState = CPU.RunState.STATE_RUNNING;
while (!Thread.currentThread().isInterrupted() && (currentRunState == CPU.RunState.STATE_RUNNING)) {
startTime = System.nanoTime();
cycles_executed = 0;
while ((cycles_executed < cycles_to_execute) && !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 {
cycles = dispatch();
cycles_executed += cycles;
executedCycles.addAndGet(cycles);
int cycles = dispatch();
cyclesExecutedPerTimeSlice.addAndGet(cycles);
cyclesExecutedGlobal.addAndGet(cycles);
tep.advanceClock(cycles);
if (cpu.isBreakpointSet(PC)) {
throw new Breakpoint();
Expand All @@ -227,10 +230,10 @@ public CPU.RunState run(CPU cpu) {
return CPU.RunState.STATE_STOPPED_BAD_INSTR;
}
}
endTime = System.nanoTime() - startTime;
if (endTime < slice) {
long endTime = System.nanoTime() - startTime;
if (endTime < timeSliceNanos) {
// time correction
SleepUtils.preciseSleepNanos(slice - endTime);
SleepUtils.preciseSleepNanos(timeSliceNanos - endTime);
}
}
return currentRunState;
Expand Down Expand Up @@ -344,7 +347,12 @@ private int doInterrupt() throws Throwable {
break;
case 1:
cycles += 12;
writeWord((SP - 2) & 0xFFFF, PC);
if (memory.read(PC) == 0x76) {
// jump over HALT
writeWord((SP - 2) & 0xFFFF, (PC + 1) & 0xFFFF);
} else {
writeWord((SP - 2) & 0xFFFF, PC);
}
SP = (SP - 2) & 0xffff;
PC = 0x38;
memptr = PC;
Expand Down Expand Up @@ -1866,24 +1874,31 @@ int I_IN_R_REF_C(int reg) {
int I_OUT_REF_C_B() {
return I_OUT_REF_C_R(regs[REG_B]);
}

int I_OUT_REF_C_C() {
return I_OUT_REF_C_R(regs[REG_C]);
}

int I_OUT_REF_C_D() {
return I_OUT_REF_C_R(regs[REG_D]);
}

int I_OUT_REF_C_E() {
return I_OUT_REF_C_R(regs[REG_E]);
}

int I_OUT_REF_C_H() {
return I_OUT_REF_C_R(regs[REG_H]);
}

int I_OUT_REF_C_L() {
return I_OUT_REF_C_R(regs[REG_L]);
}

int I_OUT_REF_C_A() {
return I_OUT_REF_C_R(regs[REG_A]);
}

int I_OUT_REF_C_R(int reg) {
memptr = (regs[REG_B] << 8) | regs[REG_C];
context.writeIO(memptr, (byte) reg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ public interface ContextZ80 extends Context8080 {
* at address 0066h. Routines should exit with RETN instruction.
*/
void signalNonMaskableInterrupt();

/**
* Explicitly adds machine cycles (slows down CPU).
* <p>
* Used primarily in contention implementation.
*
* @param tStates number of t-states (machine cycles) to add
*/
void addCycles(int tStates);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package net.emustudio.plugins.device.zxspectrum.bus;

import net.emustudio.emulib.plugins.annotations.PluginContext;
import net.emustudio.emulib.plugins.cpu.TimedEventsProcessor;
import net.emustudio.emulib.plugins.memory.AbstractMemoryContext;
import net.emustudio.emulib.plugins.memory.MemoryContext;
Expand All @@ -28,26 +27,49 @@
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.*;

/**
* Also a memory proxy
* ZX Spectrum bus.
* Adds memory & I/O port contention.
* <p>
* At cycle 14335 (just one cycle before the top left corner is reached) the delay is 6 cycles.
* At cycle 14336 the delay is 5 cycles, and so on according to the following table:
*
* Cycle # Delay
* ------- -----
* 14335 6 (until 14341)
* 14336 5 ( " " )
* 14337 4 ( " " )
* 14338 3 ( " " )
* 14339 2 ( " " )
* 14340 1 ( " " )
* 14341 No delay
* 14342 No delay
* 14343 6 (until 14349)
* 14344 5 ( " " )
* 14345 4 ( " " )
* 14346 3 ( " " )
* 14347 2 ( " " )
* 14348 1 ( " " )
* 14349 No delay
* 14350 No delay
*/
@NotThreadSafe
public class ZxSpectrumBusImpl extends AbstractMemoryContext<Byte> implements ZxSpectrumBus {
private ContextZ80 cpu;
private MemoryContext<Byte> memory;
private volatile byte busData; // data on the bus
private TimedEventsProcessor ted;

private final Map<Integer, Context8080.CpuPortDevice> deferredAttachments = new HashMap<>();


public void initialize(ContextZ80 cpu, MemoryContext<Byte> memory) {
this.cpu = Objects.requireNonNull(cpu);
this.memory = Objects.requireNonNull(memory);
this.ted = cpu.getTimedEventsProcessor().orElseThrow(() -> new RuntimeException("CPU must provide TimedEventProcessor"));


for (Map.Entry<Integer, Context8080.CpuPortDevice> attachment : deferredAttachments.entrySet()) {
// TODO: contended device proxy if needed
Expand Down

0 comments on commit 0eae970

Please sign in to comment.