Files

119 lines
4.1 KiB
C#

// <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(); // but IFF1=false
this.cpu.Step(); // IFF1=false, no interrupt, EI runs → IFF1=true
Assert.IsTrue(this.cpu.IFF1, "IFF1 must be true after EI");
Assert.AreEqual(0x0001, this.cpu.PC.Joined, "Normal execution, no interrupt");
this.cpu.Step(); // IFF1 true, INT still low
Assert.AreEqual(0x0038, this.cpu.PC.Joined, "INT fires if INT is low");
// Produce a new falling edge on INT (raise then lower) to retrigger
this.cpu.RaiseINT();
this.cpu.LowerINT();
Assert.IsFalse(this.cpu.IFF1, "IFF1 has been disabled by the INT handler");
this.cpu.Step();
Assert.AreEqual(0x0038, this.cpu.PC.Joined - 1, "Inside INT handled at 0x38");
}
[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");
}
}
}