added LDPC encoder and layered decoder

lifted from my LDPC project:
https://github.com/xdsopl/LDPC
This commit is contained in:
Ahmet Inan 2018-12-11 16:29:47 +01:00
commit 199a988def
4 changed files with 536 additions and 0 deletions

View file

@ -48,6 +48,15 @@ Implemented are the following Encoders and Decoders:
* [bose_chaudhuri_hocquenghem_encoder.hh](bose_chaudhuri_hocquenghem_encoder.hh)
* [bose_chaudhuri_hocquenghem_decoder.hh](bose_chaudhuri_hocquenghem_decoder.hh)
### [ldpc_encoder.hh](ldpc_encoder.hh)
[Low-density parity-check](https://en.wikipedia.org/wiki/Low-density_parity-check_code) encoder
### [ldpc_decoder.hh](ldpc_decoder.hh)
[Low-density parity-check](https://en.wikipedia.org/wiki/Low-density_parity-check_code) layered decoder
### [exclusive_reduce.hh](exclusive_reduce.hh)
Reduce N times while excluding ith input element

220
ldpc_decoder.hh Normal file
View file

@ -0,0 +1,220 @@
/*
LDPC SISO layered decoder
Copyright 2018 Ahmet Inan <inan@aicodix.de>
*/
#ifndef LDPC_DECODER_HH
#define LDPC_DECODER_HH
#include <algorithm>
#include "exclusive_reduce.hh"
namespace CODE {
template <typename TABLE, int BETA>
class LDPCDecoder
{
static const int M = TABLE::M;
static const int N = TABLE::N;
static const int K = TABLE::K;
static const int R = N-K;
static const int q = R/M;
static const int CNC = TABLE::LINKS_MAX_CN - 2;
static const int LT = TABLE::LINKS_TOTAL;
int8_t bnl[LT];
int8_t pty[R];
uint16_t pos[R * CNC];
uint8_t cnc[R];
static int8_t sqadd(int8_t a, int8_t b)
{
int16_t x = int16_t(a) + int16_t(b);
x = std::min<int16_t>(std::max<int16_t>(x, -128), 127);
return x;
}
static int8_t sqsub(int8_t a, int8_t b)
{
int16_t x = int16_t(a) - int16_t(b);
x = std::min<int16_t>(std::max<int16_t>(x, -128), 127);
return x;
}
static uint8_t sqsubu(uint8_t a, uint8_t b)
{
int16_t x = int16_t(a) - int16_t(b);
x = std::max<int16_t>(x, 0);
return x;
}
static int8_t smin(int8_t a, int8_t b)
{
return std::min(a, b);
}
static int8_t sclamp(int8_t x, int8_t a, int8_t b)
{
return std::min(std::max(x, a), b);
}
static int8_t seor(int8_t a, int8_t b)
{
return a ^ b;
}
static int8_t sqabs(int8_t a)
{
return std::abs(std::max<int8_t>(a, -127));
}
static int8_t ssign(int8_t a, int8_t b)
{
return ((b > 0) - (b < 0)) * a;
}
bool bad(int8_t *data, int8_t *parity)
{
{
int cnt = cnc[0];
{
int8_t cnv = ssign(1, parity[0]);
for (int c = 0; c < cnt; ++c)
cnv = ssign(cnv, data[pos[c]]);
if (cnv <= 0)
return true;
}
for (int j = 1; j < M; ++j) {
int8_t cnv = ssign(ssign(1, parity[j+(q-1)*M-1]), parity[j]);
for (int c = 0; c < cnt; ++c)
cnv = ssign(cnv, data[pos[CNC*j+c]]);
if (cnv <= 0)
return true;
}
}
for (int i = 1; i < q; ++i) {
int cnt = cnc[i];
for (int j = 0; j < M; ++j) {
int8_t cnv = ssign(ssign(1, parity[M*(i-1)+j]), parity[M*i+j]);
for (int c = 0; c < cnt; ++c)
cnv = ssign(cnv, data[pos[CNC*(M*i+j)+c]]);
if (cnv <= 0)
return true;
}
}
return false;
}
void finalp(int8_t *links, int cnt)
{
int8_t mags[cnt], mins[cnt];
for (int i = 0; i < cnt; ++i)
mags[i] = sqsubu(sqabs(links[i]), BETA);
exclusive_reduce(mags, mins, cnt, smin);
int8_t signs[cnt];
exclusive_reduce(links, signs, cnt, seor);
for (int i = 0; i < cnt; ++i)
signs[i] |= 127;
for (int i = 0; i < cnt; ++i)
links[i] = ssign(mins[i], signs[i]);
}
void update(int8_t *data, int8_t *parity)
{
int8_t *bl = bnl;
{
int cnt = cnc[0];
{
int deg = cnt + 1;
int8_t inp[deg], out[deg];
for (int c = 0; c < cnt; ++c)
inp[c] = out[c] = sqsub(data[pos[c]], bl[c]);
inp[cnt] = out[cnt] = sqsub(parity[0], bl[cnt]);
finalp(out, deg);
for (int c = 0; c < cnt; ++c)
data[pos[c]] = sqadd(inp[c], out[c]);
parity[0] = sqadd(inp[cnt], out[cnt]);
for (int d = 0; d < deg; ++d)
*bl++ = sclamp(out[d], -32, 31);
}
int deg = cnt + 2;
for (int j = 1; j < M; ++j) {
int8_t inp[deg], out[deg];
for (int c = 0; c < cnt; ++c)
inp[c] = out[c] = sqsub(data[pos[CNC*j+c]], bl[c]);
inp[cnt] = out[cnt] = sqsub(parity[j+(q-1)*M-1], bl[cnt]);
inp[cnt+1] = out[cnt+1] = sqsub(parity[j], bl[cnt+1]);
finalp(out, deg);
for (int c = 0; c < cnt; ++c)
data[pos[CNC*j+c]] = sqadd(inp[c], out[c]);
parity[j+(q-1)*M-1] = sqadd(inp[cnt], out[cnt]);
parity[j] = sqadd(inp[cnt+1], out[cnt+1]);
for (int d = 0; d < deg; ++d)
*bl++ = sclamp(out[d], -32, 31);
}
}
for (int i = 1; i < q; ++i) {
int cnt = cnc[i];
int deg = cnt + 2;
for (int j = 0; j < M; ++j) {
int8_t inp[deg], out[deg];
for (int c = 0; c < cnt; ++c)
inp[c] = out[c] = sqsub(data[pos[CNC*(M*i+j)+c]], bl[c]);
inp[cnt] = out[cnt] = sqsub(parity[M*(i-1)+j], bl[cnt]);
inp[cnt+1] = out[cnt+1] = sqsub(parity[M*i+j], bl[cnt+1]);
finalp(out, deg);
for (int c = 0; c < cnt; ++c)
data[pos[CNC*(M*i+j)+c]] = sqadd(inp[c], out[c]);
parity[M*(i-1)+j] = sqadd(inp[cnt], out[cnt]);
parity[M*i+j] = sqadd(inp[cnt+1], out[cnt+1]);
for (int d = 0; d < deg; ++d)
*bl++ = sclamp(out[d], -32, 31);
}
}
}
public:
LDPCDecoder()
{
for (int i = 0; i < R; ++i)
cnc[i] = 0;
int bit_pos = 0;
const int *row_ptr = TABLE::POS;
for (int g = 0; TABLE::LEN[g]; ++g) {
int bit_deg = TABLE::DEG[g];
for (int r = 0; r < TABLE::LEN[g]; ++r) {
int acc_pos[bit_deg];
for (int d = 0; d < bit_deg; ++d)
acc_pos[d] = row_ptr[d];
row_ptr += bit_deg;
for (int j = 0; j < M; ++j) {
for (int d = 0; d < bit_deg; ++d) {
int n = acc_pos[d];
pos[CNC*n+cnc[n]++] = bit_pos;
}
++bit_pos;
for (int d = 0; d < bit_deg; ++d)
acc_pos[d] = (acc_pos[d] + q) % R;
}
}
}
uint16_t tmp[R * CNC];
for (int i = 0; i < q; ++i)
for (int j = 0; j < M; ++j)
for (int c = 0; c < CNC; ++c)
tmp[CNC*(M*i+j)+c] = pos[CNC*(q*j+i)+c];
for (int i = 0; i < R * CNC; ++i)
pos[i] = tmp[i];
}
int operator()(int8_t *data, int8_t *parity, int trials = 25)
{
for (int i = 0; i < LT; ++i)
bnl[i] = 0;
for (int i = 0; i < q; ++i)
for (int j = 0; j < M; ++j)
pty[M*i+j] = parity[q*j+i];
while (bad(data, pty) && --trials >= 0)
update(data, pty);
for (int i = 0; i < q; ++i)
for (int j = 0; j < M; ++j)
parity[q*j+i] = pty[M*i+j];
return trials;
}
};
}
#endif

63
ldpc_encoder.hh Normal file
View file

@ -0,0 +1,63 @@
/*
LDPC SISO encoder
Copyright 2018 Ahmet Inan <inan@aicodix.de>
*/
#ifndef LDPC_ENCODER_HH
#define LDPC_ENCODER_HH
namespace CODE {
template <typename TABLE>
class LDPCEncoder
{
static const int M = TABLE::M;
static const int N = TABLE::N;
static const int K = TABLE::K;
static const int R = N-K;
static const int q = R/M;
static const int CNC = TABLE::LINKS_MAX_CN - 2;
uint16_t pos[R * CNC];
uint8_t cnc[R];
public:
LDPCEncoder()
{
for (int i = 0; i < R; ++i)
cnc[i] = 0;
int bit_pos = 0;
const int *row_ptr = TABLE::POS;
for (int g = 0; TABLE::LEN[g]; ++g) {
int bit_deg = TABLE::DEG[g];
for (int r = 0; r < TABLE::LEN[g]; ++r) {
int acc_pos[bit_deg];
for (int d = 0; d < bit_deg; ++d)
acc_pos[d] = row_ptr[d];
row_ptr += bit_deg;
for (int j = 0; j < M; ++j) {
for (int d = 0; d < bit_deg; ++d) {
int n = acc_pos[d];
pos[CNC*n+cnc[n]++] = bit_pos;
}
++bit_pos;
for (int d = 0; d < bit_deg; ++d)
acc_pos[d] = (acc_pos[d] + q) % R;
}
}
}
}
void operator()(int8_t *data, int8_t *parity)
{
int8_t tmp = 1;
for (int i = 0; i < R; ++i) {
for (int j = 0; j < cnc[i]; ++j)
tmp *= data[pos[CNC*i+j]];
parity[i] = tmp;
}
}
};
}
#endif

View file

@ -0,0 +1,244 @@
/*
Regression Test for the Low-density parity-check Encoder and Decoder
Table entries below copied from:
https://www.etsi.org/deliver/etsi_en/302700_302799/302755/01.04.01_60/en_302755v010401p.pdf
Copyright 2018 Ahmet Inan <inan@aicodix.de>
*/
#include <iostream>
#include <random>
#include <cmath>
#include <cassert>
#include <chrono>
#include <algorithm>
#include <functional>
#include "ldpc_encoder.hh"
#include "ldpc_decoder.hh"
struct DVB_T2_TABLE_A1
{
static const int M = 360;
static const int N = 64800;
static const int K = 32400;
static const int LINKS_MIN_CN = 6;
static const int LINKS_MAX_CN = 7;
static const int LINKS_TOTAL = 226799;
static const int DEG_MAX = 8;
static constexpr int DEG[] = {
8, 3, 0
};
static constexpr int LEN[] = {
36, 54, 0
};
static constexpr int POS[] = {
54, 9318, 14392, 27561, 26909, 10219, 2534, 8597,
55, 7263, 4635, 2530, 28130, 3033, 23830, 3651,
56, 24731, 23583, 26036, 17299, 5750, 792, 9169,
57, 5811, 26154, 18653, 11551, 15447, 13685, 16264,
58, 12610, 11347, 28768, 2792, 3174, 29371, 12997,
59, 16789, 16018, 21449, 6165, 21202, 15850, 3186,
60, 31016, 21449, 17618, 6213, 12166, 8334, 18212,
61, 22836, 14213, 11327, 5896, 718, 11727, 9308,
62, 2091, 24941, 29966, 23634, 9013, 15587, 5444,
63, 22207, 3983, 16904, 28534, 21415, 27524, 25912,
64, 25687, 4501, 22193, 14665, 14798, 16158, 5491,
65, 4520, 17094, 23397, 4264, 22370, 16941, 21526,
66, 10490, 6182, 32370, 9597, 30841, 25954, 2762,
67, 22120, 22865, 29870, 15147, 13668, 14955, 19235,
68, 6689, 18408, 18346, 9918, 25746, 5443, 20645,
69, 29982, 12529, 13858, 4746, 30370, 10023, 24828,
70, 1262, 28032, 29888, 13063, 24033, 21951, 7863,
71, 6594, 29642, 31451, 14831, 9509, 9335, 31552,
72, 1358, 6454, 16633, 20354, 24598, 624, 5265,
73, 19529, 295, 18011, 3080, 13364, 8032, 15323,
74, 11981, 1510, 7960, 21462, 9129, 11370, 25741,
75, 9276, 29656, 4543, 30699, 20646, 21921, 28050,
76, 15975, 25634, 5520, 31119, 13715, 21949, 19605,
77, 18688, 4608, 31755, 30165, 13103, 10706, 29224,
78, 21514, 23117, 12245, 26035, 31656, 25631, 30699,
79, 9674, 24966, 31285, 29908, 17042, 24588, 31857,
80, 21856, 27777, 29919, 27000, 14897, 11409, 7122,
81, 29773, 23310, 263, 4877, 28622, 20545, 22092,
82, 15605, 5651, 21864, 3967, 14419, 22757, 15896,
83, 30145, 1759, 10139, 29223, 26086, 10556, 5098,
84, 18815, 16575, 2936, 24457, 26738, 6030, 505,
85, 30326, 22298, 27562, 20131, 26390, 6247, 24791,
86, 928, 29246, 21246, 12400, 15311, 32309, 18608,
87, 20314, 6025, 26689, 16302, 2296, 3244, 19613,
88, 6237, 11943, 22851, 15642, 23857, 15112, 20947,
89, 26403, 25168, 19038, 18384, 8882, 12719, 7093,
0, 14567, 24965,
1, 3908, 100,
2, 10279, 240,
3, 24102, 764,
4, 12383, 4173,
5, 13861, 15918,
6, 21327, 1046,
7, 5288, 14579,
8, 28158, 8069,
9, 16583, 11098,
10, 16681, 28363,
11, 13980, 24725,
12, 32169, 17989,
13, 10907, 2767,
14, 21557, 3818,
15, 26676, 12422,
16, 7676, 8754,
17, 14905, 20232,
18, 15719, 24646,
19, 31942, 8589,
20, 19978, 27197,
21, 27060, 15071,
22, 6071, 26649,
23, 10393, 11176,
24, 9597, 13370,
25, 7081, 17677,
26, 1433, 19513,
27, 26925, 9014,
28, 19202, 8900,
29, 18152, 30647,
30, 20803, 1737,
31, 11804, 25221,
32, 31683, 17783,
33, 29694, 9345,
34, 12280, 26611,
35, 6526, 26122,
36, 26165, 11241,
37, 7666, 26962,
38, 16290, 8480,
39, 11774, 10120,
40, 30051, 30426,
41, 1335, 15424,
42, 6865, 17742,
43, 31779, 12489,
44, 32120, 21001,
45, 14508, 6996,
46, 979, 25024,
47, 4554, 21896,
48, 7989, 21777,
49, 4972, 20661,
50, 6612, 2730,
51, 12742, 4418,
52, 29194, 595,
53, 19267, 20113,
};
};
constexpr int DVB_T2_TABLE_A1::DEG[];
constexpr int DVB_T2_TABLE_A1::LEN[];
constexpr int DVB_T2_TABLE_A1::POS[];
typedef DVB_T2_TABLE_A1 TABLE;
int main()
{
const int TRIALS = 25;
const int FACTOR = 2;
const int BETA = 1;
const int CODE_LEN = TABLE::N;
const int DATA_LEN = TABLE::K;
CODE::LDPCEncoder<TABLE> encode;
CODE::LDPCDecoder<TABLE, BETA> decode;
std::random_device rd;
std::default_random_engine generator(rd());
typedef std::uniform_int_distribution<int> uniform;
typedef std::normal_distribution<float> normal;
int8_t *code = new int8_t[CODE_LEN];
int8_t *orig = new int8_t[CODE_LEN];
int8_t *noisy = new int8_t[CODE_LEN];
float *symb = new float[CODE_LEN];
float min_SNR = 20, min_mbs = 1000, max_mbs = 0;
for (float SNR = -5; SNR <= 15; SNR += 0.1) {
float mean = 1;
float sigma = std::sqrt(mean * mean / (2 * std::pow(10, SNR / 10)));
auto data = std::bind(uniform(0, 1), generator);
auto awgn = std::bind(normal(0.0, sigma), generator);
for (int i = 0; i < DATA_LEN; ++i)
code[i] = 1 - 2 * data();
encode(code, code + DATA_LEN);
for (int i = 0; i < CODE_LEN; ++i)
orig[i] = code[i];
for (int i = 0; i < CODE_LEN; ++i)
symb[i] = code[i];
for (int i = 0; i < CODE_LEN; ++i)
symb[i] += awgn();
// $LLR=log(\frac{p(x=+1|y)}{p(x=-1|y)})$
// $p(x|\mu,\sigma)=\frac{1}{\sqrt{2\pi}\sigma}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}$
float DIST = 2; // BPSK
float fact = DIST * FACTOR / (sigma * sigma);
for (int i = 0; i < CODE_LEN; ++i)
code[i] = std::min<float>(std::max<float>(std::nearbyint(fact * symb[i]), -128), 127);
for (int i = 0; i < CODE_LEN; ++i)
noisy[i] = code[i];
auto start = std::chrono::system_clock::now();
int count = decode(code, code + DATA_LEN, TRIALS);
auto end = std::chrono::system_clock::now();
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
float mbs = (float)DATA_LEN / usec.count();
int iters = count < 0 ? TRIALS : TRIALS - count;
int awgn_errors = 0;
for (int i = 0; i < CODE_LEN; ++i)
awgn_errors += noisy[i] * orig[i] < 0;
int quantization_erasures = 0;
for (int i = 0; i < CODE_LEN; ++i)
quantization_erasures += !noisy[i];
int uncorrected_errors = 0;
for (int i = 0; i < CODE_LEN; ++i)
uncorrected_errors += code[i] * orig[i] <= 0;
int decoder_errors = 0;
for (int i = 0; i < CODE_LEN; ++i)
decoder_errors += code[i] * orig[i] <= 0 && orig[i] * noisy[i] > 0;
float bit_error_rate = (float)uncorrected_errors / (float)(CODE_LEN);
if (!uncorrected_errors)
min_SNR = std::min(min_SNR, SNR);
min_mbs = std::min(min_mbs, mbs);
max_mbs = std::max(max_mbs, mbs);
if (0) {
std::cerr << SNR << " Es/N0 => standard deviation of " << sigma << " with mean " << mean << std::endl;
std::cerr << awgn_errors << " errors caused by AWGN." << std::endl;
std::cerr << quantization_erasures << " erasures caused by quantization." << std::endl;
std::cerr << decoder_errors << " errors caused by decoder." << std::endl;
std::cerr << uncorrected_errors << " errors uncorrected." << std::endl;
std::cerr << bit_error_rate << " bit error rate." << std::endl;
if (count < 0) {
std::cerr << "decoder failed at converging to a code word!" << std::endl;
} else {
std::cerr << iters << " iterations were needed." << std::endl;
}
std::cerr << usec.count() << " microseconds to decode." << std::endl;
std::cerr << mbs << " megabit per second." << std::endl;
} else {
std::cout << SNR << " " << bit_error_rate << " " << iters << " " << mbs << std::endl;
}
}
delete[] code;
delete[] orig;
delete[] noisy;
delete[] symb;
std::cerr << "QEF at: " << min_SNR << " SNR, speed min: " << min_mbs << " Mb/s and speed max: " << max_mbs << " Mb/s." << std::endl;
assert(min_SNR < -1.8);
std::cerr << "Low-density parity-check code regression test passed!" << std::endl;
return 0;
}