mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-01-16 16:19:33 +00:00
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.
This commit is contained in:
parent
03661fc90b
commit
cbcfd2b47e
@ -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
|
||||
|
||||
@ -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<IClockable>();
|
||||
_clockableDevices.Add(_memBus);
|
||||
_clockableDevices.Add(_diskController);
|
||||
_clockableDevices = new List<IClockable>();
|
||||
_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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time (in msec) for one system clock
|
||||
/// </summary>
|
||||
@ -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<IClockable> _clockableDevices;
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto.CPU
|
||||
{
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto.CPU.Nova
|
||||
{
|
||||
|
||||
@ -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
|
||||
/// <param name="input">Normal input to be shifted</param>
|
||||
/// <param name="t">CPU t register, for MAGIC shifts only</param>
|
||||
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:
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The status of the INIT flag
|
||||
/// </summary>
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows task-specific handling for BLOCK microinstructions.
|
||||
/// (Disk and Display BLOCKs have side effects apart from removing Wakeup from the task, for example).
|
||||
/// </summary>
|
||||
protected virtual void ExecuteBlock()
|
||||
{
|
||||
// Nothing by default
|
||||
}
|
||||
|
||||
//
|
||||
// Per uInstruction Task Data:
|
||||
// Modified by both the base Task implementation and any subclasses
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto.CPU
|
||||
{
|
||||
|
||||
@ -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}",
|
||||
|
||||
@ -31,6 +31,26 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@ -76,9 +96,10 @@
|
||||
<Compile Include="Memory\IMemoryMappedDevice.cs" />
|
||||
<Compile Include="Memory\Memory.cs" />
|
||||
<Compile Include="Memory\MemoryBus.cs" />
|
||||
<Compile Include="OctalHelpers.cs" />
|
||||
<Compile Include="Conversion.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Scheduler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Disassembly\altocode24.mu" />
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conversion from millseconds to nanoseconds
|
||||
/// </summary>
|
||||
public static readonly ulong MsecToNsec = 1000000;
|
||||
|
||||
/// <summary>
|
||||
/// Conversion from microseconds to nanoseconds
|
||||
/// </summary>
|
||||
public static readonly ulong UsecToNsec = 1000;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// FakeDisplayController draws the display without the aid of the
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto
|
||||
namespace Contralto
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by classes implementing devices that are clocked (i.e. that are dependent
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
/// <summary>
|
||||
/// This is a hack to see how the microcode expects INIT to work
|
||||
/// </summary>
|
||||
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()
|
||||
/// <summary>
|
||||
/// Allows the Disk Sector task to disable the SECLATE signal.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Contralto.Logging
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
211
Contralto/Scheduler.cs
Normal file
211
Contralto/Scheduler.cs
Normal file
@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace Contralto
|
||||
{
|
||||
/// <summary>
|
||||
/// The SchedulerEventCallback describes a delegate that is invoked whenever a scheduled event has
|
||||
/// reached its due-date and is fired.
|
||||
/// </summary>
|
||||
/// <param name="timeNsec">The current Alto time (in nsec)</param>
|
||||
/// <param name="skew">The delta between the requested exec time and the actual exec time (in nsec)</param>
|
||||
/// <param name="context">An object containing context useful to the scheduler of the event</param>
|
||||
public delegate void SchedulerEventCallback(ulong timeNsec, ulong skewNsec, object context);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Event
|
||||
{
|
||||
public Event(ulong timestampNsec, object context, SchedulerEventCallback callback)
|
||||
{
|
||||
_timestampNsec = timestampNsec;
|
||||
_context = context;
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The absolute time (in nsec) to raise the event.
|
||||
/// </summary>
|
||||
public ulong TimestampNsec
|
||||
{
|
||||
get { return _timestampNsec; }
|
||||
set { _timestampNsec = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object containing context to be passed to the
|
||||
/// event callback.
|
||||
/// </summary>
|
||||
public object Context
|
||||
{
|
||||
get { return _context; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to be executed when the callback fires.
|
||||
/// </summary>
|
||||
public SchedulerEventCallback EventCallback
|
||||
{
|
||||
get { return _callback; }
|
||||
}
|
||||
|
||||
private ulong _timestampNsec;
|
||||
private object _context;
|
||||
private SchedulerEventCallback _callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Scheduler class provides infrastructure for scheduling Alto time-based hardware events
|
||||
/// (for example, sector marks, or video task wakeups).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SchedulerQueue
|
||||
{
|
||||
public SchedulerQueue()
|
||||
{
|
||||
_queue = new LinkedList<Event>();
|
||||
}
|
||||
|
||||
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<Event> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: provide more optimal data structure here once profiling can be done.
|
||||
/// </summary>
|
||||
private LinkedList<Event> _queue;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user