1
0
mirror of https://github.com/livingcomputermuseum/sImlac.git synced 2026-01-13 15:27:40 +00:00

1938 lines
67 KiB
C#

/*
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.Text;
using imlac.IO;
using imlac.Debugger;
namespace imlac
{
public enum ProcessorState
{
Halted,
Running,
BreakpointHalt
}
public enum ExecState
{
Fetch,
Defer,
ExtraDefer,
Execute
}
public class Processor
{
public Processor(ImlacSystem system)
{
_system = system;
_mem = _system.Memory;
_iotDispatch = new IIOTDevice[0x200]; // 9 bits of IOT instructions
Reset();
InitializeCache();
}
public void Reset()
{
_pc = 0x0020; // 40 oct, standard bootstrap address
_ac = 0x0000;
_link = 0;
_sp[0] = 0;
_sp[1] = 0;
_currentInstruction = null;
_currentIndirectAddress = 0x0000;
_instructionState = ExecState.Fetch;
_state = ProcessorState.Halted;
_byteAccess = ByteAccess.Normal;
}
public void RegisterDeviceIOTs(IIOTDevice device)
{
int[] handledIOTs = device.GetHandledIOTs();
foreach (int i in handledIOTs)
{
if (_iotDispatch[i] == null)
{
_iotDispatch[i] = device;
}
else
{
// We have an overlap here; can't have two devices sharing the same
// IOT. It would be bad.
throw new InvalidOperationException(String.Format("IOT conflict on {0:x3} between {1} and {2}.", i, _iotDispatch[i], device));
}
}
}
public void UnregisterDeviceIOTs(IIOTDevice device)
{
int[] handledIOTs = device.GetHandledIOTs();
foreach (int i in handledIOTs)
{
_iotDispatch[i] = null;
}
}
public ProcessorState State
{
get { return _state; }
set { _state = value; }
}
public ExecState InstructionState
{
get { return _instructionState; }
}
public ushort PC
{
get { return _pc; }
set { _pc = value; }
}
public ushort AC
{
get { return _ac; }
set { _ac = value; }
}
public ushort DS
{
get { return _ds; }
set { _ds = value; }
}
public ushort BreakpointAddress
{
get { return _breakpointAddress; }
}
public bool CanBeInterrupted()
{
// We can be interrupted while in the Fetch state, iff we are not executing
// an instruction modified by SBR or SBL (byte-wise two-instruction ops are atomic).
return _instructionState == ExecState.Fetch && _byteAccess == ByteAccess.Normal;
}
public string Disassemble(ushort address)
{
string disassembly;
try
{
disassembly = GetCachedInstruction(address).Disassemble(address);
}
catch
{
// We had some trouble; present as undecodable
disassembly = String.Format("Invalid instruction {0}",
Helpers.ToOctal(_mem.Fetch((ushort)(address & Memory.SizeMask))));
}
return disassembly;
}
public void InitializeCache()
{
_instructionCache = new Instruction[Memory.Size];
}
public void InvalidateCache(ushort address)
{
_instructionCache[address & Memory.SizeMask] = null;
}
public void Clock()
{
if (_state == ProcessorState.Halted)
{
return;
}
//
// Grab data switches from console if enabled
//
if (_system.Display.DataSwitchMappingEnabled)
{
_ds = _system.Display.DataSwitches;
}
switch (_instructionState)
{
case ExecState.Fetch:
_currentInstruction = GetCachedInstruction(_pc);
if (_currentInstruction.IsIndirect)
{
// Indirect instruction: the next state is Defer so we can do the indirect memory op before execution.
_instructionState = ExecState.Defer;
}
else if (_currentInstruction.IsOperateOrIOT)
{
//
// Operate or IOT instruction (no memory referenced): execute the instruction now, and return to the Fetch state.
//
Execute();
_instructionState = ExecState.Fetch;
}
else
{
// Processor order: Move to the Exec state
_instructionState = ExecState.Execute;
}
break;
case ExecState.Defer:
//
// Get the indirect address for this instruction.
// On the PDS-4, indirection can continue indefinitely.
//
ushort effectiveAddress = GetEffectiveAddress(_currentInstruction.Data);
_currentIndirectAddress = _mem.Fetch(effectiveAddress);
if (Configuration.CPUType == ImlacCPUType.PDS1 ||
(_pc >= 0x20 && _pc < 0x40) || _pc == 0x3ff7 || _pc == 0x3fe6) // latter is a hack to fix broken bootstrap on pds-4
{
// done, only one level of indirection on the PDS-1.
_instructionState = ExecState.Execute;
}
else
{
if ((_currentIndirectAddress & 0x8000) != 0)
{
// Indirect bit is set -- continue for another level of indirection.
_instructionState = ExecState.ExtraDefer;
}
else
{
_instructionState = ExecState.Execute;
}
}
AutoIncrement(effectiveAddress);
break;
case ExecState.ExtraDefer:
// Continue indirection using the current indirect address:
_currentIndirectAddress = _mem.Fetch(_currentIndirectAddress);
if ((_currentIndirectAddress & 0x8000) == 0)
{
// Indirect bit is unset; exit from this state and finally
// go execute the instruction.
_instructionState = ExecState.Execute;
}
// TODO: Do auto-index locations apply for multiple indirects?
AutoIncrement(_currentIndirectAddress);
break;
case ExecState.Execute:
//
// Execute the instruction, return to Fetch state.
//
Execute();
_instructionState = ExecState.Fetch;
break;
}
}
private void AutoIncrement(ushort effectiveAddress)
{
//
// If this is an auto-increment indirect index register (octal locations 10-17 on each 2k page)
// then we will increment the contents (and the indirect address).
// On the PDS-4 there are auto-decrement registers at octal 20-27).
//
if ((effectiveAddress & 0x7ff) >= 0x08 && (effectiveAddress & 0x7ff) < 0x10)
{
_currentIndirectAddress++;
_mem.Store(effectiveAddress, (ushort)(_currentIndirectAddress));
}
else if (Configuration.CPUType == ImlacCPUType.PDS4 &&
(effectiveAddress & 0x7ff) >= 0x10 && (effectiveAddress & 0x7ff) < 0x18)
{
_currentIndirectAddress--;
_mem.Store(effectiveAddress, (ushort)(_currentIndirectAddress));
}
}
private Instruction GetCachedInstruction(ushort address)
{
// TODO: factor this masking logic out.
if (_instructionCache[address & Memory.SizeMask] == null)
{
_instructionCache[address & Memory.SizeMask] = new Instruction(_mem.Fetch((ushort)(address & Memory.SizeMask)));
}
return _instructionCache[address & Memory.SizeMask];
}
private void Execute()
{
ushort q;
uint res;
// if (Trace.TraceOn) Trace.Log(LogType.Processor, "{0} - {1}", _pc, _currentInstruction.Disassemble(_pc));
switch (_currentInstruction.Opcode)
{
case Opcode.ADD:
q = DoFetchForCurrentInstruction();
res = (uint)(_ac + q);
_ac = (ushort)res;
// link bit is complemented if carry-out occurs
if ((res & 0x10000) != 0)
{
_link = (ushort)((_link ^ 1) & 0x1);
}
_pc++;
break;
case Opcode.AND:
_ac = (ushort)(_ac & DoFetchForCurrentInstruction());
_pc++;
break;
case Opcode.DAC:
DoStoreForCurrentInstruction(_ac);
_pc++;
break;
case Opcode.DACS:
_sp[_currentInstruction.Data] = _ac;
_pc++;
break;
case Opcode.DCM: // PDS-4 only
// Decrement memory by 1
_byteAccess = ByteAccess.Normal;
q = DoFetchForCurrentInstruction();
DoStoreForCurrentInstruction((ushort)(q - 1));
_pc++;
break;
case Opcode.DEA: // PDS-4 only
// Decrement AC; complement link on underflow.
if (_ac == 0)
{
_link = (ushort)((_link ^ 1) & 0x1);
}
_ac--;
_pc++;
break;
case Opcode.IOR:
_ac = (ushort)(_ac | DoFetchForCurrentInstruction());
_pc++;
break;
case Opcode.IOT:
DoIOT();
_pc++;
break;
case Opcode.ISZ:
_byteAccess = ByteAccess.Normal;
q = DoFetchForCurrentInstruction();
q++;
DoStoreForCurrentInstruction(q);
if (q == 0)
{
_pc++; // skip next instruction
}
_pc++;
break;
case Opcode.JMP:
_byteAccess = ByteAccess.Normal;
if (_currentInstruction.IsIndirect)
{
_pc = _currentIndirectAddress;
}
else
{
_pc = GetEffectiveAddress(_currentInstruction.Data);
}
break;
case Opcode.JMS:
_byteAccess = ByteAccess.Normal;
// Store next PC at location specified by instruction (Q),
// continue execution at Q+1
DoStoreForCurrentInstruction((ushort)(_pc + 1));
if (_currentInstruction.IsIndirect)
{
_pc = (ushort)(_currentIndirectAddress + 1);
}
else
{
_pc = (ushort)(GetEffectiveAddress(_currentInstruction.Data) + 1);
}
break;
case Opcode.LAC:
_ac = DoFetchForCurrentInstruction();
_pc++;
break;
case Opcode.LACS:
_ac = _sp[_currentInstruction.Data];
_pc++;
break;
case Opcode.LAMP: // PDS-4 only
// Flashes a keyboard indicator lamp for 150ms.
// At this time, thou cannot GET ye LAMP.
Console.WriteLine("*LAMP*");
_pc++;
break;
case Opcode.LAW:
_ac = _currentInstruction.Data;
_pc++;
break;
case Opcode.LIAC:
// TODO: Are byte accesses allowed here? Manual doesn't say.
_ac = DoFetchForAddress(_ac);
_pc++;
break;
case Opcode.LWC:
_ac = (ushort)(-_currentInstruction.Data);
_pc++;
break;
case Opcode.OPR:
// Execute the Operate Class 1 instruction based on the data bits.
// T1: Clear AC and / or Link
if ((_currentInstruction.Data & 0x0001) != 0)
{
_ac = 0x0000;
}
if ((_currentInstruction.Data & 0x0008) != 0)
{
_link = 0;
}
// T2: 1's Complement AC and / or Link
if ((_currentInstruction.Data & 0x0002) != 0)
{
_ac = (ushort)(~_ac);
}
if ((_currentInstruction.Data & 0x0010) != 0)
{
_link = (ushort)((~_link) & 0x1);
}
// T3: Increment AC and / or OR data switches with AC
if ((_currentInstruction.Data & 0x0004) != 0)
{
_ac++;
// If an overflow occurs, the link is complemented.
_link = (_ac == 0) ? (ushort)((~_link) & 0x1) : _link;
}
if ((_currentInstruction.Data & 0x0020) != 0)
{
_ac |= _ds;
}
if ((_currentInstruction.Data & 0x8000) == 0)
{
// Halt the CPU.
_state = ProcessorState.Halted;
}
_pc++;
break;
case Opcode.POP: // PDS-4 only
_ac = Pop(_currentInstruction.Data);
_pc++;
break;
case Opcode.POPA: // PDS-4 only
_pc = Pop(_currentInstruction.Data);
_pc++; // Yes, we still increment this (net result is PC+2 gets popped from the stack)
break;
case Opcode.POPD: // PDS-4 only
// Pops the MDS stack into the DPC. Only active if
// the display is not running.
// TODO: this also loads the light pen marker into bit 0 of DPC
// and sets the link if CAM is on.
if (_system.DisplayProcessor.State == ProcessorState.Halted)
{
_system.DisplayProcessor.Pop();
}
_pc++;
break;
case Opcode.PUSH: // PDS-4 only
Push(_currentInstruction.Data, _ac);
_pc++;
break;
case Opcode.PUSHA: // PDS-4 only
_pc++; // PC after this instruction is pushed to the stack.
Push(_currentInstruction.Data, _pc);
break;
case Opcode.PUSHD: // PDS-4 only
// Pushes the DPC onto the DPC stack. Only active if
// the display is not running.
// TODO: the CAM bit is set according to the link bit status.
if (_system.DisplayProcessor.State == ProcessorState.Halted)
{
_system.DisplayProcessor.Push();
}
_pc++;
break;
case Opcode.RAL:
// TODO: this is pretty inefficient.
for (int i = 0; i < _currentInstruction.Data; i++)
{
ushort oldLink = _link;
_link = (ushort)((_ac & 0x8000) >> 15);
_ac = (ushort)((_ac << 1) | oldLink);
}
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.StartProcessor();
}
_pc++;
break;
case Opcode.RAR:
for (int i = 0; i < _currentInstruction.Data; i++)
{
ushort oldLink = _link;
_link = (ushort)((_ac & 0x0001));
_ac = (ushort)((_ac >> 1) | (oldLink << 15));
}
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.StartProcessor();
}
_pc++;
break;
case Opcode.RDAX: // PDS-4 only
// Load the display's XAC into bits 3-15. Bits
// 3 and 4 are the scissor bits.
_ac = (ushort)_system.DisplayProcessor.X;
_pc++;
break;
case Opcode.RDAY: // PDS-4 only
// Load the display's YAC into bits 3-15. Bits
// 3 and 4 are the scissor bits.
_ac = (ushort)_system.DisplayProcessor.Y;
_pc++;
break;
case Opcode.RDPC: // PDS-4 only
// Bits 1-15 of the DPC are loaded into the AC.
// The Light Pen bit is loaded into AC bit 0.
_ac = (ushort)(_system.DisplayProcessor.PC & 0x7fff);
// TODO: Light pen bit, whenever we implement this.
_pc++;
break;
case Opcode.SAD: // PDS-4 only
q = DoFetchForCurrentInstruction();
if (_ac != q)
{
_pc++;
}
_pc++;
break;
case Opcode.SAL:
// The shift operators are arithmetic (& preserve sign). Shifting left only shifts bits 2-15 left,
// bit 0 stays the same and bit 1 is lost (the link register is not involved in any way).
ushort bitZero = (ushort)(_ac & 0x8000);
for (int i = 0; i < _currentInstruction.Data; i++)
{
_ac = (ushort)(_ac << 1);
}
_ac = (ushort)(bitZero | (_ac & 0x7fff));
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.StartProcessor();
}
_pc++;
break;
case Opcode.SAM:
q = DoFetchForCurrentInstruction();
if (_ac == q)
{
_pc++;
}
_pc++;
break;
case Opcode.SAR:
// The shift operators are arithmetic (& preserve sign). Shifting right shifts bits 1-14 left,
// bit 0 is copied to bit 1, and bit 15 is lost (the link register is not involved in any way).
bitZero = (ushort)(_ac & 0x8000);
for (int i = 0; i < _currentInstruction.Data; i++)
{
_ac = (ushort)(_ac >> 1);
_ac = (ushort)(bitZero | _ac);
}
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.StartProcessor();
}
_pc++;
break;
case Opcode.SBL: // PDS-4 only
_byteAccess = ByteAccess.Left;
_pc++;
break;
case Opcode.SBR: // PDS-4 only
_byteAccess = ByteAccess.Right;
_pc++;
break;
case Opcode.SKP:
bool skip = false;
// AC == 0
if ((_currentInstruction.Data & 0x0001) != 0)
{
skip = (_ac == 0);
}
// AC > = 0
if ((_currentInstruction.Data & 0x0002) != 0)
{
skip = ((_ac & 0x8000) == 0);
}
// Link == 0
if ((_currentInstruction.Data & 0x0004) != 0)
{
skip = (_link == 0);
}
// Display on
if ((_currentInstruction.Data & 0x0008) != 0)
{
skip = (_system.DisplayProcessor.State == ProcessorState.Running);
}
// Keyboard data present
if ((_currentInstruction.Data & 0x0010) != 0)
{
skip = _system.Keyboard.KeyReady;
}
// TTY input present
if ((_currentInstruction.Data & 0x0020) != 0)
{
skip = _system.TTY.DataReady;
}
// TTY send complete
if ((_currentInstruction.Data & 0x0040) != 0)
{
skip = _system.TTY.DataSendReady;
}
// 40Hz display sync
if ((_currentInstruction.Data & 0x0080) != 0)
{
skip = _system.DisplayProcessor.FrameLatch;
}
// Paper Tape Reader data present
if ((_currentInstruction.Data & 0x0100) != 0)
{
skip = _system.PaperTapeReader.DataReady();
}
if (_currentInstruction.SkipNegate)
{
skip = !skip;
}
if (skip)
{
_pc++;
}
_pc++;
break;
case Opcode.SUB:
q = DoFetchForCurrentInstruction();
res = (uint)(_ac - q);
_ac = (ushort)res;
// link bit is complemented if carry-in occurs
if ((res & 0x10000) != 0)
{
_link = (ushort)((_link ^ 1) & 0x1);
}
_pc++;
break;
case Opcode.SWAP: // PDS-4 only
_ac = (ushort)((_ac << 8) | (_ac >> 8));
_pc++;
break;
case Opcode.TAC: // PDS-4 only
if ((_ac & 0x8000) != 0)
{
// Negative AC: no change to PC
}
else if (_ac != 0)
{
// Positive AC, increment PC once.
_pc++;
}
else
{
// Zero AC, increment PC twice.
_pc += 2;
}
_pc++;
break;
case Opcode.XAM:
q = DoFetchForCurrentInstruction();
ushort ac = _ac;
_ac = q;
DoStoreForCurrentInstruction(ac);
_pc++;
break;
case Opcode.XOR:
_ac = (ushort)(_ac ^ DoFetchForCurrentInstruction());
_pc++;
break;
default:
throw new NotImplementedException(String.Format("Unimplemented Opcode {0}", _currentInstruction.Opcode));
}
//
// If this isn't an SBL/SBR instruction, reset the byte access mode
// now that we're done with the modified instruction.
//
if (_currentInstruction.Opcode != Opcode.SBL &&
_currentInstruction.Opcode != Opcode.SBR)
{
_byteAccess = ByteAccess.Normal;
}
// If the next instruction has a breakpoint set we'll halt at this point, before executing it.
if (BreakpointManager.TestBreakpoint(BreakpointType.Execution, _pc))
{
_state = ProcessorState.BreakpointHalt;
_breakpointAddress = _pc;
}
}
private void DoIOT()
{
//
// Dispatch the IOT instruction to the correct device, if one is registered.
// We use the Data field (the full 9 bits including the device and IOP code)
// as the index. See the comments in IIOTDevice for the reasoning here.
//
// We special case IOT 60 here -- this does not appear in any documentation but it does
// show up in the 2nd-stage loader on a number of tapes as the first instruction.
// Unsure what the purpose is (there are some hints that doing an IOT (any IOT) is needed to
// trigger something) but ignoring it works fine for now.
//
if (_currentInstruction.Data == 0x30)
{
return;
}
IIOTDevice device = _iotDispatch[_currentInstruction.Data];
if (device != null)
{
device.ExecuteIOT(_currentInstruction.Data);
}
else
{
Trace.Log(LogType.Processor, "Unimplemented IOT device {0}, IOT opcode {1}", Helpers.ToOctal((ushort)_currentInstruction.IOTDevice), Helpers.ToOctal(_currentInstruction.Data));
}
}
private ushort DoFetchForCurrentInstruction()
{
ushort effectiveAddress = _currentInstruction.IsIndirect ? _currentIndirectAddress : GetEffectiveAddress(_currentInstruction.Data);
ushort value = DoFetchForAddress(effectiveAddress);
switch (_byteAccess)
{
case ByteAccess.Left:
value = (ushort)(value >> 8);
break;
case ByteAccess.Right:
value = (ushort)(value & 0xff);
break;
}
return value;
}
private ushort DoFetchForAddress(ushort effectiveAddress)
{
// If there's a read breakpoint on this address we will halt here.
if (BreakpointManager.TestBreakpoint(BreakpointType.Read, effectiveAddress))
{
_state = ProcessorState.BreakpointHalt;
_breakpointAddress = effectiveAddress;
}
return _mem.Fetch(effectiveAddress);
}
private ushort GetEffectiveAddress(ushort baseAddress)
{
return (ushort)((_pc & (Memory.SizeMask & 0xf800)) | (baseAddress & 0x07ff));
}
private void DoStoreForCurrentInstruction(ushort word)
{
ushort effectiveAddress = _currentInstruction.IsIndirect ? _currentIndirectAddress : GetEffectiveAddress(_currentInstruction.Data);
// If there's a write breakpoint on this address we will halt here.
if (BreakpointManager.TestBreakpoint(BreakpointType.Write, effectiveAddress))
{
_state = ProcessorState.BreakpointHalt;
_breakpointAddress = effectiveAddress;
}
switch (_byteAccess)
{
case ByteAccess.Normal:
_mem.Store(effectiveAddress, word);
break;
case ByteAccess.Left:
{
ushort value = _mem.Fetch(effectiveAddress);
_mem.Store(effectiveAddress, (ushort)((value & 0xff) | (word << 8)));
}
break;
case ByteAccess.Right:
{
ushort value = _mem.Fetch(effectiveAddress);
_mem.Store(effectiveAddress, (ushort)((value & 0xff00) | (word & 0xff)));
}
break;
}
}
private void Push(ushort pointer, ushort value)
{
_mem.Store((ushort)(_sp[pointer] & Memory.SizeMask), value);
_sp[pointer]--;
}
private ushort Pop(ushort pointer)
{
_sp[pointer]++;
return _mem.Fetch((ushort)(_sp[pointer] & Memory.SizeMask));
}
private Instruction _currentInstruction;
private ushort _currentIndirectAddress;
private ExecState _instructionState;
private ImlacSystem _system;
private Memory _mem;
private Instruction[] _instructionCache;
private ProcessorState _state;
//
// registers
//
private ushort _pc;
private ushort _link;
private ushort _ac;
//
// PDS-4 Stack Pointers
//
private ushort[] _sp = new ushort[2];
//
// PDS-4 byte access mode set by SBL, SBR
//
private ByteAccess _byteAccess;
//
// Debug information -- the PC at which the last breakpoint occurred.
//
private ushort _breakpointAddress;
//
// Front panel data switch
//
private ushort _ds;
//
// IOT dispatch table
//
private IIOTDevice[] _iotDispatch;
private enum ByteAccess
{
Normal = 0,
Left,
Right
}
private enum Opcode
{
// PDS-1 Opcodes:
LAW, // Load Accumulator With
LWC, // Load Accumulator With complement
JMP, // Jump
DAC, // Deposit Accumulator
XAM, // Exchange Accumulator with Memory
ISZ, // Increment and Skip on Zero
JMS, // Jump to Subroutine
AND, // Logical AND
IOR, // Inclusive OR
XOR, // Exclusive OR
LAC, // Load Accumulator w/memory
ADD, // Add to Accumulator
SUB, // Subtract from Accumulator
SAM, // Skip if Accumulator is same as memory
OPR, // Operate class 1 instruction
IOT, // IOT instruction
RAL, // Rotate left
RAR, // Rotate right
SAL, // Shift left
SAR, // Shift right
SKP, // generic skip op (encompasses all class 3 instructions which may be combined in a multitude of ways)
// PDS-4 Opcodes:
SAD, // Skip if memory not equal to AC
DCM, // Decrement memory by 1
SWAP, // Swap AC bytes
RDPC, // Read DPC
RDAX, // Read Display XAC
RDAY, // Read Display YAC
SBL, // Set byte left
SBR, // Set byte right
TAC, // Test AC
DEA, // Decrement AC
POPD, // Pop MDS Stack
PUSHD, // Push MDS Stack
LAMP, // Flash kybd lamp
DACS, // AC into SP
LACS, // SP into AC
PUSH, // AC into stack
PUSHA, // PC into stack
POP, // Stack into AC
POPA, // Stack into PC
LIAC, // Load AC indirect
EXACT, // EXACT instruction
}
private class Instruction
{
public Instruction(ushort word)
{
Decode(word);
}
public bool IsIndirect
{
get { return _indirect; }
}
public bool IsOperateOrIOT
{
get { return _operateOrIOT; }
}
public bool DisplayOn
{
get { return _displayOn; }
}
public Opcode Opcode
{
get { return _opcode; }
}
public ushort Data
{
get { return _data; }
}
public bool SkipNegate
{
get { return _skipNegate; }
}
public int IOTDevice
{
get { return _iotDevice; }
}
public int IOP
{
get { return _iop; }
}
public string Disassemble(ushort address)
{
StringBuilder sb = new StringBuilder();
if (IsIndirect &&
Opcode != Processor.Opcode.LAW &&
Opcode != Processor.Opcode.LWC)
{
sb.Append("I ");
}
string effectiveAddress = Helpers.ToOctal(GetEffectiveAddress(address, Data));
// TODO: might make sense to define this as a big ol' table.
switch (Opcode)
{
case Processor.Opcode.ADD:
sb.AppendFormat("ADD {0}", effectiveAddress);
break;
case Processor.Opcode.AND:
sb.AppendFormat("AND {0}", effectiveAddress);
break;
case Processor.Opcode.DAC:
sb.AppendFormat("DAC {0}", effectiveAddress);
break;
case Processor.Opcode.DACS:
sb.AppendFormat("DACS {0}", Data);
break;
case Processor.Opcode.DCM:
sb.AppendFormat("DCM {0}", effectiveAddress);
break;
case Processor.Opcode.EXACT:
sb.AppendFormat("EXACT {0}", Helpers.ToOctal(Data));
break;
case Processor.Opcode.IOR:
sb.AppendFormat("IOR {0}", effectiveAddress);
break;
case Processor.Opcode.IOT:
sb.Append(DisassembleIOT());
break;
case Processor.Opcode.ISZ:
sb.AppendFormat("ISZ {0}", effectiveAddress);
break;
case Processor.Opcode.JMP:
sb.AppendFormat("JMP {0}", effectiveAddress);
break;
case Processor.Opcode.JMS:
sb.AppendFormat("JMS {0}", effectiveAddress);
break;
case Processor.Opcode.LAC:
sb.AppendFormat("LAC {0}", effectiveAddress);
break;
case Processor.Opcode.LACS:
sb.AppendFormat("LACS {0}", Data);
break;
case Processor.Opcode.LAW:
sb.AppendFormat("LAW {0}", Helpers.ToOctal(Data));
break;
case Processor.Opcode.LWC:
sb.AppendFormat("LWC {0}\t!({1})", Helpers.ToOctal(Data), Helpers.ToOctal((ushort)(-Data)));
break;
case Processor.Opcode.OPR:
sb.Append(DisassembleOPR());
break;
case Processor.Opcode.POP:
sb.AppendFormat("POP {0}", Data);
break;
case Processor.Opcode.POPA:
sb.AppendFormat("POPA {0}", Data);
break;
case Processor.Opcode.POPD:
sb.AppendFormat("POPA {0}", Data);
break;
case Processor.Opcode.PUSH:
sb.AppendFormat("PUSH {0}", Data);
break;
case Processor.Opcode.PUSHA:
sb.AppendFormat("PUSHA {0}", Data);
break;
case Processor.Opcode.PUSHD:
sb.AppendFormat("PUSHD {0}", Data);
break;
case Processor.Opcode.RAL:
sb.AppendFormat("RAL {0},{1}", Data, DisplayOn ? "DON" : String.Empty);
break;
case Processor.Opcode.RAR:
sb.AppendFormat("RAR {0},{1}", Data, DisplayOn ? "DON" : String.Empty);
break;
case Processor.Opcode.SAD:
sb.AppendFormat("SAD {0}", effectiveAddress);
break;
case Processor.Opcode.SAL:
sb.AppendFormat("SAL {0},{1}", Data, DisplayOn ? "DON" : String.Empty);
break;
case Processor.Opcode.SAM:
sb.AppendFormat("SAM {0}", effectiveAddress);
break;
case Processor.Opcode.SAR:
sb.AppendFormat("SAR {0},{1}", Data, DisplayOn ? "DON" : String.Empty);
break;
case Processor.Opcode.SKP:
sb.Append(DisassembleSKP());
break;
case Processor.Opcode.SUB:
sb.AppendFormat("SUB {0}", effectiveAddress);
break;
case Processor.Opcode.XAM:
sb.AppendFormat("XAM {0}", effectiveAddress);
break;
case Processor.Opcode.XOR:
sb.AppendFormat("XOR {0}", effectiveAddress);
break;
default:
// All other ops take no args, just print as is (including undecoded ops)
if (Enum.IsDefined(typeof(Opcode), Opcode))
{
sb.AppendFormat("{0}", Opcode);
}
else
{
sb.AppendFormat("Illegal instruction {0}", Helpers.ToOctal((ushort)Opcode));
}
break;
}
return sb.ToString();
}
private string DisassembleIOT()
{
// todo: just have a lookup table for this.
string iot;
switch (Data)
{
case 0x01:
iot = "DLA\t! Load DPC With AC";
break;
case 0x02:
if (Configuration.CPUType == ImlacCPUType.PDS4)
{
iot = "DON\t! Display On";
}
else
{
// Shouldn't be used on PDS-1 but we'll note it here
iot = "DON (PDS-4)\t! Display On ";
}
break;
case 0x03:
if (Configuration.CPUType == ImlacCPUType.PDS4)
{
iot = "DLN\t! Load DPC and Turn Display On";
}
else
{
iot = "DLA\t! Load CP With AC";
}
break;
case 0x09:
iot = "CTB\t! Clear TTY Break";
break;
case 0x0a:
iot = "DOF\t! Turn Display Processor Off";
break;
case 0x11:
iot = "KRB\t! Keyboard Read";
break;
case 0x12:
iot = "KCF\t! Keyboard Clear Flag";
break;
case 0x13:
iot = "KRC\t! Keyboard Read and Clear Flag";
break;
case 0x19:
iot = "RRB\t! TTY Read";
break;
case 0x1a:
iot = "RCF\t! Clear Input TTY Status";
break;
case 0x1b:
iot = "RRC\t! TTY Read and Clear Flag";
break;
case 0x21:
iot = "TPR\t! TTY Transmit";
break;
case 0x22:
iot = "TCF\t! Clear Output TTY Status";
break;
case 0x23:
iot = "TPC\t! TTY Print and Clear Flag";
break;
case 0x29:
iot = "HRB\t! Read Paper Tape Reader";
break;
case 0x2a:
iot = "HOF\t! Stop Paper Tape Reader";
break;
case 0x31:
iot = "HON\t! Start Paper Tape Reader";
break;
case 0x32:
iot = "STB\t! Set TTY Break";
break;
case 0x39:
iot = "SCF\t! Clear 40 Cycle Sync";
break;
case 0x3a:
iot = "IOS\t! IOT Sync";
break;
case 0x71:
iot = "IOF\t! Disable First Level Interrupt";
break;
case 0x72:
iot = "ION\t! Enable First Level Interrupt";
break;
case 0xb9:
iot = "PPC\t! Punch Accumulator";
break;
case 0xbc:
iot = "PSF\t! Skip if Punch Flag is Set";
break;
default:
iot = String.Format("IOT {0}\t! {1}", Helpers.ToOctal(Data), GetIOTDescription(Data));
break;
}
return iot;
}
private string GetIOTDescription(int iot)
{
foreach(IOTDescription desc in _iotDescription)
{
if (desc.Code == iot)
{
return desc.Description;
}
}
return "Unknown IOT";
}
private string DisassembleOPR()
{
string opr = String.Empty;
string[] lowerCodes = { "NOP", "CLA", "CMA", "STA", "IAC", "COA", "CIA", "CMA, IAC", "CLA, CMA, IAC" };
string[] upperCodes = { "", "CLL", "CML", "STL", "ODA", "CLL, CML", "CML, ODA", "CLL, CML", "CLL, CML, ODA" };
// check for two specially named combinations of upper and lower bits:
if (Data == 0x9)
{
opr = "CAL";
}
else if (Data == 0x21)
{
opr = "LDA";
}
else
{
int lowIndex = Data & 0x7;
int highIndex = (Data & 0x38) >> 3;
if (highIndex != 0 && lowIndex != 0)
{
opr = String.Format("{0},{1}", lowerCodes[lowIndex], upperCodes[highIndex]);
}
else if (highIndex != 0)
{
opr = upperCodes[highIndex];
}
else if (lowIndex != 0)
{
opr = lowerCodes[lowIndex];
}
}
if ((Data & 0x8000) == 0)
{
opr += string.IsNullOrEmpty(opr) ? "HLT" : ", HLT";
}
return opr;
}
private string DisassembleSKP()
{
string skp = String.Empty;
// Data should be non-empty
if (Data == 0)
{
throw new InvalidOperationException("SKP instruction with no flags set.");
}
// these correspond to the bit set, from the lsb to the msb and can be combined.
string[] codes = { "ASZ", "ASP", "LSZ", "DSF", "KSF", "RSF", "TSF", "SSF", "HSF" };
string[] notCodes = { "ASN", "ASM", "LSN", "DSN", "KSN", "RSN", "TSN", "SSN", "HSN" };
for (int i = 0; i < 9; i++)
{
if ((Data & (0x01) << i) != 0)
{
if (!string.IsNullOrEmpty(skp))
{
skp += ",";
}
skp += SkipNegate ? notCodes[i] : codes[i];
}
}
return skp;
}
private ushort GetEffectiveAddress(ushort currentAddress, ushort baseAddress)
{
return (ushort)((currentAddress & 0x0800) | baseAddress);
}
private void Decode(ushort word)
{
// try to decode from most specified op type to least specific.
if (!DecodeClass1(word))
{
if (!DecodeClass2(word))
{
if (!DecodeAct2Class(word))
{
if (!DecodeClass3(word))
{
if (!DecodeIOT(word))
{
if (!DecodeExactClass(word))
{
if (!DecodeOrder(word))
{
Helpers.SignalError(
LogType.Processor,
"Unhandled instruction {0}",
Helpers.ToOctal(word));
}
}
}
}
}
}
}
}
private bool DecodeClass1(ushort word)
{
// All class 1 instructions contain 0s in bits 1-9. (Bit zero
// encodes a Halt instruction if clear).
if ((word & 0x7fc0) == 0x0000)
{
_opcode = Opcode.OPR;
//
// Save the bits defining the T1, T2, and T3 operations
// (bits 10-15) and the HALT flag.
//
_data = (ushort)(word & 0x803f);
_operateOrIOT = true;
return true;
}
else
{
// not a class 1 microinstruction
return false;
}
}
private bool DecodeClass2(ushort word)
{
//
// All class 2 instructions contain 0000011 in bits 0-6
// and are SHIFT instructions (PDS-1) or
// ACT 1 class instructions (PDS-4)
//
if ((word & 0xfe00) == 0x0600)
{
// If bit 7 is set, this is a Display On instruction for the PDS-1
// (which somehow got lumped in with SHIFT instructions on the -1 and was apparently
// corrected to be more consistent with other display control instructions on the PDS-4).
if (Configuration.CPUType == ImlacCPUType.PDS1 && (word & 0x0040) == 0x0040)
{
_displayOn = true;
}
//
// On the PDS-1, there are only shifts/rotates (and the above DON oddity)
// in this decode branch (instruction prefix octal 003xxx) and it's
// possible to specify a Shift/Rotate of 0 bits (effectively a NOP).
// On the PDS-4, there are extra instructions here, some of which use
// what would be zero shift values on the PDS-1.
bool decoded = true;
if (Configuration.CPUType == ImlacCPUType.PDS4)
{
switch (word & 0x1f)
{
case 0x00:
_opcode = Opcode.SWAP;
break;
case 0x04:
_opcode = Opcode.RDPC;
break;
case 0x05:
_opcode = Opcode.RDAX;
break;
case 0x06:
_opcode = Opcode.RDAY;
break;
case 0x08:
_opcode = Opcode.SBL;
break;
case 0x09:
_opcode = Opcode.SBR;
break;
case 0x14:
_opcode = Opcode.TAC;
break;
case 0x15:
_opcode = Opcode.DEA;
break;
case 0x28:
_opcode = Opcode.POPD;
break;
case 0x29:
_opcode = Opcode.PUSHD;
break;
case 0x2a:
_opcode = Opcode.LAMP;
break;
case 0x18:
case 0x19:
_opcode = Opcode.DACS;
_data = (ushort)(word & 0x1);
break;
case 0x1a:
case 0x1b:
_opcode = Opcode.LACS;
_data = (ushort)(word & 0x1);
break;
default:
decoded = false;
break;
}
}
else
{
decoded = false;
}
if (!decoded)
{
// Must be a shift or rotate operation.
_data = (ushort)(word & 0x0003);
if ((word & 0x0020) == 0x0000)
{
// Rotate
if ((word & 0x0010) == 0x0000)
{
_opcode = Opcode.RAL;
}
else
{
_opcode = Opcode.RAR;
}
}
else
{
// Shift
if ((word & 0x0010) == 0x0000)
{
_opcode = Opcode.SAL;
}
else
{
_opcode = Opcode.SAR;
}
}
}
_operateOrIOT = true;
return true;
}
else
{
// not a class 2 microinstruction
return false;
}
}
private bool DecodeClass3(ushort word)
{
// Operate class 3 instructions have 00010 in bits 1-6
if ((word & 0x7e00) == 0x0400)
{
_opcode = Opcode.SKP;
_skipNegate = (word & 0x8000) != 0x0000;
// Save the flag bits
_data = (ushort)(word & 0x01ff);
_operateOrIOT = true;
return true;
}
else
{
return false;
}
}
private bool DecodeAct2Class(ushort word)
{
// Act 2 Class instructions have 1 000 011 000 in bits 0-9
// and are only valid on PDS-4 processors.
if (Configuration.CPUType == ImlacCPUType.PDS4 &&
(word & 0xffc0) == 0x8600)
{
_operateOrIOT = false; // All require two cycles
_data = (ushort)(word & 0x1);
switch (word & 0xf)
{
case 0x0:
case 0x1:
_opcode = Opcode.PUSH;
break;
case 0x2:
case 0x3:
_opcode = Opcode.PUSHA;
break;
case 0x4:
case 0x5:
_opcode = Opcode.POP;
break;
case 0x6:
case 0x7:
_opcode = Opcode.POPA;
break;
case 0x8:
_opcode = Opcode.LIAC;
break;
default:
// Nope.
return false;
}
return true;
}
else
{
return false;
}
}
private bool DecodeIOT(ushort word)
{
// All IOT instructions contain 0000001 in bits 0-6
if ((word & 0xfe00) == 0x0200)
{
_opcode = Opcode.IOT;
_iotDevice = (word & 0x1f8) >> 3;
_iop = (ushort)(word & 0x0007);
_data = (ushort)(word & 0x1ff);
_operateOrIOT = true;
return true;
}
else
{
return false;
}
}
private bool DecodeExactClass(ushort word)
{
// All Exact Class instructions contain 1 000 001 in bits 0-6
// and exist only on PDS-4 systems
if (Configuration.CPUType == ImlacCPUType.PDS4 &&
(word & 0xfe00) == 0x8200)
{
_data = (ushort)(word & 0x1ff);
_opcode = Opcode.EXACT;
_operateOrIOT = true;
return true;
}
else
{
return false;
}
}
private bool DecodeOrder(ushort word)
{
int orderCode = (word & 0x7800) >> 9;
_indirect = (word & 0x8000) != 0;
_data = (ushort)(word & 0x07ff);
switch (orderCode)
{
case 0x04:
_opcode = _indirect ? Opcode.LWC : Opcode.LAW;
_indirect = false; // This is never actually an indirect instruction.
break;
case 0x08:
_opcode = Opcode.JMP;
break;
case 0x0c:
_opcode = Opcode.SAD;
break;
case 0x10:
_opcode = Opcode.DAC;
break;
case 0x14:
_opcode = Opcode.XAM;
break;
case 0x18:
_opcode = Opcode.ISZ;
break;
case 0x1c:
_opcode = Opcode.JMS;
break;
case 0x20:
_opcode = Opcode.DCM;
break;
case 0x24:
_opcode = Opcode.AND;
break;
case 0x28:
_opcode = Opcode.IOR;
break;
case 0x2c:
_opcode = Opcode.XOR;
break;
case 0x30:
_opcode = Opcode.LAC;
break;
case 0x34:
_opcode = Opcode.ADD;
break;
case 0x38:
_opcode = Opcode.SUB;
break;
case 0x3c:
_opcode = Opcode.SAM;
break;
default:
return false;
}
return true;
}
public struct IOTDescription
{
public IOTDescription(int code, string description)
{
Code = code;
Description = description;
}
public int Code;
public string Description;
}
private static IOTDescription[] _iotDescription = new IOTDescription[]
{
new IOTDescription(0x41, "Read Interrupt Status (Word One)"),
new IOTDescription(0x42, "Read Interrupt Status (Word Two)"),
new IOTDescription(0x61, "Arm Device Status (Word One)"),
new IOTDescription(0x62, "Arm Device Status (Word Two)"),
new IOTDescription(0x89, "Read Second Level Interrupt Status"),
new IOTDescription(0x91, "Disable Second Level Interrupts"),
new IOTDescription(0x92, "Enable Second Level Interrupts"),
new IOTDescription(0x93, "Disable, Enable Second Level Interrupts"),
new IOTDescription(0xd1, "Turn on Audible Tone for appx. 2 sec."),
new IOTDescription(0x0c, "CBS-41 Skip if Cassette Data Ready"),
new IOTDescription(0x14, "CBS-41 Skip if Cassette Data Bit = 1"),
new IOTDescription(0x1c, "CBS-41 Write One Pulse"),
new IOTDescription(0x64, "CBS-41 Start Cassette"),
new IOTDescription(0x6c, "CBS-41 Stop Cassette"),
new IOTDescription(0xcc, "CBS-42 Skip if Data Ready"),
new IOTDescription(0xd4, "CBS-42 Skip if Data Bit = 1"),
new IOTDescription(0xdc, "CBS-42 Write One Pulse"),
new IOTDescription(0xe4, "CBS-42 Start Cassette"),
new IOTDescription(0xec, "CBS-42 Stop Cassette"),
new IOTDescription(0x04, "Tablet clear Status"),
new IOTDescription(0x24, "Tablet Skip"),
new IOTDescription(0x2c, "Tablet Read X Co-ordinate"),
new IOTDescription(0x34, "Tablet Read Y Co-ordinate"),
new IOTDescription(0x3c, "Tablet Read Z-Co-ordinate"),
new IOTDescription(0x101, "Disc Drive Load Memory Address"),
new IOTDescription(0x102, "Disc Drive Load Partial Read Control"),
new IOTDescription(0x104, "Disk Drive Skip if Drive Ready"),
new IOTDescription(0x109, "Disk Drive Seek Cylinder Dual Drive Only"),
new IOTDescription(0x10a, "Disk Drive Control Command"),
new IOTDescription(0x10c, "Disk Drive Skip if Attention Drive 0"),
new IOTDescription(0x113, "Disk Drive Write Sector Command"),
new IOTDescription(0x114, "Disk Drive Skip if Attention Drive 1"),
new IOTDescription(0x11b, "Disk Drive Read Sector Command"),
new IOTDescription(0x11c, "Disk Drive Skip if attention Drive 2"),
new IOTDescription(0x121, "Disk Drive Read Status Command"),
new IOTDescription(0x124, "Disk Drive Skip if Attention Drive 3"),
new IOTDescription(0xc4, "Clear Display Halt Status Flag"),
new IOTDescription(0x129, "Function Keyboard Load Word One"),
new IOTDescription(0x121, "Function Keyboard Load Word Two"),
new IOTDescription(0x131, "Function Keyboard Read Word One"),
new IOTDescription(0x132, "Function Keyboard Read Word Two"),
new IOTDescription(0x134, "Function Keyboard Skip if Data"),
new IOTDescription(0xc3, "Read Mouse X and Y coordinates."),
new IOTDescription(0xd9, "Read Mouse Switches and Keyset"),
new IOTDescription(0x16c, "Skip on Graphic Device Input"),
new IOTDescription(0xc9, "Read Joystick X Coordinate"),
new IOTDescription(0xca, "Read Joystick Y Coordinate"),
new IOTDescription(0x6a, "HDC-41 Half Duplex Turnaround Initialize"),
new IOTDescription(0xa4, "HDC-41 Skip if Clear to Send"),
new IOTDescription(0x44, "Start Print (hard copy)"),
new IOTDescription(0x4c, "Skip if Hard Copy Busy"),
new IOTDescription(0xa9, "Read KYB #2"),
new IOTDescription(0xaa, "Clear KYB #2"),
new IOTDescription(0xa9, "Read KYB #2"),
new IOTDescription(0x59, "Read Light Pen Status to AC"),
new IOTDescription(0x5a, "Clear Light Pen Status"),
new IOTDescription(0x5c, "Skip if Light Pen Status = 1"),
new IOTDescription(0x12a, "Clear Light Pen #2 Status"),
new IOTDescription(0x12c, "Skip if Light Pen #2 Status = 1"),
new IOTDescription(0x139, "Print and Clear Printer Flag"),
new IOTDescription(0x13c, "Skip if Printer Ready"),
new IOTDescription(0x141, "MTC-41 Load Memory Address"),
new IOTDescription(0x142, "MTC-41 Load Record Size"),
new IOTDescription(0x144, "MTC-41 Skip if Selected Transport Ready"),
new IOTDescription(0x149, "MTC-41 Read DMA Memory Address"),
new IOTDescription(0x14a, "MTC-41 Read Tape Status"),
new IOTDescription(0x14c, "MTC-41 Skip if Tape System Ready"),
new IOTDescription(0x153, "MTC-41 Transport Command"),
new IOTDescription(0x154, "MTC-41 Skip if Tape Data Ready"),
new IOTDescription(0x15b, "MTC-41 Control Command"),
new IOTDescription(0x15c, "MTC-41 Skip if No Check Flag"),
new IOTDescription(0x49, "Set Memory Protect Per AC Bit One"),
new IOTDescription(0x4a, "Clear Memory Protect Status"),
new IOTDescription(0x79, "XYR-1 Load X Display AC Buffer"),
new IOTDescription(0x7a, "XYR-1 Load Y Display AC Buffer"),
new IOTDescription(0x7c, "Load Plotter Register"),
new IOTDescription(0x51, "Load Clock from AC"),
new IOTDescription(0x52, "Clear Clock Status"),
new IOTDescription(0x54, "Skip if Clock Status = 1"),
new IOTDescription(0x69, "Read Clock Into AC"),
new IOTDescription(0x81, "Clear TTY-2 Break"),
new IOTDescription(0x82, "Set TTY-2 Break"),
new IOTDescription(0x84, "Skip if TTY-2 Input Status Set"),
new IOTDescription(0x8c, "Skip if TTY-2 Input Status Clear"),
new IOTDescription(0x94, "Skip if TTY-2 Output Status Set"),
new IOTDescription(0x9c, "Skip if TTY-2 Output Status Clear"),
new IOTDescription(0x3a, "IOT Sync (External Diagnostic Scope Trigger)"),
new IOTDescription(0x74, "Load Send/Receive Format"),
new IOTDescription(0xb1, "Load Send Speed"),
new IOTDescription(0xb1, "Load Receive Speed"),
};
private Opcode _opcode;
private ushort _data;
private bool _skipNegate;
private bool _displayOn;
private int _iotDevice;
private int _iop;
private bool _indirect;
private bool _operateOrIOT;
}
}
}