diff --git a/EightBit/Bus.cs b/EightBit/Bus.cs index 81a2eba..79dff0f 100644 --- a/EightBit/Bus.cs +++ b/EightBit/Bus.cs @@ -8,9 +8,7 @@ namespace EightBit { public abstract class Bus : IMapper { - private bool _reading; private bool _writing; - private byte _data; public event EventHandler? WritingByte; @@ -48,27 +46,21 @@ namespace EightBit public void Read() { Debug.Assert(!this._writing, "Writing flag is in an invalid state"); - Debug.Assert(!this._reading, "Reading flag is in an invalid state"); this.ReadingByte?.Invoke(this, EventArgs.Empty); - this._reading = true; this.Data = this.Reference(); - this._reading = false; ReadByte?.Invoke(this, EventArgs.Empty); Debug.Assert(!this._writing, "Writing flag is in an invalid state"); - Debug.Assert(!this._reading, "Reading flag is in an invalid state"); } public void Write() { Debug.Assert(!this._writing, "Writing flag is in an invalid state"); - Debug.Assert(!this._reading, "Reading flag is in an invalid state"); this.WritingByte?.Invoke(this, EventArgs.Empty); this._writing = true; this.Reference() = this.Data; this._writing = false; this.WrittenByte?.Invoke(this, EventArgs.Empty); Debug.Assert(!this._writing, "Writing flag is in an invalid state"); - Debug.Assert(!this._reading, "Reading flag is in an invalid state"); } diff --git a/EightBit/EightBit.UnitTest/BusTests.cs b/EightBit/EightBit.UnitTest/BusTests.cs new file mode 100644 index 0000000..ea9a12d --- /dev/null +++ b/EightBit/EightBit.UnitTest/BusTests.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) Adrian Conlon. All rights reserved. +// +namespace EightBit.UnitTest +{ + using EightBit; + + [TestClass] + public class BusTests + { + // Minimal Bus whose entire address space maps to a single read-only RAM region. + // Seed()/RawPeek() reach the RAM directly, bypassing the access-level check, + // so they can be used to set up and verify the underlying memory independently + // of whatever the Bus write path does. + private sealed class ReadOnlyBus : Bus + { + private readonly Ram _ram = new(0x10000); + private readonly MemoryMapping _mapping; + + public ReadOnlyBus() + => this._mapping = new(this._ram, 0x0000, (ushort)Mask.Sixteen, AccessLevel.ReadOnly); + + /// Writes directly to the backing RAM, bypassing the access-level guard. + public void Seed(ushort address, byte value) => this._ram.Poke(address, value); + + /// Reads directly from the backing RAM, bypassing the access-level guard. + public byte RawPeek(ushort address) => this._ram.Peek(address); + + public override MemoryMapping Mapping(ushort _) => this._mapping; + + public override void Initialize() { } + } + + private readonly ReadOnlyBus _bus = new(); + + [TestMethod] + public void Bus_Read_From_ReadOnly_Returns_Memory_Value() + { + _bus.Seed(0x1000, 0xAB); + _bus.Address.Joined = 0x1000; + _bus.Read(); + Assert.AreEqual(0xAB, _bus.Data, "Read from read-only mapping must return the underlying memory value"); + } + + [TestMethod] + public void Bus_Write_To_ReadOnly_Does_Not_Modify_Memory() + { + _bus.Seed(0x2000, 0x55); + _bus.Address.Joined = 0x2000; + _bus.Data = 0xFF; + _bus.Write(); + Assert.AreEqual(0x55, _bus.RawPeek(0x2000), "Write to read-only mapping must not modify the underlying memory"); + Assert.AreEqual(0xFF, _bus.Data, "Write to read-only mapping must leave the written value on the bus, not the memory value"); + } + + [TestMethod] + public void Bus_Read_After_Write_To_ReadOnly_Returns_Original_Value() + { + _bus.Seed(0x3000, 0x42); + _bus.Address.Joined = 0x3000; + _bus.Data = 0xFF; + _bus.Write(); // silently discarded + _bus.Read(); // must see the original value, not 0xFF + Assert.AreEqual(0x42, _bus.Data, "Read after a discarded write must return the original memory value"); + } + } +}