mirror of
https://github.com/livingcomputermuseum/sImlac.git
synced 2026-01-16 08:25:24 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c5437059 | ||
|
|
f0bc983c8a | ||
|
|
0af475cc27 | ||
|
|
8b7280ebe7 | ||
|
|
4c65f894be | ||
|
|
fcf0c96b6c | ||
|
|
6f30305c79 | ||
|
|
4130c969c2 | ||
|
|
3c55ecfb5d | ||
|
|
1dd50d74f4 | ||
|
|
3efc31f107 | ||
|
|
5124c35296 | ||
|
|
6f7711fe27 | ||
|
|
8ced87eb1e | ||
|
|
3a7afbafc0 | ||
|
|
aee6c5dc63 | ||
|
|
99d6173392 | ||
|
|
64d027dcab | ||
|
|
5b9eb37f9c | ||
|
|
7dce231368 | ||
|
|
7b616105b2 | ||
|
|
6ddbf3791d | ||
|
|
b0321a70e6 | ||
|
|
8b5fb8e997 | ||
|
|
d33cc3d166 | ||
|
|
18bb3eb9bd | ||
|
|
d54292d346 | ||
|
|
98189686ce | ||
|
|
ed54a38d3a | ||
|
|
a7f81a8b8f | ||
|
|
34d48856b6 | ||
|
|
cc6cc29682 | ||
|
|
e306c11e4f | ||
|
|
91682a2f6d | ||
|
|
47c6643cce | ||
|
|
085886e30c | ||
|
|
d1971d41de | ||
|
|
39032d79c4 | ||
|
|
2fb927d857 | ||
|
|
9ed3e85ab6 | ||
|
|
2d2f4d9b6d | ||
|
|
0c26c2f080 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -190,3 +190,7 @@ _Pvt_Extensions/
|
||||
ModelManifest.xml
|
||||
/sImlac.VC.VC.opendb
|
||||
/sImlac.VC.db
|
||||
/.vs/imlac/v15/Server/sqlite3/db.lock
|
||||
/.vs/imlac/v15/Server/sqlite3/storage.ide
|
||||
/.vs/imlac/v15/Server/sqlite3/storage.ide-shm
|
||||
/.vs/imlac/v15/Server/sqlite3/storage.ide-wal
|
||||
|
||||
2
.travis.yml
Normal file
2
.travis.yml
Normal file
@ -0,0 +1,2 @@
|
||||
language: csharp
|
||||
solution: imlac.sln
|
||||
@ -3,6 +3,6 @@
|
||||
"",
|
||||
"\\imlac"
|
||||
],
|
||||
"SelectedNode": "\\imlac",
|
||||
"SelectedNode": "\\imlac.sln",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/larsbrinkhoff/sImlac/history)
|
||||
8
appveyor.yml
Normal file
8
appveyor.yml
Normal file
@ -0,0 +1,8 @@
|
||||
platform:
|
||||
- x86
|
||||
install:
|
||||
- nuget restore
|
||||
build:
|
||||
project: imlac.sln
|
||||
artifacts:
|
||||
- path: imlac/bin
|
||||
34
imlac/Configuration.cs
Normal file
34
imlac/Configuration.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using static imlac.ImlacSystem;
|
||||
|
||||
namespace imlac
|
||||
{
|
||||
public enum ImlacCPUType
|
||||
{
|
||||
PDS1,
|
||||
PDS4,
|
||||
}
|
||||
|
||||
public static class Configuration
|
||||
{
|
||||
static Configuration()
|
||||
{
|
||||
MITMode = false;
|
||||
CPUType = ImlacCPUType.PDS1;
|
||||
SquiggleMode = false;
|
||||
ShowInvisibleVectors = false;
|
||||
HaltOnInvalidOpcodes = true;
|
||||
}
|
||||
//
|
||||
// Static System configuration parameters
|
||||
//
|
||||
public static bool MITMode;
|
||||
public static ImlacCPUType CPUType;
|
||||
public static bool SquiggleMode;
|
||||
public static bool ShowInvisibleVectors;
|
||||
public static bool HaltOnInvalidOpcodes;
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ namespace imlac.Debugger
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSubNode(List<string> words, MethodInfo method, object instance)
|
||||
public void AddSubNode(List<string> words, DebuggerCommand command)
|
||||
{
|
||||
// We should never hit this case.
|
||||
if (words.Count == 0)
|
||||
@ -77,13 +77,13 @@ namespace imlac.Debugger
|
||||
if (subNode == null)
|
||||
{
|
||||
// No, it has not -- create one and add it now.
|
||||
subNode = new DebuggerCommand(instance, words[0], null, null, null);
|
||||
subNode = new DebuggerCommand(command.Instance, words[0], command.Description, command.Usage, null);
|
||||
this.SubCommands.Add(subNode);
|
||||
|
||||
if (words.Count == 1)
|
||||
{
|
||||
// This is the last stop -- set the method and be done with it now.
|
||||
subNode.Methods.Add(method);
|
||||
subNode.Methods.Add(command.Methods[0]);
|
||||
|
||||
// early return.
|
||||
return;
|
||||
@ -98,7 +98,7 @@ namespace imlac.Debugger
|
||||
// If we're on the last word at this point then this is an overloaded command.
|
||||
// Check that we don't have any other commands with this number of arguments.
|
||||
//
|
||||
int argCount = method.GetParameters().Length;
|
||||
int argCount = command.Methods[0].GetParameters().Length;
|
||||
foreach (MethodInfo info in subNode.Methods)
|
||||
{
|
||||
if (info.GetParameters().Length == argCount)
|
||||
@ -110,7 +110,7 @@ namespace imlac.Debugger
|
||||
//
|
||||
// We're ok. Add it to the method list.
|
||||
//
|
||||
subNode.Methods.Add(method);
|
||||
subNode.Methods.Add(command.Methods[0]);
|
||||
|
||||
// and return early.
|
||||
return;
|
||||
@ -119,7 +119,7 @@ namespace imlac.Debugger
|
||||
|
||||
// We have more words to go.
|
||||
words.RemoveAt(0);
|
||||
subNode.AddSubNode(words, method, instance);
|
||||
subNode.AddSubNode(words, command);
|
||||
}
|
||||
|
||||
public DebuggerCommand FindSubNodeByName(string name)
|
||||
@ -159,14 +159,22 @@ namespace imlac.Debugger
|
||||
// Get the command string from the prompt.
|
||||
string command = _consolePrompt.Prompt().Trim();
|
||||
|
||||
if (command != String.Empty)
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
next = ExecuteLine(command, system);
|
||||
// Repeat the last command
|
||||
command = _lastCommand;
|
||||
Console.WriteLine(">> {0}", command);
|
||||
}
|
||||
|
||||
next = ExecuteLine(command, system);
|
||||
|
||||
_lastCommand = command;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(
|
||||
"Error: {0}",
|
||||
e.InnerException != null ? e.InnerException.Message : e.Message);
|
||||
}
|
||||
|
||||
return next;
|
||||
@ -650,9 +658,9 @@ namespace imlac.Debugger
|
||||
{
|
||||
string[] commandWords = c.Name.Split(' ');
|
||||
|
||||
// This is kind of ugly, we know that at this point every command built above have only
|
||||
// This is kind of ugly, we know that at this point every command built above has only
|
||||
// one method. When building the tree, overloaded commands may end up with more than one.
|
||||
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0], c.Instance);
|
||||
_commandRoot.AddSubNode(new List<string>(commandWords), c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,5 +691,7 @@ namespace imlac.Debugger
|
||||
private DebuggerPrompt _consolePrompt;
|
||||
private DebuggerCommand _commandRoot;
|
||||
private List<DebuggerCommand> _commandList;
|
||||
|
||||
private string _lastCommand;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,8 +197,11 @@ namespace imlac.Debugger
|
||||
clear = sb.ToString();
|
||||
}
|
||||
|
||||
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
|
||||
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
|
||||
// Default to 80 columns if BufferWidth happens to be zero.
|
||||
int bufferWidth = Console.BufferWidth > 0 ? Console.BufferWidth : 80;
|
||||
|
||||
int column = ((_textPosition + _originColumn) % bufferWidth);
|
||||
int row = ((_textPosition + _originColumn) / bufferWidth) + _originRow;
|
||||
|
||||
// Move cursor to origin to draw string
|
||||
Console.CursorLeft = _originColumn;
|
||||
@ -294,6 +297,13 @@ namespace imlac.Debugger
|
||||
{
|
||||
changed = _input.Trim().ToLower() != matchString.Trim().ToLower();
|
||||
|
||||
// Add a space if the output is different than the input (in which case a completion
|
||||
// actually took place.
|
||||
if (changed)
|
||||
{
|
||||
matchString += " ";
|
||||
}
|
||||
|
||||
_input = matchString;
|
||||
TextPosition = _input.Length;
|
||||
}
|
||||
@ -320,6 +330,7 @@ namespace imlac.Debugger
|
||||
}
|
||||
|
||||
DebuggerCommand match = null;
|
||||
bool exactMatch = false;
|
||||
|
||||
// Search for exact matches. If we find one it's guaranteed to be unique
|
||||
// so we can follow that node.
|
||||
@ -328,6 +339,7 @@ namespace imlac.Debugger
|
||||
if (c.Name.ToLower() == tokens[0].ToLower())
|
||||
{
|
||||
match = c;
|
||||
exactMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -391,7 +403,7 @@ namespace imlac.Debugger
|
||||
{
|
||||
subMatch = FuzzyMatch(match, tokens, silent);
|
||||
}
|
||||
else // if (exactMatch)
|
||||
else
|
||||
{
|
||||
if (!silent && match.SubCommands.Count > 1)
|
||||
{
|
||||
@ -419,11 +431,27 @@ namespace imlac.Debugger
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
else if (!silent &&
|
||||
match.SubCommands.Count == 0 &&
|
||||
exactMatch)
|
||||
{
|
||||
// No more completions; this was an exact match, so
|
||||
// instead print the help for this command if any
|
||||
// is available.
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(match.Description);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(match.Usage))
|
||||
{
|
||||
Console.WriteLine("Parameters: {0}", match.Usage);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (subMatch == String.Empty)
|
||||
{
|
||||
return String.Format("{0} ", match.Name);
|
||||
return String.Format("{0}", match.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -24,28 +24,57 @@ namespace imlac
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
ImlacSystem system = new ImlacSystem();
|
||||
ConsoleExecutor debuggerPrompt =
|
||||
new ConsoleExecutor(system);
|
||||
_startupArgs = args;
|
||||
|
||||
_console = new SDLConsole(0.5f);
|
||||
_imlacSystem = new ImlacSystem();
|
||||
_imlacSystem.AttachConsole(_console);
|
||||
_imlacSystem.Reset();
|
||||
|
||||
_state = SystemExecutionState.Debugging;
|
||||
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnBreak);
|
||||
|
||||
PrintHerald();
|
||||
|
||||
if (args.Length > 0)
|
||||
//
|
||||
// Start the debugger / execution thread
|
||||
//
|
||||
_debuggerThread = new System.Threading.Thread(new System.Threading.ThreadStart(DebuggerThread));
|
||||
_debuggerThread.Start();
|
||||
|
||||
//
|
||||
// Show the display; this will return when the display window is closed.
|
||||
//
|
||||
_console.Show();
|
||||
|
||||
//
|
||||
// Kill the system if it's still running.
|
||||
//
|
||||
_debuggerThread.Abort();
|
||||
}
|
||||
|
||||
private static void DebuggerThread()
|
||||
{
|
||||
//
|
||||
// Wait for the display to be ready.
|
||||
//
|
||||
_console.WaitForSync();
|
||||
|
||||
ConsoleExecutor debuggerPrompt = new ConsoleExecutor(_imlacSystem);
|
||||
|
||||
if (_startupArgs.Length > 0)
|
||||
{
|
||||
//
|
||||
// Assume arg 0 is a script file to be executed.
|
||||
//
|
||||
Console.WriteLine("Executing startup script '{0}'", args[0]);
|
||||
Console.WriteLine("Executing startup script '{0}'", _startupArgs[0]);
|
||||
|
||||
try
|
||||
{
|
||||
_state = debuggerPrompt.ExecuteScript(system, args[0]);
|
||||
_state = debuggerPrompt.ExecuteScript(_imlacSystem, _startupArgs[0]);
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Error parsing script: {0}", e.Message);
|
||||
}
|
||||
@ -59,19 +88,19 @@ namespace imlac
|
||||
{
|
||||
case SystemExecutionState.Halted:
|
||||
case SystemExecutionState.Debugging:
|
||||
_state = debuggerPrompt.Prompt(system);
|
||||
_state = debuggerPrompt.Prompt(_imlacSystem);
|
||||
break;
|
||||
|
||||
case SystemExecutionState.SingleStep:
|
||||
system.SingleStep();
|
||||
system.Display.RenderCurrent(false);
|
||||
_imlacSystem.SingleStep();
|
||||
_imlacSystem.Display.RenderCurrent(false);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
break;
|
||||
|
||||
case SystemExecutionState.SingleFrame:
|
||||
system.SingleStep();
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.DisplayProcessor.FrameLatch)
|
||||
if (_imlacSystem.DisplayProcessor.FrameLatch)
|
||||
{
|
||||
Console.WriteLine("Frame completed.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
@ -79,55 +108,85 @@ namespace imlac
|
||||
break;
|
||||
|
||||
case SystemExecutionState.UntilDisplayStart:
|
||||
system.SingleStep();
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.DisplayProcessor.State == ProcessorState.Running)
|
||||
if (_imlacSystem.DisplayProcessor.State == ProcessorState.Running)
|
||||
{
|
||||
Console.WriteLine("Display started.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
break;
|
||||
|
||||
case SystemExecutionState.Running:
|
||||
system.SingleStep();
|
||||
case SystemExecutionState.SingleDisplayOperation:
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (system.Processor.State == ProcessorState.Halted)
|
||||
if (_imlacSystem.DisplayProcessor.DisplayDrawLatch)
|
||||
{
|
||||
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(system.Processor.PC));
|
||||
_imlacSystem.Display.RenderCurrent(false);
|
||||
Console.WriteLine("Display operation completed.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
else if (system.Processor.State == ProcessorState.BreakpointHalt)
|
||||
break;
|
||||
|
||||
case SystemExecutionState.Running:
|
||||
_imlacSystem.SingleStep();
|
||||
|
||||
if (_imlacSystem.Processor.State == ProcessorState.Halted)
|
||||
{
|
||||
Console.WriteLine("Main processor halted at {0}", Helpers.ToOctal(_imlacSystem.Processor.PC));
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
else if (_imlacSystem.Processor.State == ProcessorState.BreakpointHalt)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Breakpoint hit: {0} at address {1}",
|
||||
BreakpointManager.GetBreakpoint(system.Processor.BreakpointAddress),
|
||||
Helpers.ToOctal(system.Processor.BreakpointAddress));
|
||||
BreakpointManager.GetBreakpoint(_imlacSystem.Processor.BreakpointAddress),
|
||||
Helpers.ToOctal(_imlacSystem.Processor.BreakpointAddress));
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Internal error during execution: {0}", e.Message);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
if (!(e is System.Threading.ThreadAbortException))
|
||||
{
|
||||
Console.WriteLine("Internal error during execution: {0}", e.Message);
|
||||
_state = SystemExecutionState.Debugging;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are exiting, shut things down.
|
||||
//
|
||||
_imlacSystem.Shutdown();
|
||||
}
|
||||
|
||||
static void OnBreak(object sender, ConsoleCancelEventArgs e)
|
||||
private static void OnBreak(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
Console.WriteLine("User break.");
|
||||
_state = SystemExecutionState.Debugging;
|
||||
e.Cancel = true;
|
||||
|
||||
// Flush console input.
|
||||
while(Console.KeyAvailable)
|
||||
{
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void PrintHerald()
|
||||
private static void PrintHerald()
|
||||
{
|
||||
Console.WriteLine("sImlac v0.2. (c) 2016-2018 Living Computers: Museum+Labs");
|
||||
Console.WriteLine("sImlac v0.3. (c) 2016-2020 Living Computers: Museum+Labs");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static string[] _startupArgs;
|
||||
private static SDLConsole _console;
|
||||
private static ImlacSystem _imlacSystem;
|
||||
private static SystemExecutionState _state;
|
||||
|
||||
private static System.Threading.Thread _debuggerThread;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,17 @@ namespace imlac
|
||||
ushort value = Convert.ToUInt16(octal, 8);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void SignalError(LogType logtype, string format, params object[] args)
|
||||
{
|
||||
if (Configuration.HaltOnInvalidOpcodes)
|
||||
{
|
||||
throw new NotImplementedException(String.Format(format, args));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Trace.TraceOn) Trace.Log(logtype, format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,16 +155,24 @@ namespace imlac
|
||||
|
||||
~FrameTimer()
|
||||
{
|
||||
//
|
||||
// Clean stuff up
|
||||
//
|
||||
DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
|
||||
DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
|
||||
try
|
||||
{
|
||||
//
|
||||
// Clean stuff up
|
||||
//
|
||||
DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
|
||||
DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
|
||||
|
||||
//
|
||||
// Fire off a final event to release any call that's waiting...
|
||||
//
|
||||
_event.Set();
|
||||
//
|
||||
// Fire off a final event to release any call that's waiting...
|
||||
//
|
||||
_event.Set();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Nothing. If the above fails it's due to the Win32 APIs not being present.
|
||||
// Just fail quietly.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -144,7 +144,7 @@ namespace imlac
|
||||
/// <summary>
|
||||
/// Indicates whether a key is currently pressed.
|
||||
/// </summary>
|
||||
bool IsKeyPressed
|
||||
bool NewKeyPressed
|
||||
{
|
||||
get;
|
||||
}
|
||||
@ -173,6 +173,16 @@ namespace imlac
|
||||
get;
|
||||
}
|
||||
|
||||
int MouseX
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
int MouseY
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool ThrottleFramerate
|
||||
{
|
||||
get;
|
||||
@ -201,9 +211,9 @@ namespace imlac
|
||||
|
||||
void ClearDisplay();
|
||||
|
||||
void MoveAbsolute(uint x, uint y, DrawingMode mode);
|
||||
void MoveAbsolute(int x, int y, DrawingMode mode);
|
||||
|
||||
void DrawPoint(uint x, uint y);
|
||||
void DrawPoint(int x, int y);
|
||||
|
||||
void RenderCurrent(bool completeFrame);
|
||||
|
||||
@ -211,8 +221,14 @@ namespace imlac
|
||||
|
||||
void SetScale(float scale);
|
||||
|
||||
void SetIntensity(int intensity);
|
||||
|
||||
void SetBlink(bool on);
|
||||
|
||||
void MapDataSwitch(uint switchNumber, VKeys key);
|
||||
|
||||
VKeys GetDataSwitchMapping(uint switchNumber);
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,38 +69,87 @@ namespace imlac.IO
|
||||
|
||||
public void Clock()
|
||||
{
|
||||
if (_interruptsEnabled)
|
||||
if (_interruptsEnabled && _interruptEnableCount == 0)
|
||||
{
|
||||
// Collect up devices that want to interrupt us.
|
||||
_interruptStatus = 0;
|
||||
|
||||
// bit 14: 40 cycle sync
|
||||
if (_system.DisplayProcessor.FrameLatch)
|
||||
//
|
||||
// PDS-1 and PDS-4 status bits are arranged almost entirely differently.
|
||||
//
|
||||
if (Configuration.CPUType == ImlacCPUType.PDS1)
|
||||
{
|
||||
_interruptStatus |= 0x0002;
|
||||
}
|
||||
// PDS-1:
|
||||
|
||||
// bit 12 - TTY rcv
|
||||
if (_system.TTY.DataReady)
|
||||
{
|
||||
_interruptStatus |= 0x0008;
|
||||
}
|
||||
// bit 14: 40 cycle sync
|
||||
if (_system.DisplayProcessor.FrameLatch &&
|
||||
_system.DisplayProcessor.DisplayHalted)
|
||||
{
|
||||
_interruptStatus |= 0x0002;
|
||||
}
|
||||
|
||||
// bit 11 - keyboard
|
||||
if (_system.Keyboard.KeyReady)
|
||||
{
|
||||
_interruptStatus |= 0x0010;
|
||||
}
|
||||
// bit 12 - TTY rcv
|
||||
if (_system.TTY.DataReady)
|
||||
{
|
||||
_interruptStatus |= 0x0008;
|
||||
}
|
||||
|
||||
// bit 2 - ACI-1 (clock)
|
||||
if (_system.Clock.TimerTriggered)
|
||||
// bit 11 - keyboard
|
||||
if (_system.Keyboard.KeyReady)
|
||||
{
|
||||
_interruptStatus |= 0x0010;
|
||||
}
|
||||
|
||||
// bit 10 - TTY send
|
||||
if (_system.TTY.DataSentLatch)
|
||||
{
|
||||
_interruptStatus |= 0x0020;
|
||||
}
|
||||
|
||||
// bit 2 - ACI-1 (clock)
|
||||
if (_system.Clock.TimerTriggered)
|
||||
{
|
||||
_interruptStatus |= 0x2000;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_interruptStatus |= 0x2000;
|
||||
// PDS-4:
|
||||
|
||||
// Bit 15: Display halt
|
||||
if (_system.DisplayProcessor.DisplayHalted)
|
||||
{
|
||||
_interruptStatus |= 0x0001;
|
||||
}
|
||||
|
||||
// Bit 14: 40 cycle sync
|
||||
if (_system.DisplayProcessor.FrameLatch)
|
||||
{
|
||||
_interruptStatus |= 0x0002;
|
||||
}
|
||||
|
||||
// Bit 2: TTY Send
|
||||
if (_system.TTY.DataSentLatch)
|
||||
{
|
||||
_interruptStatus |= 0x2000;
|
||||
}
|
||||
|
||||
// Bit 1: TTY Receive
|
||||
if (_system.TTY.DataReady)
|
||||
{
|
||||
_interruptStatus |= 0x4000;
|
||||
}
|
||||
|
||||
// Bit 0: TTY Keyboard
|
||||
if (_system.Keyboard.KeyReady)
|
||||
{
|
||||
_interruptStatus |= 0x8000;
|
||||
}
|
||||
}
|
||||
|
||||
// mask it with our interrupt mask and if non-zero then we have a pending interrupt,
|
||||
// which we will execute at the start of the next CPU instruction.
|
||||
if ((_interruptMask & _interruptStatus) != 0)
|
||||
if (_interruptsEnabled && (_interruptMask & _interruptStatus) != 0)
|
||||
{
|
||||
_interruptPending = true;
|
||||
}
|
||||
@ -109,7 +158,7 @@ namespace imlac.IO
|
||||
// If we have an interrupt pending and the processor is starting the next instruction
|
||||
// we will interrupt now, otherwise we wait until the processor is ready.
|
||||
//
|
||||
if (_interruptPending && _system.Processor.InstructionState == ExecState.Fetch)
|
||||
if (_interruptPending && _system.Processor.CanBeInterrupted())
|
||||
{
|
||||
// save the current PC at 0
|
||||
_system.Memory.Store(0x0000, _system.Processor.PC);
|
||||
@ -124,6 +173,10 @@ namespace imlac.IO
|
||||
Trace.Log(LogType.Interrupt, "Interrupt triggered (for device(s) {0})", Helpers.ToOctal((ushort)(_interruptMask & _interruptStatus)));
|
||||
}
|
||||
}
|
||||
else if (_interruptsEnabled)
|
||||
{
|
||||
_interruptEnableCount--;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetHandledIOTs()
|
||||
@ -135,6 +188,7 @@ namespace imlac.IO
|
||||
{
|
||||
//
|
||||
// Dispatch the IOT instruction.
|
||||
// TODO: handle PDS-4 IOTs.
|
||||
//
|
||||
switch (iotCode)
|
||||
{
|
||||
@ -146,12 +200,20 @@ namespace imlac.IO
|
||||
|
||||
case 0x72:
|
||||
_interruptsEnabled = true;
|
||||
// Interrupts are enabled the second instruction after the ION.
|
||||
_interruptEnableCount = 1;
|
||||
Trace.Log(LogType.Interrupt, "Interrupts enabled");
|
||||
break;
|
||||
|
||||
case 0x41:
|
||||
_system.Processor.AC |= (ushort)(_interruptStatus);
|
||||
Trace.Log(LogType.Interrupt, "Interrupt status {0} copied to AC", Helpers.ToOctal((ushort)_interruptStatus));
|
||||
Trace.Log(LogType.Interrupt, "Interrupt status word 1 {0} copied to AC", Helpers.ToOctal((ushort)_interruptStatus));
|
||||
break;
|
||||
|
||||
case 0x42:
|
||||
// TODO: implement second status register when we have devices that use it
|
||||
// implemented.
|
||||
Trace.Log(LogType.Interrupt, "Interrupt status word 2 STUB.");
|
||||
break;
|
||||
|
||||
case 0x61:
|
||||
@ -161,10 +223,11 @@ namespace imlac.IO
|
||||
|
||||
|
||||
default:
|
||||
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0:x4}", iotCode));
|
||||
throw new NotImplementedException(String.Format("Unimplemented Interrupt IOT instruction {0}", Helpers.ToOctal((ushort)iotCode)));
|
||||
}
|
||||
}
|
||||
|
||||
private int _interruptEnableCount;
|
||||
private bool _interruptsEnabled;
|
||||
private bool _interruptPending;
|
||||
private int _interruptMask;
|
||||
@ -174,10 +237,18 @@ namespace imlac.IO
|
||||
|
||||
private readonly int[] _handledIOTs =
|
||||
{
|
||||
0x41, // read interrupt status bits
|
||||
0x41, // read interrupt status bits
|
||||
0x61, // arm/disarm devices (set interrupt mask)
|
||||
0x71, // IOF (disable interrupts)
|
||||
0x72, // ION (enabled masked interrupts)
|
||||
|
||||
// 2nd level interrupt facility
|
||||
0x42, // Read Interrupt Status Bits Word Two
|
||||
0x89, // Read 2nd Level Interrupt status
|
||||
0x91, // Disable 2nd Level Interrupts
|
||||
0x92, // Enable 2nd Level Interrupts
|
||||
0x93, // Disable, then Enable
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ namespace imlac.IO
|
||||
|
||||
public enum ImlacKey
|
||||
{
|
||||
None = 0x0,
|
||||
DataXmit = 0x2,
|
||||
Down = 0x4,
|
||||
Right = 0x5,
|
||||
@ -39,6 +40,7 @@ namespace imlac.IO
|
||||
Tab = 0x9,
|
||||
CR = 0x0d,
|
||||
FF = 0x0c,
|
||||
LF = 0x0a,
|
||||
PageXmit = 0xe,
|
||||
Home = 0xf,
|
||||
Brk = 0x19,
|
||||
@ -117,26 +119,18 @@ namespace imlac.IO
|
||||
public void Clock()
|
||||
{
|
||||
// If we do not already have a key latched and one has been pressed,
|
||||
// we will latch it now.
|
||||
// Based on the keycode & modifiers we will generate an Imlac keyboard code
|
||||
if (_system.Display.IsKeyPressed)
|
||||
// we will raise the Ready flag now.
|
||||
if (!_keyReady)
|
||||
{
|
||||
_keyCode = GetScancodeForCurrentKey();
|
||||
if (_keyCode != 0)
|
||||
{
|
||||
Trace.Log(LogType.Keyboard, "Key latched {0}", Helpers.ToOctal(_keyCode));
|
||||
if (_system.Display.NewKeyPressed)
|
||||
{
|
||||
_keyReady = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyCode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_keyCode = 0;
|
||||
_keyReady = false;
|
||||
}
|
||||
|
||||
@ -155,20 +149,18 @@ namespace imlac.IO
|
||||
switch (iotCode)
|
||||
{
|
||||
case 0x11:
|
||||
_system.Processor.AC |= _keyCode;
|
||||
_system.Processor.AC |= GetScancodeForCurrentKey();
|
||||
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}", Helpers.ToOctal(_system.Processor.AC));
|
||||
break;
|
||||
|
||||
case 0x12:
|
||||
_keyCode = 0;
|
||||
_keyReady = false;
|
||||
_system.Display.UnlatchKey();
|
||||
Trace.Log(LogType.Keyboard, "Keyboard flag reset.");
|
||||
break;
|
||||
|
||||
case 0x13:
|
||||
_system.Processor.AC |= _keyCode;
|
||||
_keyCode = 0;
|
||||
_system.Processor.AC |= GetScancodeForCurrentKey();
|
||||
_keyReady = false;
|
||||
_system.Display.UnlatchKey();
|
||||
Trace.Log(LogType.Keyboard, "Key OR'd into AC {0}, keyboard flag reset.", Helpers.ToOctal(_system.Processor.AC));
|
||||
@ -196,7 +188,7 @@ namespace imlac.IO
|
||||
|
||||
// bit 8 is always set
|
||||
scanCode = (ushort)(scanCode | 0x80);
|
||||
|
||||
|
||||
//
|
||||
// The Repeat, Control, and Shift keys correspond to bits 5, 6, and 7 of the
|
||||
// scancode returned.
|
||||
@ -225,7 +217,6 @@ namespace imlac.IO
|
||||
private readonly int[] _handledIOTs = { 0x11, 0x12, 0x13 };
|
||||
|
||||
private bool _keyReady;
|
||||
private ushort _keyCode;
|
||||
|
||||
private ImlacSystem _system;
|
||||
|
||||
@ -247,6 +238,7 @@ namespace imlac.IO
|
||||
{
|
||||
_keyMappings = new Dictionary<ImlacKey, ImlacKeyMapping>();
|
||||
|
||||
AddMapping(ImlacKey.None, 0x0, 0x0);
|
||||
AddMapping(ImlacKey.DataXmit, 0x2, 0x0);
|
||||
AddMapping(ImlacKey.Down, 0x4, 0x0);
|
||||
AddMapping(ImlacKey.Right, 0x5, 0x0);
|
||||
@ -255,6 +247,7 @@ namespace imlac.IO
|
||||
AddMapping(ImlacKey.Tab, 0x9, 0x0);
|
||||
AddMapping(ImlacKey.CR, 0x0d, 0x0);
|
||||
AddMapping(ImlacKey.FF, 0x0c, 0x0);
|
||||
AddMapping(ImlacKey.LF, 0x0a, 0x0);
|
||||
AddMapping(ImlacKey.PageXmit, 0xe, 0x0);
|
||||
AddMapping(ImlacKey.Home, 0xf, 0x0);
|
||||
AddMapping(ImlacKey.Brk, 0x19, 0x0);
|
||||
|
||||
@ -36,6 +36,10 @@ namespace imlac.IO
|
||||
_dataSendReady = true;
|
||||
_dataReady = false;
|
||||
_clocks = 0;
|
||||
_dataBufferFull = false;
|
||||
_dataSentLatch = false;
|
||||
_rxData = 0;
|
||||
_txData = 0;
|
||||
|
||||
if (_dataChannel != null)
|
||||
{
|
||||
@ -50,6 +54,11 @@ namespace imlac.IO
|
||||
throw new ArgumentNullException("channel");
|
||||
}
|
||||
|
||||
if (_dataChannel != null)
|
||||
{
|
||||
_dataChannel.Close();
|
||||
}
|
||||
|
||||
_dataChannel = channel;
|
||||
}
|
||||
|
||||
@ -61,27 +70,22 @@ namespace imlac.IO
|
||||
{
|
||||
_clocks = 0;
|
||||
|
||||
if (_dataChannel.DataAvailable)
|
||||
if (_dataChannel.DataAvailable && !_dataReady)
|
||||
{
|
||||
_dataReady = true;
|
||||
_data = _dataChannel.Read();
|
||||
_rxData = _dataChannel.Read();
|
||||
Trace.Log(LogType.TTY, "i");
|
||||
}
|
||||
else
|
||||
|
||||
// Are we waiting to send something?
|
||||
if (_dataBufferFull && _dataChannel.OutputReady)
|
||||
{
|
||||
_dataReady = false;
|
||||
_dataChannel.Write(_txData);
|
||||
Trace.Log(LogType.TTY, "o {0}", Helpers.ToOctal(_txData));
|
||||
_dataBufferFull = false;
|
||||
_dataSentLatch = true;
|
||||
_dataSendReady = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Are we waiting to send something?
|
||||
if (!_dataSendReady && _dataChannel.OutputReady)
|
||||
{
|
||||
_dataChannel.Write(_data);
|
||||
Trace.Log(LogType.TTY, "o");
|
||||
|
||||
// Sent, reset flag.
|
||||
_dataSendReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +99,16 @@ namespace imlac.IO
|
||||
get { return _dataSendReady; }
|
||||
}
|
||||
|
||||
public bool DataSentLatch
|
||||
{
|
||||
get
|
||||
{
|
||||
bool latch = _dataSentLatch;
|
||||
_dataSentLatch = false;
|
||||
return latch;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetHandledIOTs()
|
||||
{
|
||||
return _handledIOTs;
|
||||
@ -105,8 +119,8 @@ namespace imlac.IO
|
||||
switch (iotCode)
|
||||
{
|
||||
case 0x19: // RRB - TTY read
|
||||
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_data));
|
||||
_system.Processor.AC |= _data;
|
||||
Trace.Log(LogType.TTY, "TTY read {0}", Helpers.ToOctal(_rxData));
|
||||
_system.Processor.AC |= _rxData;
|
||||
break;
|
||||
|
||||
case 0x1a: // RCF - Clear TTY status
|
||||
@ -114,30 +128,35 @@ namespace imlac.IO
|
||||
break;
|
||||
|
||||
case 0x1b: // RRC - Read and clear status
|
||||
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_data));
|
||||
Trace.Log(LogType.TTY, "TTY read {0}, status cleared.", Helpers.ToOctal(_rxData));
|
||||
_dataReady = false;
|
||||
_system.Processor.AC |= _data;
|
||||
_system.Processor.AC |= _rxData;
|
||||
break;
|
||||
|
||||
case 0x21: // TPR - transmit
|
||||
if (_dataSendReady) // only if transmitter is ready
|
||||
if (_dataSendReady)
|
||||
{
|
||||
_data = (byte)_system.Processor.AC;
|
||||
_txData = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
_dataBufferFull = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x22: // TCF - clear output flag
|
||||
_dataSendReady = true;
|
||||
break;
|
||||
|
||||
case 0x23: // TPC - print, clear flag
|
||||
_data = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
break;
|
||||
|
||||
case 0x23: // TPC - print, clear flag
|
||||
if (_dataSendReady)
|
||||
{
|
||||
_txData = (byte)_system.Processor.AC;
|
||||
_dataSendReady = false;
|
||||
_dataBufferFull = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_data));
|
||||
Trace.Log(LogType.TTY, "Stub: TTY xmit op", Helpers.ToOctal(_rxData));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -146,10 +165,13 @@ namespace imlac.IO
|
||||
|
||||
private bool _dataReady;
|
||||
private bool _dataSendReady;
|
||||
private byte _data;
|
||||
private bool _dataBufferFull;
|
||||
private bool _dataSentLatch;
|
||||
private byte _rxData;
|
||||
private byte _txData;
|
||||
|
||||
private int _clocks;
|
||||
private readonly int _dataClocks = 100;
|
||||
private readonly int _dataClocks = 90; // Appx. 50kbps
|
||||
|
||||
private ISerialDataChannel _dataChannel;
|
||||
|
||||
|
||||
221
imlac/IO/TTYChannels/TelnetDataChannel.cs
Normal file
221
imlac/IO/TTYChannels/TelnetDataChannel.cs
Normal file
@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace imlac.IO.TTYChannels
|
||||
{
|
||||
|
||||
public class TelnetDataChannel : ISerialDataChannel
|
||||
{
|
||||
public TelnetDataChannel(string server, int port, bool raw)
|
||||
{
|
||||
//
|
||||
// Try to open the channel.
|
||||
//
|
||||
try
|
||||
{
|
||||
_telnetStream = new TelnetStream(server, port, raw);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to connect to {0}:{1}, error: {2}",
|
||||
server, port, e.Message);
|
||||
|
||||
_telnetStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
// TODO: how to handle reset?
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_telnetStream.Close();
|
||||
_telnetStream = null;
|
||||
}
|
||||
|
||||
public byte Read()
|
||||
{
|
||||
byte b = (byte)_telnetStream.ReadByte();
|
||||
Trace.Log(LogType.Telnet, "r:{0:x2}({1}) ", b, (char)b);
|
||||
return b;
|
||||
}
|
||||
|
||||
public void Write(byte value)
|
||||
{
|
||||
Trace.Log(LogType.Telnet, "w:{0:x2}({1})", value, (char)value);
|
||||
_telnetStream.WriteByte(value);
|
||||
}
|
||||
|
||||
public bool DataAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
return _telnetStream != null ? _telnetStream.DataAvailable : false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OutputReady
|
||||
{
|
||||
get
|
||||
{
|
||||
// Always return true, we can always send data
|
||||
return _telnetStream != null ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
private TelnetStream _telnetStream;
|
||||
}
|
||||
|
||||
|
||||
public class TelnetStream
|
||||
{
|
||||
public TelnetStream(string host, int port, bool raw)
|
||||
{
|
||||
_tcpClient = new TcpClient(host, port);
|
||||
_tcpStream = _tcpClient.GetStream();
|
||||
_raw = raw;
|
||||
_abort = false;
|
||||
|
||||
_asyncBuffer = new byte[2048];
|
||||
_inputBuffer = new Queue<byte>();
|
||||
|
||||
_bufferLock = new ReaderWriterLockSlim();
|
||||
_streamLock = new ReaderWriterLockSlim();
|
||||
|
||||
//
|
||||
// Kick off reading from the stream, asynchronously.
|
||||
_tcpStream.BeginRead(
|
||||
_asyncBuffer,
|
||||
0,
|
||||
_asyncBuffer.Length,
|
||||
new AsyncCallback(AsyncReadCallback),
|
||||
null);
|
||||
}
|
||||
|
||||
public bool DataAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
bool avail = false;
|
||||
_bufferLock.EnterReadLock();
|
||||
avail = _inputBuffer.Count > 0;
|
||||
_bufferLock.ExitReadLock();
|
||||
|
||||
return avail;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_streamLock.EnterWriteLock();
|
||||
_abort = true;
|
||||
_tcpStream.Close();
|
||||
_tcpClient.Close();
|
||||
_streamLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
byte b = 0;
|
||||
_bufferLock.EnterUpgradeableReadLock();
|
||||
if (_inputBuffer.Count > 0)
|
||||
{
|
||||
_bufferLock.EnterWriteLock();
|
||||
b = _inputBuffer.Dequeue();
|
||||
_bufferLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_bufferLock.ExitUpgradeableReadLock();
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public void WriteByte(byte b)
|
||||
{
|
||||
_tcpStream.WriteByte(b);
|
||||
}
|
||||
|
||||
private void AsyncReadCallback(IAsyncResult ar)
|
||||
{
|
||||
_streamLock.EnterReadLock();
|
||||
|
||||
if (_abort)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Process incoming data
|
||||
// TODO: The telnet processing is terrible.
|
||||
//
|
||||
int bytesRead = _tcpStream.EndRead(ar);
|
||||
|
||||
for (int i = 0; i < bytesRead; )
|
||||
{
|
||||
byte b = _asyncBuffer[i++];
|
||||
|
||||
if (!_raw && b == IAC)
|
||||
{
|
||||
// For now we just eat all option requests.
|
||||
b = _asyncBuffer[i++];
|
||||
|
||||
switch(b)
|
||||
{
|
||||
case WILL:
|
||||
case WONT:
|
||||
case DO:
|
||||
case DONT:
|
||||
i++;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_bufferLock.EnterWriteLock();
|
||||
_inputBuffer.Enqueue(b);
|
||||
_bufferLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// And start the next read
|
||||
//
|
||||
_tcpStream.BeginRead(
|
||||
_asyncBuffer,
|
||||
0,
|
||||
_asyncBuffer.Length,
|
||||
new AsyncCallback(AsyncReadCallback),
|
||||
null);
|
||||
|
||||
_streamLock.ExitReadLock();
|
||||
}
|
||||
|
||||
private const byte IAC = 255;
|
||||
private const byte WILL = 251;
|
||||
private const byte WONT = 252;
|
||||
private const byte DO = 253;
|
||||
private const byte DONT = 254;
|
||||
|
||||
private byte[] _asyncBuffer;
|
||||
|
||||
private Queue<byte> _inputBuffer;
|
||||
|
||||
private TcpClient _tcpClient;
|
||||
private NetworkStream _tcpStream;
|
||||
private bool _raw;
|
||||
private bool _abort;
|
||||
|
||||
private ReaderWriterLockSlim _bufferLock;
|
||||
private ReaderWriterLockSlim _streamLock;
|
||||
}
|
||||
}
|
||||
@ -15,15 +15,39 @@
|
||||
along with sImlac. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace imlac
|
||||
{
|
||||
// TODO: make memory size configurable.
|
||||
public class Memory
|
||||
{
|
||||
public Memory(ImlacSystem system)
|
||||
{
|
||||
_mem = new ushort[Size];
|
||||
{
|
||||
_system = system;
|
||||
SetMemorySize(0x2000);
|
||||
}
|
||||
|
||||
public void SetMemorySize(ushort size)
|
||||
{
|
||||
if (size != 0x1000 && size != 0x2000 && size != 0x4000)
|
||||
{
|
||||
throw new InvalidOperationException("Size must be 4k, 8k, or 16k.");
|
||||
}
|
||||
|
||||
_size = size;
|
||||
_sizeMask = (ushort)(size - 1);
|
||||
|
||||
_mem = new ushort[Size];
|
||||
|
||||
if (_system.Processor != null)
|
||||
{
|
||||
_system.Processor.InitializeCache();
|
||||
}
|
||||
|
||||
if (_system.DisplayProcessor != null)
|
||||
{
|
||||
_system.DisplayProcessor.InitializeCache();
|
||||
}
|
||||
}
|
||||
|
||||
public ushort Fetch(ushort address)
|
||||
@ -44,15 +68,18 @@ namespace imlac
|
||||
|
||||
public static ushort Size
|
||||
{
|
||||
get { return 8192; }
|
||||
get { return _size; }
|
||||
}
|
||||
|
||||
public static ushort SizeMask
|
||||
{
|
||||
get { return 0x1fff; }
|
||||
get { return _sizeMask; }
|
||||
}
|
||||
|
||||
private static ushort _size;
|
||||
private static ushort _sizeMask;
|
||||
|
||||
private ushort[] _mem;
|
||||
private ImlacSystem _system;
|
||||
}
|
||||
}
|
||||
}
|
||||
816
imlac/PDS1DisplayProcessor.cs
Normal file
816
imlac/PDS1DisplayProcessor.cs
Normal file
@ -0,0 +1,816 @@
|
||||
/*
|
||||
This file is part of sImlac.
|
||||
|
||||
sImlac is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
sImlac is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with sImlac. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using imlac.IO;
|
||||
using imlac.Debugger;
|
||||
|
||||
namespace imlac
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// DisplayProcessor implements the Display processor found in an Imlac PDS-1 with long vector hardware.
|
||||
/// </summary>
|
||||
public class PDS1DisplayProcessor : DisplayProcessorBase
|
||||
{
|
||||
public PDS1DisplayProcessor(ImlacSystem system) : base(system)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
_sgrModeOn = false;
|
||||
_sgrBeamOn = false;
|
||||
_sgrDJRMOn = false;
|
||||
}
|
||||
|
||||
public override void InitializeCache()
|
||||
{
|
||||
_instructionCache = new PDS1DisplayInstruction[Memory.Size];
|
||||
}
|
||||
|
||||
public override void InvalidateCache(ushort address)
|
||||
{
|
||||
_instructionCache[address & Memory.SizeMask] = null;
|
||||
}
|
||||
|
||||
public override string Disassemble(ushort address, DisplayProcessorMode mode, out int length)
|
||||
{
|
||||
//
|
||||
// Return a precached instruction if we have it due to previous execution
|
||||
// otherwise disassemble it now in the requested mode; this disassembly
|
||||
// does not get added to the cache.
|
||||
//
|
||||
if (_instructionCache[address & Memory.SizeMask] != null)
|
||||
{
|
||||
return _instructionCache[address & Memory.SizeMask].Disassemble(mode, _mem, out length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PDS1DisplayInstruction(_mem.Fetch(address), address, mode).Disassemble(mode, _mem, out length);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Clock()
|
||||
{
|
||||
_clocks++;
|
||||
|
||||
if (_clocks > _frameClocks40Hz)
|
||||
{
|
||||
_clocks = 0;
|
||||
_frameLatch = true;
|
||||
_system.Display.FrameDone();
|
||||
}
|
||||
|
||||
if (_state == ProcessorState.Halted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case DisplayProcessorMode.Processor:
|
||||
ExecuteProcessor();
|
||||
break;
|
||||
|
||||
case DisplayProcessorMode.Increment:
|
||||
ExecuteIncrement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override int[] GetHandledIOTs()
|
||||
{
|
||||
return _handledIOTs;
|
||||
}
|
||||
|
||||
public override void ExecuteIOT(int iotCode)
|
||||
{
|
||||
//
|
||||
// Dispatch the IOT instruction.
|
||||
//
|
||||
switch (iotCode)
|
||||
{
|
||||
case 0x03: // DLA:
|
||||
PC = _system.Processor.AC;
|
||||
|
||||
// this is for debugging only, we keep track of the load address
|
||||
// to make it easy to see where the main Display List starts
|
||||
_dpcEntry = PC;
|
||||
break;
|
||||
|
||||
case 0x0a: // Halt display processor
|
||||
HaltProcessor();
|
||||
break;
|
||||
|
||||
case 0x39: // Clear display 40Hz sync latch
|
||||
_frameLatch = false;
|
||||
break;
|
||||
|
||||
case 0xc4: // clear halt state
|
||||
_halted = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers.SignalError(LogType.DisplayProcessor, "Unhandled IOT {0}", Helpers.ToOctal((ushort)iotCode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteProcessor()
|
||||
{
|
||||
PDS1DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Processor);
|
||||
instruction.UsageMode = DisplayProcessorMode.Processor;
|
||||
|
||||
switch (instruction.Opcode)
|
||||
{
|
||||
case DisplayOpcode.DEIM:
|
||||
_mode = DisplayProcessorMode.Increment;
|
||||
_immediateWord = instruction.Data;
|
||||
_immediateHalf = ImmediateHalf.Second;
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Enter increment mode");
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DJMP:
|
||||
if (!_dadr)
|
||||
{
|
||||
// DADR off, use only 12 bits
|
||||
_pc = (ushort)((instruction.Data & 0xfff) | _block);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pc = (ushort)(instruction.Data | _block);
|
||||
}
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DJMS:
|
||||
Push();
|
||||
|
||||
if (!_dadr)
|
||||
{
|
||||
// DADR off, use only 12 bits
|
||||
_pc = (ushort)((instruction.Data & 0xfff) | _block);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pc = (ushort)(instruction.Data | _block);
|
||||
}
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DOPR:
|
||||
// Each of bits 4-11 can be combined in any fashion
|
||||
// to do a number of operations simultaneously; we walk the bits
|
||||
// and perform the operations as set.
|
||||
if ((instruction.Data & 0x800) == 0)
|
||||
{
|
||||
// DHLT -- halt the display processor. other micro-ops in this
|
||||
// instruction are still run.
|
||||
HaltProcessor();
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x400) != 0)
|
||||
{
|
||||
// HV Sync; this is currently a no-op, not much to do in emulation.
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "HV Sync");
|
||||
|
||||
MoveAbsolute(X, Y, DrawingMode.Off);
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x200) != 0)
|
||||
{
|
||||
// DIXM -- increment X DAC MSB
|
||||
X += 0x20;
|
||||
MoveAbsolute(X, Y, DrawingMode.Off);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DIXM, X is now {0}", X);
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x100) != 0)
|
||||
{
|
||||
// DIYM -- increment Y DAC MSB
|
||||
Y += 0x20;
|
||||
MoveAbsolute(X, Y, DrawingMode.Off);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DIYM, Y is now {0}", Y);
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x80) != 0)
|
||||
{
|
||||
// DDXM - decrement X DAC MSB
|
||||
X -= 0x20;
|
||||
MoveAbsolute(X, Y, DrawingMode.Off);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDXM, X is now {0}", X);
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x40) != 0)
|
||||
{
|
||||
// DDYM - decrement y DAC MSB
|
||||
Y -= 0x20;
|
||||
MoveAbsolute(X, Y, DrawingMode.Off);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDYM, Y is now {0}", Y);
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x20) != 0)
|
||||
{
|
||||
// DRJM - return from display subroutine
|
||||
ReturnFromDisplaySubroutine();
|
||||
_pc--; // hack (we add +1 at the end...)
|
||||
}
|
||||
|
||||
if ((instruction.Data & 0x10) != 0)
|
||||
{
|
||||
// DDSP -- intensify point on screen for 1.8us (one instruction)
|
||||
// at the current position.
|
||||
DrawPoint(X, Y);
|
||||
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "DDSP at {0},{1}", X, Y);
|
||||
}
|
||||
|
||||
// F/C ops:
|
||||
int f = (instruction.Data & 0xc) >> 2;
|
||||
int c = instruction.Data & 0x3;
|
||||
|
||||
switch (f)
|
||||
{
|
||||
case 0x0:
|
||||
// if bit 15 is set, the MIT mods flip the DADR bit.
|
||||
if (Configuration.MITMode && (c == 1))
|
||||
{
|
||||
_dadr = !_dadr;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
// Set scale based on C
|
||||
switch (c)
|
||||
{
|
||||
case 0:
|
||||
_scale = 1.0f;
|
||||
break;
|
||||
|
||||
default:
|
||||
_scale = c;
|
||||
break;
|
||||
}
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Scale set to {0}", _scale);
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
if (!Configuration.MITMode)
|
||||
{
|
||||
_block = (ushort)(c << 12);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
// TODO: light pen sensitize
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Light pen, stub!");
|
||||
break;
|
||||
}
|
||||
|
||||
_pc++;
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DLXA:
|
||||
X = instruction.Data << 1;
|
||||
|
||||
DrawingMode mode;
|
||||
if (_sgrModeOn && _sgrBeamOn)
|
||||
{
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 X set to {0}", X);
|
||||
mode = DrawingMode.SGR1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = DrawingMode.Off;
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "X set to {0}", X);
|
||||
}
|
||||
|
||||
MoveAbsolute(X, Y, mode);
|
||||
|
||||
if (_sgrDJRMOn)
|
||||
{
|
||||
ReturnFromDisplaySubroutine();
|
||||
}
|
||||
else
|
||||
{
|
||||
_pc++;
|
||||
}
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DLYA:
|
||||
Y = instruction.Data << 1;
|
||||
|
||||
if (_sgrModeOn && _sgrBeamOn)
|
||||
{
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 Y set to {0}", Y);
|
||||
mode = DrawingMode.SGR1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Y set to {0}", Y);
|
||||
mode = DrawingMode.Off;
|
||||
}
|
||||
|
||||
MoveAbsolute(X, Y, mode);
|
||||
|
||||
if (_sgrDJRMOn)
|
||||
{
|
||||
ReturnFromDisplaySubroutine();
|
||||
}
|
||||
else
|
||||
{
|
||||
_pc++;
|
||||
}
|
||||
break;
|
||||
|
||||
case DisplayOpcode.DLVH:
|
||||
DrawLongVector(instruction.Data);
|
||||
break;
|
||||
|
||||
case DisplayOpcode.SGR1:
|
||||
_sgrModeOn = (instruction.Data & 0x1) != 0;
|
||||
_sgrDJRMOn = (instruction.Data & 0x2) != 0;
|
||||
_sgrBeamOn = (instruction.Data & 0x4) != 0;
|
||||
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "SGR-1 instruction: Enter {0} BeamOn {1} DRJM {2}", _sgrModeOn, _sgrBeamOn, _sgrDJRMOn);
|
||||
_pc++;
|
||||
break;
|
||||
|
||||
case DisplayOpcode.Invalid:
|
||||
Helpers.SignalError(
|
||||
LogType.DisplayProcessor,
|
||||
"Execution of invalid display processor instruction {0}",
|
||||
Helpers.ToOctal(instruction.Data));
|
||||
_pc++;
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers.SignalError(
|
||||
LogType.DisplayProcessor,
|
||||
"Unimplemented Display Processor Opcode {0}, operands {1}",
|
||||
Helpers.ToOctal((ushort)instruction.Opcode),
|
||||
Helpers.ToOctal(instruction.Data));
|
||||
break;
|
||||
}
|
||||
|
||||
// If the next instruction has a breakpoint set we'll halt at this point, before executing it.
|
||||
if (BreakpointManager.TestBreakpoint(BreakpointType.Display, _pc))
|
||||
{
|
||||
_state = ProcessorState.BreakpointHalt;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteIncrement()
|
||||
{
|
||||
int halfWord = _immediateHalf == ImmediateHalf.First ? (_immediateWord & 0xff00) >> 8 : (_immediateWord & 0xff);
|
||||
|
||||
int newX = (int)X;
|
||||
int newY = (int)Y;
|
||||
|
||||
// translate the half word to vector movements or escapes
|
||||
if ((halfWord & 0x80) == 0)
|
||||
{
|
||||
if ((halfWord & 0x40) != 0)
|
||||
{
|
||||
// Escape code
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment mode escape on halfword {0}", _immediateHalf);
|
||||
_mode = DisplayProcessorMode.Processor;
|
||||
_pc++; // move to next word
|
||||
|
||||
// Moved this into this check (not sure it makes sense to do a DJMS when not escaped from Increment mode)
|
||||
if ((halfWord & 0x20) != 0)
|
||||
{
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment mode return from subroutine.");
|
||||
ReturnFromDisplaySubroutine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stay in increment mode.
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment instruction, non-drawing.");
|
||||
MoveToNextHalfWord();
|
||||
}
|
||||
|
||||
if ((halfWord & 0x10) != 0)
|
||||
{
|
||||
newX += 0x20;
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment X MSB, X is now {0}", X);
|
||||
}
|
||||
|
||||
if ((halfWord & 0x08) != 0)
|
||||
{
|
||||
newX = newX & (0xffe0);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset X LSB, X is now {0}", X);
|
||||
}
|
||||
|
||||
if ((halfWord & 0x02) != 0)
|
||||
{
|
||||
newY += 0x20;
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment Y MSB, Y is now {0}", Y);
|
||||
}
|
||||
|
||||
if ((halfWord & 0x01) != 0)
|
||||
{
|
||||
newY = newY & (0xffe0);
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset Y LSB, Y is now {0}", Y);
|
||||
}
|
||||
|
||||
MoveAbsolute(newX, newY, DrawingMode.Off);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
|
||||
int xMag = (int)(((halfWord & 0x18) >> 3) * _scale);
|
||||
|
||||
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
|
||||
int yMag = (int)((halfWord & 0x03) * _scale);
|
||||
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor,
|
||||
"Inc mode ({0}:{1}), x={2} y={3} dx={4} dy={5} beamon {6}",
|
||||
Helpers.ToOctal((ushort)_pc),
|
||||
Helpers.ToOctal((ushort)halfWord),
|
||||
newX,
|
||||
newY,
|
||||
xSign * xMag,
|
||||
ySign * yMag,
|
||||
(halfWord & 0x40) != 0);
|
||||
|
||||
newX = (int)(newX + xSign * xMag * 2);
|
||||
newY = (int)(newY + ySign * yMag * 2);
|
||||
|
||||
MoveAbsolute(newX, newY, (halfWord & 0x40) == 0 ? DrawingMode.Off : DrawingMode.Dotted);
|
||||
|
||||
MoveToNextHalfWord();
|
||||
}
|
||||
|
||||
// Assign back to X, Y registers; clipping into range.
|
||||
X = newX;
|
||||
Y = newY;
|
||||
|
||||
// If the next instruction has a breakpoint set we'll halt at this point, before executing it.
|
||||
if (_immediateHalf == ImmediateHalf.First && BreakpointManager.TestBreakpoint(BreakpointType.Display, _pc))
|
||||
{
|
||||
_state = ProcessorState.BreakpointHalt;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToNextHalfWord()
|
||||
{
|
||||
if (_immediateHalf == ImmediateHalf.Second)
|
||||
{
|
||||
_pc++;
|
||||
_immediateWord = _mem.Fetch(_pc);
|
||||
_immediateHalf = ImmediateHalf.First;
|
||||
|
||||
// Update the instruction cache with the type of instruction (to aid in debugging).
|
||||
PDS1DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Increment);
|
||||
}
|
||||
else
|
||||
{
|
||||
_immediateHalf = ImmediateHalf.Second;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLongVector(ushort word0)
|
||||
{
|
||||
//
|
||||
// A Long Vector instruction is 3 words long:
|
||||
// Word 0: upper 4 bits indicate the opcode (4), lower 12 specify N-M
|
||||
// Word 1: upper 3 bits specify beam options (dotted, solid, etc) and the lower 12 specify the larger increment "M"
|
||||
// Word 2: upper 3 bits specify signs, lower 12 specify the smaller increment "N"
|
||||
// M is the larger absolute value between dX and dY
|
||||
// N is the smaller.
|
||||
|
||||
//
|
||||
// Unsure at the moment what the N-M bits are for (I'm guessing they're there to help the processor figure things out).
|
||||
// Also unsure what bits are used in the 12 bits for N and M (the DACs are only 11-bits, but normally only 10 can be specified)...
|
||||
//
|
||||
ushort word1 = _mem.Fetch(++_pc);
|
||||
ushort word2 = _mem.Fetch(++_pc);
|
||||
|
||||
uint M = (uint)(word1 & 0x3ff);
|
||||
uint N = (uint)(word2 & 0x3ff);
|
||||
|
||||
bool beamOn = (word1 & 0x2000) != 0;
|
||||
bool dotted = (word1 & 0x4000) != 0;
|
||||
|
||||
int dySign = (word2 & 0x2000) != 0 ? -1 : 1;
|
||||
int dxSign = (word2 & 0x4000) != 0 ? -1 : 1;
|
||||
bool dyGreater = (word2 & 0x1000) != 0;
|
||||
|
||||
uint dx = 0;
|
||||
uint dy = 0;
|
||||
|
||||
if (dyGreater)
|
||||
{
|
||||
dy = M;
|
||||
dx = N;
|
||||
}
|
||||
else
|
||||
{
|
||||
dx = M;
|
||||
dy = N;
|
||||
}
|
||||
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector x={0} y={1} dx={2} dy={3} beamOn {4} dotted {5}", X, Y, dx * dxSign, dy * dySign, beamOn, dotted);
|
||||
|
||||
// * 2 for translation to 11-bit space
|
||||
// The docs don't call this out, but the scale setting used in increment mode appears to apply
|
||||
// to the LVH vectors as well. (Maze appears to rely on this.)
|
||||
int newX = (int)(X + (dx * dxSign) * 2 * _scale);
|
||||
int newY = (int)(Y + (dy * dySign) * 2 * _scale);
|
||||
|
||||
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector, move complete - x={0} y={1}", newX, newY, dx * dxSign, dy * dySign, beamOn, dotted);
|
||||
|
||||
MoveAbsolute(newX, newY, beamOn ? (dotted ? DrawingMode.Dotted : DrawingMode.Normal) : DrawingMode.Off);
|
||||
|
||||
// Assign back to X, Y registers; clipping into range.
|
||||
X = newX;
|
||||
Y = newY;
|
||||
|
||||
_pc++;
|
||||
}
|
||||
|
||||
private void ReturnFromDisplaySubroutine()
|
||||
{
|
||||
Pop();
|
||||
}
|
||||
|
||||
private PDS1DisplayInstruction GetCachedInstruction(ushort address, DisplayProcessorMode mode)
|
||||
{
|
||||
if (_instructionCache[address & Memory.SizeMask] == null)
|
||||
{
|
||||
_instructionCache[address & Memory.SizeMask] = new PDS1DisplayInstruction(_mem.Fetch(address), address, mode);
|
||||
}
|
||||
|
||||
return _instructionCache[address & Memory.SizeMask];
|
||||
}
|
||||
|
||||
// SGR-1 mode switches
|
||||
private bool _sgrModeOn;
|
||||
private bool _sgrDJRMOn;
|
||||
private bool _sgrBeamOn;
|
||||
|
||||
protected const int _frameClocks40Hz = 13889; // cycles per 1/40th of a second (rounded up) for a 1.8uS clock speed.
|
||||
|
||||
private PDS1DisplayInstruction[] _instructionCache;
|
||||
|
||||
private readonly int[] _handledIOTs = { 0x3, 0xa, 0x39, 0xc4 };
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PDS-1 Display instruction decoder and disassembler.
|
||||
/// </summary>
|
||||
private class PDS1DisplayInstruction : DisplayInstructionBase
|
||||
{
|
||||
public PDS1DisplayInstruction(ushort word, ushort address, DisplayProcessorMode mode) : base (word, address, mode)
|
||||
{
|
||||
}
|
||||
|
||||
public override string Disassemble(DisplayProcessorMode mode, Memory mem, out int length)
|
||||
{
|
||||
if (mode == DisplayProcessorMode.Indeterminate)
|
||||
{
|
||||
mode = _usageMode;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case DisplayProcessorMode.Increment:
|
||||
length = 1;
|
||||
return DisassembleIncrement();
|
||||
|
||||
case DisplayProcessorMode.Processor:
|
||||
return DisassembleProcessor(mem, out length);
|
||||
|
||||
case DisplayProcessorMode.Indeterminate:
|
||||
length = 1;
|
||||
return "Indeterminate";
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format("{0} is not a supported disassembly mode for this processor.", mode));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Decode()
|
||||
{
|
||||
if (_usageMode == DisplayProcessorMode.Processor)
|
||||
{
|
||||
DecodeProcessor();
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeProcessor()
|
||||
{
|
||||
int op = (_word & 0x7000) >> 12;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case 0x00:
|
||||
// opr code
|
||||
_opcode = DisplayOpcode.DOPR;
|
||||
_data = (ushort)(_word & 0xfff);
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
_opcode = DisplayOpcode.DLXA;
|
||||
_data = (ushort)(_word & 0x3ff);
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
_opcode = DisplayOpcode.DLYA;
|
||||
_data = (ushort)(_word & 0x3ff);
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
_opcode = DisplayOpcode.DEIM;
|
||||
_data = (ushort)(_word & 0xff);
|
||||
|
||||
if ((_word & 0xff00) == 0x3800)
|
||||
{
|
||||
Console.WriteLine("PPM-1 not implemented (instr {0})", Helpers.ToOctal(_word));
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
_opcode = DisplayOpcode.DLVH;
|
||||
_data = (ushort)(_word & 0xfff);
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
_opcode = DisplayOpcode.DJMS;
|
||||
_data = (ushort)(_word & 0xfff);
|
||||
|
||||
if (Configuration.MITMode && (_word & 0x8000) != 0)
|
||||
{
|
||||
// MIT's mod takes the MSB of the address from the MSB of the instruction word
|
||||
_data |= 0x1000;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
_opcode = DisplayOpcode.DJMP;
|
||||
_data = (ushort)(_word & 0xfff);
|
||||
|
||||
if (Configuration.MITMode && (_word & 0x8000) != 0)
|
||||
{
|
||||
// MIT's mod takes the MSB of the address from the MSB of the instruction word
|
||||
_data |= 0x1000;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
DecodeExtendedInstruction(_word);
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers.SignalError(
|
||||
LogType.DisplayProcessor,
|
||||
"Unhandled Display Processor Mode instruction {0}",
|
||||
Helpers.ToOctal(_word));
|
||||
|
||||
_opcode = DisplayOpcode.Invalid;
|
||||
_data = _word;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeExtendedInstruction(ushort word)
|
||||
{
|
||||
int op = (word & 0x1f8) >> 3;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case 0x36:
|
||||
case 0x37:
|
||||
_opcode = DisplayOpcode.ASG1;
|
||||
break;
|
||||
|
||||
case 0x3a:
|
||||
case 0x3b:
|
||||
_opcode = DisplayOpcode.VIC1;
|
||||
break;
|
||||
|
||||
case 0x3c:
|
||||
case 0x3d:
|
||||
_opcode = DisplayOpcode.MCI1;
|
||||
break;
|
||||
|
||||
case 0x3e:
|
||||
_opcode = DisplayOpcode.STI1;
|
||||
break;
|
||||
|
||||
case 0x3f:
|
||||
_opcode = DisplayOpcode.SGR1;
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers.SignalError(
|
||||
LogType.DisplayProcessor,
|
||||
"Unhandled extended Display Processor Mode instruction {0}",
|
||||
Helpers.ToOctal(word));
|
||||
|
||||
_opcode = DisplayOpcode.Invalid;
|
||||
_data = word;
|
||||
break;
|
||||
}
|
||||
|
||||
_data = (ushort)(word & 0x7);
|
||||
|
||||
}
|
||||
|
||||
private void DecodeImmediate()
|
||||
{
|
||||
// TODO: eventually actually precache movement calculations.
|
||||
}
|
||||
|
||||
protected override string DisassembleExtended(Memory mem, out int length)
|
||||
{
|
||||
string ret = String.Empty;
|
||||
switch (_opcode)
|
||||
{
|
||||
case DisplayOpcode.DLVH:
|
||||
length = 3;
|
||||
ret = DisassembleLongVector(mem);
|
||||
break;
|
||||
|
||||
default:
|
||||
length = 1;
|
||||
// Handle as yet not-special-cased opcodes
|
||||
ret = String.Format("{0} {1}", _opcode, Helpers.ToOctal(_data));
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private string DisassembleLongVector(Memory mem)
|
||||
{
|
||||
//
|
||||
// A Long Vector instruction is 3 words long:
|
||||
// Word 0: upper 4 bits indicate the opcode (4), lower 12 specify N-M
|
||||
// Word 1: upper 3 bits specify beam options (dotted, solid, etc) and the lower 12 specify the larger increment "M"
|
||||
// Word 2: upper 3 bits specify signs, lower 12 specify the smaller increment "N"
|
||||
// M is the larger absolute value between dX and dY
|
||||
// N is the smaller.
|
||||
//
|
||||
// TODO: Would make sense to precache this during decoding, would require
|
||||
// modifications to cache invalidation logic.
|
||||
ushort word1 = mem.Fetch((ushort)(_address + 1));
|
||||
ushort word2 = mem.Fetch((ushort)(_address + 2));
|
||||
|
||||
uint M = (uint)(word1 & 0x3ff);
|
||||
uint N = (uint)(word2 & 0x3ff);
|
||||
|
||||
bool beamOn = (word1 & 0x2000) != 0;
|
||||
bool dotted = (word1 & 0x4000) != 0;
|
||||
|
||||
int dySign = (word2 & 0x2000) != 0 ? -1 : 1;
|
||||
int dxSign = (word2 & 0x4000) != 0 ? -1 : 1;
|
||||
bool dyGreater = (word2 & 0x1000) != 0;
|
||||
|
||||
uint dx = 0;
|
||||
uint dy = 0;
|
||||
|
||||
if (dyGreater)
|
||||
{
|
||||
dy = M;
|
||||
dx = N;
|
||||
}
|
||||
else
|
||||
{
|
||||
dx = M;
|
||||
dy = N;
|
||||
}
|
||||
|
||||
return String.Format("DLVH ({0},{1}) {2} {3}",
|
||||
dx * dxSign, dy * dySign,
|
||||
beamOn ? "ON" : "OFF",
|
||||
dotted ? "DOTTED" : String.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1043
imlac/PDS4DisplayProcessor.cs
Normal file
1043
imlac/PDS4DisplayProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
|
||||
[assembly: AssemblyProduct("sImlac")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ using System.Threading;
|
||||
using imlac.IO;
|
||||
using SDL2;
|
||||
using static SDL2.SDL;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace imlac
|
||||
{
|
||||
@ -39,6 +40,9 @@ namespace imlac
|
||||
//
|
||||
public class SDLConsole : IImlacConsole
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
static extern bool SetProcessDPIAware();
|
||||
|
||||
public SDLConsole(float scaleFactor)
|
||||
{
|
||||
if (scaleFactor <= 0)
|
||||
@ -51,27 +55,29 @@ namespace imlac
|
||||
|
||||
_lock = new ReaderWriterLockSlim();
|
||||
_keyLatchedLock = new ReaderWriterLockSlim();
|
||||
_syncEvent = new AutoResetEvent(false);
|
||||
|
||||
_frame = 0;
|
||||
_frame = 0;
|
||||
|
||||
try
|
||||
{
|
||||
_frameTimer = new FrameTimer(40);
|
||||
}
|
||||
catch(Exception)
|
||||
catch
|
||||
{
|
||||
// Unable to initialize frame timer, we will not be able
|
||||
// to throttle execution.
|
||||
_frameTimer = null;
|
||||
}
|
||||
|
||||
_timer = new HighResTimer();
|
||||
|
||||
_fullScreen = false;
|
||||
|
||||
_displayList = new List<Vector>(_displayListSize);
|
||||
_displayListIndex = 0;
|
||||
|
||||
_blink = false;
|
||||
_intensity = 1.0f;
|
||||
|
||||
//
|
||||
// Prepopulate the display list with Vectors. Only those used in the current frame are
|
||||
// actually rendered, we prepopulate the list to prevent having to cons up new ones
|
||||
@ -79,27 +85,26 @@ namespace imlac
|
||||
//
|
||||
for (int i = 0; i < _displayListSize; i++)
|
||||
{
|
||||
_displayList.Add(new Vector(DrawingMode.Off, 0, 0, 0, 0));
|
||||
_displayList.Add(new Vector(DrawingMode.Off, 1.0f, false, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
BuildKeyMappings();
|
||||
InvokeDisplayThread();
|
||||
BuildKeyMappings();
|
||||
}
|
||||
|
||||
public bool IsKeyPressed
|
||||
public bool NewKeyPressed
|
||||
{
|
||||
get
|
||||
{
|
||||
_keyLatchedLock.EnterReadLock();
|
||||
bool latched = _keyLatched;
|
||||
bool pressed = _newKeyPressed;
|
||||
_keyLatchedLock.ExitReadLock();
|
||||
return latched;
|
||||
return pressed;
|
||||
}
|
||||
}
|
||||
|
||||
public ImlacKey Key
|
||||
{
|
||||
get { return _latchedKeyCode; }
|
||||
get { return _currentKeyCode; }
|
||||
}
|
||||
|
||||
public ImlacKeyModifiers KeyModifiers
|
||||
@ -109,8 +114,8 @@ namespace imlac
|
||||
|
||||
public void UnlatchKey()
|
||||
{
|
||||
_keyLatchedLock.EnterReadLock();
|
||||
_keyLatched = false;
|
||||
_keyLatchedLock.EnterReadLock();
|
||||
_newKeyPressed = false;
|
||||
_keyLatchedLock.ExitReadLock();
|
||||
}
|
||||
|
||||
@ -119,6 +124,16 @@ namespace imlac
|
||||
get { return (ushort)_dataSwitches; }
|
||||
}
|
||||
|
||||
public int MouseX
|
||||
{
|
||||
get { return _mouseX; }
|
||||
}
|
||||
|
||||
public int MouseY
|
||||
{
|
||||
get { return _mouseY; }
|
||||
}
|
||||
|
||||
public bool ThrottleFramerate
|
||||
{
|
||||
get { return _throttleFramerate; }
|
||||
@ -150,6 +165,28 @@ namespace imlac
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
//
|
||||
// Tell the SDL event loop to wrap things up.
|
||||
//
|
||||
_userEvent.type = SDL.SDL_EventType.SDL_QUIT;
|
||||
SDL.SDL_PushEvent(ref _userEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the screen to be ready for access.
|
||||
/// </summary>
|
||||
public void WaitForSync()
|
||||
{
|
||||
_syncEvent.WaitOne();
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
ShowInternal();
|
||||
}
|
||||
|
||||
public void ClearDisplay()
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
@ -169,6 +206,16 @@ namespace imlac
|
||||
UpdateDisplayScale();
|
||||
}
|
||||
|
||||
public void SetIntensity(int intensity)
|
||||
{
|
||||
_intensity = ((float)intensity / 16.0f);
|
||||
}
|
||||
|
||||
public void SetBlink(bool on)
|
||||
{
|
||||
_blink = on;
|
||||
}
|
||||
|
||||
public void MapDataSwitch(uint switchNumber, VKeys key)
|
||||
{
|
||||
_dataSwitchMappings[switchNumber] = key;
|
||||
@ -181,6 +228,16 @@ namespace imlac
|
||||
|
||||
private void InitializeSDL()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set high DPI awareness on Windows platforms, if this fails it's OK.
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
DoUpdateDisplayScale();
|
||||
|
||||
int retVal = 0;
|
||||
@ -197,13 +254,15 @@ namespace imlac
|
||||
throw new InvalidOperationException("SDL_SetHint failed to set scale quality.");
|
||||
}
|
||||
|
||||
SDL.SDL_WindowFlags flags = SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN;
|
||||
|
||||
_sdlWindow = SDL.SDL_CreateWindow(
|
||||
"Imlac PDS-1",
|
||||
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||
_xResolution,
|
||||
_yResolution,
|
||||
_fullScreen ? SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP | SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN : SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);
|
||||
_fullScreen ? SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP | flags : flags);
|
||||
|
||||
|
||||
if (_sdlWindow == IntPtr.Zero)
|
||||
@ -226,6 +285,11 @@ namespace imlac
|
||||
|
||||
SDL.SDL_SetRenderDrawBlendMode(_sdlRenderer, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Clear the screen.
|
||||
SDL.SDL_SetRenderDrawColor(_sdlRenderer, 0, 0, 0, 0xff);
|
||||
SDL.SDL_RenderFillRect(_sdlRenderer, ref _displayRect);
|
||||
SDL.SDL_RenderPresent(_sdlRenderer);
|
||||
|
||||
// Register a User event for rendering and resizing.
|
||||
_userEventType = SDL.SDL_RegisterEvents(1);
|
||||
_userEvent = new SDL.SDL_Event();
|
||||
@ -256,14 +320,33 @@ namespace imlac
|
||||
|
||||
if (_sdlWindow != null)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
SDL.SDL_SetWindowSize(_sdlWindow, _xResolution, _yResolution);
|
||||
SDL.SDL_SetWindowFullscreen(_sdlWindow,
|
||||
_fullScreen ? (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
|
||||
//
|
||||
// Calculate x/y offsets to center display rendering
|
||||
///
|
||||
int newWidth = 0;
|
||||
int newHeight = 0;
|
||||
SDL.SDL_GetWindowSize(_sdlWindow, out newWidth, out newHeight);
|
||||
|
||||
_xOffset = Math.Max(0, (newWidth - _xResolution) / 2);
|
||||
_yOffset = Math.Max(0, (newHeight - _yResolution) / 2);
|
||||
|
||||
_displayRect.h = newHeight == 0 ? _yResolution : newHeight;
|
||||
_displayRect.w = newWidth == 0 ? _xResolution : newWidth;
|
||||
|
||||
// Clear the display list so no garbage remains.
|
||||
_displayListIndex = 0;
|
||||
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveAbsolute(uint x, uint y, DrawingMode mode)
|
||||
{
|
||||
public void MoveAbsolute(int x, int y, DrawingMode mode)
|
||||
{
|
||||
//
|
||||
// Take coordinates as an 11-bit quantity (0-2048) even though we may not be displaying the full resolution.
|
||||
//
|
||||
@ -271,12 +354,16 @@ namespace imlac
|
||||
{
|
||||
AddNewVector(mode, _x, _y, x, y);
|
||||
}
|
||||
else if(Configuration.ShowInvisibleVectors)
|
||||
{
|
||||
AddNewVector(DrawingMode.Debug, _x, _y, x, y);
|
||||
}
|
||||
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
|
||||
public void DrawPoint(uint x, uint y)
|
||||
public void DrawPoint(int x, int y)
|
||||
{
|
||||
_x = x;
|
||||
_y = y;
|
||||
@ -296,24 +383,15 @@ namespace imlac
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeDisplayThread()
|
||||
{
|
||||
_displayThread = new System.Threading.Thread(new System.Threading.ThreadStart(DisplayThread));
|
||||
_displayThread.Start();
|
||||
|
||||
//
|
||||
// Wait until the display has been initialized.
|
||||
//
|
||||
_syncEvent = new AutoResetEvent(false);
|
||||
_syncEvent.WaitOne();
|
||||
}
|
||||
|
||||
private void DisplayThread()
|
||||
private void ShowInternal()
|
||||
{
|
||||
InitializeSDL();
|
||||
|
||||
// Signal that the display is ready.
|
||||
_syncEvent.Set();
|
||||
|
||||
while (true)
|
||||
|
||||
bool quit = false;
|
||||
while (!quit)
|
||||
{
|
||||
SDL.SDL_Event e;
|
||||
|
||||
@ -336,23 +414,36 @@ namespace imlac
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_QUIT:
|
||||
quit = true;
|
||||
return;
|
||||
|
||||
case SDL.SDL_EventType.SDL_KEYDOWN:
|
||||
SdlKeyDown(e.key.keysym.sym);
|
||||
SdlKeyDown(e.key.keysym.sym, e.key.repeat > 0);
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_KEYUP:
|
||||
SdlKeyUp(e.key.keysym.sym);
|
||||
break;
|
||||
|
||||
|
||||
case SDL.SDL_EventType.SDL_MOUSEMOTION:
|
||||
SdlMouseMove(e.motion.x, e.motion.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDL.SDL_Delay(0);
|
||||
}
|
||||
|
||||
//
|
||||
// Done, clean up.
|
||||
//
|
||||
SDL.SDL_DestroyRenderer(_sdlRenderer);
|
||||
SDL.SDL_DestroyWindow(_sdlWindow);
|
||||
SDL.SDL_Quit();
|
||||
}
|
||||
|
||||
private void SdlKeyDown(SDL_Keycode key)
|
||||
private void SdlKeyDown(SDL_Keycode key, bool repeat)
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
@ -375,16 +466,40 @@ namespace imlac
|
||||
|
||||
UpdateDataSwitches(key, true /* key down */);
|
||||
|
||||
if (key == SDL_Keycode.SDLK_INSERT)
|
||||
if (key == SDL_Keycode.SDLK_INSERT && !repeat)
|
||||
{
|
||||
FullScreenToggle();
|
||||
break;
|
||||
}
|
||||
|
||||
// If this is a repeated keystroke, we'll only log it if
|
||||
// the Repeat key (Alt) is being held down.
|
||||
if (repeat && (_keyModifiers & ImlacKeyModifiers.Rept) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_keyLatchedLock.EnterWriteLock();
|
||||
|
||||
_keyLatched = true;
|
||||
_latchedKeyCode = TranslateKeyCode(key);
|
||||
// Only accept this key if we're not waiting
|
||||
// for the Imlac to read the last one.
|
||||
if (!_newKeyPressed)
|
||||
{
|
||||
ImlacKey newCode = TranslateKeyCode(key);
|
||||
|
||||
// Only latch valid keys.
|
||||
if (newCode != ImlacKey.Invalid)
|
||||
{
|
||||
_newKeyPressed = true;
|
||||
_currentKeyCode = newCode;
|
||||
}
|
||||
}
|
||||
|
||||
if (!repeat)
|
||||
{
|
||||
// Only count new keystrokes.
|
||||
_keyPressedCount++;
|
||||
}
|
||||
_keyLatchedLock.ExitWriteLock();
|
||||
break;
|
||||
}
|
||||
@ -410,16 +525,34 @@ namespace imlac
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
UpdateDataSwitches(key, false /* key down */);
|
||||
UpdateDataSwitches(key, false /* key up */);
|
||||
|
||||
_keyLatchedLock.EnterWriteLock();
|
||||
_latchedKeyCode = ImlacKey.Invalid;
|
||||
|
||||
//
|
||||
// Decrement our pressed keycount, when this reaches zero
|
||||
// this means the last pressed key has been released and we
|
||||
// can set the keycode to None to indicate no keys being down.
|
||||
// (This avoids issues with n-key rollover.)
|
||||
//
|
||||
_keyPressedCount--;
|
||||
if (_keyPressedCount == 0)
|
||||
{
|
||||
_currentKeyCode = ImlacKey.None;
|
||||
}
|
||||
|
||||
_keyLatchedLock.ExitWriteLock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SdlMouseMove(int x, int y)
|
||||
{
|
||||
_mouseX = x;
|
||||
_mouseY = y;
|
||||
}
|
||||
|
||||
|
||||
void FullScreenToggle()
|
||||
{
|
||||
_fullScreen = !_fullScreen;
|
||||
@ -440,23 +573,20 @@ namespace imlac
|
||||
//
|
||||
// Wait for rendering to complete before returning.
|
||||
//
|
||||
_syncEvent.WaitOne();
|
||||
WaitForSync();
|
||||
}
|
||||
|
||||
public void DoRender(bool completeFrame)
|
||||
{
|
||||
{
|
||||
|
||||
// Draw the current set of vectors
|
||||
_lock.EnterReadLock();
|
||||
_frame++;
|
||||
|
||||
if (_frame == 60)
|
||||
// Blinking items are on for 16 frames, off for 16.
|
||||
if ((_frame % 16) == 0)
|
||||
{
|
||||
double currentTime = _timer.GetCurrentTime();
|
||||
double fps = _frame / ((currentTime - _lastTime));
|
||||
_lastTime = currentTime;
|
||||
_frame = 0;
|
||||
|
||||
SDL.SDL_SetWindowTitle(_sdlWindow, String.Format("Imlac PDS-1 fps {0}", fps));
|
||||
_frameBlink = !_frameBlink;
|
||||
}
|
||||
|
||||
//
|
||||
@ -479,7 +609,7 @@ namespace imlac
|
||||
// And draw in this frame's vectors
|
||||
for (int i = 0; i < _displayListIndex; i++)
|
||||
{
|
||||
_displayList[i].Draw(_sdlRenderer);
|
||||
_displayList[i].Draw(_sdlRenderer, _frameBlink);
|
||||
}
|
||||
|
||||
SDL.SDL_RenderPresent(_sdlRenderer);
|
||||
@ -497,22 +627,22 @@ namespace imlac
|
||||
_syncEvent.Set();
|
||||
}
|
||||
|
||||
private void AddNewVector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
|
||||
private void AddNewVector(DrawingMode mode, int startX, int startY, int endX, int 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);
|
||||
startX = (int)(startX * _scaleFactor + _xOffset);
|
||||
startY = (int)(startY *_scaleFactor - _yOffset);
|
||||
endX = (int)(endX * _scaleFactor + _xOffset);
|
||||
endY = (int)(endY * _scaleFactor - _yOffset);
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
Vector newVector = _displayList[_displayListIndex];
|
||||
newVector.Modify(mode, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
|
||||
newVector.Modify(mode, _intensity, _blink, (short)startX, (short)(_yResolution - startY), (short)endX, (short)(_yResolution - endY));
|
||||
_displayListIndex++;
|
||||
|
||||
_lock.ExitWriteLock();
|
||||
@ -525,9 +655,17 @@ namespace imlac
|
||||
|
||||
private class Vector
|
||||
{
|
||||
public Vector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
|
||||
static Vector()
|
||||
{
|
||||
_rng = new Random();
|
||||
}
|
||||
|
||||
public Vector(DrawingMode mode, float intensity, bool blink, uint startX, uint startY, uint endX, uint endY)
|
||||
{
|
||||
_mode = mode;
|
||||
_intensity = intensity;
|
||||
_blink = blink;
|
||||
|
||||
_x1 = (int)startX;
|
||||
_y1 = (int)startY;
|
||||
_x2 = (int)endX;
|
||||
@ -536,29 +674,63 @@ namespace imlac
|
||||
UpdateColor();
|
||||
}
|
||||
|
||||
public void Modify(DrawingMode mode, short startX, short startY, short endX, short endY)
|
||||
public void Modify(DrawingMode mode, float intensity, bool blink, short startX, short startY, short endX, short endY)
|
||||
{
|
||||
if (_mode != mode)
|
||||
{
|
||||
_mode = mode;
|
||||
UpdateColor();
|
||||
}
|
||||
|
||||
_x1 = (int)startX;
|
||||
_y1 = (int)startY;
|
||||
_x2 = (int)endX;
|
||||
_y2 = (int)endY;
|
||||
|
||||
_intensity = intensity;
|
||||
_blink = blink;
|
||||
_x1 = (int)(startX + Perturbation());
|
||||
_y1 = (int)(startY + Perturbation());
|
||||
_x2 = (int)(endX + Perturbation());
|
||||
_y2 = (int)(endY + Perturbation());
|
||||
UpdateColor();
|
||||
}
|
||||
|
||||
public void Draw(IntPtr sdlRenderer)
|
||||
public void Draw(IntPtr sdlRenderer, bool blinkOn)
|
||||
{
|
||||
// TODO: handle dotted lines, line thickness options
|
||||
SDL.SDL_SetRenderDrawColor(sdlRenderer, _color.R, _color.G, _color.B, _color.A);
|
||||
SDL.SDL_RenderDrawLine(sdlRenderer, _x1, _y1, _x2, _y2);
|
||||
// TODO: handle dotted lines, line thickness options
|
||||
|
||||
if (!_blink || blinkOn)
|
||||
{
|
||||
SDL.SDL_SetRenderDrawColor(
|
||||
sdlRenderer,
|
||||
(byte)(_color.R * _intensity),
|
||||
(byte)(_color.G * _intensity),
|
||||
(byte)(_color.B * _intensity),
|
||||
_color.A);
|
||||
|
||||
SDL.SDL_RenderDrawLine(sdlRenderer, _x1, _y1, _x2, _y2);
|
||||
}
|
||||
}
|
||||
|
||||
private int Perturbation()
|
||||
{
|
||||
if (Configuration.SquiggleMode)
|
||||
{
|
||||
double factor = 2.5;
|
||||
return (int)(_rng.NextDouble() * factor - (factor / 2.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateColor()
|
||||
{
|
||||
// Special case for single point vectors.
|
||||
if (_mode != DrawingMode.Off &&
|
||||
_x1 == _x2 &&
|
||||
_y1 == _y2)
|
||||
{
|
||||
_mode = DrawingMode.Point;
|
||||
}
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case DrawingMode.Normal:
|
||||
@ -580,17 +752,21 @@ namespace imlac
|
||||
}
|
||||
}
|
||||
|
||||
private DrawingMode _mode;
|
||||
private DrawingMode _mode;
|
||||
private Color _color;
|
||||
private float _intensity;
|
||||
private bool _blink;
|
||||
private int _x1;
|
||||
private int _y1;
|
||||
private int _x2;
|
||||
private int _y2;
|
||||
|
||||
private static Color NormalColor = Color.FromArgb(196, Color.ForestGreen);
|
||||
private static Color PointColor = Color.FromArgb(255, Color.ForestGreen);
|
||||
private static Color SGRColor = Color.FromArgb(128, Color.ForestGreen);
|
||||
private static Color DebugColor = Color.FromArgb(255, Color.OrangeRed);
|
||||
private static Color NormalColor = Color.FromArgb(255, Color.GreenYellow);
|
||||
private static Color PointColor = Color.FromArgb(255, Color.GreenYellow);
|
||||
private static Color SGRColor = Color.FromArgb(127, Color.DarkGreen);
|
||||
private static Color DebugColor = Color.FromArgb(127, Color.Red);
|
||||
|
||||
private static Random _rng;
|
||||
}
|
||||
|
||||
private static void BuildKeyMappings()
|
||||
@ -605,6 +781,7 @@ namespace imlac
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_TAB, ImlacKey.Tab);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_RETURN, ImlacKey.CR);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAGEUP, ImlacKey.FF);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_RIGHTBRACKET, ImlacKey.LF);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAGEDOWN, ImlacKey.PageXmit);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_HOME, ImlacKey.Home);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PAUSE, ImlacKey.Brk);
|
||||
@ -612,9 +789,9 @@ namespace imlac
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_SPACE, ImlacKey.Space);
|
||||
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_COMMA, ImlacKey.Comma);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PLUS, ImlacKey.Minus);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_EQUALS, ImlacKey.Minus);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_PERIOD, ImlacKey.Period);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUESTION, ImlacKey.Slash);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_SLASH, ImlacKey.Slash);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_0, ImlacKey.K0);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_1, ImlacKey.K1);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_2, ImlacKey.K2);
|
||||
@ -633,7 +810,7 @@ namespace imlac
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_4, ImlacKey.D4);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_5, ImlacKey.D5);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_KP_6, ImlacKey.D6);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUOTEDBL, ImlacKey.Unlabeled);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_QUOTE, ImlacKey.Unlabeled);
|
||||
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_a, ImlacKey.A);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_b, ImlacKey.B);
|
||||
@ -663,6 +840,8 @@ namespace imlac
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_z, ImlacKey.Z);
|
||||
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_DELETE, ImlacKey.Del);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_BACKSPACE, ImlacKey.Del);
|
||||
_sdlImlacKeymap.Add(SDL_Keycode.SDLK_BACKSLASH, ImlacKey.Brk);
|
||||
|
||||
_sdlVKeymap = new Dictionary<SDL_Keycode, VKeys>();
|
||||
|
||||
@ -808,9 +987,10 @@ namespace imlac
|
||||
|
||||
private static Dictionary<SDL_Keycode, ImlacKey> _sdlImlacKeymap;
|
||||
private static Dictionary<SDL_Keycode, VKeys> _sdlVKeymap;
|
||||
private ImlacKey _latchedKeyCode;
|
||||
private ImlacKey _currentKeyCode;
|
||||
private ImlacKeyModifiers _keyModifiers;
|
||||
private bool _keyLatched;
|
||||
private int _keyPressedCount;
|
||||
private bool _newKeyPressed;
|
||||
|
||||
private ReaderWriterLockSlim _keyLatchedLock;
|
||||
|
||||
@ -840,7 +1020,15 @@ namespace imlac
|
||||
};
|
||||
|
||||
private int _dataSwitches;
|
||||
private DataSwitchMappingMode _dataSwitchMappingMode;
|
||||
private DataSwitchMappingMode _dataSwitchMappingMode;
|
||||
|
||||
//
|
||||
// Mouse Data
|
||||
//
|
||||
private int _mouseX;
|
||||
private int _mouseY;
|
||||
private int _mouseButtons;
|
||||
|
||||
|
||||
//
|
||||
// SDL
|
||||
@ -861,16 +1049,19 @@ namespace imlac
|
||||
Resize
|
||||
}
|
||||
|
||||
private System.Threading.Thread _displayThread;
|
||||
|
||||
private AutoResetEvent _syncEvent;
|
||||
|
||||
private uint _x;
|
||||
private uint _y;
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
private int _xResolution;
|
||||
private int _yResolution;
|
||||
private float _scaleFactor;
|
||||
private float _intensity;
|
||||
private bool _blink; // The Blink attribute is applied to vectors while this is set.
|
||||
private bool _frameBlink; // Blink'ed vectors are blanked while this is set (toggled every 16 frames).
|
||||
private int _xOffset;
|
||||
private int _yOffset;
|
||||
private bool _renderCompleteFrame;
|
||||
|
||||
private bool _fullScreen;
|
||||
@ -884,11 +1075,9 @@ namespace imlac
|
||||
private System.Threading.ReaderWriterLockSlim _lock;
|
||||
|
||||
private uint _frame;
|
||||
private double _lastTime;
|
||||
|
||||
// Framerate management
|
||||
FrameTimer _frameTimer;
|
||||
HighResTimer _timer;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
216
imlac/System.cs
216
imlac/System.cs
@ -31,6 +31,7 @@ namespace imlac
|
||||
Halted,
|
||||
SingleStep,
|
||||
SingleFrame,
|
||||
SingleDisplayOperation,
|
||||
UntilDisplayStart,
|
||||
Running,
|
||||
Quit
|
||||
@ -39,19 +40,19 @@ namespace imlac
|
||||
public class ImlacSystem
|
||||
{
|
||||
public ImlacSystem()
|
||||
{
|
||||
_display = new SDLConsole(0.5f);
|
||||
{
|
||||
_memory = new Memory(this);
|
||||
_paperTapeReader = new PaperTapeReader(this);
|
||||
_tty = new TTY(this);
|
||||
_keyboard = new Keyboard(this);
|
||||
_clock = new AddressableClock(this);
|
||||
_interruptFacility = new InterruptFacility(this);
|
||||
_displayProcessor = new DisplayProcessor(this);
|
||||
_interruptFacility = new InterruptFacility(this);
|
||||
|
||||
_processor = new Processor(this);
|
||||
|
||||
// Register IOT devices
|
||||
_processor.RegisterDeviceIOTs(_displayProcessor);
|
||||
AttachDisplayProcessor();
|
||||
|
||||
// Register IOT devices
|
||||
_processor.RegisterDeviceIOTs(_paperTapeReader);
|
||||
_processor.RegisterDeviceIOTs(_tty);
|
||||
_processor.RegisterDeviceIOTs(_keyboard);
|
||||
@ -70,6 +71,16 @@ namespace imlac
|
||||
_processor.Reset();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_display.Shutdown();
|
||||
}
|
||||
|
||||
public void AttachConsole(IImlacConsole console)
|
||||
{
|
||||
_display = console;
|
||||
}
|
||||
|
||||
public Memory Memory
|
||||
{
|
||||
get { return _memory; }
|
||||
@ -80,7 +91,7 @@ namespace imlac
|
||||
get { return _processor; }
|
||||
}
|
||||
|
||||
public DisplayProcessor DisplayProcessor
|
||||
public DisplayProcessorBase DisplayProcessor
|
||||
{
|
||||
get { return _displayProcessor; }
|
||||
}
|
||||
@ -124,8 +135,12 @@ namespace imlac
|
||||
_keyboard.Clock();
|
||||
_clock.Clock();
|
||||
|
||||
// interrupts last so that devices that raise interrupts get clocked first
|
||||
_interruptFacility.Clock();
|
||||
// interrupts last so that devices that raise interrupts get clocked first.
|
||||
// We clock interrupts on the leading edge of the processor's Fetch state.
|
||||
if (_processor.InstructionState == ExecState.Fetch)
|
||||
{
|
||||
_interruptFacility.Clock();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -161,11 +176,10 @@ namespace imlac
|
||||
return SystemExecutionState.Running;
|
||||
}
|
||||
|
||||
[DebuggerFunction("step", "Executes a single instruction cycle at the specified address", "<address>")]
|
||||
private SystemExecutionState StepProcessor(ushort address)
|
||||
[DebuggerFunction("set pc", "Sets the Processor's PC to the specified address", "<address>")]
|
||||
private SystemExecutionState SetPC(ushort address)
|
||||
{
|
||||
Processor.PC = address;
|
||||
Processor.State = ProcessorState.Running;
|
||||
return SystemExecutionState.SingleStep;
|
||||
}
|
||||
|
||||
@ -190,13 +204,29 @@ namespace imlac
|
||||
return SystemExecutionState.UntilDisplayStart;
|
||||
}
|
||||
|
||||
[DebuggerFunction("set bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
|
||||
private SystemExecutionState SetBootstrap(string bootstrap)
|
||||
[DebuggerFunction("step display", "Runs until the end of the next display drawing or positioning ooperation")]
|
||||
private SystemExecutionState RunDisplayOp()
|
||||
{
|
||||
Processor.State = ProcessorState.Running;
|
||||
return SystemExecutionState.SingleDisplayOperation;
|
||||
}
|
||||
|
||||
[DebuggerFunction("load bootstrap", "Loads the specified bootstrap into memory at 40", "<bootstrap>")]
|
||||
private SystemExecutionState LoadBootstrap(string bootstrap)
|
||||
{
|
||||
LoadMemory(Paths.BuildBootPath(bootstrap), 0x20, 0x20);
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("boot", "Loads the specified bootstrap into memory at 40 and executes it", "<bootstrap>")]
|
||||
private SystemExecutionState Boot(string bootstrap)
|
||||
{
|
||||
LoadMemory(Paths.BuildBootPath(bootstrap), 0x20, 0x20);
|
||||
Processor.PC = 0x20;
|
||||
Processor.State = ProcessorState.Running;
|
||||
return SystemExecutionState.Running;
|
||||
}
|
||||
|
||||
[DebuggerFunction("save memory", "Saves the specified range of memory to a file", "<file> <start> <length>")]
|
||||
private SystemExecutionState SaveMemoryContents(string file, ushort start, ushort length)
|
||||
{
|
||||
@ -239,13 +269,36 @@ namespace imlac
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("display memory", "Displays memory contents", "<start> <length>")]
|
||||
[DebuggerFunction("show memory", "Displays memory contents", "<start> <length>")]
|
||||
private SystemExecutionState DisplayMemory(ushort start, ushort length)
|
||||
{
|
||||
DumpMemory(start, length);
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("set memory size 4kw", "Sets memory size to 4KW")]
|
||||
private SystemExecutionState SetMemorySize4kw()
|
||||
{
|
||||
_memory.SetMemorySize(0x1000);
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
|
||||
[DebuggerFunction("set memory size 8kw", "Sets memory size to 8KW")]
|
||||
private SystemExecutionState SetMemorySize8kw()
|
||||
{
|
||||
_memory.SetMemorySize(0x2000);
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
|
||||
[DebuggerFunction("set memory size 16kw", "Sets memory size to 16KW")]
|
||||
private SystemExecutionState SetMemorySize16kw()
|
||||
{
|
||||
_memory.SetMemorySize(0x4000);
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("set logging", "Sets the logging configuration", "<loglevel>")]
|
||||
private SystemExecutionState SetLogging(LogType value)
|
||||
{
|
||||
@ -281,6 +334,20 @@ namespace imlac
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("attach tty telnet", "Attaches the Imlac's TTY to a raw telnet port", "<host> <port>")]
|
||||
private SystemExecutionState AttachTTY(string host, ushort port)
|
||||
{
|
||||
TTY.SetChannel(new TelnetDataChannel(host, port, false));
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("attach tty raw", "Attaches the Imlac's TTY to a raw port", "<host> <port>")]
|
||||
private SystemExecutionState AttachTTYRaw(string host, ushort port)
|
||||
{
|
||||
TTY.SetChannel(new TelnetDataChannel(host, port, true));
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("detach tty", "Detaches the Imlac's TTY from host inputs")]
|
||||
private SystemExecutionState DetachTTY()
|
||||
{
|
||||
@ -448,23 +515,114 @@ namespace imlac
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("enable mit mode", "Turns MIT modifications on.")]
|
||||
private SystemExecutionState EnableMITMode()
|
||||
{
|
||||
Configuration.MITMode = true;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("disable mit mode", "Turns MIT modifications off.")]
|
||||
private SystemExecutionState DisableMITMode()
|
||||
{
|
||||
Configuration.MITMode = false;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("enable squiggle mode", "Turns vector perturbations on.")]
|
||||
private SystemExecutionState EnableSquiggleMode()
|
||||
{
|
||||
Configuration.SquiggleMode = true;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("disable squiggle mode", "Turns vector perturbations off.")]
|
||||
private SystemExecutionState DisableSquiggleMode()
|
||||
{
|
||||
Configuration.SquiggleMode = false;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("draw invisible vectors", "Displays non-drawing beam motion in red.")]
|
||||
private SystemExecutionState ShowInvisibleVectors()
|
||||
{
|
||||
Configuration.ShowInvisibleVectors = true;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("hide invisible vectors", "Hides non-drawing beam motion (the default).")]
|
||||
private SystemExecutionState HideInvisibleVectors()
|
||||
{
|
||||
Configuration.ShowInvisibleVectors = false;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("set cpu type", "Selects CPU type: PDS1 or PDS4", "<cpu>")]
|
||||
private SystemExecutionState SetCPUType(ImlacCPUType type)
|
||||
{
|
||||
if (Configuration.CPUType != type)
|
||||
{
|
||||
Configuration.CPUType = type;
|
||||
AttachDisplayProcessor();
|
||||
_displayProcessor.Reset();
|
||||
}
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("halt on invalid instructions", "Halts emulation if an invalid instruction is encountered.")]
|
||||
private SystemExecutionState HaltOnInvalidInstructions()
|
||||
{
|
||||
Configuration.HaltOnInvalidOpcodes = true;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
[DebuggerFunction("continue on invalid instructions", "Continues emulation if an invalid instruction is encountered.")]
|
||||
private SystemExecutionState ContinueOnInvalidInstructions()
|
||||
{
|
||||
Configuration.HaltOnInvalidOpcodes = false;
|
||||
return SystemExecutionState.Debugging;
|
||||
}
|
||||
|
||||
private void AttachDisplayProcessor()
|
||||
{
|
||||
// Unregister current display processor
|
||||
|
||||
if (_displayProcessor != null)
|
||||
{
|
||||
_processor.UnregisterDeviceIOTs(_displayProcessor);
|
||||
}
|
||||
|
||||
if (Configuration.CPUType == ImlacCPUType.PDS1)
|
||||
{
|
||||
_displayProcessor = new PDS1DisplayProcessor(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_displayProcessor = new PDS4DisplayProcessor(this);
|
||||
}
|
||||
|
||||
_processor.RegisterDeviceIOTs(_displayProcessor);
|
||||
}
|
||||
|
||||
public enum DisassemblyMode
|
||||
{
|
||||
Processor,
|
||||
DisplayProcessor,
|
||||
DisplayIncrement,
|
||||
DisplayCompact,
|
||||
DisplayAuto
|
||||
}
|
||||
|
||||
public void PrintProcessorStatus()
|
||||
{
|
||||
Console.WriteLine("PC={0} AC={1} MB={2} - {3}",
|
||||
Console.WriteLine("PC={0} AC={1} MB={2} - {3}\n{4}",
|
||||
Helpers.ToOctal(Processor.PC),
|
||||
Helpers.ToOctal(Processor.AC),
|
||||
Helpers.ToOctal(Memory.Fetch(Processor.PC)),
|
||||
Processor.State);
|
||||
Processor.State,
|
||||
Processor.Disassemble(Processor.PC));
|
||||
|
||||
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6} - {7}",
|
||||
Console.WriteLine("DPC={0} DT={1} DPCE={2} X={3} Y={4}\nMode={5} HalfWord={6}",
|
||||
Helpers.ToOctal(DisplayProcessor.PC),
|
||||
Helpers.ToOctal(DisplayProcessor.DT),
|
||||
Helpers.ToOctal(DisplayProcessor.DPCEntry),
|
||||
@ -546,28 +704,35 @@ namespace imlac
|
||||
}
|
||||
|
||||
ushort endAddress = (ushort)Math.Min(Memory.Size, startAddress + length);
|
||||
|
||||
for (ushort address = startAddress; address <= endAddress; address++)
|
||||
ushort address = startAddress;
|
||||
while (address < endAddress)
|
||||
{
|
||||
string disassembly = string.Empty;
|
||||
|
||||
int size = 0;
|
||||
try
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case DisassemblyMode.Processor:
|
||||
size = 1;
|
||||
disassembly = Processor.Disassemble(address);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayAuto:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Indeterminate, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayProcessor:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Processor, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayIncrement:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment);
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.Increment, out size);
|
||||
break;
|
||||
|
||||
case DisassemblyMode.DisplayCompact:
|
||||
disassembly = DisplayProcessor.Disassemble(address, DisplayProcessorMode.CompactAddressing, out size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -575,9 +740,12 @@ namespace imlac
|
||||
{
|
||||
// this can happen if the data is not a valid instruction
|
||||
disassembly = "<invalid instruction>";
|
||||
size = 1;
|
||||
}
|
||||
|
||||
Console.WriteLine("{0}\\{1} {2}", Helpers.ToOctal(address), Helpers.ToOctal(Memory.Fetch(address)), disassembly);
|
||||
|
||||
address += (ushort)size;
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,7 +794,7 @@ namespace imlac
|
||||
}
|
||||
|
||||
private Processor _processor;
|
||||
private DisplayProcessor _displayProcessor;
|
||||
private DisplayProcessorBase _displayProcessor;
|
||||
private IImlacConsole _display;
|
||||
private Memory _memory;
|
||||
private PaperTapeReader _paperTapeReader;
|
||||
|
||||
@ -36,6 +36,7 @@ namespace imlac
|
||||
Interrupt = 0x10,
|
||||
TTY = 0x20,
|
||||
PTR = 0x40,
|
||||
Telnet = 0x80,
|
||||
All = 0x7fffffff
|
||||
}
|
||||
|
||||
@ -97,6 +98,10 @@ namespace imlac
|
||||
case LogType.Interrupt:
|
||||
color = ConsoleColor.Blue;
|
||||
break;
|
||||
|
||||
case LogType.Telnet:
|
||||
color = ConsoleColor.Cyan;
|
||||
break;
|
||||
|
||||
default:
|
||||
// No change
|
||||
|
||||
81
imlac/app.manifest
Normal file
81
imlac/app.manifest
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<dpiAware>true</dpiAware>
|
||||
</windowsSettings>
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
@ -34,6 +34,9 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="SDL2-CS, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SDL2-CS.dll.2.0.0.0\lib\net20\SDL2-CS.dll</HintPath>
|
||||
@ -46,15 +49,19 @@
|
||||
<Reference Include="System.Data" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="Debugger\BreakpointManager.cs" />
|
||||
<Compile Include="Debugger\Console.cs" />
|
||||
<Compile Include="Debugger\DebuggerAttributes.cs" />
|
||||
<Compile Include="Debugger\DebuggerPrompt.cs" />
|
||||
<Compile Include="PDS4DisplayProcessor.cs" />
|
||||
<Compile Include="PDS1DisplayProcessor.cs" />
|
||||
<Compile Include="HighResTimer.cs" />
|
||||
<Compile Include="IO\TTYChannels\NullDataChannel.cs" />
|
||||
<Compile Include="IO\TTYChannels\SerialDataChannel.cs" />
|
||||
<Compile Include="IO\TTYChannels\StreamDataChannel.cs" />
|
||||
<Compile Include="IO\TTYChannels\ISerialDataChannel.cs" />
|
||||
<Compile Include="IO\TTYChannels\TelnetDataChannel.cs" />
|
||||
<Compile Include="SDLConsole.cs" />
|
||||
<Compile Include="DisplayProcessor.cs" />
|
||||
<Compile Include="Helpers.cs" />
|
||||
@ -85,6 +92,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="app.manifest" />
|
||||
<None Include="boot\mtty">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
sImlac v0.2 README - (c) 2016-2018 Living Computers: Museum+Labs
|
||||
sImlac v0.3 README - (c) 2016-2020 Living Computers: Museum+Labs
|
||||
-----------------------------------------------------------------
|
||||
|
||||
1. Overview
|
||||
-----------
|
||||
sImlac is an attempt to emulate/simulate the oft neglected Imlac PDS-1
|
||||
computer/terminal. The Imlac combined a 16-bit CPU (very PDP-8 like) with
|
||||
a simple (but fairly flexible) Display Processor which drove a vector
|
||||
sImlac is an attempt to emulate/simulate the oft neglected Imlac PDS-1 and
|
||||
PDS-4 computers/terminals. The Imlac combined a 16-bit CPU (very PDP-8 like)
|
||||
with a simple (but fairly flexible) Display Processor which drove a vector
|
||||
display.
|
||||
|
||||
sImlac currently emulates the current hardware:
|
||||
|
||||
- Standard Imlac Processor / Display Processor (with 1.8us cycle timings)
|
||||
- 8KW of core memory
|
||||
- Standard Imlac PDS-1D Processor / Display Processor (with 1.8us cycle timings)
|
||||
- Standard Imlac PDS-4 Processor / Display Processor (with 990ns cycle timings)
|
||||
- Up to 16KW of core memory
|
||||
- Vector display (with long-persistence phosphor)
|
||||
- PTR and TTY interfaces (using physical serial ports or files as inputs)
|
||||
- PTR and TTY interfaces (using physical serial ports, telnet hosts, or files as inputs)
|
||||
- Keyboard
|
||||
- Interrupt facility
|
||||
- Long Vector (LVH-1 option) instruction support
|
||||
@ -23,7 +24,8 @@ 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.2, there are still likely to be bugs.
|
||||
Since this is v0.3, there are still likely to be bugs. In particular, the PDS-4 support
|
||||
introduced in this release is not well tested due to lack of applicable software.
|
||||
|
||||
Questions, comments, or bug reports can be directed at
|
||||
joshd@livingcomputers.org.
|
||||
@ -36,7 +38,7 @@ Windows Vista or later, with version 4.5.3 or later of the .NET Framework instal
|
||||
.NET should be present by default on Windows Vista and later; if it is not installed
|
||||
on your computer it can be obtained at https://www.microsoft.com/net.
|
||||
|
||||
As ContrAlto is a .NET application it will also run under Mono
|
||||
As sImlac is a .NET application it will also run under Mono
|
||||
(http://www.mono-project.com/) on Unix and OS X.
|
||||
|
||||
sImlac uses SDL 2.0 for display and input. On Windows the appropriate SDL.dll is
|
||||
@ -57,20 +59,18 @@ sImlac comes with three boot loaders, and the correct one must be chosen for
|
||||
the image you are loading (see software.txt for a listing that describes the
|
||||
available software and the loader to use).
|
||||
|
||||
A loader can be loaded using the "set bootstrap" command. Let's say we want
|
||||
A loader can be loaded and run using the "boot" command. Let's say we want
|
||||
to play a game of "Space War!." The image named "war" uses the STTY (special
|
||||
TTY) loader, so we issue the following command:
|
||||
TTY) loader, so we issue the following commands:
|
||||
|
||||
> set bootstrap stty
|
||||
|
||||
Now we need to attach the image to the TTY port, this is done by:
|
||||
We need to attach the image to the TTY port, this is done by:
|
||||
|
||||
> attach tty file <path-to-images>\war
|
||||
|
||||
Once this is done, we can start the CPU running. By default, the PC is set
|
||||
to 40(8), the start of the boot loader, so we can just do:
|
||||
Once this is done, we can start the CPU running the bootstrap, which will
|
||||
load the "war" program into memory and start it running:
|
||||
|
||||
> go
|
||||
> boot stty
|
||||
|
||||
And the loader will run. This will take a few seconds to complete, after which
|
||||
the title screen for "Space War!" will appear in the display window.
|
||||
@ -135,8 +135,11 @@ the "Insert" key will toggle between window and fullscreen modes.
|
||||
------------
|
||||
|
||||
reset : Resets the Imlac, but does not clear its memory.
|
||||
Any files attached to the paper tape reader or TTY
|
||||
interface are reset to the beginning of the file.
|
||||
|
||||
set bootstrap <boot>: Loads the specified bootstrap into memory at 40(8).
|
||||
boot <boot> : Loads the specified bootstrap into memory at 40(8) and
|
||||
executes it.
|
||||
Options are PTR (paper tape), TTY (standard TTY), and
|
||||
STTY (alternate TTY).
|
||||
|
||||
@ -149,9 +152,17 @@ attach tty file [file] : Specifies a TTY or STTY-format file to attach to the TT
|
||||
bootstraps.
|
||||
|
||||
attach tty port [port] [rate] [parity] [data bits] [stop bits] :
|
||||
Specifies a physical port on the host machine to connect
|
||||
to the emulated TTY port. Allows the emulated Imlac to
|
||||
talk to real serial devices.
|
||||
Specifies a physical port on the host machine to connect
|
||||
to the emulated TTY port. Allows the emulated Imlac to
|
||||
talk to real serial devices.
|
||||
|
||||
attach tty telnet [host] [port] :
|
||||
Specifies a telnet host to connect to the emulated TTY
|
||||
port. Allows the emulated Imlac to talk to another
|
||||
computer over the Internet.
|
||||
|
||||
attach tty raw [host] [port] :
|
||||
As above, but using a raw connection rather than Telnet.
|
||||
|
||||
go <addr> : Starts the system running at the current PC, or <addr> if
|
||||
specified.
|
||||
@ -165,6 +176,16 @@ step frame end : Runs the current 40hz frame to its completion. If the
|
||||
|
||||
step frame start : Runs until the start of the next frame.
|
||||
|
||||
set cpu type [type] : Sets the type of Imlac system sImlac will emulate.
|
||||
Valid options are "PDS1" and "PDS4". Switching the cpu
|
||||
type in the middle of a running program will produce
|
||||
interesting results.
|
||||
|
||||
enable mit mode : Turns on MIT's DADR addressing modifications. This is
|
||||
necessary to run some Imlac software from MIT.
|
||||
|
||||
disable mit mode : The opposite of the above.
|
||||
|
||||
edit memory [addr] : Begins a very simple memory editor starting at address
|
||||
'addr'. You will be shown the current address\contents,
|
||||
and prompted for new contents. Press ENTER to keep the
|
||||
@ -331,6 +352,13 @@ Thanks go out to:
|
||||
7. Revision History
|
||||
-------------------
|
||||
|
||||
v0.3 - Added PDS-4 support.
|
||||
- Many bugfixes to PDS-1 emulation
|
||||
- Added telnet/raw ports
|
||||
- Added high-dpi awareness
|
||||
- Enabled MIT display addressing modifications
|
||||
- Fixed 40 cycle / display halt interrupt
|
||||
|
||||
v0.2 - Updated to use SDL-CS for better cross-platform support.
|
||||
|
||||
v0.1 - Initial Release
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user