- Added the FPU.

- Added an exception handler for the FPU.
- Added constants for cartridges and fixed header properties.
- PIF HLE determines the console region for the saved register that's used by the OS.
- The disassembler shows CP1 opcodes.
master
Nabile Rahmani 2018-01-23 18:46:12 +01:00
parent 9910f7ab0b
commit 1800179515
18 changed files with 452 additions and 15 deletions

View File

@ -55,6 +55,8 @@ namespace DotN64.CPU
}
}
public bool IsCoprocessorUsable(byte unit) => ((byte)Status.CU & 1 << unit) != 0 || (unit == 0 && Status.KSU == StatusRegister.Mode.Kernel);
public void Run(Instruction instruction)
{
if (operations.TryGetValue((OpCode)instruction.RS, out var operation))

View File

@ -0,0 +1,138 @@
using System;
using System.Collections.Specialized;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class FloatingPointUnit
{
/// <summary>
/// See: datasheet#7.2.4.
/// </summary>
public class ControlStatusRegister : Register
{
#region Fields
private static readonly BitVector32.Section rmSection = BitVector32.CreateSection((1 << 2) - 1),
flagsSection = BitVector32.CreateSection((1 << 5) - 1, rmSection),
enablesSection = BitVector32.CreateSection((1 << 5) - 1, flagsSection),
causeSection = BitVector32.CreateSection((1 << 6) - 1, enablesSection),
constant1 = BitVector32.CreateSection((1 << 5) - 1, causeSection),
cSection = BitVector32.CreateSection(1, constant1),
fsSection = BitVector32.CreateSection(1, cSection);
#endregion
#region Properties
protected override uint Data
{
get => fpu.cpu.FCR31;
set => fpu.cpu.FCR31 = value;
}
/// <summary>
/// Bits 1 and 0 in the FCR31 register constitute the Rounding Mode (RM) bits.
/// These bits specify the rounding mode that FPU uses for all floating-point operations.
/// </summary>
public RoundingMode RM
{
get => (RoundingMode)this[rmSection];
set => this[rmSection] = (byte)value;
}
/// <summary>
/// The Flag bits are cumulative and indicate the exceptions that were raised after reset.
/// Flag bits are set to 1 if an IEEE754 exception is raised but the occurrence of the exception is prohibited.
/// Otherwise, they remain unchanged.
/// The Flag bits are never cleared as a side effect of floating-point operations; however, they can be set or cleared by writing a new value into the FCR31, using a CTC1 instruction.
/// </summary>
public ExceptionFlags Flags
{
get => (ExceptionFlags)this[flagsSection];
set => this[flagsSection] = (byte)value;
}
/// <summary>
/// A floating-point exception is generated any time a Cause bit and the corresponding Enable bit are set.
/// As soon as the Cause bit enabled through the Floating-point operation, an exception occurs.
/// When both Cause and Enable bits are set by the CTC1 instruction, an exception also occurs.
/// </summary>
public ExceptionFlags Enables
{
get => (ExceptionFlags)this[enablesSection];
set => this[enablesSection] = (byte)value;
}
/// <summary>
/// Bits 17:12 in the FCR31 contain Cause bits which reflect the results of the most recently executed floating-point instruction.
/// The Cause bits are a logical extension of the CP0 Cause register; they identify the exceptions raised by the last floating-point operation; and generate exceptions if the corresponding Enable bit is set.
/// If more than one exception occurs on a single instruction, each appropriate bit is set.
/// </summary>
public ExceptionFlags Cause
{
get => (ExceptionFlags)this[causeSection];
set => this[causeSection] = (byte)value;
}
/// <summary>
/// When a floating-point Compare operation takes place, the result is stored at bit 23, the Condition bit.
/// The C bit is set to 1 if the condition is true; the bit is cleared to 0 if the condition is false.
/// Bit 23 is affected only by compare and CTC1 instructions.
/// </summary>
public bool C
{
get => Convert.ToBoolean(this[cSection]);
set => this[cSection] = Convert.ToInt32(value);
}
/// <summary>
/// The FS bit enables a value that cannot be normalized (denormalized number) to be flashed.
/// When the FS bit is set and the enable bit is not set for the underflow exception and illegal exception, the result of the denormalized number does not cause the unimplemented operation exception, but is flushed.
/// Whether the flushed result is 0 or the minimum normalized value is determined depending on the rounding mode (refer to Table 7-2).
/// If the result is flushed, the Flag and Cause bits are set for the underflow and illegal exceptions.
/// </summary>
public bool FS
{
get => Convert.ToBoolean(this[fsSection]);
set => this[fsSection] = Convert.ToInt32(value);
}
#endregion
#region Constructors
public ControlStatusRegister(FloatingPointUnit fpu)
: base(fpu) { }
#endregion
#region Enumerations
public enum RoundingMode : byte
{
/// <summary>Round result to nearest representable value; round to value with least-significant bit 0 when the two nearest representable values are equally near.</summary>
RN = 0b00,
/// <summary>Round toward 0: round to value closest to and not greater in magnitude than the infinitely precise result.</summary>
RZ = 0b01,
/// <summary>Round toward + ∞: round to value closest to and not less than the infinitely precise result.</summary>
RP = 0b10,
/// <summary>Round toward ∞: round to value closest to and not greater than the infinitely precise result.</summary>
RM = 0b11
}
[Flags]
public enum ExceptionFlags : byte
{
/// <summary>Inexact Operation.</summary>
I = 1 << 0,
/// <summary>Underflow.</summary>
U = 1 << 1,
/// <summary>Overflow.</summary>
O = 1 << 2,
/// <summary>Division by Zero.</summary>
Z = 1 << 3,
/// <summary>Invalid Operation.</summary>
V = 1 << 4,
/// <summary>Unimplemented Operation.</summary>
E = 1 << 5
}
#endregion
}
}
}
}

View File

@ -0,0 +1,55 @@
using System.Collections.Specialized;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class FloatingPointUnit
{
/// <summary>
/// See: datasheet#7.2.5.
/// </summary>
public class ImplementationRevisionRegister : Register
{
#region Fields
private static readonly BitVector32.Section revSection = BitVector32.CreateSection((1 << 8) - 1),
impSection = BitVector32.CreateSection((1 << 8) - 1, revSection);
#endregion
#region Properties
protected override uint Data
{
get => fpu.cpu.FCR0;
set => fpu.cpu.FCR0 = value;
}
/// <summary>
/// Revision number in the form of y.x.
/// </summary>
public byte Rev
{
get => (byte)this[revSection];
set => this[revSection] = value;
}
/// <summary>
/// Implementation number (0x0B).
/// </summary>
public byte Imp
{
get => (byte)this[impSection];
set => this[impSection] = value;
}
#endregion
#region Constructors
public ImplementationRevisionRegister(FloatingPointUnit fpu)
: base(fpu)
{
Imp = 0x0B;
}
#endregion
}
}
}
}

View File

@ -0,0 +1,16 @@
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class FloatingPointUnit
{
public enum OpCode : byte
{
/// <summary>Move Control Word From FPU.</summary>
CF = 0b00010,
/// <summary>Move Control Word To FPU.</summary>
CT = 0b00110
}
}
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Specialized;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class FloatingPointUnit
{
public abstract class Register
{
#region Fields
protected readonly FloatingPointUnit fpu;
#endregion
#region Properties
private BitVector32 Bits => new BitVector32((int)Data);
protected abstract uint Data { get; set; }
#endregion
#region Indexers
protected int this[BitVector32.Section section]
{
get => Bits[section];
set
{
var bits = Bits;
bits[section] = value;
Data = (uint)bits.Data;
}
}
protected bool this[int mask]
{
get => Bits[mask];
set
{
var bits = Bits;
bits[mask] = value;
Data = (uint)bits.Data;
}
}
#endregion
#region Constructors
protected Register(FloatingPointUnit fpu)
{
this.fpu = fpu;
}
#endregion
}
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class FloatingPointUnit : ICoprocessor
{
#region Fields
private readonly VR4300 cpu;
private readonly IReadOnlyDictionary<OpCode, Action<Instruction>> operations;
#endregion
#region Properties
public ImplementationRevisionRegister ImplementationRevision { get; }
public ControlStatusRegister ControlStatus { get; }
/// <summary>
/// Condition signal.
/// </summary>
public bool CO { get; private set; }
#endregion
#region Constructors
public FloatingPointUnit(VR4300 cpu)
{
this.cpu = cpu;
ImplementationRevision = new ImplementationRevisionRegister(this);
ControlStatus = new ControlStatusRegister(this);
operations = new Dictionary<OpCode, Action<Instruction>>
{
[OpCode.CF] = i =>
{
switch (i.RD)
{
case 0:
cpu.GPR[i.RT] = (ulong)(int)cpu.FCR0;
break;
case 31:
cpu.GPR[i.RT] = (ulong)(int)cpu.FCR31;
break;
default:
ExceptionProcessing.ReservedInstruction(cpu, i);
return;
}
},
[OpCode.CT] = i =>
{
switch (i.RD)
{
case 0:
return; // Read-only register.
case 31:
cpu.FCR31 = (uint)cpu.GPR[i.RT];
CO = ControlStatus.C;
if ((ControlStatus.Cause & ControlStatus.Enables) != 0)
{
ExceptionProcessing.FloatingPoint(cpu);
return;
}
break;
default:
ExceptionProcessing.ReservedInstruction(cpu, i);
return;
}
}
};
}
#endregion
#region Methods
public void Run(Instruction instruction)
{
if (operations.TryGetValue((OpCode)instruction.RS, out var operation))
operation(instruction);
else
ExceptionProcessing.ReservedInstruction(cpu, instruction);
}
#endregion
}
}
}

View File

@ -71,6 +71,8 @@
}
public static void CoprocessorUnusable(VR4300 cpu, byte unit) => HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.CpU, unit);
public static void FloatingPoint(VR4300 cpu) => HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.FPE);
#endregion
}
}

View File

@ -56,12 +56,12 @@ namespace DotN64.CPU
/// <summary>
/// 32-bit floating-point Implementation/Revision register.
/// </summary>
public float FCR0 { get; set; }
public uint FCR0 { get; set; }
/// <summary>
/// 32-bit floating-point Control/Status register.
/// </summary>
public float FCR31 { get; set; }
public uint FCR31 { get; set; }
private byte divMode;
/// <summary>
@ -121,6 +121,8 @@ namespace DotN64.CPU
public SystemControlUnit CP0 => COP[0] as SystemControlUnit;
public FloatingPointUnit CP1 => COP[1] as FloatingPointUnit;
public ulong? DelaySlot { get; private set; }
#endregion
@ -128,6 +130,7 @@ namespace DotN64.CPU
public VR4300()
{
COP[0] = new SystemControlUnit(this);
COP[1] = new FloatingPointUnit(this);
operations = new Dictionary<Instruction, Action<Instruction>>
{
[Instruction.FromOpCode(OpCode.LUI)] = i => GPR[i.RT] = (ulong)(i.Immediate << 16),
@ -206,7 +209,7 @@ namespace DotN64.CPU
{
var unit = instruction.COPz.Value;
if (((byte)CP0.Status.CU & 1 << unit) != 0 || (unit == 0 && CP0.Status.KSU == SystemControlUnit.StatusRegister.Mode.Kernel))
if (CP0.IsCoprocessorUsable(unit))
COP[unit].Run(instruction);
else
ExceptionProcessing.CoprocessorUnusable(this, unit);

View File

@ -0,0 +1,14 @@
namespace DotN64
{
public partial class Cartridge
{
public enum CountryCode : byte
{
Germany = 0x44,
USA = 0x45,
Japan = 0x4A,
Europe = 0x50,
Australia = 0x55
}
}
}

View File

@ -0,0 +1,12 @@
namespace DotN64
{
public partial class Cartridge
{
public enum MediaFormat
{
Cartridge = 'N',
Disk = 'D',
ExpandableCartridge = 'E'
}
}
}

View File

@ -1,14 +1,17 @@
using System;
using System.IO;
using System.Net;
using System.Text;
namespace DotN64
{
using Helpers;
public class Cartridge
public partial class Cartridge
{
#region Fields
public const ushort HeaderSize = 0x40, BootstrapSize = 0x1000 - HeaderSize;
#endregion
#region Properties
public byte[] ROM { get; set; }
@ -26,11 +29,13 @@ namespace DotN64
public string ImageName => Encoding.ASCII.GetString(ROM, 0x20, 0x34 - 0x20);
public byte ManufacturerID => ROM[0x3B];
public MediaFormat Format => (MediaFormat)BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x38));
public ushort ID => (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(ROM, 0x3C));
public ushort ID => BitHelper.FromBigEndian(BitConverter.ToUInt16(ROM, 0x3C));
public byte CountryCode => ROM[0x3E];
public CountryCode Country => (CountryCode)ROM[0x3E];
public byte Version => ROM[0x3F];
#endregion
#region Methods

View File

@ -33,6 +33,9 @@
case VR4300.OpCode.COP0:
opCode = ((VR4300.SystemControlUnit.OpCode)instruction.RS).ToString(); // Not accounting for RT, Func...
break;
case VR4300.OpCode.COP1:
opCode = ((VR4300.FloatingPointUnit.OpCode)instruction.RS).ToString();
break;
}
return $"{instruction}.{opCode}";
@ -103,8 +106,9 @@
case VR4300.SystemControlUnit.OpCode.MT:
case VR4300.SystemControlUnit.OpCode.MF:
return Format(instruction, FormatRegister(instruction.RT, cpu), FormatCP0Register(instruction.RD, cpu));
default:
return FormatOpCode(instruction);
}
break;
}
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RS, cpu), FormatRegister(instruction.RT, cpu));
@ -114,6 +118,8 @@
/// Jump type.
/// </summary>
public static string J(VR4300.Instruction instruction, VR4300 cpu) => Format(instruction, $"0x{instruction.Target:X8}");
public static string Unknown(VR4300.Instruction instruction) => FormatOpCode(instruction);
#endregion
}
}

View File

@ -178,7 +178,7 @@ namespace DotN64.Diagnostics
#endregion
#region Methods
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 string Disassemble(VR4300.Instruction instruction, bool withRegisterContents) => operationFormats.TryGetValue(instruction.ToOpCode(), out var format) && format != null ? format(instruction, withRegisterContents ? nintendo64.CPU : null) : InstructionFormat.Unknown(instruction);
private void Disassemble(ulong? address = null, bool withRegisterContents = true)
{

View File

@ -84,6 +84,14 @@
<Compile Include="CPU\VR4300\VR4300.ExceptionProcessing.cs" />
<Compile Include="CPU\VR4300\VR4300.ICoprocessor.cs" />
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.OpCode.cs" />
<Compile Include="CPU\VR4300\CP1\VR4300.FloatingPointUnit.cs" />
<Compile Include="PIF\PeripheralInterface.TVType.cs" />
<Compile Include="Cartridge.MediaFormat.cs" />
<Compile Include="Cartridge.CountryCode.cs" />
<Compile Include="CPU\VR4300\CP1\VR4300.FloatingPointUnit.OpCode.cs" />
<Compile Include="CPU\VR4300\CP1\VR4300.FloatingPointUnit.Register.cs" />
<Compile Include="CPU\VR4300\CP1\VR4300.FloatingPointUnit.ImplementationRevisionRegister.cs" />
<Compile Include="CPU\VR4300\CP1\VR4300.FloatingPointUnit.ControlStatusRegister.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />
@ -103,6 +111,7 @@
<Folder Include="Helpers\" />
<Folder Include="PIF\" />
<Folder Include="RDRAM\" />
<Folder Include="CPU\VR4300\CP1\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -35,9 +35,15 @@ namespace DotN64.Helpers
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort FromBigEndian(ushort value) => (ushort)IPAddress.NetworkToHostOrder((short)value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint FromBigEndian(uint value) => (uint)IPAddress.NetworkToHostOrder((int)value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ToBigEndian(ushort value) => (ushort)IPAddress.HostToNetworkOrder((short)value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ToBigEndian(uint value) => (uint)IPAddress.HostToNetworkOrder((int)value);
#endregion

View File

@ -35,7 +35,7 @@
return c ^ 0xFFFFFFFF;
}
public static uint GetSeed(byte[] data, int offset = 0x40, int length = 0x1000 - 0x40)
public static uint GetSeed(byte[] data, int offset = Cartridge.HeaderSize, int length = Cartridge.BootstrapSize)
{
switch (ComputeCRC32(data, offset, length))
{

View File

@ -0,0 +1,12 @@
namespace DotN64
{
public partial class PeripheralInterface
{
private enum TVType : byte
{
PAL = 0,
NTSC = 1,
MPAL = 2
}
}
}

View File

@ -95,14 +95,14 @@ namespace DotN64
if (DeviceStateFlags.ROM == DeviceState.ROMType.Cartridge && nintendo64.Cartridge != null)
{
for (int i = 0x40; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
for (int i = Cartridge.HeaderSize; i < Cartridge.HeaderSize + Cartridge.BootstrapSize; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
{
BitHelper.Write(nintendo64.RCP.SP.DMEM, i, BitHelper.FromBigEndian(BitConverter.ToUInt32(nintendo64.Cartridge.ROM, i)));
}
}
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.S3] = (ulong)DeviceStateFlags.ROM;
// TODO: $s4 is the video mode (0 being PAL settings). This value is hard-coded into regional boot ROMs.
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.S4] = (ulong)GetRegion();
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.S5] = (ulong)DeviceStateFlags.Reset;
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.S6] = DeviceStateFlags.IPL3Seed;
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.S7] = DeviceStateFlags.Version;
@ -115,15 +115,33 @@ namespace DotN64
nintendo64.CPU.GPR[8] = 0xC0;
nintendo64.CPU.GPR[10] = 0x40;
nintendo64.CPU.GPR[11] = 0xFFFFFFFFA4000040;
nintendo64.CPU.GPR[20] = 0x1;
nintendo64.CPU.GPR[29] = 0xFFFFFFFFA4001FF0;
nintendo64.CPU.GPR[31] = 0xFFFFFFFFA4001550;
nintendo64.CPU.PC = 0xFFFFFFFFA4000040;
}
/// <summary>
/// Determines the console region from the loaded cartridge's header.
/// </summary>
private TVType GetRegion()
{
switch (nintendo64.Cartridge?.Country)
{
case Cartridge.CountryCode.Europe:
case Cartridge.CountryCode.Germany:
case Cartridge.CountryCode.Australia:
return TVType.PAL;
case Cartridge.CountryCode.Japan:
case Cartridge.CountryCode.USA:
return TVType.NTSC;
default:
return 0;
}
}
public void Reset()
{
if (nintendo64.Cartridge?.ROM.Length >= 0x1000)
if (nintendo64.Cartridge?.ROM.Length >= Cartridge.HeaderSize + Cartridge.BootstrapSize)
DeviceStateFlags = CIC.GetSeed(nintendo64.Cartridge.ROM);
if (BootROM == null)