Coprocessor, debugger, and exception stuff.

* VR4300.SystemControlUnit.cs: Implemented coprocessor operations.

* VR4300.ExceptionProcessing.cs: More exceptions.

* VR4300.Exceptions.cs: Support coprocessor ops.

* DotN64.csproj:
* VR4300.ICoprocessor.cs:

* VR4300.Instruction.cs: Implemented IEquatable to avoid boxing in
  dictionary key comparisons.
Added COPz for coprocessor ops.

* VR4300.OpCode.cs: Proper coprocessor ops.

* VR4300.cs: Added coprocessor unit support.

* Debugger.InstructionFormat.cs: (Slightly) take into account
  coprocessor ops.

* Debugger.cs: Using an instruction cursor to avoid confusion.
Fixed a case where the debugger would run a cycle despite exiting it.
Coprocessor ops aren't properly disassembled.
master
Nabile Rahmani 2018-01-17 10:34:02 +01:00
parent da3a886d71
commit 4ee50a1f7f
10 changed files with 140 additions and 28 deletions

View File

@ -1,11 +1,17 @@
using System;
using System.Collections.Generic;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class SystemControlUnit
public partial class SystemControlUnit : ICoprocessor
{
#region Fields
private readonly VR4300 cpu;
private readonly IReadOnlyDictionary<OpCode, Action<Instruction>> operations;
#endregion
#region Properties
public ulong[] Registers { get; } = new ulong[32];
@ -17,11 +23,17 @@ namespace DotN64.CPU
#endregion
#region Constructors
public SystemControlUnit()
public SystemControlUnit(VR4300 cpu)
{
this.cpu = cpu;
Config = new ConfigRegister(this);
Status = new StatusRegister(this);
Cause = new CauseRegister(this);
operations = new Dictionary<OpCode, Action<Instruction>>
{
[OpCode.MT] = i => Registers[i.RD] = cpu.GPR[i.RT],
[OpCode.MF] = i => cpu.GPR[i.RT] = (ulong)(int)Registers[i.RD]
};
}
#endregion
@ -42,7 +54,23 @@ namespace DotN64.CPU
throw new Exception($"Unknown memory map segment for location 0x{address:X16}.");
}
}
public void Run(Instruction instruction)
{
if (operations.TryGetValue((OpCode)instruction.RS, out var operation))
operation(instruction);
else
ExceptionProcessing.ReservedInstruction(cpu, instruction);
}
#endregion
public enum OpCode : byte
{
/// <summary>Move To System Control Coprocessor.</summary>
MT = 0b00100,
/// <summary>Move From System Control Coprocessor.</summary>
MF = 0b00000
}
}
}
}

View File

@ -62,6 +62,15 @@
}
public static void Interrupt(VR4300 cpu) => HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.Int);
public static void ReservedInstruction(VR4300 cpu, Instruction instruction)
{
HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.RI);
throw new UnimplementedOperationException(instruction); // TODO: Remove this and the parameter once every instruction gets implemented.
}
public static void CoprocessorUnusable(VR4300 cpu, byte unit) => HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.CpU, unit);
#endregion
}
}

View File

@ -12,16 +12,18 @@ namespace DotN64.CPU
#region Constructors
public UnimplementedOperationException(Instruction instruction)
: base($"Unimplemented opcode ({GetOpCodeType(instruction).Name} {FormatOpCodeBits(instruction)}) from instruction 0x{(uint)instruction:X8}.")
: base($"Unimplemented opcode ({GetOpCodeName(instruction)} {FormatOpCodeBits(instruction)}) from instruction 0x{(uint)instruction:X8}.")
{
Instruction = instruction;
}
#endregion
#region Methods
private static string GetOpCodeName(Instruction instruction) => instruction.COPz.HasValue ? $"CP{instruction.COPz}" : GetOpCodeType(instruction).Name;
private static Type GetOpCodeType(Instruction instruction) => instruction.Special?.GetType() ?? instruction.RegImm?.GetType() ?? instruction.OP.GetType();
private static string FormatOpCodeBits(Instruction instruction) => "0b" + Convert.ToString((byte?)instruction.Special ?? (byte?)instruction.RegImm ?? (byte)instruction.OP, 2);
private static string FormatOpCodeBits(Instruction instruction) => "0b" + Convert.ToString((byte?)instruction.Special ?? (byte?)instruction.RegImm ?? (instruction.COPz.HasValue ? instruction.RS : (byte)instruction.OP), 2);
#endregion
}
}

View File

@ -0,0 +1,12 @@
namespace DotN64.CPU
{
public partial class VR4300
{
public interface ICoprocessor
{
#region Methods
void Run(Instruction instruction);
#endregion
}
}
}

View File

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System;
using System.Runtime.CompilerServices;
namespace DotN64.CPU
{
@ -9,7 +10,7 @@ namespace DotN64.CPU
/// <summary>
/// See: datasheet#3.1.
/// </summary>
public struct Instruction
public struct Instruction : IEquatable<Instruction>
{
#region Fields
private uint data;
@ -22,6 +23,7 @@ namespace DotN64.CPU
private const int RDShift = 11, RDSize = (1 << 5) - 1;
private const int SAShift = 6, SASize = (1 << 5) - 1;
private const int FunctShift = 0, FunctSize = (1 << 6) - 1;
private const byte COPzSize = (1 << 2) - 1;
public const int Size = sizeof(uint);
#endregion
@ -117,6 +119,11 @@ namespace DotN64.CPU
RT = (byte)value;
}
}
/// <summary>
/// Gets the coprocessor unit index.
/// </summary>
public byte? COPz => (OpCode)((byte)OP & ~COPzSize) == OpCode.COP0 ? (byte?)((byte)OP & COPzSize) : null;
#endregion
#region Methods
@ -148,6 +155,12 @@ namespace DotN64.CPU
}
}
public bool Equals(Instruction other) => other.data == data;
public override bool Equals(object obj) => obj is Instruction && ((Instruction)obj).data == data;
public override int GetHashCode() => (int)data;
public override string ToString()
{
switch (OP)

View File

@ -6,10 +6,12 @@
{
SPECIAL = 0b000000,
REGIMM = 0b000001,
COP0 = 0b010000,
COP1 = 0b010001,
COP2 = 0b010010,
COP3 = 0b010011,
/// <summary>Load Upper Immediate.</summary>
LUI = 0b001111,
/// <summary>Move To System Control Coprocessor.</summary>
MTC0 = 0b010000,
/// <summary>Or Immediate.</summary>
ORI = 0b001101,
/// <summary>Load Word.</summary>

View File

@ -11,7 +11,7 @@ namespace DotN64.CPU
public partial class VR4300
{
#region Fields
private readonly IReadOnlyDictionary<uint, Action<Instruction>> operations;
private readonly IReadOnlyDictionary<Instruction, Action<Instruction>> operations;
private readonly IReadOnlyDictionary<byte, float> divModeMultipliers = new Dictionary<byte, float>
{
[0b01] = 1.5f,
@ -117,7 +117,9 @@ namespace DotN64.CPU
/// </summary>
public Action<uint, uint> WriteSysAD { get; set; }
public SystemControlUnit CP0 { get; } = new SystemControlUnit();
public ICoprocessor[] COP { get; } = new ICoprocessor[4];
public SystemControlUnit CP0 => COP[0] as SystemControlUnit;
public ulong? DelaySlot { get; private set; }
#endregion
@ -125,10 +127,10 @@ namespace DotN64.CPU
#region Constructors
public VR4300()
{
operations = new Dictionary<uint, Action<Instruction>>
COP[0] = new SystemControlUnit(this);
operations = new Dictionary<Instruction, Action<Instruction>>
{
[Instruction.FromOpCode(OpCode.LUI)] = i => GPR[i.RT] = (ulong)(i.Immediate << 16),
[Instruction.FromOpCode(OpCode.MTC0)] = i => CP0.Registers[i.RD] = GPR[i.RT],
[Instruction.FromOpCode(OpCode.ORI)] = i => GPR[i.RT] = GPR[i.RS] | i.Immediate,
[Instruction.FromOpCode(OpCode.LW)] = i => GPR[i.RT] = (ulong)(int)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[Instruction.FromOpCode(OpCode.ANDI)] = i => GPR[i.RT] = i.Immediate & GPR[i.RS],
@ -156,6 +158,7 @@ namespace DotN64.CPU
WriteWord(address, (ReadWord(address) & ~((uint)(1 << 8) - 1)) | (byte)GPR[i.RT]);
},
[Instruction.FromOpCode(OpCode.LBU)] = i => GPR[i.RT] = (byte)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[Instruction.FromOpCode(OpCode.COP3)] = i => ExceptionProcessing.ReservedInstruction(this, i), // CP3 access throws a reserved instruction for this CPU.
[Instruction.FromOpCode(SpecialOpCode.ADD)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[Instruction.FromOpCode(SpecialOpCode.JR)] = i =>
{
@ -199,8 +202,17 @@ namespace DotN64.CPU
{
if (operations.TryGetValue(instruction.ToOpCode(), out var operation))
operation(instruction);
else if (instruction.COPz.HasValue)
{
var unit = instruction.COPz.Value;
if (((byte)CP0.Status.CU & 1 << unit) != 0 || (unit == 0 && CP0.Status.KSU == SystemControlUnit.StatusRegister.Mode.Kernel))
COP[unit].Run(instruction);
else
ExceptionProcessing.CoprocessorUnusable(this, unit);
}
else
throw new UnimplementedOperationException(instruction);
ExceptionProcessing.ReservedInstruction(this, instruction);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -5,7 +5,7 @@
public partial class Debugger
{
// TODO: Double check for possibly missing exceptions in formatting according to the CPU's documentation, as I started with help from a MIPS I (?) general documentation (https://en.wikibooks.org/wiki/MIPS_Assembly/Instruction_Formats).
// NOTE: Perhaps this should be moved beside the CPU. It would probably be nice to make it overridable from a more general MIPS format class, as well as make it instanciable for changing display variables (register prefix, etc.).
// NOTE: Perhaps this should be moved beside the CPU. It would probably be nice to make it overridable from a more general MIPS format class, as well as make it instantiable for changing display variables (register prefix, etc.).
// NOTE: If assembling input has to be implemented, make an enum Type, and move the currently used methods inside a Disassembler class, and the new ones into Assembler. The caller would look for the type associated with a given instruction and choose from here.
/// <summary>
/// Formatting helpers for disassembling instructions.
@ -17,7 +17,29 @@
#endregion
#region Methods
private static string Format(VR4300.Instruction instruction, params object[] values) => $"{instruction} {string.Join(Separator, values)}";
private static string Format(VR4300.Instruction instruction, params object[] values) => $"{FormatOpCode(instruction)} {string.Join(Separator, values)}";
private static string FormatOpCode(VR4300.Instruction instruction)
{
// I was originally going to use the CPU in all cases and casting the COP[instruction.COPz] to a debuggable coprocessor to get the opcode, but it just makes the argument list larger because I have to show the register contents.
// You could argue this should be made instantiable but hey.
// Besides, coprocessors are already hard-coded here (FormatCP0Register). I'll have to clean things up some day.
if (instruction.COPz.HasValue)
{
var opCode = "?";
switch (instruction.OP)
{
case VR4300.OpCode.COP0:
opCode = ((VR4300.SystemControlUnit.OpCode)instruction.RS).ToString(); // Not accounting for RT, Func...
break;
}
return $"{instruction}.{opCode}";
}
return instruction.ToString();
}
private static string FormatRegister(int index, VR4300 cpu) => RegisterPrefix + (VR4300.GPRIndex)index + (cpu != null ? FormatRegisterContents(cpu.GPR[index]) : string.Empty);
@ -75,11 +97,17 @@
switch (instruction.OP)
{
case VR4300.OpCode.MTC0:
return Format(instruction, FormatRegister(instruction.RT, cpu), FormatCP0Register(instruction.RD, cpu));
default:
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RS, cpu), FormatRegister(instruction.RT, cpu));
case VR4300.OpCode.COP0:
switch ((VR4300.SystemControlUnit.OpCode)instruction.RS)
{
case VR4300.SystemControlUnit.OpCode.MT:
case VR4300.SystemControlUnit.OpCode.MF:
return Format(instruction, FormatRegister(instruction.RT, cpu), FormatCP0Register(instruction.RD, cpu));
}
break;
}
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RS, cpu), FormatRegister(instruction.RT, cpu));
}
/// <summary>

View File

@ -15,7 +15,7 @@ namespace DotN64.Diagnostics
private readonly IReadOnlyCollection<Command> commands;
private readonly IDictionary<ulong, string> labels = new Dictionary<ulong, string>();
private readonly IList<ulong> breakpoints = new List<ulong>();
private readonly IDictionary<uint, Func<VR4300.Instruction, VR4300, string>> operationFormats = new Dictionary<uint, Func<VR4300.Instruction, VR4300, string>>
private readonly IDictionary<VR4300.Instruction, Func<VR4300.Instruction, VR4300, string>> operationFormats = new Dictionary<VR4300.Instruction, Func<VR4300.Instruction, VR4300, string>>
{
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ADDI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ADDIU)] = InstructionFormat.I,
@ -30,7 +30,7 @@ namespace DotN64.Diagnostics
[VR4300.Instruction.FromOpCode(VR4300.OpCode.LBU)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.LUI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.LW)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.MTC0)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.COP0)] = InstructionFormat.R, // FIXME: all CP0 ops are treated as such at the moment.
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ORI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.SB)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.SLTI)] = InstructionFormat.I,
@ -58,6 +58,8 @@ namespace DotN64.Diagnostics
#endregion
#region Properties
private ulong Cursor => nintendo64.CPU.DelaySlot ?? nintendo64.CPU.PC;
public Status DebuggerStatus { get; private set; }
#endregion
@ -85,7 +87,7 @@ namespace DotN64.Diagnostics
for (var i = BigInteger.Zero; i < count; i++)
{
Disassemble(nintendo64.CPU.PC + (ulong)(i * VR4300.Instruction.Size), false);
Disassemble(Cursor + (ulong)(i * VR4300.Instruction.Size), false);
}
}),
new Command(new[] { "label", "labels", "l" }, "Shows, adds or removes a label attached to an address.", args =>
@ -96,11 +98,11 @@ namespace DotN64.Diagnostics
{
case "add":
case "a":
labels[args.Length <= index + 1 ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber)] = args[index++];
labels[args.Length <= index + 1 ? Cursor : ulong.Parse(args[index++], NumberStyles.HexNumber)] = args[index++];
break;
case "remove":
case "r":
labels.Remove(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
labels.Remove(args.Length <= index ? Cursor : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "clear":
case "c":
@ -133,11 +135,11 @@ namespace DotN64.Diagnostics
{
case "add":
case "a":
breakpoints.Add(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
breakpoints.Add(args.Length <= index ? Cursor : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "remove":
case "r":
breakpoints.Remove(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
breakpoints.Remove(args.Length <= index ? Cursor : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "clear":
case "c":
@ -181,7 +183,7 @@ namespace DotN64.Diagnostics
private void Disassemble(ulong? address = null, bool withRegisterContents = true)
{
if (!address.HasValue)
address = nintendo64.CPU.DelaySlot ?? nintendo64.CPU.PC;
address = Cursor;
var instruction = nintendo64.CPU.ReadSysAD(nintendo64.CPU.CP0.Translate(address.Value));
@ -264,13 +266,16 @@ namespace DotN64.Diagnostics
switch (DebuggerStatus)
{
case Status.Running:
if (breakpoints.Contains(nintendo64.CPU.PC))
if (breakpoints.Contains(Cursor))
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Hit a breakpoint; entering debug prompt.");
Console.ResetColor();
Debug();
if (DebuggerStatus != Status.Running) // Prevents running the instruction if we chose to quit (e.g. covers the case where the next one causes an exception, dropping us right back into the debugger).
break;
}
try

View File

@ -82,6 +82,7 @@
<Compile Include="RCP\MI\RealityCoprocessor.MIPSInterface.Interrupts.cs" />
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.CauseRegister.cs" />
<Compile Include="CPU\VR4300\VR4300.ExceptionProcessing.cs" />
<Compile Include="CPU\VR4300\VR4300.ICoprocessor.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />