// q65.c
// q65 modes encoding/decoding functions
//
// (c) 2020 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy
// ------------------------------------------------------------------------------
// This file is part of the qracodes project, a Forward Error Control
// encoding/decoding package based on Q-ary RA (Repeat and Accumulate) LDPC codes.
//
// qracodes 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 3 of the License, or
// (at your option) any later version.
// qracodes 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 qracodes source distribution.
// If not, see .
#include
#include
#include
#include "q65.h"
#include "pdmath.h"
// Minimum codeword loglikelihood for decoding
#define Q65_LLH_THRESHOLD -260.0f
// This value produce the same WER performance in decode_fullaplist
// #define Q65_LLH_THRESHOLD -262.0f
static int _q65_crc6(int *x, int sz);
static void _q65_crc12(int *y, int *x, int sz);
float q65_llh;
int q65_init(q65_codec_ds *pCodec, const qracode *pqracode)
{
// Eb/No value for which we optimize the decoder metric (AWGN/Rayleigh cases)
const float EbNodBMetric = 2.8f;
const float EbNoMetric = (float)pow(10,EbNodBMetric/10);
float R; // code effective rate (after puncturing)
int nm; // bits per symbol
if (!pCodec)
return -1; // why do you called me?
if (!pqracode)
return -2; // invalid qra code
if (pqracode->M!=64)
return -3; // q65 supports only codes over GF(64)
pCodec->pQraCode = pqracode;
// allocate buffers used by encoding/decoding functions
pCodec->x = (int*)malloc(pqracode->K*sizeof(int));
pCodec->y = (int*)malloc(pqracode->N*sizeof(int));
pCodec->qra_v2cmsg = (float*)malloc(pqracode->NMSG*pqracode->M*sizeof(float));
pCodec->qra_c2vmsg = (float*)malloc(pqracode->NMSG*pqracode->M*sizeof(float));
pCodec->ix = (float*)malloc(pqracode->N*pqracode->M*sizeof(float));
pCodec->ex = (float*)malloc(pqracode->N*pqracode->M*sizeof(float));
if (pCodec->x== NULL ||
pCodec->y== NULL ||
pCodec->qra_v2cmsg== NULL ||
pCodec->qra_c2vmsg== NULL ||
pCodec->ix== NULL ||
pCodec->ex== NULL) {
q65_free(pCodec);
return -4; // out of memory
}
// compute and store the AWGN/Rayleigh Es/No ratio for which we optimize
// the decoder metric
nm = _q65_get_bits_per_symbol(pqracode);
R = _q65_get_code_rate(pqracode);
pCodec->decoderEsNoMetric = 1.0f*nm*R*EbNoMetric;
return 1;
}
void q65_free(q65_codec_ds *pCodec)
{
if (!pCodec)
return;
// free internal buffers
if (pCodec->x!=NULL)
free(pCodec->x);
if (pCodec->y!=NULL)
free(pCodec->y);
if (pCodec->qra_v2cmsg!=NULL)
free(pCodec->qra_v2cmsg);
if (pCodec->qra_c2vmsg!=NULL)
free(pCodec->qra_c2vmsg);
if (pCodec->ix!=NULL)
free(pCodec->ix);
if (pCodec->ex!=NULL)
free(pCodec->ex);
pCodec->pQraCode = NULL;
pCodec->x = NULL;
pCodec->y = NULL;
pCodec->qra_v2cmsg = NULL;
pCodec->qra_c2vmsg = NULL;
pCodec->qra_v2cmsg = NULL;
pCodec->ix = NULL;
pCodec->ex = NULL;
return;
}
int q65_encode(const q65_codec_ds *pCodec, int *pOutputCodeword, const int *pInputMsg)
{
const qracode *pQraCode;
int *px;
int *py;
int nK;
int nN;
if (!pCodec)
return -1; // which codec?
pQraCode = pCodec->pQraCode;
px = pCodec->x;
py = pCodec->y;
nK = _q65_get_message_length(pQraCode);
nN = _q65_get_codeword_length(pQraCode);
// copy the information symbols into the internal buffer
memcpy(px,pInputMsg,nK*sizeof(int));
// compute and append the appropriate CRC if required
switch (pQraCode->type) {
case QRATYPE_NORMAL:
break;
case QRATYPE_CRC:
case QRATYPE_CRCPUNCTURED:
px[nK] = _q65_crc6(px,nK);
break;
case QRATYPE_CRCPUNCTURED2:
_q65_crc12(px+nK,px,nK);
break;
default:
return -2; // code type not supported
}
// encode with the given qra code
qra_encode(pQraCode,py,px);
// puncture the CRC symbols as required
// and copy the result to the destination buffer
switch (pQraCode->type) {
case QRATYPE_NORMAL:
case QRATYPE_CRC:
// no puncturing
memcpy(pOutputCodeword,py,nN*sizeof(int));
break;
case QRATYPE_CRCPUNCTURED:
// strip the single CRC symbol from the encoded codeword
memcpy(pOutputCodeword,py,nK*sizeof(int)); // copy the systematic symbols
memcpy(pOutputCodeword+nK,py+nK+1,(nN-nK)*sizeof(int)); // copy the check symbols skipping the CRC symbol
break;
case QRATYPE_CRCPUNCTURED2:
// strip the 2 CRC symbols from the encoded codeword
memcpy(pOutputCodeword,py,nK*sizeof(int)); // copy the systematic symbols
memcpy(pOutputCodeword+nK,py+nK+2,(nN-nK)*sizeof(int)); // copy the check symbols skipping the two CRC symbols
break;
default:
return -2; // code type unsupported
}
return 1; // ok
}
int q65_intrinsics(q65_codec_ds *pCodec, float *pIntrinsics, const float *pInputEnergies)
{
// compute observations intrinsics probabilities
// for the AWGN/Rayleigh channels
// NOTE:
// A true Rayleigh channel metric would require that the channel gains were known
// for each symbol in the codeword. Such gains cannot be estimated reliably when
// the Es/No ratio is small. Therefore we compute intrinsic probabilities assuming
// that, on average, these channel gains are unitary.
// In general it is even difficult to estimate the Es/No ratio for the AWGN channel
// Therefore we always compute the intrinsic probabilities assuming that the Es/No
// ratio is known and equal to the constant decoderEsNoMetric. This assumption will
// generate the true intrinsic probabilities only when the actual Eb/No ratio is
// equal to this constant. As in all the other cases the probabilities are evaluated
// with a wrong scaling constant we can expect that the decoder performance at different
// Es/No will be worse. Anyway, since the EsNoMetric constant has been chosen so that the
// decoder error rate is about 50%, we obtain almost optimal error rates down to
// any useful Es/No ratio.
const qracode *pQraCode;
int nN, nBits;
float EsNoMetric;
if (pCodec==NULL)
return -1; // which codec?
pQraCode = pCodec->pQraCode;
nN = _q65_get_codeword_length(pQraCode);
nBits = pQraCode->m;
EsNoMetric = pCodec->decoderEsNoMetric;
qra_mfskbesselmetric(pIntrinsics,pInputEnergies,nBits,nN,EsNoMetric);
return 1; // success
}
int q65_esnodb(const q65_codec_ds *pCodec, float *pEsNodB, const int *ydec, const float *pInputEnergies)
{
// compute average Es/No for the AWGN/Rayleigh channel cases
int k,j;
float sigplusnoise=0;
float noise=0;
int nN, nM;
const float *pIn = pInputEnergies;
const int *py = ydec;
float EsNodB;
nN = q65_get_codeword_length(pCodec);
nM = q65_get_alphabet_size(pCodec);
for (k=0;k4)
return Q65_DECODE_INVPARAMS; // invalid submode
// As the symbol duration in q65 is different than in QRA64,
// the fading tables continue to be valid if the B90Ts parameter
// is properly scaled to the QRA64 symbol interval
// Compute index to most appropriate weighting function coefficients
B90 = B90Ts/TS_QRA64;
hidx = (int)(logf(B90)/logf(1.09f) - 0.499f);
// Unlike in QRA64 we accept any B90, anyway limiting it to
// the extreme cases (0.9 to 210 Hz approx.)
if (hidx<0)
hidx = 0;
else
if (hidx > 63) //Changed by K1JT: previously max was 64.
hidx=63; //Changed by K1JT: previously max was 64.
// select the appropriate weighting fading coefficients array
if (fadingModel==0) { // gaussian fading model
// point to gaussian energy weighting taps
hlen = glen_tab_gauss[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_gauss[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else if (fadingModel==1) {
// point to lorentzian energy weighting taps
hlen = glen_tab_lorentz[hidx]; // hlen = (L+1)/2 (where L=(odd) number of taps of w fun)
hptr = gptr_tab_lorentz[hidx]; // pointer to the first (L+1)/2 coefficients of w fun
}
else
return Q65_DECODE_INVPARAMS; // invalid fading model
// compute (euristically) the optimal decoder metric accordingly the given spread amount
// We assume that the decoder 50% decoding threshold is:
// Es/No(dB) = Es/No(AWGN)(dB) + 8*log(B90)/log(240)(dB)
// that's to say, at the maximum Doppler spread bandwidth (240 Hz for QRA64)
// there's a ~8 dB Es/No degradation over the AWGN case
fTemp = 8.0f*logf(B90)/logf(240.0f); // assumed Es/No degradation for the given fading bandwidth
EsNoMetric = pCodec->decoderEsNoMetric*powf(10.0f,fTemp/10.0f);
nM = q65_get_alphabet_size(pCodec);
nN = q65_get_codeword_length(pCodec);
nBinsPerTone = 1<ffNoiseVar = fNoiseVar;
pCodec->ffEsNoMetric = EsNoMetric;
pCodec->nBinsPerTone = nBinsPerTone;
pCodec->nBinsPerSymbol = nBinsPerSymbol;
pCodec->nWeights = hlen;
weight = pCodec->ffWeight;
// compute the fast fading weights accordingly to the Es/No ratio
// for which we compute the exact intrinsics probabilities
for (k=0;kmaxlogp) // keep track of the max
maxlogp = fTemp;
pCurIx[k]=fTemp;
pCurBin += nBinsPerTone; // next tone
}
// exponentiate and accumulate the normalization constant
sumix = 0.0f;
for (k=0;knBinsPerTone;
nBinsPerSymbol = pCodec->nBinsPerSymbol;
nWeights = pCodec->nWeights;
ffNoiseVar = pCodec->ffNoiseVar;
ffEsNoMetric = pCodec->ffEsNoMetric;
nTotWeights = 2*nWeights-1;
// compute symbols energy (noise included) summing the
// energies pertaining to the decoded symbols in the codeword
EsPlusWNo = 0.0f;
pCurSym = pInputEnergies + nM; // point to first central bin of first symbol tone
for (n=0;npQraCode;
ix = pCodec->ix;
ex = pCodec->ex;
nK = _q65_get_message_length(pQraCode);
nN = _q65_get_codeword_length(pQraCode);
nM = pQraCode->M;
nBits = pQraCode->m;
px = pCodec->x;
py = pCodec->y;
// Depuncture intrinsics observations as required by the code type
switch (pQraCode->type) {
case QRATYPE_CRCPUNCTURED:
memcpy(ix,pIntrinsics,nK*nM*sizeof(float)); // information symbols
pd_init(PD_ROWADDR(ix,nM,nK),pd_uniform(nBits),nM); // crc
memcpy(ix+(nK+1)*nM,pIntrinsics+nK*nM,(nN-nK)*nM*sizeof(float)); // parity checks
break;
case QRATYPE_CRCPUNCTURED2:
memcpy(ix,pIntrinsics,nK*nM*sizeof(float)); // information symbols
pd_init(PD_ROWADDR(ix,nM,nK),pd_uniform(nBits),nM); // crc
pd_init(PD_ROWADDR(ix,nM,nK+1),pd_uniform(nBits),nM); // crc
memcpy(ix+(nK+2)*nM,pIntrinsics+nK*nM,(nN-nK)*nM*sizeof(float)); // parity checks
break;
case QRATYPE_NORMAL:
case QRATYPE_CRC:
default:
// no puncturing
memcpy(ix,pIntrinsics,nN*nM*sizeof(float)); // as they are
}
// mask the intrinsics with the available a priori knowledge
if (pAPMask!=NULL)
_q65_mask(pQraCode,ix,pAPMask,pAPSymbols);
// Compute the extrinsic symbols probabilities with the message-passing algorithm
// Stop if the extrinsics information does not converges to unity
// within the given number of iterations
rc = qra_extrinsic( pQraCode,
ex,
ix,
100,
pCodec->qra_v2cmsg,
pCodec->qra_c2vmsg);
if (rc<0)
// failed to converge to a solution
return Q65_DECODE_FAILED;
// decode the information symbols (punctured information symbols included)
qra_mapdecode(pQraCode,px,ex,ix);
// verify CRC match
switch (pQraCode->type) {
case QRATYPE_CRC:
case QRATYPE_CRCPUNCTURED:
crc6=_q65_crc6(px,nK); // compute crc-6
if (crc6!=px[nK])
return Q65_DECODE_CRCMISMATCH; // crc doesn't match
break;
case QRATYPE_CRCPUNCTURED2:
_q65_crc12(crc12, px,nK); // compute crc-12
if (crc12[0]!=px[nK] ||
crc12[1]!=px[nK+1])
return Q65_DECODE_CRCMISMATCH; // crc doesn't match
break;
case QRATYPE_NORMAL:
default:
// nothing to check
break;
}
// copy the decoded msg to the user buffer (excluding punctured symbols)
if (pDecodedMsg)
memcpy(pDecodedMsg,px,nK*sizeof(int));
#ifndef Q65_CHECKLLH
if (pDecodedCodeword==NULL) // user is not interested in the decoded codeword
return rc; // return the number of iterations required to decode
#else
if (pDecodedCodeword==NULL) // we must have a buffer
return Q65_DECODE_INVPARAMS; // return error
#endif
// crc matches therefore we can reconstruct the transmitted codeword
// reencoding the information available in px...
qra_encode(pQraCode, py, px);
// ...and strip the punctured symbols from the codeword
switch (pQraCode->type) {
case QRATYPE_CRCPUNCTURED:
memcpy(pDecodedCodeword,py,nK*sizeof(int));
memcpy(pDecodedCodeword+nK,py+nK+1,(nN-nK)*sizeof(int)); // puncture crc-6 symbol
break;
case QRATYPE_CRCPUNCTURED2:
memcpy(pDecodedCodeword,py,nK*sizeof(int));
memcpy(pDecodedCodeword+nK,py+nK+2,(nN-nK)*sizeof(int)); // puncture crc-12 symbols
break;
case QRATYPE_CRC:
case QRATYPE_NORMAL:
default:
memcpy(pDecodedCodeword,py,nN*sizeof(int)); // no puncturing
}
#ifdef Q65_CHECKLLH
if (q65_check_llh(NULL,pDecodedCodeword, nN, nM, pIntrinsics)==0) // llh less than threshold
return Q65_DECODE_LLHLOW;
#endif
return rc; // return the number of iterations required to decode
}
// Compute and verify the loglikelihood of the decoded codeword
int q65_check_llh(float *llh, const int* ydec, const int nN, const int nM, const float *pIntrin)
{
int k;
float t = 0;
for (k=0;k=Q65_LLH_THRESHOLD);
}
// Full AP decoding from a list of codewords
int q65_decode_fullaplist(q65_codec_ds *codec,
int *ydec,
int *xdec,
const float *pIntrinsics,
const int *pCodewords,
const int nCodewords)
{
int k;
int nK, nN, nM;
float llh, maxllh, llh_threshold;
int maxcw = -1; // index of the most likely codeword
const int *pCw;
if (nCodewords<1 || nCodewords>Q65_FULLAPLIST_SIZE)
return Q65_DECODE_INVPARAMS; // invalid list length
nK = q65_get_message_length(codec);
nN = q65_get_codeword_length(codec);
nM = q65_get_alphabet_size(codec);
// we adjust the llh threshold in order to mantain the
// same false decode rate independently from the size
// of the list
llh_threshold = Q65_LLH_THRESHOLD + logf(1.0f*nCodewords/3);
maxllh = llh_threshold; // at least one llh should be larger than the threshold
// compute codewords log likelihoods and find max
pCw = pCodewords; // start from the first codeword
for (k=0;kmaxllh) {
maxllh = llh;
maxcw = k;
}
// printf("BBB %d %f\n",k,llh);
// point to next codeword
pCw+=nN;
}
q65_llh=maxllh; // save for Joe's use
if (maxcw<0) // no llh larger than threshold found
return Q65_DECODE_FAILED;
pCw = pCodewords+nN*maxcw;
memcpy(ydec,pCw,nN*sizeof(int));
memcpy(xdec,pCw,nK*sizeof(int));
return maxcw; // index to the decoded message (>=0)
}
// helper functions -------------------------------------------------------------
int _q65_get_message_length(const qracode *pCode)
{
// return the actual information message length (in symbols)
// excluding any punctured symbol
int nMsgLength;
switch (pCode->type) {
case QRATYPE_NORMAL:
nMsgLength = pCode->K;
break;
case QRATYPE_CRC:
case QRATYPE_CRCPUNCTURED:
// one information symbol of the underlying qra code is reserved for CRC
nMsgLength = pCode->K-1;
break;
case QRATYPE_CRCPUNCTURED2:
// two code information symbols are reserved for CRC
nMsgLength = pCode->K-2;
break;
default:
nMsgLength = -1;
}
return nMsgLength;
}
int _q65_get_codeword_length(const qracode *pCode)
{
// return the actual codeword length (in symbols)
// excluding any punctured symbol
int nCwLength;
switch (pCode->type) {
case QRATYPE_NORMAL:
case QRATYPE_CRC:
// no puncturing
nCwLength = pCode->N;
break;
case QRATYPE_CRCPUNCTURED:
// the CRC symbol is punctured
nCwLength = pCode->N-1;
break;
case QRATYPE_CRCPUNCTURED2:
// the two CRC symbols are punctured
nCwLength = pCode->N-2;
break;
default:
nCwLength = -1;
}
return nCwLength;
}
float _q65_get_code_rate(const qracode *pCode)
{
return 1.0f*_q65_get_message_length(pCode)/_q65_get_codeword_length(pCode);
}
int _q65_get_alphabet_size(const qracode *pCode)
{
return pCode->M;
}
int _q65_get_bits_per_symbol(const qracode *pCode)
{
return pCode->m;
}
static void _q65_mask(const qracode *pcode, float *ix, const int *mask, const int *x)
{
// mask intrinsic information ix with available a priori knowledge
int k,kk, smask;
const int nM=pcode->M;
const int nm=pcode->m;
int nK;
// Exclude from masking the symbols which have been punctured.
// nK is the length of the mask and x arrays, which do
// not include any punctured symbol
nK = _q65_get_message_length(pcode);
// for each symbol set to zero the probability
// of the values which are not allowed by
// the a priori information
for (k=0;k>1) ^ CRC6_GEN_POL;
else
sr = (sr>>1);
t>>=1;
}
}
return sr;
}
static void _q65_crc12(int *y, int *x, int sz)
{
int k,j,t,sr = 0;
for (k=0;k>1) ^ CRC12_GEN_POL;
else
sr = (sr>>1);
t>>=1;
}
}
y[0] = sr&0x3F;
y[1] = (sr>>6);
}