/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2024, Paul Elder * * Fixed / Floating point utility tests */ #include "../src/ipa/libipa/fixedpoint.h" #include #include #include #include #include #include "test.h" using namespace std; using namespace libcamera; using namespace ipa; class FixedPointUtilsTest : public Test { protected: template int quantizedCheck(float input, typename Q::QuantizedType expected, float value) { Q q(input); using T = typename Q::QuantizedType; std::cout << " Checking " << input << " == " << q << std::endl; T quantized = q.quantized(); if (quantized != expected) { std::cout << " ** Q Expected " << input << " to quantize to " << utils::hex(expected) << ", got " << utils::hex(quantized) << " - (" << q << ")" << std::endl; return 1; } if ((std::abs(q.value() - value)) > 0.0001f) { std::cout << " ** V Expected " << input << " to quantize to " << value << ", got " << q.value() << " - (" << q << ")" << std::endl; return 1; } return 0; } template int signExtendCheck() { Q minValue(Q::TraitsType::min); using T = typename Q::QuantizedType; T minQuantized = minValue.quantized(); /* Take the value and expand it into a larger register */ RegisterType reg = static_cast(minValue.quantized()); std::cout << " Checking " << minValue << " == " << utils::hex(reg) << std::endl; /* * Ensure that the minimum negative value is not sign * extended when casting up to a larger register storage * type. */ if (reg != minQuantized) { std::cout << " ** Sign extension check failed for min value. Expected " << utils::hex(minQuantized) << ", got " << utils::hex(reg) << std::endl; return 1; } /* And we should be consistent when we convert back to Q */ Q q2(static_cast(reg)); if (!(q2 == minValue)) { std::cout << " ** Sign extension check failed for min value. Expected " << minValue << ", got " << q2 << std::endl; return 1; } return 0; } template void introduce(std::string_view type) { using T = typename Q::QuantizedType; std::cout << std::endl; std::cout << type << "(" << Q::TraitsType::min << " .. " << Q::TraitsType::max << ") " << " Min: " << Q(Q::TraitsType::min) << " -- Max: " << Q(Q::TraitsType::max) << " Step:" << Q(T(1)).value() << std::endl; } int testFixedPointQuantizers() { unsigned int fails = 0; /* * These aim to specifically test all the corner cases of the * quantization and de-quantization process. Including clamping * to min/max, zero points and making sure that steps are * correct. * * In particular test signed and unsigned types and a mix of the * highest bit width of a storage type and smaller widths that * require bit masking and sign extension. * * Note we must hard code these. Any calculation of expected * values risks replicating bugs in the implementation. * * As the underlying types are integer and float the limit of * precision is around 24 bits so we do not test wider types. */ /* clang-format off */ /* Q1.7(-1 .. 0.992188) Min: [0x80:-1] -- Max: [0x7f:0.992188] Step:0.0078125*/ introduce>("Q1.7"); fails += quantizedCheck>(-2.000f, 0b1'0000000, -1.0f); /* Clamped to Min */ fails += quantizedCheck>(-1.000f, 0b1'0000000, -1.0f); /* Min */ fails += quantizedCheck>(-0.992f, 0b1'0000001, -0.992188f); /* Min + 1 step */ fails += quantizedCheck>(-0.006f, 0b1'1111111, -0.0078125f); /* -1 step */ fails += quantizedCheck>( 0.000f, 0b0'0000000, 0.0f); /* Zero */ fails += quantizedCheck>( 0.008f, 0b0'0000001, 0.0078125f); /* +1 step */ fails += quantizedCheck>( 0.992f, 0b0'1111111, 0.992188f); /* Max */ fails += quantizedCheck>( 2.000f, 0b0'1111111, 0.992188f); /* Clamped to Max */ /* UQ1.7(0 .. 1.99219) Min: [0x00:0] -- Max: [0xff:1.99219] Step:0.0078125 */ introduce>("UQ1.7"); fails += quantizedCheck>(-1.0f, 0b0'0000000, 0.0f); /* Clamped to Min */ fails += quantizedCheck>( 0.0f, 0b0'0000000, 0.0f); /* Min / Zero */ fails += quantizedCheck>( 1.0f, 0b1'0000000, 1.0f); /* Mid */ fails += quantizedCheck>( 1.992f, 0b1'1111111, 1.99219f); /* Max */ fails += quantizedCheck>( 2.000f, 0b1'1111111, 1.99219f); /* Clamped to Max */ /* Q4.7(-8 .. 7.99219) Min: [0x0400:-8] -- Max: [0x03ff:7.99219] Step:0.0078125 */ introduce>("Q4.7"); fails += quantizedCheck>(-8.0f, 0b1000'0000000, -8.0f); /* Min */ fails += quantizedCheck>(-0.008f, 0b1111'1111111, -0.0078125); /* -1 step */ fails += quantizedCheck>( 0.0f, 0b0000'0000000, 0.0f); /* Zero */ fails += quantizedCheck>( 0.008f, 0b0000'0000001, 0.0078125f); /* +1 step */ fails += quantizedCheck>( 7.992f, 0b0111'1111111, 7.99219f); /* Max */ /* Retain additional tests from original testFixedPoint() */ fails += quantizedCheck>( 0.2f, 0b0000'0011010, 0.203125f); /* 0x01a */ fails += quantizedCheck>(-0.2f, 0b1111'1100110, -0.203125f); /* 0x7e6 */ fails += quantizedCheck>(-0.8f, 0b1111'0011010, -0.796875f); /* 0x79a */ fails += quantizedCheck>(-0.4f, 0b1111'1001101, -0.398438f); /* 0x7cd */ fails += quantizedCheck>(-1.4f, 0b1110'1001101, -1.39844f); /* 0x74d */ /* UQ4.8(0 .. 15.9961) Min: [0x0000:0] -- Max: [0x0fff:15.9961] Step:0.00390625 */ introduce>("UQ4.8"); fails += quantizedCheck>( 0.0f, 0b0000'00000000, 0.00f); fails += quantizedCheck>(16.0f, 0b1111'11111111, 15.9961f); /* Q5.4(-16 .. 15.9375) Min: [0x0100:-16] -- Max: [0x00ff:15.9375] Step:0.0625 */ introduce>("Q5.4"); fails += quantizedCheck>(-16.00f, 0b10000'0000, -16.00f); fails += quantizedCheck>( 15.94f, 0b01111'1111, 15.9375f); /* UQ5.8(0 .. 31.9961) Min: [0x0000:0] -- Max: [0x1fff:31.9961] Step:0.00390625 */ introduce>("UQ5.8"); fails += quantizedCheck>( 0.00f, 0b00000'00000000, 0.00f); fails += quantizedCheck>(32.00f, 0b11111'11111111, 31.9961f); /* Q12.4(-2048 .. 2047.94) Min: [0x8000:-2048] -- Max: [0x7fff:2047.94] Step:0.0625 */ introduce>("Q12.4"); fails += quantizedCheck>( 0.0f, 0b000000000000'0000, 0.0f); fails += quantizedCheck>( 7.5f, 0b000000000111'1000, 7.5f); /* UQ12.4(0 .. 4095.94) Min: [0x0000:0] -- Max: [0xffff:4095.94] Step:0.0625 */ introduce>("UQ12.4"); fails += quantizedCheck>(0.0f, 0b000000000000'0000, 0.0f); fails += quantizedCheck>(7.5f, 0b000000000111'1000, 7.5f); /* Q4.20 */ introduce>("Q4.20"); fails += quantizedCheck>( -9.0f, 0b1000'00000000000000000000, -8.0f); fails += quantizedCheck>( -8.0f, 0b1000'00000000000000000000, -8.0f); fails += quantizedCheck>( 8.0f, 0b0111'11111111111111111111, 8.0f); fails += quantizedCheck>( 9.0f, 0b0111'11111111111111111111, 8.0f); /* UQ4.20 */ introduce>("UQ4.20"); fails += quantizedCheck>(-1.0f, 0b0000'00000000000000000000, 0.0f); fails += quantizedCheck>( 0.0f, 0b0000'00000000000000000000, 0.0f); fails += quantizedCheck>(16.0f, 0b1111'11111111111111111111, 16.0f); fails += quantizedCheck>(20.0f, 0b1111'11111111111111111111, 16.0f); /* Storage selection tests */ static_assert(std::is_same_v::TraitsType::QuantizedType, uint8_t>); static_assert(std::is_same_v::TraitsType::QuantizedType, uint8_t>); static_assert(std::is_same_v::TraitsType::QuantizedType, uint16_t>); static_assert(std::is_same_v::TraitsType::QuantizedType, uint16_t>); static_assert(std::is_same_v::TraitsType::QuantizedType, uint32_t>); static_assert(std::is_same_v::TraitsType::QuantizedType, uint32_t>); /* * Test and validate that sign extension can not modify a * quantized value when stored or cast to a larger register. */ std::cout << std::endl; std::cout << "Testing sign extension of quantized values when cast to larger registers" << std::endl; fails += signExtendCheck, uint8_t>(); fails += signExtendCheck, uint8_t>(); fails += signExtendCheck, uint16_t>(); fails += signExtendCheck, uint16_t>(); fails += signExtendCheck, uint32_t>(); /* clang-format on */ std::cout << std::endl; if (fails > 0) { cout << "Fixed point quantizer tests failed: " << std::dec << fails << " failures." << std::endl; return TestFail; } return TestPass; } int run() { unsigned int fails = 0; if (testFixedPointQuantizers() != TestPass) fails++; return fails ? TestFail : TestPass; } }; TEST_REGISTER(FixedPointUtilsTest)