1
0
mirror of https://github.com/livingcomputermuseum/sImlac.git synced 2026-01-11 23:53:24 +00:00

Initial commit.

This commit is contained in:
Josh Dersch 2017-05-30 11:01:26 -07:00
commit 0e9b1e87a7
43 changed files with 8164 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

192
.gitignore vendored Normal file
View File

@ -0,0 +1,192 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.vsp
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
# NuGet Packages Directory
packages/*
## TODO: If the tool you use requires repositories.config
## uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since
# NuGet packages use it for MSBuild targets.
# This line needs to be after the ignore of the build folder
# (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
/sImlac.VC.VC.opendb
/sImlac.VC.db

24
imlac.sln Normal file
View File

@ -0,0 +1,24 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "imlac", "imlac\imlac.csproj", "{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15852500-EF77-4CF6-92A6-00351E33DA93}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}.Debug|x86.ActiveCfg = Debug|x86
{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}.Debug|x86.Build.0 = Debug|x86
{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}.Release|x86.ActiveCfg = Release|x86
{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,117 @@
/*
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;
namespace imlac.Debugger
{
[Flags]
public enum BreakpointType
{
None = 0,
Execution = 1,
Read = 2,
Write = 4,
Display = 8,
}
public struct BreakpointEntry
{
public BreakpointEntry(BreakpointType type, ushort address)
{
Type = type;
Address = address;
}
public BreakpointType Type;
public ushort Address;
}
public static class BreakpointManager
{
static BreakpointManager()
{
// Allocate enough breakpoint entries for a fully-stocked
// 32KW machine. We use a flat array to make lookup as
// cheap as possible.
_breakPoints = new BreakpointType[0x8000];
_enableBreakpoints = false;
}
public static bool BreakpointsEnabled
{
get { return _enableBreakpoints; }
set { _enableBreakpoints = value; }
}
public static void SetBreakpoint(BreakpointEntry entry)
{
if (entry.Type == BreakpointType.None)
{
_breakPoints[entry.Address & Memory.SizeMask] = BreakpointType.None;
}
else
{
_breakPoints[entry.Address & Memory.SizeMask] |= entry.Type;
}
}
public static BreakpointType GetBreakpoint(ushort address)
{
return _breakPoints[address & Memory.SizeMask];
}
public static bool TestBreakpoint(BreakpointEntry entry)
{
if (!_enableBreakpoints)
{
return false;
}
return (_breakPoints[entry.Address & Memory.SizeMask] & entry.Type) != 0;
}
public static bool TestBreakpoint(BreakpointType type, ushort address)
{
if (!_enableBreakpoints)
{
return false;
}
return (_breakPoints[address & Memory.SizeMask] & type) != 0;
}
public static List<BreakpointEntry> EnumerateBreakpoints()
{
List<BreakpointEntry> breakpoints = new List<BreakpointEntry>();
for(ushort i=0;i<_breakPoints.Length;i++)
{
if (_breakPoints[i] != BreakpointType.None)
{
breakpoints.Add(new BreakpointEntry(_breakPoints[i], i));
}
}
return breakpoints;
}
private static BreakpointType[] _breakPoints;
private static bool _enableBreakpoints;
}
}

687
imlac/Debugger/Console.cs Normal file
View File

@ -0,0 +1,687 @@
/*
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 System.Reflection;
using System.Text;
using System.IO;
namespace imlac.Debugger
{
/// <summary>
/// Defines a node in the debug command tree.
/// </summary>
public class DebuggerCommand
{
public DebuggerCommand(object instance, string name, String description, String usage, MethodInfo method)
{
Instance = instance;
Name = name.Trim().ToLower();
Description = description;
Usage = usage;
Methods = new List<MethodInfo>(4);
if (method != null)
{
Methods.Add(method);
}
SubCommands = new List<DebuggerCommand>();
}
public object Instance;
public string Name;
public string Description;
public string Usage;
public List<MethodInfo> Methods;
public List<DebuggerCommand> SubCommands;
public override string ToString()
{
if (this.Methods.Count == 0)
{
return String.Format("{0}... ({1})", this.Name, this.SubCommands.Count);
}
else
{
return this.Name;
}
}
public void AddSubNode(List<string> words, MethodInfo method, object instance)
{
// We should never hit this case.
if (words.Count == 0)
{
throw new InvalidOperationException("Out of words building command node.");
}
// Check the root to see if a node for the first incoming word has already been added
DebuggerCommand subNode = FindSubNodeByName(words[0]);
if (subNode == null)
{
// No, it has not -- create one and add it now.
subNode = new DebuggerCommand(instance, words[0], null, null, null);
this.SubCommands.Add(subNode);
if (words.Count == 1)
{
// This is the last stop -- set the method and be done with it now.
subNode.Methods.Add(method);
// early return.
return;
}
}
else
{
// The node already exists, we will be adding a subnode, hopefully.
if (words.Count == 1)
{
//
// 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;
foreach (MethodInfo info in subNode.Methods)
{
if (info.GetParameters().Length == argCount)
{
throw new InvalidOperationException("Duplicate overload for console command");
}
}
//
// We're ok. Add it to the method list.
//
subNode.Methods.Add(method);
// and return early.
return;
}
}
// We have more words to go.
words.RemoveAt(0);
subNode.AddSubNode(words, method, instance);
}
public DebuggerCommand FindSubNodeByName(string name)
{
DebuggerCommand found = null;
foreach (DebuggerCommand sub in SubCommands)
{
if (sub.Name == name)
{
found = sub;
break;
}
}
return found;
}
}
public class ConsoleExecutor
{
public ConsoleExecutor(params object[] commandObjects)
{
List<object> commandList = new List<object>(commandObjects);
BuildCommandTree(commandList);
_consolePrompt = new DebuggerPrompt(_commandRoot);
}
public SystemExecutionState Prompt(ImlacSystem system)
{
SystemExecutionState next = SystemExecutionState.Debugging;
try
{
system.PrintProcessorStatus();
// Get the command string from the prompt.
string command = _consolePrompt.Prompt().Trim();
if (command != String.Empty)
{
next = ExecuteLine(command, system);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return next;
}
public SystemExecutionState ExecuteScript(ImlacSystem system, string scriptFile)
{
SystemExecutionState state = SystemExecutionState.Halted;
using (StreamReader sr = new StreamReader(scriptFile))
{
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
{
Console.WriteLine(line);
state = ExecuteLine(line, system);
}
}
}
return state;
}
private SystemExecutionState ExecuteLine(string line, ImlacSystem system)
{
SystemExecutionState next = SystemExecutionState.Debugging;
if (line.StartsWith("#"))
{
// Comments start with "#", just ignore them
}
else if(line.StartsWith("@"))
{
// A line beginning with an "@" indicates a script to execute.
string scriptFile = line.Substring(1);
next = ExecuteScript(system, scriptFile);
}
else
{
string[] args = null;
DebuggerCommand command = GetDebuggerCommandFromCommandString(line, out args);
if (command == null)
{
// Not a command.
Console.WriteLine("Invalid command.");
}
else
{
next = InvokeConsoleMethod(command, args, system);
}
}
return next;
}
private SystemExecutionState InvokeConsoleMethod(DebuggerCommand command, string[] args, ImlacSystem system)
{
MethodInfo method = null;
//
// Find the method that matches the arg count we were passed
// (i.e. handle overloaded commands).
// That this only matches on argument count is somewhat of a kluge...
//
foreach (MethodInfo m in command.Methods)
{
ParameterInfo[] paramInfo = m.GetParameters();
if (args == null && paramInfo.Length == 0 ||
paramInfo.Length == args.Length)
{
// found a match
method = m;
break;
}
}
if (method == null)
{
// invalid argument count.
throw new ArgumentException(String.Format("Invalid argument count to command."));
}
ParameterInfo[] parameterInfo = method.GetParameters();
object[] invokeParams;
if (args == null)
{
invokeParams = null;
}
else
{
invokeParams = new object[parameterInfo.Length];
}
int argIndex = 0;
for (int paramIndex = 0; paramIndex < parameterInfo.Length; paramIndex++)
{
ParameterInfo p = parameterInfo[paramIndex];
if (p.ParameterType.IsEnum)
{
//
// This is an enumeration type.
// See if we can find an enumerant that matches the argument.
//
FieldInfo[] fields = p.ParameterType.GetFields();
foreach (FieldInfo f in fields)
{
if (!f.IsSpecialName && args[argIndex].ToLower() == f.Name.ToLower())
{
invokeParams[paramIndex] = f.GetRawConstantValue();
break;
}
}
if (invokeParams[paramIndex] == null)
{
// no match, provide possible values
StringBuilder sb = new StringBuilder(String.Format("Invalid value for parameter {0}. Possible values are:", paramIndex));
foreach (FieldInfo f in fields)
{
if (!f.IsSpecialName)
{
sb.AppendFormat("{0} ", f.Name);
}
}
sb.AppendLine();
throw new ArgumentException(sb.ToString());
}
argIndex++;
}
else if (p.ParameterType.IsArray)
{
//
// If a function takes an array type, i should do something here, yeah.
//
argIndex++;
}
else
{
if (p.ParameterType == typeof(bool))
{
invokeParams[paramIndex] = bool.Parse(args[argIndex++]);
}
else if (p.ParameterType == typeof(uint))
{
invokeParams[paramIndex] = TryParseUint(args[argIndex++]);
}
else if (p.ParameterType == typeof(ushort))
{
invokeParams[paramIndex] = TryParseUshort(args[argIndex++]);
}
else if (p.ParameterType == typeof(string))
{
invokeParams[paramIndex] = args[argIndex++];
}
else if (p.ParameterType == typeof(char))
{
invokeParams[paramIndex] = (char)args[argIndex++][0];
}
else if (p.ParameterType == typeof(float))
{
invokeParams[paramIndex] = float.Parse(args[argIndex++]);
}
else
{
throw new ArgumentException(String.Format("Unhandled type for parameter {0}, type {1}", paramIndex, p.ParameterType));
}
}
}
//
// If we've made it THIS far, then we were able to parse all the commands into what they should be.
// Invoke the method on the object instance associated with the command.
//
return (SystemExecutionState)method.Invoke(command.Instance, invokeParams);
}
private object GetInstanceFromMethod(MethodInfo method)
{
Type instanceType = method.DeclaringType;
PropertyInfo property = instanceType.GetProperty("Instance");
return property.GetValue(null, null);
}
enum ParseState
{
NonWhiteSpace = 0,
WhiteSpace = 1,
QuotedString = 2,
}
private List<string> SplitArgs(string commandString)
{
// We split on whitespace and specially handle quoted strings (quoted strings count as a single arg)
//
List<string> args = new List<string>();
commandString = commandString.Trim();
StringBuilder sb = new StringBuilder();
ParseState state = ParseState.NonWhiteSpace;
foreach(char c in commandString)
{
switch (state)
{
case ParseState.NonWhiteSpace:
if (char.IsWhiteSpace(c))
{
// End of token
args.Add(sb.ToString());
sb.Clear();
state = ParseState.WhiteSpace;
}
else if (c == '\"')
{
// Start of quoted string
state = ParseState.QuotedString;
}
else
{
// Character in token
sb.Append(c);
}
break;
case ParseState.WhiteSpace:
if (!char.IsWhiteSpace(c))
{
// Start of new token
if (c != '\"')
{
sb.Append(c);
state = ParseState.NonWhiteSpace;
}
else
{
// Start of quoted string
state = ParseState.QuotedString;
}
}
break;
case ParseState.QuotedString:
if (c == '\"')
{
// End of quoted string.
args.Add(sb.ToString());
sb.Clear();
state = ParseState.WhiteSpace;
}
else
{
// Character in quoted string
sb.Append(c);
}
break;
}
}
if (sb.Length > 0)
{
// Add the last token to the args list
args.Add(sb.ToString());
}
return args;
}
private DebuggerCommand GetDebuggerCommandFromCommandString(string command, out string[] args)
{
args = null;
List<string> cmdArgs = SplitArgs(command);
DebuggerCommand current = _commandRoot;
int commandIndex = 0;
while (true)
{
// If this node has an executor and no subnodes, or if this node has an executor
// and there are no further arguments, then we're done.
if ((current.Methods.Count > 0 && current.SubCommands.Count == 0) ||
(current.Methods.Count > 0 && commandIndex > cmdArgs.Count -1))
{
break;
}
if (commandIndex > cmdArgs.Count - 1)
{
// Out of args with no match.
return null;
}
// Otherwise we continue down the tree.
DebuggerCommand next = current.FindSubNodeByName(cmdArgs[commandIndex]);
commandIndex++;
if (next == null)
{
//
// If a matching subcommand was not found, then if we had a previous node with an
// executor, use that; otherwise the command is invalid.
//
if (current.Methods.Count > 0)
{
break;
}
else
{
return null;
}
}
current = next;
}
// Now current should point to the command with the executor
// and commandIndex should point to the first argument to the command.
cmdArgs.RemoveRange(0, commandIndex);
args = cmdArgs.ToArray();
return current;
}
private enum Radix
{
Binary = 2,
Octal = 8,
Decimal = 10,
Hexadecimal = 16,
}
private static uint TryParseUint(string arg)
{
uint result = 0;
Radix radix = Radix.Decimal;
switch (arg[0])
{
case 'b':
radix = Radix.Binary;
arg = arg.Remove(0, 1);
break;
case 'o':
radix = Radix.Octal;
arg = arg.Remove(0, 1);
break;
case 'd':
radix = Radix.Decimal;
arg = arg.Remove(0, 1);
break;
case 'x':
radix = Radix.Hexadecimal;
arg = arg.Remove(0, 1);
break;
default:
radix = Radix.Octal;
break;
}
try
{
result = Convert.ToUInt32(arg, (int)radix);
}
catch
{
Console.WriteLine("{0} is not a valid 32-bit value.", arg);
throw;
}
return result;
}
private static ushort TryParseUshort(string arg)
{
ushort result = 0;
Radix radix = Radix.Decimal;
switch (arg[0])
{
case 'b':
radix = Radix.Binary;
arg = arg.Remove(0, 1);
break;
case 'o':
radix = Radix.Octal;
arg = arg.Remove(0, 1);
break;
case 'd':
radix = Radix.Decimal;
arg = arg.Remove(0, 1);
break;
case 'x':
radix = Radix.Hexadecimal;
arg = arg.Remove(0, 1);
break;
default:
radix = Radix.Octal;
break;
}
try
{
result = Convert.ToUInt16(arg, (int)radix);
}
catch
{
Console.WriteLine("{0} is not a valid 16-bit value.", arg);
throw;
}
return result;
}
/// <summary>
/// Builds the debugger command tree.
/// </summary>
private void BuildCommandTree(List<object> commandObjects)
{
// Build the flat list which will be built into the tree, by walking
// the classes that provide the methods
_commandList = new List<DebuggerCommand>();
// Add ourself to the list
commandObjects.Add(this);
foreach (object commandObject in commandObjects)
{
Type type = commandObject.GetType();
foreach (MethodInfo info in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
object[] attribs = info.GetCustomAttributes(typeof(DebuggerFunction), true);
if (attribs.Length > 1)
{
throw new InvalidOperationException(String.Format("More than one ConsoleFunction attribute set on {0}", info.Name));
}
else if (attribs.Length == 1)
{
// we have a debugger attribute set on this method
// this cast should always succeed given that we're filtering for this type above.
DebuggerFunction function = (DebuggerFunction)attribs[0];
DebuggerCommand newCommand = new DebuggerCommand(commandObject, function.CommandName, function.Description, function.Usage, info);
_commandList.Add(newCommand);
}
}
}
// Now actually build the command tree from the above list!
_commandRoot = new DebuggerCommand(null, "Root", null, null, null);
foreach (DebuggerCommand c in _commandList)
{
string[] commandWords = c.Name.Split(' ');
// This is kind of ugly, we know that at this point every command built above have only
// one method. When building the tree, overloaded commands may end up with more than one.
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0], c.Instance);
}
}
[DebuggerFunction("show commands", "Shows debugger commands and their descriptions.")]
private SystemExecutionState ShowCommands()
{
foreach (DebuggerCommand cmd in _commandList)
{
if (!string.IsNullOrEmpty(cmd.Usage))
{
Console.WriteLine("{0} {2} - {1}", cmd.Name, cmd.Description, cmd.Usage);
}
else
{
Console.WriteLine("{0} - {1}", cmd.Name, cmd.Description);
}
}
return SystemExecutionState.Debugging;
}
[DebuggerFunction("quit", "Terminates the emulator process")]
private SystemExecutionState Quit()
{
return SystemExecutionState.Quit;
}
private DebuggerPrompt _consolePrompt;
private DebuggerCommand _commandRoot;
private List<DebuggerCommand> _commandList;
}
}

View File

@ -0,0 +1,63 @@
/*
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;
namespace imlac.Debugger
{
public class DebuggerFunction : Attribute
{
public DebuggerFunction(string commandName)
{
_commandName = commandName;
_usage = "<No help available>";
}
public DebuggerFunction(string commandName, string description)
{
_commandName = commandName;
_description = description;
}
public DebuggerFunction(string commandName, string description, string usage)
{
_commandName = commandName;
_description = description;
_usage = usage;
}
public string CommandName
{
get { return _commandName; }
}
public string Usage
{
get { return _usage; }
}
public string Description
{
get { return _description; }
}
private string _commandName;
private string _description;
private string _usage;
}
}

View File

@ -0,0 +1,496 @@
/*
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 System.Text;
namespace imlac.Debugger
{
public class DebuggerPrompt
{
public DebuggerPrompt(DebuggerCommand root)
{
_commandTree = root;
_commandHistory = new List<string>(64);
_historyIndex = 0;
}
/// <summary>
/// Runs a nifty interactive debugger prompt.
/// </summary>
public string Prompt()
{
DisplayPrompt();
ClearInput();
UpdateOrigin();
bool entryDone = false;
while (!entryDone)
{
UpdateDisplay();
// Read one keystroke from the console...
ConsoleKeyInfo key = Console.ReadKey(true);
//Parse special chars...
switch (key.Key)
{
case ConsoleKey.Escape: //Clear input
ClearInput();
break;
case ConsoleKey.Backspace: // Delete last char
DeleteCharAtCursor(true /* backspace */);
break;
case ConsoleKey.Delete: //Delete character at cursor
DeleteCharAtCursor(false /* delete */);
break;
case ConsoleKey.LeftArrow:
MoveLeft();
break;
case ConsoleKey.RightArrow:
MoveRight();
break;
case ConsoleKey.UpArrow:
HistoryPrev();
break;
case ConsoleKey.DownArrow:
HistoryNext();
break;
case ConsoleKey.Home:
MoveToBeginning();
break;
case ConsoleKey.End:
MoveToEnd();
break;
case ConsoleKey.Tab:
DoCompletion(false /* silent */);
break;
case ConsoleKey.Enter:
DoCompletion(true /* silent */);
UpdateDisplay();
CRLF();
entryDone = true;
break;
case ConsoleKey.Spacebar:
if (!_input.EndsWith(" ") &&
DoCompletion(true /* silent */))
{
UpdateDisplay();
}
else
{
InsertChar(key.KeyChar);
}
break;
default:
// Not a special key, just insert it if it's deemed printable.
if (char.IsLetterOrDigit(key.KeyChar) ||
char.IsPunctuation(key.KeyChar) ||
char.IsSymbol(key.KeyChar) ||
char.IsWhiteSpace(key.KeyChar))
{
InsertChar(key.KeyChar);
}
break;
}
}
// Done. Add to history if input is non-empty
if (_input != string.Empty)
{
_commandHistory.Add(_input);
HistoryIndex = _commandHistory.Count - 1;
}
return _input;
}
private void DeleteCharAtCursor(bool backspace)
{
if (_input.Length == 0)
{
//nothing to delete, bail.
return;
}
if (backspace)
{
if (TextPosition == 0)
{
// We's at the beginning of the input,
// can't backspace from here.
return;
}
else
{
// remove 1 char at the position before the cursor.
// and move the cursor back one char
_input = _input.Remove(TextPosition - 1, 1);
TextPosition--;
}
}
else
{
if (TextPosition == _input.Length)
{
// At the end of input, can't delete a char from here
return;
}
else
{
// remove one char at the current cursor pos.
// do not move the cursor
_input = _input.Remove(TextPosition, 1);
}
}
}
private void DisplayPrompt()
{
Console.Write(">");
}
private void UpdateDisplay()
{
//if the current input string is shorter than the last, then we need to erase a few chars at the end.
string clear = String.Empty;
if (_input.Length < _lastInputLength)
{
StringBuilder sb = new StringBuilder(_lastInputLength - _input.Length);
for (int i = 0; i < _lastInputLength - _input.Length; i++)
{
sb.Append(' ');
}
clear = sb.ToString();
}
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
// Move cursor to origin to draw string
Console.CursorLeft = _originColumn;
Console.CursorTop = _originRow;
Console.Write(_input + clear);
// Move cursor to text position to draw cursor
Console.CursorLeft = column;
Console.CursorTop = row;
Console.CursorVisible = true;
_lastInputLength = _input.Length;
}
private void MoveLeft()
{
TextPosition--;
}
private void MoveRight()
{
TextPosition++;
}
private void HistoryPrev()
{
if (HistoryIndex < _commandHistory.Count)
{
_input = _commandHistory[HistoryIndex];
TextPosition = _input.Length;
HistoryIndex--;
}
}
private void HistoryNext()
{
if (HistoryIndex < _commandHistory.Count)
{
HistoryIndex++;
_input = _commandHistory[HistoryIndex];
TextPosition = _input.Length;
}
else
{
_input = String.Empty;
}
}
private void MoveToBeginning()
{
TextPosition = 0;
}
private void MoveToEnd()
{
TextPosition = _input.Length;
}
private void ClearInput()
{
_input = String.Empty;
HistoryIndex = _commandHistory.Count - 1;
TextPosition = 0;
}
private void InsertChar(char c)
{
_input = _input.Insert(TextPosition, c.ToString());
TextPosition++;
}
private void CRLF()
{
Console.WriteLine();
}
private bool DoCompletion(bool silent)
{
// This code should probably move to another class, but hey, I'm lazy.
// Take the current input and see if it matches anything in the command tree
string[] tokens = _input.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// Save off the current cursor row; this is an ugly-ish hack to detect whether the match process
// output anything. If the cursor row changes then we'll need to move the prompt
int oldRow = Console.CursorTop;
string matchString = FuzzyMatch(_commandTree, new List<string>(tokens), silent);
bool changed = false;
if (matchString != String.Empty)
{
changed = _input.Trim().ToLower() != matchString.Trim().ToLower();
_input = matchString;
TextPosition = _input.Length;
}
if (!silent && oldRow != Console.CursorTop)
{
DisplayPrompt();
UpdateOrigin();
}
return changed;
}
private string FuzzyMatch(DebuggerCommand root, List<string> tokens, bool silent)
{
if (tokens.Count == 0)
{
if (!silent)
{
// If there are no tokens, just show the completion for the root.
PrintCompletions(root.SubCommands);
}
return String.Empty;
}
DebuggerCommand match = null;
// Search for exact matches. If we find one it's guaranteed to be unique
// so we can follow that node.
foreach (DebuggerCommand c in root.SubCommands)
{
if (c.Name.ToLower() == tokens[0].ToLower())
{
match = c;
break;
}
}
if (match == null)
{
// No exact match. Try a substring match.
// If we have an unambiguous match then we can complete it automatically.
// If the match is ambiguous, display possible completions and return String.Empty.
List<DebuggerCommand> completions = new List<DebuggerCommand>();
foreach (DebuggerCommand c in root.SubCommands)
{
if (c.Name.StartsWith(tokens[0], StringComparison.InvariantCultureIgnoreCase))
{
completions.Add(c);
}
}
if (completions.Count == 1)
{
// unambiguous match. use it.
match = completions[0];
}
else if (completions.Count > 1)
{
// ambiguous match. display possible completions.
if (!silent)
{
PrintCompletions(completions);
}
}
}
if (match == null)
{
// If we reach this point then no matches are available. return the tokens we have...
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tokens.Count; i++)
{
if (i < tokens.Count - 1)
{
sb.AppendFormat("{0} ", tokens[i]);
}
else
{
sb.AppendFormat("{0}", tokens[i]);
}
}
return sb.ToString();
}
else
{
// A match was found
tokens.RemoveAt(0);
string subMatch = String.Empty;
if (tokens.Count > 0)
{
subMatch = FuzzyMatch(match, tokens, silent);
}
else // if (exactMatch)
{
if (!silent && match.SubCommands.Count > 1)
{
// More than one possible completion
// Just show the completions for this node.
PrintCompletions(match.SubCommands);
}
else if(!silent && match.SubCommands.Count == 1)
{
// Just one possible completion; fill it out.
DebuggerCommand next = match.SubCommands[0];
StringBuilder sb =
new StringBuilder(String.Format("{0} {1}", match.Name, next.Name));
while(next.SubCommands.Count > 0)
{
if (next.SubCommands.Count > 1)
{
break;
}
next = next.SubCommands[0];
sb.AppendFormat(" {0}", next.Name);
}
return sb.ToString();
}
}
if (subMatch == String.Empty)
{
return String.Format("{0} ", match.Name);
}
else
{
return String.Format("{0} {1}", match.Name, subMatch);
}
}
}
private void PrintCompletions(List<DebuggerCommand> completions)
{
// Just print all available completions at this node
Console.WriteLine();
Console.WriteLine("Possible completions are:");
foreach (DebuggerCommand c in completions)
{
Console.Write("{0}\t", c);
}
Console.WriteLine();
}
private void UpdateOrigin()
{
_originRow = Console.CursorTop;
_originColumn = Console.CursorLeft;
}
private int TextPosition
{
get
{
return _textPosition;
}
set
{
// Clip input between 0 and the length of input (+1, to allow adding text at end)
_textPosition = Math.Max(0, value);
_textPosition = Math.Min(_textPosition, _input.Length);
}
}
private int HistoryIndex
{
get
{
return _historyIndex;
}
set
{
_historyIndex = Math.Min(_commandHistory.Count - 1, value);
_historyIndex = Math.Max(0, _historyIndex);
}
}
private DebuggerCommand _commandTree;
private int _originRow;
private int _originColumn;
private string _input;
private int _textPosition;
private int _lastInputLength;
private List<string> _commandHistory;
private int _historyIndex;
}
}

992
imlac/DisplayProcessor.cs Normal file
View File

@ -0,0 +1,992 @@
/*
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
{
public enum DisplayProcessorMode
{
Indeterminate,
Processor,
Increment
}
public enum ImmediateHalf
{
First,
Second
}
/// <summary>
/// DisplayProcessor implements the Display processor found in an Imlac PDS-1 with long vector hardware.
/// </summary>
public class DisplayProcessor : IIOTDevice
{
public DisplayProcessor(ImlacSystem system)
{
_system = system;
_mem = _system.Memory;
_instructionCache = new DisplayInstruction[Memory.Size];
_dtStack = new Stack<ushort>(8);
Reset();
}
public void Reset()
{
State = ProcessorState.Halted;
_mode = DisplayProcessorMode.Processor;
_pc = 0;
_block = 0;
_dtStack.Clear();
X = 0;
Y = 0;
_scale = 1.0f;
_sgrModeOn = false;
_sgrBeamOn = false;
_sgrDJRMOn = false;
_system.Display.MoveAbsolute(X, Y, DrawingMode.Off);
_clocks = 0;
_frameLatch = false;
}
public ushort PC
{
get { return _pc; }
set
{
_pc = value;
// block is set whenever DPC is set by the main processor
_block = (ushort)(value & 0x1000);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DPC set to {0} (block {1})", Helpers.ToOctal(_pc), Helpers.ToOctal(_block));
}
}
public ProcessorState State
{
get { return _state; }
set
{
_state = value;
if (_state == ProcessorState.Halted)
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Display processor halted.");
}
else
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Display processor started.");
}
}
}
public DisplayProcessorMode Mode
{
get { return _mode; }
}
public ImmediateHalf Half
{
get { return _immediateHalf; }
}
public ushort DT
{
get
{
if (_dtStack.Count > 0)
{
return _dtStack.Peek();
}
else
{
return 0;
}
}
}
public bool FrameLatch
{
get { return _frameLatch; }
set { _frameLatch = value; }
}
public uint X
{
get { return _x; }
set
{
_x = value & 0x7ff;
}
}
public uint Y
{
get { return _y; }
set
{
_y = value & 0x7ff;
}
}
public ushort DPCEntry
{
get { return _dpcEntry; }
}
public void InvalidateCache(ushort address)
{
_instructionCache[address & Memory.SizeMask] = null;
}
public string Disassemble(ushort address, DisplayProcessorMode mode)
{
//
// 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);
}
else
{
return new DisplayInstruction((ushort)(address & Memory.SizeMask), mode).Disassemble(mode);
}
}
public 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 int[] GetHandledIOTs()
{
return _handledIOTs;
}
public void ExecuteIOT(int iotCode)
{
//
// Dispatch the IOT instruction.
//
switch (iotCode)
{
case 0x03: // load DPC with main processor's AC
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
State = ProcessorState.Halted;
break;
case 0x39: // Clear display 40Hz sync latch
_frameLatch = false;
break;
case 0xc4: // clear halt state
State = ProcessorState.Running;
break;
default:
throw new NotImplementedException(String.Format("Unimplemented Display IOT instruction {0:x4}", iotCode));
}
}
private void ExecuteProcessor()
{
DisplayInstruction 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:
_pc = (ushort)(instruction.Data | _block);
break;
case DisplayOpcode.DJMS:
_dtStack.Push((ushort)(_pc + 1));
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DT stack push {0}, depth is now {1}", Helpers.ToOctal((ushort)(_pc + 1)), _dtStack.Count);
_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.
State = ProcessorState.Halted;
}
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");
_system.Display.MoveAbsolute(X, Y, DrawingMode.Off);
}
if ((instruction.Data & 0x200) != 0)
{
// DIXM -- increment X DAC MSB
X += 0x20;
_system.Display.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;
_system.Display.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;
_system.Display.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;
_system.Display.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.
_system.Display.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:
// nothing
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:
switch (c)
{
case 0:
_block = 0x0000;
break;
case 1:
_block = 0x1000;
break;
default:
throw new NotImplementedException("Unimplemented DSTB call -- code may expect > 8KW of memory.");
}
break;
case 0x3:
// TODO: light pen sensitize
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Light pen, stub!");
break;
}
_pc++;
break;
case DisplayOpcode.DLXA:
X = (uint)(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);
}
_system.Display.MoveAbsolute(X, Y, mode);
if (_sgrDJRMOn)
{
ReturnFromDisplaySubroutine();
}
else
{
_pc++;
}
break;
case DisplayOpcode.DLYA:
Y = (uint)(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;
}
_system.Display.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;
default:
throw new NotImplementedException(String.Format("Unimplemented Display Processor Opcode {0}, operands {1}", Helpers.ToOctal((ushort)instruction.Opcode), Helpers.ToOctal(instruction.Data)));
}
// 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);
// 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)
{
X += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment X MSB, X is now {0}", X);
}
if ((halfWord & 0x08) != 0)
{
X = X & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset X LSB, X is now {0}", X);
}
if ((halfWord & 0x02) != 0)
{
Y += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment Y MSB, Y is now {0}", Y);
}
if ((halfWord & 0x01) != 0)
{
Y = Y & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset Y LSB, Y is now {0}", Y);
}
_system.Display.MoveAbsolute(X, Y, 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), X, Y, xSign * xMag, ySign * yMag, (halfWord & 0x40) != 0);
X = (uint)(X + xSign * xMag * 2);
Y = (uint)(Y + ySign * yMag * 2);
_system.Display.MoveAbsolute(X, Y, (halfWord & 0x40) == 0 ? DrawingMode.Off : DrawingMode.Normal);
MoveToNextHalfWord();
}
// 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).
DisplayInstruction 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.)
X = (uint)(X + (dx * dxSign) * 2 * _scale);
Y = (uint)(Y + (dy * dySign) * 2 * _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector, move complete - x={0} y={1}", X, Y, dx * dxSign, dy * dySign, beamOn, dotted);
_system.Display.MoveAbsolute(X, Y, beamOn ? (dotted ? DrawingMode.Dotted : DrawingMode.Normal) : DrawingMode.Off);
_pc++;
}
private void ReturnFromDisplaySubroutine()
{
if (_dtStack.Count > 0)
{
_pc = _dtStack.Pop();
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DT stack pop {0}, depth is now {1}", Helpers.ToOctal(_pc), _dtStack.Count);
}
else
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DT stack empty on pop! Leaving DPC undisturbed at {0}", Helpers.ToOctal(_pc));
}
}
private DisplayInstruction GetCachedInstruction(ushort address, DisplayProcessorMode mode)
{
if (_instructionCache[address & Memory.SizeMask] == null)
{
_instructionCache[address & Memory.SizeMask] = new DisplayInstruction(_mem.Fetch(address), mode);
}
return _instructionCache[address & Memory.SizeMask];
}
private uint _x;
private uint _y;
private float _scale;
private ushort _pc;
private ushort _block;
private Stack<ushort> _dtStack;
private ushort _dpcEntry;
// SGR-1 mode switches
private bool _sgrModeOn;
private bool _sgrDJRMOn;
private bool _sgrBeamOn;
private ushort _immediateWord;
private ImmediateHalf _immediateHalf;
private int _clocks;
private const int _frameClocks40Hz = 13889; // cycles per 1/40th of a second (rounded up)
private bool _frameLatch;
private ProcessorState _state;
private DisplayProcessorMode _mode;
private ImlacSystem _system;
private Memory _mem;
private DisplayInstruction[] _instructionCache;
private readonly int[] _handledIOTs = { 0x3, 0xa, 0x39, 0xc4 };
private enum DisplayOpcode
{
// Basic instructions
DLXA, // Load X Accumulator
DLYA, // Load Y Accumulator
DEIM, // Enter Immediate Mode
DJMS, // Jump to subroutine
DJMP, // Jump to address
DHLT, // Halt display
DNOP, // No op
DSTS, // Set scale
DSTB, // Set block
DDSP, // Display intensification
DIXM, // Display increment X MSB
DIYM, // Display increment Y MSB
DDXM, // Display decrement X MSB
DDYM, // Display decrement Y MSB
DRJM, // Return jump
DHVC, // Display HV Sync
DLVH, // Long vector
DOPR, // Generic Display OPR microinstruction
// Optional extended instructions
SGR1,
ASG1,
VIC1,
MCI1,
STI1,
LPA1,
}
private class DisplayInstruction
{
public DisplayInstruction(ushort word)
{
_usageMode = DisplayProcessorMode.Indeterminate;
_word = word;
Decode();
}
public DisplayInstruction(ushort word, DisplayProcessorMode mode)
{
_usageMode = mode;
_word = word;
if (mode == DisplayProcessorMode.Processor)
{
Decode();
}
else
{
DecodeImmediate();
}
}
public DisplayOpcode Opcode
{
get { return _opcode; }
}
public ushort Data
{
get { return _data; }
}
/// <summary>
/// Set when the instruction is actually executed by the display processor.
/// Used to aid in disassembly (since it provides the context needed to determine what type of
/// processor instruction it is)
/// </summary>
public DisplayProcessorMode UsageMode
{
get { return _usageMode; }
set { _usageMode = value; }
}
public string Disassemble(DisplayProcessorMode mode)
{
if (mode == DisplayProcessorMode.Indeterminate)
{
mode = _usageMode;
}
switch (mode)
{
case DisplayProcessorMode.Increment:
return DisassembleIncrement();
case DisplayProcessorMode.Processor:
return DisassembleProcessor();
case DisplayProcessorMode.Indeterminate:
return "Indeterminate";
default:
throw new InvalidOperationException();
}
}
private void Decode()
{
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 & 0x0800) != 0)
{
Console.Write("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);
break;
case 0x06:
_opcode = DisplayOpcode.DJMP;
_data = (ushort)(_word & 0xfff);
break;
case 0x07:
DecodeExtendedInstruction(_word);
break;
default:
throw new NotImplementedException(String.Format("Unhandled Display Processor Mode instruction {0}", Helpers.ToOctal(_word)));
}
}
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:
throw new NotImplementedException(String.Format("Unhandled extended Display Processor Mode instruction {0}", Helpers.ToOctal(word)));
}
_data = (ushort)(word & 0x7);
}
private string DisassembleIncrement()
{
return DisassembleIncrementHalf(ImmediateHalf.First) + " | " + DisassembleIncrementHalf(ImmediateHalf.Second);
}
private string DisassembleIncrementHalf(ImmediateHalf half)
{
string ret = string.Empty;
int halfWord = half == ImmediateHalf.First ? (_word & 0xff00) >> 8 : (_word & 0xff);
// translate the half word to vector movements or escapes
// special case for "Enter Immediate mode" halfword (030) in first half.
if (half == ImmediateHalf.First && halfWord == 0x30)
{
ret += "E";
}
else if ((halfWord & 0x80) == 0)
{
if ((halfWord & 0x10) != 0)
{
ret += "IX ";
}
if ((halfWord & 0x08) != 0)
{
ret += "ZX ";
}
if ((halfWord & 0x02) != 0)
{
ret += "IY ";
}
if ((halfWord & 0x01) != 0)
{
ret += "ZY ";
}
if ((halfWord & 0x40) != 0)
{
if ((halfWord & 0x20) != 0)
{
// escape and return
ret += "F RJM";
}
else
{
// Escape
ret += "F";
}
}
}
else
{
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
int xMag = (int)(((halfWord & 0x18) >> 3));
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03));
ret += String.Format("{0},{1} {2}", xMag * xSign, yMag * ySign, (halfWord & 0x40) == 0 ? "OFF" : "ON");
}
return ret;
}
private void DecodeImmediate()
{
// TODO: eventually actually precache movement calculations.
}
private string DisassembleProcessor()
{
string ret = String.Empty;
if (_opcode == DisplayOpcode.DOPR)
{
string[] codes = { "INV0 ", "INV1 ", "INV2 ", "INV3 ", "DDSP ", "DRJM ", "DDYM ", "DDXM ", "DIYM ", "DIXM ", "DHVC ", "DHLT " };
for (int i = 4; i < 12; i++)
{
if ((_data & (0x01) << i) != 0)
{
if (!string.IsNullOrEmpty(ret))
{
ret += ",";
}
ret += codes[i];
}
}
// F/C ops:
int f = (_data & 0xc) >> 2;
int c = _data & 0x3;
switch (f)
{
case 0x0:
// nothing
break;
case 0x1:
ret += String.Format("DSTS {0}", c);
break;
case 0x2:
ret += String.Format("DSTB {0}", c);
break;
case 0x3:
ret += String.Format("DLPN {0}", c);
break;
}
}
else
{
// keep things simple -- should add special support for extended instructions at some point...
ret = String.Format("{0} {1} ", _opcode, Helpers.ToOctal(_data));
}
return ret;
}
private DisplayOpcode _opcode;
private ushort _data;
private DisplayProcessorMode _usageMode;
private ushort _word;
}
}
}

133
imlac/EntryPoint.cs Normal file
View File

@ -0,0 +1,133 @@
/*
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 imlac.Debugger;
using System;
namespace imlac
{
class EntryPoint
{
static void Main(string[] args)
{
ImlacSystem system = new ImlacSystem();
ConsoleExecutor debuggerPrompt =
new ConsoleExecutor(system);
_state = SystemExecutionState.Debugging;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnBreak);
PrintHerald();
if (args.Length > 0)
{
//
// Assume arg 0 is a script file to be executed.
//
Console.WriteLine("Executing startup script '{0}'", args[0]);
try
{
_state = debuggerPrompt.ExecuteScript(system, args[0]);
}
catch(Exception e)
{
Console.WriteLine("Error parsing script: {0}", e.Message);
}
}
while (_state != SystemExecutionState.Quit)
{
try
{
switch (_state)
{
case SystemExecutionState.Halted:
case SystemExecutionState.Debugging:
_state = debuggerPrompt.Prompt(system);
break;
case SystemExecutionState.SingleStep:
system.SingleStep();
system.Display.RenderCurrent(false);
_state = SystemExecutionState.Debugging;
break;
case SystemExecutionState.SingleFrame:
system.SingleStep();
if (system.DisplayProcessor.FrameLatch)
{
Console.WriteLine("Frame completed.");
_state = SystemExecutionState.Debugging;
}
break;
case SystemExecutionState.UntilDisplayStart:
system.SingleStep();
if (system.DisplayProcessor.State == ProcessorState.Running)
{
Console.WriteLine("Display started.");
_state = SystemExecutionState.Debugging;
}
break;
case SystemExecutionState.Running:
system.SingleStep();
if (system.Processor.State == ProcessorState.Halted)
{
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(system.Processor.PC));
_state = SystemExecutionState.Debugging;
}
else if (system.Processor.State == ProcessorState.BreakpointHalt)
{
Console.WriteLine(
"Breakpoint hit: {0} at address {1}",
BreakpointManager.GetBreakpoint(system.Processor.BreakpointAddress),
Helpers.ToOctal(system.Processor.BreakpointAddress));
_state = SystemExecutionState.Debugging;
}
break;
}
}
catch(Exception e)
{
Console.WriteLine("Internal error during execution: {0}", e.Message);
_state = SystemExecutionState.Debugging;
}
}
}
static void OnBreak(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("User break.");
_state = SystemExecutionState.Debugging;
e.Cancel = true;
}
static void PrintHerald()
{
Console.WriteLine("sImlac v0.1. (c) 2016, 2017 Living Computers: Museum+Labs");
Console.WriteLine();
}
private static SystemExecutionState _state;
}
}

41
imlac/Helpers.cs Normal file
View File

@ -0,0 +1,41 @@
/*
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;
namespace imlac
{
public class Helpers
{
public static string ToOctal(ushort value)
{
return ToOctal(value, 6);
}
public static string ToOctal(ushort value, int digits)
{
string octalString = Convert.ToString(value, 8);
return new String('0', digits - octalString.Length) + octalString;
}
public static ushort GetUshortForOctalString(string octal)
{
ushort value = Convert.ToUInt16(octal, 8);
return value;
}
}
}

209
imlac/HighResTimer.cs Normal file
View File

@ -0,0 +1,209 @@
/*
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.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
namespace imlac
{
/// <summary>
/// HighResTimer gives us access to NT's very-high-resolution PerformanceCounters.
/// This gives us the precision we need to sync emulation to any speed we desire.
/// </summary>
public sealed class HighResTimer
{
[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceCounter(
out long lpPerformanceCount);
[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(
out long lpFrequency);
public HighResTimer()
{
//What's the frequency, Kenneth?
if (QueryPerformanceFrequency(out _frequency) == false)
{
// high-performance counter not supported
throw new Win32Exception();
}
}
/// <summary>
/// Returns the current time in seconds.
/// </summary>
/// <returns></returns>
public double GetCurrentTime()
{
long currentTime;
QueryPerformanceCounter(out currentTime);
return (double)(currentTime) / (double)_frequency;
}
private long _frequency;
}
public sealed class FrameTimer
{
[DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)]
static extern UInt32 TimeGetDevCaps(ref TimeCaps timeCaps, UInt32 sizeTimeCaps);
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
static extern UInt32 TimeBeginPeriod(UInt32 uPeriod);
[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
public static extern uint TimeEndPeriod(uint uMilliseconds);
[DllImport("kernel32.dll", EntryPoint = "CreateTimerQueue")]
public static extern IntPtr CreateTimerQueue();
[DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueEx")]
public static extern bool DeleteTimerQueue(IntPtr hTimerQueue, IntPtr hCompletionEvent);
[DllImport("kernel32.dll", EntryPoint = "CreateTimerQueueTimer")]
public static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer, IntPtr hTimerQueue, IntPtr Callback, IntPtr Parameter, UInt32 DueTime, UInt32 Period, uint Flags);
[DllImport("kernel32.dll", EntryPoint = "ChangeTimerQueueTimer")]
public static extern bool ChangeTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, UInt32 DueTime, UInt32 Period);
[DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueTimer")]
public static extern bool DeleteTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, IntPtr hCompletionEvent);
[StructLayout(LayoutKind.Sequential)]
public struct TimeCaps
{
public UInt32 wPeriodMin;
public UInt32 wPeriodMax;
};
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate void UnmanagedTimerCallback(IntPtr param, bool timerOrWait);
/// <summary>
/// FrameTimer provides a simple method to synchronize execution to a given framerate.
/// Calling WaitForFrame() blocks until the start of the next frame.
///
/// NOTE: This code uses the Win32 TimerQueue APIs instead of the System.Threading.Timer
/// APIs because the .NET APIs do not allow execution of the callback on the timer's thread --
/// it queues up a new worker thread. This lowers the accuracy of the timer, and since we
/// need all the precision we can get they're not suitable here.
/// </summary>
/// <param name="framesPerSecond">The frame rate to sync to.</param>
public FrameTimer(double framesPerSecond)
{
//
// Set the timer to the minimum value (1ms). This should be supported on any modern x86 system.
// If not, too bad...
//
UInt32 res = TimeBeginPeriod(1);
if (res != 0)
{
throw new InvalidOperationException("Unable to set timer period.");
}
//
// Create a new timer queue
//
_hTimerQueue = CreateTimerQueue();
if (_hTimerQueue == IntPtr.Zero)
{
throw new InvalidOperationException("Unable to create timer queue.");
}
//
// Since we only have a resolution of 1ms, we have to do some hackery to get a slightly more accurate framerate.
// (60fps NTSC requires 16 2/3s ms frame delay.)
// We alternate between two rates at varying intervals and this gets us fairly close to the desired frame rate
// (~60.25 fps for NTSC), which is almost impercetible.
//
_callback = new UnmanagedTimerCallback(TimerCallbackFn);
_highPeriod = (uint)Math.Ceiling(1000.0 * (1.0 / framesPerSecond));
_lowPeriod = (uint)Math.Floor(1000.0 * (1.0 / framesPerSecond));
_periodTenths = _periodSwitch = (uint)((1000.0 * (1.0 / framesPerSecond) - Math.Floor(1000.0 * (1.0 / framesPerSecond))) * 10.0);
if (!CreateTimerQueueTimer(out _hTimer, _hTimerQueue, Marshal.GetFunctionPointerForDelegate(_callback), IntPtr.Zero, _lowPeriod, _lowPeriod, 0x00000020 /* execute in timer thread */))
{
throw new InvalidOperationException("Unable to create timer queue timer.");
}
_event = new AutoResetEvent(false);
_lowTimer = 0;
}
~FrameTimer()
{
//
// 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();
}
/// <summary>
/// Waits for the timer to fire.
/// </summary>
public void WaitForFrame()
{
_event.WaitOne();
}
/// <summary>
/// Callback from timer queue. Work done here is executed on the timer's thread, so must be quick.
/// </summary>
/// <param name="lpParameter"></param>
/// <param name="TimerOrWaitFired"></param>
private void TimerCallbackFn(IntPtr lpParameter, bool TimerOrWaitFired)
{
_event.Set();
_lowTimer++;
if (_lowTimer >= _periodSwitch)
{
_lowTimer = 0;
_period = !_period;
ChangeTimerQueueTimer(_hTimerQueue, _hTimer, _period ? _lowPeriod : _highPeriod, _period ? _lowPeriod : _highPeriod);
_periodSwitch = !_period ? _periodTenths : 10 - _periodTenths;
}
}
private IntPtr _hTimerQueue;
private IntPtr _hTimer;
private AutoResetEvent _event;
private UnmanagedTimerCallback _callback;
private uint _lowPeriod;
private uint _highPeriod;
private uint _periodSwitch;
private uint _periodTenths;
private int _lowTimer;
private bool _period;
}
}

114
imlac/IImlacConsole.cs Normal file
View File

@ -0,0 +1,114 @@
/*
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 imlac.IO;
namespace imlac
{
public enum DrawingMode
{
Off,
Normal,
Dotted,
Hidden,
SGR1, // reduced intensity
Point, // increased intensity
Debug, // For debugging purposes
}
/// <summary>
/// IImlacConsole provides the interface for the components making up a standard
/// "console" setup, which at the moment includes only the Display and Keyboard,
/// but could be extended.
/// </summary>
public interface IImlacConsole
{
/// <summary>
/// Indicates whether a key is currently pressed.
/// </summary>
bool IsKeyPressed
{
get;
}
/// <summary>
/// Indicates the currently pressed key (if IsKeyPressed is true)
/// </summary>
ImlacKey Key
{
get;
}
/// <summary>
/// Indicates any modifier bits (ctrl, shift) being pressed.
/// </summary>
ImlacKeyModifiers KeyModifiers
{
get;
}
/// <summary>
/// Indicates the status of the front-panel data switches
/// </summary>
ushort DataSwitches
{
get;
}
bool ThrottleFramerate
{
get;
set;
}
bool DataSwitchMappingEnabled
{
get;
set;
}
DataSwitchMappingMode DataSwitchMode
{
get;
set;
}
bool FullScreen
{
get;
set;
}
void UnlatchKey();
void ClearDisplay();
void MoveAbsolute(uint x, uint y, DrawingMode mode);
void DrawPoint(uint x, uint y);
void RenderCurrent(bool completeFrame);
void FrameDone();
void SetScale(float scale);
void MapDataSwitch(uint switchNumber, VKeys key);
VKeys GetDataSwitchMapping(uint switchNumber);
}
}

115
imlac/IO/Clock.cs Normal file
View File

@ -0,0 +1,115 @@
/*
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;
namespace imlac.IO
{
/// <summary>
/// Implements the ACI-1 "Addressable Clock With Interrupt" option
/// </summary>
public class AddressableClock : IIOTDevice
{
public AddressableClock(ImlacSystem system)
{
_system = system;
Reset();
}
public void Reset()
{
_timerTriggered = false;
_timerCount = _timerInit = 0;
}
public void Clock()
{
if (_timerInit > 0)
{
if (_timerCount > 0)
{
_timerTriggered = false;
_timerCount--;
}
if (_timerCount == 0)
{
_timerTriggered = true;
_timerCount = _timerInit;
}
}
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
}
public bool TimerTriggered
{
get { return _timerTriggered; }
}
public void ExecuteIOT(int iotCode)
{
//
// Dispatch the IOT instruction.
//
switch (iotCode)
{
case 0x51:
_timerCount = _timerInit = _system.Processor.AC;
_timerTriggered = false;
break;
case 0x52:
_timerTriggered = false;
break;
case 0x54:
if (_timerTriggered)
{
_system.Processor.PC++;
}
// TODO: does this reset status?
break;
case 0x69:
_system.Processor.AC |= (ushort)_timerCount;
break;
default:
throw new NotImplementedException(String.Format("Unimplemented ACI-1 IOT instruction {0:x4}", iotCode));
}
}
private bool _timerTriggered;
private int _timerCount;
private int _timerInit;
private ImlacSystem _system;
private readonly int[] _handledIOTs =
{
0x51, // load timer from AC
0x52, // Clear timer status
0x54, // Skip if timer status = 1
0x69, // read timer to AC
};
}
}

41
imlac/IO/IIOTDevice.cs Normal file
View File

@ -0,0 +1,41 @@
/*
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/>.
*/
namespace imlac.IO
{
public interface IIOTDevice
{
/// <summary>
/// Returns an array of 9-bit IOT instructions handled by this device. (See below)
/// </summary>
/// <returns></returns>
int[] GetHandledIOTs();
/// <summary>
/// Executes the specified IOT opcode.
///
/// iotCode is the 9 bits describing the device number and IOP code. All 9 are required because
/// despite it looking like the device code might group IOT instructions by device, it doesn't
/// actually do so in any useful manner (for example device code 06 includes IOPs for the
/// Paper-tape reader, the TTY interface, and the Tablet. Using the full 9 bits allows each
/// device implementation to register for the IOT instructions rather than the device codes.
///
/// </summary>
/// <param name="iotCode">The 9-bit IOT code to execute</param>
void ExecuteIOT(int iotCode);
}
}

View File

@ -0,0 +1,183 @@
/*
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;
namespace imlac.IO
{
//
// From the PDS-1 Technical manual:
// The PDS-1 program interrupt facility, when enabled under program control (001 162),
// allows the status flip-flop of an I/O device to force an interrupt upon the completion of the
// current instruction, thereby alleviating the need for repeated flag checking by the main program.
// An interrupt is the equivalent of a subroutine jump to memory location 0000.
// ...
// The interrupt causes the address of the next instruction of the main program to be stored in memory
// location 0000, the next instruction to be taken from 0001, and the program interrupt facility to be
// disabled.
//
// The implementation here is overly simplistic -- on every clock, if enabled, we check the
// implemented system devices to see if their status flags indicate pending data, if the mask enables
// interrupts we do the ISR routine at 0000.
//
// The interrupt status bits are:
// 15 - Light pen (LPA-1)
// 14 - 40 Cycle Sync & End of Display Frame
// 13 - Memory Protect (PMP-1)
// 12 - TTY Receive
// 11 - Keyboard
// 10 - TTY send
// 9 - Joystick, Mouse, or Trackball (JST-1, GMI-1, or TBL-1)
// 8 - Tablet (TBI-1)
// 7 - Punch (PTP-1)
// 6 - Keyboard #2 (KYB-1)
// 5 - TKA IN
// 4 - TKA OUT (TKA-1)
// 3 - 16 Bit input/PTR (GSI-1 or PTR-1)
// 2 - Addressable clock w/input (ACI-1)
// 1 - unused
// 0 - Printer (PRT-1)
//
public class InterruptFacility : IIOTDevice
{
public InterruptFacility(ImlacSystem system)
{
_system = system;
}
public void Reset()
{
_interruptsEnabled = false;
_interruptMask = 0;
_interruptStatus = 0;
_interruptPending = false;
}
public void Clock()
{
if (_interruptsEnabled)
{
// Collect up devices that want to interrupt us.
_interruptStatus = 0;
// bit 14: 40 cycle sync
if (_system.DisplayProcessor.FrameLatch)
{
_interruptStatus |= 0x0002;
}
// bit 12 - TTY rcv
if (_system.TTY.DataReady)
{
_interruptStatus |= 0x0008;
}
// bit 11 - keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x0010;
}
// bit 2 - ACI-1 (clock)
if (_system.Clock.TimerTriggered)
{
_interruptStatus |= 0x2000;
}
// mask it with our interrupt mask and if non-zero then we have a pending interrupt,
// which we will execute at the start of the next CPU instruction.
if ((_interruptMask & _interruptStatus) != 0)
{
_interruptPending = true;
}
//
// 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)
{
// save the current PC at 0
_system.Memory.Store(0x0000, _system.Processor.PC);
// continue execution at 1
_system.Processor.PC = 0x0001;
// and disable further interrupts
_interruptsEnabled = false;
_interruptPending = false;
Trace.Log(LogType.Interrupt, "Interrupt triggered (for device(s) {0})", Helpers.ToOctal((ushort)(_interruptMask & _interruptStatus)));
}
}
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
}
public void ExecuteIOT(int iotCode)
{
//
// Dispatch the IOT instruction.
//
switch (iotCode)
{
case 0x71:
_interruptsEnabled = false;
_interruptPending = false;
Trace.Log(LogType.Interrupt, "Interrupts disabled");
break;
case 0x72:
_interruptsEnabled = true;
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));
break;
case 0x61:
_interruptMask = _system.Processor.AC;
Trace.Log(LogType.Interrupt, "Interrupt mask set to {0}", Helpers.ToOctal((ushort)_interruptMask));
break;
default:
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0:x4}", iotCode));
}
}
private bool _interruptsEnabled;
private bool _interruptPending;
private int _interruptMask;
private int _interruptStatus;
private ImlacSystem _system;
private readonly int[] _handledIOTs =
{
0x41, // read interrupt status bits
0x61, // arm/disarm devices (set interrupt mask)
0x71, // IOF (disable interrupts)
0x72, // ION (enabled masked interrupts)
};
}
}

328
imlac/IO/Keyboard.cs Normal file
View File

@ -0,0 +1,328 @@
/*
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;
namespace imlac.IO
{
[Flags]
public enum ImlacKeyModifiers
{
None = 0x000,
Shift = 0x100,
Ctrl = 0x200,
Rept = 0x400,
}
public enum ImlacKey
{
DataXmit = 0x2,
Down = 0x4,
Right = 0x5,
Up = 0x6,
Left = 0x8,
Tab = 0x9,
CR = 0x0d,
FF = 0x0c,
PageXmit = 0xe,
Home = 0xf,
Brk = 0x19,
Esc = 0x1b,
Space = 0x20,
Comma = 0x2c,
Minus = 0x2d,
Period = 0x2e,
Slash = 0x2f,
K0 = 0x30,
K1 = 0x31,
K2 = 0x32,
K3 = 0x33,
K4 = 0x34,
K5 = 0x35,
K6 = 0x36,
K7 = 0x37,
K8 = 0x38,
K9 = 0x39,
Colon = 0x3a,
Semicolon = 0x3b,
D0 = 0x18,
D2 = 0x1a,
D4 = 0x1c,
D5 = 0x1d,
D6 = 0x1e,
Unlabeled = 0x1f,
A = 0x61,
B = 0x62,
C = 0x63,
D = 0x64,
E = 0x65,
F = 0x66,
G = 0x67,
H = 0x68,
I = 0x69,
J = 0x6a,
K = 0x6b,
L = 0x6c,
M = 0x6d,
N = 0x6e,
O = 0x6f,
P = 0x70,
Q = 0x71,
R = 0x72,
S = 0x73,
T = 0x74,
U = 0x75,
V = 0x76,
W = 0x77,
X = 0x78,
Y = 0x79,
Z = 0x7a,
Del = 0x7f,
Invalid = 0xff,
}
public class Keyboard : IIOTDevice
{
public Keyboard(ImlacSystem system)
{
_system = system;
Reset();
}
static Keyboard()
{
BuildKeyMappings();
}
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)
{
_keyCode = GetScancodeForCurrentKey();
if (_keyCode != 0)
{
Trace.Log(LogType.Keyboard, "Key latched {0}", Helpers.ToOctal(_keyCode));
_keyReady = true;
}
}
else
{
_keyCode = 0;
}
}
public void Reset()
{
_keyCode = 0;
_keyReady = false;
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
}
public bool KeyReady
{
get { return _keyReady; }
}
public void ExecuteIOT(int iotCode)
{
switch (iotCode)
{
case 0x11:
_system.Processor.AC |= _keyCode;
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}", Helpers.ToOctal(_system.Processor.AC));
break;
case 0x12:
_keyCode = 0;
_keyReady = false;
_system.Display.UnlatchKey();
Trace.Log(LogType.Keyboard, "Keyboard flag reset.");
break;
case 0x13:
_system.Processor.AC |= _keyCode;
_keyCode = 0;
_keyReady = false;
_system.Display.UnlatchKey();
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}, keyboard flag reset.", Helpers.ToOctal(_system.Processor.AC));
break;
}
}
private ushort GetScancodeForCurrentKey()
{
ushort scanCode = 0;
ImlacKey key = _system.Display.Key;
ImlacKeyModifiers modifiers = _system.Display.KeyModifiers;
Trace.Log(LogType.Keyboard, "Keypress is {0}", key);
if (key != ImlacKey.Invalid)
{
scanCode = (modifiers & ImlacKeyModifiers.Shift) != 0 ? _keyMappings[key].ShiftedCode : _keyMappings[key].NormalCode;
if (scanCode == 0)
{
// no code for shifted key, just use normal one.
scanCode = _keyMappings[key].NormalCode;
}
// 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.
//
if ((modifiers & ImlacKeyModifiers.Rept) != 0)
{
scanCode |= 0x400;
}
if ((modifiers & ImlacKeyModifiers.Ctrl) != 0)
{
scanCode |= 0x200;
}
if ((modifiers & ImlacKeyModifiers.Shift) != 0)
{
scanCode |= 0x100;
}
Trace.Log(LogType.Keyboard, "Final keycode is {0}", Helpers.ToOctal(scanCode));
}
return scanCode;
}
private readonly int[] _handledIOTs = { 0x11, 0x12, 0x13 };
private bool _keyReady;
private ushort _keyCode;
private ImlacSystem _system;
private struct ImlacKeyMapping
{
public ImlacKeyMapping(ImlacKey key, byte normal, byte shifted)
{
Key = key;
NormalCode = normal;
ShiftedCode = shifted;
}
public ImlacKey Key;
public byte NormalCode;
public byte ShiftedCode;
}
private static void BuildKeyMappings()
{
_keyMappings = new Dictionary<ImlacKey, ImlacKeyMapping>();
AddMapping(ImlacKey.DataXmit, 0x2, 0x0);
AddMapping(ImlacKey.Down, 0x4, 0x0);
AddMapping(ImlacKey.Right, 0x5, 0x0);
AddMapping(ImlacKey.Up, 0x6, 0x0);
AddMapping(ImlacKey.Left, 0x8, 0x0);
AddMapping(ImlacKey.Tab, 0x9, 0x0);
AddMapping(ImlacKey.CR, 0x0d, 0x0);
AddMapping(ImlacKey.FF, 0x0c, 0x0);
AddMapping(ImlacKey.PageXmit, 0xe, 0x0);
AddMapping(ImlacKey.Home, 0xf, 0x0);
AddMapping(ImlacKey.Brk, 0x19, 0x0);
AddMapping(ImlacKey.Esc, 0x1b, 0x0);
AddMapping(ImlacKey.Space, 0x20, 0x0);
AddMapping(ImlacKey.Comma, 0x2c, 0x3c);
AddMapping(ImlacKey.Minus, 0x2d, 0x3d);
AddMapping(ImlacKey.Period, 0x2e, 0x3e);
AddMapping(ImlacKey.Slash, 0x2f, 0x3f);
AddMapping(ImlacKey.K0, 0x30, 0x0);
AddMapping(ImlacKey.K1, 0x31, 0x21);
AddMapping(ImlacKey.K2, 0x32, 0x22);
AddMapping(ImlacKey.K3, 0x33, 0x23);
AddMapping(ImlacKey.K4, 0x34, 0x24);
AddMapping(ImlacKey.K5, 0x35, 0x25);
AddMapping(ImlacKey.K6, 0x36, 0x26);
AddMapping(ImlacKey.K7, 0x37, 0x27);
AddMapping(ImlacKey.K8, 0x38, 0x28);
AddMapping(ImlacKey.K9, 0x39, 0x29);
AddMapping(ImlacKey.Colon, 0x3a, 0x2a);
AddMapping(ImlacKey.Semicolon, 0x3b, 0x2b);
AddMapping(ImlacKey.D0, 0x18, 0x0);
AddMapping(ImlacKey.D2, 0x1a, 0x0);
AddMapping(ImlacKey.D4, 0x1c, 0x0);
AddMapping(ImlacKey.D5, 0x1d, 0x0);
AddMapping(ImlacKey.D6, 0x1e, 0x0);
AddMapping(ImlacKey.Unlabeled, 0x1f, 0x0);
AddMapping(ImlacKey.A, 0x61, 0x41);
AddMapping(ImlacKey.B, 0x62, 0x42);
AddMapping(ImlacKey.C, 0x63, 0x43);
AddMapping(ImlacKey.D, 0x64, 0x44);
AddMapping(ImlacKey.E, 0x65, 0x45);
AddMapping(ImlacKey.F, 0x66, 0x46);
AddMapping(ImlacKey.G, 0x67, 0x47);
AddMapping(ImlacKey.H, 0x68, 0x48);
AddMapping(ImlacKey.I, 0x69, 0x49);
AddMapping(ImlacKey.J, 0x6a, 0x4a);
AddMapping(ImlacKey.K, 0x6b, 0x4b);
AddMapping(ImlacKey.L, 0x6c, 0x4c);
AddMapping(ImlacKey.M, 0x6d, 0x4d);
AddMapping(ImlacKey.N, 0x6e, 0x4e);
AddMapping(ImlacKey.O, 0x6f, 0x4f);
AddMapping(ImlacKey.P, 0x70, 0x50);
AddMapping(ImlacKey.Q, 0x71, 0x51);
AddMapping(ImlacKey.R, 0x72, 0x52);
AddMapping(ImlacKey.S, 0x73, 0x53);
AddMapping(ImlacKey.T, 0x74, 0x54);
AddMapping(ImlacKey.U, 0x75, 0x55);
AddMapping(ImlacKey.V, 0x76, 0x56);
AddMapping(ImlacKey.W, 0x77, 0x57);
AddMapping(ImlacKey.X, 0x78, 0x58);
AddMapping(ImlacKey.Y, 0x79, 0x59);
AddMapping(ImlacKey.Z, 0x7a, 0x5a);
AddMapping(ImlacKey.Del, 0x7f, 0x0);
}
private static void AddMapping(ImlacKey key, byte normal, byte shifted)
{
_keyMappings.Add(key, new ImlacKeyMapping(key, normal, shifted));
}
private static Dictionary<ImlacKey, ImlacKeyMapping> _keyMappings;
}
}

145
imlac/IO/PaperTapeReader.cs Normal file
View File

@ -0,0 +1,145 @@
/*
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.IO;
namespace imlac.IO
{
public class PaperTapeReader : IIOTDevice
{
public PaperTapeReader(ImlacSystem system)
{
_system = system;
}
public void Reset()
{
_tapeContents = null;
_tapeIndex = 0;
_dataReady = false;
_state = ReaderState.Stopped;
_clocks = 0;
}
public void Clock()
{
if (_tapeContents != null && _state == ReaderState.Running)
{
_clocks++;
if (_clocks > _tapeAdvanceClocks)
{
_clocks = 0;
if (_tapeIndex < _tapeContents.Length)
{
_dataReady = !_dataReady;
if (!_dataReady)
{
_tapeIndex++;
Console.Write(":");
}
}
else
{
_dataReady = false;
}
}
}
}
public void LoadTape(string path)
{
_state = ReaderState.Stopped;
_tapeIndex = 0;
FileStream fs = File.OpenRead(path);
_tapeContents = new byte[fs.Length];
fs.Read(_tapeContents, 0, (int)fs.Length);
fs.Close();
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
}
public void ExecuteIOT(int iotCode)
{
//
// Dispatch the IOT instruction.
//
switch (iotCode)
{
case 0x29: // Read -- contents of tape are OR'd into the processor's AC.
if (_tapeIndex < _tapeContents.Length)
{
_system.Processor.AC |= _tapeContents[_tapeIndex];
Trace.Log(LogType.PTR, "PTR read {0:x2}", _tapeContents[_tapeIndex]);
}
else
{
Trace.Log(LogType.PTR, "PTR read past end of tape.");
}
break;
case 0x2a: // Halt
_state = ReaderState.Stopped;
Trace.Log(LogType.PTR, "PTR stopped.");
_dataReady = false;
break;
case 0x31: // Start
_state = ReaderState.Running;
Trace.Log(LogType.PTR, "PTR started.");
_dataReady = false;
break;
default:
throw new NotImplementedException(String.Format("Unimplemented PTR IOT instruction {0:x4}", iotCode));
}
}
public bool DataReady()
{
return _dataReady;
}
private ImlacSystem _system;
private ReaderState _state;
private byte[] _tapeContents;
private int _tapeIndex;
private bool _dataReady;
private int _clocks;
private const int _tapeAdvanceClocks = 1000; // fudged
private readonly int[] _handledIOTs = { 0x29, 0x2a, 0x31 };
private enum ReaderState
{
Stopped,
Running
}
}
}

158
imlac/IO/TTY.cs Normal file
View File

@ -0,0 +1,158 @@
/*
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 imlac.IO.TTYChannels;
namespace imlac.IO
{
public class TTY : IIOTDevice
{
public TTY(ImlacSystem system)
{
_system = system;
_dataChannel = new NullDataChannel();
Reset();
}
public void Reset()
{
_dataSendReady = true;
_dataReady = false;
_clocks = 0;
if (_dataChannel != null)
{
_dataChannel.Reset();
}
}
public void SetChannel(ISerialDataChannel channel)
{
if (channel == null)
{
throw new ArgumentNullException("channel");
}
_dataChannel = channel;
}
public void Clock()
{
_clocks++;
if (_clocks > _dataClocks)
{
_clocks = 0;
if (_dataChannel.DataAvailable)
{
_dataReady = true;
_data = _dataChannel.Read();
Trace.Log(LogType.TTY, "i");
}
else
{
_dataReady = false;
}
}
// Are we waiting to send something?
if (!_dataSendReady && _dataChannel.OutputReady)
{
_dataChannel.Write(_data);
Trace.Log(LogType.TTY, "o");
// Sent, reset flag.
_dataSendReady = true;
}
}
public bool DataReady
{
get { return _dataReady; }
}
public bool DataSendReady
{
get { return _dataSendReady; }
}
public int[] GetHandledIOTs()
{
return _handledIOTs;
}
public void ExecuteIOT(int iotCode)
{
switch (iotCode)
{
case 0x19: // RRB - TTY read
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_data));
_system.Processor.AC |= _data;
break;
case 0x1a: // RCF - Clear TTY status
_dataReady = false;
break;
case 0x1b: // RRC - Read and clear status
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_data));
_dataReady = false;
_system.Processor.AC |= _data;
break;
case 0x21: // TPR - transmit
if (_dataSendReady) // only if transmitter is ready
{
_data = (byte)_system.Processor.AC;
_dataSendReady = false;
}
break;
case 0x22: // TCF - clear output flag
_dataSendReady = true;
break;
case 0x23: // TPC - print, clear flag
_data = (byte)_system.Processor.AC;
_dataSendReady = false;
break;
default:
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_data));
break;
}
}
private readonly int[] _handledIOTs = { 0x9, 0x19, 0x1a, 0x1b, 0x21, 0x22, 0x23 };
private bool _dataReady;
private bool _dataSendReady;
private byte _data;
private int _clocks;
private readonly int _dataClocks = 100;
private ISerialDataChannel _dataChannel;
private ImlacSystem _system;
}
}

View File

@ -0,0 +1,57 @@
/*
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/>.
*/
namespace imlac.IO.TTYChannels
{
public interface ISerialDataChannel
{
/// <summary>
/// Resets the channel to initial state, if necessary.
/// </summary>
void Reset();
/// <summary>
/// Closes the channel.
/// </summary>
void Close();
/// <summary>
/// Reads a single byte from the channel.
/// Implementers may block if no data is ready.
/// </summary>
/// <returns></returns>
byte Read();
/// <summary>
/// Writes a single byte to the channel.
/// Implementers may block if the channel isn't ready to send.
/// </summary>
/// <param name="b"></param>
void Write(byte b);
/// <summary>
/// Indicates that at least one byte is ready to be Read.
/// </summary>
bool DataAvailable { get; }
/// <summary>
/// Indicates that at least one byte can be transmitted.
/// </summary>
bool OutputReady { get; }
}
}

View File

@ -0,0 +1,68 @@
/*
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/>.
*/
namespace imlac.IO.TTYChannels
{
/// <summary>
/// ISerialDataChannel implementation that provides a bit-bucket.
/// </summary>
public class NullDataChannel : ISerialDataChannel
{
public NullDataChannel()
{
}
public void Reset()
{
}
public void Close()
{
}
public byte Read()
{
return 0;
}
public void Write(byte value)
{
}
public bool DataAvailable
{
get
{
return false;
}
}
public bool OutputReady
{
get
{
// Always return true, bits just go into the bucket.
return true;
}
}
}
}

View File

@ -0,0 +1,69 @@
/*
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.IO.Ports;
namespace imlac.IO.TTYChannels
{
/// <summary>
/// An implementation of ISerialDataChannel over a real RS232 port on the host machine.
/// </summary>
public class SerialDataChannel : ISerialDataChannel
{
public SerialDataChannel(SerialPort port)
{
_serialPort = port;
_serialPort.WriteTimeout = 10;
}
public void Reset()
{
// Flush our buffers
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
}
public void Close()
{
_serialPort.Close();
}
public byte Read()
{
return (byte)_serialPort.ReadByte();
}
public void Write(byte value)
{
// Really. You have a ReadByte function but no analog for Write?
_serialPort.Write(new byte[] { value }, 0, 1);
}
public bool DataAvailable
{
get { return _serialPort.BytesToRead > 0; }
}
public bool OutputReady
{
// Always true
get { return true; }
}
private SerialPort _serialPort;
}
}

View File

@ -0,0 +1,92 @@
/*
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.IO;
namespace imlac.IO.TTYChannels
{
/// <summary>
/// Implements an ISerialDataChannel that sources data to or from a Stream.
/// </summary>
public class StreamDataChannel : ISerialDataChannel
{
public StreamDataChannel(Stream data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
_dataStream = data;
}
public void Reset()
{
// Try to reposition if possible.
if (_dataStream.CanSeek)
{
_dataStream.Seek(0, SeekOrigin.Begin);
}
}
public void Close()
{
_dataStream.Close();
}
public byte Read()
{
return (byte)_dataStream.ReadByte();
}
public void Write(byte value)
{
//
// Write if we can, no-op if not.
//
if (_dataStream.CanWrite)
{
_dataStream.WriteByte(value);
}
else
{
Trace.Log(LogType.TTY, "Dropped TTY output {0}", Helpers.ToOctal(value));
}
}
public bool DataAvailable
{
get
{
return _dataStream.Position < _dataStream.Length;
}
}
public bool OutputReady
{
get
{
// Always return true, even if the Stream doesn't support writing.
return true;
}
}
private Stream _dataStream;
}
}

58
imlac/Memory.cs Normal file
View File

@ -0,0 +1,58 @@
/*
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/>.
*/
namespace imlac
{
// TODO: make memory size configurable.
public class Memory
{
public Memory(ImlacSystem system)
{
_mem = new ushort[Size];
_system = system;
}
public ushort Fetch(ushort address)
{
ushort word = _mem[address & SizeMask];
return word;
}
public void Store(ushort address, ushort word)
{
_mem[address & SizeMask] = word;
// Invalidate processor caches
_system.Processor.InvalidateCache(address);
_system.DisplayProcessor.InvalidateCache(address);
}
public static ushort Size
{
get { return 8192; }
}
public static ushort SizeMask
{
get { return 0x1fff; }
}
private ushort[] _mem;
private ImlacSystem _system;
}
}

35
imlac/Paths.cs Normal file
View File

@ -0,0 +1,35 @@
/*
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.IO;
namespace imlac
{
/// <summary>
/// Defines the paths pointing to various resources used by the emulator.
/// </summary>
public static class Paths
{
public static string BuildBootPath(string file)
{
return Path.Combine(_boot, file);
}
private static string _boot = "boot";
}
}

1202
imlac/Processor.cs Normal file

File diff suppressed because it is too large Load Diff

14
imlac/Program.cs Normal file
View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace imlac
{
class Program
{
static void Main(string[] args)
{
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("sImlac")]
[assembly: AssemblyDescription("Imlac PDS-1 Emulator")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
[assembly: AssemblyProduct("sImlac")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("49681c7f-fe61-475f-9009-2b2fc1ab2aff")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

878
imlac/SDLConsole.cs Normal file
View File

@ -0,0 +1,878 @@
/*
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 System.Windows.Forms;
using System.Drawing;
using System.Threading;
using imlac.IO;
using SdlDotNet.Graphics;
namespace imlac
{
public enum DataSwitchMappingMode
{
Toggle, // Pressing a key toggles the switch (from 0 to 1 or from 1 to 0)
Momentary, // Holding a key down indicates a "1", release indicates "0"
MomentaryInverted, // Same as above, but inverted
}
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>
/// This is used to filter keyboard messages from the App's message loop.
/// This is necessary because the "Video" object that SDL provides appears
/// to eat certain keystrokes and since I have no means to control it
/// (and I'm not really all that interested in compiling my own version)
/// this is the only alternative.
///
/// This class captures keystrokes for keys the emulator is interested in
/// and fires an event containing the keycode.
/// These keyboard messages are always passed on to the app.
/// </summary>
public class KeyboardFilter : IMessageFilter
{
public KeyboardFilter()
{
_keyModifiers = ImlacKeyModifiers.None;
_keyLatched = false;
_dataSwitches = 0x0; // ffff;
_latchedKeyCode = ImlacKey.Invalid;
_dataSwitchMappingMode = DataSwitchMappingMode.Toggle;
_keyLatchedLock = new ReaderWriterLockSlim();
}
static KeyboardFilter()
{
BuildKeyMappings();
}
public event EventHandler FullScreenToggle;
// TODO: should ensure consistency (threading)
public ImlacKey LatchedKey
{
get { return _latchedKeyCode; }
}
public ImlacKeyModifiers Modifiers
{
get { return _keyModifiers; }
}
public bool KeyLatched
{
get
{
_keyLatchedLock.EnterReadLock();
bool latched = _keyLatched;
_keyLatchedLock.ExitReadLock();
return latched;
}
set
{
_keyLatchedLock.EnterWriteLock();
_keyLatched = value;
_keyLatchedLock.ExitWriteLock();
}
}
public ushort DataSwitches
{
get { return (ushort)_dataSwitches; }
}
public DataSwitchMappingMode DataSwitchMode
{
get { return _dataSwitchMappingMode; }
set { _dataSwitchMappingMode = value; }
}
public void MapDataSwitch(uint switchNumber, VKeys key)
{
_dataSwitchMappings[switchNumber] = key;
}
public VKeys GetDataSwitchMapping(uint switchNumber)
{
return _dataSwitchMappings[switchNumber];
}
public bool PreFilterMessage(ref Message m)
{
bool ret = false;
switch (m.Msg)
{
case WM_SYSKEYDOWN:
case WM_SOMETHINGDOWN:
//
// If this is a modifier key (Alt, Shift, Ctrl) then we track it separately
// (it is not tracked as a key)
//
switch ((VKeys)m.WParam.ToInt32())
{
case VKeys.Shift:
_keyModifiers |= ImlacKeyModifiers.Shift;
break;
case VKeys.Ctrl:
_keyModifiers |= ImlacKeyModifiers.Ctrl;
break;
case VKeys.Alt:
_keyModifiers |= ImlacKeyModifiers.Rept;
break;
default:
UpdateDataSwitches((VKeys)m.WParam.ToInt32(), true /* key down */);
//Console.WriteLine("{0:x}", m.WParam.ToInt32());
if ((VKeys)m.WParam.ToInt32() == VKeys.Insert)
{
if (FullScreenToggle != null)
{
FullScreenToggle(this, null);
}
}
_keyLatchedLock.EnterWriteLock();
_keyLatched = true;
_latchedKeyCode = TranslateKeyCode((VKeys)m.WParam.ToInt32());
_keyLatchedLock.ExitWriteLock();
break;
}
break;
case WM_SYSKEYUP:
case WM_SOMETHINGUP:
//
// We only track keyboard modifiers and data switch toggles here.
//
switch ((VKeys)m.WParam.ToInt32())
{
case VKeys.Shift:
_keyModifiers &= (~ImlacKeyModifiers.Shift);
break;
case VKeys.Ctrl:
_keyModifiers &= (~ImlacKeyModifiers.Ctrl);
break;
case VKeys.Alt:
_keyModifiers &= (~ImlacKeyModifiers.Rept);
break;
default:
UpdateDataSwitches((VKeys)m.WParam.ToInt32(), false /* key up */);
_latchedKeyCode = ImlacKey.Invalid;
break;
}
break;
default:
break;
}
return ret;
}
private ImlacKey TranslateKeyCode(VKeys virtualKey)
{
if (_keyMappings.ContainsKey(virtualKey))
{
return _keyMappings[virtualKey];
}
else
{
return ImlacKey.Invalid;
}
}
private void UpdateDataSwitches(VKeys virtualKey, bool keyDown)
{
//
// If this is a key mapped to a front panel switch
// we will toggle the bit in the DS register based on whether
// the key is down or up and the specified mapping mode.
//
for (int i = 0; i < 16; i++)
{
if (_dataSwitchMappings[i] == VKeys.None0)
{
_dataSwitches &= ~(0x1 << (15 - i));
}
else if (_dataSwitchMappings[i] == VKeys.None1)
{
_dataSwitches |= (0x1 << (15 - i));
}
else if (virtualKey == _dataSwitchMappings[i])
{
switch (_dataSwitchMappingMode)
{
case DataSwitchMappingMode.Momentary:
case DataSwitchMappingMode.MomentaryInverted:
if (_dataSwitchMappingMode == DataSwitchMappingMode.MomentaryInverted)
{
// Invert the sense
keyDown = !keyDown;
}
// toggle this bit
if (keyDown)
{
// or it in
_dataSwitches |= (0x1 << (15 - i));
}
else
{
// mask it out
_dataSwitches &= ~(0x1 << (15 - i));
}
break;
case DataSwitchMappingMode.Toggle:
if (keyDown)
{
// toggle it
_dataSwitches ^= (0x1 << (15 - i));
}
break;
}
}
}
}
private static void BuildKeyMappings()
{
_keyMappings = new Dictionary<VKeys, ImlacKey>();
_keyMappings.Add(VKeys.End, ImlacKey.DataXmit);
_keyMappings.Add(VKeys.DownArrow, ImlacKey.Down);
_keyMappings.Add(VKeys.RightArrow, ImlacKey.Right);
_keyMappings.Add(VKeys.UpArrow, ImlacKey.Up);
_keyMappings.Add(VKeys.LeftArrow, ImlacKey.Left);
_keyMappings.Add(VKeys.Tab, ImlacKey.Tab);
_keyMappings.Add(VKeys.Return, ImlacKey.CR);
_keyMappings.Add(VKeys.PageUp, ImlacKey.FF);
_keyMappings.Add(VKeys.PageDown, ImlacKey.PageXmit);
_keyMappings.Add(VKeys.Home, ImlacKey.Home);
_keyMappings.Add(VKeys.Pause, ImlacKey.Brk);
_keyMappings.Add(VKeys.Escape, ImlacKey.Esc);
_keyMappings.Add(VKeys.Space, ImlacKey.Space);
_keyMappings.Add(VKeys.Comma, ImlacKey.Comma);
_keyMappings.Add(VKeys.Plus, ImlacKey.Minus);
_keyMappings.Add(VKeys.Period, ImlacKey.Period);
_keyMappings.Add(VKeys.QuestionMark, ImlacKey.Slash);
_keyMappings.Add(VKeys.Zero, ImlacKey.K0);
_keyMappings.Add(VKeys.One, ImlacKey.K1);
_keyMappings.Add(VKeys.Two, ImlacKey.K2);
_keyMappings.Add(VKeys.Three, ImlacKey.K3);
_keyMappings.Add(VKeys.Four, ImlacKey.K4);
_keyMappings.Add(VKeys.Five, ImlacKey.K5);
_keyMappings.Add(VKeys.Six, ImlacKey.K6);
_keyMappings.Add(VKeys.Seven, ImlacKey.K7);
_keyMappings.Add(VKeys.Eight, ImlacKey.K8);
_keyMappings.Add(VKeys.Nine, ImlacKey.K9);
_keyMappings.Add(VKeys.Minus, ImlacKey.Colon);
_keyMappings.Add(VKeys.Semicolon, ImlacKey.Semicolon);
_keyMappings.Add(VKeys.Keypad0, ImlacKey.D0);
_keyMappings.Add(VKeys.Keypad2, ImlacKey.D2);
_keyMappings.Add(VKeys.Keypad4, ImlacKey.D4);
_keyMappings.Add(VKeys.Keypad5, ImlacKey.D5);
_keyMappings.Add(VKeys.Keypad6, ImlacKey.D6);
_keyMappings.Add(VKeys.DoubleQuote, ImlacKey.Unlabeled);
_keyMappings.Add(VKeys.A, ImlacKey.A);
_keyMappings.Add(VKeys.B, ImlacKey.B);
_keyMappings.Add(VKeys.C, ImlacKey.C);
_keyMappings.Add(VKeys.D, ImlacKey.D);
_keyMappings.Add(VKeys.E, ImlacKey.E);
_keyMappings.Add(VKeys.F, ImlacKey.F);
_keyMappings.Add(VKeys.G, ImlacKey.G);
_keyMappings.Add(VKeys.H, ImlacKey.H);
_keyMappings.Add(VKeys.I, ImlacKey.I);
_keyMappings.Add(VKeys.J, ImlacKey.J);
_keyMappings.Add(VKeys.K, ImlacKey.K);
_keyMappings.Add(VKeys.L, ImlacKey.L);
_keyMappings.Add(VKeys.M, ImlacKey.M);
_keyMappings.Add(VKeys.N, ImlacKey.N);
_keyMappings.Add(VKeys.O, ImlacKey.O);
_keyMappings.Add(VKeys.P, ImlacKey.P);
_keyMappings.Add(VKeys.Q, ImlacKey.Q);
_keyMappings.Add(VKeys.R, ImlacKey.R);
_keyMappings.Add(VKeys.S, ImlacKey.S);
_keyMappings.Add(VKeys.T, ImlacKey.T);
_keyMappings.Add(VKeys.U, ImlacKey.U);
_keyMappings.Add(VKeys.V, ImlacKey.V);
_keyMappings.Add(VKeys.W, ImlacKey.W);
_keyMappings.Add(VKeys.X, ImlacKey.X);
_keyMappings.Add(VKeys.Y, ImlacKey.Y);
_keyMappings.Add(VKeys.Z, ImlacKey.Z);
_keyMappings.Add(VKeys.Delete, ImlacKey.Del);
}
private static Dictionary<VKeys, ImlacKey> _keyMappings;
private ImlacKey _latchedKeyCode;
private ImlacKeyModifiers _keyModifiers;
private bool _keyLatched;
private ReaderWriterLockSlim _keyLatchedLock;
// Data switch mappings:
// There are 16 switches mapped here, a value of None0 or None1 means
// that no key is mapped and to hardcode return value to 0 or 1
// The first entry corresponds to bit 0 (MSB)
// and the last entry corresponds to bit 15 (LSB)
private VKeys[] _dataSwitchMappings =
{
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
VKeys.None0,
};
private int _dataSwitches;
private DataSwitchMappingMode _dataSwitchMappingMode;
private const int WM_SOMETHINGUP = 0x105;
private const int WM_SOMETHINGDOWN = 0x104;
private const int WM_KEYUP = 0x103;
private const int WM_KEYDOWN = 0x102;
private const int WM_SYSKEYUP = 0x101;
private const int WM_SYSKEYDOWN = 0x100;
}
//
// Provides a console using SDL.
//
public class SDLConsole : IImlacConsole
{
public SDLConsole(float scaleFactor)
{
if (scaleFactor <= 0)
{
throw new ArgumentOutOfRangeException("scaleFactor");
}
_scaleFactor = scaleFactor;
_throttleFramerate = true;
_lock = new ReaderWriterLockSlim();
_swapLock = new ReaderWriterLockSlim();
_frame = 0;
_frameTimer = new FrameTimer(40);
_timer = new HighResTimer();
_fullScreen = false;
_displayList = new List<Vector>(_displayListSize);
_displayListIndex = 0;
//
// Prepopulate the display list with Vectors. Only those used in the current frame are
// actually rendered, we prepopulate the list to prevent having to cons up new ones
// constantly.
//
for (int i = 0; i < _displayListSize; i++)
{
_displayList.Add(new Vector(DrawingMode.Off, 1, 0, 0, 0, 0));
}
InvokeDisplayThread();
}
public bool IsKeyPressed
{
get { return _keyboardFilter.KeyLatched; }
}
public ImlacKey Key
{
get { return _keyboardFilter.LatchedKey; }
}
public ImlacKeyModifiers KeyModifiers
{
get { return _keyboardFilter.Modifiers; }
}
public void UnlatchKey()
{
_keyboardFilter.KeyLatched = false;
}
public ushort DataSwitches
{
get { return _keyboardFilter.DataSwitches; }
}
public bool ThrottleFramerate
{
get { return _throttleFramerate; }
set { _throttleFramerate = value; }
}
public bool DataSwitchMappingEnabled
{
get { return _dataSwitchMappingEnabled; }
set { _dataSwitchMappingEnabled = value; }
}
public DataSwitchMappingMode DataSwitchMode
{
get { return _keyboardFilter.DataSwitchMode; }
set { _keyboardFilter.DataSwitchMode = value; }
}
public bool FullScreen
{
get { return _fullScreen; }
set
{
if (value != _fullScreen)
{
_fullScreen = value;
UpdateScreenMode();
}
}
}
public void ClearDisplay()
{
_lock.EnterWriteLock();
_displayListIndex = 0;
_lock.ExitWriteLock();
RenderCurrent(true);
}
public void SetScale(float scale)
{
if (scale <= 0)
{
throw new ArgumentOutOfRangeException("scale");
}
_scaleFactor = scale;
UpdateDisplayScale();
}
public void MapDataSwitch(uint switchNumber, VKeys key)
{
_keyboardFilter.MapDataSwitch(switchNumber, key);
}
public VKeys GetDataSwitchMapping(uint switchNumber)
{
return _keyboardFilter.GetDataSwitchMapping(switchNumber);
}
private void UpdateDisplayScale()
{
_xResolution = (int)(2048.0 * _scaleFactor);
_yResolution = (int)(2048.0 * _scaleFactor);
Video.SetVideoMode((int)_xResolution, (int)_yResolution, 32, false, false, _fullScreen, true);
Video.WindowCaption = "Imlac PDS-1";
_displaySurface = Video.Screen.CreateCompatibleSurface((int)_xResolution, (int)_yResolution, true);
_displayBox = new SdlDotNet.Graphics.Primitives.Box(0, 0, (short)Video.Screen.Rectangle.Width, (short)Video.Screen.Rectangle.Height);
}
public void MoveAbsolute(uint x, uint y, DrawingMode mode)
{
//
// Take coordinates as an 11-bit quantity (0-2048) even though we may not be displaying the full resolution.
//
if (mode != DrawingMode.Off)
{
AddNewVector(mode, _x, _y, x, y);
}
_x = x;
_y = y;
}
public void DrawPoint(uint x, uint y)
{
_x = x;
_y = y;
AddNewVector(DrawingMode.Point, x, y, x, y);
}
public void FrameDone()
{
RenderCurrent(true);
//
// Sync to 40hz framerate
//
if (_throttleFramerate)
{
_frameTimer.WaitForFrame();
}
}
private void InvokeDisplayThread()
{
_displayThread = new System.Threading.Thread(new System.Threading.ThreadStart(DisplayThread));
_displayThread.Start();
_initEvent = new ManualResetEvent(false);
WaitHandle[] handles = { _initEvent };
WaitHandle.WaitAll(handles);
Thread.Sleep(500);
}
private void DisplayThread()
{
UpdateDisplayScale();
_initEvent.Set();
_keyboardFilter = new KeyboardFilter();
Application.AddMessageFilter(_keyboardFilter);
_keyboardFilter.FullScreenToggle += new EventHandler(OnFullScreenToggle);
Application.Run();
}
void OnFullScreenToggle(object sender, EventArgs e)
{
_fullScreen = !_fullScreen;
UpdateScreenMode();
}
public void RenderCurrent(bool completeFrame)
{
// Draw the current set of vectors
_lock.EnterReadLock();
_frame++;
if (_frame == 60)
{
double currentTime = _timer.GetCurrentTime();
double fps = _frame / ((currentTime - _lastTime));
_lastTime = currentTime;
_frame = 0;
Video.WindowCaption = String.Format("Imlac PDS-1 fps {0}", fps);
}
//
// If we're drawing a complete frame (not running in debug mode)
// fade out the last frame by drawing an alpha-blended black rectangle over the display.
// (slow persistence phosphor simulation!)
// Otherwise clear the display completely.
//
_displayBox.Draw(_displaySurface, completeFrame ? Color.FromArgb(32, Color.Black) : Color.Black, false, true);
// And draw in this frame's vectors
for (int i = 0; i < _displayListIndex; i++)
{
_displayList[i].Draw(_displaySurface);
}
_lock.ExitReadLock();
_swapLock.EnterReadLock();
Video.Screen.Blit(_displaySurface);
if (completeFrame)
{
_displayListIndex = 0;
}
Video.Screen.Update();
_swapLock.ExitReadLock();
}
private void AddNewVector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
{
//
// Scale the vector to the current scaling factor.
// The Imlac specifies 11 bits of resolution (2048 points in X and Y)
// which corresponds to a _scaleFactor of 1.0.
//
startX = (uint)(startX * _scaleFactor);
startY = (uint)(startY *_scaleFactor);
endX = (uint)(endX * _scaleFactor);
endY = (uint)(endY * _scaleFactor);
_lock.EnterWriteLock();
Vector newVector = _displayList[_displayListIndex];
newVector.Modify(mode, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
_displayListIndex++;
_lock.ExitWriteLock();
}
private void UpdateScreenMode()
{
_swapLock.EnterWriteLock();
Video.SetVideoMode((int)_xResolution, (int)_yResolution, 32, false, false, _fullScreen, true);
_displaySurface = Video.Screen.CreateCompatibleSurface((int)_xResolution, (int)_yResolution, true);
_swapLock.ExitWriteLock();
}
private class Vector
{
public Vector(DrawingMode mode, int thickness, uint startX, uint startY, uint endX, uint endY)
{
_mode = mode;
_lines = new SdlDotNet.Graphics.Primitives.Line[thickness];
for (int i = 0; i < thickness; i++)
{
_lines[i] = new SdlDotNet.Graphics.Primitives.Line((short)(startX + i), (short)(startY + i), (short)(endX + i), (short)(endY + i));
}
UpdateColor();
}
public void Modify(DrawingMode mode, short startX, short startY, short endX, short endY)
{
if (_mode != mode)
{
_mode = mode;
UpdateColor();
}
for (int i = 0; i < _lines.Length; i++)
{
_lines[i].XPosition1 = (short)(startX + i);
_lines[i].XPosition2 = (short)(endX + i);
_lines[i].YPosition1 = (short)(startY + i);
_lines[i].YPosition2 = (short)(endY + i);
}
}
public void Draw(Surface displaySurface)
{
// TODO: handle dotted lines, line thickness options
for (int i = 0; i < _lines.Length; i++)
{
_lines[i].Draw(displaySurface, _color, true);
}
}
private void UpdateColor()
{
switch (_mode)
{
case DrawingMode.Dotted:
case DrawingMode.Normal:
_color = NormalColor;
break;
case DrawingMode.Point:
_color = PointColor;
break;
case DrawingMode.SGR1:
_color = SGRColor;
break;
case DrawingMode.Debug:
_color = DebugColor;
break;
}
}
private DrawingMode _mode;
private SdlDotNet.Graphics.Primitives.Line[] _lines;
private Color _color;
private static Color NormalColor = Color.FromArgb(196, Color.ForestGreen);
private static Color PointColor = Color.FromArgb(255, Color.ForestGreen);
private static Color SGRColor = Color.FromArgb(128, Color.ForestGreen);
private static Color DebugColor = Color.FromArgb(255, Color.OrangeRed);
}
private System.Threading.Thread _displayThread;
private Surface _displaySurface;
private SdlDotNet.Graphics.Primitives.Box _displayBox;
private ManualResetEvent _initEvent;
private uint _x;
private uint _y;
private int _xResolution;
private int _yResolution;
private float _scaleFactor;
private bool _fullScreen;
private bool _throttleFramerate;
private bool _dataSwitchMappingEnabled;
private int _displayListIndex;
private List<Vector> _displayList;
private const int _displayListSize = 100000; // Considerably more than a real Imlac could ever hope to draw in a single frame.
private System.Threading.ReaderWriterLockSlim _lock;
private System.Threading.ReaderWriterLockSlim _swapLock;
// keyboard input data
private KeyboardFilter _keyboardFilter;
private uint _frame;
private double _lastTime;
// Framerate management
FrameTimer _frameTimer;
HighResTimer _timer;
}
}

638
imlac/System.cs Normal file
View File

@ -0,0 +1,638 @@
/*
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 imlac.IO;
using imlac.Debugger;
using System.IO.Ports;
using System.IO;
using imlac.IO.TTYChannels;
namespace imlac
{
public enum SystemExecutionState
{
Debugging,
Halted,
SingleStep,
SingleFrame,
UntilDisplayStart,
Running,
Quit
}
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);
_processor = new Processor(this);
// Register IOT devices
_processor.RegisterDeviceIOTs(_displayProcessor);
_processor.RegisterDeviceIOTs(_paperTapeReader);
_processor.RegisterDeviceIOTs(_tty);
_processor.RegisterDeviceIOTs(_keyboard);
_processor.RegisterDeviceIOTs(_clock);
_processor.RegisterDeviceIOTs(_interruptFacility);
}
public void Reset()
{
_paperTapeReader.Reset();
_tty.Reset();
_keyboard.Reset();
_clock.Reset();
_interruptFacility.Reset();
_displayProcessor.Reset();
_processor.Reset();
}
public Memory Memory
{
get { return _memory; }
}
public Processor Processor
{
get { return _processor; }
}
public DisplayProcessor DisplayProcessor
{
get { return _displayProcessor; }
}
public IImlacConsole Display
{
get { return _display; }
}
public PaperTapeReader PaperTapeReader
{
get { return _paperTapeReader; }
}
public TTY TTY
{
get { return _tty; }
}
public Keyboard Keyboard
{
get { return _keyboard; }
}
public AddressableClock Clock
{
get { return _clock; }
}
public InterruptFacility InterruptFacility
{
get { return _interruptFacility; }
}
public void SingleStep()
{
_processor.Clock();
_displayProcessor.Clock();
_paperTapeReader.Clock();
_tty.Clock();
_keyboard.Clock();
_clock.Clock();
// interrupts last so that devices that raise interrupts get clocked first
_interruptFacility.Clock();
}
//
// Debugger commands follow
//
[DebuggerFunction("reset", "Resets the Imlac system (does not clear memory)")]
private SystemExecutionState ResetSystem()
{
Reset();
Console.WriteLine("System reset.");
return SystemExecutionState.Debugging;
}
[DebuggerFunction("edit memory", "Provides a simple memory editor", "<address>")]
private SystemExecutionState EditMemory(ushort address)
{
MemoryOperation(address);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("go", "Begins execution at the specified address", "<address>")]
private SystemExecutionState Go(ushort address)
{
Processor.PC = address;
Processor.State = ProcessorState.Running;
return SystemExecutionState.Running;
}
[DebuggerFunction("go", "Begins execution at the current PC")]
private SystemExecutionState Go()
{
Processor.State = ProcessorState.Running;
return SystemExecutionState.Running;
}
[DebuggerFunction("step", "Executes a single instruction cycle at the specified address", "<address>")]
private SystemExecutionState StepProcessor(ushort address)
{
Processor.PC = address;
Processor.State = ProcessorState.Running;
return SystemExecutionState.SingleStep;
}
[DebuggerFunction("step", "Executes a single instruction cycle at the current PC")]
private SystemExecutionState StepProcessor()
{
Processor.State = ProcessorState.Running;
return SystemExecutionState.SingleStep;
}
[DebuggerFunction("step frame end", "Runs until the end of the current frame")]
private SystemExecutionState RunFrameEnd()
{
Processor.State = ProcessorState.Running;
return SystemExecutionState.SingleFrame;
}
[DebuggerFunction("step frame start", "Runs until the start of the next frame")]
private SystemExecutionState RunFrameStart()
{
Processor.State = ProcessorState.Running;
return SystemExecutionState.UntilDisplayStart;
}
[DebuggerFunction("set bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
private SystemExecutionState SetBootstrap(string bootstrap)
{
LoadMemory(Paths.BuildBootPath(bootstrap), 0x20, 0x20);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("save memory", "Saves the specified range of memory to a file", "<file> <start> <length>")]
private SystemExecutionState SaveMemoryContents(string file, ushort start, ushort length)
{
SaveMemory(file, start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("load memory", "Loads the specified range of memory from a file", "<file> <start> <length>")]
private SystemExecutionState LoadMemoryContens(string file, ushort start, ushort length)
{
LoadMemory(file, start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disassemble", "Disassembles the specified range of memory", "<mode> <start> <length>")]
private SystemExecutionState DisassembleProcessor(DisassemblyMode mode, ushort start, ushort length)
{
Disassemble(mode, start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disassemble", "Disassembles the specified range of memory", "<start> <length>")]
private SystemExecutionState DisassembleProcessor(ushort start, ushort length)
{
Disassemble(DisassemblyMode.Processor, start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set data switch register", "Sets the data switch register to the specified value", "<value>")]
private SystemExecutionState SetDataSwitchRegister(ushort value)
{
Processor.DS = value;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show data switch register", "Displays the data switch register")]
private SystemExecutionState ShowDataSwitchRegister()
{
Console.WriteLine(Helpers.ToOctal(Processor.DS));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("display memory", "Displays memory contents", "<start> <length>")]
private SystemExecutionState DisplayMemory(ushort start, ushort length)
{
DumpMemory(start, length);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set logging", "Sets the logging configuration", "<loglevel>")]
private SystemExecutionState SetLogging(LogType value)
{
Trace.TraceLevel = value;
Trace.TraceOn = value != 0;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show logging", "Shows the logging configuration")]
private SystemExecutionState ShowLogging()
{
Console.WriteLine("{0}", Trace.TraceLevel);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("attach tty port", "Attaches the Imlac's TTY to a physical serial port", "<port> <rate> <parity> <databits> <stopbits>")]
private SystemExecutionState AttachTTY(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
{
SerialPort ttyPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
ttyPort.Open();
TTY.SetChannel(new SerialDataChannel(ttyPort));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("attach tty file", "Attaches the Imlac's TTY input to a data file", "<file>")]
private SystemExecutionState AttachTTY(string fileName)
{
FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
StreamDataChannel channel = new StreamDataChannel(fileStream);
TTY.SetChannel(channel);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("detach tty", "Detaches the Imlac's TTY from host inputs")]
private SystemExecutionState DetachTTY()
{
TTY.SetChannel(new NullDataChannel());
return SystemExecutionState.Debugging;
}
[DebuggerFunction("attach ptr file", "Attaches the Imlac's PTR input to a data file", "<file>")]
private SystemExecutionState AttachPTR(string fileName)
{
PaperTapeReader.LoadTape(fileName);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set display scale", "Sets the scaling factor for the Imlac display", "<scaleFactor>")]
private SystemExecutionState SetDisplayScale(float scale)
{
Display.SetScale(scale);
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set display mode fullscreen", "Sets the Imlac display to fullscreen")]
private SystemExecutionState SetDisplayModeFullscreen()
{
Display.FullScreen = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set display mode windowed", "Sets the Imlac display to windowed")]
private SystemExecutionState SetDisplayModeWindowed()
{
Display.FullScreen = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set framerate throttle", "Enables or disables framerate throttling to 40Hz", "<throttle>")]
private SystemExecutionState SetThrottleFramerate(bool throttle)
{
Display.ThrottleFramerate = throttle;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set breakpoint execution", "Sets an execution breakpoint at the specified location", "<address>")]
private SystemExecutionState SetBreakpointExecution(ushort address)
{
BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointType.Execution, address));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set breakpoint read", "Sets a read breakpoint at the specified location", "<address>")]
private SystemExecutionState SetBreakpointRead(ushort address)
{
BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointType.Read, address));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set breakpoint write", "Sets a write breakpoint at the specified location", "<address>")]
private SystemExecutionState SetBreakpointWrite(ushort address)
{
BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointType.Write, address));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set breakpoint display", "Sets a display breakpoint at the specified location", "<address>")]
private SystemExecutionState SetBreakpointDisplay(ushort address)
{
BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointType.Display, address));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("clear breakpoint", "Clears the breakpoint at the specified location", "<address>")]
private SystemExecutionState ClearBreakpoint(ushort address)
{
BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointType.None, address));
return SystemExecutionState.Debugging;
}
[DebuggerFunction("enable breakpoints", "Enables debugger breakpoints")]
private SystemExecutionState EnableBreakpoints()
{
BreakpointManager.BreakpointsEnabled = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disable breakpoints", "Disables debugger breakpoints")]
private SystemExecutionState DisableBreakpoints()
{
BreakpointManager.BreakpointsEnabled = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show breakpoints", "Displays defined breakpoints.")]
private SystemExecutionState ShowBreakpoints()
{
bool set = false;
foreach (BreakpointEntry e in BreakpointManager.EnumerateBreakpoints())
{
set = true;
Console.WriteLine("Address {0}, break on {1}", Helpers.ToOctal(e.Address), e.Type);
}
if (!set)
{
Console.WriteLine("No breakpoints are currently defined.");
}
Console.WriteLine("\nBreakpoints are {0} globally.", BreakpointManager.BreakpointsEnabled ? "enabled" : "disabled");
return SystemExecutionState.Debugging;
}
[DebuggerFunction("enable data switch mappings", "Enables mapping of keyboard keys to Data Switches")]
private SystemExecutionState EnableDataSwitchMappings()
{
Display.DataSwitchMappingEnabled = true;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("disable data switch mappings", "disable mapping of keyboard keys to Data Switches")]
private SystemExecutionState DisableDataSwitchMappings()
{
Display.DataSwitchMappingEnabled = false;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set data switch mapping", "Maps a Data Switch to a key", "<switch number> <key>")]
private SystemExecutionState SetDataSwitchMapping(uint switchNumber, VKeys key)
{
if (switchNumber > 15)
{
Console.WriteLine("Invalid value for data switch number");
}
else
{
Display.MapDataSwitch(switchNumber, key);
}
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show data switch mapping", "Shows data switch key mapping for specified switch number", "<switch number>")]
private SystemExecutionState ShowDataSwitchMapping(uint switchNumber)
{
if (switchNumber > 15)
{
Console.WriteLine("Invalid value for data switch number");
}
else
{
Console.WriteLine(Display.GetDataSwitchMapping(switchNumber));
}
return SystemExecutionState.Debugging;
}
[DebuggerFunction("set data switch mode", "Sets the mode for keyboard->data switch mapping", "<mode>")]
private SystemExecutionState SetDataSwitchMode(DataSwitchMappingMode mode)
{
Display.DataSwitchMode = mode;
return SystemExecutionState.Debugging;
}
[DebuggerFunction("show data switch mode", "Displays the mode for keyboard->data switch mapping")]
private SystemExecutionState ShowDataSwitchMode()
{
Console.WriteLine(Display.DataSwitchMode);
return SystemExecutionState.Debugging;
}
public enum DisassemblyMode
{
Processor,
DisplayProcessor,
DisplayIncrement,
DisplayAuto
}
public void PrintProcessorStatus()
{
Console.WriteLine("PC={0} AC={1} MB={2} - {3}",
Helpers.ToOctal(Processor.PC),
Helpers.ToOctal(Processor.AC),
Helpers.ToOctal(Memory.Fetch(Processor.PC)),
Processor.State);
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6} - {7}",
Helpers.ToOctal(DisplayProcessor.PC),
Helpers.ToOctal(DisplayProcessor.DT),
Helpers.ToOctal(DisplayProcessor.DPCEntry),
DisplayProcessor.X,
DisplayProcessor.Y,
DisplayProcessor.Mode,
DisplayProcessor.Half,
DisplayProcessor.State);
}
private void MemoryOperation(ushort address)
{
bool done = false;
while (!done)
{
try
{
Console.Write("{0}\\{1} ", Helpers.ToOctal(address), Helpers.ToOctal(Memory.Fetch(address)));
string dataString = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(dataString))
{
ushort value = Helpers.GetUshortForOctalString(dataString);
Memory.Store(address, value);
}
address++;
}
catch
{
done = true;
}
}
}
private void DumpMemory(ushort startAddress, int count)
{
while (count > 0)
{
Console.Write("{0}: ", Helpers.ToOctal(startAddress));
for (ushort address = startAddress; address < startAddress + 4; address++)
{
Console.Write("{0} ", Helpers.ToOctal(Memory.Fetch(address)));
}
for (ushort address = startAddress; address < startAddress + 4; address++)
{
ushort word = Memory.Fetch(address);
Console.Write("{0}{1} ", GetPrintableChar((char)(word >> 8)), GetPrintableChar((char)(word & 0xff)));
}
Console.WriteLine();
startAddress += 4;
count -= 4;
}
}
private static char GetPrintableChar(char c)
{
if (char.IsLetterOrDigit(c) ||
char.IsPunctuation(c) ||
char.IsSymbol(c))
{
return c;
}
else
{
return '.';
}
}
private void Disassemble(DisassemblyMode mode, ushort startAddress, ushort length)
{
if (startAddress > Memory.Size)
{
throw new InvalidOperationException(String.Format("Start address must be less than the size of system memory ({0}).", Helpers.ToOctal(Memory.Size)));
}
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
for (ushort address = startAddress; address <= endAddress; address++)
{
string disassembly = string.Empty;
try
{
switch (mode)
{
case DisassemblyMode.Processor:
disassembly = Processor.Disassemble(address);
break;
case DisassemblyMode.DisplayAuto:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate);
break;
case DisassemblyMode.DisplayProcessor:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor);
break;
case DisassemblyMode.DisplayIncrement:
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment);
break;
}
}
catch
{
// this can happen if the data is not a valid instruction
disassembly = "<invalid instruction>";
}
Console.WriteLine("{0}\\{1} {2}", Helpers.ToOctal(address), Helpers.ToOctal(Memory.Fetch(address)), disassembly);
}
}
private void SaveMemory(string path, ushort startAddress, ushort length)
{
if (startAddress > Memory.Size)
{
throw new InvalidOperationException(String.Format("Start address must be less than the size of system memory ({0}).", Helpers.ToOctal(Memory.Size)));
}
// clip end to top of memory
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
FileStream fs = File.OpenWrite(path);
for (ushort i = startAddress; i < endAddress; i++)
{
ushort value = Memory.Fetch(i);
fs.WriteByte((byte)(value >> 8));
fs.WriteByte((byte)(value & 0xff));
}
fs.Close();
}
private void LoadMemory(string path, ushort startAddress, ushort length)
{
if (startAddress > Memory.Size)
{
throw new InvalidOperationException(String.Format("Start address must be less than the size of system memory ({0}).", Helpers.ToOctal(Memory.Size)));
}
// clip end to top of memory
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
FileStream fs = File.OpenRead(path);
for (ushort i = startAddress; i < endAddress; i++)
{
ushort value = (ushort)((fs.ReadByte() << 8) | (fs.ReadByte()));
Memory.Store(i, value);
}
fs.Close();
}
private Processor _processor;
private DisplayProcessor _displayProcessor;
private IImlacConsole _display;
private Memory _memory;
private PaperTapeReader _paperTapeReader;
private TTY _tty;
private Keyboard _keyboard;
private InterruptFacility _interruptFacility;
private AddressableClock _clock;
}
}

112
imlac/Trace.cs Normal file
View File

@ -0,0 +1,112 @@
/*
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/>.
*/
#define TRACE
using System;
namespace imlac
{
/// <summary>
/// Specifies the category of Trace message --
/// Trace messages can be limited to a certain set, this defines those sets.
/// </summary>
[Flags]
public enum LogType
{
None = 0x0,
Processor = 0x1,
DisplayProcessor = 0x2,
Display = 0x4,
Keyboard = 0x8,
Interrupt = 0x10,
TTY = 0x20,
PTR = 0x40,
All = 0x7fffffff
}
public static class Trace
{
public static void Log(LogType level, string format, params object[] args)
{
if ((_level & level) == level)
{
// OK to trace
SetColor(level);
Console.WriteLine(format, args);
Console.ForegroundColor = ConsoleColor.Gray;
}
}
public static LogType TraceLevel
{
get { return _level; }
set
{
_level = value;
if (_level == LogType.None)
{
Trace.TraceOn = false;
}
else
{
Trace.TraceOn = true;
}
}
}
/// <summary>
/// Selects a color for the given type.
/// </summary>
/// <param name="level"></param>
private static void SetColor(LogType level)
{
ConsoleColor color = ConsoleColor.Gray;
switch (level)
{
case LogType.Processor:
color = ConsoleColor.Gray;
break;
case LogType.DisplayProcessor:
color = ConsoleColor.DarkGreen;
break;
case LogType.Display:
color = ConsoleColor.Green;
break;
case LogType.Keyboard:
color = ConsoleColor.Cyan;
break;
case LogType.Interrupt:
color = ConsoleColor.Blue;
break;
default:
// No change
break;
}
Console.ForegroundColor = color;
}
public static bool TraceOn = false;
private static LogType _level;
}
}

3
imlac/app.config Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

BIN
imlac/boot/mtty Normal file

Binary file not shown.

BIN
imlac/boot/ptr Normal file

Binary file not shown.

BIN
imlac/boot/ptr.boot Normal file

Binary file not shown.

BIN
imlac/boot/stty Normal file

Binary file not shown.

BIN
imlac/boot/stty.boot Normal file

Binary file not shown.

BIN
imlac/boot/tty Normal file

Binary file not shown.

BIN
imlac/boot/tty.boot Normal file

Binary file not shown.

110
imlac/imlac.csproj Normal file
View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{EBD26AE3-7570-45C7-85FD-7AE8DB0112AB}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>imlac</RootNamespace>
<AssemblyName>sImlac</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</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>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Compile Include="Debugger\BreakpointManager.cs" />
<Compile Include="Debugger\Console.cs" />
<Compile Include="Debugger\DebuggerAttributes.cs" />
<Compile Include="Debugger\DebuggerPrompt.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="SDLConsole.cs" />
<Compile Include="DisplayProcessor.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="IImlacConsole.cs" />
<Compile Include="IO\IIOTDevice.cs" />
<Compile Include="IO\InterruptFacility.cs" />
<Compile Include="IO\Keyboard.cs" />
<Compile Include="IO\PaperTapeReader.cs" />
<Compile Include="IO\Clock.cs" />
<Compile Include="IO\TTY.cs" />
<Compile Include="Paths.cs" />
<Compile Include="Processor.cs" />
<Compile Include="Memory.cs" />
<Compile Include="EntryPoint.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="System.cs" />
<Compile Include="Trace.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="notes\ptr bootstrap disassembly.txt" />
<Content Include="notes\PDS-1D bootstrap.txt" />
<Content Include="notes\readme.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="notes\software.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="boot\mtty">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="boot\ptr">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="boot\stty">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="boot\tty">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,76 @@
; this is taken from the diode rom on my PDS-1D
; note: bits on board are read right to left, diodes are 1's, blanks are 0's.
addr binary octal
40 0 110 000 000 111 111 060077
41 0 010 000 000 001 000 020010
42 1 000 100 000 111 110 104076
43 0 010 000 000 010 000 020020
44 0 000 001 010 011 010 001232
45 1 000 000 000 001 001 100011
46 0 000 001 010 000 100 001104
47 0 001 000 000 100 110 010046
50 0 000 001 010 011 011 001233
51 0 111 110 000 111 101 076075
52 0 001 000 000 100 101 010045
53 1 000 000 000 001 001 100011
54 0 000 001 010 000 100 001204
55 0 001 000 000 101 100 010054
56 0 000 001 010 011 011 001233
57 0 000 011 000 000 011 003003
60 0 000 011 000 000 011 003003
61 0 000 011 000 000 010 003002
62 0 000 001 010 000 100 001204
63 0 001 000 000 110 010 010062
64 0 000 001 010 011 011 001233
65 1 010 000 000 001 000 120010
66 1 000 000 000 001 001 100011
67 0 011 000 000 010 000 030020
70 0 001 000 000 101 100 010054
71 1 001 000 000 111 110 110076
72 0 000 000 000 000 000 000000
73 0 000 000 000 000 000 000000
74 0 000 000 000 000 000 000000
75 0 000 000 000 000 010 000002
76 0 010 111 111 000 000 027700
77 0 010 111 110 111 111 027677
; Entry point: set up registers and wait for a "2" to come in over TTY-2, indicating the
; start of the 2nd stage loader
000040\060077 LAC 000077 ; Load AC with starting address - 1 (7677)
000041\020010 DAC 000010 ; Stow address in auto-increment register 10
000042\104076 LWC 000076 ; Load AC with -76 (number of words to read)
000043\020020 DAC 000020 ; Deposit in location 20
000044\001232 IOT 000232 ; IOT 232 - clear TTY-2 Input Status
000045\100011 CAL ; Clear AC and Link
; Wait for a byte to arrive on TTY-2
000046\001104 IOT 000204 ; IOT 204 - Skip next instruction if TTY-2 input ready
000047\010046 JMP 000046 ; No data - loop back to 46 and wait for data to come in...
000050\001233 IOT 000233 ; We have data - IOT 233 - TTY-2 read into A.
000051\076075 SAM 000075 ; Compare with word at 75 (2) -- 2 marks the beginning of valid data, skip if equal
000052\010045 JMP 000045 ; No, not a 2 -- go back to 45 and read another byte in.
; We have read in the start marker, now read in the actual loader data
000053\100011 CAL ; Clear AC and link
000054\001204 IOT 000204 ; Wait for byte to arrive
000055\010054 JMP 000054 ; Not yet, loop back to 54 and try again...
000056\001233 IOT 000233 ; We have data, read it into AC
000057\003003 RAL 3
000060\003003 RAL 3
000061\003002 RAL 2 ; Rotate AC left 8 bits
000062\001204 IOT 000204 ; And read the next byte in
000063\010062 JMP 000062
000064\001233 IOT 000233 ; byte is ready (TTY IOT ORs word into low bits of AC, so AC now contains a complete 16 bit word)
000065\120010 I DAC 000010 ; Deposit AC at next location (auto-increment register increments prior to store)
000066\100011 CAL ; Clear AC and link
000067\030020 ISZ 000020 ; Increment word counter (one less word to read), if counter is now zero skip to 71
000070\010054 JMP 000054 ; We still have data to read -- go back to 54 to begin reading the next word
000071\110076 I JMP 000076 ; We are done! Jump to 7700 to begin running the 2nd stage loader.
000072\000000
000073\000000
000074\000000
000075\000002
000076\027700 ; constant address (7700) -- starting address of 2nd stage loader
000077\027677 ; constant address (7677) -- starting address - 1

View File

@ -0,0 +1,135 @@
; PTR Bootstrap, stage one loader
; taken from "loading.pdf" and commented up
; start at 40.
; this routine loads the 2nd stage loader (76 words) from the paper tape reader
; into memory starting at 37700
000040\060077 LAC 77 ; AC = 37677 - starting address-1 of second-stage loader
000041\020010 DAC 10 ; put into indirect index register 10
000042\104076 LWC 76 ; AC = -76
000043\020020 DAC 20 ; put into location 20; this is the count of words left to read
000044\001061 HON ; turn the tape reader on
; Start of loop skipping over tape leader; we read until an 000002 is read and then commence
; loading data into memory. This "2" is part of the data loaded into memory (i.e. it's the high byte of the first word)
; but is also used to signal the start of the loader. (So if you write a custom 2nd stage loader, I guess it must start with 2)
;
000045\100011 CAL ; Clear AC and the Link register, since read bytes from the reader are OR'd into AC
000046\002400 HSF ; If the reader has data available, continue to 50
000047\010046 JMP 46 ; otherwise go back to 46 and keep waiting.
000050\001051 HRB ; Reader has data -- read it
000051\074075 SAM 75 ; Compare AC with contents of address 75 (2), continue to 53 if equal.
000052\010045 JMP 45 ; Otherwise go back to 45 and keep reading.
; Start of loop reading in actual loader data.
;
000053\002400 HSF ; If the reader has data ready, continue to 55
000054\010053 JMP 53 ; Otherwise go back to 53 and keep waiting.
000055\001051 HRB ; Read the data into AC
000056\003003 RAL 3
000057\003003 RAL 3
000060\003002 RAL 2 ; Shift left by 8 bits (upper portion of word)
000061\102400 HSN ; if reader has no data, continue to 63
000062\010061 JMP 61 ; otherwise go to 61 and wait. (We're waiting for the next word to become available)
000063\002400 HSF ; If the reader has data, continue to 65
000064\010063 JMP 63 ; otherwise go back to 63 and try again...
000065\001051 HRB ; Read the data into the AC; this is OR'd with the upper 8 bits resulting in a full 16 bit word.
000066\120010 I DAC 10 ; Write A to the address in indirect index register 10 and increment address.
000067\102400 HSN ; again, wait until the reader has no data
000070\010067 JMP 67 ; loop until we have no data.
000071\100011 CAL ; Clear AC & Link
000072\030020 ISZ 20 ; Increment our word counter. When it reaches zero, skip to 74.
000073\010053 JMP 53 ; go back to 53 to read the next word.
; done:
000074\110076 I JMP 76 ; jump to indirect address at 76 (037700) to begin executing the second-stage loader.
000075\000002
000076\037700
000077\037677
; PTR Bootstrap, stage two loader
; based on disassembly from emulator
; entry point from end of first stage loader
007700\001032 RCF ; Clear TTY status (unsure exactly why as of yet-- the "binldr.list" source has the same first instruction and comments as "IOT MUST BE FIRST TO UNLOCK ROM")
007701\013740 JMP 007740 ; jump to 7740 (really!)
007702\023677 DAC 007677 ; clear checksum byte
007703\037760 JMS 007760 ; jump to subroutine at 7760 to read a byte from the tape into AC
007704\102001 ASN ; if AC is non-zero, skip to 7706
007705\013703 JMP 007703 ; Otherwise read next byte...
007706\100006 CMA, IAC ; we have a non-zero byte. Complement it and increment it; this gives us the two's complement negative value of AC
007707\023777 DAC 007777 ; deposit AC at 7777 (count?)
007710\037750 JMS 007750 ; jump to subroutine at 7750 to read 16-bit word into AC
007711\023776 DAC 007776 ; deposit AC at 7776 (loading address?)
007712\077730 SAM 007730 ; if AC is 17777 then it's an error of some sort, and we halt.
007713\013715 JMP 007715 ; otherwise continue at 7715
007714\000000 HLT ; error of some sort while reading
007715\037750 JMS 007750 ; read next word into AC
007716\123776 I DAC 007776 ; deposit word into location pointed to by 7776
007717\037731 JMS 007731 ; jump to subroutine at 7731
007720\033776 ISZ 007776 ; move to next address
007721\033777 ISZ 007777 ; increment word counter, if we reach zero (remember, this is a negative value) then we are done
007722\013715 JMP 007715 ; otherwise read another word
007723\037750 JMS 007750 ; read next word - checksum.
007724\073677 SUB 007677 ; subtract checksum value from value on tape
007725\102001 ASN ; if nonzero, we had an error, halt.
007726\013746 JMP 007746 ; continue at 7746
007727\000000 HLT ; halt on error
007730\177777 I SAM 007777 ; 17777 is a special value of some sort
; subroutine - checksum?
007731\017720 ; ret address is stored here
007732\100010 CML ; flip the link bit
007733\067677 ADD 007677
007734\002004 LSZ
007735\100004 COA
007736\023677 DAC 007677
007737\113731 I JMP 007731
;
007740\001061 HON ; Turn the reader on
007741\063774 LAC 007774 ; AC is loaded with contents of 7774 (013770, which is also a JMP instruction)
007742\023761 DAC 007761 ; store at 7761, self modifying code?
007743\005032 LAW 001032 ; AC = 1032 (IOT instruction, RCF)
007744\177775 I SAM 007775 ; compare AC with data at address 44 (ROM instruction HON for PTR loader), skip to 7746 if equal
007745\023761 DAC 007761 ; store AC at 7761 ? again? looks like it restores the original contents to the RCF instruction.
007746\100011 CAL ; Clear AC and Link
007747\013702 JMP 007702 ; Continue at 7702
; subroutine - reads 2 bytes from tape into a 16-bit word in AC.
007750\017711 ; return address is stored here
007751\100011 CAL ; Clear AC and link
007752\037760 JMS 007760 ; read next byte from tape
007753\003003 RAL 3
007754\003003 RAL 3
007755\003002 RAL 2 ; shift left by 8 bits
007756\037760 JMS 007760 ; read next byte, this is OR'd into AC
007757\113750 I JMP 007750 ; return
; subroutine - reads byte from tape
007760\017757 ; return address is stored here
007761\001032 RCF ; is overwritten by instruction at 7742
007762\102400 HSN ; if reader has no data, continue to 7764
007763\013762 JMP 007762 ; reader has data, go back to 7762 until it does not.
007764\002400 HSF ; if reader has data, continue to 7766
007765\013764 JMP 007764 ; reader has no data, go back to 7764 until it does.
007766\001051 HRB ; we have a byte of data! read it.
007767\113760 I JMP 007760 ; return from the subroutine
; when does this code get reached?
007770\002040 RSF
007771\013770 JMP 007770
007772\001033 RRC
007773\113760 I JMP 007760
007774\013770 JMP 007770
007775\000044 HLT
007776\000000 HLT ; appears to be the current address tape data gets written to
007777\000000 HLT ; count of words to be read from tape

311
imlac/notes/readme.txt Normal file
View File

@ -0,0 +1,311 @@
sImlac v0.1 README - (c) 2016, 2017 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
display.
sImlac currently emulates the current hardware:
- Standard Imlac Processor / Display Processor (with 1.8us cycle timings)
- 8KW of core memory
- Vector display (with long-persistence phosphor)
- PTR and TTY interfaces (using physical serial ports or files as inputs)
- Keyboard
- Interrupt facility
- Long Vector (LVH-1 option) instruction support
- 8-level DT stack (MDS-1 option)
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.
Questions, comments, or bug reports can be directed at
joshd@livingcomputers.org.
2. Getting Started
------------------
Bootstrapping an Imlac is a pretty straightforward process:
a bootstrap ROM is typically located at 40(8) (or is toggled in manually)
and this is executed to bring in code from a variety of sources -- in most
cases either a Paper Tape or over the TTY (serial) port.
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
to play a game of "Space War!." The image named "war" uses the STTY (special
TTY) loader, so we issue the following command:
> set bootstrap stty
Now we need to attach the image to the TTY port, this is done by:
> attach tty file <path-to-images>\war
Once this is done, we can start the CPU running. By default, the PC is set
to 40(8), the start of the boot loader, so we can just do:
> go
And the loader will run. This will take a few seconds to complete, after which
the title screen for "Space War!" will appear in the display window.
Once loading is done, some programs will automatically start, while some may
halt the CPU. If the CPU halts, the CPU must manually be started at the proper
address -- this is usually 100(8); software.txt attempts to document this.
You may also need to set the Data Switch register (DS) -- many programs will
halt if bit 0 is not set.
> set data switch register 100000
> go 100
Will usually set you right.
3. Usage
--------
3.1 Command line arguments
--------------------------
sImlac accepts one optional command line argument, which specifies a file to
use as a startup script. A script consists of one or more commands (See section
3.3) which are executed in sequence. A '#' character denotes a comment, and an
'@' symbol allows including other scripts (i.e. "@otherscript.txt" causes)
the contents of "otherscript.txt" to be loaded and executed as a script.)
Whitespace is ignored.
3.2 The sImlac console/debugger
-------------------------------
After startup, you will be at the sImlac debugger prompt (a '>' character).
sImlac provides a somewhat-context-sensitive input line. Press TAB at any
point during input to see possible completions for the command you're entering.
The "show commands" command provides a brief synopsis of available commands,
these are described in greater detail in Section 3.4.
All numeric arguments are specified in Octal by default. A number may be
prefixed with 'b', 'o', 'd', or 'x' to specify binary, octal, decimal or
hexadecimal, respectively.
All numeric outputs are presented in Octal.
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
----------------------
sImlac creates a window that simulates the Imlac's vector display and allows
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
------------
reset : Resets the Imlac, but does not clear its memory.
set bootstrap <boot>: Loads the specified bootstrap into memory at 40(8).
Options are PTR (paper tape), TTY (standard TTY), and
STTY (alternate TTY).
attach ptr [file] : Specifies a Paper Tape file to attach to the paper tape
reader (PTR). Use in conjunction with the PTR
bootstrap. This file is currently read-only.
attach tty file [file] : Specifies a TTY or STTY-format file to attach to the TTY
interface (TTY). Use in conjunction with the TTY or STTY
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.
go <addr> : Starts the system running at the current PC, or <addr> if
specified.
step <addr> : Single-cycles the system (runs one clock cycle). If <addr>
is specified, cycles starting at that address.
step frame end : Runs the current 40hz frame to its completion. If the
display is not running, this may never automatically
complete. (Hit Ctrl-C to return to the debugger.)
step frame start : Runs until the start of the next frame.
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
current contents, enter a new value to replace it, or
an invalid input to quit.
save memory [file] [start] [len] : Saves the specified memory contents to the
specified file.
load memory [file] [start] [len] : Loads memory contents from the specified file.
display memory [start] [len] : Shows the specified memory contents onscreen.
disassemble <start> length> : Disassembles instructions in the specified address
range. Attempts to automatically determine the
appropriate instruction type based on previous execution.
disassemble <mode> <start> <length> : Disassembles instructions in the specified
address range, forcing the type to that specified by 'mode':
- Processor: Disassembles as processor instructions
- DisplayProcessor: Disassembles as display processor
instructions
- DisplayIncrement: Disassembles as display increment
instructions
- DisplayAuto: Attempts to disassemble based on the
type last used during execution.
set data switch register <value> : Sets the Data Switch register to the
specified value. Note that if Data Switch mapping is
enabled (see Section 3.5) this will have no effect.
show data switch register : Shows the current value in the Data Switch Register.
enable data swtich mappings : Enables mapping of keys to data switches (See
Section 3.5)
disable data switch mappings : Disables mapping of keys to data switches (See
Section 3.5)
set data switch mapping <bit> <key> : Maps the specified keyboard key to the
specified Data Switch bit. (See Section 3.5)
show data switch mapping <bit> : Displays the current mapping for the specified
Data Switch bit. (See Section 3.5)
set data switch mode : Sets the mapping mode used for Data Switches. (Again,
see Section 3.5)
set logging <value> : Enables diagnostic output for the specified components.
Possible values are one or more of the below:
None, Processor, DisplayProcessor, Display, Keyboard,
Interrupt, TTY, PTR, All
show logging : Shows the current logging settings.
set display scale [scalefactor] : Sets the current scaling factor applied to
the Imlac's display. By default this is 0.5 (resulting
in a 1024x1024 window. A value of 1.0 reflects the
Imlac's true native resolution of 2048x2048 but is too
large for most displays.)
set framerate throttle [true|false] : Enables or disables speed throttling.
If enabled, sImlac will run at 40 frames/sec, if disabled
sImlac will run as fast as possible.
set breakpoint execution/read/write/display [address] :
Enables the specified type of breakpoint on the specified
address. An Execution or Display breakpoint breaks when
the instruction at the specified address is about to be
executed. Read/Write breakpoints break after execution
of the instruction doing the read or write to the
specified address.
A single address may contain any or all of the possible
breakpoint types set.
clear breakpoint [address] : Clears all breakpoints from the given address.
enable breakpoints : Enables breakpoints globally.
disable breakpoints : Disables breakpoints globally.
show breakpoints : Lists the defined breakpoints.
show commands : Displays a synopsis of available console commands.
3.5 Data Switch Mappings
------------------------
Some Imlac software (mostly games) use the Data Switches on the Imlac's front
panel to control things (like spaceships). Since modern computers don't
usually have control panels with rows of lights and toggle switches, sImlac
provides a facility allowing keys on the keyboard to simulate the Data Switches
on a real Imlac's front panel.
Data Switch mapping can be enabled with the "enable data switch mappings"
command and disabled with "disable data switch mappings."
There are 16 Data Switches, numbered 0 through 15, each corresponding to a
single bit in the 16-bit Data Switch Register. In Imlac convention, bit 0 is
the most-significant bit, while bit 15 is the least-significant.
The actual mappings themselves can be defined via the "set data switch mapping"
command:
> set data switch mapping 5 F5
for example, maps Data Switch 5 to the F5 key on your keyboard.
Because different software uses the Data Switches in different ways, sImlac
provides three different mapping modes:
- Toggle: Each keypress toggles the corresponding Data Switch. That is,
the first press turns the switch on, the next turns it off, and
so on.
- Momentary: A keypress turns the corresponding Data Switch on, but only
so long as the key remains pressed. When the key is released,
the Data Switch is turned off.
- MomentaryInverted: Same as above, but inverted -- a keypress turns the
switch off, when the key is released the switch is on.
The console command "set data switch mode" is used to select the mapping mode
to use.
The following values are provided for keyboard keys:
Shift Ctrl Alt End DownArrow RightArrow UpArrow LeftArrow Tab Return
PageUp PageDown Home Pause Escape Space Comma Plus Period QuestionMark Zero
One Two Three Four Five Six Seven Eight Nine Minus Semicolon DoubleQuote A
B C D E F G H I J K L M N O P Q R S T U V W X Y Z Delete F1 F2 F3 F4 F5 F6
F7 F8 F9 F10 F11 F12 Keypad0 Keypad1 Keypad2 Keypad3 Keypad4 Keypad5
Keypad6 Keypad7 Keypad8 Keypad9 Insert
Additionally, two special values exist: None0 and None1. These indicate that
no key is to be mapped to the specified Data Switch bit, but instead that bit
should be forced to 0 (for None0) or 1 (for None1).
5. Getting Software
-------------------
See Tom Uban's archive at: http://www.ubanproductions.com/imlac_sw.html.
software.txt (included with this distribution) attempts to describe this software
in greater detail.
6. Thanks
---------
Thanks go out to:
- Tom Uban for making sure the software and documentation he had got archived;
without this, not only would there be no hardware information left (making
an emulator difficult to write) there wouldn't even be any software to run
on it!
- Bitsavers.org for making documentation for the Imlac available.

169
imlac/notes/software.txt Normal file
View File

@ -0,0 +1,169 @@
The following is a brief synopsis of the software available in Tom Uban's archive at: http://www.ubanproductions.com/imlac_sw.html.
Filename Loader Start Description / Notes
---------------------------------------------------------------------------------------------------------------------------------
2locFltPtMathSrc.ptp N/A N/A Assembly code, presumably floating point math routines.
40tp_blockPunch1.0.ptp PTR Var Block Punch utility (see blockpunch.pdf for usage).
40tp_debugger5.0.ptp PTR Auto Debugger utility (see debugger.pdf)
40tp_eightLevelTest2.1AlphaGraphic.ptp PTR Unk Presumably a test for the 8-level DT stack, dttest.pdf is likely the
documentation.
40tp_lightPen1.0.ptp PTR Auto Basic LightPen test (unknown usage)
40tp_longVectorTest.ptp PTR Auto Long Vector (LVH) test utility (see longvectest.pdf for usage).
(Ensure bit 0 of DS is set to run)
40tp_loSpeedAssm601.ptp PTR 100 Two-pass Imlac assembler (see lowspeedassembler.pdf for usage).
40tp_simpleDisplay.ptp PTR 100 Prints "HELLO" in the center of the screen. This looks to be identical
to the sample program listed on pages VI-2.18 through VI-2.21 of the
PDS-1 Technical Manual (PDS-1_TechnicalMan.pdf). Ensure Bit 0 of DS is
set when running.
40tp_spacewar2.5.ptp PTR 100 Extraordinarly crude version of "Space War!" Looks to be a work in
progress.
40tp_symFormII_1.2C1_8kg.ptp PTR 100 Some manner of symbol editor. Appears to allow creation of characters
and symbols; arrow keys move the cursor, and "B" draws a vector from
the last point to the current cursor location.
40tp_tse-4_supGrid.ptp PTR 100 Appears to be a basic visual Text Editor. Ensure Bit 0 of DS is set
when running.
40tp_ttyio.ptp PTR 5400 TIO (Teletype Input Output). Allows using a teletype to debug the system.
See tio.pdf and tiosrc.pdf for usage and source code.
40tp_upperMemTest1.ptp PTR Auto Upper Memory Test 1. See memtest.pdf for usage.
binldr.i N/A N/A Assembly code for Binary TTY 2nd stage "block" loader.
binldr.list N/A N/A Assembler list for above
blinkg.txt STTY Auto Unknown; displays blinking cursor at upper left and appears to use the
TTY inteface. Ensure Bit 0 of DS is set when running.
check (long) TTY Auto Long-vector version of Checkers. See checkers.pdf for usage. Ensure
bit 0 of DS is set when running.
check (short) TTY 100 Short Vector version of Checkers. See check.doc for usage.
clock.txt STTY Auto Displays an analog clock.
core16k TTY Auto Displays an octal dump of core onscreen. Arrow keys scroll through
memory, an address can be typed in at the bottom left of the screen,
hitting space wil jump to that address. Unsure if memory can be edited.
coreop TTY Auto Displays a basic disassembly of core contents. Arrow keys scroll, an
address can be typed in at the bottom left, space will jump to that
address.
crash TTY Auto Unknown. (Perhaps a variant of crashmit.txt below. Does not currently
run.)
crashmit.txt STTY Auto Simple "Lunar Lander" clone; currently runs extremely fast making it
unplayable. Arrow keys control thrust.
cubic.txt STTY Auto Unknown.
debug TTY Auto Debugger. Does not currently work correctly; see debugger.pdf for usage.
debug2.txt STTY Auto Debugger, appears to work. See debugger.pdf for usage.
dts TTY Auto Unknown, possibly a terminal emulator. Displays a cursor at the top left.
fortranARDSsrc.ptp N/A N/A FORTRAN source. Unknown purpose.
imquad TTY Auto "Space War!" clone. Data Switches are used as controls (unsure of exact
mapping). DS 0 must be set. CR restarts.
integerMath.ptp N/A N/A Assembly code, presumably for integer math routines.
keybrd.txt STTY Auto "Keyboard Diagnostic 1." Displays onscreen keyboard, keys borders light
up as keys are pressed. Row of numbers at the top shows repeat count,
scancode, and third number of unknown purpose (appears to always be 0).
lemwar TTY Auto "Lunar Lander" clone, appears to be unfinished. Arrow keys control
thrust, CR restarts.
life STTY Auto Conway's "Game Of Life". Set DS bit 0 to run. Arrow keys move cursor
around grid. "C" toggles a cell on or off, "Q" resets the grid, "R"
runs a single step, "G" runs continuously, and "H" halts the run.
lowerMemTest1.ptp PTR Auto Lower memory test, see memtest.pdf for usage.
maze TTY Auto Generates mazes (hit CR when the grid appears to start) and places a mouse
at one side and a cheese wedge at the other; use the arrow keys to move
the mouse through the maze.
munch TTY Auto The famous "Munching Squares" demo. Change the DS switch settings to
generate different patterns. Trippy.
newapollo STTY Auto Another "Lunar Lander" clone. Use arrow keys to adjust thrust, CR to
restart. Ensure DS bit 0 is set.
nspwar TTY Auto Another space war clone; unsure of the controls -- all I can do at the moment
is make my ship explode. (May use data switches -- DS 0 must be set to run.)
pinball TTY Auto Pinball game. Seems to want a Digitizer tablet in order to actually play.
pixlib.txt STTY Unk Unknown. Given the name, not likely to be a standalone program.
pong.txt STTY Auto The classic paddle game.
rom3 TTY Unk Based on the .i file, looks to be some manner of TTY loader.
rom3.i
snarf STTY Auto Fun game -- hunt the moving circles. See snarf.pdf or snarf.doc for usage details.
snarf2.i Patch for above, unknown what it fixes / changes.
snarf (short) TTY 100 Presumably a short-vector version of the above.
spwar TTY 100 Looks to be the same lame "Space War!" clone as 40tp_spacewar2.5.ptp.
ssv.txt STTY Auto Describes itself as "SSV.22." This is a terminal emulator / debugger used at MIT
to talk with their ITS systems.
sts TTY 100 Some manner of text editor. Wants a digitizer tablet. Set DS bit 0.
stsold TTY Auto Presumably an older version of sts. Runs but behaves oddly.
sts-short TTY Auto Very similar to stsold.
t_d8level TTY Auto Likely the same as 40tp_eightLevelTest2.1AlphaGraphic.ptp.
t_longvec TTY 100 Same as 40tp_longVectorTest.ptp.
t_lowmem TTY Auto Same as lowerMemtest1.ptp.
t_upmem TTY Auto Same as 40tp_upperMemTest1.ptp.
tank (long) TTY Auto Tank game, long vector version; see tank.doc for details.
tank2.i N/A N/A Assembly language patch for one of these here tank games. Unknown
purpose.
tank (short) TTY Auto Tank game, short vector version. See tank.doc for details.
teksim TTY Auto Terminal emulator for the venerable Tektronix 4010.
tis TTY Auto Another terminal emulator.
tse4 TTY Auto Looks to be the same as 40tp_tse-4_supGrid.ptp.
unlabeled.ptp PTR Auto Possibly a duplicate of one of the debuggers.
war STTY Auto An actually good clone of "Space War!". Very similar in appearance to
the PDP versions. Supports three players, and is self-documenting!
wumpus TTY Auto "Hunt The Wumpus." Unsure how to play, but it has nifty graphics.
xpw TTY Auto Unknown.
yogi TTY Auto Some manner of editor. See yogi2.doc and yogireq.doc for details.