using System.Collections.Generic; using NUnit.Framework; using VMdemo.Core; using VMdemo.Simulation; namespace VMdemo.Tests.EditMode { public class Step5TranslatorEngineTests { [Test] public void StepOnce_FirstAccess_VisitsAllStagesInOrder() { var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 77); engine.SetNextVirtualAddress(0x0000_1234UL); var visited = new List(); bool completed; do { visited.Add(engine.CurrentStep); completed = engine.StepOnce(); } while (!completed); var expected = new[] { TranslationStep.GenerateVA, TranslationStep.SplitVA, TranslationStep.LookupTLB, TranslationStep.LookupPageTable, TranslationStep.HandlePageFault, TranslationStep.ComposePA, TranslationStep.Finalize }; CollectionAssert.AreEqual(expected, visited); Assert.That(engine.State.CurrentRound, Is.EqualTo(1)); Assert.That(engine.CurrentStep, Is.EqualTo(TranslationStep.GenerateVA)); } [Test] public void StepOnce_TlbHitPath_SkipsPageTableAndFaultStages() { var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 88); const ulong va = 0x0000_4567UL; engine.RunOneAccess(va); // warmup: make resident + populate tlb engine.SetNextVirtualAddress(va); var visited = new List(); bool completed; do { visited.Add(engine.CurrentStep); completed = engine.StepOnce(); } while (!completed); var expected = new[] { TranslationStep.GenerateVA, TranslationStep.SplitVA, TranslationStep.LookupTLB, TranslationStep.ComposePA, TranslationStep.Finalize }; CollectionAssert.AreEqual(expected, visited); Assert.IsTrue(engine.State.IsTlbHit); Assert.IsFalse(engine.State.IsPageFault); } [Test] public void StepOnce_PageTableHitPath_WritesBackToTlb() { var engine = CreateEngine(frameCountMb: 1, tlbEntries: 2, seed: 99); const ulong va = 0x0000_89ABUL; var parts = AddressTranslatorUtils.SplitVirtualAddress(va, 32, engine.OffsetBits); engine.PageTable.SetPresent(parts.L1Index, parts.L2Index, pfn: 3UL); engine.SetNextVirtualAddress(va); var visited = new List(); bool completed; do { visited.Add(engine.CurrentStep); completed = engine.StepOnce(); } while (!completed); var expected = new[] { TranslationStep.GenerateVA, TranslationStep.SplitVA, TranslationStep.LookupTLB, TranslationStep.LookupPageTable, TranslationStep.ComposePA, TranslationStep.Finalize }; CollectionAssert.AreEqual(expected, visited); Assert.IsFalse(engine.State.IsTlbHit); Assert.IsTrue(engine.State.IsPageTableHit); Assert.IsFalse(engine.State.IsPageFault); Assert.IsTrue(engine.TlbCache.TryLookup(parts.Vpn, out var cachedPfn)); Assert.That(cachedPfn, Is.EqualTo(3UL)); } [Test] public void RunOneAccess_WhenMemoryFull_ProducesEvictionInfoAndSyncsState() { var config = new SimulationConfig { vaBits = 32, pageSizeKB = 1024, // 1MB per page physicalMemoryMB = 1, // 1 frame total tlbEntries = 4, accessCount = 10, pageFaultPenalty = 100 }; var engine = new TranslatorEngine(config, seed: 11); var first = engine.RunOneAccess(0x0000_0123UL); var second = engine.RunOneAccess(0x0010_0123UL); // different VPN Assert.IsTrue(first.PageFault); Assert.IsTrue(second.PageFault); Assert.IsTrue(second.Eviction.HasEvicted); Assert.That(second.Eviction.EvictedVpn, Is.EqualTo(first.AddressParts.Vpn)); Assert.IsFalse(engine.TlbCache.TryLookup(first.AddressParts.Vpn, out _)); Assert.That(engine.LastEviction.EvictedVpn, Is.EqualTo(first.AddressParts.Vpn)); } [Test] public void RunOneAccess_ForcedVa_MatchesStepByStepOutcome() { const ulong va = 0x00AB_CDEFUL; var engineA = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 123); var result = engineA.RunOneAccess(va); var engineB = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 123); engineB.SetNextVirtualAddress(va); while (!engineB.StepOnce()) { } Assert.That(engineB.State.CurrentVirtualAddress, Is.EqualTo(result.VirtualAddress)); Assert.That(engineB.State.CurrentAddressParts.Vpn, Is.EqualTo(result.AddressParts.Vpn)); Assert.That(engineB.State.CurrentPfn, Is.EqualTo(result.Pfn)); Assert.That(engineB.State.CurrentPhysicalAddress, Is.EqualTo(result.PhysicalAddress)); Assert.That(engineB.State.IsPageFault, Is.EqualTo(result.PageFault)); Assert.That(engineB.State.IsPageTableHit, Is.EqualTo(result.PageTableHit)); Assert.That(engineB.State.IsTlbHit, Is.EqualTo(result.TlbHit)); Assert.That(engineB.State.CurrentRound, Is.EqualTo(result.Round)); } [Test] public void Reset_ClearsStateAndRuntimeCaches() { var engine = CreateEngine(frameCountMb: 1, tlbEntries: 4, seed: 222); engine.RunOneAccess(0x0000_1000UL); Assert.That(engine.State.CurrentRound, Is.EqualTo(1)); Assert.That(engine.TlbCache.Count, Is.GreaterThan(0)); Assert.That(engine.PhysicalMemoryManager.ResidentCount, Is.GreaterThan(0)); engine.Reset(); Assert.That(engine.State.CurrentRound, Is.EqualTo(0)); Assert.That(engine.State.CurrentStep, Is.EqualTo(TranslationStep.GenerateVA)); Assert.That(engine.State.CurrentVirtualAddress, Is.EqualTo(0UL)); Assert.That(engine.State.HasCurrentPfn, Is.False); Assert.That(engine.TlbCache.Count, Is.EqualTo(0)); Assert.That(engine.PhysicalMemoryManager.ResidentCount, Is.EqualTo(0)); Assert.That(engine.LastEviction.HasEvicted, Is.False); } private static TranslatorEngine CreateEngine(int frameCountMb, int tlbEntries, int seed) { var config = new SimulationConfig { vaBits = 32, pageSizeKB = 4, physicalMemoryMB = frameCountMb, tlbEntries = tlbEntries, accessCount = 10, pageFaultPenalty = 100 }; return new TranslatorEngine(config, seed); } } }