1
0
mirror of https://github.com/livingcomputermuseum/sImlac.git synced 2026-01-17 08:43:47 +00:00

Compare commits

..

No commits in common. "master" and "v0.2" have entirely different histories.
master ... v0.2

33 changed files with 992 additions and 4133 deletions

4
.gitignore vendored
View File

@ -190,7 +190,3 @@ _Pvt_Extensions/
ModelManifest.xml
/sImlac.VC.VC.opendb
/sImlac.VC.db
/.vs/imlac/v15/Server/sqlite3/db.lock
/.vs/imlac/v15/Server/sqlite3/storage.ide
/.vs/imlac/v15/Server/sqlite3/storage.ide-shm
/.vs/imlac/v15/Server/sqlite3/storage.ide-wal

View File

@ -1,2 +0,0 @@
language: csharp
solution: imlac.sln

View File

@ -3,6 +3,6 @@
"",
"\\imlac"
],
"SelectedNode": "\\imlac.sln",
"SelectedNode": "\\imlac",
"PreviewInSolutionExplorer": false
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
![Build Status](https://api.travis-ci.org/livingcomputermuseum/sImlac.svg)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/butfmm8cd5vge16l?svg=true)](https://ci.appveyor.com/project/larsbrinkhoff/sImlac/history)

View File

@ -1,8 +0,0 @@
platform:
- x86
install:
- nuget restore
build:
project: imlac.sln
artifacts:
- path: imlac/bin

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static imlac.ImlacSystem;
namespace imlac
{
public enum ImlacCPUType
{
PDS1,
PDS4,
}
public static class Configuration
{
static Configuration()
{
MITMode = false;
CPUType = ImlacCPUType.PDS1;
SquiggleMode = false;
ShowInvisibleVectors = false;
HaltOnInvalidOpcodes = true;
}
//
// Static System configuration parameters
//
public static bool MITMode;
public static ImlacCPUType CPUType;
public static bool SquiggleMode;
public static bool ShowInvisibleVectors;
public static bool HaltOnInvalidOpcodes;
}
}

View File

@ -63,7 +63,7 @@ namespace imlac.Debugger
}
}
public void AddSubNode(List<string> words, DebuggerCommand command)
public void AddSubNode(List<string> words, MethodInfo method, object instance)
{
// We should never hit this case.
if (words.Count == 0)
@ -77,13 +77,13 @@ namespace imlac.Debugger
if (subNode == null)
{
// No, it has not -- create one and add it now.
subNode = new DebuggerCommand(command.Instance, words[0], command.Description, command.Usage, null);
subNode = new DebuggerCommand(instance, words[0], null, null, null);
this.SubCommands.Add(subNode);
if (words.Count == 1)
{
// This is the last stop -- set the method and be done with it now.
subNode.Methods.Add(command.Methods[0]);
subNode.Methods.Add(method);
// early return.
return;
@ -98,7 +98,7 @@ namespace imlac.Debugger
// If we're on the last word at this point then this is an overloaded command.
// Check that we don't have any other commands with this number of arguments.
//
int argCount = command.Methods[0].GetParameters().Length;
int argCount = method.GetParameters().Length;
foreach (MethodInfo info in subNode.Methods)
{
if (info.GetParameters().Length == argCount)
@ -110,7 +110,7 @@ namespace imlac.Debugger
//
// We're ok. Add it to the method list.
//
subNode.Methods.Add(command.Methods[0]);
subNode.Methods.Add(method);
// and return early.
return;
@ -119,7 +119,7 @@ namespace imlac.Debugger
// We have more words to go.
words.RemoveAt(0);
subNode.AddSubNode(words, command);
subNode.AddSubNode(words, method, instance);
}
public DebuggerCommand FindSubNodeByName(string name)
@ -159,22 +159,14 @@ namespace imlac.Debugger
// Get the command string from the prompt.
string command = _consolePrompt.Prompt().Trim();
if (string.IsNullOrEmpty(command))
if (command != String.Empty)
{
// Repeat the last command
command = _lastCommand;
Console.WriteLine(">> {0}", command);
next = ExecuteLine(command, system);
}
next = ExecuteLine(command, system);
_lastCommand = command;
}
catch (Exception e)
{
Console.WriteLine(
"Error: {0}",
e.InnerException != null ? e.InnerException.Message : e.Message);
Console.WriteLine(e.Message);
}
return next;
@ -658,9 +650,9 @@ namespace imlac.Debugger
{
string[] commandWords = c.Name.Split(' ');
// This is kind of ugly, we know that at this point every command built above has only
// This is kind of ugly, we know that at this point every command built above have only
// one method. When building the tree, overloaded commands may end up with more than one.
_commandRoot.AddSubNode(new List<string>(commandWords), c);
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0], c.Instance);
}
}
@ -691,7 +683,5 @@ namespace imlac.Debugger
private DebuggerPrompt _consolePrompt;
private DebuggerCommand _commandRoot;
private List<DebuggerCommand> _commandList;
private string _lastCommand;
}
}

View File

@ -197,11 +197,8 @@ namespace imlac.Debugger
clear = sb.ToString();
}
// Default to 80 columns if BufferWidth happens to be zero.
int bufferWidth = Console.BufferWidth > 0 ? Console.BufferWidth : 80;
int column = ((_textPosition + _originColumn) % bufferWidth);
int row = ((_textPosition + _originColumn) / bufferWidth) + _originRow;
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
// Move cursor to origin to draw string
Console.CursorLeft = _originColumn;
@ -297,13 +294,6 @@ namespace imlac.Debugger
{
changed = _input.Trim().ToLower() != matchString.Trim().ToLower();
// Add a space if the output is different than the input (in which case a completion
// actually took place.
if (changed)
{
matchString += " ";
}
_input = matchString;
TextPosition = _input.Length;
}
@ -330,7 +320,6 @@ namespace imlac.Debugger
}
DebuggerCommand match = null;
bool exactMatch = false;
// Search for exact matches. If we find one it's guaranteed to be unique
// so we can follow that node.
@ -339,7 +328,6 @@ namespace imlac.Debugger
if (c.Name.ToLower() == tokens[0].ToLower())
{
match = c;
exactMatch = true;
break;
}
}
@ -403,7 +391,7 @@ namespace imlac.Debugger
{
subMatch = FuzzyMatch(match, tokens, silent);
}
else
else // if (exactMatch)
{
if (!silent && match.SubCommands.Count > 1)
{
@ -431,27 +419,11 @@ namespace imlac.Debugger
return sb.ToString();
}
else if (!silent &&
match.SubCommands.Count == 0 &&
exactMatch)
{
// No more completions; this was an exact match, so
// instead print the help for this command if any
// is available.
Console.WriteLine();
Console.WriteLine(match.Description);
if (!String.IsNullOrWhiteSpace(match.Usage))
{
Console.WriteLine("Parameters: {0}", match.Usage);
}
}
}
if (subMatch == String.Empty)
{
return String.Format("{0}", match.Name);
return String.Format("{0} ", match.Name);
}
else
{

File diff suppressed because it is too large Load Diff

View File

@ -24,57 +24,28 @@ namespace imlac
{
static void Main(string[] args)
{
_startupArgs = args;
ImlacSystem system = new ImlacSystem();
ConsoleExecutor debuggerPrompt =
new ConsoleExecutor(system);
_console = new SDLConsole(0.5f);
_imlacSystem = new ImlacSystem();
_imlacSystem.AttachConsole(_console);
_imlacSystem.Reset();
_state = SystemExecutionState.Debugging;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnBreak);
PrintHerald();
//
// Start the debugger / execution thread
//
_debuggerThread = new System.Threading.Thread(new System.Threading.ThreadStart(DebuggerThread));
_debuggerThread.Start();
//
// Show the display; this will return when the display window is closed.
//
_console.Show();
//
// Kill the system if it's still running.
//
_debuggerThread.Abort();
}
private static void DebuggerThread()
{
//
// Wait for the display to be ready.
//
_console.WaitForSync();
ConsoleExecutor debuggerPrompt = new ConsoleExecutor(_imlacSystem);
if (_startupArgs.Length > 0)
if (args.Length > 0)
{
//
// Assume arg 0 is a script file to be executed.
//
Console.WriteLine("Executing startup script '{0}'", _startupArgs[0]);
Console.WriteLine("Executing startup script '{0}'", args[0]);
try
{
_state = debuggerPrompt.ExecuteScript(_imlacSystem, _startupArgs[0]);
_state = debuggerPrompt.ExecuteScript(system, args[0]);
}
catch (Exception e)
catch(Exception e)
{
Console.WriteLine("Error parsing script: {0}", e.Message);
}
@ -88,19 +59,19 @@ namespace imlac
{
case SystemExecutionState.Halted:
case SystemExecutionState.Debugging:
_state = debuggerPrompt.Prompt(_imlacSystem);
_state = debuggerPrompt.Prompt(system);
break;
case SystemExecutionState.SingleStep:
_imlacSystem.SingleStep();
_imlacSystem.Display.RenderCurrent(false);
system.SingleStep();
system.Display.RenderCurrent(false);
_state = SystemExecutionState.Debugging;
break;
case SystemExecutionState.SingleFrame:
_imlacSystem.SingleStep();
system.SingleStep();
if (_imlacSystem.DisplayProcessor.FrameLatch)
if (system.DisplayProcessor.FrameLatch)
{
Console.WriteLine("Frame completed.");
_state = SystemExecutionState.Debugging;
@ -108,85 +79,55 @@ namespace imlac
break;
case SystemExecutionState.UntilDisplayStart:
_imlacSystem.SingleStep();
system.SingleStep();
if (_imlacSystem.DisplayProcessor.State == ProcessorState.Running)
if (system.DisplayProcessor.State == ProcessorState.Running)
{
Console.WriteLine("Display started.");
_state = SystemExecutionState.Debugging;
}
break;
case SystemExecutionState.SingleDisplayOperation:
_imlacSystem.SingleStep();
if (_imlacSystem.DisplayProcessor.DisplayDrawLatch)
{
_imlacSystem.Display.RenderCurrent(false);
Console.WriteLine("Display operation completed.");
_state = SystemExecutionState.Debugging;
}
break;
case SystemExecutionState.Running:
_imlacSystem.SingleStep();
system.SingleStep();
if (_imlacSystem.Processor.State == ProcessorState.Halted)
if (system.Processor.State == ProcessorState.Halted)
{
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(_imlacSystem.Processor.PC));
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(system.Processor.PC));
_state = SystemExecutionState.Debugging;
}
else if (_imlacSystem.Processor.State == ProcessorState.BreakpointHalt)
else if (system.Processor.State == ProcessorState.BreakpointHalt)
{
Console.WriteLine(
"Breakpoint hit: {0} at address {1}",
BreakpointManager.GetBreakpoint(_imlacSystem.Processor.BreakpointAddress),
Helpers.ToOctal(_imlacSystem.Processor.BreakpointAddress));
BreakpointManager.GetBreakpoint(system.Processor.BreakpointAddress),
Helpers.ToOctal(system.Processor.BreakpointAddress));
_state = SystemExecutionState.Debugging;
}
break;
}
}
catch (Exception e)
catch(Exception e)
{
if (!(e is System.Threading.ThreadAbortException))
{
Console.WriteLine("Internal error during execution: {0}", e.Message);
_state = SystemExecutionState.Debugging;
}
Console.WriteLine("Internal error during execution: {0}", e.Message);
_state = SystemExecutionState.Debugging;
}
}
// We are exiting, shut things down.
//
_imlacSystem.Shutdown();
}
private static void OnBreak(object sender, ConsoleCancelEventArgs e)
static void OnBreak(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("User break.");
_state = SystemExecutionState.Debugging;
e.Cancel = true;
// Flush console input.
while(Console.KeyAvailable)
{
Console.ReadKey();
}
}
private static void PrintHerald()
static void PrintHerald()
{
Console.WriteLine("sImlac v0.3. (c) 2016-2020 Living Computers: Museum+Labs");
Console.WriteLine("sImlac v0.2. (c) 2016-2018 Living Computers: Museum+Labs");
Console.WriteLine();
}
private static string[] _startupArgs;
private static SDLConsole _console;
private static ImlacSystem _imlacSystem;
private static SystemExecutionState _state;
private static System.Threading.Thread _debuggerThread;
}
}

View File

@ -37,17 +37,5 @@ namespace imlac
ushort value = Convert.ToUInt16(octal, 8);
return value;
}
public static void SignalError(LogType logtype, string format, params object[] args)
{
if (Configuration.HaltOnInvalidOpcodes)
{
throw new NotImplementedException(String.Format(format, args));
}
else
{
if (Trace.TraceOn) Trace.Log(logtype, format, args);
}
}
}
}

View File

@ -155,24 +155,16 @@ namespace imlac
~FrameTimer()
{
try
{
//
// Clean stuff up
//
DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
//
// Clean stuff up
//
DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
//
// Fire off a final event to release any call that's waiting...
//
_event.Set();
}
catch
{
// Nothing. If the above fails it's due to the Win32 APIs not being present.
// Just fail quietly.
}
//
// Fire off a final event to release any call that's waiting...
//
_event.Set();
}
/// <summary>

View File

@ -144,7 +144,7 @@ namespace imlac
/// <summary>
/// Indicates whether a key is currently pressed.
/// </summary>
bool NewKeyPressed
bool IsKeyPressed
{
get;
}
@ -173,16 +173,6 @@ namespace imlac
get;
}
int MouseX
{
get;
}
int MouseY
{
get;
}
bool ThrottleFramerate
{
get;
@ -211,9 +201,9 @@ namespace imlac
void ClearDisplay();
void MoveAbsolute(int x, int y, DrawingMode mode);
void MoveAbsolute(uint x, uint y, DrawingMode mode);
void DrawPoint(int x, int y);
void DrawPoint(uint x, uint y);
void RenderCurrent(bool completeFrame);
@ -221,14 +211,8 @@ namespace imlac
void SetScale(float scale);
void SetIntensity(int intensity);
void SetBlink(bool on);
void MapDataSwitch(uint switchNumber, VKeys key);
VKeys GetDataSwitchMapping(uint switchNumber);
void Shutdown();
}
}

View File

@ -69,87 +69,38 @@ namespace imlac.IO
public void Clock()
{
if (_interruptsEnabled && _interruptEnableCount == 0)
if (_interruptsEnabled)
{
// Collect up devices that want to interrupt us.
_interruptStatus = 0;
//
// PDS-1 and PDS-4 status bits are arranged almost entirely differently.
//
if (Configuration.CPUType == ImlacCPUType.PDS1)
// bit 14: 40 cycle sync
if (_system.DisplayProcessor.FrameLatch)
{
// PDS-1:
// bit 14: 40 cycle sync
if (_system.DisplayProcessor.FrameLatch &&
_system.DisplayProcessor.DisplayHalted)
{
_interruptStatus |= 0x0002;
}
// bit 12 - TTY rcv
if (_system.TTY.DataReady)
{
_interruptStatus |= 0x0008;
}
// bit 11 - keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x0010;
}
// bit 10 - TTY send
if (_system.TTY.DataSentLatch)
{
_interruptStatus |= 0x0020;
}
// bit 2 - ACI-1 (clock)
if (_system.Clock.TimerTriggered)
{
_interruptStatus |= 0x2000;
}
_interruptStatus |= 0x0002;
}
else
// bit 12 - TTY rcv
if (_system.TTY.DataReady)
{
// PDS-4:
_interruptStatus |= 0x0008;
}
// Bit 15: Display halt
if (_system.DisplayProcessor.DisplayHalted)
{
_interruptStatus |= 0x0001;
}
// bit 11 - keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x0010;
}
// Bit 14: 40 cycle sync
if (_system.DisplayProcessor.FrameLatch)
{
_interruptStatus |= 0x0002;
}
// Bit 2: TTY Send
if (_system.TTY.DataSentLatch)
{
_interruptStatus |= 0x2000;
}
// Bit 1: TTY Receive
if (_system.TTY.DataReady)
{
_interruptStatus |= 0x4000;
}
// Bit 0: TTY Keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x8000;
}
// bit 2 - ACI-1 (clock)
if (_system.Clock.TimerTriggered)
{
_interruptStatus |= 0x2000;
}
// mask it with our interrupt mask and if non-zero then we have a pending interrupt,
// which we will execute at the start of the next CPU instruction.
if (_interruptsEnabled && (_interruptMask & _interruptStatus) != 0)
if ((_interruptMask & _interruptStatus) != 0)
{
_interruptPending = true;
}
@ -158,7 +109,7 @@ namespace imlac.IO
// If we have an interrupt pending and the processor is starting the next instruction
// we will interrupt now, otherwise we wait until the processor is ready.
//
if (_interruptPending && _system.Processor.CanBeInterrupted())
if (_interruptPending && _system.Processor.InstructionState == ExecState.Fetch)
{
// save the current PC at 0
_system.Memory.Store(0x0000, _system.Processor.PC);
@ -173,10 +124,6 @@ namespace imlac.IO
Trace.Log(LogType.Interrupt, "Interrupt triggered (for device(s) {0})", Helpers.ToOctal((ushort)(_interruptMask & _interruptStatus)));
}
}
else if (_interruptsEnabled)
{
_interruptEnableCount--;
}
}
public int[] GetHandledIOTs()
@ -188,7 +135,6 @@ namespace imlac.IO
{
//
// Dispatch the IOT instruction.
// TODO: handle PDS-4 IOTs.
//
switch (iotCode)
{
@ -200,20 +146,12 @@ namespace imlac.IO
case 0x72:
_interruptsEnabled = true;
// Interrupts are enabled the second instruction after the ION.
_interruptEnableCount = 1;
Trace.Log(LogType.Interrupt, "Interrupts enabled");
break;
case 0x41:
_system.Processor.AC |= (ushort)(_interruptStatus);
Trace.Log(LogType.Interrupt, "Interrupt status word 1 {0} copied to AC", Helpers.ToOctal((ushort)_interruptStatus));
break;
case 0x42:
// TODO: implement second status register when we have devices that use it
// implemented.
Trace.Log(LogType.Interrupt, "Interrupt status word 2 STUB.");
Trace.Log(LogType.Interrupt, "Interrupt status {0} copied to AC", Helpers.ToOctal((ushort)_interruptStatus));
break;
case 0x61:
@ -223,11 +161,10 @@ namespace imlac.IO
default:
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0}", Helpers.ToOctal((ushort)iotCode)));
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0:x4}", iotCode));
}
}
private int _interruptEnableCount;
private bool _interruptsEnabled;
private bool _interruptPending;
private int _interruptMask;
@ -237,18 +174,10 @@ namespace imlac.IO
private readonly int[] _handledIOTs =
{
0x41, // read interrupt status bits
0x41, // read interrupt status bits
0x61, // arm/disarm devices (set interrupt mask)
0x71, // IOF (disable interrupts)
0x72, // ION (enabled masked interrupts)
// 2nd level interrupt facility
0x42, // Read Interrupt Status Bits Word Two
0x89, // Read 2nd Level Interrupt status
0x91, // Disable 2nd Level Interrupts
0x92, // Enable 2nd Level Interrupts
0x93, // Disable, then Enable
};
}
}

View File

@ -31,7 +31,6 @@ namespace imlac.IO
public enum ImlacKey
{
None = 0x0,
DataXmit = 0x2,
Down = 0x4,
Right = 0x5,
@ -40,7 +39,6 @@ namespace imlac.IO
Tab = 0x9,
CR = 0x0d,
FF = 0x0c,
LF = 0x0a,
PageXmit = 0xe,
Home = 0xf,
Brk = 0x19,
@ -119,18 +117,26 @@ namespace imlac.IO
public void Clock()
{
// If we do not already have a key latched and one has been pressed,
// we will raise the Ready flag now.
if (!_keyReady)
// we will latch it now.
// Based on the keycode & modifiers we will generate an Imlac keyboard code
if (_system.Display.IsKeyPressed)
{
if (_system.Display.NewKeyPressed)
{
_keyCode = GetScancodeForCurrentKey();
if (_keyCode != 0)
{
Trace.Log(LogType.Keyboard, "Key latched {0}", Helpers.ToOctal(_keyCode));
_keyReady = true;
}
}
else
{
_keyCode = 0;
}
}
public void Reset()
{
_keyCode = 0;
_keyReady = false;
}
@ -149,18 +155,20 @@ namespace imlac.IO
switch (iotCode)
{
case 0x11:
_system.Processor.AC |= GetScancodeForCurrentKey();
_system.Processor.AC |= _keyCode;
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}", Helpers.ToOctal(_system.Processor.AC));
break;
case 0x12:
_keyCode = 0;
_keyReady = false;
_system.Display.UnlatchKey();
Trace.Log(LogType.Keyboard, "Keyboard flag reset.");
break;
case 0x13:
_system.Processor.AC |= GetScancodeForCurrentKey();
_system.Processor.AC |= _keyCode;
_keyCode = 0;
_keyReady = false;
_system.Display.UnlatchKey();
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}, keyboard flag reset.", Helpers.ToOctal(_system.Processor.AC));
@ -188,7 +196,7 @@ namespace imlac.IO
// bit 8 is always set
scanCode = (ushort)(scanCode | 0x80);
//
// The Repeat, Control, and Shift keys correspond to bits 5, 6, and 7 of the
// scancode returned.
@ -217,6 +225,7 @@ namespace imlac.IO
private readonly int[] _handledIOTs = { 0x11, 0x12, 0x13 };
private bool _keyReady;
private ushort _keyCode;
private ImlacSystem _system;
@ -238,7 +247,6 @@ namespace imlac.IO
{
_keyMappings = new Dictionary<ImlacKey, ImlacKeyMapping>();
AddMapping(ImlacKey.None, 0x0, 0x0);
AddMapping(ImlacKey.DataXmit, 0x2, 0x0);
AddMapping(ImlacKey.Down, 0x4, 0x0);
AddMapping(ImlacKey.Right, 0x5, 0x0);
@ -247,7 +255,6 @@ namespace imlac.IO
AddMapping(ImlacKey.Tab, 0x9, 0x0);
AddMapping(ImlacKey.CR, 0x0d, 0x0);
AddMapping(ImlacKey.FF, 0x0c, 0x0);
AddMapping(ImlacKey.LF, 0x0a, 0x0);
AddMapping(ImlacKey.PageXmit, 0xe, 0x0);
AddMapping(ImlacKey.Home, 0xf, 0x0);
AddMapping(ImlacKey.Brk, 0x19, 0x0);

View File

@ -36,10 +36,6 @@ namespace imlac.IO
_dataSendReady = true;
_dataReady = false;
_clocks = 0;
_dataBufferFull = false;
_dataSentLatch = false;
_rxData = 0;
_txData = 0;
if (_dataChannel != null)
{
@ -54,11 +50,6 @@ namespace imlac.IO
throw new ArgumentNullException("channel");
}
if (_dataChannel != null)
{
_dataChannel.Close();
}
_dataChannel = channel;
}
@ -70,22 +61,27 @@ namespace imlac.IO
{
_clocks = 0;
if (_dataChannel.DataAvailable && !_dataReady)
if (_dataChannel.DataAvailable)
{
_dataReady = true;
_rxData = _dataChannel.Read();
_data = _dataChannel.Read();
Trace.Log(LogType.TTY, "i");
}
// Are we waiting to send something?
if (_dataBufferFull && _dataChannel.OutputReady)
else
{
_dataChannel.Write(_txData);
Trace.Log(LogType.TTY, "o {0}", Helpers.ToOctal(_txData));
_dataBufferFull = false;
_dataSentLatch = true;
_dataSendReady = true;
_dataReady = false;
}
}
// Are we waiting to send something?
if (!_dataSendReady && _dataChannel.OutputReady)
{
_dataChannel.Write(_data);
Trace.Log(LogType.TTY, "o");
// Sent, reset flag.
_dataSendReady = true;
}
}
@ -99,16 +95,6 @@ namespace imlac.IO
get { return _dataSendReady; }
}
public bool DataSentLatch
{
get
{
bool latch = _dataSentLatch;
_dataSentLatch = false;
return latch;
}
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
@ -119,8 +105,8 @@ namespace imlac.IO
switch (iotCode)
{
case 0x19: // RRB - TTY read
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_rxData));
_system.Processor.AC |= _rxData;
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_data));
_system.Processor.AC |= _data;
break;
case 0x1a: // RCF - Clear TTY status
@ -128,35 +114,30 @@ namespace imlac.IO
break;
case 0x1b: // RRC - Read and clear status
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_rxData));
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_data));
_dataReady = false;
_system.Processor.AC |= _rxData;
_system.Processor.AC |= _data;
break;
case 0x21: // TPR - transmit
if (_dataSendReady)
if (_dataSendReady) // only if transmitter is ready
{
_txData = (byte)_system.Processor.AC;
_data = (byte)_system.Processor.AC;
_dataSendReady = false;
_dataBufferFull = true;
}
break;
case 0x22: // TCF - clear output flag
_dataSendReady = false;
_dataSendReady = true;
break;
case 0x23: // TPC - print, clear flag
if (_dataSendReady)
{
_txData = (byte)_system.Processor.AC;
_dataSendReady = false;
_dataBufferFull = true;
}
_data = (byte)_system.Processor.AC;
_dataSendReady = false;
break;
default:
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_rxData));
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_data));
break;
}
}
@ -165,13 +146,10 @@ namespace imlac.IO
private bool _dataReady;
private bool _dataSendReady;
private bool _dataBufferFull;
private bool _dataSentLatch;
private byte _rxData;
private byte _txData;
private byte _data;
private int _clocks;
private readonly int _dataClocks = 90; // Appx. 50kbps
private readonly int _dataClocks = 100;
private ISerialDataChannel _dataChannel;

View File

@ -1,221 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace imlac.IO.TTYChannels
{
public class TelnetDataChannel : ISerialDataChannel
{
public TelnetDataChannel(string server, int port, bool raw)
{
//
// Try to open the channel.
//
try
{
_telnetStream = new TelnetStream(server, port, raw);
}
catch(Exception e)
{
Console.WriteLine("Failed to connect to {0}:{1}, error: {2}",
server, port, e.Message);
_telnetStream = null;
}
}
public void Reset()
{
// TODO: how to handle reset?
}
public void Close()
{
_telnetStream.Close();
_telnetStream = null;
}
public byte Read()
{
byte b = (byte)_telnetStream.ReadByte();
Trace.Log(LogType.Telnet, "r:{0:x2}({1}) ", b, (char)b);
return b;
}
public void Write(byte value)
{
Trace.Log(LogType.Telnet, "w:{0:x2}({1})", value, (char)value);
_telnetStream.WriteByte(value);
}
public bool DataAvailable
{
get
{
return _telnetStream != null ? _telnetStream.DataAvailable : false;
}
}
public bool OutputReady
{
get
{
// Always return true, we can always send data
return _telnetStream != null ? true : false;
}
}
private TelnetStream _telnetStream;
}
public class TelnetStream
{
public TelnetStream(string host, int port, bool raw)
{
_tcpClient = new TcpClient(host, port);
_tcpStream = _tcpClient.GetStream();
_raw = raw;
_abort = false;
_asyncBuffer = new byte[2048];
_inputBuffer = new Queue<byte>();
_bufferLock = new ReaderWriterLockSlim();
_streamLock = new ReaderWriterLockSlim();
//
// Kick off reading from the stream, asynchronously.
_tcpStream.BeginRead(
_asyncBuffer,
0,
_asyncBuffer.Length,
new AsyncCallback(AsyncReadCallback),
null);
}
public bool DataAvailable
{
get
{
bool avail = false;
_bufferLock.EnterReadLock();
avail = _inputBuffer.Count > 0;
_bufferLock.ExitReadLock();
return avail;
}
}
public void Close()
{
_streamLock.EnterWriteLock();
_abort = true;
_tcpStream.Close();
_tcpClient.Close();
_streamLock.ExitWriteLock();
}
public byte ReadByte()
{
byte b = 0;
_bufferLock.EnterUpgradeableReadLock();
if (_inputBuffer.Count > 0)
{
_bufferLock.EnterWriteLock();
b = _inputBuffer.Dequeue();
_bufferLock.ExitWriteLock();
}
_bufferLock.ExitUpgradeableReadLock();
return b;
}
public void WriteByte(byte b)
{
_tcpStream.WriteByte(b);
}
private void AsyncReadCallback(IAsyncResult ar)
{
_streamLock.EnterReadLock();
if (_abort)
{
return;
}
//
// Process incoming data
// TODO: The telnet processing is terrible.
//
int bytesRead = _tcpStream.EndRead(ar);
for (int i = 0; i < bytesRead; )
{
byte b = _asyncBuffer[i++];
if (!_raw && b == IAC)
{
// For now we just eat all option requests.
b = _asyncBuffer[i++];
switch(b)
{
case WILL:
case WONT:
case DO:
case DONT:
i++;
break;
default:
break;
}
}
else
{
_bufferLock.EnterWriteLock();
_inputBuffer.Enqueue(b);
_bufferLock.ExitWriteLock();
}
}
//
// And start the next read
//
_tcpStream.BeginRead(
_asyncBuffer,
0,
_asyncBuffer.Length,
new AsyncCallback(AsyncReadCallback),
null);
_streamLock.ExitReadLock();
}
private const byte IAC = 255;
private const byte WILL = 251;
private const byte WONT = 252;
private const byte DO = 253;
private const byte DONT = 254;
private byte[] _asyncBuffer;
private Queue<byte> _inputBuffer;
private TcpClient _tcpClient;
private NetworkStream _tcpStream;
private bool _raw;
private bool _abort;
private ReaderWriterLockSlim _bufferLock;
private ReaderWriterLockSlim _streamLock;
}
}

View File

@ -15,39 +15,15 @@
along with sImlac. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace imlac
{
// TODO: make memory size configurable.
public class Memory
{
public Memory(ImlacSystem system)
{
_system = system;
SetMemorySize(0x2000);
}
public void SetMemorySize(ushort size)
{
if (size != 0x1000 && size != 0x2000 && size != 0x4000)
{
throw new InvalidOperationException("Size must be 4k, 8k, or 16k.");
}
_size = size;
_sizeMask = (ushort)(size - 1);
_mem = new ushort[Size];
if (_system.Processor != null)
{
_system.Processor.InitializeCache();
}
if (_system.DisplayProcessor != null)
{
_system.DisplayProcessor.InitializeCache();
}
_system = system;
}
public ushort Fetch(ushort address)
@ -68,18 +44,15 @@ namespace imlac
public static ushort Size
{
get { return _size; }
get { return 8192; }
}
public static ushort SizeMask
{
get { return _sizeMask; }
get { return 0x1fff; }
}
private static ushort _size;
private static ushort _sizeMask;
private ushort[] _mem;
private ImlacSystem _system;
}
}
}

View File

@ -1,816 +0,0 @@
/*
This file is part of sImlac.
sImlac is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
sImlac is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with sImlac. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using imlac.IO;
using imlac.Debugger;
namespace imlac
{
/// <summary>
/// DisplayProcessor implements the Display processor found in an Imlac PDS-1 with long vector hardware.
/// </summary>
public class PDS1DisplayProcessor : DisplayProcessorBase
{
public PDS1DisplayProcessor(ImlacSystem system) : base(system)
{
}
public override void Reset()
{
base.Reset();
_sgrModeOn = false;
_sgrBeamOn = false;
_sgrDJRMOn = false;
}
public override void InitializeCache()
{
_instructionCache = new PDS1DisplayInstruction[Memory.Size];
}
public override void InvalidateCache(ushort address)
{
_instructionCache[address & Memory.SizeMask] = null;
}
public override string Disassemble(ushort address, DisplayProcessorMode mode, out int length)
{
//
// Return a precached instruction if we have it due to previous execution
// otherwise disassemble it now in the requested mode; this disassembly
// does not get added to the cache.
//
if (_instructionCache[address & Memory.SizeMask] != null)
{
return _instructionCache[address & Memory.SizeMask].Disassemble(mode, _mem, out length);
}
else
{
return new PDS1DisplayInstruction(_mem.Fetch(address), address, mode).Disassemble(mode, _mem, out length);
}
}
public override void Clock()
{
_clocks++;
if (_clocks > _frameClocks40Hz)
{
_clocks = 0;
_frameLatch = true;
_system.Display.FrameDone();
}
if (_state == ProcessorState.Halted)
{
return;
}
switch (_mode)
{
case DisplayProcessorMode.Processor:
ExecuteProcessor();
break;
case DisplayProcessorMode.Increment:
ExecuteIncrement();
break;
}
}
public override int[] GetHandledIOTs()
{
return _handledIOTs;
}
public override void ExecuteIOT(int iotCode)
{
//
// Dispatch the IOT instruction.
//
switch (iotCode)
{
case 0x03: // DLA:
PC = _system.Processor.AC;
// this is for debugging only, we keep track of the load address
// to make it easy to see where the main Display List starts
_dpcEntry = PC;
break;
case 0x0a: // Halt display processor
HaltProcessor();
break;
case 0x39: // Clear display 40Hz sync latch
_frameLatch = false;
break;
case 0xc4: // clear halt state
_halted = false;
break;
default:
Helpers.SignalError(LogType.DisplayProcessor, "Unhandled IOT {0}", Helpers.ToOctal((ushort)iotCode));
break;
}
}
private void ExecuteProcessor()
{
PDS1DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Processor);
instruction.UsageMode = DisplayProcessorMode.Processor;
switch (instruction.Opcode)
{
case DisplayOpcode.DEIM:
_mode = DisplayProcessorMode.Increment;
_immediateWord = instruction.Data;
_immediateHalf = ImmediateHalf.Second;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Enter increment mode");
break;
case DisplayOpcode.DJMP:
if (!_dadr)
{
// DADR off, use only 12 bits
_pc = (ushort)((instruction.Data & 0xfff) | _block);
}
else
{
_pc = (ushort)(instruction.Data | _block);
}
break;
case DisplayOpcode.DJMS:
Push();
if (!_dadr)
{
// DADR off, use only 12 bits
_pc = (ushort)((instruction.Data & 0xfff) | _block);
}
else
{
_pc = (ushort)(instruction.Data | _block);
}
break;
case DisplayOpcode.DOPR:
// Each of bits 4-11 can be combined in any fashion
// to do a number of operations simultaneously; we walk the bits
// and perform the operations as set.
if ((instruction.Data & 0x800) == 0)
{
// DHLT -- halt the display processor. other micro-ops in this
// instruction are still run.
HaltProcessor();
}
if ((instruction.Data & 0x400) != 0)
{
// HV Sync; this is currently a no-op, not much to do in emulation.
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "HV Sync");
MoveAbsolute(X, Y, DrawingMode.Off);
}
if ((instruction.Data & 0x200) != 0)
{
// DIXM -- increment X DAC MSB
X += 0x20;
MoveAbsolute(X, Y, DrawingMode.Off);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DIXM, X is now {0}", X);
}
if ((instruction.Data & 0x100) != 0)
{
// DIYM -- increment Y DAC MSB
Y += 0x20;
MoveAbsolute(X, Y, DrawingMode.Off);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DIYM, Y is now {0}", Y);
}
if ((instruction.Data & 0x80) != 0)
{
// DDXM - decrement X DAC MSB
X -= 0x20;
MoveAbsolute(X, Y, DrawingMode.Off);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDXM, X is now {0}", X);
}
if ((instruction.Data & 0x40) != 0)
{
// DDYM - decrement y DAC MSB
Y -= 0x20;
MoveAbsolute(X, Y, DrawingMode.Off);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDYM, Y is now {0}", Y);
}
if ((instruction.Data & 0x20) != 0)
{
// DRJM - return from display subroutine
ReturnFromDisplaySubroutine();
_pc--; // hack (we add +1 at the end...)
}
if ((instruction.Data & 0x10) != 0)
{
// DDSP -- intensify point on screen for 1.8us (one instruction)
// at the current position.
DrawPoint(X, Y);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDSP at {0},{1}", X, Y);
}
// F/C ops:
int f = (instruction.Data & 0xc) >> 2;
int c = instruction.Data & 0x3;
switch (f)
{
case 0x0:
// if bit 15 is set, the MIT mods flip the DADR bit.
if (Configuration.MITMode && (c == 1))
{
_dadr = !_dadr;
}
break;
case 0x1:
// Set scale based on C
switch (c)
{
case 0:
_scale = 1.0f;
break;
default:
_scale = c;
break;
}
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Scale set to {0}", _scale);
break;
case 0x2:
if (!Configuration.MITMode)
{
_block = (ushort)(c << 12);
}
break;
case 0x3:
// TODO: light pen sensitize
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Light pen, stub!");
break;
}
_pc++;
break;
case DisplayOpcode.DLXA:
X = instruction.Data << 1;
DrawingMode mode;
if (_sgrModeOn && _sgrBeamOn)
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 X set to {0}", X);
mode = DrawingMode.SGR1;
}
else
{
mode = DrawingMode.Off;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "X set to {0}", X);
}
MoveAbsolute(X, Y, mode);
if (_sgrDJRMOn)
{
ReturnFromDisplaySubroutine();
}
else
{
_pc++;
}
break;
case DisplayOpcode.DLYA:
Y = instruction.Data << 1;
if (_sgrModeOn && _sgrBeamOn)
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 Y set to {0}", Y);
mode = DrawingMode.SGR1;
}
else
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Y set to {0}", Y);
mode = DrawingMode.Off;
}
MoveAbsolute(X, Y, mode);
if (_sgrDJRMOn)
{
ReturnFromDisplaySubroutine();
}
else
{
_pc++;
}
break;
case DisplayOpcode.DLVH:
DrawLongVector(instruction.Data);
break;
case DisplayOpcode.SGR1:
_sgrModeOn = (instruction.Data & 0x1) != 0;
_sgrDJRMOn = (instruction.Data & 0x2) != 0;
_sgrBeamOn = (instruction.Data & 0x4) != 0;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 instruction: Enter {0} BeamOn {1} DRJM {2}", _sgrModeOn, _sgrBeamOn, _sgrDJRMOn);
_pc++;
break;
case DisplayOpcode.Invalid:
Helpers.SignalError(
LogType.DisplayProcessor,
"Execution of invalid display processor instruction {0}",
Helpers.ToOctal(instruction.Data));
_pc++;
break;
default:
Helpers.SignalError(
LogType.DisplayProcessor,
"Unimplemented Display Processor Opcode {0}, operands {1}",
Helpers.ToOctal((ushort)instruction.Opcode),
Helpers.ToOctal(instruction.Data));
break;
}
// If the next instruction has a breakpoint set we'll halt at this point, before executing it.
if (BreakpointManager.TestBreakpoint(BreakpointType.Display, _pc))
{
_state = ProcessorState.BreakpointHalt;
}
}
private void ExecuteIncrement()
{
int halfWord = _immediateHalf == ImmediateHalf.First ? (_immediateWord & 0xff00) >> 8 : (_immediateWord & 0xff);
int newX = (int)X;
int newY = (int)Y;
// translate the half word to vector movements or escapes
if ((halfWord & 0x80) == 0)
{
if ((halfWord & 0x40) != 0)
{
// Escape code
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment mode escape on halfword {0}", _immediateHalf);
_mode = DisplayProcessorMode.Processor;
_pc++; // move to next word
// Moved this into this check (not sure it makes sense to do a DJMS when not escaped from Increment mode)
if ((halfWord & 0x20) != 0)
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment mode return from subroutine.");
ReturnFromDisplaySubroutine();
}
}
else
{
// Stay in increment mode.
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment instruction, non-drawing.");
MoveToNextHalfWord();
}
if ((halfWord & 0x10) != 0)
{
newX += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment X MSB, X is now {0}", X);
}
if ((halfWord & 0x08) != 0)
{
newX = newX & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset X LSB, X is now {0}", X);
}
if ((halfWord & 0x02) != 0)
{
newY += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment Y MSB, Y is now {0}", Y);
}
if ((halfWord & 0x01) != 0)
{
newY = newY & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset Y LSB, Y is now {0}", Y);
}
MoveAbsolute(newX, newY, DrawingMode.Off);
}
else
{
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
int xMag = (int)(((halfWord & 0x18) >> 3) * _scale);
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03) * _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor,
"Inc mode ({0}:{1}), x={2} y={3} dx={4} dy={5} beamon {6}",
Helpers.ToOctal((ushort)_pc),
Helpers.ToOctal((ushort)halfWord),
newX,
newY,
xSign * xMag,
ySign * yMag,
(halfWord & 0x40) != 0);
newX = (int)(newX + xSign * xMag * 2);
newY = (int)(newY + ySign * yMag * 2);
MoveAbsolute(newX, newY, (halfWord & 0x40) == 0 ? DrawingMode.Off : DrawingMode.Dotted);
MoveToNextHalfWord();
}
// Assign back to X, Y registers; clipping into range.
X = newX;
Y = newY;
// If the next instruction has a breakpoint set we'll halt at this point, before executing it.
if (_immediateHalf == ImmediateHalf.First && BreakpointManager.TestBreakpoint(BreakpointType.Display, _pc))
{
_state = ProcessorState.BreakpointHalt;
}
}
private void MoveToNextHalfWord()
{
if (_immediateHalf == ImmediateHalf.Second)
{
_pc++;
_immediateWord = _mem.Fetch(_pc);
_immediateHalf = ImmediateHalf.First;
// Update the instruction cache with the type of instruction (to aid in debugging).
PDS1DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Increment);
}
else
{
_immediateHalf = ImmediateHalf.Second;
}
}
private void DrawLongVector(ushort word0)
{
//
// A Long Vector instruction is 3 words long:
// Word 0: upper 4 bits indicate the opcode (4), lower 12 specify N-M
// Word 1: upper 3 bits specify beam options (dotted, solid, etc) and the lower 12 specify the larger increment "M"
// Word 2: upper 3 bits specify signs, lower 12 specify the smaller increment "N"
// M is the larger absolute value between dX and dY
// N is the smaller.
//
// Unsure at the moment what the N-M bits are for (I'm guessing they're there to help the processor figure things out).
// Also unsure what bits are used in the 12 bits for N and M (the DACs are only 11-bits, but normally only 10 can be specified)...
//
ushort word1 = _mem.Fetch(++_pc);
ushort word2 = _mem.Fetch(++_pc);
uint M = (uint)(word1 & 0x3ff);
uint N = (uint)(word2 & 0x3ff);
bool beamOn = (word1 & 0x2000) != 0;
bool dotted = (word1 & 0x4000) != 0;
int dySign = (word2 & 0x2000) != 0 ? -1 : 1;
int dxSign = (word2 & 0x4000) != 0 ? -1 : 1;
bool dyGreater = (word2 & 0x1000) != 0;
uint dx = 0;
uint dy = 0;
if (dyGreater)
{
dy = M;
dx = N;
}
else
{
dx = M;
dy = N;
}
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector x={0} y={1} dx={2} dy={3} beamOn {4} dotted {5}", X, Y, dx * dxSign, dy * dySign, beamOn, dotted);
// * 2 for translation to 11-bit space
// The docs don't call this out, but the scale setting used in increment mode appears to apply
// to the LVH vectors as well. (Maze appears to rely on this.)
int newX = (int)(X + (dx * dxSign) * 2 * _scale);
int newY = (int)(Y + (dy * dySign) * 2 * _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector, move complete - x={0} y={1}", newX, newY, dx * dxSign, dy * dySign, beamOn, dotted);
MoveAbsolute(newX, newY, beamOn ? (dotted ? DrawingMode.Dotted : DrawingMode.Normal) : DrawingMode.Off);
// Assign back to X, Y registers; clipping into range.
X = newX;
Y = newY;
_pc++;
}
private void ReturnFromDisplaySubroutine()
{
Pop();
}
private PDS1DisplayInstruction GetCachedInstruction(ushort address, DisplayProcessorMode mode)
{
if (_instructionCache[address & Memory.SizeMask] == null)
{
_instructionCache[address & Memory.SizeMask] = new PDS1DisplayInstruction(_mem.Fetch(address), address, mode);
}
return _instructionCache[address & Memory.SizeMask];
}
// SGR-1 mode switches
private bool _sgrModeOn;
private bool _sgrDJRMOn;
private bool _sgrBeamOn;
protected const int _frameClocks40Hz = 13889; // cycles per 1/40th of a second (rounded up) for a 1.8uS clock speed.
private PDS1DisplayInstruction[] _instructionCache;
private readonly int[] _handledIOTs = { 0x3, 0xa, 0x39, 0xc4 };
/// <summary>
/// PDS-1 Display instruction decoder and disassembler.
/// </summary>
private class PDS1DisplayInstruction : DisplayInstructionBase
{
public PDS1DisplayInstruction(ushort word, ushort address, DisplayProcessorMode mode) : base (word, address, mode)
{
}
public override string Disassemble(DisplayProcessorMode mode, Memory mem, out int length)
{
if (mode == DisplayProcessorMode.Indeterminate)
{
mode = _usageMode;
}
switch (mode)
{
case DisplayProcessorMode.Increment:
length = 1;
return DisassembleIncrement();
case DisplayProcessorMode.Processor:
return DisassembleProcessor(mem, out length);
case DisplayProcessorMode.Indeterminate:
length = 1;
return "Indeterminate";
default:
throw new InvalidOperationException(String.Format("{0} is not a supported disassembly mode for this processor.", mode));
}
}
protected override void Decode()
{
if (_usageMode == DisplayProcessorMode.Processor)
{
DecodeProcessor();
}
else
{
DecodeImmediate();
}
}
private void DecodeProcessor()
{
int op = (_word & 0x7000) >> 12;
switch (op)
{
case 0x00:
// opr code
_opcode = DisplayOpcode.DOPR;
_data = (ushort)(_word & 0xfff);
break;
case 0x01:
_opcode = DisplayOpcode.DLXA;
_data = (ushort)(_word & 0x3ff);
break;
case 0x02:
_opcode = DisplayOpcode.DLYA;
_data = (ushort)(_word & 0x3ff);
break;
case 0x03:
_opcode = DisplayOpcode.DEIM;
_data = (ushort)(_word & 0xff);
if ((_word & 0xff00) == 0x3800)
{
Console.WriteLine("PPM-1 not implemented (instr {0})", Helpers.ToOctal(_word));
}
break;
case 0x04:
_opcode = DisplayOpcode.DLVH;
_data = (ushort)(_word & 0xfff);
break;
case 0x05:
_opcode = DisplayOpcode.DJMS;
_data = (ushort)(_word & 0xfff);
if (Configuration.MITMode && (_word & 0x8000) != 0)
{
// MIT's mod takes the MSB of the address from the MSB of the instruction word
_data |= 0x1000;
}
break;
case 0x06:
_opcode = DisplayOpcode.DJMP;
_data = (ushort)(_word & 0xfff);
if (Configuration.MITMode && (_word & 0x8000) != 0)
{
// MIT's mod takes the MSB of the address from the MSB of the instruction word
_data |= 0x1000;
}
break;
case 0x07:
DecodeExtendedInstruction(_word);
break;
default:
Helpers.SignalError(
LogType.DisplayProcessor,
"Unhandled Display Processor Mode instruction {0}",
Helpers.ToOctal(_word));
_opcode = DisplayOpcode.Invalid;
_data = _word;
break;
}
}
void DecodeExtendedInstruction(ushort word)
{
int op = (word & 0x1f8) >> 3;
switch (op)
{
case 0x36:
case 0x37:
_opcode = DisplayOpcode.ASG1;
break;
case 0x3a:
case 0x3b:
_opcode = DisplayOpcode.VIC1;
break;
case 0x3c:
case 0x3d:
_opcode = DisplayOpcode.MCI1;
break;
case 0x3e:
_opcode = DisplayOpcode.STI1;
break;
case 0x3f:
_opcode = DisplayOpcode.SGR1;
break;
default:
Helpers.SignalError(
LogType.DisplayProcessor,
"Unhandled extended Display Processor Mode instruction {0}",
Helpers.ToOctal(word));
_opcode = DisplayOpcode.Invalid;
_data = word;
break;
}
_data = (ushort)(word & 0x7);
}
private void DecodeImmediate()
{
// TODO: eventually actually precache movement calculations.
}
protected override string DisassembleExtended(Memory mem, out int length)
{
string ret = String.Empty;
switch (_opcode)
{
case DisplayOpcode.DLVH:
length = 3;
ret = DisassembleLongVector(mem);
break;
default:
length = 1;
// Handle as yet not-special-cased opcodes
ret = String.Format("{0} {1}", _opcode, Helpers.ToOctal(_data));
break;
}
return ret;
}
private string DisassembleLongVector(Memory mem)
{
//
// A Long Vector instruction is 3 words long:
// Word 0: upper 4 bits indicate the opcode (4), lower 12 specify N-M
// Word 1: upper 3 bits specify beam options (dotted, solid, etc) and the lower 12 specify the larger increment "M"
// Word 2: upper 3 bits specify signs, lower 12 specify the smaller increment "N"
// M is the larger absolute value between dX and dY
// N is the smaller.
//
// TODO: Would make sense to precache this during decoding, would require
// modifications to cache invalidation logic.
ushort word1 = mem.Fetch((ushort)(_address + 1));
ushort word2 = mem.Fetch((ushort)(_address + 2));
uint M = (uint)(word1 & 0x3ff);
uint N = (uint)(word2 & 0x3ff);
bool beamOn = (word1 & 0x2000) != 0;
bool dotted = (word1 & 0x4000) != 0;
int dySign = (word2 & 0x2000) != 0 ? -1 : 1;
int dxSign = (word2 & 0x4000) != 0 ? -1 : 1;
bool dyGreater = (word2 & 0x1000) != 0;
uint dx = 0;
uint dy = 0;
if (dyGreater)
{
dy = M;
dx = N;
}
else
{
dx = M;
dy = N;
}
return String.Format("DLVH ({0},{1}) {2} {3}",
dx * dxSign, dy * dySign,
beamOn ? "ON" : "OFF",
dotted ? "DOTTED" : String.Empty);
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
[assembly: AssemblyProduct("sImlac")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@ -24,7 +24,6 @@ using System.Threading;
using imlac.IO;
using SDL2;
using static SDL2.SDL;
using System.Runtime.InteropServices;
namespace imlac
{
@ -40,9 +39,6 @@ namespace imlac
//
public class SDLConsole : IImlacConsole
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetProcessDPIAware();
public SDLConsole(float scaleFactor)
{
if (scaleFactor <= 0)
@ -55,29 +51,27 @@ namespace imlac
_lock = new ReaderWriterLockSlim();
_keyLatchedLock = new ReaderWriterLockSlim();
_syncEvent = new AutoResetEvent(false);
_frame = 0;
_frame = 0;
try
{
_frameTimer = new FrameTimer(40);
}
catch
catch(Exception)
{
// Unable to initialize frame timer, we will not be able
// to throttle execution.
_frameTimer = null;
}
_timer = new HighResTimer();
_fullScreen = false;
_displayList = new List<Vector>(_displayListSize);
_displayListIndex = 0;
_blink = false;
_intensity = 1.0f;
//
// Prepopulate the display list with Vectors. Only those used in the current frame are
// actually rendered, we prepopulate the list to prevent having to cons up new ones
@ -85,26 +79,27 @@ namespace imlac
//
for (int i = 0; i < _displayListSize; i++)
{
_displayList.Add(new Vector(DrawingMode.Off, 1.0f, false, 0, 0, 0, 0));
_displayList.Add(new Vector(DrawingMode.Off, 0, 0, 0, 0));
}
BuildKeyMappings();
BuildKeyMappings();
InvokeDisplayThread();
}
public bool NewKeyPressed
public bool IsKeyPressed
{
get
{
_keyLatchedLock.EnterReadLock();
bool pressed = _newKeyPressed;
bool latched = _keyLatched;
_keyLatchedLock.ExitReadLock();
return pressed;
return latched;
}
}
public ImlacKey Key
{
get { return _currentKeyCode; }
get { return _latchedKeyCode; }
}
public ImlacKeyModifiers KeyModifiers
@ -114,8 +109,8 @@ namespace imlac
public void UnlatchKey()
{
_keyLatchedLock.EnterReadLock();
_newKeyPressed = false;
_keyLatchedLock.EnterReadLock();
_keyLatched = false;
_keyLatchedLock.ExitReadLock();
}
@ -124,16 +119,6 @@ namespace imlac
get { return (ushort)_dataSwitches; }
}
public int MouseX
{
get { return _mouseX; }
}
public int MouseY
{
get { return _mouseY; }
}
public bool ThrottleFramerate
{
get { return _throttleFramerate; }
@ -165,28 +150,6 @@ namespace imlac
}
}
public void Shutdown()
{
//
// Tell the SDL event loop to wrap things up.
//
_userEvent.type = SDL.SDL_EventType.SDL_QUIT;
SDL.SDL_PushEvent(ref _userEvent);
}
/// <summary>
/// Waits for the screen to be ready for access.
/// </summary>
public void WaitForSync()
{
_syncEvent.WaitOne();
}
public void Show()
{
ShowInternal();
}
public void ClearDisplay()
{
_lock.EnterWriteLock();
@ -206,16 +169,6 @@ namespace imlac
UpdateDisplayScale();
}
public void SetIntensity(int intensity)
{
_intensity = ((float)intensity / 16.0f);
}
public void SetBlink(bool on)
{
_blink = on;
}
public void MapDataSwitch(uint switchNumber, VKeys key)
{
_dataSwitchMappings[switchNumber] = key;
@ -228,16 +181,6 @@ namespace imlac
private void InitializeSDL()
{
try
{
// Set high DPI awareness on Windows platforms, if this fails it's OK.
SetProcessDPIAware();
}
catch
{
// Nothing to do.
}
DoUpdateDisplayScale();
int retVal = 0;
@ -254,15 +197,13 @@ namespace imlac
throw new InvalidOperationException("SDL_SetHint failed to set scale quality.");
}
SDL.SDL_WindowFlags flags = SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN;
_sdlWindow = SDL.SDL_CreateWindow(
"Imlac PDS-1",
SDL.SDL_WINDOWPOS_UNDEFINED,
SDL.SDL_WINDOWPOS_UNDEFINED,
_xResolution,
_yResolution,
_fullScreen ? SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP | flags : flags);
_fullScreen ? SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP | SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN : SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);
if (_sdlWindow == IntPtr.Zero)
@ -285,11 +226,6 @@ namespace imlac
SDL.SDL_SetRenderDrawBlendMode(_sdlRenderer, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
// Clear the screen.
SDL.SDL_SetRenderDrawColor(_sdlRenderer, 0, 0, 0, 0xff);
SDL.SDL_RenderFillRect(_sdlRenderer, ref _displayRect);
SDL.SDL_RenderPresent(_sdlRenderer);
// Register a User event for rendering and resizing.
_userEventType = SDL.SDL_RegisterEvents(1);
_userEvent = new SDL.SDL_Event();
@ -320,33 +256,14 @@ namespace imlac
if (_sdlWindow != null)
{
_lock.EnterWriteLock();
SDL.SDL_SetWindowSize(_sdlWindow, _xResolution, _yResolution);
SDL.SDL_SetWindowFullscreen(_sdlWindow,
_fullScreen ? (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
//
// Calculate x/y offsets to center display rendering
///
int newWidth = 0;
int newHeight = 0;
SDL.SDL_GetWindowSize(_sdlWindow, out newWidth, out newHeight);
_xOffset = Math.Max(0, (newWidth - _xResolution) / 2);
_yOffset = Math.Max(0, (newHeight - _yResolution) / 2);
_displayRect.h = newHeight == 0 ? _yResolution : newHeight;
_displayRect.w = newWidth == 0 ? _xResolution : newWidth;
// Clear the display list so no garbage remains.
_displayListIndex = 0;
_lock.ExitWriteLock();
}
}
public void MoveAbsolute(int x, int y, DrawingMode mode)
{
public void MoveAbsolute(uint x, uint y, DrawingMode mode)
{
//
// Take coordinates as an 11-bit quantity (0-2048) even though we may not be displaying the full resolution.
//
@ -354,16 +271,12 @@ namespace imlac
{
AddNewVector(mode, _x, _y, x, y);
}
else if(Configuration.ShowInvisibleVectors)
{
AddNewVector(DrawingMode.Debug, _x, _y, x, y);
}
_x = x;
_y = y;
}
public void DrawPoint(int x, int y)
public void DrawPoint(uint x, uint y)
{
_x = x;
_y = y;
@ -383,15 +296,24 @@ namespace imlac
}
}
private void ShowInternal()
private void InvokeDisplayThread()
{
_displayThread = new System.Threading.Thread(new System.Threading.ThreadStart(DisplayThread));
_displayThread.Start();
//
// Wait until the display has been initialized.
//
_syncEvent = new AutoResetEvent(false);
_syncEvent.WaitOne();
}
private void DisplayThread()
{
InitializeSDL();
// Signal that the display is ready.
_syncEvent.Set();
bool quit = false;
while (!quit)
while (true)
{
SDL.SDL_Event e;
@ -414,36 +336,23 @@ namespace imlac
break;
case SDL.SDL_EventType.SDL_QUIT:
quit = true;
return;
case SDL.SDL_EventType.SDL_KEYDOWN:
SdlKeyDown(e.key.keysym.sym, e.key.repeat > 0);
SdlKeyDown(e.key.keysym.sym);
break;
case SDL.SDL_EventType.SDL_KEYUP:
SdlKeyUp(e.key.keysym.sym);
break;
case SDL.SDL_EventType.SDL_MOUSEMOTION:
SdlMouseMove(e.motion.x, e.motion.y);
break;
}
}
SDL.SDL_Delay(0);
}
//
// Done, clean up.
//
SDL.SDL_DestroyRenderer(_sdlRenderer);
SDL.SDL_DestroyWindow(_sdlWindow);
SDL.SDL_Quit();
}
private void SdlKeyDown(SDL_Keycode key, bool repeat)
private void SdlKeyDown(SDL_Keycode key)
{
switch(key)
{
@ -466,40 +375,16 @@ namespace imlac
UpdateDataSwitches(key, true /* key down */);
if (key == SDL_Keycode.SDLK_INSERT && !repeat)
if (key == SDL_Keycode.SDLK_INSERT)
{
FullScreenToggle();
break;
}
// If this is a repeated keystroke, we'll only log it if
// the Repeat key (Alt) is being held down.
if (repeat && (_keyModifiers & ImlacKeyModifiers.Rept) == 0)
{
break;
}
_keyLatchedLock.EnterWriteLock();
// Only accept this key if we're not waiting
// for the Imlac to read the last one.
if (!_newKeyPressed)
{
ImlacKey newCode = TranslateKeyCode(key);
_keyLatched = true;
_latchedKeyCode = TranslateKeyCode(key);
// Only latch valid keys.
if (newCode != ImlacKey.Invalid)
{
_newKeyPressed = true;
_currentKeyCode = newCode;
}
}
if (!repeat)
{
// Only count new keystrokes.
_keyPressedCount++;
}
_keyLatchedLock.ExitWriteLock();
break;
}
@ -525,34 +410,16 @@ namespace imlac
break;
default:
UpdateDataSwitches(key, false /* key up */);
UpdateDataSwitches(key, false /* key down */);
_keyLatchedLock.EnterWriteLock();
//
// Decrement our pressed keycount, when this reaches zero
// this means the last pressed key has been released and we
// can set the keycode to None to indicate no keys being down.
// (This avoids issues with n-key rollover.)
//
_keyPressedCount--;
if (_keyPressedCount == 0)
{
_currentKeyCode = ImlacKey.None;
}
_latchedKeyCode = ImlacKey.Invalid;
_keyLatchedLock.ExitWriteLock();
break;
}
}
private void SdlMouseMove(int x, int y)
{
_mouseX = x;
_mouseY = y;
}
void FullScreenToggle()
{
_fullScreen = !_fullScreen;
@ -573,20 +440,23 @@ namespace imlac
//
// Wait for rendering to complete before returning.
//
WaitForSync();
_syncEvent.WaitOne();
}
public void DoRender(bool completeFrame)
{
{
// Draw the current set of vectors
_lock.EnterReadLock();
_frame++;
// Blinking items are on for 16 frames, off for 16.
if ((_frame % 16) == 0)
if (_frame == 60)
{
_frameBlink = !_frameBlink;
double currentTime = _timer.GetCurrentTime();
double fps = _frame / ((currentTime - _lastTime));
_lastTime = currentTime;
_frame = 0;
SDL.SDL_SetWindowTitle(_sdlWindow, String.Format("Imlac PDS-1 fps {0}", fps));
}
//
@ -609,7 +479,7 @@ namespace imlac
// And draw in this frame's vectors
for (int i = 0; i < _displayListIndex; i++)
{
_displayList[i].Draw(_sdlRenderer, _frameBlink);
_displayList[i].Draw(_sdlRenderer);
}
SDL.SDL_RenderPresent(_sdlRenderer);
@ -627,22 +497,22 @@ namespace imlac
_syncEvent.Set();
}
private void AddNewVector(DrawingMode mode, int startX, int startY, int endX, int endY)
private void AddNewVector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
{
//
// Scale the vector to the current scaling factor.
// The Imlac specifies 11 bits of resolution (2048 points in X and Y)
// which corresponds to a _scaleFactor of 1.0.
//
startX = (int)(startX * _scaleFactor + _xOffset);
startY = (int)(startY *_scaleFactor - _yOffset);
endX = (int)(endX * _scaleFactor + _xOffset);
endY = (int)(endY * _scaleFactor - _yOffset);
startX = (uint)(startX * _scaleFactor);
startY = (uint)(startY *_scaleFactor);
endX = (uint)(endX * _scaleFactor);
endY = (uint)(endY * _scaleFactor);
_lock.EnterWriteLock();
Vector newVector = _displayList[_displayListIndex];
newVector.Modify(mode, _intensity, _blink, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
newVector.Modify(mode, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
_displayListIndex++;
_lock.ExitWriteLock();
@ -655,17 +525,9 @@ namespace imlac
private class Vector
{
static Vector()
{
_rng = new Random();
}
public Vector(DrawingMode mode, float intensity, bool blink, uint startX, uint startY, uint endX, uint endY)
public Vector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
{
_mode = mode;
_intensity = intensity;
_blink = blink;
_x1 = (int)startX;
_y1 = (int)startY;
_x2 = (int)endX;
@ -674,63 +536,29 @@ namespace imlac
UpdateColor();
}
public void Modify(DrawingMode mode, float intensity, bool blink, short startX, short startY, short endX, short endY)
public void Modify(DrawingMode mode, short startX, short startY, short endX, short endY)
{
if (_mode != mode)
{
_mode = mode;
UpdateColor();
}
_intensity = intensity;
_blink = blink;
_x1 = (int)(startX + Perturbation());
_y1 = (int)(startY + Perturbation());
_x2 = (int)(endX + Perturbation());
_y2 = (int)(endY + Perturbation());
UpdateColor();
_x1 = (int)startX;
_y1 = (int)startY;
_x2 = (int)endX;
_y2 = (int)endY;
}
public void Draw(IntPtr sdlRenderer, bool blinkOn)
public void Draw(IntPtr sdlRenderer)
{
// TODO: handle dotted lines, line thickness options
if (!_blink || blinkOn)
{
SDL.SDL_SetRenderDrawColor(
sdlRenderer,
(byte)(_color.R * _intensity),
(byte)(_color.G * _intensity),
(byte)(_color.B * _intensity),
_color.A);
SDL.SDL_RenderDrawLine(sdlRenderer, _x1, _y1, _x2, _y2);
}
}
private int Perturbation()
{
if (Configuration.SquiggleMode)
{
double factor = 2.5;
return (int)(_rng.NextDouble() * factor - (factor / 2.0));
}
else
{
return 0;
}
// TODO: handle dotted lines, line thickness options
SDL.SDL_SetRenderDrawColor(sdlRenderer, _color.R, _color.G, _color.B, _color.A);
SDL.SDL_RenderDrawLine(sdlRenderer, _x1, _y1, _x2, _y2);
}
private void UpdateColor()
{
// Special case for single point vectors.
if (_mode != DrawingMode.Off &&
_x1 == _x2 &&
_y1 == _y2)
{
_mode = DrawingMode.Point;
}
switch (_mode)
{
case DrawingMode.Normal:
@ -752,21 +580,17 @@ namespace imlac
}
}
private DrawingMode _mode;
private DrawingMode _mode;
private Color _color;
private float _intensity;
private bool _blink;
private int _x1;
private int _y1;
private int _x2;
private int _y2;
private static Color NormalColor = Color.FromArgb(255, Color.GreenYellow);
private static Color PointColor = Color.FromArgb(255, Color.GreenYellow);
private static Color SGRColor = Color.FromArgb(127, Color.DarkGreen);
private static Color DebugColor = Color.FromArgb(127, Color.Red);
private static Random _rng;
private static Color NormalColor = Color.FromArgb(196, Color.ForestGreen);
private static Color PointColor = Color.FromArgb(255, Color.ForestGreen);
private static Color SGRColor = Color.FromArgb(128, Color.ForestGreen);
private static Color DebugColor = Color.FromArgb(255, Color.OrangeRed);
}
private static void BuildKeyMappings()
@ -781,7 +605,6 @@ namespace imlac
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_TAB, ImlacKey.Tab);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_RETURN, ImlacKey.CR);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAGEUP, ImlacKey.FF);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_RIGHTBRACKET, ImlacKey.LF);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAGEDOWN, ImlacKey.PageXmit);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_HOME, ImlacKey.Home);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAUSE, ImlacKey.Brk);
@ -789,9 +612,9 @@ namespace imlac
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_SPACE, ImlacKey.Space);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_COMMA, ImlacKey.Comma);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_EQUALS, ImlacKey.Minus);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PLUS, ImlacKey.Minus);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PERIOD, ImlacKey.Period);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_SLASH, ImlacKey.Slash);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUESTION, ImlacKey.Slash);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_0, ImlacKey.K0);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_1, ImlacKey.K1);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_2, ImlacKey.K2);
@ -810,7 +633,7 @@ namespace imlac
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_4, ImlacKey.D4);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_5, ImlacKey.D5);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_6, ImlacKey.D6);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUOTE, ImlacKey.Unlabeled);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUOTEDBL, ImlacKey.Unlabeled);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_a, ImlacKey.A);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_b, ImlacKey.B);
@ -840,8 +663,6 @@ namespace imlac
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_z, ImlacKey.Z);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_DELETE, ImlacKey.Del);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_BACKSPACE, ImlacKey.Del);
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_BACKSLASH, ImlacKey.Brk);
_sdlVKeymap = new Dictionary<SDL_Keycode, VKeys>();
@ -987,10 +808,9 @@ namespace imlac
private static Dictionary<SDL_Keycode, ImlacKey> _sdlImlacKeymap;
private static Dictionary<SDL_Keycode, VKeys> _sdlVKeymap;
private ImlacKey _currentKeyCode;
private ImlacKey _latchedKeyCode;
private ImlacKeyModifiers _keyModifiers;
private int _keyPressedCount;
private bool _newKeyPressed;
private bool _keyLatched;
private ReaderWriterLockSlim _keyLatchedLock;
@ -1020,15 +840,7 @@ namespace imlac
};
private int _dataSwitches;
private DataSwitchMappingMode _dataSwitchMappingMode;
//
// Mouse Data
//
private int _mouseX;
private int _mouseY;
private int _mouseButtons;
private DataSwitchMappingMode _dataSwitchMappingMode;
//
// SDL
@ -1049,19 +861,16 @@ namespace imlac
Resize
}
private System.Threading.Thread _displayThread;
private AutoResetEvent _syncEvent;
private int _x;
private int _y;
private uint _x;
private uint _y;
private int _xResolution;
private int _yResolution;
private float _scaleFactor;
private float _intensity;
private bool _blink; // The Blink attribute is applied to vectors while this is set.
private bool _frameBlink; // Blink'ed vectors are blanked while this is set (toggled every 16 frames).
private int _xOffset;
private int _yOffset;
private bool _renderCompleteFrame;
private bool _fullScreen;
@ -1075,9 +884,11 @@ namespace imlac
private System.Threading.ReaderWriterLockSlim _lock;
private uint _frame;
private double _lastTime;
// Framerate management
FrameTimer _frameTimer;
HighResTimer _timer;
}
}

View File

@ -31,7 +31,6 @@ namespace imlac
Halted,
SingleStep,
SingleFrame,
SingleDisplayOperation,
UntilDisplayStart,
Running,
Quit
@ -40,19 +39,19 @@ namespace imlac
public class ImlacSystem
{
public ImlacSystem()
{
{
_display = new SDLConsole(0.5f);
_memory = new Memory(this);
_paperTapeReader = new PaperTapeReader(this);
_tty = new TTY(this);
_keyboard = new Keyboard(this);
_clock = new AddressableClock(this);
_interruptFacility = new InterruptFacility(this);
_interruptFacility = new InterruptFacility(this);
_displayProcessor = new DisplayProcessor(this);
_processor = new Processor(this);
AttachDisplayProcessor();
// Register IOT devices
// Register IOT devices
_processor.RegisterDeviceIOTs(_displayProcessor);
_processor.RegisterDeviceIOTs(_paperTapeReader);
_processor.RegisterDeviceIOTs(_tty);
_processor.RegisterDeviceIOTs(_keyboard);
@ -71,16 +70,6 @@ namespace imlac
_processor.Reset();
}
public void Shutdown()
{
_display.Shutdown();
}
public void AttachConsole(IImlacConsole console)
{
_display = console;
}
public Memory Memory
{
get { return _memory; }
@ -91,7 +80,7 @@ namespace imlac
get { return _processor; }
}
public DisplayProcessorBase DisplayProcessor
public DisplayProcessor DisplayProcessor
{
get { return _displayProcessor; }
}
@ -135,12 +124,8 @@ namespace imlac
_keyboard.Clock();
_clock.Clock();
// interrupts last so that devices that raise interrupts get clocked first.
// We clock interrupts on the leading edge of the processor's Fetch state.
if (_processor.InstructionState == ExecState.Fetch)
{
_interruptFacility.Clock();
}
// interrupts last so that devices that raise interrupts get clocked first
_interruptFacility.Clock();
}
//
@ -176,10 +161,11 @@ namespace imlac
return SystemExecutionState.Running;
}
[DebuggerFunction("set pc", "Sets the Processor's PC to the specified address", "<address>")]
private SystemExecutionState SetPC(ushort address)
[DebuggerFunction("step", "Executes a single instruction cycle at the specified address", "<address>")]
private SystemExecutionState StepProcessor(ushort address)
{
Processor.PC = address;
Processor.State = ProcessorState.Running;
return SystemExecutionState.SingleStep;
}
@ -204,29 +190,13 @@ namespace imlac
return SystemExecutionState.UntilDisplayStart;
}
[DebuggerFunction("step display", "Runs until the end of the next display drawing or positioning ooperation")]
private SystemExecutionState RunDisplayOp()
{
Processor.State = ProcessorState.Running;
return SystemExecutionState.SingleDisplayOperation;
}
[DebuggerFunction("load bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
private SystemExecutionState LoadBootstrap(string bootstrap)
[DebuggerFunction("set bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
private SystemExecutionState SetBootstrap(string bootstrap)
{
LoadMemory(Paths.BuildBootPath(bootstrap), 0x20, 0x20);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("boot", "Loads the specified bootstrap into memory at 40 and executes it", "<bootstrap>")]
private SystemExecutionState Boot(string bootstrap)
{
LoadMemory(Paths.BuildBootPath(bootstrap), 0x20, 0x20);
Processor.PC = 0x20;
Processor.State = ProcessorState.Running;
return SystemExecutionState.Running;
}
[DebuggerFunction("save memory", "Saves the specified range of memory to a file", "<file> <start> <length>")]
private SystemExecutionState SaveMemoryContents(string file, ushort start, ushort length)
{
@ -269,36 +239,13 @@ namespace imlac
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show memory", "Displays memory contents", "<start> <length>")]
[DebuggerFunction("display memory", "Displays memory contents", "<start> <length>")]
private SystemExecutionState DisplayMemory(ushort start, ushort length)
{
DumpMemory(start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set memory size 4kw", "Sets memory size to 4KW")]
private SystemExecutionState SetMemorySize4kw()
{
_memory.SetMemorySize(0x1000);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set memory size 8kw", "Sets memory size to 8KW")]
private SystemExecutionState SetMemorySize8kw()
{
_memory.SetMemorySize(0x2000);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set memory size 16kw", "Sets memory size to 16KW")]
private SystemExecutionState SetMemorySize16kw()
{
_memory.SetMemorySize(0x4000);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set logging", "Sets the logging configuration", "<loglevel>")]
private SystemExecutionState SetLogging(LogType value)
{
@ -334,20 +281,6 @@ namespace imlac
return SystemExecutionState.Debugging;
}
[DebuggerFunction("attach tty telnet", "Attaches the Imlac's TTY to a raw telnet port", "<host> <port>")]
private SystemExecutionState AttachTTY(string host, ushort port)
{
TTY.SetChannel(new TelnetDataChannel(host, port, false));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("attach tty raw", "Attaches the Imlac's TTY to a raw port", "<host> <port>")]
private SystemExecutionState AttachTTYRaw(string host, ushort port)
{
TTY.SetChannel(new TelnetDataChannel(host, port, true));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("detach tty", "Detaches the Imlac's TTY from host inputs")]
private SystemExecutionState DetachTTY()
{
@ -515,114 +448,23 @@ namespace imlac
return SystemExecutionState.Debugging;
}
[DebuggerFunction("enable mit mode", "Turns MIT modifications on.")]
private SystemExecutionState EnableMITMode()
{
Configuration.MITMode = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disable mit mode", "Turns MIT modifications off.")]
private SystemExecutionState DisableMITMode()
{
Configuration.MITMode = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("enable squiggle mode", "Turns vector perturbations on.")]
private SystemExecutionState EnableSquiggleMode()
{
Configuration.SquiggleMode = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disable squiggle mode", "Turns vector perturbations off.")]
private SystemExecutionState DisableSquiggleMode()
{
Configuration.SquiggleMode = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("draw invisible vectors", "Displays non-drawing beam motion in red.")]
private SystemExecutionState ShowInvisibleVectors()
{
Configuration.ShowInvisibleVectors = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("hide invisible vectors", "Hides non-drawing beam motion (the default).")]
private SystemExecutionState HideInvisibleVectors()
{
Configuration.ShowInvisibleVectors = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set cpu type", "Selects CPU type: PDS1 or PDS4", "<cpu>")]
private SystemExecutionState SetCPUType(ImlacCPUType type)
{
if (Configuration.CPUType != type)
{
Configuration.CPUType = type;
AttachDisplayProcessor();
_displayProcessor.Reset();
}
return SystemExecutionState.Debugging;
}
[DebuggerFunction("halt on invalid instructions", "Halts emulation if an invalid instruction is encountered.")]
private SystemExecutionState HaltOnInvalidInstructions()
{
Configuration.HaltOnInvalidOpcodes = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("continue on invalid instructions", "Continues emulation if an invalid instruction is encountered.")]
private SystemExecutionState ContinueOnInvalidInstructions()
{
Configuration.HaltOnInvalidOpcodes = false;
return SystemExecutionState.Debugging;
}
private void AttachDisplayProcessor()
{
// Unregister current display processor
if (_displayProcessor != null)
{
_processor.UnregisterDeviceIOTs(_displayProcessor);
}
if (Configuration.CPUType == ImlacCPUType.PDS1)
{
_displayProcessor = new PDS1DisplayProcessor(this);
}
else
{
_displayProcessor = new PDS4DisplayProcessor(this);
}
_processor.RegisterDeviceIOTs(_displayProcessor);
}
public enum DisassemblyMode
{
Processor,
DisplayProcessor,
DisplayIncrement,
DisplayCompact,
DisplayAuto
}
public void PrintProcessorStatus()
{
Console.WriteLine("PC={0} AC={1} MB={2} - {3}\n{4}",
Console.WriteLine("PC={0} AC={1} MB={2} - {3}",
Helpers.ToOctal(Processor.PC),
Helpers.ToOctal(Processor.AC),
Helpers.ToOctal(Memory.Fetch(Processor.PC)),
Processor.State,
Processor.Disassemble(Processor.PC));
Processor.State);
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6}",
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6} - {7}",
Helpers.ToOctal(DisplayProcessor.PC),
Helpers.ToOctal(DisplayProcessor.DT),
Helpers.ToOctal(DisplayProcessor.DPCEntry),
@ -704,35 +546,28 @@ namespace imlac
}
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
ushort address = startAddress;
while (address < endAddress)
for (ushort address = startAddress; address <= endAddress; address++)
{
string disassembly = string.Empty;
int size = 0;
try
{
switch (mode)
{
case DisassemblyMode.Processor:
size = 1;
disassembly = Processor.Disassemble(address);
break;
case DisassemblyMode.DisplayAuto:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate, out size);
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate);
break;
case DisassemblyMode.DisplayProcessor:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor, out size);
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor);
break;
case DisassemblyMode.DisplayIncrement:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment, out size);
break;
case DisassemblyMode.DisplayCompact:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.CompactAddressing, out size);
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment);
break;
}
}
@ -740,12 +575,9 @@ namespace imlac
{
// this can happen if the data is not a valid instruction
disassembly = "<invalid instruction>";
size = 1;
}
Console.WriteLine("{0}\\{1} {2}", Helpers.ToOctal(address), Helpers.ToOctal(Memory.Fetch(address)), disassembly);
address += (ushort)size;
}
}
@ -794,7 +626,7 @@ namespace imlac
}
private Processor _processor;
private DisplayProcessorBase _displayProcessor;
private DisplayProcessor _displayProcessor;
private IImlacConsole _display;
private Memory _memory;
private PaperTapeReader _paperTapeReader;

View File

@ -36,7 +36,6 @@ namespace imlac
Interrupt = 0x10,
TTY = 0x20,
PTR = 0x40,
Telnet = 0x80,
All = 0x7fffffff
}
@ -98,10 +97,6 @@ namespace imlac
case LogType.Interrupt:
color = ConsoleColor.Blue;
break;
case LogType.Telnet:
color = ConsoleColor.Cyan;
break;
default:
// No change

View File

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware>true</dpiAware>
</windowsSettings>
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -34,9 +34,6 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="SDL2-CS, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SDL2-CS.dll.2.0.0.0\lib\net20\SDL2-CS.dll</HintPath>
@ -49,19 +46,15 @@
<Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration.cs" />
<Compile Include="Debugger\BreakpointManager.cs" />
<Compile Include="Debugger\Console.cs" />
<Compile Include="Debugger\DebuggerAttributes.cs" />
<Compile Include="Debugger\DebuggerPrompt.cs" />
<Compile Include="PDS4DisplayProcessor.cs" />
<Compile Include="PDS1DisplayProcessor.cs" />
<Compile Include="HighResTimer.cs" />
<Compile Include="IO\TTYChannels\NullDataChannel.cs" />
<Compile Include="IO\TTYChannels\SerialDataChannel.cs" />
<Compile Include="IO\TTYChannels\StreamDataChannel.cs" />
<Compile Include="IO\TTYChannels\ISerialDataChannel.cs" />
<Compile Include="IO\TTYChannels\TelnetDataChannel.cs" />
<Compile Include="SDLConsole.cs" />
<Compile Include="DisplayProcessor.cs" />
<Compile Include="Helpers.cs" />
@ -92,7 +85,6 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="app.manifest" />
<None Include="boot\mtty">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@ -1,20 +1,19 @@
sImlac v0.3 README - (c) 2016-2020 Living Computers: Museum+Labs
sImlac v0.2 README - (c) 2016-2018 Living Computers: Museum+Labs
-----------------------------------------------------------------
1. Overview
-----------
sImlac is an attempt to emulate/simulate the oft neglected Imlac PDS-1 and
PDS-4 computers/terminals. The Imlac combined a 16-bit CPU (very PDP-8 like)
with a simple (but fairly flexible) Display Processor which drove a vector
sImlac is an attempt to emulate/simulate the oft neglected Imlac PDS-1
computer/terminal. The Imlac combined a 16-bit CPU (very PDP-8 like) with
a simple (but fairly flexible) Display Processor which drove a vector
display.
sImlac currently emulates the current hardware:
- Standard Imlac PDS-1D Processor / Display Processor (with 1.8us cycle timings)
- Standard Imlac PDS-4 Processor / Display Processor (with 990ns cycle timings)
- Up to 16KW of core memory
- Standard Imlac Processor / Display Processor (with 1.8us cycle timings)
- 8KW of core memory
- Vector display (with long-persistence phosphor)
- PTR and TTY interfaces (using physical serial ports, telnet hosts, or files as inputs)
- PTR and TTY interfaces (using physical serial ports or files as inputs)
- Keyboard
- Interrupt facility
- Long Vector (LVH-1 option) instruction support
@ -24,8 +23,7 @@ This is enough to have fun with the small amount of archived software that's out
there. Support for additional hardware is planned, but is mostly dependent on
finding software that requires it.
Since this is v0.3, there are still likely to be bugs. In particular, the PDS-4 support
introduced in this release is not well tested due to lack of applicable software.
Since this is v0.2, there are still likely to be bugs.
Questions, comments, or bug reports can be directed at
joshd@livingcomputers.org.
@ -38,7 +36,7 @@ Windows Vista or later, with version 4.5.3 or later of the .NET Framework instal
.NET should be present by default on Windows Vista and later; if it is not installed
on your computer it can be obtained at https://www.microsoft.com/net.
As sImlac is a .NET application it will also run under Mono
As ContrAlto is a .NET application it will also run under Mono
(http://www.mono-project.com/) on Unix and OS X.
sImlac uses SDL 2.0 for display and input. On Windows the appropriate SDL.dll is
@ -59,18 +57,20 @@ sImlac comes with three boot loaders, and the correct one must be chosen for
the image you are loading (see software.txt for a listing that describes the
available software and the loader to use).
A loader can be loaded and run using the "boot" command. Let's say we want
A loader can be loaded using the "set bootstrap" command. Let's say we want
to play a game of "Space War!." The image named "war" uses the STTY (special
TTY) loader, so we issue the following commands:
TTY) loader, so we issue the following command:
We need to attach the image to the TTY port, this is done by:
> set bootstrap stty
Now we need to attach the image to the TTY port, this is done by:
> attach tty file <path-to-images>\war
Once this is done, we can start the CPU running the bootstrap, which will
load the "war" program into memory and start it running:
Once this is done, we can start the CPU running. By default, the PC is set
to 40(8), the start of the boot loader, so we can just do:
> boot stty
> go
And the loader will run. This will take a few seconds to complete, after which
the title screen for "Space War!" will appear in the display window.
@ -135,11 +135,8 @@ the "Insert" key will toggle between window and fullscreen modes.
------------
reset : Resets the Imlac, but does not clear its memory.
Any files attached to the paper tape reader or TTY
interface are reset to the beginning of the file.
boot <boot> : Loads the specified bootstrap into memory at 40(8) and
executes it.
set bootstrap <boot>: Loads the specified bootstrap into memory at 40(8).
Options are PTR (paper tape), TTY (standard TTY), and
STTY (alternate TTY).
@ -152,17 +149,9 @@ attach tty file [file] : Specifies a TTY or STTY-format file to attach to the TT
bootstraps.
attach tty port [port] [rate] [parity] [data bits] [stop bits] :
Specifies a physical port on the host machine to connect
to the emulated TTY port. Allows the emulated Imlac to
talk to real serial devices.
attach tty telnet [host] [port] :
Specifies a telnet host to connect to the emulated TTY
port. Allows the emulated Imlac to talk to another
computer over the Internet.
attach tty raw [host] [port] :
As above, but using a raw connection rather than Telnet.
Specifies a physical port on the host machine to connect
to the emulated TTY port. Allows the emulated Imlac to
talk to real serial devices.
go <addr> : Starts the system running at the current PC, or <addr> if
specified.
@ -176,16 +165,6 @@ step frame end : Runs the current 40hz frame to its completion. If the
step frame start : Runs until the start of the next frame.
set cpu type [type] : Sets the type of Imlac system sImlac will emulate.
Valid options are "PDS1" and "PDS4". Switching the cpu
type in the middle of a running program will produce
interesting results.
enable mit mode : Turns on MIT's DADR addressing modifications. This is
necessary to run some Imlac software from MIT.
disable mit mode : The opposite of the above.
edit memory [addr] : Begins a very simple memory editor starting at address
'addr'. You will be shown the current address\contents,
and prompted for new contents. Press ENTER to keep the
@ -352,13 +331,6 @@ Thanks go out to:
7. Revision History
-------------------
v0.3 - Added PDS-4 support.
- Many bugfixes to PDS-1 emulation
- Added telnet/raw ports
- Added high-dpi awareness
- Enabled MIT display addressing modifications
- Fixed 40 cycle / display halt interrupt
v0.2 - Updated to use SDL-CS for better cross-platform support.
v0.1 - Initial Release