diff --git a/Docs/Interrupts.md b/Docs/Interrupts.md
index 8e77fd2..1cab414 100644
--- a/Docs/Interrupts.md
+++ b/Docs/Interrupts.md
@@ -7,3 +7,21 @@ Non-maskable interrupts are triggered by the interrupt source by firing its `Nmi
Maskable interrupts are handled by the processor in the following way: after an instruction execution is finished, and if maskable interrupts are enabled, the list of registered interrupts sources is scanned for instances that have the `IntLineIsActive` property set to _true_. If at least one match is found, the interrupt is serviced the standard way (depends on the current interrupt mode). For interrupt mode 2 the `ValueOnDataBus` of the interrupt source is used as well, so it should have a meaningful value.
The `HALT` instruction behaves the expected way: after this instruction is executed the processor will enter a loop in which `NOP` instructions are executed (PC is not incremented) until an interrupt is received; note however that if `AutoSopOnDiPlusHalt` is _true_ a `HALT` instruction may cause the execution to stop (depends on the processor [configuration](Configuration.md)).
+
+### Interrupt events
+
+[IZ80Processor](../Main/IZ80Processor.cs) provides the following interrupt related events:
+
+* _`NonMaskableInterruptServicingStart`_ is fired right before a maskable interrupt is going to be serviced. The execution state is as follows when the event is fired:
+
+ * For interrupt mode 0: the opcode has been already fetched from the data bus and is about to be executed.
+
+ * For interrupt mode 1: PC is already set to 0x0038 and the return address has been pushed to the stack.
+
+ * For interrupt mode 2: PC is already set to the address of the routine to execute and the return address has been pushed to the stack.
+
+* _`MaskableInterruptServicingStart`_ is fired right before a non-maskable interrupt is going to be serviced. PC is already set to 0x0066 and the return address has been pushed to the stack when this event is fired.
+
+* _`BeforeRetiInstructionExecution`_ and _`BeforeRetnInstructionExecution`_ are fired before a RETI/RETN instruction is about to be executed, right after the corresponding _`BeforeInstructionExecution`_ event.
+
+* _`AfterRetiInstructionExecution`_ and _`AfterRetnInstructionExecution`_ are fired after a RETI/RETN instruction is about to be executed, right after the corresponding _`AfterInstructionExecution`_ event.
diff --git a/Main.Tests/InterruptSourceForTests.cs b/Main.Tests/InterruptSourceForTests.cs
index 759652d..2280804 100644
--- a/Main.Tests/InterruptSourceForTests.cs
+++ b/Main.Tests/InterruptSourceForTests.cs
@@ -6,8 +6,7 @@ public class InterruptSourceForTests : IZ80InterruptSource
{
public void FireNmi()
{
- if(NmiInterruptPulse != null)
- NmiInterruptPulse(this, EventArgs.Empty);
+ NmiInterruptPulse?.Invoke(this, EventArgs.Empty);
}
public event EventHandler NmiInterruptPulse;
diff --git a/Main.Tests/Z80ProcessorTests_InstructionExecution.cs b/Main.Tests/Z80ProcessorTests_InstructionExecution.cs
index 1caa31c..589c45f 100644
--- a/Main.Tests/Z80ProcessorTests_InstructionExecution.cs
+++ b/Main.Tests/Z80ProcessorTests_InstructionExecution.cs
@@ -372,6 +372,138 @@ public void Does_not_auto_stops_when_RET_is_found_with_stack_not_equal_to_initia
#region Before and after instruction execution events
+ [Test]
+ [TestCase(0x4D)]
+ [TestCase(0x5D)]
+ [TestCase(0x6D)]
+ [TestCase(0x7D)]
+ public void Fires_before_and_after_reti_instruction_execution_in_proper_order(byte opcode)
+ {
+ var beforeExecutionEventRaised = false;
+ var afterExecutionEventRaised = false;
+ var beforeRetiExecutionEventRaised = false;
+ var afterRetiExecutionEventRaised = false;
+
+ var instructionBytes = new byte[]
+ {
+ 0xED, opcode
+ };
+ Sut.Memory.SetContents(0, instructionBytes);
+
+ Sut.BeforeInstructionExecution += (sender, e) =>
+ {
+ Assert.IsFalse(beforeExecutionEventRaised);
+ Assert.IsFalse(beforeRetiExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetiExecutionEventRaised);
+
+ beforeExecutionEventRaised = true;
+ };
+
+ Sut.BeforeRetiInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsFalse(beforeRetiExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetiExecutionEventRaised);
+
+ beforeRetiExecutionEventRaised = true;
+ };
+
+ Sut.AfterInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetiExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetiExecutionEventRaised);
+
+ afterExecutionEventRaised = true;
+ };
+
+ Sut.AfterRetiInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetiExecutionEventRaised);
+ Assert.IsTrue(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetiExecutionEventRaised);
+
+ afterRetiExecutionEventRaised = true;
+ };
+
+ Sut.ExecuteNextInstruction();
+
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetiExecutionEventRaised);
+ Assert.IsTrue(afterExecutionEventRaised);
+ Assert.IsTrue(afterRetiExecutionEventRaised);
+ }
+
+ [Test]
+ [TestCase(0x45)]
+ [TestCase(0x55)]
+ [TestCase(0x65)]
+ [TestCase(0x75)]
+ public void Fires_before_and_after_retn_instruction_execution_in_proper_order(byte opcode)
+ {
+ var beforeExecutionEventRaised = false;
+ var afterExecutionEventRaised = false;
+ var beforeRetnExecutionEventRaised = false;
+ var afterRetnExecutionEventRaised = false;
+
+ var instructionBytes = new byte[]
+ {
+ 0xED, opcode
+ };
+ Sut.Memory.SetContents(0, instructionBytes);
+
+ Sut.BeforeInstructionExecution += (sender, e) =>
+ {
+ Assert.IsFalse(beforeExecutionEventRaised);
+ Assert.IsFalse(beforeRetnExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetnExecutionEventRaised);
+
+ beforeExecutionEventRaised = true;
+ };
+
+ Sut.BeforeRetnInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsFalse(beforeRetnExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetnExecutionEventRaised);
+
+ beforeRetnExecutionEventRaised = true;
+ };
+
+ Sut.AfterInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetnExecutionEventRaised);
+ Assert.IsFalse(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetnExecutionEventRaised);
+
+ afterExecutionEventRaised = true;
+ };
+
+ Sut.AfterRetnInstructionExecution += (sender, e) =>
+ {
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetnExecutionEventRaised);
+ Assert.IsTrue(afterExecutionEventRaised);
+ Assert.IsFalse(afterRetnExecutionEventRaised);
+
+ afterRetnExecutionEventRaised = true;
+ };
+
+ Sut.ExecuteNextInstruction();
+
+ Assert.IsTrue(beforeExecutionEventRaised);
+ Assert.IsTrue(beforeRetnExecutionEventRaised);
+ Assert.IsTrue(afterExecutionEventRaised);
+ Assert.IsTrue(afterRetnExecutionEventRaised);
+ }
+
[Test]
public void Fires_before_and_after_instruction_execution_with_proper_opcodes_and_local_state()
{
@@ -833,8 +965,10 @@ public int Execute(byte firstOpcodeByte)
else
TimesEachInstructionIsExecuted[firstOpcodeByte] = 1;
- if(ExtraBeforeFetchCode != null)
- ExtraBeforeFetchCode(firstOpcodeByte);
+ if (firstOpcodeByte == 0xED)
+ ProcessorAgent.FetchNextOpcode();
+
+ ExtraBeforeFetchCode?.Invoke(firstOpcodeByte);
InstructionFetchFinished(this,
new InstructionFetchFinishedEventArgs()
@@ -844,10 +978,9 @@ public int Execute(byte firstOpcodeByte)
IsHaltInstruction = (firstOpcodeByte == HALT_opcode)
});
- if(ExtraAfterFetchCode != null)
- ExtraAfterFetchCode(firstOpcodeByte);
+ ExtraAfterFetchCode?.Invoke(firstOpcodeByte);
- if(TStatesReturner == null)
+ if (TStatesReturner == null)
return 0;
else
return TStatesReturner(firstOpcodeByte);
diff --git a/Main.Tests/Z80ProcessorTests_Interrupts.cs b/Main.Tests/Z80ProcessorTests_Interrupts.cs
index 1e8bc98..19d1b82 100644
--- a/Main.Tests/Z80ProcessorTests_Interrupts.cs
+++ b/Main.Tests/Z80ProcessorTests_Interrupts.cs
@@ -13,6 +13,9 @@ public class Z80ProcessorTests_Interrupts
private const byte HALT_opcode = 0x76;
private const byte NOP_opcode = 0x00;
private const byte RST20h_opcode = 0xE7;
+ private const byte IM0_opcode = 0x46;
+ private const byte IM1_opcode = 0x56;
+ private const byte IM2_opcode = 0x5E;
Z80ProcessorForTests Sut { get; set; }
Fixture Fixture { get; set; }
@@ -463,5 +466,58 @@ public void Halted_processor_awakes_on_interrupt(bool isNmi)
}
#endregion
+
+ #region Interrupt servicing start events
+
+ [Test]
+ public void Fires_NonMaskableInterruptServicingStart()
+ {
+ Sut.RegisterInterruptSource(InterruptSource1);
+
+ Sut.Memory[0] = NOP_opcode;
+
+ var nonMaskableInterruptServicingStartFired = false;
+ Sut.NonMaskableInterruptServicingStart += (server, args) =>
+ {
+ nonMaskableInterruptServicingStartFired = true;
+ };
+
+ InterruptSource1.FireNmi();
+
+ Sut.ExecuteNextInstruction();
+
+ Assert.True(nonMaskableInterruptServicingStartFired);
+ }
+
+ [Test]
+ [TestCase(IM0_opcode)]
+ [TestCase(IM1_opcode)]
+ [TestCase(IM2_opcode)]
+ public void Fires_MaskableInterruptServicingStart(byte imOpcode)
+ {
+ Sut.RegisterInterruptSource(InterruptSource1);
+
+ Sut.Memory[0] = 0xED;
+ Sut.Memory[1] = imOpcode;
+ Sut.Memory[2] = EI_opcode;
+ Sut.Memory[3] = NOP_opcode;
+
+ Sut.ExecuteNextInstruction(); //This sets interrupt mode
+ Sut.ExecuteNextInstruction(); //This runs EI
+
+ var maskableInterruptServicingStartFired = false;
+ Sut.MaskableInterruptServicingStart += (server, args) =>
+ {
+ maskableInterruptServicingStartFired = true;
+ };
+
+ InterruptSource1.IntLineIsActive = true;
+
+ Sut.ExecuteNextInstruction();
+
+ Assert.True(maskableInterruptServicingStartFired);
+ }
+
+ #endregion
}
}
diff --git a/Main/Enums/InterruptType.cs b/Main/Enums/InterruptType.cs
new file mode 100644
index 0000000..05d1f7e
--- /dev/null
+++ b/Main/Enums/InterruptType.cs
@@ -0,0 +1,18 @@
+namespace Konamiman.Z80dotNet.Enums
+{
+ ///
+ /// Defines the type of a Z80 interrupt
+ ///
+ public enum InterruptType
+ {
+ ///
+ /// Maskable interrupt
+ ///
+ Maskable = 1,
+
+ ///
+ /// Non-maskable interrupt
+ ///
+ NonMaskable
+ }
+}
\ No newline at end of file
diff --git a/Main/IZ80Processor.cs b/Main/IZ80Processor.cs
index 7ffa76d..2ffe980 100644
--- a/Main/IZ80Processor.cs
+++ b/Main/IZ80Processor.cs
@@ -395,6 +395,48 @@ public interface IZ80Processor
///
event EventHandler AfterInstructionExecution;
+ ///
+ /// Triggered when a maskable interrupt is about to be serviced.
+ ///
+ /// For IM 0: The opcode has been already fetched from the data bus and is about to be executed.
+ ///
+ /// For IM 1: PC is already set to 0x0038 and the return address has been pushed to the stack.
+ ///
+ /// For IM 2: PC is already set to the address of the routine to execute and the return address has been pushed to the stack.
+ ///
+ event EventHandler MaskableInterruptServicingStart;
+
+ ///
+ /// Triggered when a non-maskable interrupt is about to be serviced.
+ /// PC is already set to 0x0066 and the return address has been pushed to the stack
+ /// when this event is invoked.
+ ///
+ event EventHandler NonMaskableInterruptServicingStart;
+
+ ///
+ /// Triggered before a RETI instruction is about to be executed,
+ /// right after the corresponding BeforeInstructionExecution event
+ ///
+ event EventHandler BeforeRetiInstructionExecution;
+
+ ///
+ /// Triggered after a RETI instruction has been executed,
+ /// right after the corresponding AfterInstructionExecution event
+ ///
+ event EventHandler AfterRetiInstructionExecution;
+
+ ///
+ /// Triggered before a RETN instruction is about to be executed,
+ /// right after the corresponding BeforeInstructionExecution event
+ ///
+ event EventHandler BeforeRetnInstructionExecution;
+
+ ///
+ /// Triggered after a RETN instruction has been executed,
+ /// right after the corresponding AfterInstructionExecution event
+ ///
+ event EventHandler AfterRetnInstructionExecution;
+
#endregion
#region Utils
diff --git a/Main/Main.csproj b/Main/Main.csproj
index 0ae15ff..d2e188a 100644
--- a/Main/Main.csproj
+++ b/Main/Main.csproj
@@ -37,6 +37,7 @@
+
diff --git a/Main/Properties/AssemblyInfo.cs b/Main/Properties/AssemblyInfo.cs
index b2573d7..80df78c 100644
--- a/Main/Properties/AssemblyInfo.cs
+++ b/Main/Properties/AssemblyInfo.cs
@@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.6.0")]
-[assembly: AssemblyFileVersion("1.0.6.0")]
+[assembly: AssemblyVersion("1.0.7.0")]
+[assembly: AssemblyFileVersion("1.0.7.0")]
diff --git a/Main/Z80Processor.cs b/Main/Z80Processor.cs
index 1a5faf7..e8e7d39 100644
--- a/Main/Z80Processor.cs
+++ b/Main/Z80Processor.cs
@@ -1,4 +1,5 @@
-using System;
+using Konamiman.Z80dotNet.Enums;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,6 +19,9 @@ public class Z80Processor : IZ80Processor, IZ80ProcessorAgent
private const ushort NmiServiceRoutine = 0x66;
private const byte NOP_opcode = 0x00;
private const byte RST38h_opcode = 0xFF;
+ private const byte RETI_RETN_prefix = 0xED;
+ private const byte RETI_opcode = 0x4D;
+ private const byte RETN_opcode = 0x45;
public Z80Processor()
{
@@ -161,6 +165,7 @@ private int AcceptPendingInterrupt()
IsHalted = false;
Registers.IFF1 = 0;
ExecuteCall(NmiServiceRoutine);
+ TriggerInterruptEvent(InterruptType.NonMaskable);
return 11;
}
@@ -178,10 +183,12 @@ private int AcceptPendingInterrupt()
switch(InterruptMode) {
case 0:
var opcode = activeIntSource.ValueOnDataBus.GetValueOrDefault(0xFF);
+ TriggerInterruptEvent(InterruptType.Maskable);
InstructionExecutor.Execute(opcode);
return 13;
case 1:
InstructionExecutor.Execute(RST38h_opcode);
+ TriggerInterruptEvent(InterruptType.Maskable);
return 13;
case 2:
var pointerAddress = NumberUtils.CreateUshort(
@@ -191,6 +198,7 @@ private int AcceptPendingInterrupt()
lowByte: ReadFromMemoryInternal(pointerAddress),
highByte: ReadFromMemoryInternal((ushort)(pointerAddress + 1)));
ExecuteCall(callAddress);
+ TriggerInterruptEvent(InterruptType.Maskable);
return 19;
}
@@ -209,6 +217,23 @@ public void ExecuteCall(ushort address)
Registers.PC = address;
}
+ private void TriggerInterruptEvent(InterruptType interruptType)
+ {
+ switch (interruptType)
+ {
+ case InterruptType.Maskable:
+ MaskableInterruptServicingStart?.Invoke(this, EventArgs.Empty);
+ break;
+
+ case InterruptType.NonMaskable:
+ NonMaskableInterruptServicingStart?.Invoke(this, EventArgs.Empty);
+ break;
+
+ default:
+ throw new InvalidOperationException($"Unknown interrupt type: {interruptType}");
+ }
+ }
+
public void ExecuteRet()
{
var sp = (ushort)Registers.SP;
@@ -261,11 +286,22 @@ private bool InterruptsEnabled
void FireAfterInstructionExecutionEvent(int tStates)
{
+ var opcodeBytes = executionContext.OpcodeBytes.ToArray();
+
AfterInstructionExecution?.Invoke(this, new AfterInstructionExecutionEventArgs(
- executionContext.OpcodeBytes.ToArray(),
+ opcodeBytes,
stopper: this,
localUserState: executionContext.LocalUserStateFromPreviousEvent,
tStates: tStates));
+
+ if (opcodeBytes[0] == RETI_RETN_prefix)
+ {
+ opcodeBytes[1] &= 0xCF; //To account for mirrored variants
+ if (opcodeBytes[1] == RETI_opcode)
+ AfterRetiInstructionExecution?.Invoke(this, EventArgs.Empty);
+ else if (opcodeBytes[1] == RETN_opcode)
+ AfterRetnInstructionExecution?.Invoke(this, EventArgs.Empty);
+ }
}
void InstructionExecutor_InstructionFetchFinished(object sender, InstructionFetchFinishedEventArgs e)
@@ -305,12 +341,22 @@ void FireBeforeInstructionFetchEvent()
BeforeInstructionExecutionEventArgs FireBeforeInstructionExecutionEvent()
{
+ var opcodeBytes = executionContext.OpcodeBytes.ToArray();
+
var eventArgs = new BeforeInstructionExecutionEventArgs(
- executionContext.OpcodeBytes.ToArray(),
+ opcodeBytes,
executionContext.LocalUserStateFromPreviousEvent);
- if(BeforeInstructionExecution != null)
- BeforeInstructionExecution(this, eventArgs);
+ BeforeInstructionExecution?.Invoke(this, eventArgs);
+
+ if (opcodeBytes[0] == RETI_RETN_prefix)
+ {
+ opcodeBytes[1] &= 0xCF; //To account for mirrored variants
+ if(opcodeBytes[1] == RETI_opcode)
+ BeforeRetiInstructionExecution?.Invoke(this, EventArgs.Empty);
+ else if (opcodeBytes[1] == RETN_opcode)
+ BeforeRetnInstructionExecution?.Invoke(this, EventArgs.Empty);
+ }
return eventArgs;
}
@@ -638,6 +684,18 @@ public IClockSynchronizer ClockSynchronizer
public event EventHandler AfterInstructionExecution;
+ public event EventHandler MaskableInterruptServicingStart;
+
+ public event EventHandler NonMaskableInterruptServicingStart;
+
+ public event EventHandler BeforeRetiInstructionExecution;
+
+ public event EventHandler AfterRetiInstructionExecution;
+
+ public event EventHandler BeforeRetnInstructionExecution;
+
+ public event EventHandler AfterRetnInstructionExecution;
+
#endregion
#region Members of IZ80ProcessorAgent
diff --git a/README.md b/README.md
index 9074257..94e55de 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ For your convenience, you can add Z80.NET to your project [as a NuGet package](h
1. Create an instance of [the Z80Processor class](Main/Z80Processor.cs).
2. Optionally, plug your own implementations of one or more of the [dependencies](Docs/Dependencies.md).
3. [Configure your instance](Docs/Configuration.md) as appropriate.
-4. Optionally, register one or more [interrupt sources](Docs/Interrupts.md).
+4. Optionally, register one or more [interrupt sources](Docs/Interrupts.md), and capture the related events if you need to.
5. Optionally, capture [the memory access events](Docs/MemoryAccessFlow.md) and/or [the instruction execution events](Docs/InstructionExecutionFlow.md).
6. [Start the simulated processor execution](Docs/HowExecutionWorks.md) by using one of the execution control methods.
7. Execution will stop (and the execution method invoked will then return) when one of [the execution stop conditions is met](Docs/StopConditions.md). You can then check [the processor state](Docs/State.md) and, if desired, resume execution.