New Viterbi decoder -- no restriction on use.

pull/3/head
Rob Riggs 2020-11-25 08:49:23 -06:00
rodzic 2064214c3e
commit 26eef20342
12 zmienionych plików z 903 dodań i 347 usunięć

23
Convolution.h 100644
Wyświetl plik

@ -0,0 +1,23 @@
// Copyright 2020 Mobilinkd LLC.
#pragma once
#include <cstdint>
#include <cstddef>
namespace mobilinkd
{
inline constexpr uint32_t convolve_bit(uint32_t poly, uint32_t memory)
{
return __builtin_popcount(poly & memory) & 1;
}
template <size_t K, size_t k = 1>
inline constexpr uint32_t update_memory(uint32_t memory, uint32_t input)
{
return (memory << k | input) & ((1 << (K + 1)) - 1);
}
} // mobilinkd

Wyświetl plik

@ -1,303 +0,0 @@
/*
* Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <cstdio>
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <cstdint>
class CM17Convolution {
public:
CM17Convolution();
~CM17Convolution();
void decodeLinkSetup(const unsigned char* in, unsigned char* out);
void decodeData(const unsigned char* in, unsigned char* out);
void encodeLinkSetup(const unsigned char* in, unsigned char* out) const;
void encodeData(const unsigned char* in, unsigned char* out) const;
private:
uint16_t* m_metrics1;
uint16_t* m_metrics2;
uint16_t* m_oldMetrics;
uint16_t* m_newMetrics;
uint64_t* m_decisions;
uint64_t* m_dp;
void start();
void decode(uint8_t s0, uint8_t s1);
void chainback(unsigned char* out, unsigned int nBits);
void encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const;
};
inline const unsigned int PUNCTURE_LIST_LINK_SETUP[] = {
3U, 6U, 9U, 12U, 19U, 22U, 25U, 28U, 35U, 38U, 41U, 44U, 51U, 54U, 57U, 64U, 67U, 70U, 73U, 80U, 83U, 86U, 89U, 96U, 99U, 102U,
105U, 112U, 115U, 118U, 125U, 128U, 131U, 134U, 141U, 144U, 147U, 150U, 157U, 160U, 163U, 166U, 173U, 176U, 179U, 186U, 189U,
192U, 195U, 202U, 205U, 208U, 211U, 218U, 221U, 224U, 227U, 234U, 237U, 240U, 247U, 250U, 253U, 256U, 263U, 266U, 269U, 272U,
279U, 282U, 285U, 288U, 295U, 298U, 301U, 308U, 311U, 314U, 317U, 324U, 327U, 330U, 333U, 340U, 343U, 346U, 349U, 356U, 359U,
362U, 369U, 372U, 375U, 378U, 385U, 388U, 391U, 394U, 401U, 404U, 407U, 410U, 417U, 420U, 423U, 430U, 433U, 436U, 439U, 446U,
449U, 452U, 455U, 462U, 465U, 468U, 471U, 478U, 481U, 484U};
inline const unsigned int PUNCTURE_LIST_DATA[] = {
5U, 11U, 17U, 20U, 23U, 29U, 35U, 46U, 52U, 58U, 61U, 64U, 70U, 76U, 87U, 93U, 99U, 102U, 105U, 111U, 117U, 128U, 134U, 140U,
143U, 146U, 152U, 158U, 169U, 175U, 181U, 184U, 187U, 193U, 199U, 210U, 216U, 222U, 225U, 228U, 234U, 240U, 251U, 257U, 263U,
266U, 269U, 275U, 281U, 292U, 298U, 304U, 307U, 310U, 316U, 322U};
inline const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
inline const uint8_t BRANCH_TABLE1[] = {0U, 0U, 0U, 0U, 2U, 2U, 2U, 2U};
inline const uint8_t BRANCH_TABLE2[] = {0U, 2U, 2U, 0U, 0U, 2U, 2U, 0U};
const unsigned int NUM_OF_STATES_D2 = 8U;
const unsigned int NUM_OF_STATES = 16U;
const uint32_t M = 4U;
const unsigned int K = 5U;
CM17Convolution::CM17Convolution() :
m_metrics1(NULL),
m_metrics2(NULL),
m_oldMetrics(NULL),
m_newMetrics(NULL),
m_decisions(NULL),
m_dp(NULL)
{
m_metrics1 = new uint16_t[16U];
m_metrics2 = new uint16_t[16U];
m_decisions = new uint64_t[300U];
}
CM17Convolution::~CM17Convolution()
{
delete[] m_metrics1;
delete[] m_metrics2;
delete[] m_decisions;
}
inline void CM17Convolution::encodeLinkSetup(const unsigned char* in, unsigned char* out) const
{
assert(in != NULL);
assert(out != NULL);
unsigned char temp1[31U];
::memset(temp1, 0x00U, 31U);
::memcpy(temp1, in, 30U);
unsigned char temp2[61U];
encode(temp1, temp2, 244U);
unsigned int n = 0U;
unsigned int index = 0U;
for (unsigned int i = 0U; i < 488U; i++) {
if (i != PUNCTURE_LIST_LINK_SETUP[index]) {
bool b = READ_BIT1(temp2, i);
WRITE_BIT1(out, n, b);
n++;
} else {
index++;
}
}
}
inline void CM17Convolution::encodeData(const unsigned char* in, unsigned char* out) const
{
assert(in != NULL);
assert(out != NULL);
unsigned char temp1[21U];
::memset(temp1, 0x00U, 21U);
::memcpy(temp1, in, 20U);
unsigned char temp2[41U];
encode(temp1, temp2, 164U);
unsigned int n = 0U;
unsigned int index = 0U;
for (unsigned int i = 0U; i < 328U; i++) {
if (i != PUNCTURE_LIST_DATA[index]) {
bool b = READ_BIT1(temp2, i);
WRITE_BIT1(out, n, b);
n++;
} else {
index++;
}
}
}
inline void CM17Convolution::decodeLinkSetup(const unsigned char* in, unsigned char* out)
{
assert(in != NULL);
assert(out != NULL);
uint8_t temp[500U];
unsigned int n = 0U;
unsigned int index = 0U;
for (unsigned int i = 0U; i < 368U; i++) {
if (n == PUNCTURE_LIST_LINK_SETUP[index]) {
temp[n++] = 1U;
index++;
}
bool b = READ_BIT1(in, i);
temp[n++] = b ? 2U : 0U;
}
for (unsigned int i = 0U; i < 8U; i++)
temp[n++] = 0U;
start();
n = 0U;
for (unsigned int i = 0U; i < 244U; i++) {
uint8_t s0 = temp[n++];
uint8_t s1 = temp[n++];
decode(s0, s1);
}
chainback(out, 240U);
}
inline void CM17Convolution::decodeData(const unsigned char* in, unsigned char* out)
{
assert(in != NULL);
assert(out != NULL);
uint8_t temp[350U];
unsigned int n = 0U;
unsigned int index = 0U;
for (unsigned int i = 0U; i < 272U; i++) {
if (n == PUNCTURE_LIST_DATA[index]) {
temp[n++] = 1U;
index++;
}
bool b = READ_BIT1(in, i);
temp[n++] = b ? 2U : 0U;
}
for (unsigned int i = 0U; i < 8U; i++)
temp[n++] = 0U;
start();
n = 0U;
for (unsigned int i = 0U; i < 164U; i++) {
uint8_t s0 = temp[n++];
uint8_t s1 = temp[n++];
decode(s0, s1);
}
chainback(out, 160U);
}
inline void CM17Convolution::start()
{
::memset(m_metrics1, 0x00U, NUM_OF_STATES * sizeof(uint16_t));
::memset(m_metrics2, 0x00U, NUM_OF_STATES * sizeof(uint16_t));
m_oldMetrics = m_metrics1;
m_newMetrics = m_metrics2;
m_dp = m_decisions;
}
inline void CM17Convolution::decode(uint8_t s0, uint8_t s1)
{
*m_dp = 0U;
for (uint8_t i = 0U; i < NUM_OF_STATES_D2; i++) {
uint8_t j = i * 2U;
uint16_t metric = std::abs(BRANCH_TABLE1[i] - s0) + std::abs(BRANCH_TABLE2[i] - s1);
uint16_t m0 = m_oldMetrics[i] + metric;
uint16_t m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + (M - metric);
uint8_t decision0 = (m0 >= m1) ? 1U : 0U;
m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0;
m0 = m_oldMetrics[i] + (M - metric);
m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + metric;
uint8_t decision1 = (m0 >= m1) ? 1U : 0U;
m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0;
*m_dp |= (uint64_t(decision1) << (j + 1U)) | (uint64_t(decision0) << (j + 0U));
}
++m_dp;
assert((m_dp - m_decisions) <= 300);
uint16_t* tmp = m_oldMetrics;
m_oldMetrics = m_newMetrics;
m_newMetrics = tmp;
}
inline void CM17Convolution::chainback(unsigned char* out, unsigned int nBits)
{
assert(out != NULL);
uint32_t state = 0U;
while (nBits-- > 0) {
--m_dp;
uint32_t i = state >> (9 - K);
uint8_t bit = uint8_t(*m_dp >> i) & 1;
state = (bit << 7) | (state >> 1);
WRITE_BIT1(out, nBits, bit != 0U);
}
}
inline void CM17Convolution::encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const
{
assert(in != NULL);
assert(out != NULL);
assert(nBits > 0U);
uint8_t d1 = 0U, d2 = 0U, d3 = 0U, d4 = 0U;
uint32_t k = 0U;
for (unsigned int i = 0U; i < nBits; i++) {
uint8_t d = READ_BIT1(in, i) ? 1U : 0U;
uint8_t g1 = (d + d3 + d4) & 1;
uint8_t g2 = (d + d1 + d2 + d4) & 1;
d4 = d3;
d3 = d2;
d2 = d1;
d1 = d;
WRITE_BIT1(out, k, g1 != 0U);
k++;
WRITE_BIT1(out, k, g2 != 0U);
k++;
}
}

Wyświetl plik

@ -4,7 +4,8 @@
#include "M17Randomizer.h"
#include "PolynomialInterleaver.h"
#include "M17Convolution.h"
#include "Trellis.h"
#include "Viterbi.h"
#include "CRC16.h"
#include "LinkSetupFrame.h"
@ -23,7 +24,8 @@ struct M17FrameDecoder
{
M17Randomizer<368> derandomize_;
PolynomialInterleaver<45, 92, 368> interleaver_;
CM17Convolution convolution_;
Trellis<4,2> trellis_{makeTrellis<4, 2>({031,027})};
Viterbi<decltype(trellis_)> viterbi_{trellis_};
CRC16<0x5935, 0xFFFF> crc_;
enum class State {LS_FRAME, LS_LICH, AUDIO};
@ -63,32 +65,25 @@ struct M17FrameDecoder
void reset() { state_ = State::LS_FRAME; }
void decode_lsf(buffer_t& buffer)
size_t decode_lsf(buffer_t& buffer)
{
lsf_conv_buffer_t cbuffer;
for (size_t i = 0; i != 368; i += 8)
{
uint8_t tmp = 0;
for (size_t j = 0; j != 8; ++j)
{
tmp |= ((buffer[i + j] & 1) << (7 - j));
}
cbuffer[i >> 3] = tmp;
}
lsf_buffer_t lsf;
convolution_.decodeLinkSetup(cbuffer.begin(), lsf.begin());
std::array<uint8_t, 240> output;
for (auto& c : buffer) c = c * 2 - 1;
auto dp = depunctured<488>(P1, buffer);
auto ber = viterbi_.decode(dp, output);
auto lsf = to_byte_array(output);
crc_.reset();
for (auto x : lsf) crc_(x);
auto checksum = crc_.get();
if (checksum == 0) state_ = State::AUDIO;
else return;
else return ber;
LinkSetupFrame::encoded_call_t encoded_call;
std::copy(lsf.begin(), lsf.begin() + 6, encoded_call.begin());
auto mycall = LinkSetupFrame::decode_callsign(encoded_call);
std::cerr << "\nTOCALL: ";
for (auto x : mycall) if (x) std::cerr << x;
std::cerr << std::endl;
return ber;
}
void demodulate_audio(audio_buffer_t audio)
@ -100,48 +95,43 @@ struct M17FrameDecoder
std::cout.write((const char*)buf.begin(), 320);
}
void decode_audio(buffer_t& buffer)
size_t decode_audio(buffer_t& buffer)
{
audio_conv_buffer_t cbuffer;
size_t index = 0;
for (size_t i = 96; i != 368; i += 8)
{
uint8_t tmp = 0;
for (size_t j = 0; j != 8; ++j)
{
tmp |= (buffer[i + j] << (7 - j));
}
cbuffer[index++] = tmp;
}
audio_buffer_t audio;
convolution_.decodeData(cbuffer.begin(), audio.begin());
// for (auto x : audio) std::cout << std::hex << std::setw(2) << std::setfill('0') << int(x) << ' ';
// std::cout << std::endl;
std::array<int8_t, 272> tmp;
std::copy(buffer.begin() + 96, buffer.end(), tmp.begin());
std::array<uint8_t, 160> output;
for (auto& c : tmp) c = c * 2 - 1;
auto dp = depunctured<328>(P2, tmp);
auto ber = viterbi_.decode(dp, output);
auto audio = to_byte_array(output);
crc_.reset();
for (auto x : audio) crc_(x);
auto checksum = crc_.get();
demodulate_audio(audio);
uint16_t fn = (audio[0] << 8) | audio[1];
if (checksum == 0 && fn > 0x7fff) state_ = State::LS_FRAME;
return ber;
}
void operator()(buffer_t& buffer)
size_t operator()(buffer_t& buffer)
{
derandomize_(buffer);
interleaver_.deinterleave(buffer);
size_t ber = -1;
switch(state_)
{
case State::LS_FRAME:
decode_lsf(buffer);
ber = decode_lsf(buffer);
break;
case State::LS_LICH:
state_ = State::LS_FRAME;
break;
case State::AUDIO:
decode_audio(buffer);
ber = decode_audio(buffer);
break;
}
return ber;
}
};

Wyświetl plik

@ -1,7 +1,5 @@
# m17-cxx-demod
M17 Demodulator in C++ (GPL) **IMPORTANT NOTE: Until I re-implement the Viterbi
decoder, this code carries the additional restriction from G4KLX that it may not be
used for commercial purposes.**
M17 Demodulator in C++ (GPL)
This program reads a 48K SPS 16-bit, little-endian, single channel, M17 4-FSK
baseband input stream from STDIN and writes a demodulated/decoded 8K SPS
@ -41,5 +39,3 @@ encoding. It is out of date with the current M17 spec.
## Thanks
Thanks to the M17 team to for the great work on the spec.
Thanks to Jonathan Naylor, G4KLX, for the convolutional encoder/decoder.

124
Trellis.h 100644
Wyświetl plik

@ -0,0 +1,124 @@
// Copyright 2020 Mobilinkd LLC.
// make CXXFLAGS="$(pkg-config --cflags gtest) $(pkg-config --libs gtest) -I. -O3" tests/TrellisTest
#pragma once
#include "Util.h"
#include "Convolution.h"
#include <array>
#include <experimental/array>
#include <cstdlib>
#include <cstdint>
namespace mobilinkd
{
/// Puncture matrix for linx setup frame.
inline constexpr auto P1 = std::experimental::make_array<int8_t>(
1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1);
/// Puncture matrix for audio frames.
inline constexpr auto P2 = std::experimental::make_array<int8_t>(
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1,
0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1);
/**
* Convert an integer value to an array of bits, with the
* high-bit at index 0.
*
* At anything beyond -O0, the array is constructed at compile time.
*/
template <size_t N>
constexpr std::array<uint8_t, N> toBitArray(int8_t value)
{
std::array<uint8_t, N> result{};
for (size_t i = 0; i != N; ++i)
{
result[N - (i + 1)] = (value & 1);
value >>= 1;
}
return result;
}
template <size_t N>
struct NextStateTable
{
using nextStateTable_t = std::array<std::array<int8_t, N>, N>;
nextStateTable_t nextStateTable = makeNextStateTable();
static constexpr nextStateTable_t makeNextStateTable()
{
return nextStateTable_t();
}
};
template <size_t N>
struct OutputTable
{
};
/**
* Compute a cost table for a Trellis of size K, for input n of N,
* and LLR size of LLR bits + 1. (i.e. LLR = 1 allows 2 bits to
* represent -1, 0, +1).
*/
template <size_t K, size_t N, size_t LLR = 1>
struct CostTable
{
static constexpr int8_t Price = 1 << LLR;
static constexpr size_t InputValues = 1 << N;
using cost_table_t = std::array<std::array<uint8_t, InputValues>, K>;
template <typename Trellis_>
static constexpr cost_table_t makeCostTable(const Trellis_& trellis)
{
cost_table_t result;
for (size_t i = 0; i != K; ++i)
{
for (size_t j = 0; j != InputValues; ++j)
{
}
}
}
};
/**
* Only valid for a k=1 (1:n) convolutional coder.
*/
template <size_t K_, size_t n_>
struct Trellis
{
static constexpr size_t K = K_; // Memory depth of convolution.
static constexpr size_t k = 1; // Number of bits per input symbol.
static constexpr size_t n = n_; // Number of coefficients / output bits.
static constexpr size_t NumStates = (1 << K); // Number of states in the convolutional coder.
using polynomials_t = std::array<uint32_t, n_>;
polynomials_t polynomials;
Trellis(polynomials_t polys)
: polynomials(polys)
{}
};
template <size_t K, size_t n>
constexpr Trellis<K, n> makeTrellis(std::array<uint32_t, n> polys)
{
return Trellis<K, n>(polys);
}
} // mobilinkd

122
Util.h
Wyświetl plik

@ -2,11 +2,42 @@
#pragma once
#include <algorithm>
#include <cstdlib>
#include <cassert>
#include <array>
#include <bitset>
#include <tuple>
namespace mobilinkd
{
// The make_bitset stuff only works as expected in GCC10 and later.
namespace detail {
template<std::size_t...Is, class Tuple>
constexpr std::bitset<sizeof...(Is)> make_bitset(std::index_sequence<Is...>, Tuple&& tuple)
{
constexpr auto size = sizeof...(Is);
std::bitset<size> result;
using expand = int[];
for (size_t i = 0; i != size; ++i)
{
void(expand {0, result[Is] = std::get<Is>(tuple)...});
}
return result;
}
}
template<class...Bools>
constexpr auto make_bitset(Bools&&...bools)
{
return detail::make_bitset(std::make_index_sequence<sizeof...(Bools)>(),
std::make_tuple(bool(bools)...));
}
inline int from_4fsk(int symbol)
{
// Convert a 4-FSK symbol to a pair of bits.
@ -20,4 +51,95 @@ inline int from_4fsk(int symbol)
}
}
#if 0
inline std::pair<int8_t, int8_t> 4fsk_demod(int symbol)
{
// Convert a 4-FSK symbol to a pair of bits.
switch (symbol)
{
case 1: return std::pair<int8_t, int8_t>(0,0);
case 3: return std::pair<int8_t, int8_t>(0,1);
case -1: return std::pair<int8_t, int8_t>(1,0);
case -3: return std::pair<int8_t, int8_t>(1,1);
default: abort();
}
}
#endif
template <size_t M, typename T, size_t N, typename U, size_t IN>
auto depunctured(std::array<T, N> puncture_matrix, std::array<U, IN> in)
{
static_assert(M % N == 0);
std::array<U, M> result;
size_t index = 0;
size_t pindex = 0;
for (size_t i = 0; i != M; ++i)
{
if (!puncture_matrix[pindex++])
{
result[i] = 0;
}
else
{
result[i] = in[index++];
}
if (pindex == N) pindex = 0;
}
return result;
}
template <size_t IN, size_t OUT, size_t P>
size_t depuncture(const std::array<int8_t, IN>& in,
std::array<int8_t, OUT>& out, const std::array<int8_t, P>& p)
{
size_t index = 0;
size_t pindex = 0;
size_t bit_count = 0;
for (size_t i = 0; i != OUT && index < IN; ++i)
{
if (!p[pindex++])
{
out[i] = 0;
bit_count++;
}
else
{
out[i] = in[index++];
}
if (pindex == P) pindex = 0;
}
return bit_count;
}
/**
* Sign-extend an n-bit value to a specific signed integer type.
*/
template <typename T, size_t n>
constexpr T to_int(uint8_t v)
{
constexpr auto MAX_INPUT = (1 << (n - 1));
constexpr auto NEGATIVE_OFFSET = std::numeric_limits<typename std::make_unsigned<T>::type>::max() - (MAX_INPUT - 1);
T r = v & (1 << (n - 1)) ? NEGATIVE_OFFSET : 0;
return r + (v & (MAX_INPUT - 1));
}
template <typename T, size_t N>
constexpr auto to_byte_array(std::array<T, N> in)
{
std::array<uint8_t, (N + 7) / 8> out{};
out.fill(0);
size_t i = 0;
size_t b = 0;
for (auto c : in)
{
out[i] |= (c << (7 - b));
if (++b == 8)
{
++i;
b = 0;
}
}
return out;
}
} // mobilinkd

189
Viterbi.h 100644
Wyświetl plik

@ -0,0 +1,189 @@
// Copyright 2020 Mobilinkd LLC.
#pragma once
#include "Trellis.h"
#include "Convolution.h"
#include "Util.h"
#include <limits>
#include <iostream>
#include <iomanip>
namespace mobilinkd
{
template <typename Trellis_>
constexpr std::array<std::array<uint8_t, (1 << Trellis_::k)>, (1 << Trellis_::K)> makeNextState(Trellis_)
{
std::array<std::array<uint8_t, (1 << Trellis_::k)>, (1 << Trellis_::K)> result{};
for (size_t i = 0; i != (1 << Trellis_::K); ++i)
{
for (size_t j = 0; j != (1 << Trellis_::k); ++j)
{
result[i][j] = static_cast<uint8_t>(update_memory<Trellis_::K, Trellis_::k>(i, j) & ((1 << Trellis_::K) - 1));
}
}
return result;
}
template <typename Trellis_>
constexpr std::array<std::array<uint8_t, (1 << Trellis_::k)>, (1 << Trellis_::K)> makePrevState(Trellis_)
{
constexpr size_t NumStates = (1 << Trellis_::K);
constexpr size_t HalfStates = NumStates / 2;
std::array<std::array<uint8_t, (1 << Trellis_::k)>, (1 << Trellis_::K)> result{};
for (size_t i = 0; i != (1 << Trellis_::K); ++i)
{
size_t k = i >= HalfStates;
for (size_t j = 0; j != (1 << Trellis_::k); ++j)
{
size_t l = update_memory<Trellis_::K, Trellis_::k>(i, j) & (NumStates - 1);
result[l][k] = i;
}
}
return result;
}
template <typename Trellis_, size_t LLR = 2>
constexpr auto makeCost(Trellis_ trellis)
{
constexpr size_t NumStates = (1 << Trellis_::K);
constexpr size_t NumOutputs = Trellis_::n;
std::array<std::array<int16_t, NumOutputs>, NumStates> result{};
for (uint32_t i = 0; i != NumStates; ++i)
{
for (uint32_t j = 0; j != NumOutputs; ++j)
{
auto bit = convolve_bit(trellis.polynomials[j], i << 1);
result[i][j] = to_int<int8_t, LLR>(((bit << 1) - 1) * ((1 << (LLR - 1)) - 1));
}
}
return result;
}
template <typename Trellis_, size_t LLR_ = 2>
struct Viterbi
{
static_assert(LLR_ < 7); // Need to be < 7 to avoid overflow errors.
static constexpr size_t K = Trellis_::K;
static constexpr size_t k = Trellis_::k;
static constexpr size_t n = Trellis_::n;
static constexpr size_t InputValues = 1 << n;
static constexpr size_t NumStates = (1 << K);
static constexpr int32_t METRIC = ((1 << (LLR_ - 1)) - 1) << 2;
using metrics_t = std::array<int32_t, NumStates>;
using cost_t = std::array<std::array<int16_t, n>, NumStates>;
using state_transition_t = std::array<std::array<uint8_t, 2>, NumStates>;
metrics_t pathMetrics_{};
cost_t cost_;
state_transition_t nextState_;
state_transition_t prevState_;
Viterbi(Trellis_ trellis)
: cost_(makeCost<Trellis_, LLR_>(trellis))
, nextState_(makeNextState(trellis))
, prevState_(makePrevState(trellis))
{}
/**
* Viterbi soft decoder using LLR inputs where 0 == erasure.
*
* @return path metric for computing BER.
*/
template <size_t IN, size_t OUT>
size_t decode(std::array<int8_t, IN> in, std::array<uint8_t, OUT>& out)
{
constexpr auto MAX_METRIC = std::numeric_limits<typename metrics_t::value_type>::max() / 2;
metrics_t prevMetrics, currMetrics;
prevMetrics.fill(MAX_METRIC);
prevMetrics[0] = 0; // Starting point.
std::array<std::bitset<NumStates>, IN / 2> history;
// history.fill(0);
constexpr size_t BUTTERFLY_SIZE = NumStates / 2;
size_t hindex = 0;
std::array<int16_t, BUTTERFLY_SIZE> cost0;
std::array<int16_t, BUTTERFLY_SIZE> cost1;
for (size_t i = 0; i != IN; i += 2)
{
auto& hist = history[hindex];
int16_t s0 = in[i];
int16_t s1 = in[i + 1];
for (size_t j = 0; j != BUTTERFLY_SIZE; ++j)
{
int16_t c = std::abs(cost_[j][0] - s0) + std::abs(cost_[j][1] - s1);
cost0[j] = c;
cost1[j] = METRIC - c;
}
for (size_t j = 0; j != BUTTERFLY_SIZE; ++j)
{
auto& i0 = nextState_[j][0];
auto& i1 = nextState_[j][1];
int16_t c0 = cost0[j];
int16_t c1 = cost1[j];
auto& p0 = prevMetrics[j];
auto& p1 = prevMetrics[j + BUTTERFLY_SIZE];
int32_t m0 = p0 + c0;
int32_t m1 = p0 + c1;
int32_t m2 = p1 + c1;
int32_t m3 = p1 + c0;
bool d0 = m0 > m2;
bool d1 = m1 > m3;
hist.set(i0, d0);
hist.set(i1, d1);
currMetrics[i0] = d0 ? m2 : m0;
currMetrics[i1] = d1 ? m3 : m1;
}
std::swap(currMetrics, prevMetrics);
hindex += 1;
// for (size_t i = 0; i != NumStates; ++i) std::cout << std::setw(5) << prevMetrics[i] << ",";
// std::cout << std::endl;
}
// Find starting point. Should be 0 for properly flushed CCs.
size_t min_element = 0;
int32_t min_cost = prevMetrics[0];
size_t ber = min_cost / (2 * ((1 << (LLR_ - 1)) - 1));
for (size_t i = 0; i != NumStates; ++i)
{
if (prevMetrics[i] < min_cost)
{
min_cost = prevMetrics[i];
min_element = i;
}
}
// Do chainback.
auto oit = std::rbegin(out);
auto hit = std::rbegin(history);
size_t next_element = min_element;
size_t index = IN / 2;
while (oit != std::rend(out) && hit != std::rend(history))
{
auto v = (*hit++)[next_element];
if (index-- <= OUT) *oit++ = next_element & 1;
next_element = prevState_[next_element][v];
}
return ber;
}
};
} // mobilinkd

Wyświetl plik

@ -46,7 +46,7 @@ int main(int argc, char* argv[])
{
using namespace mobilinkd;
auto demod = Fsk4Demod(48000.0, 4800.0, 0.01);
auto demod = Fsk4Demod(48000.0, 4800.0, 0.03);
auto dcd = CarrierDetect<double, 10>(0.1, 0.4);
auto synch = M17Synchronizer();
auto framer = M17Framer();
@ -63,6 +63,7 @@ int main(int argc, char* argv[])
int16_t sample;
std::cin.read(reinterpret_cast<char*>(&sample), 2);
auto result = demod(sample / 5000.0);
size_t ber = 0;
if (result)
{
count += 1;
@ -77,6 +78,7 @@ int main(int argc, char* argv[])
state = State::SYNCHING;
// std::cout << "Lock!" << std::endl;
decoder.reset();
ber = -1;
}
break;
case State::SYNCHING:
@ -102,7 +104,7 @@ int main(int argc, char* argv[])
state = State::SYNCHING;
std::copy(frame, frame + len, buffer.begin());
// std::cout << "Frame!" << std::endl;
decoder(buffer);
ber = decoder(buffer);
}
}
break;
@ -115,7 +117,8 @@ int main(int argc, char* argv[])
<< ", evm: " << std::setw(10) << rms
<< ", deviation: " << std::setw(10) << estimated_deviation
<< ", freq offset: " << std::setw(10) << estimated_frequency_offset
<< ", locked: " << std::boolalpha << locked << std::ends;
<< ", locked: " << std::boolalpha << std::setw(6) << locked
<< ", ber: " << ber << std::ends;
}
}
}

Wyświetl plik

@ -0,0 +1,65 @@
#include "Convolution.h"
#include "Util.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 ConvolutionTest : public ::testing::Test {
protected:
void SetUp() override {}
// void TearDown() override {}
};
TEST_F(ConvolutionTest, convolve)
{
constexpr size_t K = 4;
auto memory = mobilinkd::update_memory<K>(0, 0);
EXPECT_EQ(mobilinkd::convolve_bit(031, memory), 0);
EXPECT_EQ(mobilinkd::convolve_bit(027, memory), 0);
memory = mobilinkd::update_memory<K>(0, 1);
EXPECT_EQ(mobilinkd::convolve_bit(031, memory), 1);
EXPECT_EQ(mobilinkd::convolve_bit(027, memory), 1);
}
TEST_F(ConvolutionTest, convolve_bit)
{
std::array<uint8_t, 8> input = {1,0,1,1,0,1,1,0};
std::array<uint8_t, 24> expected = {1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,1,1,0,0};
std::array<uint8_t, 24> encoded;
constexpr size_t K = 4;
size_t index = 0;
uint32_t memory = 0;
for (auto x : input)
{
memory = mobilinkd::update_memory<K>(memory, x);
encoded[index++] = mobilinkd::convolve_bit(031, memory);
encoded[index++] = mobilinkd::convolve_bit(027, memory);
}
// Flush
for (size_t i = 0; i != K; ++i)
{
memory = mobilinkd::update_memory<K>(memory, 0);
encoded[index++] = mobilinkd::convolve_bit(031, memory);
encoded[index++] = mobilinkd::convolve_bit(027, memory);
}
std::cout << std::endl;
for (size_t i = 0; i != encoded.size(); ++i)
{
EXPECT_EQ(encoded[i], expected[i]) << "i = " << i;
}
}

Wyświetl plik

@ -0,0 +1,49 @@
#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,70 @@
#include "Trellis.h"
#include "Util.h"
#include <gtest/gtest.h>
#include <cstdint>
// make CXXFLAGS="$(pkg-config --cflags gtest) $(pkg-config --libs gtest) -I. -O3 -std=c++17" tests/TrellisTest
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class TrellisTest : public ::testing::Test {
protected:
void SetUp() override {}
// void TearDown() override {}
};
TEST_F(TrellisTest, toBitArray)
{
auto ba1 = mobilinkd::toBitArray<4>(7);
ASSERT_EQ(ba1.size(), 4);
EXPECT_EQ(ba1[0], 0);
EXPECT_EQ(ba1[1], 1);
EXPECT_EQ(ba1[2], 1);
EXPECT_EQ(ba1[3], 1);
auto ba2 = mobilinkd::toBitArray<4>(5);
ASSERT_EQ(ba2.size(), 4);
EXPECT_EQ(ba2[0], 0);
EXPECT_EQ(ba2[1], 1);
EXPECT_EQ(ba2[2], 0);
EXPECT_EQ(ba2[3], 1);
}
TEST_F(TrellisTest, depuncture)
{
std::array<int8_t, 488> out{0};
std::array<int8_t, 368> in;
in.fill(1);
mobilinkd::depuncture(in, out, mobilinkd::P1);
for (size_t i = 0; i != out.size(); ++i) EXPECT_EQ(mobilinkd::P1[i % mobilinkd::P1.size()], out[i]) << "i = " << i;
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> depunctured{1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 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, 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, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 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, 1, 1, 0, 1, 1, 0, 1, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0};
out.fill(0);
mobilinkd::depuncture(punctured, out, mobilinkd::P1);
for (size_t i = 0; i != out.size(); ++i)
{
if (mobilinkd::P1[i % mobilinkd::P1.size()])
{
EXPECT_EQ(depunctured[i], out[i]) << "i =" << i;
}
}
}
TEST_F(TrellisTest, construct)
{
auto trellis = mobilinkd::makeTrellis<4, 2>({031,027});
ASSERT_EQ(trellis.polynomials.size(), 2);
EXPECT_EQ(trellis.polynomials[1], 027);
}

Wyświetl plik

@ -0,0 +1,228 @@
#include "Viterbi.h"
#include "Trellis.h"
#include "Util.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/ViterbiTest
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, construct)
{
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis)> viterbi(trellis);
}
TEST_F(ViterbiTest, makeNextState)
{
mobilinkd::Trellis<4,2> trellis({031,027});
auto nextState{mobilinkd::makeNextState(trellis)};
for (size_t i = 0; i != 16; ++i)
{
std::cout << "[ " << int(nextState[i][0]) << ", " << int(nextState[i][1]) << " ]" << std::endl;
}
EXPECT_EQ(nextState[0][0], 0);
}
TEST_F(ViterbiTest, makePrevState)
{
mobilinkd::Trellis<4,2> trellis({031,027});
auto prevStates{mobilinkd::makePrevState(trellis)};
for (size_t i = 0; i != 16; ++i)
{
std::cout << "[ " << int(prevStates[i][0]) << ", " << int(prevStates[i][1]) << " ]" << std::endl;
}
EXPECT_EQ(prevStates[0][0], 0);
EXPECT_EQ(prevStates[0][1], 8);
}
TEST_F(ViterbiTest, makeCost)
{
mobilinkd::Trellis<4,2> trellis({031,027});
ASSERT_EQ(trellis.polynomials[0], 031);
ASSERT_EQ(trellis.polynomials[1], 027);
auto cost{mobilinkd::makeCost(trellis)};
for (size_t i = 0; i != 16; ++i)
{
std::cout << "[ " << int(cost[i][0]) << ", " << int(cost[i][1]) << " ]" << std::endl;
}
EXPECT_EQ(cost[0][0], -1);
EXPECT_EQ(cost[1][1], 1);
}
TEST_F(ViterbiTest, makeCostLLR)
{
mobilinkd::Trellis<4,2> trellis({031,027});
ASSERT_EQ(trellis.polynomials[0], 031);
ASSERT_EQ(trellis.polynomials[1], 027);
auto cost{mobilinkd::makeCost<decltype(trellis), 4>(trellis)};
for (size_t i = 0; i != 16; ++i)
{
std::cout << "[ " << int(cost[i][0]) << ", " << int(cost[i][1]) << " ]" << std::endl;
}
EXPECT_EQ(cost[0][0], -7);
EXPECT_EQ(cost[0][1], -7);
EXPECT_EQ(cost[1][1], 7);
}
TEST_F(ViterbiTest, decode)
{
std::array<uint8_t, 8> expected = {1,0,1,1,0,1,1,0};
std::array<int8_t, 24> encoded = {1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,1,1,0,0};
std::array<uint8_t, 8> output;
for (size_t i = 0; i != encoded.size(); ++i)
{
encoded[i] = encoded[i] * 2 - 1;
}
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis)> viterbi(trellis);
viterbi.decode(encoded, output);
for (size_t i = 0; i != output.size(); ++i)
{
std::cout << int(output[i]) << ", ";
EXPECT_EQ(output[i], expected[i]);
}
std::cout << std::endl;
output.fill(0);
std::array<int8_t, 24> encoded2 = {1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1};
for (size_t i = 0; i != encoded2.size(); ++i)
{
encoded2[i] = encoded2[i] * 2 - 1;
}
auto ber = viterbi.decode(encoded2, output);
EXPECT_EQ(ber, 0);
for (size_t i = 0; i != output.size(); ++i) std::cout << int(output[i]) << ", ";
std::cout << std::endl;
}
TEST_F(ViterbiTest, decode_ber_1)
{
std::array<uint8_t, 8> expected = {1,0,1,1,0,1,1,0};
std::array<int8_t, 24> encoded = {1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,1,1,0,0};
std::array<uint8_t, 12> output;
encoded[11] = 1; // flip one bit.
for (size_t i = 0; i != encoded.size(); ++i)
{
encoded[i] = encoded[i] * 2 - 1;
}
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis)> viterbi(trellis);
auto ber = viterbi.decode(encoded,output);
for (size_t i = 0; i != expected.size(); ++i) EXPECT_EQ(output[i], expected[i]);
EXPECT_EQ(ber, 1);
}
TEST_F(ViterbiTest, decode_ber_llr)
{
std::array<uint8_t, 8> expected = {1,0,1,1,0,1,1,0};
std::array<int8_t, 24> encoded = {1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,1,1,0,0};
std::array<uint8_t, 12> output;
encoded[11] = 1;
for (size_t i = 0; i != encoded.size(); ++i)
{
encoded[i] = encoded[i] * 14 - 7;
}
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis), 4> viterbi(trellis);
auto start = std::chrono::high_resolution_clock::now();
auto ber = viterbi.decode(encoded, output);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Duration: " << (end - start).count() << "ns" << std::endl;
EXPECT_EQ(ber, 1);
for (size_t i = 0; i != expected.size(); ++i) EXPECT_EQ(output[i], expected[i]);
}
TEST_F(ViterbiTest, decode_ber_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, 488> encoded = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 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, 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, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 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, 1, 1, 0, 1, 1, 0, 1, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0};
std::array<uint8_t, 244> output;
encoded[11] = 1;
for (size_t i = 0; i != encoded.size(); ++i)
{
encoded[i] = encoded[i] * 14 - 7;
}
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis), 4> viterbi(trellis);
auto start = std::chrono::high_resolution_clock::now();
auto ber = viterbi.decode(encoded, output);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Duration: " << (end - start).count() << "ns" << std::endl;
EXPECT_EQ(ber, 0);
for (size_t i = 0; i != expected.size(); ++i) EXPECT_EQ(output[i], expected[i]);
}
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;
for (size_t i = 0; i != punctured.size(); ++i)
{
punctured[i] = punctured[i] * 2 - 1;
}
mobilinkd::Trellis<4,2> trellis({031,027});
mobilinkd::Viterbi<decltype(trellis)> viterbi(trellis);
size_t ber = 0;
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i != 10000; ++i) {
// for (size_t i = 0; i != depunctured.size(); ++i) std::cout << int(depunctured[i]) << ":" << int(expected_depunctured[i]) << ", ";
// std::cout << "zeros = " << std::count(depunctured.begin(), depunctured.end(), 0) << std::endl;
auto depunctured = mobilinkd::depunctured<488>(mobilinkd::P1, punctured);
ber += viterbi.decode(depunctured, output);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Duration: " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << "ns" << std::endl;
std::cout << "Cost: " << ber / 10000 << std::endl;
EXPECT_GT(ber, 0);
for (size_t i = 0; i != expected.size(); ++i) EXPECT_EQ(output[i], expected[i]) << "i = " << i;
}