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

Fixes for disassembly of uncached instructions; enhancements of display instruction disassembly. More refactoring of display processor code.

This commit is contained in:
Josh Dersch 2020-05-05 15:10:22 -07:00
parent fcf0c96b6c
commit 4c65f894be
4 changed files with 327 additions and 250 deletions

View File

@ -210,7 +210,7 @@ namespace imlac
public abstract void InvalidateCache(ushort address);
public abstract string Disassemble(ushort address, DisplayProcessorMode mode);
public abstract string Disassemble(ushort address, DisplayProcessorMode mode, out int length);
public abstract void Clock();
@ -291,10 +291,11 @@ namespace imlac
protected abstract class DisplayInstructionBase
{
public DisplayInstructionBase(ushort word, DisplayProcessorMode mode)
public DisplayInstructionBase(ushort word, ushort address, DisplayProcessorMode mode)
{
_usageMode = mode;
_word = word;
_address = address;
Decode();
}
@ -324,17 +325,182 @@ namespace imlac
/// </summary>
/// <param name="mode"></param>
/// <returns></returns>
public abstract string Disassemble(DisplayProcessorMode mode);
public abstract string Disassemble(DisplayProcessorMode mode, Memory mem, out int length);
/// <summary>
/// Implemented to provide decoding of this instruction word.
/// </summary>
protected abstract void Decode();
protected string DisassembleIncrement()
{
return DisassembleIncrementHalf(ImmediateHalf.First) + " | " + DisassembleIncrementHalf(ImmediateHalf.Second);
}
private string DisassembleIncrementHalf(ImmediateHalf half)
{
string ret = string.Empty;
int halfWord = half == ImmediateHalf.First ? (_word & 0xff00) >> 8 : (_word & 0xff);
// translate the half word to vector movements or escapes
// special case for "Enter Immediate mode" halfword (030) in first half.
if (half == ImmediateHalf.First && halfWord == 0x30)
{
ret += "E";
}
else if ((halfWord & 0x80) == 0)
{
if ((halfWord & 0x10) != 0)
{
ret += "IX ";
}
if ((halfWord & 0x08) != 0)
{
ret += "ZX ";
}
if (half == ImmediateHalf.Second &&
(halfWord & 0x04) != 0)
{
ret += "E PPM ";
}
if ((halfWord & 0x02) != 0)
{
ret += "IY ";
}
if ((halfWord & 0x01) != 0)
{
ret += "ZY ";
}
if ((halfWord & 0x40) != 0)
{
if ((halfWord & 0x20) != 0)
{
// escape and return
ret += "F RJM";
}
else
{
// Escape
ret += "F";
}
}
}
else
{
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
int xMag = (int)(((halfWord & 0x18) >> 3));
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03));
ret += String.Format("{0},{1} {2}", xMag * xSign, yMag * ySign, (halfWord & 0x40) == 0 ? "OFF" : "ON");
}
return ret;
}
protected string DisassembleProcessor(Memory mem, out int length)
{
length = 1;
string ret = String.Empty;
if (_opcode == DisplayOpcode.DOPR)
{
string[] codes = { "INV0 ", "INV1 ", "INV2 ", "INV3 ", "DDSP ", "DRJM ", "DDYM ", "DDXM ", "DIYM ", "DIXM ", "DHVC ", "DHLT " };
for (int i = 4; i < 11; i++)
{
if ((_data & (0x01) << i) != 0)
{
if (!string.IsNullOrEmpty(ret))
{
ret += ",";
}
ret += codes[i];
}
}
// display halt if bit 4 is unset
if ((_data & 0x800) == 0)
{
ret += " DHLT ";
}
// F/C ops:
int f = (_data & 0xc) >> 2;
int c = _data & 0x3;
switch (f)
{
case 0x0:
// nothing
if (c == 1)
{
ret += String.Format("DADR");
}
break;
case 0x1:
ret += String.Format("DSTS {0}", c);
break;
case 0x2:
ret += String.Format("DSTB {0}", c);
break;
case 0x3:
ret += String.Format("DLPN {0}", c);
break;
}
}
else
{
switch (_opcode)
{
case DisplayOpcode.DEIM:
ret = String.Format("DEIM | {0} {1}",
DisassembleIncrementHalf(ImmediateHalf.Second),
(_word & 0xff00) == 0x3800 ? "Enter PPM" : String.Empty);
break;
case DisplayOpcode.DLXA:
ret = String.Format("DLXA {0} ({1})", Helpers.ToOctal(_data), _data);
break;
case DisplayOpcode.DLYA:
ret = String.Format("DLXA {0} ({1})", Helpers.ToOctal(_data), _data);
break;
case DisplayOpcode.DJMS:
ret = String.Format("DJMS {0}", Helpers.ToOctal(_data));
break;
case DisplayOpcode.DJMP:
ret = String.Format("DJMP {0}", Helpers.ToOctal(_data));
break;
default:
ret = DisassembleExtended(mem, out length);
break;
}
}
return ret;
}
protected abstract string DisassembleExtended(Memory mem, out int length);
protected DisplayOpcode _opcode;
protected ushort _data;
protected DisplayProcessorMode _usageMode;
protected ushort _word;
protected ushort _address;
}
}

View File

@ -53,7 +53,7 @@ namespace imlac
_instructionCache[address & Memory.SizeMask] = null;
}
public override string Disassemble(ushort address, DisplayProcessorMode mode)
public override string Disassemble(ushort address, DisplayProcessorMode mode, out int length)
{
//
// Return a precached instruction if we have it due to previous execution
@ -62,11 +62,11 @@ namespace imlac
//
if (_instructionCache[address & Memory.SizeMask] != null)
{
return _instructionCache[address & Memory.SizeMask].Disassemble(mode);
return _instructionCache[address & Memory.SizeMask].Disassemble(mode, _mem, out length);
}
else
{
return new PDS1DisplayInstruction((ushort)(address & Memory.SizeMask), mode).Disassemble(mode);
return new PDS1DisplayInstruction(_mem.Fetch(address), address, mode).Disassemble(mode, _mem, out length);
}
}
@ -545,7 +545,7 @@ namespace imlac
{
if (_instructionCache[address & Memory.SizeMask] == null)
{
_instructionCache[address & Memory.SizeMask] = new PDS1DisplayInstruction(_mem.Fetch(address), mode);
_instructionCache[address & Memory.SizeMask] = new PDS1DisplayInstruction(_mem.Fetch(address), address, mode);
}
return _instructionCache[address & Memory.SizeMask];
@ -568,11 +568,11 @@ namespace imlac
/// </summary>
private class PDS1DisplayInstruction : DisplayInstructionBase
{
public PDS1DisplayInstruction(ushort word, DisplayProcessorMode mode) : base (word, mode)
public PDS1DisplayInstruction(ushort word, ushort address, DisplayProcessorMode mode) : base (word, address, mode)
{
}
public override string Disassemble(DisplayProcessorMode mode)
public override string Disassemble(DisplayProcessorMode mode, Memory mem, out int length)
{
if (mode == DisplayProcessorMode.Indeterminate)
{
@ -582,12 +582,14 @@ namespace imlac
switch (mode)
{
case DisplayProcessorMode.Increment:
length = 1;
return DisassembleIncrement();
case DisplayProcessorMode.Processor:
return DisassembleProcessor();
return DisassembleProcessor(mem, out length);
case DisplayProcessorMode.Indeterminate:
length = 1;
return "Indeterminate";
default:
@ -712,132 +714,75 @@ namespace imlac
}
private string DisassembleIncrement()
{
return DisassembleIncrementHalf(ImmediateHalf.First) + " | " + DisassembleIncrementHalf(ImmediateHalf.Second);
}
private string DisassembleIncrementHalf(ImmediateHalf half)
{
string ret = string.Empty;
int halfWord = half == ImmediateHalf.First ? (_word & 0xff00) >> 8 : (_word & 0xff);
// translate the half word to vector movements or escapes
// special case for "Enter Immediate mode" halfword (030) in first half.
if (half == ImmediateHalf.First && halfWord == 0x30)
{
ret += "E";
}
else if ((halfWord & 0x80) == 0)
{
if ((halfWord & 0x10) != 0)
{
ret += "IX ";
}
if ((halfWord & 0x08) != 0)
{
ret += "ZX ";
}
if ((halfWord & 0x02) != 0)
{
ret += "IY ";
}
if ((halfWord & 0x01) != 0)
{
ret += "ZY ";
}
if ((halfWord & 0x40) != 0)
{
if ((halfWord & 0x20) != 0)
{
// escape and return
ret += "F RJM";
}
else
{
// Escape
ret += "F";
}
}
}
else
{
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
int xMag = (int)(((halfWord & 0x18) >> 3));
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03));
ret += String.Format("{0},{1} {2}", xMag * xSign, yMag * ySign, (halfWord & 0x40) == 0 ? "OFF" : "ON");
}
return ret;
}
private void DecodeImmediate()
{
// TODO: eventually actually precache movement calculations.
}
private string DisassembleProcessor()
protected override string DisassembleExtended(Memory mem, out int length)
{
string ret = String.Empty;
if (_opcode == DisplayOpcode.DOPR)
{
string[] codes = { "INV0 ", "INV1 ", "INV2 ", "INV3 ", "DDSP ", "DRJM ", "DDYM ", "DDXM ", "DIYM ", "DIXM ", "DHVC ", "DHLT " };
for (int i = 4; i < 12; i++)
{
if ((_data & (0x01) << i) != 0)
{
if (!string.IsNullOrEmpty(ret))
{
ret += ",";
}
ret += codes[i];
}
}
// F/C ops:
int f = (_data & 0xc) >> 2;
int c = _data & 0x3;
switch (f)
{
case 0x0:
// nothing
if (c == 1)
{
ret += String.Format("DADR");
}
break;
case 0x1:
ret += String.Format("DSTS {0}", c);
break;
case 0x2:
ret += String.Format("DSTB {0}", c);
break;
case 0x3:
ret += String.Format("DLPN {0}", c);
break;
}
}
else
switch (_opcode)
{
// keep things simple -- should add special support for extended instructions at some point...
ret = String.Format("{0} {1} ", _opcode, Helpers.ToOctal(_data));
case DisplayOpcode.DLVH:
length = 3;
ret = DecodeLongVector(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 DecodeLongVector(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);
}
}
}
}

View File

@ -59,7 +59,7 @@ namespace imlac
_instructionCache[address & Memory.SizeMask] = null;
}
public override string Disassemble(ushort address, DisplayProcessorMode mode)
public override string Disassemble(ushort address, DisplayProcessorMode mode, out int length)
{
//
// Return a precached instruction if we have it due to previous execution
@ -68,11 +68,11 @@ namespace imlac
//
if (_instructionCache[address & Memory.SizeMask] != null)
{
return _instructionCache[address & Memory.SizeMask].Disassemble(mode);
return _instructionCache[address & Memory.SizeMask].Disassemble(mode, _mem, out length);
}
else
{
return new PDS4DisplayInstruction((ushort)(address & Memory.SizeMask), mode).Disassemble(mode);
return new PDS4DisplayInstruction(_mem.Fetch(address), address, mode).Disassemble(mode, _mem, out length);
}
}
@ -678,7 +678,7 @@ namespace imlac
{
if (_instructionCache[address & Memory.SizeMask] == null)
{
_instructionCache[address & Memory.SizeMask] = new PDS4DisplayInstruction(_mem.Fetch(address), mode);
_instructionCache[address & Memory.SizeMask] = new PDS4DisplayInstruction(_mem.Fetch(address), address, mode);
}
return _instructionCache[address & Memory.SizeMask];
@ -716,11 +716,11 @@ namespace imlac
/// </summary>
private class PDS4DisplayInstruction : DisplayInstructionBase
{
public PDS4DisplayInstruction(ushort word, DisplayProcessorMode mode) : base (word, mode)
public PDS4DisplayInstruction(ushort word, ushort address, DisplayProcessorMode mode) : base (word, address, mode)
{
}
public override string Disassemble(DisplayProcessorMode mode)
public override string Disassemble(DisplayProcessorMode mode, Memory mem, out int length)
{
if (mode == DisplayProcessorMode.Indeterminate)
{
@ -730,12 +730,14 @@ namespace imlac
switch (mode)
{
case DisplayProcessorMode.Increment:
length = 1;
return DisassembleIncrement();
case DisplayProcessorMode.Processor:
return DisassembleProcessor();
return DisassembleProcessor(mem, out length);
case DisplayProcessorMode.Indeterminate:
length = 1;
return "Indeterminate";
default:
@ -909,132 +911,90 @@ namespace imlac
}
}
private string DisassembleIncrement()
{
return DisassembleIncrementHalf(ImmediateHalf.First) + " | " + DisassembleIncrementHalf(ImmediateHalf.Second);
}
private string DisassembleIncrementHalf(ImmediateHalf half)
{
string ret = string.Empty;
int halfWord = half == ImmediateHalf.First ? (_word & 0xff00) >> 8 : (_word & 0xff);
// translate the half word to vector movements or escapes
// special case for "Enter Immediate mode" halfword (030) in first half.
if (half == ImmediateHalf.First && halfWord == 0x30)
{
ret += "E";
}
else if ((halfWord & 0x80) == 0)
{
if ((halfWord & 0x10) != 0)
{
ret += "IX ";
}
if ((halfWord & 0x08) != 0)
{
ret += "ZX ";
}
if ((halfWord & 0x02) != 0)
{
ret += "IY ";
}
if ((halfWord & 0x01) != 0)
{
ret += "ZY ";
}
if ((halfWord & 0x40) != 0)
{
if ((halfWord & 0x20) != 0)
{
// escape and return
ret += "F RJM";
}
else
{
// Escape
ret += "F";
}
}
}
else
{
int xSign = ((halfWord & 0x20) == 0) ? 1 : -1;
int xMag = (int)(((halfWord & 0x18) >> 3));
int ySign = (int)(((halfWord & 0x04) == 0) ? 1 : -1);
int yMag = (int)((halfWord & 0x03));
ret += String.Format("{0},{1} {2}", xMag * xSign, yMag * ySign, (halfWord & 0x40) == 0 ? "OFF" : "ON");
}
return ret;
}
private void DecodeImmediate()
{
// TODO: eventually actually precache movement calculations.
}
private string DisassembleProcessor()
protected override string DisassembleExtended(Memory mem, out int length)
{
string ret = String.Empty;
if (_opcode == DisplayOpcode.DOPR)
{
string[] codes = { "INV0 ", "INV1 ", "INV2 ", "INV3 ", "DDSP ", "DRJM ", "DDYM ", "DDXM ", "DIYM ", "DIXM ", "DHVC ", "DHLT " };
for (int i = 4; i < 12; i++)
{
if ((_data & (0x01) << i) != 0)
{
if (!string.IsNullOrEmpty(ret))
{
ret += ",";
}
ret += codes[i];
}
}
// F/C ops:
int f = (_data & 0xc) >> 2;
int c = _data & 0x3;
switch (f)
{
case 0x0:
// nothing
if (c == 1)
{
ret += String.Format("DADR");
}
break;
case 0x1:
ret += String.Format("DSTS {0}", c);
break;
case 0x2:
ret += String.Format("DSTB {0}", c);
break;
case 0x3:
ret += String.Format("DLPN {0}", c);
break;
}
}
else
switch (_opcode)
{
// keep things simple -- should add special support for extended instructions at some point...
ret = String.Format("{0} {1} ", _opcode, Helpers.ToOctal(_data));
case DisplayOpcode.DLVH:
length = 2;
ret = DecodeLongVector(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 DecodeLongVector(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: As with the PDS-1 variant, would make sense to precache this during decoding, would require
// modifications to cache invalidation logic.
ushort word1 = mem.Fetch((ushort)(_address + 1));
// Not documented, but from empirical evidence from PDS-4 games
// (pong, crash) the magnitude is scaled by 2 (i.e. specified LSBN is bit 1 of X and Y AC's)
uint M = (uint)(_word & 0x3ff) << 1;
uint N = (uint)(word1 & 0x3ff) << 1;
bool ret = (word1 & 0x8000) != 0;
bool dotted = (word1 & 0x4000) != 0;
bool dashed = (word1 & 0x2000) != 0;
bool beamOn = (word1 & 0x1000) != 0;
bool dyGreater = (word1 & 0x0800) != 0;
int mSign = (_word & 0x0400) != 0 ? -1 : 1;
int nSign = (word1 & 0x0400) != 0 ? -1 : 1;
uint dx = 0;
uint dy = 0;
int dxSign = 0;
int dySign = 0;
if (dyGreater)
{
dy = M;
dx = N;
dySign = mSign;
dxSign = nSign;
}
else
{
dx = M;
dy = N;
dxSign = mSign;
dySign = nSign;
}
return String.Format("DLVH ({0},{1}) {2}{3}{4}{5}",
dx * dxSign,
dy * dySign,
beamOn ? "ON " : "OFF ",
dotted ? "DOTTED " : String.Empty,
dashed ? "DASHED " : String.Empty,
ret ? "RET " : String.Empty);
}
}
}
}

View File

@ -667,28 +667,31 @@ 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;
}
}
@ -696,9 +699,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;
}
}