From cbcfd2b47ef65d95b59287fa352bd10979a09dac Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 17 Nov 2015 16:09:50 -0800 Subject: [PATCH] Minor bugfixes, introduced a new timing infrastructure and moved DiskController over to it. Minor performance improvements; now running at 110% speed. Display timing is still too slow. --- Contralto.sln | 6 + Contralto/AltoSystem.cs | 50 ++- Contralto/CPU/ALU.cs | 11 +- Contralto/CPU/CPU.cs | 57 +-- Contralto/CPU/ConstantMemory.cs | 4 - Contralto/CPU/MicroInstruction.cs | 4 - Contralto/CPU/NovaDisassembler.cs | 2 - Contralto/CPU/Shifter.cs | 21 +- Contralto/CPU/Tasks/CursorTask.cs | 7 +- Contralto/CPU/Tasks/DiskTask.cs | 32 +- Contralto/CPU/Tasks/DisplayHorizontalTask.cs | 9 +- Contralto/CPU/Tasks/DisplayVerticalTask.cs | 7 +- Contralto/CPU/Tasks/DisplayWordTask.cs | 7 +- Contralto/CPU/Tasks/EmulatorTask.cs | 9 +- Contralto/CPU/Tasks/MemoryRefreshTask.cs | 10 +- Contralto/CPU/Tasks/Task.cs | 52 ++- Contralto/CPU/UCodeDisassembler.cs | 3 - Contralto/CPU/UCodeMemory.cs | 10 +- Contralto/Contralto.csproj | 23 +- Contralto/{OctalHelpers.cs => Conversion.cs} | 14 +- Contralto/Debugger.cs | 1 + Contralto/Display/DisplayController.cs | 20 +- Contralto/Display/FakeDisplayController.cs | 11 +- Contralto/IClockable.cs | 8 +- Contralto/IO/DiabloPack.cs | 4 - Contralto/IO/DiskController.cs | 391 +++++++++---------- Contralto/IO/Keyboard.cs | 7 +- Contralto/Logging/Log.cs | 4 - Contralto/Memory/IMemoryMappedDevice.cs | 9 +- Contralto/Memory/Memory.cs | 5 - Contralto/Memory/MemoryBus.cs | 7 +- Contralto/Program.cs | 11 +- Contralto/Scheduler.cs | 211 ++++++++++ 33 files changed, 591 insertions(+), 436 deletions(-) rename Contralto/{OctalHelpers.cs => Conversion.cs} (68%) create mode 100644 Contralto/Scheduler.cs diff --git a/Contralto.sln b/Contralto.sln index 6d4f30c..88d78a0 100644 --- a/Contralto.sln +++ b/Contralto.sln @@ -11,13 +11,19 @@ Global EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x64.ActiveCfg = Debug|x64 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x64.Build.0 = Debug|x64 {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|Any CPU.Build.0 = Release|Any CPU + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x64.ActiveCfg = Release|x64 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs index 4d77140..3fc6fbe 100644 --- a/Contralto/AltoSystem.cs +++ b/Contralto/AltoSystem.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Contralto.CPU; using Contralto.IO; using Contralto.Memory; using Contralto.Display; +using System.Timers; namespace Contralto { @@ -18,7 +15,9 @@ namespace Contralto public class AltoSystem { public AltoSystem() - { + { + _scheduler = new Scheduler(); + _cpu = new AltoCPU(this); _memBus = new MemoryBus(); _mem = new Memory.Memory(); @@ -32,25 +31,32 @@ namespace Contralto _memBus.AddDevice(_keyboard); // Register devices that need clocks - _clockableDevices = new List(); - _clockableDevices.Add(_memBus); - _clockableDevices.Add(_diskController); + _clockableDevices = new List(); + _clockableDevices.Add(_memBus); _clockableDevices.Add(_displayController); //_clockableDevices.Add(_fakeDisplayController); - _clockableDevices.Add(_cpu); + _clockableDevices.Add(_cpu); Reset(); + + Timer t = new Timer(); + t.AutoReset = true; + t.Interval = 1000; + t.Elapsed += T_Elapsed; + t.Start(); } public void Reset() { + _scheduler.Reset(); + _cpu.Reset(); _memBus.Reset(); _mem.Reset(); ALU.Reset(); Shifter.Reset(); _diskController.Reset(); - _displayController.Reset(); + _displayController.Reset(); } /// @@ -73,6 +79,10 @@ namespace Contralto { _clockableDevices[i].Clock(); } + + _scheduler.Clock(); + + _clocks++; } public AltoCPU CPU @@ -100,6 +110,11 @@ namespace Contralto get { return _keyboard; } } + public Scheduler Scheduler + { + get { return _scheduler; } + } + /// /// Time (in msec) for one system clock /// @@ -109,13 +124,24 @@ namespace Contralto get { return 0.00017; } // appx 170nsec, TODO: more accurate value? } + + private void T_Elapsed(object sender, ElapsedEventArgs e) + { + System.Console.WriteLine("{0} CPU clocks/sec %{1}. {2} fields/sec", _clocks, ((double)_clocks / 5882353.0) * 100.0, _displayController.Fields); + _clocks = 0; + _displayController.Fields = 0; + } + private AltoCPU _cpu; private MemoryBus _memBus; private Contralto.Memory.Memory _mem; private Keyboard _keyboard; private DiskController _diskController; private DisplayController _displayController; - private FakeDisplayController _fakeDisplayController; + // private FakeDisplayController _fakeDisplayController; + + private Scheduler _scheduler; + private ulong _clocks; private List _clockableDevices; } diff --git a/Contralto/CPU/ALU.cs b/Contralto/CPU/ALU.cs index 6b0125a..1c5868a 100644 --- a/Contralto/CPU/ALU.cs +++ b/Contralto/CPU/ALU.cs @@ -1,9 +1,4 @@ -using Contralto.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; namespace Contralto.CPU { @@ -30,7 +25,7 @@ namespace Contralto.CPU public static ushort Execute(AluFunction fn, ushort bus, ushort t, int skip) { - int r = 0; + int r; switch (fn) { case AluFunction.Bus: @@ -40,7 +35,7 @@ namespace Contralto.CPU case AluFunction.T: _carry = 0; // M = 1 - r= t; + r = t; break; case AluFunction.BusOrT: diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs index 66cd539..931fc05 100644 --- a/Contralto/CPU/CPU.cs +++ b/Contralto/CPU/CPU.cs @@ -1,12 +1,7 @@ -using Contralto.Memory; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Timers; namespace Contralto.CPU -{ +{ public enum TaskType { Invalid = -1, @@ -38,7 +33,7 @@ namespace Contralto.CPU _tasks[(int)TaskType.MemoryRefresh] = new MemoryRefreshTask(this); Reset(); - } + } public Task[] Tasks { @@ -123,7 +118,21 @@ namespace Contralto.CPU public void Clock() { - ExecuteNext(); + if (_currentTask.ExecuteNext()) + { + // Invoke the task switch, this will take effect after + // the NEXT instruction, not this one. + TaskSwitch(); + } + else + { + // If we have a new task, switch to it now. + if (_nextTask != null) + { + _currentTask = _nextTask; + _nextTask = null; + } + } } /// @@ -171,28 +180,7 @@ namespace Contralto.CPU public Task NextTask { get { return _nextTask; } - } - - private void ExecuteNext() - { - if (_currentTask.ExecuteNext()) - { - // Invoke the task switch, this will take effect after - // the NEXT instruction, not this one. - TaskSwitch(); - } - else - { - // If we have a new task, switch to it now. - if (_nextTask != null) - { - _currentTask = _nextTask; - _nextTask = null; - } - } - - _clocks++; - } + } private void TaskSwitch() { @@ -224,12 +212,9 @@ namespace Contralto.CPU // Task data private Task _nextTask; // The task to switch two after the next microinstruction private Task _currentTask; // The currently executing task - private Task[] _tasks = new Task[16]; - - private long _clocks; + private Task[] _tasks = new Task[16]; // The system this CPU belongs to - private AltoSystem _system; - + private AltoSystem _system; } } diff --git a/Contralto/CPU/ConstantMemory.cs b/Contralto/CPU/ConstantMemory.cs index 5912e89..be41885 100644 --- a/Contralto/CPU/ConstantMemory.cs +++ b/Contralto/CPU/ConstantMemory.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { diff --git a/Contralto/CPU/MicroInstruction.cs b/Contralto/CPU/MicroInstruction.cs index da185cf..d26b654 100644 --- a/Contralto/CPU/MicroInstruction.cs +++ b/Contralto/CPU/MicroInstruction.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { diff --git a/Contralto/CPU/NovaDisassembler.cs b/Contralto/CPU/NovaDisassembler.cs index 1f64461..2a56492 100644 --- a/Contralto/CPU/NovaDisassembler.cs +++ b/Contralto/CPU/NovaDisassembler.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU.Nova { diff --git a/Contralto/CPU/Shifter.cs b/Contralto/CPU/Shifter.cs index ce51cf5..9178e6c 100644 --- a/Contralto/CPU/Shifter.cs +++ b/Contralto/CPU/Shifter.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { @@ -16,7 +12,7 @@ namespace Contralto.CPU RotateRight, } - //NOTE: FOR NOVA (NOVEL) SHIFTS (from aug '76 manual): + // NOTE: FOR NOVA (NOVEL) SHIFTS (from aug '76 manual): // The emulator has two additional bits of state, the SKIP and CARRY flip flops.CARRY is identical // to the Nova carry bit, and is set or cleared as appropriate when the DNS+- (do Nova shifts) // function is executed.DNS also addresses R from(1R[3 - 4] XOR 3), and sets the SKIP flip flop if @@ -95,13 +91,7 @@ namespace Contralto.CPU /// Normal input to be shifted /// CPU t register, for MAGIC shifts only public static ushort DoOperation(ushort input, ushort t) - { - // Sanity check: MAGIC and DNS cannot be set at the same time. - if (_magic && _dns) - { - throw new InvalidOperationException("Both MAGIC and DNS bits are set."); - } - + { switch(_op) { case ShifterOp.Invalid: @@ -119,6 +109,11 @@ namespace Contralto.CPU // "MAGIC places the high order bit of T into the low order bit of the // shifter output on left shifts..." _output |= (ushort)((t & 0x8000) >> 15); + + if (_count != 1) + { + throw new NotImplementedException("magic LCY 8 not implemented yet."); + } } else if (_dns) { @@ -179,7 +174,7 @@ namespace Contralto.CPU // "Swap the 8-bit halves of the 16-bit result. The carry is not affected." // _output = (ushort)(((input & 0xff00) >> 8) | ((input & 0x00ff) << 8)); - } + } break; case ShifterOp.RotateRight: diff --git a/Contralto/CPU/Tasks/CursorTask.cs b/Contralto/CPU/Tasks/CursorTask.cs index c2b1a96..d96c280 100644 --- a/Contralto/CPU/Tasks/CursorTask.cs +++ b/Contralto/CPU/Tasks/CursorTask.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { @@ -33,8 +29,7 @@ namespace Contralto.CPU break; default: - throw new InvalidOperationException(String.Format("Unhandled cursor F2 {0}.", cf2)); - break; + throw new InvalidOperationException(String.Format("Unhandled cursor F2 {0}.", cf2)); } } } diff --git a/Contralto/CPU/Tasks/DiskTask.cs b/Contralto/CPU/Tasks/DiskTask.cs index 31c8495..7907b82 100644 --- a/Contralto/CPU/Tasks/DiskTask.cs +++ b/Contralto/CPU/Tasks/DiskTask.cs @@ -1,11 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Memory; -using Contralto.Logging; namespace Contralto.CPU { @@ -31,7 +24,17 @@ namespace Contralto.CPU protected override bool ExecuteInstruction(MicroInstruction instruction) { - bool task = base.ExecuteInstruction(instruction); + bool task = base.ExecuteInstruction(instruction); + + // Deal with SECLATE semantics: If the Disk Sector task wakes up and runs before + // the Disk Controller hits the SECLATE trigger time, then SECLATE remains false. + // Otherwise, when the trigger time is hit SECLATE is raised until + // the beginning of the next sector. + if (_taskType == TaskType.DiskSector) + { + // Sector task is running; clear enable for seclate signal + _cpu._system.DiskController.DisableSeclate(); + } return task; } @@ -190,6 +193,19 @@ namespace Contralto.CPU } } + protected override void ExecuteBlock() + { + // + // Update the WDINIT signal; this is based on WDALLOW (!_wdInhib) which sets WDINIT (this is done + // in KCOM way above). + // WDINIT is reset when BLOCK (a BLOCK F1 is being executed) and WDTSKACT (the disk word task is running) are 1. + // + if (_taskType == TaskType.DiskWord) + { + _cpu._system.DiskController.WDINIT = false; + } + } + /// /// The status of the INIT flag /// diff --git a/Contralto/CPU/Tasks/DisplayHorizontalTask.cs b/Contralto/CPU/Tasks/DisplayHorizontalTask.cs index 231e19c..5ce6ac1 100644 --- a/Contralto/CPU/Tasks/DisplayHorizontalTask.cs +++ b/Contralto/CPU/Tasks/DisplayHorizontalTask.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { @@ -37,12 +33,11 @@ namespace Contralto.CPU break; case DisplayHorizontalF2.SETMODE: - // NO-op for now + _cpu._system.DisplayController.SETMODE(_busData); break; default: - throw new InvalidOperationException(String.Format("Unhandled display word F2 {0}.", dh2)); - break; + throw new InvalidOperationException(String.Format("Unhandled display word F2 {0}.", dh2)); } } } diff --git a/Contralto/CPU/Tasks/DisplayVerticalTask.cs b/Contralto/CPU/Tasks/DisplayVerticalTask.cs index 6306a5d..f0a5c95 100644 --- a/Contralto/CPU/Tasks/DisplayVerticalTask.cs +++ b/Contralto/CPU/Tasks/DisplayVerticalTask.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { @@ -37,8 +33,7 @@ namespace Contralto.CPU break; default: - throw new InvalidOperationException(String.Format("Unhandled display vertical F2 {0}.", dv2)); - break; + throw new InvalidOperationException(String.Format("Unhandled display vertical F2 {0}.", dv2)); } } } diff --git a/Contralto/CPU/Tasks/DisplayWordTask.cs b/Contralto/CPU/Tasks/DisplayWordTask.cs index 6727f62..7867459 100644 --- a/Contralto/CPU/Tasks/DisplayWordTask.cs +++ b/Contralto/CPU/Tasks/DisplayWordTask.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU @@ -38,8 +34,7 @@ namespace Contralto.CPU break; default: - throw new InvalidOperationException(String.Format("Unhandled display word F2 {0}.", dw2)); - break; + throw new InvalidOperationException(String.Format("Unhandled display word F2 {0}.", dw2)); } } } diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs index 482cccc..eb1fca9 100644 --- a/Contralto/CPU/Tasks/EmulatorTask.cs +++ b/Contralto/CPU/Tasks/EmulatorTask.cs @@ -1,10 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Memory; namespace Contralto.CPU { @@ -316,8 +310,7 @@ namespace Contralto.CPU break; default: - throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2)); - break; + throw new InvalidOperationException(String.Format("Unhandled emulator F2 {0}.", ef2)); } } diff --git a/Contralto/CPU/Tasks/MemoryRefreshTask.cs b/Contralto/CPU/Tasks/MemoryRefreshTask.cs index 397314b..7cc0927 100644 --- a/Contralto/CPU/Tasks/MemoryRefreshTask.cs +++ b/Contralto/CPU/Tasks/MemoryRefreshTask.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Memory; - -namespace Contralto.CPU +namespace Contralto.CPU { public partial class AltoCPU { diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs index b5e12f3..746cdd4 100644 --- a/Contralto/CPU/Tasks/Task.cs +++ b/Contralto/CPU/Tasks/Task.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Contralto.Memory; @@ -76,7 +72,7 @@ namespace Contralto.CPU // Grab BLOCK bit so that other tasks / hardware can look at it _block = instruction.F1 == SpecialFunction1.Block; - + //Console.WriteLine("R5:{0},R6:{1},IR:{2} - {3}:{4}", OctalHelpers.ToOctal(_cpu._r[5]), OctalHelpers.ToOctal(_cpu._r[6]), OctalHelpers.ToOctal(_cpu._ir), OctalHelpers.ToOctal(_mpc), UCodeDisassembler.DisassembleInstruction(instruction, _taskType)); @@ -92,9 +88,10 @@ namespace Contralto.CPU protected virtual bool ExecuteInstruction(MicroInstruction instruction) { bool nextTask = false; - bool swMode = false; - ushort aluData = 0; - ushort nextModifier = 0; + bool swMode = false; + bool block = false; + ushort aluData; + ushort nextModifier; _loadR = false; _loadS = false; _rSelect = 0; @@ -109,13 +106,13 @@ namespace Contralto.CPU // the memory isn't ready yet. // TODO: this needs to be seriously cleaned up. // - if ((instruction.BS == BusSource.ReadMD && - (instruction.F1 != SpecialFunction1.Constant && - instruction.F2 != SpecialFunction2.Constant)) || // ReadMD only occurs if not reading from constant ROM. + bool constantAccess = + instruction.F1 == SpecialFunction1.Constant || + instruction.F2 == SpecialFunction2.Constant; + if ((instruction.BS == BusSource.ReadMD && !constantAccess) || // ReadMD only occurs if not reading from constant ROM. instruction.F1 == SpecialFunction1.LoadMAR || instruction.F2 == SpecialFunction2.StoreMD) { - MemoryOperation op; if (instruction.BS == BusSource.ReadMD) @@ -148,8 +145,7 @@ namespace Contralto.CPU ExecuteSpecialFunction2Early(instruction); // Select BUS data. - if (instruction.F1 != SpecialFunction1.Constant && - instruction.F2 != SpecialFunction2.Constant) + if (!constantAccess) { // Normal BUS data (not constant ROM access). switch (instruction.BS) @@ -201,8 +197,7 @@ namespace Contralto.CPU break; default: - throw new InvalidOperationException(String.Format("Unhandled bus source {0}.", instruction.BS)); - break; + throw new InvalidOperationException(String.Format("Unhandled bus source {0}.", instruction.BS)); } } else @@ -222,8 +217,7 @@ namespace Contralto.CPU // selection of R; they do not affect the address supplied to the constant ROM." // Hence we use the unmodified RSELECT value here and above. if ((int)instruction.BS > 4 || - instruction.F1 == SpecialFunction1.Constant || - instruction.F2 == SpecialFunction2.Constant) + constantAccess) { _busData &= ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)]; } @@ -283,9 +277,12 @@ namespace Contralto.CPU case SpecialFunction1.Block: // Technically this is to be invoked by the hardware device associated with a task. - // That logic would be circituous and unless there's a good reason not to that is discovered + // That logic would be circuituous and unless there's a good reason not to that is discovered // later, I'm just going to directly block the current task here. _cpu.BlockTask(this._taskType); + + // Let task-specific behavior take place at the end of this cycle. + block = true; break; case SpecialFunction1.LLSH1: @@ -451,6 +448,14 @@ namespace Contralto.CPU UCodeMemory.SwitchMode((ushort)(instruction.NEXT | nextModifier)); } + // + // Do task-specific BLOCK behavior if the last instruction had a BLOCK F1. + // + if (block) + { + ExecuteBlock(); + } + // // Select next address, using the address modifier from the last instruction. // @@ -489,6 +494,15 @@ namespace Contralto.CPU // Nothing by default. } + /// + /// Allows task-specific handling for BLOCK microinstructions. + /// (Disk and Display BLOCKs have side effects apart from removing Wakeup from the task, for example). + /// + protected virtual void ExecuteBlock() + { + // Nothing by default + } + // // Per uInstruction Task Data: // Modified by both the base Task implementation and any subclasses diff --git a/Contralto/CPU/UCodeDisassembler.cs b/Contralto/CPU/UCodeDisassembler.cs index f0e9a72..eafafa4 100644 --- a/Contralto/CPU/UCodeDisassembler.cs +++ b/Contralto/CPU/UCodeDisassembler.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { diff --git a/Contralto/CPU/UCodeMemory.cs b/Contralto/CPU/UCodeMemory.cs index 57520e9..55c2d01 100644 --- a/Contralto/CPU/UCodeMemory.cs +++ b/Contralto/CPU/UCodeMemory.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.CPU { @@ -131,7 +127,8 @@ namespace Contralto.CPU if (_ramBank > 0) { - throw new InvalidOperationException("RAM bank > 0, unexpected."); + //throw new InvalidOperationException("RAM bank > 0, unexpected."); + return 0xffff; } // pretend no ram for the moment @@ -164,7 +161,8 @@ namespace Contralto.CPU if (_ramBank > 0) { - throw new InvalidOperationException("RAM bank > 0, unexpected."); + //throw new InvalidOperationException("RAM bank > 0, unexpected."); + return; } Logging.Log.Write(Logging.LogComponent.Microcode, "CRAM address for write: Bank {0}, addr {1}", diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 3acd376..60aebbb 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -31,6 +31,26 @@ prompt 4 + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + @@ -76,9 +96,10 @@ - + + diff --git a/Contralto/OctalHelpers.cs b/Contralto/Conversion.cs similarity index 68% rename from Contralto/OctalHelpers.cs rename to Contralto/Conversion.cs index 3bc4ccc..f7c9630 100644 --- a/Contralto/OctalHelpers.cs +++ b/Contralto/Conversion.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto { @@ -30,5 +26,15 @@ namespace Contralto string octalString = Convert.ToString(i, 8); return new String('0', digits - octalString.Length) + octalString; } + + /// + /// Conversion from millseconds to nanoseconds + /// + public static readonly ulong MsecToNsec = 1000000; + + /// + /// Conversion from microseconds to nanoseconds + /// + public static readonly ulong UsecToNsec = 1000; } } diff --git a/Contralto/Debugger.cs b/Contralto/Debugger.cs index 5ca4306..c6a200e 100644 --- a/Contralto/Debugger.cs +++ b/Contralto/Debugger.cs @@ -90,6 +90,7 @@ namespace Contralto _displayBuffer.UnlockBits(data); DisplayBox.Refresh(); + // If you want interlacing to be more visible: Array.Clear(_displayData, 0, _displayData.Length); } diff --git a/Contralto/Display/DisplayController.cs b/Contralto/Display/DisplayController.cs index 450dd83..ad94721 100644 --- a/Contralto/Display/DisplayController.cs +++ b/Contralto/Display/DisplayController.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Logging; +using System.Collections.Generic; using Contralto.CPU; namespace Contralto.Display @@ -27,6 +21,12 @@ namespace Contralto.Display _display = display; } + public int Fields + { + get { return _fields; } + set { _fields = value; } + } + public void Reset() { _evenField = true; @@ -254,9 +254,9 @@ namespace Contralto.Display // Timing constants // 38uS per scanline; 4uS for hblank. // ~35 scanlines for vblank (1330uS) - private const double _wordClocks = (34.0 / 38.0) / 0.017; // uSec to clocks - private const double _horizontalBlankClocks = 4.0 / 0.017; - private const double _verticalBlankClocks = 1333.0 / 0.017; + private const double _wordClocks = (34.0 / 38.0) / 0.060; // uSec to clocks + private const double _horizontalBlankClocks = 4.0 / 0.060; + private const double _verticalBlankClocks = 1333.0 / 0.060; } diff --git a/Contralto/Display/FakeDisplayController.cs b/Contralto/Display/FakeDisplayController.cs index 4dcbb23..19a184a 100644 --- a/Contralto/Display/FakeDisplayController.cs +++ b/Contralto/Display/FakeDisplayController.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Logging; -using Contralto.CPU; - -namespace Contralto.Display +namespace Contralto.Display { /// /// FakeDisplayController draws the display without the aid of the diff --git a/Contralto/IClockable.cs b/Contralto/IClockable.cs index 64da5a9..d447f42 100644 --- a/Contralto/IClockable.cs +++ b/Contralto/IClockable.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Contralto +namespace Contralto { /// /// Used by classes implementing devices that are clocked (i.e. that are dependent diff --git a/Contralto/IO/DiabloPack.cs b/Contralto/IO/DiabloPack.cs index 3fac793..2e06379 100644 --- a/Contralto/IO/DiabloPack.cs +++ b/Contralto/IO/DiabloPack.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.IO { diff --git a/Contralto/IO/DiskController.cs b/Contralto/IO/DiskController.cs index 6090c17..36895b5 100644 --- a/Contralto/IO/DiskController.cs +++ b/Contralto/IO/DiskController.cs @@ -1,17 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.Memory; using System.IO; using Contralto.Logging; using Contralto.CPU; namespace Contralto.IO { - public class DiskController : IClockable + public class DiskController { public DiskController(AltoSystem system) { @@ -22,7 +16,7 @@ namespace Contralto.IO _pack = new DiabloPack(DiabloDiskType.Diablo31); // TODO: this does not belong here. - FileStream fs = new FileStream("Disk\\tdisk4.dsk", FileMode.Open, FileAccess.Read); + FileStream fs = new FileStream("Disk\\games.dsk", FileMode.Open, FileAccess.Read); _pack.Load(fs); @@ -57,8 +51,7 @@ namespace Contralto.IO // "In addition, it causes the head address bit to be loaded from KDATA[13]." int newHead = (_kDataWrite & 0x4) >> 2; - - Log.Write(LogComponent.DiskController, "At sector time {0}:", _elapsedSectorTime); + if (newHead != _head) { // If we switch heads, we need to reload the sector @@ -121,6 +114,7 @@ namespace Contralto.IO public bool WDINIT { get { return _wdInit; } + set { _wdInit = value; } } public ushort KSTAT @@ -149,10 +143,7 @@ namespace Contralto.IO /// /// This is a hack to see how the microcode expects INIT to work /// - public bool RecordInit - { - get { return _sectorWordTime < 10; } - } + public int Cylinder { @@ -181,7 +172,7 @@ namespace Contralto.IO public double ClocksUntilNextSector { - get { return _sectorClocks - _elapsedSectorTime; } + get { return 0; } // _sectorClocks - _elapsedSectorTime; } } public bool Ready @@ -197,8 +188,7 @@ namespace Contralto.IO public void Reset() { ClearStatus(); - _recNo = 0; - _elapsedSectorTime = 0.0; + _recNo = 0; _cylinder = _destCylinder = 0; _sector = 0; _head = 0; @@ -213,126 +203,125 @@ namespace Contralto.IO _wdInit = false; _diskBitCounterEnable = false; - _sectorWordIndex = 0; - _sectorWordTime = 0; + _sectorWordIndex = 0; InitSector(); // Wakeup the sector task first thing _system.CPU.WakeupTask(CPU.TaskType.DiskSector); + + // Create events to be reused during execution + _sectorEvent = new Event(_sectorDuration, null, SectorCallback); + _wordEvent = new Event(_wordDuration, null, WordCallback); + _seekEvent = new Event(0, null, SeekCallback); + _seclateEvent = new Event(_seclateDuration, null, SeclateCallback); + + // And schedule the first sector pulse. + _system.Scheduler.Schedule(_sectorEvent); } - public void Clock() + /// + /// Allows the Disk Sector task to disable the SECLATE signal. + /// + public void DisableSeclate() { - _elapsedSectorTime++; + _seclateEnable = false; + } - // TODO: only signal sector changes if disk is loaded, etc. - if (_elapsedSectorTime > _sectorClocks ) + private void SectorCallback(ulong timeNsec, ulong skewNsec, object context) + { + // + // Next sector; move to next sector and wake up Disk Sector task. + // + _sector = (_sector + 1) % 12; + + _kStat = (ushort)((_kStat & 0x0fff) | (_sector << 12)); + + // Reset internal state machine for sector data + _sectorWordIndex = 0; + + _kDataRead = 0; + + // Load new sector in + LoadSector(); + + // Only wake up if not actively seeking. + if ((_kStat & 0x0040) == 0) { - // - // Next sector; save fractional part of elapsed time (to more accurately keep track of time), move to next sector - // and wake up sector task. - // - _elapsedSectorTime -= _sectorClocks; + Log.Write(LogType.Verbose, LogComponent.DiskController, "Waking up sector task for C/H/S {0}/{1}/{2}", _cylinder, _head, _sector); + _system.CPU.WakeupTask(CPU.TaskType.DiskSector); - _sector = (_sector + 1) % 12; + // Reset SECLATE + _seclate = false; + _seclateEnable = true; + _kStat &= 0xffef; - _kStat = (ushort)((_kStat & 0x0fff) | (_sector << 12)); + // Schedule a disk word wakeup to spin the disk + _wordEvent.TimestampNsec = _wordDuration; + _system.Scheduler.Schedule(_wordEvent); - // Reset internal state machine for sector data - _sectorWordIndex = 0; - _sectorWordTime = 0.0; - - _kDataRead = 0; - - // Load new sector in - LoadSector(); - - // Only wake up if not actively seeking. - if ((_kStat & 0x0040) == 0) - { - Log.Write(LogType.Verbose, LogComponent.DiskController, "Waking up sector task for C/H/S {0}/{1}/{2}", _cylinder, _head, _sector); - _system.CPU.WakeupTask(CPU.TaskType.DiskSector); - - // Reset SECLATE - _seclateClocks = 0; - _seclate = false; - _seclateEnable = true; - _kStat &= 0xffef; - } + // Schedule SECLATE trigger + _seclateEvent.TimestampNsec = _seclateDuration; + _system.Scheduler.Schedule(_seclateEvent); } - // If seek is in progress, move closer to the desired cylinder... - // TODO: move bitfields to enums / constants, this is getting silly. - if ((_kStat & 0x0040) != 0) - { - _elapsedSeekTime++; - if (_elapsedSeekTime > _seekClocks) - { - _elapsedSeekTime -= _seekClocks; + // Schedule next sector pulse + _sectorEvent.TimestampNsec = _sectorDuration - skewNsec; + _system.Scheduler.Schedule(_sectorEvent); + } - if (_cylinder < _destCylinder) - { - _cylinder++; - } - else if (_cylinder > _destCylinder) - { - _cylinder--; - } - - Log.Write(LogComponent.DiskController, "Seek progress: cylinder {0} reached.", _cylinder); - - // Are we *there* yet? - if (_cylinder == _destCylinder) - { - // clear Seek bit - _kStat &= 0xffbf; - - Log.Write(LogComponent.DiskController, "Seek to {0} completed.", _cylinder); - } - } - } - - // - // Spin the disk platter and read in words as applicable. - // + private void WordCallback(ulong timeNsec, ulong skewNsec, object context) + { SpinDisk(); - // Deal with SECLATE semantics: If the Disk Sector task wakes up and runs before - // we hit the trigger time, then _seclate remains false. - // Otherwise, when the trigger time is hit _seclate is raised until - // the beginning of the next sector. - if (_system.CPU.CurrentTask.Priority == (int)TaskType.DiskSector) + // Schedule next word if this wasn't the last word this sector. + if (_sectorWordIndex < _sectorWordCount) { - // Sector task is running; clear enable for seclate signal - _seclateEnable = false; + _wordEvent.TimestampNsec = _wordDuration - skewNsec; + _system.Scheduler.Schedule(_wordEvent); } - - if (_seclateEnable) - { - _seclateClocks++; - - if (_seclateClocks > _seclateDuration) - { - _seclate = true; - _kStat |= 0x0010; // TODO: move to constant field! - //Log.Write(LogComponent.DiskSectorTask, "SECLATE for sector {0} at sector time {1}", _sector, _elapsedSectorTime); - _seclateEnable = false; - } - } - - // - // Update the WDINIT signal; this is based on WDALLOW (!_wdInhib) which sets WDINIT (this is done - // in KCOM way above). - // WDINIT is reset when BLOCK (a BLOCK F1 is being executed) and WDTSKACT (the disk word task is running) are 1. - // - if (_system.CPU.CurrentTask.Priority == (int)TaskType.DiskWord && - _system.CPU.CurrentTask.BLOCK) - { - _wdInit = false; - } } + private void SeekCallback(ulong timeNsec, ulong skewNsec, object context) + { + if (_cylinder < _destCylinder) + { + _cylinder++; + } + else if (_cylinder > _destCylinder) + { + _cylinder--; + } + + Log.Write(LogComponent.DiskController, "Seek progress: cylinder {0} reached.", _cylinder); + + // Are we *there* yet? + if (_cylinder == _destCylinder) + { + // clear Seek bit + _kStat &= 0xffbf; + + Log.Write(LogComponent.DiskController, "Seek to {0} completed.", _cylinder); + } + else + { + // Nope. + // Schedule next seek step. + _seekEvent.TimestampNsec = _seekDuration - skewNsec; + _system.Scheduler.Schedule(_seekEvent); + } + } + + private void SeclateCallback(ulong timeNsec, ulong skewNsec, object context) + { + if (_seclateEnable) + { + _seclate = true; + _kStat |= 0x0010; // TODO: move to constant field! + Log.Write(LogComponent.DiskSectorTask, "SECLATE for sector {0}.", _sector); + } + } + public void ClearStatus() { // "...clears KSTAT[13]." (chksum error flag) @@ -394,17 +383,19 @@ namespace Contralto.IO _kStat |= 0x0040; // And figure out how long this will take. - _seekClocks = CalculateSeekTime(); - _elapsedSeekTime = 0.0; + _seekDuration = CalculateSeekTime(); + + _seekEvent.TimestampNsec = _seekDuration; + _system.Scheduler.Schedule(_seekEvent); - Log.Write(LogComponent.DiskController, "Seek to {0} from {1} commencing. Will take {2} clocks.", _destCylinder, _cylinder, _seekClocks); + Log.Write(LogComponent.DiskController, "Seek to {0} from {1} commencing. Will take {2} nsec.", _destCylinder, _cylinder, _seekDuration); } } - private double CalculateSeekTime() + private ulong CalculateSeekTime() { // How many cylinders are we moving? - int dt = Math.Abs(_destCylinder - _cylinder); + int dt = Math.Abs(_destCylinder - _cylinder); // // From the Hardware Manual, pg 43: @@ -412,7 +403,7 @@ namespace Contralto.IO // double seekTimeMsec = 15.0 + 8.6 * Math.Sqrt(dt); - return (seekTimeMsec / AltoSystem.ClockInterval) / 100; // div 100 to make things faster for now + return (ulong)(seekTimeMsec * Conversion.MsecToNsec) / 100; // hack to speed things up } /// @@ -437,80 +428,71 @@ namespace Contralto.IO // to generate these slices during these periods (and the clock comes from the // disk itself when actual data is present). For our purposes, the two clocks // are one and the same. - // + // - // Move the disk forward one clock - _sectorWordTime++; + // + // Pick out the word that just passed under the head. This may not be + // actual data (it could be the pre-header delay, inter-record gaps or sync words) + // and we may not actually end up doing anything with it, but we may + // need it to decide whether to do anything at all. + // + ushort diskWord = _sectorData[_sectorWordIndex].Data; - // If we have reached a new word timeslice, do something appropriate. - if (_sectorWordTime > _wordDuration) + bool bWakeup = false; + // + // If the word task is enabled AND the write ("crystal") clock is enabled + // then we will wake up the word task now. + // + if (!_seclate && !_wdInhib && !_bClkSource) + { + bWakeup = true; + } + + // + // If the clock is enabled OR the WFFO bit is set (go ahead and run the bit clock) + // and we weren't late reading this sector, then we will wake up the word task + // and read in the data if transfers are not inhibited. TODO: this should only happen on reads. + // + if (!_seclate && (_wffo || _diskBitCounterEnable)) { - // Save the fractional portion of the timeslice for the next slice - _sectorWordTime -= _wordDuration; - - // - // Pick out the word that just passed under the head. This may not be - // actual data (it could be the pre-header delay, inter-record gaps or sync words) - // and we may not actually end up doing anything with it, but we may - // need it to decide whether to do anything at all. - // - ushort diskWord = _sectorData[_sectorWordIndex].Data; - - bool bWakeup = false; - // - // If the word task is enabled AND the write ("crystal") clock is enabled - // then we will wake up the word task now. - // - if (!_seclate && !_wdInhib && !_bClkSource) - { - bWakeup = true; - } - - // - // If the clock is enabled OR the WFFO bit is set (go ahead and run the bit clock) - // and we weren't late reading this sector, then we will wake up the word task - // and read in the data if transfers are not inhibited. TODO: this should only happen on reads. - // - if (!_seclate && (_wffo || _diskBitCounterEnable)) + if (!_xferOff) { - if (!_xferOff) + if (_debugRead) { - if (_debugRead) - { - //Console.WriteLine("--- missed word {0}({1}) ---", _sectorWordIndex, _kDataRead); - } - - Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Sector {0} Word {1} read into KDATA", _sector, Conversion.ToOctal(diskWord)); - _kDataRead = diskWord; - _debugRead = _sectorData[_sectorWordIndex].Type == CellType.Data; + //Console.WriteLine("--- missed word {0}({1}) ---", _sectorWordIndex, _kDataRead); } - if (!_wdInhib) - { - bWakeup = true; - } + Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Sector {0} Word {1} read into KDATA", _sector, Conversion.ToOctal(diskWord)); + _kDataRead = diskWord; + _debugRead = _sectorData[_sectorWordIndex].Type == CellType.Data; } - // - // If the WFFO bit is cleared (wait for the sync word to be read) - // then we check the word for a "1" (the sync word) to enable - // the clock. This occurs late in the cycle so that the NEXT word - // (not the sync word) is actually read. TODO: this should only happen on reads. - // - if (!_wffo && diskWord == 1) - { - _diskBitCounterEnable = true; - } - - if (bWakeup) + if (!_wdInhib) { - Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Word task awoken for word {0}.", _sectorWordIndex); - _system.CPU.WakeupTask(TaskType.DiskWord); + bWakeup = true; } + } - // Last, move to the next word. - _sectorWordIndex++; - } + // + // If the WFFO bit is cleared (wait for the sync word to be read) + // then we check the word for a "1" (the sync word) to enable + // the clock. This occurs late in the cycle so that the NEXT word + // (not the sync word) is actually read. TODO: this should only happen on reads. + // + if (!_wffo && diskWord == 1) + { + _diskBitCounterEnable = true; + } + + if (bWakeup) + { + Log.Write(LogType.Verbose, LogComponent.DiskWordTask, "Word task awoken for word {0}.", _sectorWordIndex); + _system.CPU.WakeupTask(TaskType.DiskWord); + } + + // Last, move to the next word. + _sectorWordIndex++; + } private void LoadSector() @@ -584,7 +566,7 @@ namespace Contralto.IO _sectorData[_dataOffset] = new DataCell(1, CellType.Sync); // read-postamble - for (int i = _dataOffset + 257; i < _sectorWords;i++) + for (int i = _dataOffset + 257; i < _sectorWordCount;i++) { _sectorData[i] = new DataCell(0, CellType.Gap); } @@ -648,14 +630,7 @@ namespace Contralto.IO // WDINIT signal private bool _wdInit; - // Sector timing. Based on table on pg. 43 of the Alto Hardware Manual - private double _elapsedSectorTime; // elapsed time in this sector (in clocks) - private const double _sectorDuration = (40.0 / 12.0); // time in msec for one sector - private const double _sectorClocks = _sectorDuration / (0.00017); // number of clock cycles per sector time. - - private int _sectorWordIndex; - private double _sectorWordTime; - + // Sector timing. Based on table on pg. 43 of the Alto Hardware Manual // From altoconsts23.mu: [all constants in octal, for reference] // $MFRRDL $177757; DISK HEADER READ DELAY IS 21 WORDS @@ -664,10 +639,14 @@ namespace Contralto.IO // $MIR0BL $177775; DISK INTERRECORD PREAMBLE IS 3 WORDS <<-- writing // $MRPAL $177775; DISK READ POSTAMBLE LENGTH IS 3 WORDS // $MWPAL $177773; DISK WRITE POSTAMBLE LENGTH IS 5 WORDS <<-- writing, clearly. - private const int _sectorWords = 269 + 22 + 34; // Based on : 269 data words (+ cksums) / sector, + X words for delay / preamble / sync - private const double _wordDuration = (_sectorClocks / (double)_sectorWords); - private const double _headerReadDelay = 17; - private const double _interRecordDelay = 4; + private static ulong _sectorDuration = (ulong)((40.0 / 12.0) * Conversion.MsecToNsec); // time in nsec for one sector + private static int _sectorWordCount = 269 + 22 + 34; // Based on : 269 data words (+ cksums) / sector, + X words for delay / preamble / sync + private static ulong _wordDuration = (ulong)(_sectorDuration / (ulong)(_sectorWordCount + 1)); // time in nsec for one word + private int _sectorWordIndex; // current word being read + + private Event _sectorEvent; + private Event _wordEvent; + // offsets in words for start of data in sector private const int _headerOffset = 22; @@ -676,10 +655,16 @@ namespace Contralto.IO // SECLATE data. // 8.5uS for seclate delay (approx. 50 clocks) - private const double _seclateDuration = 0.0086 / 0.00017; + private static ulong _seclateDuration = 85 * Conversion.UsecToNsec; private bool _seclateEnable; private bool _seclate; - private double _seclateClocks; + private Event _seclateEvent; + + // Cylinder seek time (in nsec) Again, see the manual. + // Timing varies based on how many cylinders are being traveled during a seek; see + // CalculateSeekTime() for more. + private ulong _seekDuration; + private Event _seekEvent; // The data for the current sector private enum CellType @@ -706,14 +691,8 @@ namespace Contralto.IO } } - private DataCell[] _sectorData = new DataCell[_sectorWords]; - - - // Cylinder seek timing. Again, see the manual. - // Timing varies based on how many cylinders are being traveled during a seek; see - // CalculateSeekTime() for more. - private double _elapsedSeekTime; - private double _seekClocks; + private DataCell[] _sectorData = new DataCell[_sectorWordCount]; + // The pack loaded into the drive DiabloPack _pack; diff --git a/Contralto/IO/Keyboard.cs b/Contralto/IO/Keyboard.cs index 275d6bd..0f8576f 100644 --- a/Contralto/IO/Keyboard.cs +++ b/Contralto/IO/Keyboard.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Contralto.Memory; using Contralto.CPU; -using Contralto.Logging; namespace Contralto.IO { diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs index 0a4066c..12f6d72 100644 --- a/Contralto/Logging/Log.cs +++ b/Contralto/Logging/Log.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.Logging { diff --git a/Contralto/Memory/IMemoryMappedDevice.cs b/Contralto/Memory/IMemoryMappedDevice.cs index ccf8d34..c66c79c 100644 --- a/Contralto/Memory/IMemoryMappedDevice.cs +++ b/Contralto/Memory/IMemoryMappedDevice.cs @@ -1,9 +1,6 @@ -using Contralto.CPU; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; + +using Contralto.CPU; namespace Contralto.Memory { diff --git a/Contralto/Memory/Memory.cs b/Contralto/Memory/Memory.cs index 6706f49..1cc69f5 100644 --- a/Contralto/Memory/Memory.cs +++ b/Contralto/Memory/Memory.cs @@ -1,10 +1,5 @@ using Contralto.CPU; using Contralto.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.Memory { diff --git a/Contralto/Memory/MemoryBus.cs b/Contralto/Memory/MemoryBus.cs index dfa833e..b39c11c 100644 --- a/Contralto/Memory/MemoryBus.cs +++ b/Contralto/Memory/MemoryBus.cs @@ -1,10 +1,6 @@ using Contralto.CPU; -using Contralto.Logging; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Contralto.Memory { @@ -185,8 +181,7 @@ namespace Contralto.Memory 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."); - break; + throw new InvalidOperationException("Invalid ReadMR request during cycle 3 or 4 of memory operation."); case 5: // Single word read diff --git a/Contralto/Program.cs b/Contralto/Program.cs index d712372..0ec852e 100644 --- a/Contralto/Program.cs +++ b/Contralto/Program.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Contralto.CPU; -using Contralto.Memory; - -namespace Contralto +namespace Contralto { class Program { diff --git a/Contralto/Scheduler.cs b/Contralto/Scheduler.cs new file mode 100644 index 0000000..0f1ca5d --- /dev/null +++ b/Contralto/Scheduler.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; + + +namespace Contralto +{ + /// + /// The SchedulerEventCallback describes a delegate that is invoked whenever a scheduled event has + /// reached its due-date and is fired. + /// + /// The current Alto time (in nsec) + /// The delta between the requested exec time and the actual exec time (in nsec) + /// An object containing context useful to the scheduler of the event + public delegate void SchedulerEventCallback(ulong timeNsec, ulong skewNsec, object context); + + /// + /// + /// + public class Event + { + public Event(ulong timestampNsec, object context, SchedulerEventCallback callback) + { + _timestampNsec = timestampNsec; + _context = context; + _callback = callback; + } + + /// + /// The absolute time (in nsec) to raise the event. + /// + public ulong TimestampNsec + { + get { return _timestampNsec; } + set { _timestampNsec = value; } + } + + /// + /// An object containing context to be passed to the + /// event callback. + /// + public object Context + { + get { return _context; } + } + + /// + /// A delegate to be executed when the callback fires. + /// + public SchedulerEventCallback EventCallback + { + get { return _callback; } + } + + private ulong _timestampNsec; + private object _context; + private SchedulerEventCallback _callback; + } + + /// + /// The Scheduler class provides infrastructure for scheduling Alto time-based hardware events + /// (for example, sector marks, or video task wakeups). + /// + public class Scheduler + { + public Scheduler() + { + Reset(); + } + + public ulong CurrentTimeNsec + { + get { return _currentTimeNsec; } + } + + public void Reset() + { + _schedule = new SchedulerQueue(); + _currentTimeNsec = 0; + } + + public void Clock() + { + // + // Move one system clock forward in time. + // + _currentTimeNsec += _timeStepNsec; + + // + // See if we have any events waiting to fire at this timestep. + // + while (true) + { + if (_schedule.Top != null && _currentTimeNsec >= _schedule.Top.TimestampNsec) + { + // Pop the top event and fire the callback. + Event e = _schedule.Pop(); + e.EventCallback(_currentTimeNsec, _currentTimeNsec - e.TimestampNsec, e.Context); + } + else + { + // All done. + break; + } + } + } + + + public Event Schedule(Event e) + { + e.TimestampNsec += _currentTimeNsec; + _schedule.Push(e); + + return e; + } + + public void CancelEvent(Event e) + { + _schedule.Remove(e); + } + + private ulong _currentTimeNsec; + + private SchedulerQueue _schedule; + + // 170nsec is approximately one Alto system clock cycle and is the time-base for + // the scheduler. + private const ulong _timeStepNsec = 170; + } + + /// + /// Provides an "ordered" queue based on timestamp -- the top of the queue is always the + /// next event to be fired; a "push" places a new event in order on the current queue. + /// + public class SchedulerQueue + { + public SchedulerQueue() + { + _queue = new LinkedList(); + } + + public Event Top + { + get + { + if (_queue.Count > 0) + { + return _queue.First.Value; + } + else + { + return null; + } + } + } + + public void Push(Event e) + { + if (_queue.Count > 10) + { + Console.WriteLine("Count {0}", _queue.Count); + } + // Degenerate case: list is empty or new entry is earlier than the head of the list. + if (_queue.Count == 0 || _queue.First.Value.TimestampNsec >= e.TimestampNsec) + { + _queue.AddFirst(e); + return; + } + + // + // Do a linear search to find the place to put this in. + // Since we maintain a sorted list with every insertion we only need to find the first entry + // that the new entry is earlier (or equal) to. + // This will likely be adequate as the queue should never get incredibly deep; a binary + // search may be more performant if this is not the case. + // TODO: profile this and ensure this is the case. + // + LinkedListNode current = _queue.First; + while (current != null) + { + if (current.Value.TimestampNsec >= e.TimestampNsec) + { + _queue.AddBefore(current, e); + return; + } + + current = current.Next; + } + + // Add at end + _queue.AddLast(e); + } + + public Event Pop() + { + Event e = _queue.First.Value; + _queue.RemoveFirst(); + + return e; + } + + public void Remove(Event e) + { + _queue.Remove(e); + } + + /// + /// TODO: provide more optimal data structure here once profiling can be done. + /// + private LinkedList _queue; + } +}