Small update to DeviationError. Add unit tests to CMake build. Add Golay24 implementation and tests.

pull/3/head
Rob Riggs 2020-12-06 19:37:18 -06:00
rodzic 8440c11bcd
commit 4825a4f527
7 zmienionych plików z 430 dodań i 50 usunięć

Wyświetl plik

@ -19,3 +19,6 @@ pkg_check_modules(CODEC2 REQUIRED codec2)
add_executable(m17-demod m17-demod.cpp)
target_link_libraries(m17-demod ${CODEC2_LIBRARIES})
install(TARGETS m17-demod DESTINATION ${CMAKE_INSTALL_BINDIR})
enable_testing ()
add_subdirectory (tests)

Wyświetl plik

@ -88,7 +88,7 @@ struct DeviationError
}
auto deviation = max_estimate_ - min_estimate_;
auto deviation_error = deviation != ZERO ? 6.0 / deviation : 1.0;
auto deviation_error = std::min(6.0 / deviation, 5.0);
return deviation_error;
}
};

223
Golay24.h 100644
Wyświetl plik

@ -0,0 +1,223 @@
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
// All rights reserved.
#pragma once
#include <array>
#include <algorithm>
#include <utility>
#include <iostream>
#include <iomanip>
namespace mobilinkd {
// Parts are adapted from:
// http://aqdi.com/articles/using-the-golay-error-detection-and-correction-code-3/
namespace Golay24
{
namespace detail
{
// Need a constexpr sort.
// https://stackoverflow.com/a/40030044/854133
template<class T>
constexpr void swap(T& l, T& r)
{
T tmp = std::move(l);
l = std::move(r);
r = std::move(tmp);
}
template <typename T, size_t N>
struct array
{
constexpr T& operator[](size_t i)
{
return arr[i];
}
constexpr const T& operator[](size_t i) const
{
return arr[i];
}
constexpr const T* begin() const
{
return arr;
}
constexpr const T* end() const
{
return arr + N;
}
T arr[N];
};
template <typename T, size_t N>
constexpr void sort_impl(array<T, N> &array, size_t left, size_t right)
{
if (left < right)
{
size_t m = left;
for (size_t i = left + 1; i<right; i++)
if (array[i]<array[left])
swap(array[++m], array[i]);
swap(array[left], array[m]);
sort_impl(array, left, m);
sort_impl(array, m + 1, right);
}
}
template <typename T, size_t N>
constexpr array<T, N> sort(array<T, N> array)
{
auto sorted = array;
sort_impl(sorted, 0, N);
return sorted;
}
} // detail
// static constexpr uint16_t POLY = 0xAE3;
constexpr uint16_t POLY = 0xC75;
struct __attribute__((packed)) SyndromeMapEntry
{
uint32_t a{0};
uint16_t b{0};
};
/**
* Calculate the syndrome of a [23,12] Golay codeword.
*
* @return the 11-bit syndrome of the codeword in bits [22:12].
*/
constexpr uint32_t syndrome(uint32_t codeword)
{
codeword &= 0xffffffl;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return (codeword << 12);
}
constexpr bool parity(uint32_t codeword)
{
return __builtin_popcount(codeword) & 1;
}
constexpr SyndromeMapEntry makeSyndromeMapEntry(uint64_t val)
{
return SyndromeMapEntry{uint32_t(val >> 16), uint16_t(val & 0xFFFF)};
}
constexpr uint64_t makeSME(uint64_t syndrome, uint32_t bits)
{
return (syndrome << 24) | (bits & 0xFFFFFF);
}
constexpr std::array<SyndromeMapEntry, 2048> make_lut()
{
constexpr size_t LUT_SIZE = 2048;
constexpr size_t VECLEN=23;
detail::array<uint64_t, LUT_SIZE> result{};
size_t index = 0;
result[index++] = makeSME(syndrome(0), 0);
for (size_t i = 0; i != VECLEN; ++i)
{
auto v = (1 << i);
result[index++] = makeSME(syndrome(v), v);
}
for (size_t i = 0; i != VECLEN - 1; ++i)
{
for (size_t j = i + 1; j != VECLEN; ++j)
{
auto v = (1 << i) | (1 << j);
result[index++] = makeSME(syndrome(v), v);
}
}
for (size_t i = 0; i != VECLEN - 2; ++i)
{
for (size_t j = i + 1; j != VECLEN - 1; ++j)
{
for (size_t k = j + 1; k != VECLEN; ++k)
{
auto v = (1 << i) | (1 << j) | (1 << k);
result[index++] = makeSME(syndrome(v), v);
}
}
}
result = detail::sort(result);
std::array<SyndromeMapEntry, LUT_SIZE> tmp;
for (size_t i = 0; i != LUT_SIZE; ++i)
{
tmp[i] = makeSyndromeMapEntry(result[i]);
}
return tmp;
}
inline constexpr auto LUT = make_lut();
/**
* Calculate [23,12] Golay codeword.
*
* @return checkbits(11)|data(12).
*/
constexpr uint32_t encode23(uint16_t data)
{
// data &= 0xfff;
uint32_t codeword = data;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return codeword | (data << 11);
}
constexpr uint32_t encode24(uint16_t data)
{
auto codeword = encode23(data);
return ((codeword << 1) | parity(codeword));
}
bool decode(uint32_t input, uint32_t& output)
{
auto syndrm = syndrome(input >> 1);
auto it = std::lower_bound(LUT.begin(), LUT.end(), syndrm,
[](const SyndromeMapEntry& sme, uint32_t val){
return (sme.a >> 8) < val;
});
if ((it->a >> 8) == syndrm)
{
// Build the correction from the compressed entry.
auto correction = ((((it->a & 0xFF) << 16) | it->b) << 1);
// Apply the correction to the input.
output = input ^ correction;
// Only test parity for 3-bit errors.
return __builtin_popcount(syndrm) < 3 || !parity(output);
}
return false;
}
} // Golay24
} // mobilinkd

Wyświetl plik

@ -0,0 +1,27 @@
include(GoogleTest)
pkg_check_modules(CODEC2 REQUIRED codec2)
include_directories (
${TEST_SOURCE_DIR}
..
)
add_executable (ConvolutionTest ConvolutionTest.cpp)
target_link_libraries(ConvolutionTest gtest)
gtest_add_tests(ConvolutionTest "" AUTO)
add_executable (M17FramerTest M17FramerTest.cpp)
target_link_libraries(M17FramerTest gtest)
gtest_add_tests(M17FramerTest "" AUTO)
add_executable (TrellisTest TrellisTest.cpp)
target_link_libraries(TrellisTest gtest)
gtest_add_tests(TrellisTest "" AUTO)
add_executable (ViterbiTest ViterbiTest.cpp)
target_link_libraries(ViterbiTest gtest)
gtest_add_tests(ViterbiTest "" AUTO)
add_executable (Golay24Test Golay24Test.cpp)
target_link_libraries(Golay24Test gtest)
gtest_add_tests(Golay24Test "" AUTO)

Wyświetl plik

@ -0,0 +1,152 @@
#include "Golay24.h"
#include <gtest/gtest.h>
#include <cstdint>
#include <bitset>
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class Golay24Test : public ::testing::Test {
protected:
void SetUp() override {}
// void TearDown() override {}
};
TEST_F(Golay24Test, encode24)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
auto result = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(result, 0xD7880FU);
}
TEST_F(Golay24Test, decode24)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
uint32_t decoded = 0;
EXPECT_TRUE(mobilinkd::Golay24::decode(encoded, decoded));
EXPECT_EQ(decoded, 0xD7880FU);
}
TEST_F(Golay24Test, decode_corrupted_1)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
uint32_t corruption = 0x010000;
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ corruption;
uint32_t decoded = 0;
EXPECT_TRUE(mobilinkd::Golay24::decode(corrupted, decoded));
EXPECT_EQ(decoded, 0xD7880FU);
}
TEST_F(Golay24Test, decode_corrupted_2)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
uint32_t corruption = 0x010010;
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ corruption;
uint32_t decoded = 0;
EXPECT_TRUE(mobilinkd::Golay24::decode(corrupted, decoded));
EXPECT_EQ(decoded, 0xD7880FU);
}
TEST_F(Golay24Test, decode_corrupted_3)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
uint32_t corruption = 0x810100;
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ corruption;
uint32_t decoded = 0;
EXPECT_TRUE(mobilinkd::Golay24::decode(corrupted, decoded));
EXPECT_EQ(decoded, 0xD7880FU);
}
TEST_F(Golay24Test, decode_corrupted_4)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78
uint32_t corruption = 0x011110;
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ corruption;
uint32_t decoded = 0;
EXPECT_FALSE(mobilinkd::Golay24::decode(corrupted, decoded));
}
TEST_F(Golay24Test, syndrome_map)
{
uint32_t corruption = 0x010010;
auto c_syndrome = mobilinkd::Golay24::syndrome(corruption);
uint16_t link_setup_group = 0b110101111000; // 0xD78
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ (corruption << 1);
auto d_syndrome = mobilinkd::Golay24::syndrome(corrupted >> 1);
EXPECT_EQ(c_syndrome, d_syndrome);
}
TEST_F(Golay24Test, decode_map)
{
// uint16_t data = 0xD78;
uint16_t link_setup_group = 0b110101111000; // 0xD78, 3448
uint32_t corruption = 0x010010;
auto encoded = mobilinkd::Golay24::encode24(link_setup_group);
EXPECT_EQ(encoded, 0xD7880FU);
auto corrupted = encoded ^ corruption;
uint32_t decoded = 0;
EXPECT_TRUE(mobilinkd::Golay24::decode(corrupted, decoded));
EXPECT_EQ(decoded >> 12, link_setup_group);
#if 0
size_t c = 0;
for (auto x : mobilinkd::Golay24::LUT) {
std::cout << std::hex << std::setfill('0') << std::setw(8) << x.a << ":" << std::setw(4) << x.b;
if (c++ == 7) {
c = 0;
std::cout << std::endl;
} else std::cout << ", ";
}
#endif
}
/**
* Test interop between C++ & Python implementations
*/
TEST_F(Golay24Test, interop)
{
std::array<uint32_t, 4> encoded = {
0b110101111000100000001111U,
0b101000001111010110011001U,
0b000000000000000000000000U,
0b000000000001100011101011U
};
std::array<uint32_t, 4> expected = {
0b110101111000,
0b101000001111,
0b000000000000,
0b000000000001
};
for (size_t i = 0; i != encoded.size(); ++i)
{
uint32_t decoded;
EXPECT_TRUE(mobilinkd::Golay24::decode(encoded[i], decoded));
EXPECT_EQ(decoded >> 12, expected[i]);
}
}

Wyświetl plik

@ -1,49 +0,0 @@
#include "M17Convolution.h"
#include <gtest/gtest.h>
#include <cstdint>
#include <chrono>
// make CXXFLAGS="$(pkg-config --cflags gtest) $(pkg-config --libs gtest) -I. -O3 -std=c++17" tests/M17ConvolutionTest
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class ViterbiTest : public ::testing::Test {
protected:
void SetUp() override {}
// void TearDown() override {}
};
TEST_F(ViterbiTest, decode_depuncture_lsf)
{
std::array<uint8_t, 240> expected = {1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0};
std::array<int8_t, 368> punctured = {1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0};
std::array<int8_t, 488> expected_depunctured = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
std::array<uint8_t, 244> output;
CM17Convolution conv;
std::array<uint8_t, 46> in;
for (size_t i = 0; i != 368; i += 8)
{
uint8_t tmp = 0;
for (size_t j = 0; j != 8; ++j)
{
tmp |= ((punctured[i + j] & 1) << (7 - j));
}
in[i >> 3] = tmp;
}
auto start = std::chrono::high_resolution_clock::now();
conv.decodeLinkSetup(in.begin(), output.begin());
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Duration: " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << "ns" << std::endl;
// for (size_t i = 0; i != expected.size(); ++i) EXPECT_EQ(output[i], expected[i]) << "i = " << i;
}

Wyświetl plik

@ -0,0 +1,24 @@
#include "M17Framer.h"
#include <gtest/gtest.h>
#include <cstdint>
// make CXXFLAGS="$(pkg-config --cflags gtest) $(pkg-config --libs gtest) -I. -O3 -std=c++17" tests/ConvolutionTest
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class M17FramerTest : public ::testing::Test {
protected:
void SetUp() override {}
// void TearDown() override {}
};
TEST_F(M17FramerTest, construct)
{
mobilinkd::M17Framer framer;
}