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

Implemented most of the PDS-4 display hardware enhancements (including BLINK, which is very important.) Implemented SBL and SBR, multiple indirects, and fixed decdoding of ACT 2 instructions. Rewired interrupt status bits (arbitrarily rearranged on the PDS-4). SSV4 now mostly seems to work, keyboard still non-functional.

This commit is contained in:
Josh Dersch
2020-04-26 01:56:10 -07:00
parent 5b9eb37f9c
commit 64d027dcab
8 changed files with 579 additions and 128 deletions

View File

@@ -27,7 +27,9 @@ namespace imlac
{
Indeterminate,
Processor,
Increment
Increment,
MediumVector,
CompactAddressing,
}
public enum ImmediateHalf
@@ -53,6 +55,7 @@ namespace imlac
public virtual void Reset()
{
State = ProcessorState.Halted;
_halted = false;
_mode = DisplayProcessorMode.Processor;
_pc = 0;
_block = 0;
@@ -63,7 +66,7 @@ namespace imlac
_dadr = false;
_system.Display.MoveAbsolute(X, Y, DrawingMode.Off);
_system.Display.MoveAbsolute(0, 0, DrawingMode.Off);
_clocks = 0;
_frameLatch = false;
@@ -105,6 +108,11 @@ namespace imlac
}
}
public bool IsHalted
{
get { return _halted; }
}
public DisplayProcessorMode Mode
{
get { return _mode; }
@@ -137,7 +145,7 @@ namespace imlac
set { _frameLatch = value; }
}
public uint X
public int X
{
get { return _x; }
set
@@ -146,7 +154,7 @@ namespace imlac
}
}
public uint Y
public int Y
{
get { return _y; }
set
@@ -183,6 +191,20 @@ namespace imlac
}
}
public virtual void StartProcessor()
{
State = ProcessorState.Running;
// MIT DADR bit gets reset when display is started.
_dadr = false;
_halted = false;
}
public virtual void HaltProcessor()
{
State = ProcessorState.Halted;
_halted = true;
}
public abstract void InitializeCache();
public abstract void InvalidateCache(ushort address);
@@ -195,13 +217,8 @@ namespace imlac
public abstract void ExecuteIOT(int iotCode);
protected void ReturnFromDisplaySubroutine()
{
Pop();
}
protected uint _x;
protected uint _y;
protected int _x;
protected int _y;
protected float _scale;
protected ushort _pc;
protected ushort _block;
@@ -219,6 +236,9 @@ namespace imlac
protected bool _frameLatch;
protected ProcessorState _state;
protected bool _halted; // The Halted flag is independent of the current state.
// (i.e. it is set when the processor gets halted, but can later be cleared
// whiile the display remains halted.)
protected DisplayProcessorMode _mode;
protected ImlacSystem _system;
protected Memory _mem;

View File

@@ -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,6 +221,10 @@ namespace imlac
void SetScale(float scale);
void SetIntensity(int intensity);
void SetBlink(bool on);
void MapDataSwitch(uint switchNumber, VKeys key);
VKeys GetDataSwitchMapping(uint switchNumber);

View File

@@ -74,39 +74,81 @@ namespace imlac.IO
// 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)
{
_interruptStatus |= 0x0002;
}
// bit 11 - keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x0010;
}
// bit 12 - TTY rcv
if (_system.TTY.DataReady)
{
_interruptStatus |= 0x0008;
}
// bit 10 - TTY send
if (_system.TTY.DataSentLatch)
{
_interruptStatus |= 0x0020;
}
// bit 11 - keyboard
if (_system.Keyboard.KeyReady)
{
_interruptStatus |= 0x0010;
}
// bit 2 - ACI-1 (clock)
if (_system.Clock.TimerTriggered)
// 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.IsHalted)
{
_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;
}
@@ -115,7 +157,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);
@@ -158,7 +200,13 @@ namespace imlac.IO
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:
@@ -168,7 +216,7 @@ 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)));
}
}
@@ -186,8 +234,8 @@ namespace imlac.IO
0x71, // IOF (disable interrupts)
0x72, // ION (enabled masked interrupts)
// PDS-4 2nd level interrupt facility
0x42, // read interrupt status bits word two
// 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

View File

@@ -78,7 +78,7 @@ namespace imlac.IO
if (_dataBufferFull && _dataChannel.OutputReady)
{
_dataChannel.Write(_txData);
Trace.Log(LogType.TTY, "o");
Trace.Log(LogType.TTY, "o {0}", Helpers.ToOctal(_txData));
_dataBufferFull = false;
_dataSentLatch = true;
}

View File

@@ -119,7 +119,7 @@ namespace imlac
break;
case 0x0a: // Halt display processor
State = ProcessorState.Halted;
HaltProcessor();
break;
case 0x39: // Clear display 40Hz sync latch
@@ -128,10 +128,7 @@ namespace imlac
case 0xc4: // clear halt state
// TODO: what does this actually do?
//State = ProcessorState.Running;
// MIT DADR bit gets reset when display is started.
_dadr = false;
_halted = false;
break;
default:
@@ -187,7 +184,7 @@ namespace imlac
{
// DHLT -- halt the display processor. other micro-ops in this
// instruction are still run.
State = ProcessorState.Halted;
HaltProcessor();
}
if ((instruction.Data & 0x400) != 0)
@@ -292,7 +289,7 @@ namespace imlac
break;
case DisplayOpcode.DLXA:
X = (uint)(instruction.Data << 1);
X = instruction.Data << 1;
DrawingMode mode;
if (_sgrModeOn && _sgrBeamOn)
@@ -319,7 +316,7 @@ namespace imlac
break;
case DisplayOpcode.DLYA:
Y = (uint)(instruction.Data << 1);
Y = instruction.Data << 1;
if (_sgrModeOn && _sgrBeamOn)
{
@@ -371,7 +368,10 @@ namespace imlac
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)
{
@@ -398,29 +398,29 @@ namespace imlac
if ((halfWord & 0x10) != 0)
{
X += 0x20;
newX += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment X MSB, X is now {0}", X);
}
if ((halfWord & 0x08) != 0)
{
X = X & (0xffe0);
newX = newX & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset X LSB, X is now {0}", X);
}
if ((halfWord & 0x02) != 0)
{
Y += 0x20;
newY += 0x20;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Increment Y MSB, Y is now {0}", Y);
}
if ((halfWord & 0x01) != 0)
{
Y = Y & (0xffe0);
newY = newY & (0xffe0);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Reset Y LSB, Y is now {0}", Y);
}
_system.Display.MoveAbsolute(X, Y, DrawingMode.Off);
_system.Display.MoveAbsolute(newX, newY, DrawingMode.Off);
}
else
@@ -431,15 +431,28 @@ namespace imlac
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03) * _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Inc mode ({0}:{1}), x={2} y={3} dx={4} dy={5} beamon {6}", Helpers.ToOctal((ushort)_pc), Helpers.ToOctal((ushort)halfWord), X, Y, xSign * xMag, ySign * yMag, (halfWord & 0x40) != 0);
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);
X = (uint)(X + xSign * xMag * 2);
Y = (uint)(Y + ySign * yMag * 2);
_system.Display.MoveAbsolute(X, Y, (halfWord & 0x40) == 0 ? DrawingMode.Off : DrawingMode.Normal);
newX = (int)(newX + xSign * xMag * 2);
newY = (int)(newY + ySign * yMag * 2);
_system.Display.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))
{
@@ -510,16 +523,25 @@ namespace imlac
// * 2 for translation to 11-bit space
// The docs don't call this out, but the scale setting used in increment mode appears to apply
// to the LVH vectors as well. (Maze appears to rely on this.)
X = (uint)(X + (dx * dxSign) * 2 * _scale);
Y = (uint)(Y + (dy * dySign) * 2 * _scale);
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}", X, Y, dx * dxSign, dy * dySign, beamOn, dotted);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector, move complete - x={0} y={1}", newX, newY, dx * dxSign, dy * dySign, beamOn, dotted);
_system.Display.MoveAbsolute(X, Y, beamOn ? (dotted ? DrawingMode.Dotted : DrawingMode.Normal) : DrawingMode.Off);
_system.Display.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)
@@ -610,9 +632,9 @@ namespace imlac
_opcode = DisplayOpcode.DEIM;
_data = (ushort)(_word & 0xff);
if ((_word & 0x0800) != 0)
if ((_word & 0xff00) == 0x3800)
{
Console.Write("PPM-1 not implemented (instr {0})", Helpers.ToOctal(_word));
Console.WriteLine("PPM-1 not implemented (instr {0})", Helpers.ToOctal(_word));
}
break;

View File

@@ -41,6 +41,12 @@ namespace imlac
_fxyOn = false;
_fxyBeamOn = false;
_fxyDRJMOn = false;
_camEnabled = false;
_caBase = 0;
_camHalf = ImmediateHalf.First;
_camWord = 0;
_camStackDepth = 0;
}
public override void InitializeCache()
@@ -95,6 +101,10 @@ namespace imlac
case DisplayProcessorMode.Increment:
ExecuteIncrement();
break;
case DisplayProcessorMode.CompactAddressing:
ExecuteCompactAddressing();
break;
}
}
@@ -103,6 +113,21 @@ namespace imlac
return _handledIOTs;
}
public override void StartProcessor()
{
base.StartProcessor();
// Beam intensity is set to maximum after display is enabled.
_system.Display.SetIntensity(16);
_system.Display.SetBlink(false);
}
public override void HaltProcessor()
{
base.HaltProcessor();
_camEnabled = false;
}
public override void ExecuteIOT(int iotCode)
{
//
@@ -120,20 +145,16 @@ namespace imlac
if (iotCode == 0x03)
{
State = ProcessorState.Running;
// MIT DADR bit gets reset when display is started.
_dadr = false;
StartProcessor();
}
break;
case 0x02: // Turn DP On.
State = ProcessorState.Running;
// MIT DADR bit gets reset when display is started.
_dadr = false;
StartProcessor();
break;
case 0x0a: // Halt display processor
State = ProcessorState.Halted;
HaltProcessor();
break;
case 0x39: // Clear display 40Hz sync latch
@@ -141,11 +162,7 @@ namespace imlac
break;
case 0xc4: // clear halt state
// TODO: what does this actually do?
//State = ProcessorState.Running;
// MIT DADR bit gets reset when display is started.
_dadr = false;
_halted = false;
break;
default:
@@ -201,7 +218,7 @@ namespace imlac
{
// DHLT -- halt the display processor. other micro-ops in this
// instruction are still run.
State = ProcessorState.Halted;
HaltProcessor();
}
// Used to modify DSTS or DSTB operation
@@ -272,8 +289,6 @@ namespace imlac
case 0x1:
// Set scale based on C and Bit 5.
_scale = c + (bit5 ? 4 : 0);
Console.WriteLine("scale {0}", _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Scale set to {0}", _scale);
break;
@@ -361,6 +376,48 @@ namespace imlac
_pc++;
break;
case DisplayOpcode.DVIC:
_system.Display.SetIntensity(instruction.Data);
_pc++;
break;
case DisplayOpcode.DCAM:
// Enter Compact Addressing Mode, this is supposedly illegal if
// we're already in that mode, so we'll throw here to help with debugging
if (_camEnabled)
{
throw new InvalidOperationException("DCAM while in Compact Addressing Mode.");
}
_camEnabled = true;
// subroutine table address is the next word. Low 8-bits should be zero, we'll
// sanity check it.
_caBase = _mem.Fetch(++_pc);
if ((_caBase & 0xff) != 0)
{
throw new InvalidOperationException(
String.Format("CAM subroutine base address {0} not on a 256-word boundary!",
Helpers.ToOctal(_caBase)));
}
// start things off by fetching the first subroutine word. This is not strictly
// accurate with respect to timing. (Ok, it's not accurate at all.)
// TODO: refactor Immediate halfword routines here (share w/short vectors?)
_camWord = _mem.Fetch(++_pc);
_camHalf = ImmediateHalf.First;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Enter Compact Addressing mode, base address {0}",
Helpers.ToOctal(_caBase));
_mode = DisplayProcessorMode.CompactAddressing;
break;
case DisplayOpcode.DBLI:
_system.Display.SetBlink((instruction.Data) != 0);
_pc++;
break;
default:
throw new NotImplementedException(String.Format("Unimplemented Display Processor Opcode {0}, ({1}), operands {1}",
instruction.Opcode,
@@ -442,8 +499,8 @@ namespace imlac
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "Inc mode ({0}:{1}), x={2} y={3} dx={4} dy={5} beamon {6}", Helpers.ToOctal((ushort)_pc), Helpers.ToOctal((ushort)halfWord), X, Y, xSign * xMag, ySign * yMag, (halfWord & 0x40) != 0);
X = (uint)(X + xSign * xMag);
Y = (uint)(Y + ySign * yMag);
X = X + xSign * xMag;
Y = Y + ySign * yMag;
_system.Display.MoveAbsolute(X, Y, (halfWord & 0x40) == 0 ? DrawingMode.Off : DrawingMode.Normal);
MoveToNextHalfWord();
@@ -465,7 +522,7 @@ namespace imlac
_immediateHalf = ImmediateHalf.First;
// Update the instruction cache with the type of instruction (to aid in debugging).
PDS4DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Increment);
PDS4DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.Increment);
}
else
{
@@ -473,6 +530,63 @@ namespace imlac
}
}
private void ExecuteCompactAddressing()
{
int halfWord = _camHalf == ImmediateHalf.First ? (_camWord & 0xff00) >> 8 : (_camWord & 0xff);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor,
"CAM Halfword {2} is {0} (fullword {1})", Helpers.ToOctal((ushort)halfWord), Helpers.ToOctal(_camWord), _camHalf);
// Is this an exit?
if (halfWord == 0xff)
{
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "CAM: Exit from CAM.");
_camEnabled = false;
_pc++;
}
else
{
// Do a subroutine jump to the routine at base address + routine halfword
ushort subroutineAddress = (ushort)(_caBase | halfWord);
_camStackDepth = _dtStack.Count;
// Store the current PC on the MDS stack
_pc--;
Push();
// TODO: does the MIT DADR mod factor in here? I assume not because
// the standard DSTB mechanism doesn't apply either
// Jump to the subroutine at table address. On return to this stack depth
// we will return to CAM to pick up the next halfword.
_pc = _mem.Fetch(subroutineAddress);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "CAM: Jump to subroutine at {0}",
Helpers.ToOctal(_pc));
}
// Return to Processor mode for the duration of the subroutine.
_mode = DisplayProcessorMode.Processor;
}
private void MoveToNextCAMHalfWord()
{
if (_camHalf == ImmediateHalf.Second)
{
_pc++;
_camWord = _mem.Fetch(_pc);
_camHalf = ImmediateHalf.First;
// Update the instruction cache with the type of instruction (to aid in debugging).
PDS4DisplayInstruction instruction = GetCachedInstruction(_pc, DisplayProcessorMode.CompactAddressing);
}
else
{
_camHalf = ImmediateHalf.Second;
}
}
private void DrawLongVector(ushort word0)
{
//
@@ -529,8 +643,8 @@ namespace imlac
// The docs don't call this out, but the scale setting used in increment mode appears to apply
// to the LVH vectors as well. (Maze appears to rely on this.)
X = (uint)(X + (dx * dxSign) * _scale);
Y = (uint)(Y + (dy * dySign) * _scale);
X = (int)(X + (dx * dxSign) * _scale);
Y = (int)(Y + (dy * dySign) * _scale);
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "LongVector, move complete - x={0} y={1}", X, Y, dx * dxSign, dy * dySign, beamOn, dotted);
@@ -545,6 +659,21 @@ namespace imlac
}
}
private void ReturnFromDisplaySubroutine()
{
Pop();
// If CAM is enabled, we return to CAM mode to pick up the next word.
if (_camEnabled && _camStackDepth == _dtStack.Count)
{
MoveToNextCAMHalfWord();
_mode = DisplayProcessorMode.CompactAddressing;
if (Trace.TraceOn) Trace.Log(LogType.DisplayProcessor, "CAM: Return from subroutine. PC {0} halfword {1}",
Helpers.ToOctal(_pc), _camHalf);
}
}
private PDS4DisplayInstruction GetCachedInstruction(ushort address, DisplayProcessorMode mode)
{
if (_instructionCache[address & Memory.SizeMask] == null)
@@ -554,12 +683,23 @@ namespace imlac
return _instructionCache[address & Memory.SizeMask];
}
// SGR-1 mode switches
private bool _fxyOn;
private bool _fxyDRJMOn;
private bool _fxyBeamOn;
// CAM data -- enabled bit and base address.
private bool _camEnabled;
private ushort _caBase;
private int _camStackDepth;
protected ushort _camWord;
protected ImmediateHalf _camHalf;
/// <summary>
/// TODO: All available PDS-4 docs insist that the X/Y AC LSB is four bits wide.
/// This was 5 bits on the PDS-1...

View File

@@ -34,6 +34,7 @@ namespace imlac
{
Fetch,
Defer,
ExtraDefer,
Execute
}
@@ -59,6 +60,7 @@ namespace imlac
_currentIndirectAddress = 0x0000;
_instructionState = ExecState.Fetch;
_state = ProcessorState.Halted;
_byteAccess = ByteAccess.Normal;
}
public void RegisterDeviceIOTs(IIOTDevice device)
@@ -124,6 +126,13 @@ namespace imlac
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;
@@ -195,21 +204,49 @@ namespace imlac
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 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).
//
if ((effectiveAddress & 0x7ff) >= 0x08 && (effectiveAddress & 0x7ff) < 0x10)
if (Configuration.CPUType == ImlacCPUType.PDS1 ||
(_pc >= 0x20 && _pc < 0x40) || _pc == 0x3ff7 || _pc == 0x3fe6) // latter is a hack to fix broken bootstrap on pds-4
{
_currentIndirectAddress++;
_mem.Store(effectiveAddress, (ushort)(_currentIndirectAddress));
// 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);
_instructionState = ExecState.Execute;
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:
@@ -223,6 +260,26 @@ namespace imlac
}
}
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.
@@ -239,7 +296,7 @@ namespace imlac
ushort q;
uint res;
if (Trace.TraceOn) Trace.Log(LogType.Processor, "{0} - {1}", _pc, _currentInstruction.Disassemble(_pc));
// if (Trace.TraceOn) Trace.Log(LogType.Processor, "{0} - {1}", _pc, _currentInstruction.Disassemble(_pc));
switch (_currentInstruction.Opcode)
{
@@ -267,8 +324,14 @@ namespace imlac
_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++;
@@ -295,6 +358,7 @@ namespace imlac
break;
case Opcode.ISZ:
_byteAccess = ByteAccess.Normal;
q = DoFetchForCurrentInstruction();
q++;
DoStoreForCurrentInstruction(q);
@@ -308,6 +372,7 @@ namespace imlac
break;
case Opcode.JMP:
_byteAccess = ByteAccess.Normal;
if (_currentInstruction.IsIndirect)
{
_pc = _currentIndirectAddress;
@@ -319,6 +384,7 @@ namespace imlac
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));
@@ -338,9 +404,15 @@ namespace imlac
_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;
@@ -350,6 +422,7 @@ namespace imlac
break;
case Opcode.LIAC:
// TODO: Are byte accesses allowed here? Manual doesn't say.
_ac = DoFetchForAddress(_ac);
_pc++;
break;
@@ -462,7 +535,7 @@ namespace imlac
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.State = ProcessorState.Running;
_system.DisplayProcessor.StartProcessor();
}
_pc++;
@@ -478,7 +551,7 @@ namespace imlac
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.State = ProcessorState.Running;
_system.DisplayProcessor.StartProcessor();
}
_pc++;
@@ -528,7 +601,7 @@ namespace imlac
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.State = ProcessorState.Running;
_system.DisplayProcessor.StartProcessor();
}
_pc++;
@@ -555,12 +628,22 @@ namespace imlac
if (_currentInstruction.DisplayOn)
{
_system.DisplayProcessor.State = ProcessorState.Running;
_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;
@@ -685,12 +768,22 @@ namespace imlac
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()
@@ -717,14 +810,27 @@ namespace imlac
}
else
{
Trace.Log(LogType.Processor, "Unimplemented IOT device {0}, IOT opcode {1}", Helpers.ToOctal((ushort)_currentInstruction.IOTDevice), Helpers.ToOctal(_currentInstruction.Data));
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);
return DoFetchForAddress(effectiveAddress);
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)
@@ -755,7 +861,26 @@ namespace imlac
_breakpointAddress = effectiveAddress;
}
_mem.Store(effectiveAddress, word);
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)
@@ -790,7 +915,10 @@ namespace imlac
//
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.
@@ -808,6 +936,13 @@ namespace imlac
//
private IIOTDevice[] _iotDispatch;
private enum ByteAccess
{
Normal = 0,
Left,
Right
}
private enum Opcode
{
// PDS-1 Opcodes:
@@ -1467,10 +1602,10 @@ namespace imlac
private bool DecodeAct2Class(ushort word)
{
// Act 2 Class instructions have 0 001 000 011 in bits 0-9
// 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) == 0x10c0)
(word & 0xffc0) == 0x8600)
{
_operateOrIOT = false; // All require two cycles

View File

@@ -71,6 +71,8 @@ namespace imlac
_displayList = new List<Vector>(_displayListSize);
_displayListIndex = 0;
_blink = false;
//
// 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
@@ -78,7 +80,7 @@ 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();
@@ -117,6 +119,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; }
@@ -189,6 +201,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;
@@ -306,8 +328,8 @@ namespace imlac
}
}
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.
//
@@ -320,7 +342,7 @@ namespace imlac
_y = y;
}
public void DrawPoint(uint x, uint y)
public void DrawPoint(int x, int y)
{
_x = x;
_y = y;
@@ -381,6 +403,11 @@ namespace imlac
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;
}
}
@@ -463,6 +490,13 @@ namespace imlac
}
}
private void SdlMouseMove(int x, int y)
{
_mouseX = x;
_mouseY = y;
}
void FullScreenToggle()
{
_fullScreen = !_fullScreen;
@@ -493,6 +527,12 @@ namespace imlac
_lock.EnterReadLock();
_frame++;
// Blinking items are on for 16 frames, off for 16.
if ((_frame % 16) == 0)
{
_frameBlink = !_frameBlink;
}
//
// If we're drawing a complete frame (not running in debug mode)
// fade out the last frame by drawing an alpha-blended black rectangle over the display.
@@ -513,7 +553,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);
@@ -531,22 +571,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 + _xOffset);
startY = (uint)(startY *_scaleFactor - _yOffset);
endX = (uint)(endX * _scaleFactor + _xOffset);
endY = (uint)(endY * _scaleFactor - _yOffset);
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();
@@ -559,9 +599,12 @@ namespace imlac
private class Vector
{
public Vector(DrawingMode mode, uint startX, uint startY, uint endX, uint endY)
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;
@@ -570,26 +613,42 @@ 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();
if (blink)
{
Console.WriteLine("blink {0},{1}-{2},{3}", startX, startY, endX, endY);
}
_intensity = intensity;
_blink = blink;
_x1 = (int)startX;
_y1 = (int)startY;
_x2 = (int)endX;
_y2 = (int)endY;
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);
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 void UpdateColor()
@@ -625,12 +684,14 @@ namespace imlac
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(200, Color.ForestGreen);
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(255, Color.OrangeRed);
@@ -886,7 +947,15 @@ namespace imlac
};
private int _dataSwitches;
private DataSwitchMappingMode _dataSwitchMappingMode;
private DataSwitchMappingMode _dataSwitchMappingMode;
//
// Mouse Data
//
private int _mouseX;
private int _mouseY;
private int _mouseButtons;
//
// SDL
@@ -909,12 +978,15 @@ namespace imlac
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;