mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2026-10-23 23:02:16 +00:00
Add Z80 interrupt and pin tests
This commit is contained in:
1 parent
1876a0d1f0
commit
e9603b0e08
9 files changed
+644
-2
No files matched your search
+8
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34902.65
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.5.11801.241 oobstable
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EightBit", "EightBit\EightBit.csproj", "{B9DB62BA-F1C6-41B4-BE0F-85A1D4E64A33}"
|
||||
EndProject
|
||||
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Z80.HarteTest", "Z80\Z80.Ha
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EightBit.UnitTest", "EightBit\EightBit.UnitTest\EightBit.UnitTest.csproj", "{A8B3D819-0E84-4586-ADB1-D57AB1A8165B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Z80.UnitTests", "Z80\Z80.UnitTests\Z80.UnitTests.csproj", "{DB313D5B-9DC3-4844-9916-925678967112}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -135,6 +137,10 @@ Global
|
||||
{A8B3D819-0E84-4586-ADB1-D57AB1A8165B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8B3D819-0E84-4586-ADB1-D57AB1A8165B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8B3D819-0E84-4586-ADB1-D57AB1A8165B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB313D5B-9DC3-4844-9916-925678967112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB313D5B-9DC3-4844-9916-925678967112}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB313D5B-9DC3-4844-9916-925678967112}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB313D5B-9DC3-4844-9916-925678967112}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// <copyright file="Board.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using EightBit;
|
||||
|
||||
public sealed class Board : Bus
|
||||
{
|
||||
private readonly Ram ram = new(0x10000);
|
||||
private readonly InputOutput ports = new();
|
||||
private readonly MemoryMapping mapping;
|
||||
|
||||
public Board()
|
||||
{
|
||||
this.CPU = new(this, this.ports);
|
||||
this.mapping = new(this.ram, 0x0000, (ushort)Mask.Sixteen, AccessLevel.ReadWrite);
|
||||
}
|
||||
|
||||
public Z80 CPU { get; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
public override MemoryMapping Mapping(ushort absolute) => this.mapping;
|
||||
|
||||
public override void RaisePOWER()
|
||||
{
|
||||
base.RaisePOWER();
|
||||
this.CPU.RaisePOWER();
|
||||
this.CPU.RaiseRESET();
|
||||
this.CPU.RaiseINT();
|
||||
this.CPU.RaiseNMI();
|
||||
}
|
||||
|
||||
public override void LowerPOWER()
|
||||
{
|
||||
this.CPU.LowerPOWER();
|
||||
base.LowerPOWER();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// <copyright file="HaltTests.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class HaltTests
|
||||
{
|
||||
private readonly Board board = new();
|
||||
private readonly Z80 cpu;
|
||||
|
||||
public HaltTests() => this.cpu = this.board.CPU;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
this.board.RaisePOWER();
|
||||
this.cpu.PC.Joined = 0x0000;
|
||||
this.cpu.SP.Joined = 0xFF00;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => this.board.LowerPOWER();
|
||||
|
||||
[TestMethod]
|
||||
public void Halt_Instruction_Lowers_Halt_Pin()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x76); // HALT
|
||||
Assert.IsTrue(this.cpu.HALT.Raised(), "HALT pin should be inactive before HALT instruction");
|
||||
this.cpu.Step();
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "HALT pin should be active after HALT instruction");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Halt_State_Does_Not_Advance_PC()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x76); // HALT
|
||||
this.board.Poke(0x0001, 0x00); // NOP (should not execute while halted)
|
||||
this.cpu.Step(); // execute HALT — PC advances to 0x0001
|
||||
var pcAfterHalt = this.cpu.PC.Joined;
|
||||
this.cpu.Step(); // CPU is halted — PC must not advance
|
||||
Assert.AreEqual(pcAfterHalt, this.cpu.PC.Joined, "PC must not advance while CPU is halted");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Halt_Nmi_Raises_Halt_Pin()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x76); // HALT
|
||||
this.cpu.Step(); // enter HALT
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "CPU must be halted before NMI test");
|
||||
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step(); // NMI exits HALT and jumps to 0x0066
|
||||
Assert.IsTrue(this.cpu.HALT.Raised(), "HALT pin must be raised after NMI");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Halt_Int_Raises_Halt_Pin_When_IFF1_Enabled()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x76); // HALT
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.Step(); // enter HALT
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "CPU must be halted before INT test");
|
||||
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step(); // INT exits HALT (IM1 → jumps to 0x0038)
|
||||
Assert.IsTrue(this.cpu.HALT.Raised(), "HALT pin must be raised after INT with IFF1 enabled");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Halt_Int_Does_Not_Exit_When_IFF1_Disabled()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x76); // HALT
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.Step(); // enter HALT
|
||||
var pcAfterHalt = this.cpu.PC.Joined;
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "CPU must be halted before INT test");
|
||||
|
||||
this.cpu.LowerINT(); // INT pending, but IFF1=false so it will be dismissed
|
||||
this.cpu.Step();
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "CPU must remain halted when IFF1 is disabled");
|
||||
Assert.AreEqual(pcAfterHalt, this.cpu.PC.Joined, "PC must not change when INT is masked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// <copyright file="IffTests.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class IffTests
|
||||
{
|
||||
private readonly Board board = new();
|
||||
private readonly Z80 cpu;
|
||||
|
||||
public IffTests() => this.cpu = this.board.CPU;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
this.board.RaisePOWER();
|
||||
this.cpu.PC.Joined = 0x0000;
|
||||
this.cpu.SP.Joined = 0xFF00;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => this.board.LowerPOWER();
|
||||
|
||||
[TestMethod]
|
||||
public void DI_Clears_IFF1_And_IFF2()
|
||||
{
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.IFF2 = true;
|
||||
this.board.Poke(0x0000, 0xF3); // DI
|
||||
this.cpu.Step();
|
||||
Assert.IsFalse(this.cpu.IFF1, "DI must clear IFF1");
|
||||
Assert.IsFalse(this.cpu.IFF2, "DI must clear IFF2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EI_Sets_IFF1_And_IFF2()
|
||||
{
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.IFF2 = false;
|
||||
this.board.Poke(0x0000, 0xFB); // EI
|
||||
this.cpu.Step();
|
||||
Assert.IsTrue(this.cpu.IFF1, "EI must set IFF1");
|
||||
Assert.IsTrue(this.cpu.IFF2, "EI must set IFF2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_Prevents_INT()
|
||||
{
|
||||
this.board.Poke(0x0000, 0x00); // NOP
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step(); // pending cleared (IFF1=false), then NOP executes
|
||||
Assert.AreEqual(0x0001, this.cpu.PC.Joined, "INT must be blocked when IFF1 is clear");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EI_Followed_By_INT_Fires_On_Next_LowerINT()
|
||||
{
|
||||
// After EI, the previous INT pending was already consumed without firing
|
||||
// (because IFF1 was false when it was checked). INT must produce a new
|
||||
// falling edge to retrigger. The INT pin is still low, so raise it first.
|
||||
this.board.Poke(0x0000, 0xFB); // EI
|
||||
this.board.Poke(0x0001, 0x00); // NOP
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.IM = 1;
|
||||
|
||||
this.cpu.LowerINT(); // pending=true, but IFF1=false
|
||||
this.cpu.Step(); // pending cleared (IFF1=false), EI runs → IFF1=true
|
||||
Assert.IsTrue(this.cpu.IFF1, "IFF1 must be true after EI");
|
||||
Assert.AreEqual(0x0001, this.cpu.PC.Joined, "No interrupt must fire during EI step");
|
||||
|
||||
this.cpu.Step(); // NOP at 0x0001 — still no interrupt (pending was cleared)
|
||||
Assert.AreEqual(0x0002, this.cpu.PC.Joined, "No interrupt must fire on instruction after EI");
|
||||
|
||||
// Produce a new falling edge on INT (raise then lower) to retrigger
|
||||
this.cpu.RaiseINT();
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0038, this.cpu.PC.Joined, "INT must fire after new falling edge post-EI");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Im_Set_Mode_0()
|
||||
{
|
||||
this.cpu.IM = 2;
|
||||
this.board.Poke(0x0000, 0xED); // IM 0 prefix
|
||||
this.board.Poke(0x0001, 0x46); // IM 0 (ED 46)
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0, this.cpu.IM, "IM 0 instruction must set interrupt mode to 0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Im_Set_Mode_1()
|
||||
{
|
||||
this.cpu.IM = 0;
|
||||
this.board.Poke(0x0000, 0xED); // IM 1 prefix
|
||||
this.board.Poke(0x0001, 0x56); // IM 1 (ED 56)
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(1, this.cpu.IM, "IM 1 instruction must set interrupt mode to 1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Im_Set_Mode_2()
|
||||
{
|
||||
this.cpu.IM = 0;
|
||||
this.board.Poke(0x0000, 0xED); // IM 2 prefix
|
||||
this.board.Poke(0x0001, 0x5E); // IM 2 (ED 5E)
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(2, this.cpu.IM, "IM 2 instruction must set interrupt mode to 2");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// <copyright file="IntTests.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class IntTests
|
||||
{
|
||||
private readonly Board board = new();
|
||||
private readonly Z80 cpu;
|
||||
|
||||
public IntTests() => this.cpu = this.board.CPU;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
this.board.RaisePOWER();
|
||||
this.cpu.PC.Joined = 0x0200;
|
||||
this.cpu.SP.Joined = 0xFF00;
|
||||
this.cpu.IFF1 = true;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => this.board.LowerPOWER();
|
||||
|
||||
// ── IM 1 ─────────────────────────────────────────────────────────────
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM1_Jumps_To_0x0038()
|
||||
{
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0038, this.cpu.PC.Joined, "IM 1 INT must jump to 0x0038");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM1_Saves_PC_On_Stack()
|
||||
{
|
||||
this.cpu.PC.Joined = 0x1234;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
|
||||
var low = this.board.Peek(this.cpu.SP.Joined);
|
||||
var high = this.board.Peek((ushort)(this.cpu.SP.Joined + 1));
|
||||
var savedPc = (ushort)((high << 8) | low);
|
||||
Assert.AreEqual(0x1234, savedPc, "IM 1 INT must save PC on the stack");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM1_Clears_IFF1_And_IFF2()
|
||||
{
|
||||
this.cpu.IFF2 = true;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
Assert.IsFalse(this.cpu.IFF1, "INT must clear IFF1");
|
||||
Assert.IsFalse(this.cpu.IFF2, "INT must clear IFF2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Int_Blocked_When_IFF1_Disabled()
|
||||
{
|
||||
this.board.Poke(0x0200, 0x00); // NOP
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT(); // INT pending, but IFF1 = false
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0201, this.cpu.PC.Joined, "INT must not fire when IFF1 is disabled");
|
||||
}
|
||||
|
||||
// ── IM 2 ─────────────────────────────────────────────────────────────
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM2_Jumps_Via_Vector_Table()
|
||||
{
|
||||
// Vector table: address = (IV << 8) | bus_data_byte
|
||||
// IV = 0x10, bus byte = 0x20 → table entry at 0x1020
|
||||
// Table entry (little-endian) = 0x5678
|
||||
this.cpu.IV = 0x10;
|
||||
this.board.Poke(0x1020, 0x78);
|
||||
this.board.Poke(0x1021, 0x56);
|
||||
|
||||
this.cpu.IM = 2;
|
||||
this.board.Data = 0x20; // interrupt device places vector byte on data bus
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x5678, this.cpu.PC.Joined, "IM 2 INT must read jump address from vector table");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM2_Saves_PC_On_Stack()
|
||||
{
|
||||
this.cpu.PC.Joined = 0xABCD;
|
||||
this.cpu.IV = 0x10;
|
||||
this.board.Poke(0x1020, 0x78);
|
||||
this.board.Poke(0x1021, 0x56);
|
||||
|
||||
this.cpu.IM = 2;
|
||||
this.board.Data = 0x20;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
|
||||
var low = this.board.Peek(this.cpu.SP.Joined);
|
||||
var high = this.board.Peek((ushort)(this.cpu.SP.Joined + 1));
|
||||
var savedPc = (ushort)((high << 8) | low);
|
||||
Assert.AreEqual(0xABCD, savedPc, "IM 2 INT must save PC on the stack");
|
||||
}
|
||||
|
||||
// ── IM 0 ─────────────────────────────────────────────────────────────
|
||||
|
||||
[TestMethod]
|
||||
public void Int_IM0_Executes_Instruction_From_Data_Bus()
|
||||
{
|
||||
// RST 7 (0xFF) placed on data bus by interrupt device → jumps to 0x0038
|
||||
this.cpu.PC.Joined = 0x0200;
|
||||
this.cpu.IM = 0;
|
||||
this.board.Data = 0xFF; // RST 7
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0038, this.cpu.PC.Joined, "IM 0 INT must execute RST 7 from data bus");
|
||||
}
|
||||
|
||||
// ── RETI ─────────────────────────────────────────────────────────────
|
||||
|
||||
[TestMethod]
|
||||
public void Reti_Restores_PC_From_Stack()
|
||||
{
|
||||
// RETI (ED 4D) at 0x0038 — return to saved PC
|
||||
this.board.Poke(0x0038, 0xED);
|
||||
this.board.Poke(0x0039, 0x4D);
|
||||
|
||||
this.cpu.PC.Joined = 0x0200;
|
||||
this.cpu.IM = 1;
|
||||
this.cpu.LowerINT();
|
||||
this.cpu.Step(); // handles INT, jumps to 0x0038, saves 0x0200
|
||||
|
||||
Assert.AreEqual(0x0038, this.cpu.PC.Joined);
|
||||
|
||||
this.cpu.Step(); // executes RETI
|
||||
Assert.AreEqual(0x0200, this.cpu.PC.Joined, "RETI must restore PC from stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// <copyright file="NmiTests.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class NmiTests
|
||||
{
|
||||
private readonly Board board = new();
|
||||
private readonly Z80 cpu;
|
||||
|
||||
public NmiTests() => this.cpu = this.board.CPU;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
this.board.RaisePOWER();
|
||||
this.cpu.PC.Joined = 0x0100;
|
||||
this.cpu.SP.Joined = 0xFF00;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => this.board.LowerPOWER();
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Jumps_To_0x0066()
|
||||
{
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0066, this.cpu.PC.Joined, "NMI must jump to 0x0066");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Saves_PC_On_Stack()
|
||||
{
|
||||
this.cpu.PC.Joined = 0x1234;
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
|
||||
// PushShort pushes High first then Low, so:
|
||||
// mem[SP-1] = PC.High, mem[SP-2] = PC.Low
|
||||
var low = this.board.Peek(this.cpu.SP.Joined); // mem[SP] = PC.Low
|
||||
var high = this.board.Peek((ushort)(this.cpu.SP.Joined + 1)); // mem[SP+1] = PC.High
|
||||
var savedPc = (ushort)((high << 8) | low);
|
||||
Assert.AreEqual(0x1234, savedPc, "NMI must save PC on the stack");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Clears_IFF1()
|
||||
{
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
Assert.IsFalse(this.cpu.IFF1, "NMI must clear IFF1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Preserves_IFF1_In_IFF2()
|
||||
{
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.IFF2 = false;
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
Assert.IsTrue(this.cpu.IFF2, "NMI must copy IFF1 into IFF2 before clearing IFF1");
|
||||
Assert.IsFalse(this.cpu.IFF1, "NMI must clear IFF1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Fires_Even_When_IFF1_Disabled()
|
||||
{
|
||||
this.cpu.IFF1 = false;
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0066, this.cpu.PC.Joined, "NMI must fire regardless of IFF1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Nmi_Exits_Halt_State()
|
||||
{
|
||||
this.board.Poke(0x0100, 0x76); // HALT
|
||||
this.cpu.Step(); // enter HALT
|
||||
Assert.IsTrue(this.cpu.HALT.Lowered(), "CPU must be halted before NMI test");
|
||||
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step(); // NMI exits HALT
|
||||
Assert.IsTrue(this.cpu.HALT.Raised(), "NMI must raise HALT pin");
|
||||
Assert.AreEqual(0x0066, this.cpu.PC.Joined, "NMI must jump to 0x0066 from HALT state");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Retn_Restores_IFF1_From_IFF2()
|
||||
{
|
||||
// RETN (ED 45) at 0x0066
|
||||
this.board.Poke(0x0066, 0xED);
|
||||
this.board.Poke(0x0067, 0x45);
|
||||
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step(); // handles NMI: IFF2=true, IFF1=false, jumps to 0x0066
|
||||
|
||||
Assert.IsFalse(this.cpu.IFF1, "IFF1 must be false after NMI");
|
||||
Assert.IsTrue(this.cpu.IFF2, "IFF2 must be true after NMI");
|
||||
|
||||
this.cpu.Step(); // executes RETN at 0x0066: IFF1 = IFF2 = true
|
||||
Assert.IsTrue(this.cpu.IFF1, "RETN must restore IFF1 from IFF2");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// <copyright file="ResetTests.cs" company="Adrian Conlon">
|
||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||
// </copyright>
|
||||
namespace Z80.UnitTests
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class ResetTests
|
||||
{
|
||||
private readonly Board board = new();
|
||||
private readonly Z80 cpu;
|
||||
|
||||
public ResetTests() => this.cpu = this.board.CPU;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
this.board.RaisePOWER();
|
||||
this.cpu.PC.Joined = 0x1234;
|
||||
this.cpu.SP.Joined = 0x8000;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => this.board.LowerPOWER();
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Sets_PC_To_Zero()
|
||||
{
|
||||
this.cpu.LowerRESET();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x0000, this.cpu.PC.Joined, "RESET must set PC to 0x0000");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Clears_IFF1_And_IFF2()
|
||||
{
|
||||
this.cpu.IFF1 = true;
|
||||
this.cpu.IFF2 = true;
|
||||
this.cpu.LowerRESET();
|
||||
this.cpu.Step();
|
||||
Assert.IsFalse(this.cpu.IFF1, "RESET must clear IFF1");
|
||||
Assert.IsFalse(this.cpu.IFF2, "RESET must clear IFF2");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Sets_IM_To_Zero()
|
||||
{
|
||||
this.cpu.IM = 2;
|
||||
this.cpu.LowerRESET();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0, this.cpu.IM, "RESET must set interrupt mode to 0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Clears_I_And_R_Registers()
|
||||
{
|
||||
this.cpu.IV = 0xAB;
|
||||
this.cpu.REFRESH = 0xCD;
|
||||
this.cpu.LowerRESET();
|
||||
this.cpu.Step();
|
||||
Assert.AreEqual(0x00, this.cpu.IV, "RESET must clear I register");
|
||||
Assert.AreEqual(0x00, (byte)this.cpu.REFRESH, "RESET must clear R register");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Deasserts_Reset_Pin()
|
||||
{
|
||||
this.cpu.LowerRESET(); // asserts RESET — fires LoweredRESET event
|
||||
Assert.IsTrue(this.cpu.RESET.Lowered(), "RESET pin must be asserted before step");
|
||||
this.cpu.Step();
|
||||
Assert.IsTrue(this.cpu.RESET.Raised(), "HandleRESET must deassert the RESET pin");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Has_Priority_Over_NMI()
|
||||
{
|
||||
// Assert both RESET and NMI simultaneously; RESET is checked first in PoweredStep
|
||||
this.cpu.LowerRESET();
|
||||
this.cpu.LowerNMI();
|
||||
this.cpu.Step();
|
||||
// RESET handler runs, sets PC=0 — NMI (0x0066) must not win
|
||||
Assert.AreEqual(0x0000, this.cpu.PC.Joined, "RESET must take priority over pending NMI");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||
<GenerateDocumentationFile>False</GenerateDocumentationFile>
|
||||
<SignAssembly>False</SignAssembly>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<WarningLevel>9999</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<WarningLevel>9999</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.12.6" />
|
||||
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="1.4.3" />
|
||||
<PackageReference Include="MSTest" Version="3.6.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\EightBit\EightBit.csproj" />
|
||||
<ProjectReference Include="..\Z80.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="EightBit" />
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -22,12 +22,15 @@
|
||||
<Compile Remove="Z80.FuseTest\**" />
|
||||
<Compile Remove="Z80.HarteTest\**" />
|
||||
<Compile Remove="Z80.Test\**" />
|
||||
<Compile Remove="Z80.UnitTests\**" />
|
||||
<EmbeddedResource Remove="Z80.FuseTest\**" />
|
||||
<EmbeddedResource Remove="Z80.HarteTest\**" />
|
||||
<EmbeddedResource Remove="Z80.Test\**" />
|
||||
<EmbeddedResource Remove="Z80.UnitTests\**" />
|
||||
<None Remove="Z80.FuseTest\**" />
|
||||
<None Remove="Z80.HarteTest\**" />
|
||||
<None Remove="Z80.Test\**" />
|
||||
<None Remove="Z80.UnitTests\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in new issue
Block a user