From a69e8a6f77addbfd74bf072e629384a5ccc191f5 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 21 Oct 2018 18:46:59 +0100 Subject: [PATCH] Implement Fcvtn (single->half) --- ChocolArm64/Instruction/AInstEmitSimdCvt.cs | 10 ++-- ChocolArm64/Instruction/ASoftFloat.cs | 61 +++++++++++++++++++++ Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs | 29 ++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs index 76d984a23b..eb1cecf0d5 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs @@ -122,14 +122,14 @@ namespace ChocolArm64.Instruction for (int Index = 0; Index < Elems; Index++) { - EmitVectorExtractF(Context, Op.Rd, Index, SizeF); + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); if (SizeF == 0) { - //TODO: This need the half precision floating point type, - //that is not yet supported on .NET. We should probably - //do our own implementation on the meantime. - throw new NotImplementedException(); + Context.Emit(OpCodes.Ldc_I4_0); + Context.EmitCall(typeof(ASoftFloat), nameof(ASoftFloat.ConvertSingleToHalf)); + + EmitVectorInsert(Context, Op.Rd, Part + Index, 1); } else /* if (SizeF == 1) */ { diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs index 2d9a9f0ebe..a8383b43a7 100644 --- a/ChocolArm64/Instruction/ASoftFloat.cs +++ b/ChocolArm64/Instruction/ASoftFloat.cs @@ -231,6 +231,67 @@ namespace ChocolArm64.Instruction uint new_exp = (uint)((exponent + 127) & 0xFF) << 23; return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | new_exp | (x_mantissa << 13))); } + + public static ushort ConvertSingleToHalf(float op1, bool alt_hp) + { + uint op1_bits = (uint)BitConverter.SingleToInt32Bits(op1); + + uint sign = (op1_bits >> 31) & 1; + uint exp = (op1_bits >> 23) & 0xFF; + uint mantissa = op1_bits & ((1u << 23) - 1); + + // Convert from biased-single to biased-half precision + int new_exp = ((int)exp - 127) + 15; + ushort new_mantissa = (ushort)(mantissa >> 13); + + if (exp == 0xFF && mantissa != 0) + { + // NaN + if (alt_hp) + { + return (ushort)(sign << 15); + } + return (ushort)((sign << 15) | 0x7E00u | new_mantissa); + } + else if (exp == 0xFF && mantissa == 0) + { + // Infinity + if (alt_hp) + { + return (ushort)((sign << 15) | 0x7FFFu); + } + return (ushort)((sign << 15) | 0x7C00u); + } + else if (exp == 0 && mantissa == 0) + { + // Zero + return (ushort)(sign << 15); + } + + if (new_exp <= 0) + { + // Output a denormal + new_mantissa |= 0x0400; // explicit leading bit + new_mantissa >>= 1 - new_exp; + return (ushort)((sign << 15) | new_mantissa); + } + + // TODO: Rounding correction + // We currently assume truncation rounding + + if (new_exp >= 0x1F && !alt_hp) + { + // Output an infinity + return (ushort)((sign << 15) | 0x7C00u); + } + else if (new_exp >= 0x20 && alt_hp) + { + // Output a maximum value + return (ushort)((sign << 15) | 0x7FFFu); + } + + return (ushort)((sign << 15) | (ushort)((uint)new_exp << 10) | new_mantissa); + } } static class ASoftFloat_32 diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs index 3c8ad0711c..02c3395167 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -39,5 +39,34 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + + [TestCase((ushort)0x0000, 0x00000000u)] // Positive Zero + [TestCase((ushort)0x8000, 0x80000000u)] // Negative Zero + [TestCase((ushort)0x3E00, 0x3FC00000u)] // +1.5 + [TestCase((ushort)0xBE00, 0xBFC00000u)] // -1.5 + [TestCase((ushort)0xFFFF, 0xFFFFE000u)] // -QNaN + [TestCase((ushort)0x7C00, 0x7F800000u)] // +Inf + [TestCase((ushort)0x3C00, 0x3F800000u)] // 1.0 + [TestCase((ushort)0xC000, 0xC0000000u)] // -2.0 + [TestCase((ushort)0x7BFF, 0x477FE000u)] // 65504.0 (Largest Normal) + [TestCase((ushort)0x03FF, 0x387FC000u)] // 0.00006097555 (Largest Subnormal) + [TestCase((ushort)0x0001, 0x33800000u)] // 5.96046448e-8 (Smallest Subnormal) + public void Fcvtn_V_f16(ushort Result, uint Value) + { + uint Opcode = 0x0E216801; // FCVTN V1.4H, V0.4S + + Vector128 V0 = Sse.StaticCast(Sse2.SetAllVector128(Value)); + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0); + + Assert.Multiple(() => + { + Assert.That(Sse2.Extract(Sse.StaticCast(ThreadState.V1), (byte)0), Is.EqualTo(Result)); + Assert.That(Sse2.Extract(Sse.StaticCast(ThreadState.V1), (byte)1), Is.EqualTo(Result)); + Assert.That(Sse2.Extract(Sse.StaticCast(ThreadState.V1), (byte)2), Is.EqualTo(Result)); + Assert.That(Sse2.Extract(Sse.StaticCast(ThreadState.V1), (byte)3), Is.EqualTo(Result)); + }); + CompareAgainstUnicorn(); + } } }