mirror of
https://git.vladimir.cc/vladimir/libfec.git
synced 2026-08-25 17:27:34 +00:00
262 lines
8.6 KiB
C
262 lines
8.6 KiB
C
// K=24 r=1/2 Viterbi decoder for ICE
|
|
// Copyright April 2014, Phil Karn, KA9Q
|
|
#include <smmintrin.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
|
|
#include "viterbi224.h"
|
|
#include "code.h"
|
|
|
|
static inline int parity(int x){
|
|
return __builtin_parity(x);
|
|
}
|
|
|
|
|
|
typedef union { uint32_t w[1<<18]; uint16_t s[1<<19];} decision_t;
|
|
typedef union { int16_t s[1<<23]; __m128i v[1<<20];} metric_t;
|
|
|
|
static union branchtab224 { uint16_t s[1<<22]; __m128i v[1<<19];} Branchtab224[2];
|
|
|
|
// State info for instance of Viterbi decoder
|
|
struct v224 {
|
|
metric_t metrics1; // path metric buffer 1
|
|
metric_t metrics2; // path metric buffer 2
|
|
void *dp; // Pointer to current decision
|
|
metric_t *old_metrics,*new_metrics; // Pointers to path metrics, swapped on every bit
|
|
void *decisions; // Beginning of decisions for block
|
|
};
|
|
|
|
// Initialize Viterbi decoder for start of new frame
|
|
int init_viterbi224(void *p,int starting_state){
|
|
struct v224 *vp = p;
|
|
int i;
|
|
|
|
if(p == NULL)
|
|
return -1;
|
|
|
|
for(i=0;i<(1<<(K-1));i++)
|
|
vp->metrics1.s[i] = (SHRT_MIN+5000);
|
|
|
|
vp->old_metrics = &vp->metrics1;
|
|
vp->new_metrics = &vp->metrics2;
|
|
vp->dp = vp->decisions;
|
|
vp->old_metrics->s[starting_state & ((1<<(K-1))-1)] = SHRT_MIN; // Bias known start state
|
|
return 0;
|
|
}
|
|
|
|
// Create a new instance of a Viterbi decoder
|
|
void *create_viterbi224(int len){
|
|
void *p;
|
|
struct v224 *vp;
|
|
int i;
|
|
int state;
|
|
|
|
// Ordinary malloc() only returns 8-byte alignment, we need 16
|
|
i = posix_memalign(&p, sizeof(__m128i),sizeof(struct v224));
|
|
assert(i == 0);
|
|
|
|
vp = (struct v224 *)p;
|
|
p = malloc(len*sizeof(decision_t));
|
|
assert(p != NULL);
|
|
// free(vp);
|
|
// return NULL;
|
|
|
|
vp->decisions = (decision_t *)p;
|
|
|
|
for(state=0;state < (1<<(K-2));state++){
|
|
Branchtab224[0].s[state] = G1FLIP ^ parity((2*state) & POLY1) ? 255 : 0;
|
|
Branchtab224[1].s[state] = G2FLIP ^ parity((2*state) & POLY2) ? 255 : 0;
|
|
}
|
|
init_viterbi224(vp,0);
|
|
return vp;
|
|
}
|
|
|
|
// Viterbi chainback
|
|
int chainback_viterbi224(
|
|
void *p,
|
|
unsigned char *data, /* Decoded output data */
|
|
unsigned int nbits, /* Number of data bits */
|
|
unsigned int endstate){ /* Terminal encoder state */
|
|
struct v224 *vp = p;
|
|
decision_t *d = (decision_t *)vp->decisions;
|
|
|
|
if(d == NULL)
|
|
return -1;
|
|
|
|
endstate &= (1<<(K-1))-1;
|
|
|
|
#if 1
|
|
{
|
|
unsigned char dbyte = 0;
|
|
while(nbits-- > 0){
|
|
int bit;
|
|
|
|
// Accumulate decoded data bits as they fall off the right end of endstate
|
|
dbyte = ((endstate & 1) << 7) | (dbyte >> 1);
|
|
if((nbits & 7) == 0)
|
|
data[nbits>>3] = dbyte;
|
|
bit = (d[nbits].w[endstate>>5] >> (endstate & 31)) & 1; // these constants do NOT change with K
|
|
endstate = (bit << (K-2)) | (endstate >> 1);
|
|
}
|
|
}
|
|
#else
|
|
// alternative that accumulates the data byte in the low 8 bits of endstate
|
|
// This requires endstate to be at least K+8 bits wide
|
|
endstate <<= 8;
|
|
while(nbits-- > 0){
|
|
int bit;
|
|
|
|
bit = (d[nbits].w[endstate>>13] >> ((endstate >> 8)& 31)) & 1; // Constants do NOT change with K
|
|
endstate = (bit << (K+6)) | (endstate >> 1);
|
|
if((nbits & 7) == 0)
|
|
data[nbits>>3] = endstate & 0xff;
|
|
}
|
|
#endif
|
|
return 0;
|
|
|
|
}
|
|
|
|
// Delete instance of a Viterbi decoder
|
|
void delete_viterbi224(void *p){
|
|
struct v224 *vp = p;
|
|
|
|
if(vp != NULL){
|
|
free(vp->decisions);
|
|
free(vp);
|
|
}
|
|
}
|
|
|
|
|
|
// Process received symbols
|
|
int update_viterbi224_blk(void *p,const unsigned char *syms,int nbits){
|
|
struct v224 *vp = p;
|
|
decision_t *d = (decision_t *)vp->dp;
|
|
|
|
while(nbits--){
|
|
__m128i sym0v,sym1v;
|
|
void *tmp;
|
|
int i;
|
|
|
|
// Splat the 0th symbol across sym0v, the 1st symbol across sym1v, etc
|
|
sym0v = _mm_set1_epi16(syms[0]);
|
|
sym1v = _mm_set1_epi16(syms[1]);
|
|
syms += 2;
|
|
|
|
// SSE2 doesn't support minimum of unsigned words but does minimum of signed words.
|
|
// So we use signed shorts. SSE 4.1 does do minimum of unsigned words (PMINUW)
|
|
// but there doesn't seem to be any particular advantage to it
|
|
for(i=0; i < 1<<(K-5); i++){
|
|
__m128i decision0,decision1,metric,m_metric,m0,m1,m2,m3,survivor0,survivor1;
|
|
|
|
// Form branch metrics
|
|
// Because Branchtab takes on values 0 and 255, and the values of sym?v are offset binary in the range 0-255,
|
|
// the XOR operations constitute conditional negation.
|
|
// metric and m_metric (-metric) are in the range 0-510
|
|
metric = _mm_add_epi16(_mm_xor_si128(Branchtab224[0].v[i],sym0v),_mm_xor_si128(Branchtab224[1].v[i],sym1v));
|
|
m_metric = _mm_sub_epi16(_mm_set1_epi16(510),metric);
|
|
|
|
// Add branch metrics to path metrics using saturating signed addition
|
|
m0 = _mm_adds_epi16(vp->old_metrics->v[i],metric);
|
|
m3 = _mm_adds_epi16(vp->old_metrics->v[(1<<(K-5))+i],metric);
|
|
m1 = _mm_adds_epi16(vp->old_metrics->v[(1<<(K-5))+i],m_metric);
|
|
m2 = _mm_adds_epi16(vp->old_metrics->v[i],m_metric);
|
|
|
|
#if 0
|
|
_mm_prefetch((void *)&Branchtab224[0].v[i+1],3);
|
|
_mm_prefetch((void *)&Branchtab224[1].v[i+1],3);
|
|
_mm_prefetch((void *)&vp->old_metrics->v[i+1],0);
|
|
_mm_prefetch((void *)&vp->old_metrics->v[(1<<(K-5))+i],0);
|
|
#endif
|
|
|
|
// Determine winners in one of two ways:
|
|
// Direct comparison ought to be faster by not having to wait for the minimum operation,
|
|
// but it doesn't seem to make any difference on the machines I've tried (Core i7-3612QM; Xeon E5620),
|
|
// perhaps because memory bandwidth is the bottleneck anyway.
|
|
// One minor difference is that the first method breaks ties in favor of the 1-branch
|
|
// while the second breaks ties in favor of the 0-branch. That probably doesn't matter in practice.
|
|
#if 0
|
|
// Find winning metrics. Note: signed, there is an unsigned version on SSE 4.1
|
|
survivor0 = _mm_min_epi16(m0,m1);
|
|
survivor1 = _mm_min_epi16(m2,m3);
|
|
// Compare survivors with metrics to see who won
|
|
decision0 = _mm_cmpeq_epi16(survivor0,m1);
|
|
decision1 = _mm_cmpeq_epi16(survivor1,m3);
|
|
#else
|
|
// Do this before computing the surviving metrics
|
|
decision0 = _mm_cmpgt_epi16(m0,m1);
|
|
decision1 = _mm_cmpgt_epi16(m2,m3);
|
|
// Find winning metrics. Note: signed, there is an unsigned version on SSE 4.1
|
|
survivor0 = _mm_min_epi16(m0,m1);
|
|
survivor1 = _mm_min_epi16(m2,m3);
|
|
#endif
|
|
|
|
// Pack each set of decisions into 8 8-bit bytes, then interleave and compress into 16 bits
|
|
d->s[i] = _mm_movemask_epi8(_mm_unpacklo_epi8(_mm_packs_epi16(decision0,_mm_setzero_si128()),_mm_packs_epi16(decision1,_mm_setzero_si128())));
|
|
|
|
// Store surviving metrics
|
|
vp->new_metrics->v[2*i] = _mm_unpacklo_epi16(survivor0,survivor1);
|
|
vp->new_metrics->v[2*i+1] = _mm_unpackhi_epi16(survivor0,survivor1);
|
|
}
|
|
|
|
// Eyeball experiments show the metric spread for this code to start at 5000-6000 or so due to the large
|
|
// starting bias for state 0. After 1 constraint length it falls to 1000-2000
|
|
#if 0
|
|
{
|
|
int minmetric,maxmetric;
|
|
|
|
minmetric = 9999999;
|
|
maxmetric = -9999999;
|
|
for(i=0;i<(1<<(K-1));i++){
|
|
if(minmetric > vp->new_metrics->s[i])
|
|
minmetric = vp->new_metrics->s[i];
|
|
if(maxmetric < vp->new_metrics->s[i])
|
|
maxmetric = vp->new_metrics->s[i];
|
|
}
|
|
printf("metric range %d %d spread %d\n",minmetric,maxmetric,maxmetric-minmetric);
|
|
}
|
|
#endif
|
|
// See if we need to renormalize
|
|
// The comparison constant is chosen empirically; a higher value causes renormalization
|
|
// to take place less often, but it risks some other metric saturating positive
|
|
// This number should be 32767 minus the maximum observed metric spread (see above) minus a margin
|
|
if(vp->new_metrics->s[0] >= 25000){
|
|
int i,adjust;
|
|
__m128i adjustv;
|
|
union { __m128i v; uint16_t w[8]; } t;
|
|
|
|
// Find smallest metric and set adjustv to bring it down to SHRT_MIN
|
|
adjustv = vp->new_metrics->v[0];
|
|
for(i=1;i<(1<<(K-4));i++)
|
|
adjustv = _mm_min_epi16(adjustv,vp->new_metrics->v[i]);
|
|
|
|
adjustv = _mm_min_epi16(adjustv,_mm_srli_si128(adjustv,8));
|
|
adjustv = _mm_min_epi16(adjustv,_mm_srli_si128(adjustv,4));
|
|
adjustv = _mm_min_epi16(adjustv,_mm_srli_si128(adjustv,2));
|
|
t.v = adjustv;
|
|
adjust = t.w[0] - SHRT_MIN;
|
|
adjustv = _mm_set1_epi16(adjust);
|
|
|
|
// We cannot use a saturated subtract, because we often have to adjust by more than SHRT_MAX
|
|
// This is okay since it can't overflow anyway
|
|
for(i=0;i < 1<<(K-4);i++)
|
|
vp->new_metrics->v[i] = _mm_sub_epi16(vp->new_metrics->v[i],adjustv);
|
|
#if 0
|
|
printf("Adjust metrics by %d\n",adjust);
|
|
#endif
|
|
}
|
|
d++;
|
|
// Swap pointers to old and new metrics
|
|
tmp = vp->old_metrics;
|
|
vp->old_metrics = vp->new_metrics;
|
|
vp->new_metrics = tmp;
|
|
}
|
|
vp->dp = d;
|
|
return 0;
|
|
}
|
|
|
|
|