mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2026-10-23 23:02:16 +00:00
103 lines
4.1 KiB
C#
103 lines
4.1 KiB
C#
// <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_State_Forces_Nop_Regardless_Of_Memory_Contents()
|
|
{
|
|
// The Z80 manual states: "the data received from the memory is ignored
|
|
// and an NOP instruction is forced internally to the CPU."
|
|
// INC A (0x3C) at the looping address must not execute — A must be unchanged.
|
|
this.board.Poke(0x0000, 0x76); // HALT
|
|
this.board.Poke(0x0001, 0x3C); // INC A — must be suppressed while halted
|
|
this.cpu.A = 0x42;
|
|
this.cpu.Step(); // execute HALT — enters halt state, PC moves to 0x0001
|
|
this.cpu.Step(); // looping — memory byte (INC A) must be ignored, NOP forced
|
|
Assert.AreEqual(0x42, this.cpu.A, "Memory contents must be ignored during HALT; NOP must be forced");
|
|
}
|
|
|
|
[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");
|
|
}
|
|
}
|
|
}
|