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
parent
da3a886d71
commit
4ee50a1f7f
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
namespace DotN64.CPU
|
||||
{
|
||||
public partial class VR4300
|
||||
{
|
||||
public interface ICoprocessor
|
||||
{
|
||||
#region Methods
|
||||
void Run(Instruction instruction);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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\" />
|
||||
|
|
Loading…
Reference in New Issue