The CpuZ80 class
This class represents the Z80 CPU of a Spectrum virtual machine. Using this class you can get and set the value of each register, register pair, flag, and other state holders. The class also provides a few contol methods and events that are raised at different stages of the CPU’s execution cycle.
Namespace: Spect.Net.SpectrumEmu.Scripting
Assembly: Spect.Net.SpectrumEmu
public sealed class CpuZ80
Register properties
The class has separate properties for all 8-bit registers and 16-bit register pairs of the CPU.
An 8-bit register’s value is represented with a System.Byte
, while a 16-bit register pair’s value
with a System.UInt16
instance.
8-bit registers
Name | Description |
---|---|
A |
Accummulator |
B |
Register B |
C |
Register C |
D |
Register D |
E |
Register E |
H |
Register H |
L |
Register L |
F |
Flags register |
I |
Interrupt Page Address Register |
R |
Memory Refresh Register |
XH |
Higher 8 bits of the IX index register |
XL |
Lower 8 bits of the IX index register |
YH |
Higher 8 bits of the IY index register |
YL |
Lower 8 bits of the IY index register |
WZh |
Higher 8 bits of the internal WZ register |
WZl |
Lower 8 bits of the internal WZ register |
16-bit register pairs
Name | Description |
---|---|
AF |
Register pair AF |
BC |
Register pair BC |
DE |
Register pair DE |
HL |
Register pair HL |
_AF_ |
Register pair AF’ |
_BC_ |
Register pair BC’ |
_DE_ |
Register pair DE’ |
_HL_ |
Register pair HL’ |
PC |
Program Counter Register |
SP |
Stack Pointer Register |
IR |
I/R 8-bit registers as a register pair |
IX |
Index register IX |
IY |
Index register IY |
WZ |
The internal WZ register |
Z80 CPU flags
With these properties, you can query the individual flags of the F register. These properties
retrieve a System.Boolean
value.
Name | Description |
---|---|
SFlag |
S (Sign) Flag |
ZFlag |
Z (Zero) Flag |
R5Flag |
R5 Flag, Bit 5 of the F register |
HFlag |
H (Half Carry) Flag |
R3Flag |
R3 Flag, Bit 3 of the F register |
PVFlag |
P/V (Parity/Overflow) Flag |
NFlag |
N (Add/Subtract) Flag |
CFlag |
C (Carry) Flag |
CPU state properties
There are various properties that indicate the internal state of the CPU
Tacts
public long Tacts { get; }
Gets the current T-state tacts of the CPU — the clock cycles since the CPU was powered on/reset last time
IFF1
public bool IFF1 { get; }
Interrupt Enable Flip-Flop #1. Disables interrupts from being accepted.
IFF2
public bool IFF2 { get; }
Interrupt Enable Flip-Flop #2. Temporary storage location for IFF1.
InterruptMode
public byte InterruptMode { get; }
Gets the current Interrupt mode (IM 0
, IM 1
, or IM 2
)
IsInterruptBlocked
public bool IsInterruptBlocked { get; }
Indicates that the CPU internally blocks the interrupts, even if the maskable interrupt is enabled. When the CPU is within processing a multi-byte statement, this flag is set.
IsInOpExecution
public bool IsInOpExecution { get; }
Indicates that the CPU still needs to read more bytes to decode a multi-byte operation.
MaskableInterruptModeEntered
public bool MaskableInterruptModeEntered { get; }
Indicates that the instructions the CPU is executing are the part of the maskable interrupt method.
Control methods
The CpuZ80
class provides a few control operation that you can use in the scripts.
Reset()
public void Reset()
Issues a Reset (RST
) signal to the CPU.
DisableInterrupt()
public void DisableInterrupt()
Disables the maskable interrupt.
EnableInterrupt()
public void EnableInterrupt()
Enables the maskable interrupt.
Operation tracking
The CpuZ80
class allows you to track the addresses the CPU executes an operation for.
public AddressTrackingState OperationTrackingState { get; }
The OperationTrackingState
property’s value is an AddressTrackingState
instance. It provides a bit for each memory address in the #0000
-#FFFF
range to check if the
particular byte in the memory has been read as a part of CPU’s M1 machine cycle.
public void ResetOperationTracking()
The ResetOperationTracking()
method resets the OperationTrackingState
property as if no
operations had been executed.
The following sampe demonstrates how you can use these members of CpuZ80
to check which instructions
have been executed. (This code snippet is an extract for a unit test of SpectNetIde.)
[TestMethod]
public async Task OperationTrackingWorks()
{
// --- Arrange
var sm = SpectrumVmFactory.CreateSpectrum48Pal();
sm.Breakpoints.AddBreakpoint(0x11CB);
sm.StartDebug();
await sm.CompletionTask;
var pcBefore = sm.Cpu.PC;
sm.ExecutionCompletionReason.ShouldBe(ExecutionCompletionReason.BreakpointReached);
sm.MachineState.ShouldBe(VmState.Paused);
// --- Act
sm.Cpu.ResetOperationTracking();
sm.Breakpoints.ClearAllBreakpoints();
sm.Breakpoints.AddBreakpoint(0x11CE);
sm.StartDebug();
await sm.CompletionTask;
var pcAfter = sm.Cpu.PC;
// --- Assert
pcBefore.ShouldBe((ushort)0x11CB);
pcAfter.ShouldBe((ushort)0x11CE);
sm.Cpu.OperationTrackingState.TouchedAny(0x0000, 0x11CA).ShouldBeFalse();
sm.Cpu.OperationTrackingState[0x11CB].ShouldBeTrue();
sm.Cpu.OperationTrackingState[0x11CC].ShouldBeTrue();
sm.Cpu.OperationTrackingState[0x11CD].ShouldBeTrue();
sm.Cpu.OperationTrackingState.TouchedAny(0x11CE, 0xFFFF).ShouldBeFalse();
}
Z80 CPU events
The CpuZ80
class provides about a dozen events that you can use in script.
InterruptExecuting
public event EventHandler InterruptExecuting
This event is raised just before a maskable interrupt is about to execute.
NmiExecuting
public event EventHandler NmiExecuting
This event is raised just before a non-maskable interrupt is about to execute.
MemoryReading
public event EventHandler<AddressEventArgs> MemoryReading
This event is raised just before the memory is being read. The event argument (AddressEventArgs
)
has a property, Address
that tells the memory address going to be read.
public ushort Address { get; }
When this operation is being executed, the contents of the memory has not been read yet.
MemoryRead
public event EventHandler<AddressAndDataEventArgs> MemoryRead
This event is raised right after the memory has been read. The event argument (AddressAndDataEventArgs
)
contains these properties:
public ushort Address { get; }
public byte Data { get; }
The Address
keeps the address read, Data
holds the data byte resulted from the read operation.
MemoryWriting
public event EventHandler<AddressAndDataEventArgs> MemoryWriting
This event is raised just before the memory is being written. The Address
and Data
properties of the
event argument (AddressAndDataEventArgs
) contain the memory address, and the data byte to write,
respectively.
MemoryWritten
public event EventHandler<AddressAndDataEventArgs> MemoryWritten
This event is raised just after the memory has been written. The Address
and Data
properties of the
event argument (AddressAndDataEventArgs
) contain the memory address, and the data byte written,
respectively.
PortReading
public event EventHandler<AddressEventArgs> PortReading
This event is raised just before a port is being read. The event argument (AddressEventArgs
)
has a property, Address
that tells the port address going to be read.
public ushort Address { get; }
When this operation is being executed, the port has not been read yet.
PortRead
public event EventHandler<AddressAndDataEventArgs> PortRead
This event is raised just after a port has been read. The event argument (AddressAndDataEventArgs
)
contains these properties:
public ushort Address { get; }
public byte Data { get; }
The Address
keeps the address of the port read, Data
holds the data byte resulted from the read operation.
PortWriting
public event EventHandler<AddressAndDataEventArgs> PortWriting
This event is raised just before a port is being written. The Address
and Data
properties of the
event argument (AddressAndDataEventArgs
) contain the port address, and the data byte to write,
respectively.
PortWritten
public event EventHandler<AddressAndDataEventArgs> PortWritten
This event is raised just after a port has been written. The Address
and Data
properties of the
event argument (AddressAndDataEventArgs
) contain the port address, and the data byte written,
respectively.
OperationExecuting
public event EventHandler<Z80InstructionExecutionEventArgs> OperationExecuting
This event is raised just before a Z80 operation is being executed. The event arguments
instance (Z80InstructionExecutionEventArgs
) provides
properties you can check the operation being executed.
When this event is raised, the CPU might not have read all operation code bytes, just those one that are enough to decode the type of operation to execute. If the operation contains arguments that are part of the operation code, those are not yet read.
For example when this event is signed, the opcode part for#80
LD A,#80
, or the #23 opcode part ofLD (IX+#23),C
operation is not read.
OperationExecuted
public event EventHandler<Z80InstructionExecutionEventArgs> OperationExecuted
This event is raised just after a Z80 operation has been executed. The event arguments
instance (Z80InstructionExecutionEventArgs
) provides
properties you can check the operation executed.
When this event is raised, the full operation is already read.
OperationExecuting and OperationExecuted sample
To demonstrate the difference between these events, the following sample code snippets (extracts from SpectNetIde unit tests) provide more details.
[TestMethod]
public async Task OperationExecutingIsInvoked()
{
// --- Arrange
var sm = SpectrumVmFactory.CreateSpectrum48Pal();
sm.CachedVmStateFolder = STATE_FOLDER;
await sm.StartAndRunToMain(true);
var events = new List<Z80InstructionExecutionEventArgs>();
// --- Act
var entryAddress = sm.InjectCode(@"
.org #8000
di; 8000: No prefix
bit 3,a; 8001: CB prefix
ld a,(ix+2); 8003: DD prefix
ld a,(iy+6); 8006: FD prefix
bit 2,(ix+2); 8009: DD CB prefixes
bit 2,(iy+6); 800D: FD CB prefixes
in d,(c); 8011: ED prefix
ret
");
sm.Cpu.OperationExecuting += (s, e) => { events.Add(e); };
sm.CallCode(entryAddress);
await sm.CompletionTask;
// --- Assert
var sampleIndex = events.Count - 1 - 7; // -1 for the RET operation, -7 for the 7 operations
var op = events[sampleIndex];
op.PcBefore.ShouldBe((ushort)0x8000);
op.Instruction.SequenceEqual(new byte[] { 0xF3 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0xF3);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 1];
op.PcBefore.ShouldBe((ushort)0x8001);
op.Instruction.SequenceEqual(new byte []{ 0xCB, 0x5F}).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x5F);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 2];
op.PcBefore.ShouldBe((ushort)0x8003);
op.Instruction.SequenceEqual(new byte[] { 0xDD, 0x7E }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x7E);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 3];
op.PcBefore.ShouldBe((ushort)0x8006);
op.Instruction.SequenceEqual(new byte[] { 0xFD, 0x7E }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x7E);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 4];
op.PcBefore.ShouldBe((ushort)0x8009);
op.Instruction.SequenceEqual(new byte[] { 0xDD, 0xCB, 0x56 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x56);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 5];
op.PcBefore.ShouldBe((ushort)0x800D);
op.Instruction.SequenceEqual(new byte[] { 0xFD, 0xCB, 0x56 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x56);
op.PcAfter.ShouldBeNull();
op = events[sampleIndex + 6];
op.PcBefore.ShouldBe((ushort)0x8011);
op.Instruction.SequenceEqual(new byte[] { 0xED, 0x50 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x50);
op.PcAfter.ShouldBeNull();
}
You can check that the Instruction
property is tested against the partial opcode.
[TestMethod]
public async Task OperationExecutedIsInvoked()
{
// --- Arrange
var sm = SpectrumVmFactory.CreateSpectrum48Pal();
sm.CachedVmStateFolder = STATE_FOLDER;
await sm.StartAndRunToMain(true);
var events = new List<Z80InstructionExecutionEventArgs>();
// --- Act
var entryAddress = sm.InjectCode(@"
.org #8000
di; 8000: No prefix
bit 3,a; 8001: CB prefix
ld a,(ix+2); 8003: DD prefix
ld a,(iy+6); 8006: FD prefix
bit 2,(ix+2); 8009: DD CB prefixes
bit 2,(iy+6); 800D: FD CB prefixes
in d,(c); 8011: ED prefix
ret
");
sm.Cpu.OperationExecuted += (s, e) => { events.Add(e); };
sm.CallCode(entryAddress);
await sm.CompletionTask;
// --- Assert
var sampleIndex = events.Count - 1 - 7;
// -1 for the RET operation, -7 for the 7 operations
var op = events[sampleIndex];
op.PcBefore.ShouldBe((ushort)0x8000);
op.Instruction.SequenceEqual(new byte[] { 0xF3 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0xF3);
op.PcAfter.ShouldBe((ushort)0x8001);
op = events[sampleIndex + 1];
op.PcBefore.ShouldBe((ushort)0x8001);
op.Instruction.SequenceEqual(new byte[] { 0xCB, 0x5F }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x5F);
op.PcAfter.ShouldBe((ushort)0x8003);
op = events[sampleIndex + 2];
op.PcBefore.ShouldBe((ushort)0x8003);
op.Instruction.SequenceEqual(new byte[] { 0xDD, 0x7E, 0x02}).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x7E);
op.PcAfter.ShouldBe((ushort)0x8006);
op = events[sampleIndex + 3];
op.PcBefore.ShouldBe((ushort)0x8006);
op.Instruction.SequenceEqual(new byte[] { 0xFD, 0x7E, 0x06 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x7E);
op.PcAfter.ShouldBe((ushort)0x8009);
op = events[sampleIndex + 4];
op.PcBefore.ShouldBe((ushort)0x8009);
op.Instruction.SequenceEqual(new byte[] { 0xDD, 0xCB, 0x56, 0x02 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x56);
op.PcAfter.ShouldBe((ushort)0x800D);
op = events[sampleIndex + 5];
op.PcBefore.ShouldBe((ushort)0x800D);
op.Instruction.SequenceEqual(new byte[] { 0xFD, 0xCB, 0x56, 0x06 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x56);
op.PcAfter.ShouldBe((ushort)0x8011);
op = events[sampleIndex + 6];
op.PcBefore.ShouldBe((ushort)0x8011);
op.Instruction.SequenceEqual(new byte[] { 0xED, 0x50 }).ShouldBeTrue();
op.OpCode.ShouldBe((byte)0x50);
op.PcAfter.ShouldBe((ushort)0x8013);
}
Here, the Instruction
property is checked against the entire opcode.
The AddressTrackingState class
This class represents tracking information regarding memory. The scripting engine
(SpectrumVm
and CpuZ80
classes) use this type to track
operation execution, memory reads and memory writes.
This class stores a flag for each memory address in the #0000
-#FFFF
range. A false value
means that the tracking event has not been signed for that particular address. The true value
says that the tracking event has happened.
For example, when you start the Spectrum virtual machine, after executing the first
instruction (DI
), the flag for #0000 is set to true, but all the others remains false.
Namespace: Spect.Net.SpectrumEmu.Scripting
Assembly: Spect.Net.SpectrumEmu
public sealed class AddressTrackingState
this[ushort]
public bool this[ushort address] { get; }
Gets the status bit of the specified address
.
TouchedAll(ushort, ushort)
public bool TouchedAll(ushort startAddr, ushort endAddr)
Checks if all tracking flag is set between startAddr
and endAddr
.
Both addresses are inclusive. Returns true, if all flag is set; otherwise, false.
TouchedAny(ushort, ushort)
public bool TouchedAny(ushort startAddr, ushort endAddr)
Checks if any tracking flag is set between startAddr
and endAddr
at all.
Both addresses are inclusive. Returns true, if any of the flags is set; otherwise, false.
The Z80InstructionExecutionEventArgs class
This class provides event arguments the the OperationExecuting
and OperationExecuted
events of the CpuZ80
class.
PcBefore
public ushort PcBefore { get; }
The value of PC before the execution.
Instruction
public IList<byte> Instruction { get; }
The opcode bytes available at the time of event. For example, if the operation is LD A,(IX+2)
,
this list contains #DD
and #7E
for the OperationExecuting
event, but #DD
, #7E
,
and #02
for the OperationExecuted
event.
OpCode
public byte OpCode { get; }
The main operation code. For example, if the operation is LD A,(IX+2)
, this value is #7E
from
the entire operation opcode (#DD
#7E
#02
).
PcAfter
public ushort? PcAfter { get; }
The value of PC after the operation has been executed. It is null during OerationExecuting
.