From 6938988427e7f96adcd8fe76fe5d0a19b014b2b2 Mon Sep 17 00:00:00 2001
From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com>
Date: Thu, 13 Aug 2020 07:34:02 +0200
Subject: [PATCH] Fix Vcvt_FI & Vcvt_RM; Add Vfma_S & Vfms_S. Add Tests.
 (#1471)

* Fix Vcvt_FI & Vcvt_RM; Add Vfma_S & Vfms_S. Add Tests.

* Address PR feedback & Nit.
---
 ARMeilleure/Decoders/OpCode32SimdCvtFI.cs     |  14 +-
 ARMeilleure/Decoders/OpCode32SimdS.cs         |   9 +-
 ARMeilleure/Decoders/OpCodeTable.cs           |  12 +-
 .../Instructions/InstEmitSimdArithmetic32.cs  |  38 ++-
 ARMeilleure/Instructions/InstEmitSimdCvt32.cs |  16 +-
 .../Instructions/InstEmitSimdHelper32.cs      |   2 +-
 .../Instructions/InstEmitSimdMove32.cs        |   2 +-
 ARMeilleure/Instructions/InstName.cs          |   2 +
 ARMeilleure/Translation/PTC/Ptc.cs            |   2 +-
 .../Ryujinx.Memory.Tests.csproj               |   6 +-
 .../Ryujinx.Tests.Unicorn.csproj              |   4 -
 Ryujinx.Tests/Cpu/CpuTest.cs                  |   4 +
 Ryujinx.Tests/Cpu/CpuTest32.cs                |   5 +
 Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs         | 220 ++++++++++++++++++
 Ryujinx.Tests/Ryujinx.Tests.csproj            |   4 +-
 15 files changed, 309 insertions(+), 31 deletions(-)
 create mode 100644 Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs

diff --git a/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs b/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs
index aaedcb3c..b654a192 100644
--- a/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs
+++ b/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs
@@ -2,12 +2,20 @@
 {
     class OpCode32SimdCvtFI : OpCode32SimdS
     {
-        public int Opc2 { get; private set; }
-
         public OpCode32SimdCvtFI(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
         {
-            Opc2 = (opCode >> 16) & 0x7;
             Opc = (opCode >> 7) & 0x1;
+
+            bool toInteger = (Opc2 & 0b100) != 0;
+
+            if (toInteger)
+            {
+                Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e);
+            }
+            else
+            {
+                Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e);
+            }
         }
     }
 }
diff --git a/ARMeilleure/Decoders/OpCode32SimdS.cs b/ARMeilleure/Decoders/OpCode32SimdS.cs
index 2e860d9c..766cf4ba 100644
--- a/ARMeilleure/Decoders/OpCode32SimdS.cs
+++ b/ARMeilleure/Decoders/OpCode32SimdS.cs
@@ -2,14 +2,17 @@
 {
     class OpCode32SimdS : OpCode32, IOpCode32Simd
     {
-        public int Vd { get; private set; }
-        public int Vm { get; private set; }
-        public int Opc { get; protected set; }
+        public int Vd { get; protected set; }
+        public int Vm { get; protected set; }
+        public int Opc { get; protected set; } // "with_zero" (Opc<1>) [Vcmp, Vcmpe].
+        public int Opc2 { get; private set; } // opc2 or RM (opc2<1:0>) [Vcvt, Vrint].
         public int Size { get; protected set; }
 
         public OpCode32SimdS(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
         {
             Opc = (opCode >> 15) & 0x3;
+            Opc2 = (opCode >> 16) & 0x7;
+
             Size = (opCode >> 8) & 0x3;
 
             bool single = Size != 3;
diff --git a/ARMeilleure/Decoders/OpCodeTable.cs b/ARMeilleure/Decoders/OpCodeTable.cs
index bbcc15ba..4daccfdb 100644
--- a/ARMeilleure/Decoders/OpCodeTable.cs
+++ b/ARMeilleure/Decoders/OpCodeTable.cs
@@ -825,15 +825,17 @@ namespace ARMeilleure.Decoders
             SetA32("<<<<11101x11010xxxxx101x01x0xxxx", InstName.Vcmp,     InstEmit32.Vcmp,     typeof(OpCode32SimdS));
             SetA32("<<<<11101x11010xxxxx101x11x0xxxx", InstName.Vcmpe,    InstEmit32.Vcmpe,    typeof(OpCode32SimdS));
             SetA32("<<<<11101x110111xxxx101x11x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_FD,  typeof(OpCode32SimdS)); // FP 32 and 64, scalar.
-            SetA32("<<<<11101x11110xxxxx10xx11x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_FI,  typeof(OpCode32SimdCvtFI)); // FP32 to int.
-            SetA32("<<<<11101x111000xxxx10xxx1x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_FI,  typeof(OpCode32SimdCvtFI)); // Int to FP32.
-            SetA32("111111101x1111xxxxxx10>>x1x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_R,   typeof(OpCode32SimdCvtFI)); // The many FP32 to int encodings (fp).
+            SetA32("<<<<11101x11110xxxxx101x11x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_FI,  typeof(OpCode32SimdCvtFI)); // FP32 to int.
+            SetA32("<<<<11101x111000xxxx101xx1x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_FI,  typeof(OpCode32SimdCvtFI)); // Int to FP32.
+            SetA32("111111101x1111xxxxxx101xx1x0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_RM,  typeof(OpCode32SimdCvtFI)); // The many FP32 to int encodings (fp).
             SetA32("111100111x111011xxxx011xxxx0xxxx", InstName.Vcvt,     InstEmit32.Vcvt_V,   typeof(OpCode32SimdCmpZ)); // FP and integer, vector.
             SetA32("<<<<11101x00xxxxxxxx101xx0x0xxxx", InstName.Vdiv,     InstEmit32.Vdiv_S,   typeof(OpCode32SimdRegS));
             SetA32("<<<<11101xx0xxxxxxxx1011x0x10000", InstName.Vdup,     InstEmit32.Vdup,     typeof(OpCode32SimdDupGP));
             SetA32("111100111x11xxxxxxxx11000xx0xxxx", InstName.Vdup,     InstEmit32.Vdup_1,   typeof(OpCode32SimdDupElem));
             SetA32("111100110x00xxxxxxxx0001xxx1xxxx", InstName.Veor,     InstEmit32.Veor_I,   typeof(OpCode32SimdBinary));
             SetA32("111100101x11xxxxxxxxxxxxxxx0xxxx", InstName.Vext,     InstEmit32.Vext,     typeof(OpCode32SimdExt));
+            SetA32("<<<<11101x10xxxxxxxx101xx0x0xxxx", InstName.Vfma,     InstEmit32.Vfma_S,   typeof(OpCode32SimdRegS));
+            SetA32("<<<<11101x10xxxxxxxx101xx1x0xxxx", InstName.Vfms,     InstEmit32.Vfms_S,   typeof(OpCode32SimdRegS));
             SetA32("111101001x10xxxxxxxxxx00xxxxxxxx", InstName.Vld1,     InstEmit32.Vld1,     typeof(OpCode32SimdMemSingle));
             SetA32("111101000x10xxxxxxxx0111xxxxxxxx", InstName.Vld1,     InstEmit32.Vld1,     typeof(OpCode32SimdMemPair)); // Regs = 1.
             SetA32("111101000x10xxxxxxxx1010xxxxxxxx", InstName.Vld1,     InstEmit32.Vld1,     typeof(OpCode32SimdMemPair)); // Regs = 2.
@@ -918,8 +920,8 @@ namespace ARMeilleure.Decoders
             SetA32("111100111x111011xxxx010x0xx0xxxx", InstName.Vrecpe,   InstEmit32.Vrecpe,   typeof(OpCode32SimdSqrte));
             SetA32("111100100x00xxxxxxxx1111xxx1xxxx", InstName.Vrecps,   InstEmit32.Vrecps,   typeof(OpCode32SimdReg));
             SetA32("111100111x11xx00xxxx000<<xx0xxxx", InstName.Vrev,     InstEmit32.Vrev,     typeof(OpCode32SimdRev));
-            SetA32("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint,    InstEmit32.Vrint_RM, typeof(OpCode32SimdCvtFI));
-            SetA32("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint,    InstEmit32.Vrint_Z,  typeof(OpCode32SimdCvtFI));
+            SetA32("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint,    InstEmit32.Vrint_RM, typeof(OpCode32SimdS));
+            SetA32("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint,    InstEmit32.Vrint_Z,  typeof(OpCode32SimdS));
             SetA32("1111001x1x>>>xxxxxxx0010>xx1xxxx", InstName.Vrshr,    InstEmit32.Vrshr,    typeof(OpCode32SimdShImm));
             SetA32("111100111x111011xxxx010x1xx0xxxx", InstName.Vrsqrte,  InstEmit32.Vrsqrte,  typeof(OpCode32SimdSqrte));
             SetA32("111100100x10xxxxxxxx1111xxx1xxxx", InstName.Vrsqrts,  InstEmit32.Vrsqrts,  typeof(OpCode32SimdReg));
diff --git a/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
index f7f3d47e..57176794 100644
--- a/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
+++ b/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
@@ -231,6 +231,38 @@ namespace ARMeilleure.Instructions
             }
         }
 
+        public static void Vfma_S(ArmEmitterContext context) // Fused.
+        {
+            if (Optimizations.FastFP && Optimizations.UseSse2)
+            {
+                // TODO: Use FMA instruction set.
+                EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd);
+            }
+            else
+            {
+                EmitScalarTernaryOpF32(context, (op1, op2, op3) =>
+                {
+                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3);
+                });
+            }
+        }
+
+        public static void Vfms_S(ArmEmitterContext context) // Fused.
+        {
+            if (Optimizations.FastFP && Optimizations.UseSse2)
+            {
+                // TODO: Use FMA instruction set.
+                EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd);
+            }
+            else
+            {
+                EmitScalarTernaryOpF32(context, (op1, op2, op3) =>
+                {
+                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3);
+                });
+            }
+        }
+
         public static void Vmov_S(ArmEmitterContext context)
         {
             if (Optimizations.FastFP && Optimizations.UseSse2)
@@ -586,7 +618,8 @@ namespace ARMeilleure.Instructions
             {
                 EmitScalarTernaryOpF32(context, (op1, op2, op3) =>
                 {
-                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3);
+                    Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3);
+                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, res);
                 });
             }
         }
@@ -657,7 +690,8 @@ namespace ARMeilleure.Instructions
             {
                 EmitScalarTernaryOpF32(context, (op1, op2, op3) =>
                 {
-                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3);
+                    Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3);
+                    return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, res);
                 });
             }
         }
diff --git a/ARMeilleure/Instructions/InstEmitSimdCvt32.cs b/ARMeilleure/Instructions/InstEmitSimdCvt32.cs
index 00b8ffd6..e4efea70 100644
--- a/ARMeilleure/Instructions/InstEmitSimdCvt32.cs
+++ b/ARMeilleure/Instructions/InstEmitSimdCvt32.cs
@@ -139,6 +139,7 @@ namespace ARMeilleure.Instructions
             }
         }
 
+        // VCVT (floating-point to integer, floating-point) | VCVT (integer to floating-point, floating-point).
         public static void Vcvt_FI(ArmEmitterContext context)
         {
             OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
@@ -236,13 +237,14 @@ namespace ARMeilleure.Instructions
             return roundMode;
         }
 
-        public static void Vcvt_R(ArmEmitterContext context)
+        // VCVTA/M/N/P (floating-point).
+        public static void Vcvt_RM(ArmEmitterContext context)
         {
-            OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
+            OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; // toInteger == true (opCode<18> == 1 => Opc2<2> == 1).
 
             OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32;
 
-            bool unsigned = (op.Opc & 1) == 0;
+            bool unsigned = op.Opc == 0;
             int rm = op.Opc2 & 3;
 
             if (Optimizations.UseSse41 && rm != 0b00)
@@ -277,9 +279,10 @@ namespace ARMeilleure.Instructions
             }
         }
 
+        // VRINTA/M/N/P (floating-point).
         public static void Vrint_RM(ArmEmitterContext context)
         {
-            OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
+            OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
 
             OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32;
 
@@ -320,9 +323,10 @@ namespace ARMeilleure.Instructions
             }
         }
 
+        // VRINTZ (floating-point).
         public static void Vrint_Z(ArmEmitterContext context)
         {
-            IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
+            OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
 
             if (Optimizations.UseSse2)
             {
@@ -355,7 +359,7 @@ namespace ARMeilleure.Instructions
         private static void EmitSse41ConvertInt32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed)
         {
             // A port of the similar round function in InstEmitSimdCvt.
-            OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
+            OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
 
             bool doubleSize = (op.Size & 1) != 0;
             int shift = doubleSize ? 1 : 2;
diff --git a/ARMeilleure/Instructions/InstEmitSimdHelper32.cs b/ARMeilleure/Instructions/InstEmitSimdHelper32.cs
index e045c601..a962c0fc 100644
--- a/ARMeilleure/Instructions/InstEmitSimdHelper32.cs
+++ b/ARMeilleure/Instructions/InstEmitSimdHelper32.cs
@@ -906,7 +906,7 @@ namespace ARMeilleure.Instructions
             OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp;
 
             bool doubleSize = (op.Size & 1) != 0;
-            int shift = doubleSize ? 1 : 2;
+
             Intrinsic inst1 = doubleSize ? inst64pt1 : inst32pt1;
             Intrinsic inst2 = doubleSize ? inst64pt2 : inst32pt2;
 
diff --git a/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/ARMeilleure/Instructions/InstEmitSimdMove32.cs
index b484381f..52292242 100644
--- a/ARMeilleure/Instructions/InstEmitSimdMove32.cs
+++ b/ARMeilleure/Instructions/InstEmitSimdMove32.cs
@@ -559,7 +559,7 @@ namespace ARMeilleure.Instructions
             }
         }
 
-        public static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func<Operand, Operand, (Operand, Operand)> shuffleFunc)
+        private static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func<Operand, Operand, (Operand, Operand)> shuffleFunc)
         {
             OpCode32Simd op = (OpCode32Simd)context.CurrOp;
 
diff --git a/ARMeilleure/Instructions/InstName.cs b/ARMeilleure/Instructions/InstName.cs
index d7283029..9e820f6b 100644
--- a/ARMeilleure/Instructions/InstName.cs
+++ b/ARMeilleure/Instructions/InstName.cs
@@ -563,6 +563,8 @@ namespace ARMeilleure.Instructions
         Vdup,
         Veor,
         Vext,
+        Vfma,
+        Vfms,
         Vld1,
         Vld2,
         Vld3,
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index 2ff98f85..ccb3f705 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -20,7 +20,7 @@ namespace ARMeilleure.Translation.PTC
     {
         private const string HeaderMagic = "PTChd";
 
-        private const int InternalVersion = 20; //! To be incremented manually for each change to the ARMeilleure project.
+        private const int InternalVersion = 1471; //! To be incremented manually for each change to the ARMeilleure project.
 
         private const string BaseDir = "Ryujinx";
 
diff --git a/Ryujinx.Memory.Tests/Ryujinx.Memory.Tests.csproj b/Ryujinx.Memory.Tests/Ryujinx.Memory.Tests.csproj
index bc916ea2..c7c15185 100644
--- a/Ryujinx.Memory.Tests/Ryujinx.Memory.Tests.csproj
+++ b/Ryujinx.Memory.Tests/Ryujinx.Memory.Tests.csproj
@@ -7,9 +7,9 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="nunit" Version="3.12.0" />
-    <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+    <PackageReference Include="NUnit" Version="3.12.0" />
+    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
index 600f0b03..efd84a08 100644
--- a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
+++ b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
@@ -22,8 +22,4 @@
     <Optimize>false</Optimize>
   </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
-  </ItemGroup>
-
 </Project>
diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs
index 12afcfc7..e0778e05 100644
--- a/Ryujinx.Tests/Cpu/CpuTest.cs
+++ b/Ryujinx.Tests/Cpu/CpuTest.cs
@@ -205,7 +205,9 @@ namespace Ryujinx.Tests.Cpu
         {
             if (Ignore_FpcrFz_FpcrDn)
             {
+#pragma warning disable CS0162
                 fpcr &= ~((1 << (int)Fpcr.Fz) | (1 << (int)Fpcr.Dn));
+#pragma warning restore CS0162
             }
 
             Opcode(opcode);
@@ -319,7 +321,9 @@ namespace Ryujinx.Tests.Cpu
 
             if (IgnoreAllExcept_FpsrQc)
             {
+#pragma warning disable CS0162
                 fpsrMask &= Fpsr.Qc;
+#pragma warning restore CS0162
             }
 
             if (fpSkips != FpSkips.None)
diff --git a/Ryujinx.Tests/Cpu/CpuTest32.cs b/Ryujinx.Tests/Cpu/CpuTest32.cs
index 3f457193..ca966840 100644
--- a/Ryujinx.Tests/Cpu/CpuTest32.cs
+++ b/Ryujinx.Tests/Cpu/CpuTest32.cs
@@ -470,6 +470,11 @@ namespace Ryujinx.Tests.Cpu
 
         protected static V128 MakeVectorE0E1(ulong e0, ulong e1) => new V128(e0, e1);
 
+        protected static V128 MakeVectorE0E1E2E3(uint e0, uint e1, uint e2, uint e3)
+        {
+            return new V128(e0, e1, e2, e3);
+        }
+
         protected static ulong GetVectorE0(V128 vector) => vector.Extract<ulong>(0);
         protected static ulong GetVectorE1(V128 vector) => vector.Extract<ulong>(1);
 
diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs
new file mode 100644
index 00000000..c25f2fa2
--- /dev/null
+++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs
@@ -0,0 +1,220 @@
+#define SimdCvt32
+
+using ARMeilleure.State;
+using NUnit.Framework;
+using System.Collections.Generic;
+
+namespace Ryujinx.Tests.Cpu
+{
+    [Category("SimdCvt32")]
+    public sealed class CpuTestSimdCvt32 : CpuTest32
+    {
+#if SimdCvt32
+
+#region "ValueSource (Opcodes)"
+#endregion
+
+#region "ValueSource (Types)"
+        private static uint[] _1S_()
+        {
+            return new uint[] { 0x00000000u, 0x7FFFFFFFu,
+                                0x80000000u, 0xFFFFFFFFu };
+        }
+
+        private static IEnumerable<uint> _1S_F_()
+        {
+            yield return 0xFF7FFFFFu; // -Max Normal    (float.MinValue)
+            yield return 0x80800000u; // -Min Normal
+            yield return 0x807FFFFFu; // -Max Subnormal
+            yield return 0x80000001u; // -Min Subnormal (-float.Epsilon)
+            yield return 0x7F7FFFFFu; // +Max Normal    (float.MaxValue)
+            yield return 0x00800000u; // +Min Normal
+            yield return 0x007FFFFFu; // +Max Subnormal
+            yield return 0x00000001u; // +Min Subnormal (float.Epsilon)
+
+            if (!NoZeros)
+            {
+                yield return 0x80000000u; // -Zero
+                yield return 0x00000000u; // +Zero
+            }
+
+            if (!NoInfs)
+            {
+                yield return 0xFF800000u; // -Infinity
+                yield return 0x7F800000u; // +Infinity
+            }
+
+            if (!NoNaNs)
+            {
+                yield return 0xFFC00000u; // -QNaN (all zeros payload) (float.NaN)
+                yield return 0xFFBFFFFFu; // -SNaN (all ones  payload)
+                yield return 0x7FC00000u; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN)
+                yield return 0x7FBFFFFFu; // +SNaN (all ones  payload)
+            }
+
+            for (int cnt = 1; cnt <= RndCnt; cnt++)
+            {
+                yield return GenNormalS();
+                yield return GenSubnormalS();
+            }
+        }
+
+        private static IEnumerable<ulong> _1D_F_()
+        {
+            yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal    (double.MinValue)
+            yield return 0x8010000000000000ul; // -Min Normal
+            yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal
+            yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon)
+            yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal    (double.MaxValue)
+            yield return 0x0010000000000000ul; // +Min Normal
+            yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal
+            yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon)
+
+            if (!NoZeros)
+            {
+                yield return 0x8000000000000000ul; // -Zero
+                yield return 0x0000000000000000ul; // +Zero
+            }
+
+            if (!NoInfs)
+            {
+                yield return 0xFFF0000000000000ul; // -Infinity
+                yield return 0x7FF0000000000000ul; // +Infinity
+            }
+
+            if (!NoNaNs)
+            {
+                yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN)
+                yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones  payload)
+                yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN)
+                yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones  payload)
+            }
+
+            for (int cnt = 1; cnt <= RndCnt; cnt++)
+            {
+                yield return GenNormalD();
+                yield return GenSubnormalD();
+            }
+        }
+#endregion
+
+        private const int RndCnt = 2;
+
+        private static readonly bool NoZeros = false;
+        private static readonly bool NoInfs  = false;
+        private static readonly bool NoNaNs  = false;
+
+        [Explicit]
+        [Test, Pairwise, Description("VCVT.<dt>.F32 <Sd>, <Sm>")]
+        public void Vcvt_F32_I32([Values(0u, 1u, 2u, 3u)] uint rd,
+                                 [Values(0u, 1u, 2u, 3u)] uint rm,
+                                 [ValueSource(nameof(_1S_F_))] uint s0,
+                                 [ValueSource(nameof(_1S_F_))] uint s1,
+                                 [ValueSource(nameof(_1S_F_))] uint s2,
+                                 [ValueSource(nameof(_1S_F_))] uint s3,
+                                 [Values] bool unsigned) // <U32, S32>
+        {
+            uint opcode = 0xeebc0ac0u; // VCVT.U32.F32 S0, S0
+
+            if (!unsigned)
+            {
+                opcode |= 1 << 16; // opc2<0>
+            }
+
+            opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22);
+            opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5);
+
+            V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3);
+
+            SingleOpcode(opcode, v0: v0);
+
+            CompareAgainstUnicorn();
+        }
+
+        [Explicit]
+        [Test, Pairwise, Description("VCVT.<dt>.F64 <Sd>, <Dm>")]
+        public void Vcvt_F64_I32([Values(0u, 1u, 2u, 3u)] uint rd,
+                                 [Values(0u, 1u)] uint rm,
+                                 [ValueSource(nameof(_1D_F_))] ulong d0,
+                                 [ValueSource(nameof(_1D_F_))] ulong d1,
+                                 [Values] bool unsigned) // <U32, S32>
+        {
+            uint opcode = 0xeebc0bc0u; // VCVT.U32.F64 S0, D0
+
+            if (!unsigned)
+            {
+                opcode |= 1 << 16; // opc2<0>
+            }
+
+            opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22);
+            opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
+
+            V128 v0 = MakeVectorE0E1(d0, d1);
+
+            SingleOpcode(opcode, v0: v0);
+
+            CompareAgainstUnicorn();
+        }
+
+        [Explicit]
+        [Test, Pairwise, Description("VCVT.F32.<dt> <Sd>, <Sm>")]
+        public void Vcvt_I32_F32([Values(0u, 1u, 2u, 3u)] uint rd,
+                                 [Values(0u, 1u, 2u, 3u)] uint rm,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s0,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s1,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s2,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s3,
+                                 [Values] bool unsigned, // <U32, S32>
+                                 [Values(RMode.Rn)] RMode rMode)
+        {
+            uint opcode = 0xeeb80a40u; // VCVT.F32.U32 S0, S0
+
+            if (!unsigned)
+            {
+                opcode |= 1 << 7; // op
+            }
+
+            opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5);
+            opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22);
+
+            V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3);
+
+            int fpscr = (int)rMode << (int)Fpcr.RMode;
+
+            SingleOpcode(opcode, v0: v0, fpscr: fpscr);
+
+            CompareAgainstUnicorn();
+        }
+
+        [Explicit]
+        [Test, Pairwise, Description("VCVT.F64.<dt> <Dd>, <Sm>")]
+        public void Vcvt_I32_F64([Values(0u, 1u)] uint rd,
+                                 [Values(0u, 1u, 2u, 3u)] uint rm,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s0,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s1,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s2,
+                                 [ValueSource(nameof(_1S_))] [Random(RndCnt)] uint s3,
+                                 [Values] bool unsigned, // <U32, S32>
+                                 [Values(RMode.Rn)] RMode rMode)
+        {
+            uint opcode = 0xeeb80b40u; // VCVT.F64.U32 D0, S0
+
+            if (!unsigned)
+            {
+                opcode |= 1 << 7; // op
+            }
+
+            opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5);
+            opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
+
+            V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3);
+
+            int fpscr = (int)rMode << (int)Fpcr.RMode;
+
+            SingleOpcode(opcode, v0: v0, fpscr: fpscr);
+
+            CompareAgainstUnicorn();
+        }
+#endif
+    }
+}
diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj
index 67be33c4..ff8e855e 100644
--- a/Ryujinx.Tests/Ryujinx.Tests.csproj
+++ b/Ryujinx.Tests/Ryujinx.Tests.csproj
@@ -28,9 +28,9 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
     <PackageReference Include="NUnit" Version="3.12.0" />
-    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
+    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
   </ItemGroup>
 
   <ItemGroup>