Huge changes (too lazy to separate).

* VR4300.SystemControlUnit.StatusRegister.cs: Removed unneeded
  value.

* VR4300.SystemControlUnit.cs: Renamed method.

* VR4300.Instruction.cs: Refactored instruction contents. Now
  individual parts can be written to as well.
It is also possible to strip an instruction to its bare opcode
  identifier, or even create one from a specified opcode, so this can
  be used as a key in a dictionary of operations.
A basic ToString implementation displays the opcode of the
  instruction.

* VR4300.OpCode.cs:
* VR4300.RegImmOpCode.cs:
* VR4300.SpecialOpCode.cs: Added opcodes.

* VR4300.cs: Unified operations into a single dictionary thanks to the
  instruction refactoring.
Added ops: JAL, SLTI, XORI, BLEZL, SB, LBU, SLT, BGEZL.

* Debugger.Command.cs: Basic display of what the command is about.

* Debugger.InstructionFormat.cs: First pass of the formatter.

* Debugger.cs: Proper disassembly of instructions. Stepping also
  displays the contents of registers.
~Infinite~ count argument for stepping/disassembling.
Refactored instruction fetch into using the CPU's SysAD bus (no need
  to manually access the N64's memory maps).

* DotN64.csproj:
* RealityCoprocessor.SignalProcessor.StatusRegister.cs:
* RealityCoprocessor.DisplayProcessor.StatusRegister.cs:
* RealityCoprocessor.RDRAMInterface.RDRAMConfigIndex.cs:
* RealityCoprocessor.RDRAMInterface.RDRAMConfigRegister.cs:

* MappingEntryExtensions.cs: Saves some typing.

* BitHelper.cs: Reusable methods.

* Nintendo64.cs: More memory maps.

* Program.cs: Minor changes.

* RealityCoprocessor.Interface.cs:
* RealityCoprocessor.AudioInterface.cs:
* RealityCoprocessor.VideoInterface.cs:
* RealityCoprocessor.SerialInterface.cs: Simplified mapping accesses.

* RealityCoprocessor.DisplayProcessor.cs: Added an actual register.
Simplified mapping accesses.

* RealityCoprocessor.MIPSInterface.cs: Added dummy version register
  read.
Simplified mapping accesses.

* RealityCoprocessor.PeripheralInterface.cs: Basic DMA.
Simplified mapping accesses.

* RealityCoprocessor.RDRAMInterface.cs: Removed constants that get set
  in the boot process.
Added RDRAM registers.
Simplified mapping accesses.

* RealityCoprocessor.SignalProcessor.cs: Better types for existing
  registers and handle status writes.
Simplified mapping accesses.
master
Nabile Rahmani 2017-12-11 15:04:53 +01:00
parent 8905d75136
commit 23b06e69b5
28 changed files with 732 additions and 256 deletions

View File

@ -259,7 +259,6 @@ namespace DotN64.CPU
[Flags]
public enum CoprocessorUsabilities : byte
{
None = 0,
CP0 = 1 << 0,
CP1 = 1 << 1,
CP2 = 1 << 2,

View File

@ -27,7 +27,7 @@ namespace DotN64.CPU
/// Translates a virtual address into a physical address.
/// See: datasheet#5.2.4 Table 5-3.
/// </summary>
public ulong Map(ulong address)
public ulong Translate(ulong address)
{
switch (address >> 29 & 0b111)
{

View File

@ -1,12 +1,27 @@
namespace DotN64.CPU
using System.Runtime.CompilerServices;
namespace DotN64.CPU
{
using static Helpers.BitHelper;
public partial class VR4300
{
/// <summary>
/// See: datasheet#3.1.
/// </summary>
public struct Instruction
{
#region Fields
private uint data;
private const int OPShift = 26, OPSize = (1 << 6) - 1;
private const int RSShift = 21, RSSize = (1 << 5) - 1;
private const int RTShift = 16, RTSize = (1 << 5) - 1;
private const int ImmediateShift = 0, ImmediateSize = (1 << 16) - 1;
private const int TargetShift = 0, TargetSize = (1 << 26) - 1;
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;
public const int Size = sizeof(uint);
#endregion
@ -14,42 +29,137 @@
/// <summary>
/// 6-bit operation code.
/// </summary>
public OpCode OP => (OpCode)(data >> 26);
public OpCode OP
{
get => (OpCode)Get(data, OPShift, OPSize);
set => Set(ref data, OPShift, OPSize, (uint)value);
}
/// <summary>
/// 5-bit source register number.
/// </summary>
public byte RS => (byte)(data >> 21 & ((1 << 5) - 1));
public byte RS
{
get => (byte)Get(data, RSShift, RSSize);
set => Set(ref data, RSShift, RSSize, value);
}
/// <summary>
/// 5-bit target (source/destination) register number or branch condition.
/// </summary>
public byte RT => (byte)(data >> 16 & ((1 << 5) - 1));
public byte RT
{
get => (byte)Get(data, RTShift, RTSize);
set => Set(ref data, RTShift, RTSize, value);
}
/// <summary>
/// 16-bit immediate value, branch displacement or address displacement.
/// </summary>
public ushort Immediate => (ushort)(data & ((1 << 16) - 1));
public ushort Immediate
{
get => (ushort)Get(data, ImmediateShift, ImmediateSize);
set => Set(ref data, ImmediateShift, ImmediateSize, value);
}
/// <summary>
/// 26-bit unconditional branch target address.
/// </summary>
public uint Target => data & ((1 << 26) - 1);
public uint Target
{
get => Get(data, TargetShift, TargetSize);
set => Set(ref data, TargetShift, TargetSize, value);
}
/// <summary>
/// 5-bit destination register number.
/// </summary>
public byte RD => (byte)(data >> 11 & ((1 << 5) - 1));
public byte RD
{
get => (byte)Get(data, RDShift, RDSize);
set => Set(ref data, RDShift, RDSize, value);
}
/// <summary>
/// 5-bit shift amount.
/// </summary>
public byte SA => (byte)(data >> 6 & ((1 << 5) - 1));
public byte SA
{
get => (byte)Get(data, SAShift, SASize);
set => Set(ref data, SAShift, SASize, value);
}
/// <summary>
/// 6-bit function field.
/// </summary>
public byte Funct => (byte)(data & ((1 << 6) - 1));
public byte Funct
{
get => (byte)Get(data, FunctShift, FunctSize);
set => Set(ref data, FunctShift, FunctSize, value);
}
public SpecialOpCode? Special
{
get => OP == OpCode.SPECIAL ? (SpecialOpCode?)Funct : null;
set
{
OP = OpCode.SPECIAL;
Funct = (byte)value;
}
}
public RegImmOpCode? RegImm
{
get => OP == OpCode.REGIMM ? (RegImmOpCode?)RT : null;
set
{
OP = OpCode.REGIMM;
RT = (byte)value;
}
}
#endregion
#region Methods
public static Instruction FromOpCode(OpCode op) => new Instruction { OP = op };
public static Instruction FromOpCode(SpecialOpCode op) => new Instruction { Special = op };
public static Instruction FromOpCode(RegImmOpCode op) => new Instruction { RegImm = op };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Instruction ToOpCode()
{
switch (OP)
{
case OpCode.SPECIAL:
return new Instruction
{
OP = OP,
Funct = Funct
};
case OpCode.REGIMM:
return new Instruction
{
OP = OP,
RT = RT
};
default:
return new Instruction { OP = OP };
}
}
public override string ToString()
{
switch (OP)
{
case OpCode.SPECIAL:
return Special.ToString();
case OpCode.REGIMM:
return RegImm.ToString();
default:
return OP.ToString();
}
}
#endregion
#region Operators

View File

@ -31,7 +31,19 @@
/// <summary>Add Immediate.</summary>
ADDI = 0b001000,
/// <summary>Cache Operation.</summary>
CACHE = 0b101111
CACHE = 0b101111,
/// <summary>Jump And Link.</summary>
JAL = 0b000011,
/// <summary>Set On Less Than Immediate.</summary>
SLTI = 0b001010,
/// <summary>Exclusive Or Immediate.</summary>
XORI = 0b001110,
/// <summary>Branch On Less Than Or Equal To Zero Likely.</summary>
BLEZL = 0b010110,
/// <summary>Store Byte.</summary>
SB = 0b101000,
/// <summary>Load Byte Unsigned.</summary>
LBU = 0b100100
}
}
}

View File

@ -5,7 +5,9 @@
public enum RegImmOpCode : byte
{
/// <summary>Branch On Greater Than Or Equal To Zero And Link.</summary>
BGEZAL = 0b10001
BGEZAL = 0b10001,
/// <summary>Branch On Greater Than Or Equal To Zero Likely.</summary>
BGEZL = 0b00011
}
}
}

View File

@ -33,7 +33,9 @@
/// <summary>Shift Right Logical Variable.</summary>
SRLV = 0b000110,
/// <summary>And.</summary>
AND = 0b100100
AND = 0b100100,
/// <summary>Set On Less Than.</summary>
SLT = 0b101010
}
}
}

View File

@ -11,9 +11,7 @@ namespace DotN64.CPU
public partial class VR4300
{
#region Fields
private readonly IReadOnlyDictionary<OpCode, Action<Instruction>> operations;
private readonly IReadOnlyDictionary<SpecialOpCode, Action<Instruction>> specialOperations;
private readonly IReadOnlyDictionary<RegImmOpCode, Action<Instruction>> regImmOperations;
private readonly IReadOnlyDictionary<uint, Action<Instruction>> operations;
private const ulong ResetVector = 0xFFFFFFFFBFC00000;
@ -90,66 +88,64 @@ namespace DotN64.CPU
#region Constructors
public VR4300()
{
operations = new Dictionary<OpCode, Action<Instruction>>
operations = new Dictionary<uint, Action<Instruction>>
{
[OpCode.SPECIAL] = i =>
[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] = (ulong)(i.Immediate & (ushort)GPR[i.RS]),
[Instruction.FromOpCode(OpCode.BEQL)] = i => BranchLikely(i, (rs, rt) => rs == rt),
[Instruction.FromOpCode(OpCode.ADDIU)] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[Instruction.FromOpCode(OpCode.SW)] = i => WriteWord((ulong)(short)i.Immediate + GPR[i.RS], (uint)GPR[i.RT]),
[Instruction.FromOpCode(OpCode.BNEL)] = i => BranchLikely(i, (rs, rt) => rs != rt),
[Instruction.FromOpCode(OpCode.BNE)] = i => Branch(i, (rs, rt) => rs != rt),
[Instruction.FromOpCode(OpCode.BEQ)] = i => Branch(i, (rs, rt) => rs == rt),
[Instruction.FromOpCode(OpCode.ADDI)] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[Instruction.FromOpCode(OpCode.CACHE)] = i => { /* TODO: Implement and compare the performance if it's a concern. */ },
[Instruction.FromOpCode(OpCode.JAL)] = i =>
{
if (specialOperations.TryGetValue((SpecialOpCode)i.Funct, out var operation))
operation(i);
else
throw new Exception($"Unknown special opcode (0b{Convert.ToString(i.Funct, 2)}) from instruction 0x{(uint)i:X8}.");
GPR[31] = PC + Instruction.Size;
DelaySlot = PC;
PC = (PC & ~((ulong)(1 << 28) - 1)) | (i.Target << 2);
},
[OpCode.REGIMM] = i =>
[Instruction.FromOpCode(OpCode.SLTI)] = i => GPR[i.RT] = GPR[i.RS] < (ulong)(short)i.Immediate ? (ulong)1 : 0,
[Instruction.FromOpCode(OpCode.XORI)] = i => GPR[i.RT] = GPR[i.RS] ^ i.Immediate,
[Instruction.FromOpCode(OpCode.BLEZL)] = i => BranchLikely(i, (rs, rt) => rs <= 0),
[Instruction.FromOpCode(OpCode.SB)] = i =>
{
if (regImmOperations.TryGetValue((RegImmOpCode)i.RT, out var operation))
operation(i);
else
throw new Exception($"Unknown reg imm opcode (0b{Convert.ToString(i.RT, 2)}) from instruction 0x{(uint)i:X8}.");
var address = (ulong)(short)i.Immediate + GPR[i.RS];
WriteWord(address, (ReadWord(address) & ~((uint)(1 << 8) - 1)) | (byte)GPR[i.RT]);
},
[OpCode.LUI] = i => GPR[i.RT] = (ulong)(i.Immediate << 16),
[OpCode.MTC0] = i => CP0.Registers[i.RD] = GPR[i.RT],
[OpCode.ORI] = i => GPR[i.RT] = GPR[i.RS] | i.Immediate,
[OpCode.LW] = i => GPR[i.RT] = (ulong)(int)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[OpCode.ANDI] = i => GPR[i.RT] = (ulong)(i.Immediate & (ushort)GPR[i.RS]),
[OpCode.BEQL] = i => BranchLikely(i, (rs, rt) => rs == rt),
[OpCode.ADDIU] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[OpCode.SW] = i => WriteWord((ulong)(short)i.Immediate + GPR[i.RS], (uint)GPR[i.RT]),
[OpCode.BNEL] = i => BranchLikely(i, (rs, rt) => rs != rt),
[OpCode.BNE] = i => Branch(i, (rs, rt) => rs != rt),
[OpCode.BEQ] = i => Branch(i, (rs, rt) => rs == rt),
[OpCode.ADDI] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[OpCode.CACHE] = i => { /* TODO: Implement and compare the performance if it's a concern. */ }
};
specialOperations = new Dictionary<SpecialOpCode, Action<Instruction>>
{
[SpecialOpCode.ADD] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[SpecialOpCode.JR] = i =>
[Instruction.FromOpCode(OpCode.LBU)] = i => GPR[i.RT] = (byte)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[Instruction.FromOpCode(SpecialOpCode.ADD)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[Instruction.FromOpCode(SpecialOpCode.JR)] = i =>
{
DelaySlot = PC;
PC = GPR[i.RS];
},
[SpecialOpCode.SRL] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] >> i.SA),
[SpecialOpCode.OR] = i => GPR[i.RD] = GPR[i.RS] | GPR[i.RT],
[SpecialOpCode.MULTU] = i =>
[Instruction.FromOpCode(SpecialOpCode.SRL)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] >> i.SA),
[Instruction.FromOpCode(SpecialOpCode.OR)] = i => GPR[i.RD] = GPR[i.RS] | GPR[i.RT],
[Instruction.FromOpCode(SpecialOpCode.MULTU)] = i =>
{
var result = (ulong)(uint)GPR[i.RS] * (ulong)(uint)GPR[i.RT];
LO = (ulong)(int)result;
HI = (ulong)(int)(result >> 32);
},
[SpecialOpCode.MFLO] = i => GPR[i.RD] = LO,
[SpecialOpCode.SLL] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] << i.SA),
[SpecialOpCode.SUBU] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RS] - GPR[i.RT]),
[SpecialOpCode.XOR] = i => GPR[i.RD] = GPR[i.RS] ^ GPR[i.RT],
[SpecialOpCode.MFHI] = i => GPR[i.RD] = HI,
[SpecialOpCode.ADDU] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[SpecialOpCode.SLTU] = i => GPR[i.RD] = (ulong)(GPR[i.RS] < GPR[i.RT] ? 1 : 0),
[SpecialOpCode.SLLV] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] << (int)(GPR[i.RS] & ((1 << 5) - 1))),
[SpecialOpCode.SRLV] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] >> (int)(GPR[i.RS] & ((1 << 5) - 1))),
[SpecialOpCode.AND] = i => GPR[i.RD] = GPR[i.RS] & GPR[i.RT]
};
regImmOperations = new Dictionary<RegImmOpCode, Action<Instruction>>
{
[RegImmOpCode.BGEZAL] = i => Branch(i, (rs, rt) => rs >= 0, true)
[Instruction.FromOpCode(SpecialOpCode.MFLO)] = i => GPR[i.RD] = LO,
[Instruction.FromOpCode(SpecialOpCode.SLL)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] << i.SA),
[Instruction.FromOpCode(SpecialOpCode.SUBU)] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RS] - GPR[i.RT]),
[Instruction.FromOpCode(SpecialOpCode.XOR)] = i => GPR[i.RD] = GPR[i.RS] ^ GPR[i.RT],
[Instruction.FromOpCode(SpecialOpCode.MFHI)] = i => GPR[i.RD] = HI,
[Instruction.FromOpCode(SpecialOpCode.ADDU)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[Instruction.FromOpCode(SpecialOpCode.SLTU)] = i => GPR[i.RD] = (ulong)(GPR[i.RS] < GPR[i.RT] ? 1 : 0),
[Instruction.FromOpCode(SpecialOpCode.SLLV)] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] << (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.SRLV)] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] >> (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.AND)] = i => GPR[i.RD] = GPR[i.RS] & GPR[i.RT],
[Instruction.FromOpCode(SpecialOpCode.SLT)] = i => GPR[i.RD] = GPR[i.RS] < GPR[i.RT] ? (ulong)1 : 0,
[Instruction.FromOpCode(RegImmOpCode.BGEZAL)] = i => Branch(i, (rs, rt) => rs >= 0, true),
[Instruction.FromOpCode(RegImmOpCode.BGEZL)] = i => BranchLikely(i, (rs, rt) => rs >= 0)
};
}
#endregion
@ -180,10 +176,10 @@ namespace DotN64.CPU
public void Run(Instruction instruction)
{
if (operations.TryGetValue(instruction.OP, out var operation))
if (operations.TryGetValue(instruction.ToOpCode(), out var operation))
operation(instruction);
else
throw new Exception($"Unknown opcode (0b{Convert.ToString((byte)instruction.OP, 2)}) from instruction 0x{(uint)instruction:X8}.");
throw new Exception($"Unknown opcode from instruction 0x{(uint)instruction:X8}.");
}
public void Step()
@ -227,10 +223,10 @@ namespace DotN64.CPU
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint ReadWord(ulong address) => ReadSysAD(CP0.Map(address));
private uint ReadWord(ulong address) => ReadSysAD(CP0.Translate(address));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteWord(ulong address, uint value) => WriteSysAD(CP0.Map(address), value);
private void WriteWord(ulong address, uint value) => WriteSysAD(CP0.Translate(address), value);
#endregion
}
}

View File

@ -23,6 +23,10 @@ namespace DotN64.Diagnostics
Action = action;
}
#endregion
#region Methods
public override string ToString() => $"{string.Join(", ", Names)}: {Description}";
#endregion
}
}
}

View File

@ -0,0 +1,84 @@
namespace DotN64.Diagnostics
{
using CPU;
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: 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.
/// </summary>
private static class InstructionFormat
{
#region Fields
private const string Separator = ", ", RegisterPrefix = "$";
#endregion
#region Methods
private static string Format(VR4300.Instruction instruction, params object[] values) => $"{instruction} {string.Join(Separator, values)}";
private static string FormatRegister(int index, VR4300 cpu) => RegisterPrefix + (VR4300.GPRIndex)index + (cpu != null ? FormatRegisterContents(cpu.GPR[index]) : string.Empty);
private static string FormatCP0Register(int index, VR4300 cpu) => RegisterPrefix + (VR4300.SystemControlUnit.RegisterIndex)index + (cpu != null ? FormatRegisterContents(cpu.CP0.Registers[index]) : string.Empty);
private static string FormatRegisterContents(ulong value) => $"(0x{value:X})";
/// <summary>
/// Immediate type.
/// </summary>
public static string I(VR4300.Instruction instruction, VR4300 cpu)
{
switch (instruction.RegImm)
{
case VR4300.RegImmOpCode.BGEZAL:
case VR4300.RegImmOpCode.BGEZL:
return Format(instruction, FormatRegister(instruction.RS, cpu), (short)instruction.Immediate);
}
switch (instruction.OP)
{
case VR4300.OpCode.BEQ:
case VR4300.OpCode.BNE:
case VR4300.OpCode.BEQL:
case VR4300.OpCode.BNEL:
return Format(instruction, FormatRegister(instruction.RS, cpu), FormatRegister(instruction.RT, cpu), (short)instruction.Immediate);
case VR4300.OpCode.BLEZL:
return Format(instruction, FormatRegister(instruction.RS, cpu), (short)instruction.Immediate);
default:
return Format(instruction, FormatRegister(instruction.RT, cpu), FormatRegister(instruction.RS, cpu), (short)instruction.Immediate);
}
}
/// <summary>
/// Register type.
/// </summary>
public static string R(VR4300.Instruction instruction, VR4300 cpu)
{
switch (instruction.Special)
{
case VR4300.SpecialOpCode.JR:
return Format(instruction, FormatRegister(instruction.RS, cpu));
case VR4300.SpecialOpCode.SLLV:
case VR4300.SpecialOpCode.SRLV:
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RT, cpu), FormatRegister(instruction.RS, cpu));
}
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));
}
}
/// <summary>
/// Jump type.
/// </summary>
public static string J(VR4300.Instruction instruction, VR4300 cpu) => Format(instruction, $"0x{instruction.Target:X8}");
#endregion
}
}
}

View File

@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
namespace DotN64.Diagnostics
{
using CPU;
using Extensions;
public partial class Debugger
{
@ -15,6 +15,46 @@ 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>>
{
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ADDI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ADDIU)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.ANDI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.BEQ)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.BEQL)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.BLEZL)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.BNE)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.BNEL)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.CACHE)] = null,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.JAL)] = InstructionFormat.J,
[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.ORI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.SB)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.SLTI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.SW)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.OpCode.XORI)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.ADD)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.ADDU)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.AND)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.JR)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.MFHI)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.MFLO)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.MULTU)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.OR)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SLL)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SLLV)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SLT)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SLTU)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SRL)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SRLV)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.SUBU)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.SpecialOpCode.XOR)] = InstructionFormat.R,
[VR4300.Instruction.FromOpCode(VR4300.RegImmOpCode.BGEZAL)] = InstructionFormat.I,
[VR4300.Instruction.FromOpCode(VR4300.RegImmOpCode.BGEZL)] = InstructionFormat.I
};
#endregion
#region Properties
@ -30,9 +70,9 @@ namespace DotN64.Diagnostics
new Command(new[] { "continue", "c" }, "Continues execution of the CPU.", args => DebuggerStatus = Status.Running),
new Command(new[] { "step", "s" }, "Steps the CPU a specified amount of times.", args =>
{
var count = args.Length > 0 ? int.Parse(args.First()) : 1;
var count = args.Length > 0 ? BigInteger.Parse(args.First()) : 1;
for (var i = 0; i < count; i++)
for (var i = BigInteger.Zero; i < count; i++)
{
Disassemble();
nintendo64.CPU.Step();
@ -41,18 +81,18 @@ namespace DotN64.Diagnostics
new Command(new[] { "goto", "g" }, "Sets the CPU's PC to the specified address.", args => nintendo64.CPU.PC = ulong.Parse(args.First(), NumberStyles.HexNumber)),
new Command(new[] { "disassemble", "d" }, "Disassembles instructions from the current PC.", args =>
{
var count = args.Length > 0 ? ulong.Parse(args.First()) : 1;
var count = args.Length > 0 ? BigInteger.Parse(args.First()) : 1;
for (var i = 0ul; i < count; i++)
for (var i = BigInteger.Zero; i < count; i++)
{
Disassemble(nintendo64.CPU.PC + i * VR4300.Instruction.Size);
Disassemble(nintendo64.CPU.PC + (ulong)(i * VR4300.Instruction.Size), false);
}
}),
new Command(new[] { "label", "labels", "l" }, "Shows, adds or removes a label attached to an address.", args =>
{
var index = 0;
switch (args.Length == 0 ? "show" : args[index++])
switch (args.Length > 0 ? args[index++] : "show")
{
case "add":
case "a":
@ -75,7 +115,6 @@ namespace DotN64.Diagnostics
{
Console.WriteLine($".{pair.Value}: {pair.Key:X16}");
}
break;
}
@ -90,7 +129,7 @@ namespace DotN64.Diagnostics
{
var index = 0;
switch (args.Length == 0 ? "show" : args[index++])
switch (args.Length > 0 ? args[index++] : "show")
{
case "add":
case "a":
@ -120,7 +159,7 @@ namespace DotN64.Diagnostics
{
foreach (var command in commands)
{
Console.WriteLine($"- {string.Join(", ", command.Names)}: {command.Description}");
Console.WriteLine("- " + command);
}
}
else
@ -128,7 +167,7 @@ namespace DotN64.Diagnostics
var commandName = args.First();
var command = commands.First(c => c.Names.Contains(commandName));
Console.WriteLine($"{string.Join(", ", command.Names)}: {command.Description}");
Console.WriteLine(command);
}
}),
new Command(new[] { "clear" }, "Clears the terminal.", args => Console.Clear())
@ -137,32 +176,20 @@ namespace DotN64.Diagnostics
#endregion
#region Methods
private string Disassemble(VR4300.Instruction instruction)
{
switch (instruction.OP)
{
case VR4300.OpCode.SPECIAL:
return ((VR4300.SpecialOpCode)instruction.Funct).ToString();
case VR4300.OpCode.REGIMM:
return ((VR4300.RegImmOpCode)instruction.RT).ToString();
default:
return instruction.OP.ToString();
}
}
private string Disassemble(VR4300.Instruction instruction, bool withRegisterContents) => operationFormats.TryGetValue(instruction.ToOpCode(), out var format) && format != null ? format(instruction, withRegisterContents ? nintendo64.CPU : null) : instruction.ToString();
private void Disassemble(ulong? address = null)
private void Disassemble(ulong? address = null, bool withRegisterContents = true)
{
if (!address.HasValue)
address = nintendo64.CPU.DelaySlot ?? nintendo64.CPU.PC;
var physicalAddress = nintendo64.CPU.CP0.Map(address.Value);
var instruction = nintendo64.MemoryMaps.GetEntry(physicalAddress).ReadWord(physicalAddress);
var instruction = nintendo64.CPU.ReadSysAD(nintendo64.CPU.CP0.Translate(address.Value));
if (labels.ContainsKey(address.Value))
if (labels.TryGetValue(address.Value, out var label))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($".{labels[address.Value]}:");
Console.WriteLine($".{label}:");
Console.ResetColor();
}
@ -174,7 +201,7 @@ namespace DotN64.Diagnostics
Console.ForegroundColor = ConsoleColor.White;
}
Console.WriteLine($"{(hasBreakpoint ? '●' : ' ')} {address.Value:X16}: {instruction:X8} {Disassemble(instruction)}");
Console.WriteLine($"{(hasBreakpoint ? '●' : ' ')} {address.Value:X16}: {instruction:X8} {Disassemble(instruction, withRegisterContents)}");
if (hasBreakpoint)
Console.ResetColor();

View File

@ -27,6 +27,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
@ -67,6 +68,12 @@
<Compile Include="Diagnostics\Debugger.cs" />
<Compile Include="Diagnostics\Debugger.Status.cs" />
<Compile Include="Diagnostics\Debugger.Command.cs" />
<Compile Include="Diagnostics\Debugger.InstructionFormat.cs" />
<Compile Include="Helpers\BitHelper.cs" />
<Compile Include="RCP\DP\RealityCoprocessor.DisplayProcessor.StatusRegister.cs" />
<Compile Include="RCP\SP\RealityCoprocessor.SignalProcessor.StatusRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.RDRAMConfigRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.RDRAMConfigIndex.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />
@ -83,6 +90,7 @@
<Folder Include="CPU\VR4300\" />
<Folder Include="CPU\VR4300\CP0\" />
<Folder Include="Diagnostics\" />
<Folder Include="Helpers\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -20,6 +20,12 @@ namespace DotN64.Extensions
throw new Exception($"Unknown physical address: 0x{address:X16}.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadWord(this IReadOnlyList<MappingEntry> memoryMaps, ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteWord(this IReadOnlyList<MappingEntry> memoryMaps, ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -0,0 +1,37 @@
using System.Runtime.CompilerServices;
namespace DotN64.Helpers
{
public static class BitHelper
{
#region Methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Get(uint data, int shift, uint size) => data >> shift & size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Get(ulong data, int shift, ulong size) => data >> shift & size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Set(ref uint data, int shift, uint size, uint value)
{
data &= ~(size << shift);
data |= (value & size) << shift;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Set(ref ulong data, int shift, ulong size, ulong value)
{
data &= ~(size << shift);
data |= (value & size) << shift;
}
public static unsafe void Write(byte[] data, int index, uint value)
{
fixed (byte* pointer = &data[index])
{
*(uint*)pointer = value;
}
}
#endregion
}
}

View File

@ -28,69 +28,74 @@ namespace DotN64
{
new MappingEntry(0x1FC00000, 0x1FC007BF, false) // PIF Boot ROM.
{
Read = RCP.PI.ReadWord,
Write = RCP.PI.WriteWord
Read = RCP.PI.MemoryMaps.ReadWord,
Write = RCP.PI.MemoryMaps.WriteWord
},
new MappingEntry(0x1FC007C0, 0x1FC007FF, false) // PIF (JoyChannel) RAM.
{
Read = RCP.PI.ReadWord,
Write = RCP.PI.WriteWord
Read = RCP.PI.MemoryMaps.ReadWord,
Write = RCP.PI.MemoryMaps.WriteWord
},
new MappingEntry(0x04600000, 0x046FFFFF, false) // Peripheral interface (PI) registers.
{
Read = RCP.PI.ReadWord,
Write = RCP.PI.WriteWord
Read = RCP.PI.MemoryMaps.ReadWord,
Write = RCP.PI.MemoryMaps.WriteWord
},
new MappingEntry(0x04000000, 0x040FFFFF, false) // SP registers.
{
Read = RCP.SP.ReadWord,
Write = RCP.SP.WriteWord
Read = RCP.SP.MemoryMaps.ReadWord,
Write = RCP.SP.MemoryMaps.WriteWord
},
new MappingEntry(0x04400000, 0x044FFFFF, false) // Video interface (VI) registers.
{
Read = RCP.VI.ReadWord,
Write = RCP.VI.WriteWord
Read = RCP.VI.MemoryMaps.ReadWord,
Write = RCP.VI.MemoryMaps.WriteWord
},
new MappingEntry(0x04500000, 0x045FFFFF, false) // Audio interface (AI) registers.
{
Read = RCP.AI.ReadWord,
Write = RCP.AI.WriteWord
Read = RCP.AI.MemoryMaps.ReadWord,
Write = RCP.AI.MemoryMaps.WriteWord
},
new MappingEntry(0x04300000, 0x043FFFFF, false) // MIPS interface (MI) registers.
{
Read = RCP.MI.ReadWord,
Write = RCP.MI.WriteWord
Read = RCP.MI.MemoryMaps.ReadWord,
Write = RCP.MI.MemoryMaps.WriteWord
},
new MappingEntry(0x04800000, 0x048FFFFF, false) // Serial interface (SI) registers.
{
Read = RCP.SI.ReadWord,
Write = RCP.SI.WriteWord
Read = RCP.SI.MemoryMaps.ReadWord,
Write = RCP.SI.MemoryMaps.WriteWord
},
new MappingEntry(0x10000000, 0x1FBFFFFF, false) // Cartridge Domain 1 Address 2.
{
Read = RCP.PI.ReadWord
Read = RCP.PI.MemoryMaps.ReadWord
},
new MappingEntry(0x04100000, 0x041FFFFF, false) // DP command registers.
{
Read = RCP.DP.ReadWord,
Write = RCP.DP.WriteWord
Read = RCP.DP.MemoryMaps.ReadWord,
Write = RCP.DP.MemoryMaps.WriteWord
},
new MappingEntry(0x04700000, 0x047FFFFF, false) // RDRAM interface (RI) registers.
{
Read = RCP.RI.ReadWord,
Write = RCP.RI.WriteWord
Read = RCP.RI.MemoryMaps.ReadWord,
Write = RCP.RI.MemoryMaps.WriteWord
},
new MappingEntry(0x00000000, 0x03EFFFFF, false) // RDRAM memory.
{
Read = RCP.RI.ReadWord,
Write = RCP.RI.WriteWord
Read = RCP.RI.MemoryMaps.ReadWord,
Write = RCP.RI.MemoryMaps.WriteWord
},
new MappingEntry(0x03F00000, 0x03FFFFFF, false) // RDRAM registers.
{
Read = RCP.RI.MemoryMaps.ReadWord,
Write = RCP.RI.MemoryMaps.WriteWord
}
};
CPU = new VR4300
{
DivMode = 0b01, // Assuming this value as the CPU is clocked at 93.75 MHz, and the RCP would be clocked at 93.75 / 3 * 2 = 62.5 MHz.
ReadSysAD = a => MemoryMaps.GetEntry(a).ReadWord(a),
WriteSysAD = (a, v) => MemoryMaps.GetEntry(a).WriteWord(a, v)
ReadSysAD = MemoryMaps.ReadWord,
WriteSysAD = MemoryMaps.WriteWord
};
}
#endregion

View File

@ -9,11 +9,11 @@ namespace DotN64
private static void Main(string[] args)
{
var nintendo64 = new Nintendo64();
var debugger = default(Debugger);
Debugger debugger = null;
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
var arg = args[i];
switch (arg)
{

View File

@ -1,18 +1,10 @@
using System.Collections.Generic;
namespace DotN64.RCP
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public class AudioInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
private uint dramAddress;
/// <summary>
/// Starting RDRAM address (8B-aligned).
@ -35,7 +27,7 @@ namespace DotN64.RCP
public AudioInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x04500000, 0x04500003) // AI DRAM address.
{

View File

@ -0,0 +1,24 @@
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class DisplayProcessor
{
[System.Flags]
public enum StatusRegister : ushort
{
XBusDMemDMA = 1 << 0,
Freeze = 1 << 1,
Flush = 1 << 2,
StartGClk = 1 << 3,
TMemBusy = 1 << 4,
PipeBusy = 1 << 5,
CmdBusy = 1 << 6,
CBufReady = 1 << 7,
DMABusy = 1 << 8,
EndValid = 1 << 9,
StartValid = 1 << 10
}
}
}
}

View File

@ -2,34 +2,28 @@
namespace DotN64.RCP
{
using Extensions;
public partial class RealityCoprocessor
{
public class DisplayProcessor
public partial class DisplayProcessor
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public StatusRegister Status { get; set; }
#endregion
#region Constructors
public DisplayProcessor()
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x0410000C, 0x0410000F) // DP CMD status.
{
Read = o => 0
Read = o => (uint)Status
}
};
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace DotN64.RCP
@ -8,13 +7,7 @@ namespace DotN64.RCP
{
public partial class MIPSInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
public InitModeRegister InitMode { get; set; }
#endregion
@ -22,7 +15,7 @@ namespace DotN64.RCP
public MIPSInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x04300000, 0x04300003) // MI init mode.
{
@ -47,6 +40,10 @@ namespace DotN64.RCP
InitMode = mode;
}
},
new MappingEntry(0x04300004, 0x04300007) // MI version.
{
Read = o => 0 // TODO.
}
};
}

View File

@ -1,25 +1,21 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace DotN64.RCP
{
using Extensions;
using Helpers;
public partial class RealityCoprocessor
{
public partial class PeripheralInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
private const byte CICStatusOffset = 60;
private const byte ResetControllerStatus = 1 << 0, ClearInterruptStatus = 1 << 1;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
public StatusRegister Status { get; set; }
public byte[] BootROM { get; set; }
@ -52,7 +48,7 @@ namespace DotN64.RCP
public PeripheralInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x1FC00000, 0x1FC007BF) // PIF Boot ROM.
{
@ -63,13 +59,7 @@ namespace DotN64.RCP
Read = o => BitConverter.ToUInt32(RAM, (int)o),
Write = (o, v) =>
{
unsafe
{
fixed (byte* data = &RAM[(int)o])
{
*(uint*)data = v;
}
}
BitHelper.Write(RAM, (int)o, v);
if (o == CICStatusOffset && RAM[o] == (byte)CICStatus.Waiting) // The boot ROM waits for the PIF's CIC check to be OK.
RAM[o] = (byte)CICStatus.OK; // We tell it it's OK by having the loaded word that gets ANDI'd match the immediate value 128, storing non-zero which allows us to exit the BEQL loop.
@ -80,11 +70,9 @@ namespace DotN64.RCP
Read = o => (uint)Status,
Write = (o, v) =>
{
if ((v & ResetControllerStatus) != 0)
ResetController();
if ((v & ResetControllerStatus) != 0) { /* TODO. */ }
if ((v & ClearInterruptStatus) != 0)
ClearInterrupt();
if ((v & ClearInterruptStatus) != 0) { /* TODO. */ }
}
},
new MappingEntry(0x04600014, 0x04600017) // PI dom1 latency.
@ -113,7 +101,20 @@ namespace DotN64.RCP
},
new MappingEntry(0x0460000C, 0x0460000F) // PI write length.
{
Write = (o, v) => WriteLength = v & ((1 << 24) - 1)
Write = (o, v) =>
{
WriteLength = v & ((1 << 24) - 1);
Status |= StatusRegister.DMABusy;
var maps = rcp.Nintendo64.MemoryMaps;
for (uint i = 0; i < WriteLength + 1; i += sizeof(uint))
{
maps.WriteWord(DRAMAddress + i, maps.ReadWord(PBusAddress + i));
}
Status &= ~StatusRegister.DMABusy;
// TODO: Set interrupt.
}
},
new MappingEntry(0x10000000, 0x1FBFFFFF) // Cartridge Domain 1 Address 2.
{
@ -124,10 +125,6 @@ namespace DotN64.RCP
#endregion
#region Methods
private void ClearInterrupt() { /* TODO: Implement. */ }
private void ResetController() { /* TODO: Implement. */ }
public void EmulateBootROM()
{
// Replicating the memory writes to properly initialise the subsystems.
@ -152,14 +149,14 @@ namespace DotN64.RCP
{
var address = (ulong)writes[i, 0];
rcp.Nintendo64.MemoryMaps.GetEntry(address).WriteWord(address, writes[i, 1]);
rcp.Nintendo64.MemoryMaps.WriteWord(address, writes[i, 1]);
}
for (int i = 0x40; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
{
var address = (ulong)(0x04000000 + i);
rcp.Nintendo64.MemoryMaps.GetEntry(address).WriteWord(address, (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(rcp.Nintendo64.Cartridge.ROM, i)));
rcp.Nintendo64.MemoryMaps.WriteWord(address, (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(rcp.Nintendo64.Cartridge.ROM, i)));
}
// Restoring CPU state.

View File

@ -0,0 +1,15 @@
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class RDRAMInterface
{
public enum RDRAMConfigIndex : byte
{
Zero,
One,
Global
}
}
}
}

View File

@ -0,0 +1,27 @@
using System.Runtime.InteropServices;
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class RDRAMInterface
{
[StructLayout(LayoutKind.Sequential)]
public struct RDRAMConfigRegister
{
#region Fields
public uint DeviceType;
public uint DeviceID;
public uint Delay;
public uint Mode;
public uint RefInterval;
public uint RefRow;
public uint RasInterval;
public uint MinInterval;
public uint AddressSelect;
public uint DeviceManufacturer;
#endregion
}
}
}
}

View File

@ -1,33 +1,30 @@
using System;
using System.Collections.Generic;
namespace DotN64.RCP
{
using Helpers;
public partial class RealityCoprocessor
{
public partial class RDRAMInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
public byte Select { get; set; }
public byte Select { get; set; } = 0x14;
public ConfigRegister Config { get; set; }
public ConfigRegister Config { get; set; } = 0x40;
public ModeRegister Mode { get; set; }
public ModeRegister Mode { get; set; } = 0x0E;
public RefreshRegister Refresh { get; set; }
public RefreshRegister Refresh { get; set; } = 0x00063634;
public RDRAMConfigRegister[] RDRAMConfigs { get; } = new RDRAMConfigRegister[Enum.GetNames(typeof(RDRAMConfigIndex)).Length];
#endregion
#region Constructors
public RDRAMInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x0470000C, 0x0470000F) // RI select.
{
@ -48,17 +45,39 @@ namespace DotN64.RCP
},
new MappingEntry(0x04700010, 0x04700013) // RI refresh.
{
Read = a => Refresh,
Write = (a, v) => Refresh = v
},
new MappingEntry(0x00000000, 0x03EFFFFF) // RDRAM memory.
{
Read = o => BitConverter.ToUInt32(rcp.Nintendo64.RAM, (int)o),
Write = (o, v) =>
Write = (o, v) => BitHelper.Write(rcp.Nintendo64.RAM, (int)o, v)
},
new MappingEntry(0x03F00000, 0x03FFFFFF) // RDRAM registers.
{
Read = o =>
{
if (!GetRDRAMRegisterInfo((uint)o, out var register, out var index))
return 0;
unsafe
{
fixed (byte* data = &rcp.Nintendo64.RAM[(int)o])
fixed (void* pointer = &RDRAMConfigs[index.Value])
{
*(uint*)data = v;
return *((uint*)pointer + register / sizeof(uint));
}
}
},
Write = (o, v) =>
{
if (!GetRDRAMRegisterInfo((uint)o, out var register, out var index))
return;
unsafe
{
fixed (void* pointer = &RDRAMConfigs[index.Value])
{
*((uint*)pointer + register / sizeof(uint)) = v;
}
}
}
@ -66,6 +85,31 @@ namespace DotN64.RCP
};
}
#endregion
#region Methods
private bool GetRDRAMRegisterInfo(uint offset, out uint register, out int? index)
{
register = offset & ((1 << 8) - 1);
switch (offset >> 8)
{
case 0x0000:
index = (int)RDRAMConfigIndex.Zero;
break;
case 0x0004:
index = (int)RDRAMConfigIndex.One;
break;
case 0x0800:
index = (int)RDRAMConfigIndex.Global;
break;
default:
index = null;
return false;
}
return true;
}
#endregion
}
}
}

View File

@ -2,8 +2,6 @@
namespace DotN64.RCP
{
using Extensions;
public partial class RealityCoprocessor
{
public abstract class Interface
@ -13,7 +11,7 @@ namespace DotN64.RCP
#endregion
#region Properties
protected abstract IReadOnlyList<MappingEntry> MemoryMaps { get; }
public IReadOnlyList<MappingEntry> MemoryMaps { get; protected set; }
#endregion
#region Constructors
@ -22,12 +20,6 @@ namespace DotN64.RCP
this.rcp = rcp;
}
#endregion
#region Methods
public uint ReadWord(ulong address) => MemoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => MemoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}
}

View File

@ -1,18 +1,10 @@
using System.Collections.Generic;
namespace DotN64.RCP
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class SerialInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
public StatusRegister Status { get; set; }
#endregion
@ -20,7 +12,7 @@ namespace DotN64.RCP
public SerialInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x04800018, 0x0480001B) // SI status.
{

View File

@ -0,0 +1,60 @@
using System;
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class SignalProcessor
{
[Flags]
public enum StatusRegister : ushort
{
Halt = 1 << 0,
Broke = 1 << 1,
DMABusy = 1 << 2,
DMAFull = 1 << 3,
IOFull = 1 << 4,
SingleStep = 1 << 5,
InterruptOnBreak = 1 << 6,
Signal0 = 1 << 7,
Signal1 = 1 << 8,
Signal2 = 1 << 9,
Signal3 = 1 << 10,
Signal4 = 1 << 11,
Signal5 = 1 << 12,
Signal6 = 1 << 13,
Signal7 = 1 << 14
}
[Flags]
private enum WriteStatusRegister
{
ClearHalt = 1 << 0,
SetHalt = 1 << 1,
ClearBroke = 1 << 2,
ClearInterrupt = 1 << 3,
SetInterrupt = 1 << 4,
ClearSingleStep = 1 << 5,
SetSingleStep = 1 << 6,
ClearInterruptOnBreak = 1 << 7,
SetInterruptOnBreak = 1 << 8,
ClearSignal0 = 1 << 9,
SetSignal0 = 1 << 10,
ClearSignal1 = 1 << 11,
SetSignal1 = 1 << 12,
ClearSignal2 = 1 << 13,
SetSignal2 = 1 << 14,
ClearSignal3 = 1 << 15,
SetSignal3 = 1 << 16,
ClearSignal4 = 1 << 17,
SetSignal4 = 1 << 18,
ClearSignal5 = 1 << 19,
SetSignal5 = 1 << 20,
ClearSignal6 = 1 << 21,
SetSignal6 = 1 << 22,
ClearSignal7 = 1 << 23,
SetSignal7 = 1 << 24,
}
}
}
}

View File

@ -3,77 +3,133 @@ using System.Collections.Generic;
namespace DotN64.RCP
{
using Extensions;
using Helpers;
public partial class RealityCoprocessor
{
public class SignalProcessor
public partial class SignalProcessor
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
public uint Status { get; set; } = 1;
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public uint DMABusy { get; set; }
public StatusRegister Status { get; set; } = StatusRegister.Halt;
public bool DMABusy { get; set; }
/// <summary>
/// Instruction memory.
/// </summary>
public byte[] IMEM { get; } = new byte[0x1000];
/// <summary>
/// Data memory.
/// </summary>
public byte[] DMEM { get; } = new byte[0x1000];
#endregion
#region Constructors
public SignalProcessor()
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x04001000, 0x04001FFF) // SP_IMEM read/write.
{
Read = o => BitConverter.ToUInt32(IMEM, (int)o),
Write = (o, v) =>
{
unsafe
{
fixed (byte* data = &IMEM[(int)o])
{
*(uint*)data = v;
}
}
}
Write = (o, v) => BitHelper.Write(IMEM, (int)o, v)
},
new MappingEntry(0x04040010, 0x04040013) // SP status.
{
Read = o => Status,
Write = (o, v) => Status = v
Read = o => (uint)Status,
Write = (o, v) =>
{
var status = (WriteStatusRegister)v;
if (status.HasFlag(WriteStatusRegister.ClearHalt))
Status &= ~StatusRegister.Halt;
if (status.HasFlag(WriteStatusRegister.SetHalt))
Status |= StatusRegister.Halt;
if (status.HasFlag(WriteStatusRegister.ClearBroke))
Status &= ~StatusRegister.Broke;
if (status.HasFlag(WriteStatusRegister.ClearInterrupt)) { /* TODO. */ }
if (status.HasFlag(WriteStatusRegister.SetInterrupt)) { /* TODO. */ }
if (status.HasFlag(WriteStatusRegister.ClearSingleStep))
Status &= ~StatusRegister.SingleStep;
if (status.HasFlag(WriteStatusRegister.SetSingleStep))
Status |= StatusRegister.SingleStep;
if (status.HasFlag(WriteStatusRegister.ClearInterruptOnBreak))
Status &= ~StatusRegister.InterruptOnBreak;
if (status.HasFlag(WriteStatusRegister.SetInterruptOnBreak))
Status |= StatusRegister.InterruptOnBreak;
if (status.HasFlag(WriteStatusRegister.ClearSignal0))
Status &= ~StatusRegister.Signal0;
if (status.HasFlag(WriteStatusRegister.SetSignal0))
Status |= StatusRegister.Signal0;
if (status.HasFlag(WriteStatusRegister.ClearSignal1))
Status &= ~StatusRegister.Signal1;
if (status.HasFlag(WriteStatusRegister.SetSignal1))
Status |= StatusRegister.Signal1;
if (status.HasFlag(WriteStatusRegister.ClearSignal2))
Status &= ~StatusRegister.Signal2;
if (status.HasFlag(WriteStatusRegister.SetSignal2))
Status |= StatusRegister.Signal2;
if (status.HasFlag(WriteStatusRegister.ClearSignal3))
Status &= ~StatusRegister.Signal3;
if (status.HasFlag(WriteStatusRegister.SetSignal3))
Status |= StatusRegister.Signal3;
if (status.HasFlag(WriteStatusRegister.ClearSignal4))
Status &= ~StatusRegister.Signal4;
if (status.HasFlag(WriteStatusRegister.SetSignal4))
Status |= StatusRegister.Signal4;
if (status.HasFlag(WriteStatusRegister.ClearSignal5))
Status &= ~StatusRegister.Signal5;
if (status.HasFlag(WriteStatusRegister.SetSignal5))
Status |= StatusRegister.Signal5;
if (status.HasFlag(WriteStatusRegister.ClearSignal6))
Status &= ~StatusRegister.Signal6;
if (status.HasFlag(WriteStatusRegister.SetSignal6))
Status |= StatusRegister.Signal6;
if (status.HasFlag(WriteStatusRegister.ClearSignal7))
Status &= ~StatusRegister.Signal7;
if (status.HasFlag(WriteStatusRegister.SetSignal7))
Status |= StatusRegister.Signal7;
}
},
new MappingEntry(0x04040018, 0x0404001B) // SP DMA busy.
{
Read = o => DMABusy
Read = o => Convert.ToUInt32(DMABusy)
},
new MappingEntry(0x04000000, 0x04000FFF) // SP_DMEM read/write.
{
Read = o => BitConverter.ToUInt32(DMEM, (int)o),
Write = (o, v) =>
{
unsafe
{
fixed (byte* data = &DMEM[(int)o])
{
*(uint*)data = v;
}
}
}
Write = (o, v) => BitHelper.Write(DMEM, (int)o, v)
}
};
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}
}

View File

@ -6,13 +6,7 @@ namespace DotN64.RCP
{
public partial class VideoInterface : Interface
{
#region Fields
private readonly IReadOnlyList<MappingEntry> memoryMaps;
#endregion
#region Properties
protected override IReadOnlyList<MappingEntry> MemoryMaps => memoryMaps;
private ushort verticalInterrupt;
/// <summary>
/// Interrupt when current half-line = V_INTR.
@ -45,7 +39,7 @@ namespace DotN64.RCP
public VideoInterface(RealityCoprocessor rcp)
: base(rcp)
{
memoryMaps = new[]
MemoryMaps = new[]
{
new MappingEntry(0x0440000C, 0x0440000F) // VI vertical intr.
{