kopia lustrzana https://github.com/mobilinkd/m17-cxx-demod
New Viterbi decoder -- no restriction on use.
rodzic
2064214c3e
commit
26eef20342
|
@ -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
|
303
M17Convolution.h
303
M17Convolution.h
|
@ -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++;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
122
Util.h
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
Ładowanie…
Reference in New Issue