Skip to content

Commit

Permalink
[#314] zxspectrum-bus: implement memory contention
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed Apr 30, 2023
1 parent 0eae970 commit 27ce6e3
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
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;
Expand Down Expand Up @@ -375,22 +374,22 @@ private int doInterrupt() throws Throwable {
return cycles;
}

private int getreg(int reg) {
private int getReg(int reg) {
if (reg == 6) {
return memory.read((regs[REG_H] << 8) | regs[REG_L]) & 0xFF;
}
return regs[reg];
}

private void putreg(int reg, int val) {
private void putReg(int reg, int val) {
if (reg == 6) {
memory.write((regs[REG_H] << 8) | regs[REG_L], (byte) (val & 0xFF));
} else {
regs[reg] = val & 0xFF;
}
}

private void putpair(int reg, int val) {
private void putPair(int reg, int val) {
int high = (val >>> 8) & 0xFF;
int low = val & 0xFF;
int index = reg * 2;
Expand All @@ -403,7 +402,7 @@ private void putpair(int reg, int val) {
}
}

private int getpair(int reg) {
private int getPair(int reg) {
if (reg == 3) {
return SP;
}
Expand Down Expand Up @@ -1908,7 +1907,7 @@ int I_OUT_REF_C_R(int reg) {


int I_SBC_HL_RP() {
int rp = getpair((lastOpcode >>> 4) & 0x03);
int rp = getPair((lastOpcode >>> 4) & 0x03);
int hl = ((regs[REG_H] << 8) | regs[REG_L]) & 0xFFFF;
memptr = (hl + 1) & 0xFFFF;
int res = hl - rp - (flags & FLAG_C);
Expand All @@ -1926,7 +1925,7 @@ int I_SBC_HL_RP() {
}

int I_ADC_HL_RP() {
int rp = getpair((lastOpcode >>> 4) & 0x03);
int rp = getPair((lastOpcode >>> 4) & 0x03);
int hl = (regs[REG_H] << 8 | regs[REG_L]) & 0xFFFF;
memptr = (hl + 1) & 0xFFFF;
int res = hl + rp + (flags & FLAG_C);
Expand Down Expand Up @@ -2616,7 +2615,7 @@ int I_LD_RP_REF_NN() {
PC = (PC + 2) & 0xFFFF;

int tmp1 = readWord(addr);
putpair((lastOpcode >>> 4) & 3, tmp1);
putPair((lastOpcode >>> 4) & 3, tmp1);
return 20;
}

Expand Down Expand Up @@ -3395,11 +3394,11 @@ int I_HALT() {

int I_RLC_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;

int flagC = ((regValue & 0x80) != 0) ? FLAG_C : 0;
regValue = ((regValue << 1) | (regValue >>> 7)) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3412,10 +3411,10 @@ int I_RLC_R() {

int I_RRC_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = regValue & FLAG_C;
regValue = ((regValue >>> 1) | (regValue << 7)) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3428,10 +3427,10 @@ int I_RRC_R() {

int I_RL_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = ((regValue & 0x80) == 0x80) ? FLAG_C : 0;
regValue = ((regValue << 1) | (flags & FLAG_C)) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3444,10 +3443,10 @@ int I_RL_R() {

int I_RR_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = regValue & FLAG_C;
regValue = ((regValue >>> 1) | (flags << 7)) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3460,11 +3459,11 @@ int I_RR_R() {

int I_SLA_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;

int flagC = ((regValue & 0x80) == 0x80) ? FLAG_C : 0;
regValue = (regValue << 1) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3477,10 +3476,10 @@ int I_SLA_R() {

int I_SRA_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = regValue & FLAG_C;
regValue = ((regValue >>> 1) | (regValue & 0x80)) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3493,10 +3492,10 @@ int I_SRA_R() {

int I_SLL_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = ((regValue & 0x80) != 0) ? FLAG_C : 0;
regValue = ((regValue << 1) | 0x01) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3509,10 +3508,10 @@ int I_SLL_R() {

int I_SRL_R() {
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int flagC = regValue & FLAG_C;
regValue = (regValue >>> 1) & 0xFF;
putreg(reg, regValue);
putReg(reg, regValue);
flags = TABLE_SZ[regValue] | PARITY_TABLE[regValue] | flagC | TABLE_XY[regValue];
Q = flags;

Expand All @@ -3526,7 +3525,7 @@ int I_SRL_R() {
int I_BIT_N_R() {
int bit = (lastOpcode >>> 3) & 7;
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
int result = (1 << bit) & regValue;

flags = ((result != 0) ? (result & FLAG_S) : (FLAG_Z | FLAG_PV))
Expand All @@ -3549,9 +3548,9 @@ int I_BIT_N_R() {
int I_RES_N_R() {
int bit = (lastOpcode >>> 3) & 7;
int reg = lastOpcode & 7;
int regValue = getreg(reg) & 0xFF;
int regValue = getReg(reg) & 0xFF;
regValue = (regValue & (~(1 << bit)));
putreg(reg, regValue);
putReg(reg, regValue);
if (reg == 6) {
return 15;
} else {
Expand All @@ -3562,9 +3561,9 @@ int I_RES_N_R() {
int I_SET_N_R() {
int tmp = (lastOpcode >>> 3) & 7;
int tmp2 = lastOpcode & 7;
int tmp1 = getreg(tmp2) & 0xFF;
int tmp1 = getReg(tmp2) & 0xFF;
tmp1 = (tmp1 | (1 << tmp));
putreg(tmp2, tmp1);
putReg(tmp2, tmp1);
if (tmp2 == 6) {
return 15;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@
import java.util.*;

/**
* ZX Spectrum bus.
* ZX Spectrum bus (for 48K ZX spectrum).
* <p>
* 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:
*
* https://sinclair.wiki.zxnet.co.uk/wiki/Contended_memory#Timing_differences
* On the 16K and 48K models of ZX Spectrum, the memory from 0x4000 to 0x7fff is contended. If the contended
* memory is accessed 14335[2] or 14336 tstates after an interrupt (see the timing differences section below
* for information on the 14335/14336 issue), the Z80 will be delayed for 6 tstates. After 14336 tstates,
* the delay is 5 tstates:
* <p>
* Cycle # Delay
* ------- -----
* 14335 6 (until 14341)
Expand All @@ -54,13 +58,20 @@
* 14348 1 ( " " )
* 14349 No delay
* 14350 No delay
* <p>
* This pattern (6,5,4,3,2,1,0,0) continues until 14463 tstates after interrupt, at which point there is no
* delay for 96 tstates while the border and horizontal refresh are drawn. The pattern starts again at 14559
* tstates and continues for all 192 lines of screen data. After this, there is no delay until the end of the
* frame as the bottom border and vertical refresh happen, and no delay until 14335 tstates after the start of
* the next frame as the top border is drawn.
*/
@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 boolean lastMemoryContended = false;

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

Expand All @@ -70,15 +81,23 @@ public void initialize(ContextZ80 cpu, MemoryContext<Byte> memory) {
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
if (!cpu.attachDevice(attachment.getKey(), attachment.getValue())) {
throw new RuntimeException("Could not attach device " + attachment.getValue().getName() + " to CPU");
}
}

// 17 x from 14335 to 14463, then 96 tstates pause to reach "end of line", then repeat.
// the t-state 14335 is the first screen line to be read.
int cycles = 14335;
for (int line = 0; line < 192; line++) {
scheduleMemoryContention(cycles);
cycles = cycles + 16 * 8 + 96;
}
}


@Override
public void attachDevice(int port, Context8080.CpuPortDevice device) {
if (cpu == null) {
Expand Down Expand Up @@ -128,25 +147,25 @@ public void writeData(Byte data) {

@Override
public Byte read(int location) {
// TODO: contention
setContended(location);
return memory.read(location);
}

@Override
public Byte[] read(int location, int count) {
// TODO: contention
setContended(location);
return memory.read(location, count);
}

@Override
public void write(int location, Byte data) {
// TODO: contention
setContended(location);
memory.write(location, data);
}

@Override
public void write(int location, Byte[] data, int count) {
// TODO: contention
setContended(location);
memory.write(location, data, count);
}

Expand Down Expand Up @@ -174,4 +193,20 @@ public int getSize() {
public MemoryContextAnnotations annotations() {
return memory.annotations();
}

private void setContended(int location) {
this.lastMemoryContended = location >= 0x4000 && location <= 0x7FFF;
}

private void scheduleMemoryContention(int startCycles) {
for (int i = 0; i < 17; i++) {
for (int j = 0; j < 6; j++) {
ted.schedule(startCycles + i * 8 + j, () -> {
if (lastMemoryContended) {
cpu.addCycles(1);
}
});
}
}
}
}

0 comments on commit 27ce6e3

Please sign in to comment.