Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.0.7: Add interrupt and RETI/RETN related events #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Docs/Interrupts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 1 addition & 2 deletions Main.Tests/InterruptSourceForTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
143 changes: 138 additions & 5 deletions Main.Tests/Z80ProcessorTests_InstructionExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
Expand All @@ -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);
Expand Down
56 changes: 56 additions & 0 deletions Main.Tests/Z80ProcessorTests_Interrupts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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
}
}
18 changes: 18 additions & 0 deletions Main/Enums/InterruptType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Konamiman.Z80dotNet.Enums
{
/// <summary>
/// Defines the type of a Z80 interrupt
/// </summary>
public enum InterruptType
{
/// <summary>
/// Maskable interrupt
/// </summary>
Maskable = 1,

/// <summary>
/// Non-maskable interrupt
/// </summary>
NonMaskable
}
}
42 changes: 42 additions & 0 deletions Main/IZ80Processor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,48 @@ public interface IZ80Processor
/// </summary>
event EventHandler<AfterInstructionExecutionEventArgs> AfterInstructionExecution;

/// <summary>
/// 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.
/// </summary>
event EventHandler MaskableInterruptServicingStart;

/// <summary>
/// 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.
/// </summary>
event EventHandler NonMaskableInterruptServicingStart;

/// <summary>
/// Triggered before a RETI instruction is about to be executed,
/// right after the corresponding BeforeInstructionExecution event
/// </summary>
event EventHandler BeforeRetiInstructionExecution;

/// <summary>
/// Triggered after a RETI instruction has been executed,
/// right after the corresponding AfterInstructionExecution event
/// </summary>
event EventHandler AfterRetiInstructionExecution;

/// <summary>
/// Triggered before a RETN instruction is about to be executed,
/// right after the corresponding BeforeInstructionExecution event
/// </summary>
event EventHandler BeforeRetnInstructionExecution;

/// <summary>
/// Triggered after a RETN instruction has been executed,
/// right after the corresponding AfterInstructionExecution event
/// </summary>
event EventHandler AfterRetnInstructionExecution;

#endregion

#region Utils
Expand Down
1 change: 1 addition & 0 deletions Main/Main.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Dependencies Interfaces\IZ80InterruptSource.cs" />
<Compile Include="Enums\InterruptType.cs" />
<Compile Include="EventArgs\AfterInstructionExecutionEventArgs.cs" />
<Compile Include="Data Types and Utils\Bit.cs" />
<Compile Include="Dependencies Interfaces\IExecutionStopper.cs" />
Expand Down
4 changes: 2 additions & 2 deletions Main/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Loading