using Contralto.CPU; using System; using System.Collections.Generic; namespace Contralto.Memory { public enum MemoryOperation { None, LoadAddress, Read, Store } public class MemoryBus : IClockable { public MemoryBus() { _bus = new Dictionary(65536); Reset(); } public void AddDevice(IMemoryMappedDevice dev) { // // Add the new device to the hash; this is done by adding // one entry for every address claimed by the device. Since we have only 64K of address // space, this isn't too awful. // foreach(MemoryRange range in dev.Addresses) { for(ushort addr = range.Start; addr <= range.End; addr++) { if (_bus.ContainsKey(addr)) { throw new InvalidOperationException( String.Format("Memory mapped address collision for dev {0} at address {1} with {2}", dev, Conversion.ToOctal(addr), _bus[addr])); } else { _bus.Add(addr, dev); if (dev is Memory) { _mainMemory = (Memory)dev; } } } } } public void Reset() { _memoryCycle = 0; _memoryAddress = 0; _memoryData = 0; _doubleWordStore = false; _doubleWordMixed = false; _memoryOperationActive = false; _extendedMemoryReference = false; } public ushort MAR { get { return _memoryAddress; } } public ushort MD { get { return _memoryData; } } public int Cycle { get { return _memoryCycle; } } public bool Active { get { return _memoryOperationActive; } } /// /// Used for debugging only -- returns the (correctly mapped) /// word at the specified address /// public ushort DebugReadWord(ushort address) { // TODO: allow debug reads from any bank. // probably add special debug calls to IMemoryMappedDevice iface. return ReadFromBus(address, TaskType.Emulator, false); } public ushort DebugReadWord(TaskType task, ushort address) { // TODO: allow debug reads from any bank. // probably add special debug calls to IMemoryMappedDevice iface. return ReadFromBus(address, task, false); } public void Clock() { _memoryCycle++; if (_memoryOperationActive) { switch (_memoryCycle) { case 3: // Buffered read of single word _memoryData = ReadFromBus(_memoryAddress, _task, _extendedMemoryReference); break; case 4: // Buffered read of double-word _memoryData2 = ReadFromBus((ushort)(_memoryAddress ^ 1), _task, _extendedMemoryReference); break; case 5: // End of memory operation _memoryOperationActive = false; _doubleWordStore = false; break; } } } public bool Ready(MemoryOperation op) { if (_memoryOperationActive) { switch (op) { case MemoryOperation.LoadAddress: // Can't start a new Load operation until the current one is finished. return false; case MemoryOperation.Read: // Read operations take place on cycles 5 and 6 return _memoryCycle > 4; case MemoryOperation.Store: // Write operations take place on cycles 3 and 4 return _memoryCycle > 2; default: throw new InvalidOperationException(String.Format("Unexpected memory operation {0}", op)); } } else { // Nothing running right now, we're ready for anything. return true; } } public void LoadMAR(ushort address, TaskType task, bool extendedMemoryReference) { if (_memoryOperationActive) { // This should not happen; CPU should check whether the operation is possible using Ready and stall if not. throw new InvalidOperationException("Invalid LoadMAR request during active memory operation."); } else { _memoryOperationActive = true; _doubleWordStore = false; _doubleWordMixed = false; _memoryAddress = address; _extendedMemoryReference = extendedMemoryReference; _task = task; _memoryCycle = 1; } } public ushort ReadMD() { if (_memoryOperationActive) { switch (_memoryCycle) { case 1: case 2: // TODO: good microcode should never do this throw new InvalidOperationException("Unexpected microcode behavior -- ReadMD too soon after start of memory cycle."); case 3: case 4: // This should not happen; CPU should check whether the operation is possible using Ready and stall if not. throw new InvalidOperationException("Invalid ReadMR request during cycle 3 or 4 of memory operation."); case 5: // Single word read //Log.Write(LogType.Verbose, LogComponent.Memory, "Single-word read of {0} from {1} (cycle 5)", Conversion.ToOctal(_memoryData), Conversion.ToOctal(_memoryAddress ^ 1)); // debug /* if (_memoryAddress == 0xfc90 && _task != TaskType.Emulator) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "--> Task {0} read {1} from 176220.", _task, _memoryData); } */ return _memoryData; // *** // NB: Handler for double-word read (cycle 6) is in the "else" clause below; this is kind of a hack. // *** default: // Invalid state. throw new InvalidOperationException(string.Format("Unexpected memory cycle {0} in memory state machine.", _memoryCycle)); } } else { // memory state machine not running, just return last latched contents. // ("Because the Alto II latches memory contents, it is possible to execute _MD anytime after // cycle 5 of a reference and obtain the results of the read operation") // If this is memory cycle 6 we will return the last half of the doubleword to complete a double-word read. if (_memoryCycle == 6 || (_memoryCycle == 5 && _doubleWordMixed)) { //Log.Write(LogType.Verbose, LogComponent.Memory, "Double-word read of {0} from {1} (cycle 6)", Conversion.ToOctal(_memoryData2), Conversion.ToOctal(_memoryAddress ^ 1)); _doubleWordMixed = false; // debug /* if ((_memoryAddress ^ 1) == 0xfc90 && _task != TaskType.Emulator) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "--> Task {0} read {1} from 176220.", _task, _memoryData2); } */ return _memoryData2; } else { _doubleWordMixed = false; // debug /* if (_memoryAddress == 0xfc90 && _task != TaskType.Emulator) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "--> Task {0} read {1} from 176220.", _task, _memoryData); } */ //Log.Write(LogType.Verbose, LogComponent.Memory, "Single-word read of {0} from {1} (post cycle 6)", Conversion.ToOctal(_memoryData), Conversion.ToOctal(_memoryAddress)); return _memoryData; } } } public void LoadMD(ushort data) { if (_memoryOperationActive) { switch (_memoryCycle) { case 1: case 2: case 5: // TODO: good microcode should never do this throw new InvalidOperationException("Unexpected microcode behavior -- LoadMD during incorrect memory cycle."); case 3: // debug /* if (_memoryAddress == 0xfc90 || _memoryAddress == 0xfc91 || _memoryAddress == 0x151) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "++> Task {0} wrote {1} to {3} (was {2}).", _task, Conversion.ToOctal(data), Conversion.ToOctal(DebugReadWord(_task, _memoryAddress)), Conversion.ToOctal(_memoryAddress)); }*/ _memoryData = data; // Only really necessary to show in debugger // Start of doubleword write: WriteToBus(_memoryAddress, data, _task, _extendedMemoryReference); _doubleWordStore = true; _doubleWordMixed = true; /* Log.Write( LogType.Verbose, LogComponent.Memory, "Single-word store of {0} to {1} (cycle 3)", Conversion.ToOctal(data), Conversion.ToOctal(_memoryAddress)); */ break; case 4: _memoryData = data; // Only really necessary to show in debugger /* Log.Write( LogType.Verbose, LogComponent.Memory, _doubleWordStore ? "Double-word store of {0} to {1} (cycle 4)" : "Single-word store of {0} to {1} (cycle 4)", Conversion.ToOctal(data), _doubleWordStore ? Conversion.ToOctal(_memoryAddress ^ 1) : Conversion.ToOctal(_memoryAddress)); */ // debug ushort actualAddress = _doubleWordStore ? (ushort)(_memoryAddress ^ 1) : _memoryAddress; /* if (actualAddress == 0xfc90 || actualAddress == 0xfc91 || _memoryAddress == 0x151) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "--> Task {0} wrote {1} to {4} (was {2}). Extd {3}", _task, Conversion.ToOctal(data), Conversion.ToOctal(DebugReadWord(_task, actualAddress)), _extendedMemoryReference, Conversion.ToOctal(actualAddress)); } */ WriteToBus(actualAddress, data, _task, _extendedMemoryReference); /* if (actualAddress == 0xfc90 || actualAddress == 0xfc91 || _memoryAddress == 0x151) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "--> Now {0}.", Conversion.ToOctal(DebugReadWord(_task, actualAddress))); } */ break; } } } /// /// Dispatches reads to memory mapped hardware (RAM, I/O) /// /// /// private ushort ReadFromBus(ushort address, TaskType task, bool extendedMemoryReference) { if (address <= Memory.RamTop) { // Main memory access; shortcut hashtable lookup for performance reasons. return _mainMemory.Read(address, task, extendedMemoryReference); } else { // Memory-mapped device access: // Look up address in hash; if populated ask the device // to return a value otherwise throw. IMemoryMappedDevice memoryMappedDevice = null; if (_bus.TryGetValue(address, out memoryMappedDevice)) { return memoryMappedDevice.Read(address, task, extendedMemoryReference); } else { //throw new NotImplementedException(String.Format("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); //Console.WriteLine("Read from unimplemented memory-mapped I/O device at {0}.", Conversion.ToOctal(address)); return 0; } } } /// /// Dispatches writes to memory mapped hardware (RAM, I/O) /// /// /// /// private void WriteToBus(ushort address, ushort data, TaskType task, bool extendedMemoryReference) { if (address <= Memory.RamTop) { // Main memory access; shortcut hashtable lookup for performance reasons. _mainMemory.Load(address, data, task, extendedMemoryReference); } else { // Memory-mapped device access: // Look up address in hash; if populated ask the device // to store a value otherwise throw. IMemoryMappedDevice memoryMappedDevice = null; if (_bus.TryGetValue(address, out memoryMappedDevice)) { memoryMappedDevice.Load(address, data, task, extendedMemoryReference); } else { // throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); //Console.WriteLine("Write to unimplemented memory-mapped I/O device at {0}.", Conversion.ToOctal(address)); } } } /// /// Hashtable used for address-based dispatch to devices on the memory bus. /// private Dictionary _bus; // // Optimzation: keep reference to main memory; since 99.9999% of accesses go directly there, // we can avoid the hashtable overhead using a simple address check. // private Memory _mainMemory; private bool _memoryOperationActive; private int _memoryCycle; private ushort _memoryAddress; private bool _extendedMemoryReference; private TaskType _task; // Buffered read data (on cycles 3 and 4) private ushort _memoryData; private ushort _memoryData2; // Indicates a double-word store (started on cycle 3) private bool _doubleWordStore; // Indicates a mixed double-word store/load (started in cycle 3) private bool _doubleWordMixed; } }