mirror of
https://github.com/livingcomputermuseum/sImlac.git
synced 2026-01-13 15:27:40 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c5437059 | ||
|
|
f0bc983c8a | ||
|
|
0af475cc27 | ||
|
|
8b7280ebe7 | ||
|
|
4c65f894be | ||
|
|
fcf0c96b6c | ||
|
|
6f30305c79 | ||
|
|
4130c969c2 | ||
|
|
3c55ecfb5d | ||
|
|
1dd50d74f4 | ||
|
|
3efc31f107 | ||
|
|
5124c35296 | ||
|
|
6f7711fe27 | ||
|
|
8ced87eb1e | ||
|
|
3a7afbafc0 | ||
|
|
aee6c5dc63 | ||
|
|
99d6173392 | ||
|
|
64d027dcab | ||
|
|
5b9eb37f9c | ||
|
|
7dce231368 | ||
|
|
7b616105b2 | ||
|
|
6ddbf3791d | ||
|
|
b0321a70e6 | ||
|
|
8b5fb8e997 | ||
|
|
d33cc3d166 | ||
|
|
18bb3eb9bd | ||
|
|
d54292d346 | ||
|
|
98189686ce | ||
|
|
ed54a38d3a | ||
|
|
a7f81a8b8f | ||
|
|
34d48856b6 | ||
|
|
cc6cc29682 | ||
|
|
e306c11e4f | ||
|
|
91682a2f6d | ||
|
|
47c6643cce | ||
|
|
085886e30c | ||
|
|
d1971d41de | ||
|
|
39032d79c4 | ||
|
|
2fb927d857 | ||
|
|
9ed3e85ab6 | ||
|
|
2d2f4d9b6d | ||
|
|
0c26c2f080 | ||
|
|
024d95994b | ||
|
|
8d866c46c4 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -190,3 +190,7 @@ _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
|
||||
|
||||
2
.travis.yml
Normal file
2
.travis.yml
Normal file
@ -0,0 +1,2 @@
|
||||
language: csharp
|
||||
solution: imlac.sln
|
||||
3
.vs/ProjectSettings.json
Normal file
3
.vs/ProjectSettings.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"CurrentProjectSetting": null
|
||||
}
|
||||
8
.vs/VSWorkspaceState.json
Normal file
8
.vs/VSWorkspaceState.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
"",
|
||||
"\\imlac"
|
||||
],
|
||||
"SelectedNode": "\\imlac.sln",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/larsbrinkhoff/sImlac/history)
|
||||
8
appveyor.yml
Normal file
8
appveyor.yml
Normal file
@ -0,0 +1,8 @@
|
||||
platform:
|
||||
- x86
|
||||
install:
|
||||
- nuget restore
|
||||
build:
|
||||
project: imlac.sln
|
||||
artifacts:
|
||||
- path: imlac/bin
|
||||
34
imlac/Configuration.cs
Normal file
34
imlac/Configuration.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ namespace imlac.Debugger
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSubNode(List<string> words, MethodInfo method, object instance)
|
||||
public void AddSubNode(List<string> words, DebuggerCommand command)
|
||||
{
|
||||
// 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(instance, words[0], null, null, null);
|
||||
subNode = new DebuggerCommand(command.Instance, words[0], command.Description, command.Usage, 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(method);
|
||||
subNode.Methods.Add(command.Methods[0]);
|
||||
|
||||
// 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 = method.GetParameters().Length;
|
||||
int argCount = command.Methods[0].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(method);
|
||||
subNode.Methods.Add(command.Methods[0]);
|
||||
|
||||
// and return early.
|
||||
return;
|
||||
@ -119,7 +119,7 @@ namespace imlac.Debugger
|
||||
|
||||
// We have more words to go.
|
||||
words.RemoveAt(0);
|
||||
subNode.AddSubNode(words, method, instance);
|
||||
subNode.AddSubNode(words, command);
|
||||
}
|
||||
|
||||
public DebuggerCommand FindSubNodeByName(string name)
|
||||
@ -159,14 +159,22 @@ namespace imlac.Debugger
|
||||
// Get the command string from the prompt.
|
||||
string command = _consolePrompt.Prompt().Trim();
|
||||
|
||||
if (command != String.Empty)
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
next = ExecuteLine(command, system);
|
||||
// Repeat the last command
|
||||
command = _lastCommand;
|
||||
Console.WriteLine(">> {0}", command);
|
||||
}
|
||||
|
||||
next = ExecuteLine(command, system);
|
||||
|
||||
_lastCommand = command;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(
|
||||
"Error: {0}",
|
||||
e.InnerException != null ? e.InnerException.Message : e.Message);
|
||||
}
|
||||
|
||||
return next;
|
||||
@ -650,9 +658,9 @@ namespace imlac.Debugger
|
||||
{
|
||||
string[] commandWords = c.Name.Split(' ');
|
||||
|
||||
// This is kind of ugly, we know that at this point every command built above have only
|
||||
// This is kind of ugly, we know that at this point every command built above has only
|
||||
// one method. When building the tree, overloaded commands may end up with more than one.
|
||||
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0], c.Instance);
|
||||
_commandRoot.AddSubNode(new List<string>(commandWords), c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,5 +691,7 @@ namespace imlac.Debugger
|
||||
private DebuggerPrompt _consolePrompt;
|
||||
private DebuggerCommand _commandRoot;
|
||||
private List<DebuggerCommand> _commandList;
|
||||
|
||||
private string _lastCommand;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,8 +197,11 @@ namespace imlac.Debugger
|
||||
clear = sb.ToString();
|
||||
}
|
||||
|
||||
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
|
||||
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
|
||||
// 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;
|
||||
|
||||
// Move cursor to origin to draw string
|
||||
Console.CursorLeft = _originColumn;
|
||||
@ -294,6 +297,13 @@ 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;
|
||||
}
|
||||
@ -320,6 +330,7 @@ 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.
|
||||
@ -328,6 +339,7 @@ namespace imlac.Debugger
|
||||
if (c.Name.ToLower() == tokens[0].ToLower())
|
||||
{
|
||||
match = c;
|
||||
exactMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -391,7 +403,7 @@ namespace imlac.Debugger
|
||||
{
|
||||
subMatch = FuzzyMatch(match, tokens, silent);
|
||||
}
|
||||
else // if (exactMatch)
|
||||
else
|
||||
{
|
||||
if (!silent && match.SubCommands.Count > 1)
|
||||
{
|
||||
@ -419,11 +431,27 @@ 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
@ -24,28 +24,57 @@ namespace imlac
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
ImlacSystem system = new ImlacSystem();
|
||||
ConsoleExecutor debuggerPrompt =
|
||||
new ConsoleExecutor(system);
|
||||
_startupArgs = args;
|
||||
|
||||
_console = new SDLConsole(0.5f);
|
||||
_imlacSystem = new ImlacSystem();
|
||||
_imlacSystem.AttachConsole(_console);
|
||||
_imlacSystem.Reset();
|
||||
|
||||
_state = SystemExecutionState.Debugging;
|
||||
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnBreak);
|
||||
|
||||
PrintHerald();
|
||||
|
||||
if (args.Length > 0)
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
//
|
||||
// Assume arg 0 is a script file to be executed.
|
||||
//
|
||||
Console.WriteLine("Executing startup script '{0}'", args[0]);
|
||||
Console.WriteLine("Executing startup script '{0}'", _startupArgs[0]);
|
||||
|
||||
try
|
||||
{
|
||||
_state = debuggerPrompt.ExecuteScript(system, args[0]);
|
||||
_state = debuggerPrompt.ExecuteScript(_imlacSystem, _startupArgs[0]);
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Error parsing script: {0}", e.Message);
|
||||
}
|
||||
@ -59,19 +88,19 @@ namespace imlac
|
||||
{
|
||||
case SystemExecutionState.Halted:
|
||||
case SystemExecutionState.Debugging:
|
||||
_state = debuggerPrompt.Prompt(system);
|
||||
_state = debuggerPrompt.Prompt(_imlacSystem);
|
||||
break;
|
||||
|
||||
case SystemExecutionState.SingleStep:
|
||||
system.SingleStep();
|
||||
system.Display.RenderCurrent(false);
|
||||
_imlacSystem.SingleStep();
|
||||
_imlacSystem.Display.RenderCurrent(false);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
break;
|
||||
|
||||
case SystemExecutionState.SingleFrame:
|
||||
system.SingleStep();
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.DisplayProcessor.FrameLatch)
|
||||
if (_imlacSystem.DisplayProcessor.FrameLatch)
|
||||
{
|
||||
Console.WriteLine("Frame completed.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
@ -79,55 +108,85 @@ namespace imlac
|
||||
break;
|
||||
|
||||
case SystemExecutionState.UntilDisplayStart:
|
||||
system.SingleStep();
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.DisplayProcessor.State == ProcessorState.Running)
|
||||
if (_imlacSystem.DisplayProcessor.State == ProcessorState.Running)
|
||||
{
|
||||
Console.WriteLine("Display started.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
break;
|
||||
|
||||
case SystemExecutionState.Running:
|
||||
system.SingleStep();
|
||||
case SystemExecutionState.SingleDisplayOperation:
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.Processor.State == ProcessorState.Halted)
|
||||
if (_imlacSystem.DisplayProcessor.DisplayDrawLatch)
|
||||
{
|
||||
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(system.Processor.PC));
|
||||
_imlacSystem.Display.RenderCurrent(false);
|
||||
Console.WriteLine("Display operation completed.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
else if (system.Processor.State == ProcessorState.BreakpointHalt)
|
||||
break;
|
||||
|
||||
case SystemExecutionState.Running:
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (_imlacSystem.Processor.State == ProcessorState.Halted)
|
||||
{
|
||||
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(_imlacSystem.Processor.PC));
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
else if (_imlacSystem.Processor.State == ProcessorState.BreakpointHalt)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Breakpoint hit: {0} at address {1}",
|
||||
BreakpointManager.GetBreakpoint(system.Processor.BreakpointAddress),
|
||||
Helpers.ToOctal(system.Processor.BreakpointAddress));
|
||||
BreakpointManager.GetBreakpoint(_imlacSystem.Processor.BreakpointAddress),
|
||||
Helpers.ToOctal(_imlacSystem.Processor.BreakpointAddress));
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Internal error during execution: {0}", e.Message);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
if (!(e is System.Threading.ThreadAbortException))
|
||||
{
|
||||
Console.WriteLine("Internal error during execution: {0}", e.Message);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are exiting, shut things down.
|
||||
//
|
||||
_imlacSystem.Shutdown();
|
||||
}
|
||||
|
||||
static void OnBreak(object sender, ConsoleCancelEventArgs e)
|
||||
private 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void PrintHerald()
|
||||
private static void PrintHerald()
|
||||
{
|
||||
Console.WriteLine("sImlac v0.1. (c) 2016, 2017 Living Computers: Museum+Labs");
|
||||
Console.WriteLine("sImlac v0.3. (c) 2016-2020 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,17 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,16 +155,24 @@ namespace imlac
|
||||
|
||||
~FrameTimer()
|
||||
{
|
||||
//
|
||||
// Clean stuff up
|
||||
//
|
||||
DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
|
||||
DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
|
||||
try
|
||||
{
|
||||
//
|
||||
// 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();
|
||||
//
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -28,7 +28,111 @@ namespace imlac
|
||||
SGR1, // reduced intensity
|
||||
Point, // increased intensity
|
||||
Debug, // For debugging purposes
|
||||
}
|
||||
}
|
||||
|
||||
public enum VKeys
|
||||
{
|
||||
// Keys that map to Imlac keyboard keys
|
||||
Shift = 0x10,
|
||||
Ctrl = 0x11,
|
||||
Alt = 0x12,
|
||||
|
||||
End = 0x23,
|
||||
DownArrow = 0x28,
|
||||
RightArrow = 0x27,
|
||||
UpArrow = 0x26,
|
||||
LeftArrow = 0x25,
|
||||
Tab = 0x9,
|
||||
Return = 0xd,
|
||||
PageUp = 0x21,
|
||||
PageDown = 0x22,
|
||||
Home = 0x24,
|
||||
Pause = 0x91,
|
||||
Escape = 0x1b,
|
||||
Space = 0x20,
|
||||
|
||||
Comma = 0xbc,
|
||||
Plus = 0xbb,
|
||||
Period = 0xbe,
|
||||
QuestionMark = 0xbf,
|
||||
Zero = 0x30,
|
||||
One = 0x31,
|
||||
Two = 0x32,
|
||||
Three = 0x33,
|
||||
Four = 0x34,
|
||||
Five = 0x35,
|
||||
Six = 0x36,
|
||||
Seven = 0x37,
|
||||
Eight = 0x38,
|
||||
Nine = 0x39,
|
||||
Minus = 0xbd,
|
||||
Semicolon = 0xba,
|
||||
|
||||
Keypad0 = 0x60,
|
||||
Keypad2 = 0x62,
|
||||
Keypad4 = 0x64,
|
||||
Keypad5 = 0x65,
|
||||
Keypad6 = 0x66,
|
||||
DoubleQuote = 0xde,
|
||||
|
||||
A = 0x41,
|
||||
B = 0x42,
|
||||
C = 0x43,
|
||||
D = 0x44,
|
||||
E = 0x45,
|
||||
F = 0x46,
|
||||
G = 0x47,
|
||||
H = 0x48,
|
||||
I = 0x49,
|
||||
J = 0x4a,
|
||||
K = 0x4b,
|
||||
L = 0x4c,
|
||||
M = 0x4d,
|
||||
N = 0x4e,
|
||||
O = 0x4f,
|
||||
P = 0x50,
|
||||
Q = 0x51,
|
||||
R = 0x52,
|
||||
S = 0x53,
|
||||
T = 0x54,
|
||||
U = 0x55,
|
||||
V = 0x56,
|
||||
W = 0x57,
|
||||
X = 0x58,
|
||||
Y = 0x59,
|
||||
Z = 0x5a,
|
||||
|
||||
Delete = 0x8,
|
||||
|
||||
// Additional keys, not used by the Imlac but available
|
||||
// for data switch mapping.
|
||||
F1 = 0x70,
|
||||
F2 = 0x71,
|
||||
F3 = 0x72,
|
||||
F4 = 0x73,
|
||||
F5 = 0x74,
|
||||
F6 = 0x75,
|
||||
F7 = 0x76,
|
||||
F8 = 0x77,
|
||||
F9 = 0x78,
|
||||
F10 = 0x79,
|
||||
F11 = 0x7a,
|
||||
F12 = 0x7b,
|
||||
|
||||
Keypad1 = 0x61,
|
||||
Keypad3 = 0x63,
|
||||
Keypad7 = 0x67,
|
||||
Keypad8 = 0x68,
|
||||
Keypad9 = 0x69,
|
||||
|
||||
|
||||
// hack to toggle fullscreen.
|
||||
Insert = 0x2d,
|
||||
|
||||
// Special values for data switch mappings
|
||||
None0 = 0, // No key mapped to data switch, DS for this bit is set to 0
|
||||
None1 = 1, // Ditto, but for the value 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IImlacConsole provides the interface for the components making up a standard
|
||||
@ -40,7 +144,7 @@ namespace imlac
|
||||
/// <summary>
|
||||
/// Indicates whether a key is currently pressed.
|
||||
/// </summary>
|
||||
bool IsKeyPressed
|
||||
bool NewKeyPressed
|
||||
{
|
||||
get;
|
||||
}
|
||||
@ -69,6 +173,16 @@ namespace imlac
|
||||
get;
|
||||
}
|
||||
|
||||
int MouseX
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
int MouseY
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool ThrottleFramerate
|
||||
{
|
||||
get;
|
||||
@ -97,9 +211,9 @@ namespace imlac
|
||||
|
||||
void ClearDisplay();
|
||||
|
||||
void MoveAbsolute(uint x, uint y, DrawingMode mode);
|
||||
void MoveAbsolute(int x, int y, DrawingMode mode);
|
||||
|
||||
void DrawPoint(uint x, uint y);
|
||||
void DrawPoint(int x, int y);
|
||||
|
||||
void RenderCurrent(bool completeFrame);
|
||||
|
||||
@ -107,8 +221,14 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,38 +69,87 @@ namespace imlac.IO
|
||||
|
||||
public void Clock()
|
||||
{
|
||||
if (_interruptsEnabled)
|
||||
if (_interruptsEnabled && _interruptEnableCount == 0)
|
||||
{
|
||||
// Collect up devices that want to interrupt us.
|
||||
_interruptStatus = 0;
|
||||
|
||||
// bit 14: 40 cycle sync
|
||||
if (_system.DisplayProcessor.FrameLatch)
|
||||
//
|
||||
// PDS-1 and PDS-4 status bits are arranged almost entirely differently.
|
||||
//
|
||||
if (Configuration.CPUType == ImlacCPUType.PDS1)
|
||||
{
|
||||
_interruptStatus |= 0x0002;
|
||||
}
|
||||
// PDS-1:
|
||||
|
||||
// bit 12 - TTY rcv
|
||||
if (_system.TTY.DataReady)
|
||||
{
|
||||
_interruptStatus |= 0x0008;
|
||||
}
|
||||
// bit 14: 40 cycle sync
|
||||
if (_system.DisplayProcessor.FrameLatch &&
|
||||
_system.DisplayProcessor.DisplayHalted)
|
||||
{
|
||||
_interruptStatus |= 0x0002;
|
||||
}
|
||||
|
||||
// bit 11 - keyboard
|
||||
if (_system.Keyboard.KeyReady)
|
||||
{
|
||||
_interruptStatus |= 0x0010;
|
||||
}
|
||||
// bit 12 - TTY rcv
|
||||
if (_system.TTY.DataReady)
|
||||
{
|
||||
_interruptStatus |= 0x0008;
|
||||
}
|
||||
|
||||
// bit 2 - ACI-1 (clock)
|
||||
if (_system.Clock.TimerTriggered)
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_interruptStatus |= 0x2000;
|
||||
// PDS-4:
|
||||
|
||||
// Bit 15: Display halt
|
||||
if (_system.DisplayProcessor.DisplayHalted)
|
||||
{
|
||||
_interruptStatus |= 0x0001;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ((_interruptMask & _interruptStatus) != 0)
|
||||
if (_interruptsEnabled && (_interruptMask & _interruptStatus) != 0)
|
||||
{
|
||||
_interruptPending = true;
|
||||
}
|
||||
@ -109,7 +158,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.InstructionState == ExecState.Fetch)
|
||||
if (_interruptPending && _system.Processor.CanBeInterrupted())
|
||||
{
|
||||
// save the current PC at 0
|
||||
_system.Memory.Store(0x0000, _system.Processor.PC);
|
||||
@ -124,6 +173,10 @@ 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()
|
||||
@ -135,6 +188,7 @@ namespace imlac.IO
|
||||
{
|
||||
//
|
||||
// Dispatch the IOT instruction.
|
||||
// TODO: handle PDS-4 IOTs.
|
||||
//
|
||||
switch (iotCode)
|
||||
{
|
||||
@ -146,12 +200,20 @@ 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 {0} copied to AC", Helpers.ToOctal((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.");
|
||||
break;
|
||||
|
||||
case 0x61:
|
||||
@ -161,10 +223,11 @@ namespace imlac.IO
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0:x4}", iotCode));
|
||||
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0}", Helpers.ToOctal((ushort)iotCode)));
|
||||
}
|
||||
}
|
||||
|
||||
private int _interruptEnableCount;
|
||||
private bool _interruptsEnabled;
|
||||
private bool _interruptPending;
|
||||
private int _interruptMask;
|
||||
@ -174,10 +237,18 @@ 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
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ namespace imlac.IO
|
||||
|
||||
public enum ImlacKey
|
||||
{
|
||||
None = 0x0,
|
||||
DataXmit = 0x2,
|
||||
Down = 0x4,
|
||||
Right = 0x5,
|
||||
@ -39,6 +40,7 @@ namespace imlac.IO
|
||||
Tab = 0x9,
|
||||
CR = 0x0d,
|
||||
FF = 0x0c,
|
||||
LF = 0x0a,
|
||||
PageXmit = 0xe,
|
||||
Home = 0xf,
|
||||
Brk = 0x19,
|
||||
@ -117,26 +119,18 @@ namespace imlac.IO
|
||||
public void Clock()
|
||||
{
|
||||
// If we do not already have a key latched and one has been pressed,
|
||||
// we will latch it now.
|
||||
// Based on the keycode & modifiers we will generate an Imlac keyboard code
|
||||
if (_system.Display.IsKeyPressed)
|
||||
// we will raise the Ready flag now.
|
||||
if (!_keyReady)
|
||||
{
|
||||
_keyCode = GetScancodeForCurrentKey();
|
||||
if (_keyCode != 0)
|
||||
{
|
||||
Trace.Log(LogType.Keyboard, "Key latched {0}", Helpers.ToOctal(_keyCode));
|
||||
if (_system.Display.NewKeyPressed)
|
||||
{
|
||||
_keyReady = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyCode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_keyCode = 0;
|
||||
_keyReady = false;
|
||||
}
|
||||
|
||||
@ -155,20 +149,18 @@ namespace imlac.IO
|
||||
switch (iotCode)
|
||||
{
|
||||
case 0x11:
|
||||
_system.Processor.AC |= _keyCode;
|
||||
_system.Processor.AC |= GetScancodeForCurrentKey();
|
||||
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 |= _keyCode;
|
||||
_keyCode = 0;
|
||||
_system.Processor.AC |= GetScancodeForCurrentKey();
|
||||
_keyReady = false;
|
||||
_system.Display.UnlatchKey();
|
||||
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}, keyboard flag reset.", Helpers.ToOctal(_system.Processor.AC));
|
||||
@ -196,7 +188,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.
|
||||
@ -225,7 +217,6 @@ namespace imlac.IO
|
||||
private readonly int[] _handledIOTs = { 0x11, 0x12, 0x13 };
|
||||
|
||||
private bool _keyReady;
|
||||
private ushort _keyCode;
|
||||
|
||||
private ImlacSystem _system;
|
||||
|
||||
@ -247,6 +238,7 @@ 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);
|
||||
@ -255,6 +247,7 @@ 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);
|
||||
|
||||
@ -36,6 +36,10 @@ namespace imlac.IO
|
||||
_dataSendReady = true;
|
||||
_dataReady = false;
|
||||
_clocks = 0;
|
||||
_dataBufferFull = false;
|
||||
_dataSentLatch = false;
|
||||
_rxData = 0;
|
||||
_txData = 0;
|
||||
|
||||
if (_dataChannel != null)
|
||||
{
|
||||
@ -50,6 +54,11 @@ namespace imlac.IO
|
||||
throw new ArgumentNullException("channel");
|
||||
}
|
||||
|
||||
if (_dataChannel != null)
|
||||
{
|
||||
_dataChannel.Close();
|
||||
}
|
||||
|
||||
_dataChannel = channel;
|
||||
}
|
||||
|
||||
@ -61,27 +70,22 @@ namespace imlac.IO
|
||||
{
|
||||
_clocks = 0;
|
||||
|
||||
if (_dataChannel.DataAvailable)
|
||||
if (_dataChannel.DataAvailable && !_dataReady)
|
||||
{
|
||||
_dataReady = true;
|
||||
_data = _dataChannel.Read();
|
||||
_rxData = _dataChannel.Read();
|
||||
Trace.Log(LogType.TTY, "i");
|
||||
}
|
||||
else
|
||||
|
||||
// Are we waiting to send something?
|
||||
if (_dataBufferFull && _dataChannel.OutputReady)
|
||||
{
|
||||
_dataReady = false;
|
||||
_dataChannel.Write(_txData);
|
||||
Trace.Log(LogType.TTY, "o {0}", Helpers.ToOctal(_txData));
|
||||
_dataBufferFull = false;
|
||||
_dataSentLatch = true;
|
||||
_dataSendReady = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Are we waiting to send something?
|
||||
if (!_dataSendReady && _dataChannel.OutputReady)
|
||||
{
|
||||
_dataChannel.Write(_data);
|
||||
Trace.Log(LogType.TTY, "o");
|
||||
|
||||
// Sent, reset flag.
|
||||
_dataSendReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +99,16 @@ namespace imlac.IO
|
||||
get { return _dataSendReady; }
|
||||
}
|
||||
|
||||
public bool DataSentLatch
|
||||
{
|
||||
get
|
||||
{
|
||||
bool latch = _dataSentLatch;
|
||||
_dataSentLatch = false;
|
||||
return latch;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetHandledIOTs()
|
||||
{
|
||||
return _handledIOTs;
|
||||
@ -105,8 +119,8 @@ namespace imlac.IO
|
||||
switch (iotCode)
|
||||
{
|
||||
case 0x19: // RRB - TTY read
|
||||
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_data));
|
||||
_system.Processor.AC |= _data;
|
||||
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_rxData));
|
||||
_system.Processor.AC |= _rxData;
|
||||
break;
|
||||
|
||||
case 0x1a: // RCF - Clear TTY status
|
||||
@ -114,30 +128,35 @@ namespace imlac.IO
|
||||
break;
|
||||
|
||||
case 0x1b: // RRC - Read and clear status
|
||||
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_data));
|
||||
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_rxData));
|
||||
_dataReady = false;
|
||||
_system.Processor.AC |= _data;
|
||||
_system.Processor.AC |= _rxData;
|
||||
break;
|
||||
|
||||
case 0x21: // TPR - transmit
|
||||
if (_dataSendReady) // only if transmitter is ready
|
||||
if (_dataSendReady)
|
||||
{
|
||||
_data = (byte)_system.Processor.AC;
|
||||
_txData = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
_dataBufferFull = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x22: // TCF - clear output flag
|
||||
_dataSendReady = true;
|
||||
break;
|
||||
|
||||
case 0x23: // TPC - print, clear flag
|
||||
_data = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
break;
|
||||
|
||||
case 0x23: // TPC - print, clear flag
|
||||
if (_dataSendReady)
|
||||
{
|
||||
_txData = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
_dataBufferFull = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_data));
|
||||
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_rxData));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -146,10 +165,13 @@ namespace imlac.IO
|
||||
|
||||
private bool _dataReady;
|
||||
private bool _dataSendReady;
|
||||
private byte _data;
|
||||
private bool _dataBufferFull;
|
||||
private bool _dataSentLatch;
|
||||
private byte _rxData;
|
||||
private byte _txData;
|
||||
|
||||
private int _clocks;
|
||||
private readonly int _dataClocks = 100;
|
||||
private readonly int _dataClocks = 90; // Appx. 50kbps
|
||||
|
||||
private ISerialDataChannel _dataChannel;
|
||||
|
||||
|
||||
221
imlac/IO/TTYChannels/TelnetDataChannel.cs
Normal file
221
imlac/IO/TTYChannels/TelnetDataChannel.cs
Normal file
@ -0,0 +1,221 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -15,15 +15,39 @@
|
||||
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)
|
||||
{
|
||||
_mem = new ushort[Size];
|
||||
{
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
||||
public ushort Fetch(ushort address)
|
||||
@ -44,15 +68,18 @@ namespace imlac
|
||||
|
||||
public static ushort Size
|
||||
{
|
||||
get { return 8192; }
|
||||
get { return _size; }
|
||||
}
|
||||
|
||||
public static ushort SizeMask
|
||||
{
|
||||
get { return 0x1fff; }
|
||||
get { return _sizeMask; }
|
||||
}
|
||||
|
||||
private static ushort _size;
|
||||
private static ushort _sizeMask;
|
||||
|
||||
private ushort[] _mem;
|
||||
private ImlacSystem _system;
|
||||
}
|
||||
}
|
||||
}
|
||||
816
imlac/PDS1DisplayProcessor.cs
Normal file
816
imlac/PDS1DisplayProcessor.cs
Normal file
@ -0,0 +1,816 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1043
imlac/PDS4DisplayProcessor.cs
Normal file
1043
imlac/PDS4DisplayProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace imlac
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
|
||||
[assembly: AssemblyProduct("sImlac")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
1639
imlac/SDLConsole.cs
1639
imlac/SDLConsole.cs
File diff suppressed because it is too large
Load Diff
216
imlac/System.cs
216
imlac/System.cs
@ -31,6 +31,7 @@ namespace imlac
|
||||
Halted,
|
||||
SingleStep,
|
||||
SingleFrame,
|
||||
SingleDisplayOperation,
|
||||
UntilDisplayStart,
|
||||
Running,
|
||||
Quit
|
||||
@ -39,19 +40,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);
|
||||
_displayProcessor = new DisplayProcessor(this);
|
||||
_interruptFacility = new InterruptFacility(this);
|
||||
|
||||
_processor = new Processor(this);
|
||||
|
||||
// Register IOT devices
|
||||
_processor.RegisterDeviceIOTs(_displayProcessor);
|
||||
AttachDisplayProcessor();
|
||||
|
||||
// Register IOT devices
|
||||
_processor.RegisterDeviceIOTs(_paperTapeReader);
|
||||
_processor.RegisterDeviceIOTs(_tty);
|
||||
_processor.RegisterDeviceIOTs(_keyboard);
|
||||
@ -70,6 +71,16 @@ namespace imlac
|
||||
_processor.Reset();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_display.Shutdown();
|
||||
}
|
||||
|
||||
public void AttachConsole(IImlacConsole console)
|
||||
{
|
||||
_display = console;
|
||||
}
|
||||
|
||||
public Memory Memory
|
||||
{
|
||||
get { return _memory; }
|
||||
@ -80,7 +91,7 @@ namespace imlac
|
||||
get { return _processor; }
|
||||
}
|
||||
|
||||
public DisplayProcessor DisplayProcessor
|
||||
public DisplayProcessorBase DisplayProcessor
|
||||
{
|
||||
get { return _displayProcessor; }
|
||||
}
|
||||
@ -124,8 +135,12 @@ namespace imlac
|
||||
_keyboard.Clock();
|
||||
_clock.Clock();
|
||||
|
||||
// interrupts last so that devices that raise interrupts get clocked first
|
||||
_interruptFacility.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();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -161,11 +176,10 @@ namespace imlac
|
||||
return SystemExecutionState.Running;
|
||||
}
|
||||
|
||||
[DebuggerFunction("step", "Executes a single instruction cycle at the specified address", "<address>")]
|
||||
private SystemExecutionState StepProcessor(ushort address)
|
||||
[DebuggerFunction("set pc", "Sets the Processor's PC to the specified address", "<address>")]
|
||||
private SystemExecutionState SetPC(ushort address)
|
||||
{
|
||||
Processor.PC = address;
|
||||
Processor.State = ProcessorState.Running;
|
||||
return SystemExecutionState.SingleStep;
|
||||
}
|
||||
|
||||
@ -190,13 +204,29 @@ namespace imlac
|
||||
return SystemExecutionState.UntilDisplayStart;
|
||||
}
|
||||
|
||||
[DebuggerFunction("set bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
|
||||
private SystemExecutionState SetBootstrap(string bootstrap)
|
||||
[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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -239,13 +269,36 @@ namespace imlac
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("display memory", "Displays memory contents", "<start> <length>")]
|
||||
[DebuggerFunction("show 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)
|
||||
{
|
||||
@ -281,6 +334,20 @@ 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()
|
||||
{
|
||||
@ -448,23 +515,114 @@ 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}",
|
||||
Console.WriteLine("PC={0} AC={1} MB={2} - {3}\n{4}",
|
||||
Helpers.ToOctal(Processor.PC),
|
||||
Helpers.ToOctal(Processor.AC),
|
||||
Helpers.ToOctal(Memory.Fetch(Processor.PC)),
|
||||
Processor.State);
|
||||
Processor.State,
|
||||
Processor.Disassemble(Processor.PC));
|
||||
|
||||
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6} - {7}",
|
||||
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6}",
|
||||
Helpers.ToOctal(DisplayProcessor.PC),
|
||||
Helpers.ToOctal(DisplayProcessor.DT),
|
||||
Helpers.ToOctal(DisplayProcessor.DPCEntry),
|
||||
@ -546,28 +704,35 @@ namespace imlac
|
||||
}
|
||||
|
||||
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
|
||||
|
||||
for (ushort address = startAddress; address <= endAddress; address++)
|
||||
ushort address = startAddress;
|
||||
while (address < endAddress)
|
||||
{
|
||||
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);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayProcessor:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayIncrement:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayCompact:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.CompactAddressing, out size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -575,9 +740,12 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,7 +794,7 @@ namespace imlac
|
||||
}
|
||||
|
||||
private Processor _processor;
|
||||
private DisplayProcessor _displayProcessor;
|
||||
private DisplayProcessorBase _displayProcessor;
|
||||
private IImlacConsole _display;
|
||||
private Memory _memory;
|
||||
private PaperTapeReader _paperTapeReader;
|
||||
|
||||
@ -36,6 +36,7 @@ namespace imlac
|
||||
Interrupt = 0x10,
|
||||
TTY = 0x20,
|
||||
PTR = 0x40,
|
||||
Telnet = 0x80,
|
||||
All = 0x7fffffff
|
||||
}
|
||||
|
||||
@ -97,6 +98,10 @@ namespace imlac
|
||||
case LogType.Interrupt:
|
||||
color = ConsoleColor.Blue;
|
||||
break;
|
||||
|
||||
case LogType.Telnet:
|
||||
color = ConsoleColor.Cyan;
|
||||
break;
|
||||
|
||||
default:
|
||||
// No change
|
||||
|
||||
81
imlac/app.manifest
Normal file
81
imlac/app.manifest
Normal file
@ -0,0 +1,81 @@
|
||||
<?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>
|
||||
@ -34,10 +34,12 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="SdlDotNet, Version=6.1.0.0, Culture=neutral, PublicKeyToken=26ad4f7e10c61408, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\Windows\assembly\GAC_MSIL\SdlDotNet\6.1.0.0__26ad4f7e10c61408\SdlDotNet.dll</HintPath>
|
||||
<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>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@ -47,15 +49,19 @@
|
||||
<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" />
|
||||
@ -86,6 +92,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="app.manifest" />
|
||||
<None Include="boot\mtty">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
@ -98,6 +105,7 @@
|
||||
<None Include="boot\tty">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
sImlac v0.1 README - (c) 2016, 2017 Living Computers: Museum+Labs
|
||||
sImlac v0.3 README - (c) 2016-2020 Living Computers: Museum+Labs
|
||||
-----------------------------------------------------------------
|
||||
|
||||
1. Overview
|
||||
-----------
|
||||
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
|
||||
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
|
||||
display.
|
||||
|
||||
sImlac currently emulates the current hardware:
|
||||
|
||||
- Standard Imlac Processor / Display Processor (with 1.8us cycle timings)
|
||||
- 8KW of core memory
|
||||
- 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
|
||||
- Vector display (with long-persistence phosphor)
|
||||
- PTR and TTY interfaces (using physical serial ports or files as inputs)
|
||||
- PTR and TTY interfaces (using physical serial ports, telnet hosts, or files as inputs)
|
||||
- Keyboard
|
||||
- Interrupt facility
|
||||
- Long Vector (LVH-1 option) instruction support
|
||||
@ -23,12 +24,30 @@ 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.1, there are still likely to be bugs.
|
||||
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.
|
||||
|
||||
Questions, comments, or bug reports can be directed at
|
||||
joshd@livingcomputers.org.
|
||||
|
||||
2. Getting Started
|
||||
2. System Requirements
|
||||
----------------------
|
||||
|
||||
sImlac is a .NET application and should run on will run on any Windows PC running
|
||||
Windows Vista or later, with version 4.5.3 or later of the .NET Framework installed.
|
||||
.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
|
||||
(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
|
||||
included in the distribution package. On Linux, use your system's package manager
|
||||
to install SDL 2.0; on OS X the easiest way to get SDL 2.0 is to use Homebrew
|
||||
(https://brew.sh/), via the "brew install sdl2" command.
|
||||
|
||||
|
||||
3. Getting Started
|
||||
------------------
|
||||
|
||||
Bootstrapping an Imlac is a pretty straightforward process:
|
||||
@ -40,20 +59,18 @@ 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 using the "set bootstrap" command. Let's say we want
|
||||
A loader can be loaded and run using the "boot" 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 command:
|
||||
TTY) loader, so we issue the following commands:
|
||||
|
||||
> set bootstrap stty
|
||||
|
||||
Now we need to attach the image to the TTY port, this is done by:
|
||||
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. By default, the PC is set
|
||||
to 40(8), the start of the boot loader, so we can just do:
|
||||
Once this is done, we can start the CPU running the bootstrap, which will
|
||||
load the "war" program into memory and start it running:
|
||||
|
||||
> go
|
||||
> boot stty
|
||||
|
||||
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.
|
||||
@ -70,10 +87,10 @@ halt if bit 0 is not set.
|
||||
|
||||
Will usually set you right.
|
||||
|
||||
3. Usage
|
||||
4. Usage
|
||||
--------
|
||||
|
||||
3.1 Command line arguments
|
||||
4.1 Command line arguments
|
||||
--------------------------
|
||||
|
||||
sImlac accepts one optional command line argument, which specifies a file to
|
||||
@ -85,7 +102,7 @@ the contents of "otherscript.txt" to be loaded and executed as a script.)
|
||||
Whitespace is ignored.
|
||||
|
||||
|
||||
3.2 The sImlac console/debugger
|
||||
4.2 The sImlac console/debugger
|
||||
-------------------------------
|
||||
|
||||
After startup, you will be at the sImlac debugger prompt (a '>' character).
|
||||
@ -106,7 +123,7 @@ While the simulated Imlac is running (via the 'go' or other commands) the
|
||||
console is inactive; press Ctrl-C to stop the Imlac and return to the command
|
||||
prompt.
|
||||
|
||||
3.3 The sImlac display
|
||||
4.3 The sImlac display
|
||||
----------------------
|
||||
|
||||
sImlac creates a window that simulates the Imlac's vector display and allows
|
||||
@ -114,12 +131,15 @@ keyboard input to the Imlac. When Imlac programs are running, their output
|
||||
will be displayed here. By default, the display is shown in a window, pressing
|
||||
the "Insert" key will toggle between window and fullscreen modes.
|
||||
|
||||
3.4 Commands
|
||||
4.4 Commands
|
||||
------------
|
||||
|
||||
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.
|
||||
|
||||
set bootstrap <boot>: Loads the specified bootstrap into memory at 40(8).
|
||||
boot <boot> : Loads the specified bootstrap into memory at 40(8) and
|
||||
executes it.
|
||||
Options are PTR (paper tape), TTY (standard TTY), and
|
||||
STTY (alternate TTY).
|
||||
|
||||
@ -132,9 +152,17 @@ 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.
|
||||
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.
|
||||
|
||||
go <addr> : Starts the system running at the current PC, or <addr> if
|
||||
specified.
|
||||
@ -148,6 +176,16 @@ 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
|
||||
@ -237,7 +275,7 @@ show breakpoints : Lists the defined breakpoints.
|
||||
show commands : Displays a synopsis of available console commands.
|
||||
|
||||
|
||||
3.5 Data Switch Mappings
|
||||
4.5 Data Switch Mappings
|
||||
------------------------
|
||||
|
||||
Some Imlac software (mostly games) use the Data Switches on the Imlac's front
|
||||
@ -309,3 +347,19 @@ Thanks go out to:
|
||||
on it!
|
||||
|
||||
- Bitsavers.org for making documentation for the Imlac available.
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
4
imlac/packages.config
Normal file
4
imlac/packages.config
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="SDL2-CS.dll" version="2.0.0.0" targetFramework="net40" />
|
||||
</packages>
|
||||
Loading…
x
Reference in New Issue
Block a user