Files
Uladzimir KarpenkaandClaude Opus 4.8 3faad14fc1 anr, anf: vectorize the LMS tap and update loops
Both loops indexed the delay line as (in_idx + j + delay) & mask, one
masked index per tap, which made the address non-affine and stopped the
vectorizer. The window wraps at most once, so split it at the wrap and
walk two contiguous runs instead.

As in resample.c, the y/sigma reduction cannot be reassociated without
-ffast-math (which this library must not enable, see linux_port.h), so
carry four independent accumulator pairs to break the FMA dependency
chain and let the vectorizer in.

in_buff and out_buff alias in RXA -- both are midbuff -- so only the
private d/w arrays are marked restrict.

Measured in situ on an Apple M1 Pro, 512-sample buffers, cost of turning
the block on, best of 5:

    anr    59303 ns -> 20253 ns   2.93x
    anf    55073 ns -> 20511 ns   2.68x

Summation order changes, so output is not bit-identical: over 300 buffers
of the full RX chain the worst deviation is 2.4e-07, an SNR of 153 dB.
An LMS filter is an adaptive feedback loop, so its trajectory is
chaotic. As a control, perturbing a single input sample of the unmodified
code by one ulp diverges it from itself by 6.6e-07, an SNR of 144.6 dB --
i.e. this change disturbs the filter less than the last bit of the input
does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:52:33 +03:00

308 lines
8.0 KiB
C

/* anf.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2012, 2013 Warren Pratt, NR0V
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
/* Filter output and tap-window energy over a unit-stride run of the delay line.
The delay line is indexed (in_idx + j + delay) & mask, which wraps at most
once across the tap window; xanf() splits the window at the wrap so both
halves are contiguous here. Four independent accumulator pairs keep the FMAs
off a single dependency chain and let the vectorizer in -- a 'y += w[j]*x[j]'
reduction cannot be reassociated without -ffast-math, which this library must
not enable (it relies on IEEE semantics for 0/0 = NaN and x/0 = Inf). */
static inline void anf_dot (const double* WDSP_RESTRICT w,
const double* WDSP_RESTRICT x, int n, double* py, double* psigma)
{
double y0 = 0.0, y1 = 0.0, y2 = 0.0, y3 = 0.0;
double s0 = 0.0, s1 = 0.0, s2 = 0.0, s3 = 0.0;
int j = 0;
for (; j <= n - 4; j += 4)
{
y0 += w[j + 0] * x[j + 0]; s0 += x[j + 0] * x[j + 0];
y1 += w[j + 1] * x[j + 1]; s1 += x[j + 1] * x[j + 1];
y2 += w[j + 2] * x[j + 2]; s2 += x[j + 2] * x[j + 2];
y3 += w[j + 3] * x[j + 3]; s3 += x[j + 3] * x[j + 3];
}
for (; j < n; j++)
{
y0 += w[j] * x[j];
s0 += x[j] * x[j];
}
*py += (y0 + y1) + (y2 + y3);
*psigma += (s0 + s1) + (s2 + s3);
}
/* Leaky-LMS tap update over the same unit-stride run. */
static inline void anf_update (double* WDSP_RESTRICT w,
const double* WDSP_RESTRICT x, int n, double c0, double c1)
{
int j;
for (j = 0; j < n; j++)
w[j] = c0 * w[j] + c1 * x[j];
}
ANF create_anf (
int run,
int position,
int buff_size,
double *in_buff,
double *out_buff,
int dline_size,
int n_taps,
int delay,
double two_mu,
double gamma,
double lidx,
double lidx_min,
double lidx_max,
double ngamma,
double den_mult,
double lincr,
double ldecr
)
{
ANF a = (ANF) malloc0 (sizeof(anf));
a->run = run;
a->position = position;
a->buff_size = buff_size;
a->in_buff = in_buff;
a->out_buff = out_buff;
a->dline_size = dline_size;
a->mask = dline_size - 1;
a->n_taps = n_taps;
a->delay = delay;
a->two_mu = two_mu;
a->gamma = gamma;
a->in_idx = 0;
a->lidx = lidx;
a->lidx_min = lidx_min;
a->lidx_max = lidx_max;
a->ngamma = ngamma;
a->den_mult = den_mult;
a->lincr = lincr;
a->ldecr = ldecr;
memset (a->d, 0, sizeof(double) * ANF_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANF_DLINE_SIZE);
return a;
}
void destroy_anf (ANF a)
{
_aligned_free (a);
}
void xanf(ANF a, int position)
{
int i;
double c0, c1;
double y, error, sigma, inv_sigp;
double nel, nev;
if (a->run && (a->position == position))
{
const int n_taps = a->n_taps;
const int dline_size = a->dline_size;
const int mask = a->mask;
const int delay = a->delay;
const int buff_size = a->buff_size;
const double two_mu = a->two_mu;
const double gamma = a->gamma;
const double den_mult = a->den_mult;
const double lincr = a->lincr;
const double ldecr = a->ldecr;
const double lidx_min = a->lidx_min;
const double lidx_max = a->lidx_max;
/* in_buff and out_buff are the same buffer in RXA, so neither may be
marked restrict; d and w are private to the struct. */
const double* in_buff = a->in_buff;
double* out_buff = a->out_buff;
double* WDSP_RESTRICT d = a->d;
double* WDSP_RESTRICT w = a->w;
int in_idx = a->in_idx;
double lidx = a->lidx;
double ngamma = a->ngamma;
for (i = 0; i < buff_size; i++)
{
double dsamp;
int base, n1;
dsamp = in_buff[2 * i + 0];
d[in_idx] = dsamp;
base = (in_idx + delay) & mask;
if ((n1 = dline_size - base) > n_taps) n1 = n_taps;
y = 0.0;
sigma = 0.0;
anf_dot (w, d + base, n1, &y, &sigma);
if (n1 < n_taps)
anf_dot (w + n1, d, n_taps - n1, &y, &sigma);
inv_sigp = 1.0 / (sigma + 1e-10);
error = dsamp - y;
out_buff[2 * i + 0] = error;
out_buff[2 * i + 1] = 0.0;
if((nel = error * (1.0 - two_mu * sigma * inv_sigp)) < 0.0) nel = -nel;
if((nev = dsamp - (1.0 - two_mu * ngamma) * y - two_mu * error * sigma * inv_sigp) < 0.0) nev = -nev;
if (nev < nel)
{
if ((lidx += lincr) > lidx_max) lidx = lidx_max;
}
else
{
if ((lidx -= ldecr) < lidx_min) lidx = lidx_min;
}
ngamma = gamma * (lidx * lidx) * (lidx * lidx) * den_mult;
c0 = 1.0 - two_mu * ngamma;
c1 = two_mu * error * inv_sigp;
anf_update (w, d + base, n1, c0, c1);
if (n1 < n_taps)
anf_update (w + n1, d, n_taps - n1, c0, c1);
in_idx = (in_idx + mask) & mask;
}
a->in_idx = in_idx;
a->lidx = lidx;
a->ngamma = ngamma;
}
else if (a->in_buff != a->out_buff)
memcpy (a->out_buff, a->in_buff, a->buff_size * sizeof (complex));
}
void flush_anf (ANF a)
{
memset (a->d, 0, sizeof(double) * ANF_DLINE_SIZE);
memset (a->w, 0, sizeof(double) * ANF_DLINE_SIZE);
a->in_idx = 0;
}
void setBuffers_anf (ANF a, double* in, double* out)
{
a->in_buff = in;
a->out_buff = out;
}
void setSamplerate_anf (ANF a, int rate)
{
flush_anf (a);
}
void setSize_anf (ANF a, int size)
{
a->buff_size = size;
flush_anf (a);
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT void
SetRXAANFRun (int channel, int run)
{
ANF a = rxa[channel].anf.p;
if (a->run != run)
{
RXAbp1Check (channel, rxa[channel].amd.p->run, rxa[channel].snba.p->run,
rxa[channel].emnr.p->run, run, rxa[channel].anr.p->run,
rxa[channel].rnnr.p->run, rxa[channel].sbnr.p->run); // NR3 + NR4 support
EnterCriticalSection (&ch[channel].csDSP);
a->run = run;
RXAbp1Set (channel);
flush_anf (a);
LeaveCriticalSection (&ch[channel].csDSP);
}
}
PORT void
SetRXAANFVals (int channel, int taps, int delay, double gain, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->n_taps = taps;
rxa[channel].anf.p->delay = delay;
rxa[channel].anf.p->two_mu = gain; //try two_mu = 1e-4
rxa[channel].anf.p->gamma = leakage; //try gamma = 0.10
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFTaps (int channel, int taps)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->n_taps = taps;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFDelay (int channel, int delay)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->delay = delay;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFGain (int channel, double gain)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->two_mu = gain;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFLeakage (int channel, double leakage)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->gamma = leakage;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT void
SetRXAANFPosition (int channel, int position)
{
EnterCriticalSection (&ch[channel].csDSP);
rxa[channel].anf.p->position = position;
rxa[channel].bp1.p->position = position;
flush_anf (rxa[channel].anf.p);
LeaveCriticalSection (&ch[channel].csDSP);
}