mirror of
https://github.com/livingcomputermuseum/sImlac.git
synced 2026-01-11 23:53:24 +00:00
Initial commit.
This commit is contained in:
commit
0e9b1e87a7
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal 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
192
.gitignore
vendored
Normal 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
24
imlac.sln
Normal 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
|
||||
117
imlac/Debugger/BreakpointManager.cs
Normal file
117
imlac/Debugger/BreakpointManager.cs
Normal 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
687
imlac/Debugger/Console.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
63
imlac/Debugger/DebuggerAttributes.cs
Normal file
63
imlac/Debugger/DebuggerAttributes.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
496
imlac/Debugger/DebuggerPrompt.cs
Normal file
496
imlac/Debugger/DebuggerPrompt.cs
Normal 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
992
imlac/DisplayProcessor.cs
Normal 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
133
imlac/EntryPoint.cs
Normal 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
41
imlac/Helpers.cs
Normal 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
209
imlac/HighResTimer.cs
Normal 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
114
imlac/IImlacConsole.cs
Normal 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
115
imlac/IO/Clock.cs
Normal 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
41
imlac/IO/IIOTDevice.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
183
imlac/IO/InterruptFacility.cs
Normal file
183
imlac/IO/InterruptFacility.cs
Normal 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
328
imlac/IO/Keyboard.cs
Normal 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
145
imlac/IO/PaperTapeReader.cs
Normal 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
158
imlac/IO/TTY.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
57
imlac/IO/TTYChannels/ISerialDataChannel.cs
Normal file
57
imlac/IO/TTYChannels/ISerialDataChannel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
68
imlac/IO/TTYChannels/NullDataChannel.cs
Normal file
68
imlac/IO/TTYChannels/NullDataChannel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
imlac/IO/TTYChannels/SerialDataChannel.cs
Normal file
69
imlac/IO/TTYChannels/SerialDataChannel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
92
imlac/IO/TTYChannels/StreamDataChannel.cs
Normal file
92
imlac/IO/TTYChannels/StreamDataChannel.cs
Normal 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
58
imlac/Memory.cs
Normal 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
35
imlac/Paths.cs
Normal 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
1202
imlac/Processor.cs
Normal file
File diff suppressed because it is too large
Load Diff
14
imlac/Program.cs
Normal file
14
imlac/Program.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
36
imlac/Properties/AssemblyInfo.cs
Normal file
36
imlac/Properties/AssemblyInfo.cs
Normal 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
878
imlac/SDLConsole.cs
Normal 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
638
imlac/System.cs
Normal 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
112
imlac/Trace.cs
Normal 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
3
imlac/app.config
Normal 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
BIN
imlac/boot/mtty
Normal file
Binary file not shown.
BIN
imlac/boot/ptr
Normal file
BIN
imlac/boot/ptr
Normal file
Binary file not shown.
BIN
imlac/boot/ptr.boot
Normal file
BIN
imlac/boot/ptr.boot
Normal file
Binary file not shown.
BIN
imlac/boot/stty
Normal file
BIN
imlac/boot/stty
Normal file
Binary file not shown.
BIN
imlac/boot/stty.boot
Normal file
BIN
imlac/boot/stty.boot
Normal file
Binary file not shown.
BIN
imlac/boot/tty
Normal file
BIN
imlac/boot/tty
Normal file
Binary file not shown.
BIN
imlac/boot/tty.boot
Normal file
BIN
imlac/boot/tty.boot
Normal file
Binary file not shown.
110
imlac/imlac.csproj
Normal file
110
imlac/imlac.csproj
Normal 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>
|
||||
76
imlac/notes/PDS-1D bootstrap.txt
Normal file
76
imlac/notes/PDS-1D bootstrap.txt
Normal 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
|
||||
135
imlac/notes/ptr bootstrap disassembly.txt
Normal file
135
imlac/notes/ptr bootstrap disassembly.txt
Normal 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
311
imlac/notes/readme.txt
Normal 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
169
imlac/notes/software.txt
Normal 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user