mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2026-10-23 23:02:16 +00:00
119 lines
4.1 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|