ms: add sigproclib demod

This is basically a fixed version of ttsous ancient branch that can be
used instead of the VA. Required config option part of a future
patchset.

Change-Id: I6558992bd69f18526be5ebe7d424ca00ceb67772
This commit is contained in:
Eric Wild 2024-02-21 19:33:09 +01:00
parent 56c7b777f3
commit d8a1dee2c9
7 changed files with 406 additions and 11 deletions

View File

@ -55,12 +55,15 @@ const BitVector GSM::gEdgeTrainingSequence[] = {
};
const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
const BitVector GSM::gDummyBurstTSC("01110001011100010111000101");
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000"); /* GSM, GMSK (default) */
const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101"); /* EGPRS, 8-PSK */
const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111"); /* EGPRS, GMSK */
const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
// |-head-||---------midamble----------------------||--------------data----------------||t|
const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");

View File

@ -52,11 +52,16 @@ extern const BitVector gEdgeTrainingSequence[];
/** C0T0 filler burst, GSM 05.02, 5.2.6 */
extern const BitVector gDummyBurst;
extern const BitVector gDummyBurstTSC;
/** Random access burst synch. sequence */
extern const BitVector gRACHSynchSequenceTS0;
extern const BitVector gRACHSynchSequenceTS1;
extern const BitVector gRACHSynchSequenceTS2;
/** Synchronization burst sync sequence */
extern const BitVector gSCHSynchSequence;
/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
extern const BitVector gRACHBurst;

View File

@ -32,7 +32,7 @@ extern "C" {
#define M_PI 3.14159265358979323846264338327f
#endif
#define MAX_OUTPUT_LEN 4096
#define MAX_OUTPUT_LEN 4096*4
using namespace std;

View File

@ -19,6 +19,8 @@
*
*/
#include "sigProcLib.h"
#include "signalVector.h"
#include <atomic>
#include <cassert>
#include <complex>
@ -155,12 +157,12 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
auto current_gsm_time = timekeeper.gsmtime();
const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN;
const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer;
memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
#if 1
const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2];
const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer);
std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
int start;
memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale));
if (is_first_sch_acq) {
float max_corr = 0;
@ -173,9 +175,22 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits);
auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq);
#if 0
auto burst = new signalVector(buf_len, 50);
const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
struct estim_burst_params ebp;
// scale like uhd, +-2k -> +-32k
convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
int howmuchdelay = ebp.toa * 4;
std::cerr << "ooffs: " << howmuchdelay << " " << std::endl;
std::cerr << "voffs: " << start << " " << sch_decode_success << std::endl;
#endif
if (sch_decode_success) {
const auto ts_offset_symb = 0;
const auto ts_offset_symb = 4;
if (is_first_sch_acq) {
// update ts to first sample in sch buffer, to allow delay calc for current ts
first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1;
@ -190,6 +205,97 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN()
<< ":" << current_gsm_time.TN() << std::endl;
}
#else
const auto ts_offset_symb = 4;
auto burst = new signalVector(buf_len, 50);
const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL;
struct estim_burst_params ebp;
// scale like uhd, +-2k -> +-32k
convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR);
auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp);
int howmuchdelay = ebp.toa * 4;
if (!rv) {
delete burst;
DBGLG() << "SCH : \x1B[31m detect fail \033[0m NOOOOOOOOOOOOOOOOOO toa:" << ebp.toa << " "
<< current_gsm_time.FN() << ":" << current_gsm_time.TN() << std::endl;
return false;
}
SoftVector *bits;
if (is_first_sch_acq) {
// can't be legit with a buf size spanning _at least_ one SCH but delay that implies partial sch burst
if (howmuchdelay < 0 || (buf_len - howmuchdelay) < ONE_TS_BURST_LEN) {
delete burst;
return false;
}
struct estim_burst_params ebp2;
// auto sch_chunk = new signalVector(ONE_TS_BURST_LEN, 50);
// auto sch_chunk_start = sch_chunk->begin();
// memcpy(sch_chunk_start, sch_buf_f.data() + howmuchdelay, sizeof(std::complex<float>) * ONE_TS_BURST_LEN);
auto delay = delayVector(burst, NULL, -howmuchdelay);
scaleVector(*delay, (complex)1.0 / ebp.amp);
auto rv2 = detectSCHBurst(*delay, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp2);
DBGLG() << "FIRST SCH : " << (rv2 ? "yes " : " ") << "Timing offset " << ebp2.toa << " symbols"
<< std::endl;
bits = demodAnyBurst(*delay, SCH, 4, &ebp2);
delete delay;
} else {
bits = demodAnyBurst(*burst, SCH, 4, &ebp);
}
delete burst;
// clamp to +-1.5 because +-127 softbits scaled by 64 after -0.5 can be at most +-1.5
clamp_array(bits->begin(), 148, 1.5f);
float_to_sbit(&bits->begin()[0], (signed char *)&sch_demod_bits[0], 62, 148);
// float_to_sbit(&bits->begin()[106], &data[39], 62, 39);
if (decode_sch((char *)sch_demod_bits, is_first_sch_acq)) {
auto current_gsm_time_updated = timekeeper.gsmtime();
if (is_first_sch_acq) {
// update ts to first sample in sch buffer, to allow delay calc for current ts
first_sch_ts_start = first_sch_buf_rcv_ts + howmuchdelay - (ts_offset_symb * 4);
} else {
// continuous sch tracking, only update if off too much
auto diff = [](float x, float y) { return x > y ? x - y : y - x; };
auto d = diff(ebp.toa, ts_offset_symb);
if (abs(d) > 0.3) {
if (ebp.toa < ts_offset_symb)
ebp.toa = d;
else
ebp.toa = -d;
temp_ts_corr_offset += ebp.toa * 4;
DBGLG() << "offs: " << ebp.toa << " " << temp_ts_corr_offset << std::endl;
}
}
auto a = gsm_sch_check_fn(current_gsm_time_updated.FN() - 1);
auto b = gsm_sch_check_fn(current_gsm_time_updated.FN());
auto c = gsm_sch_check_fn(current_gsm_time_updated.FN() + 1);
DBGLG() << "L SCH : Timing offset " << rv << " " << ebp.toa << " " << a << b << c << "fn "
<< current_gsm_time_updated.FN() << ":" << current_gsm_time_updated.TN() << std::endl;
delete bits;
return true;
} else {
DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << ebp.toa << " " << current_gsm_time.FN()
<< ":" << current_gsm_time.TN() << std::endl;
}
delete bits;
#endif
return false;
}

View File

@ -199,6 +199,7 @@ bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
return true;
}
#if 1
convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
pow = energyDetect(sv, 20 * 4 /*sps*/);
@ -232,6 +233,42 @@ bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
// detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
#endif
}
#else
// lower layer sch detection offset, easy to verify by just printing the detected value using both the va+sigproc code.
convert_and_scale(ss + 16, e.burst, ONE_TS_BURST_LEN * 2, 15);
pow = energyDetect(sv, 20 * 4 /*sps*/);
if (pow < -1) {
LOG(ALERT) << "Received empty burst";
return false;
}
avg = sqrt(pow);
/* Detect normal or RACH bursts */
CorrType type = CorrType::TSC;
struct estim_burst_params ebp;
auto rc = detectAnyBurst(sv, mTSC, 3, 4, type, 48, &ebp);
if (rc > 0) {
type = (CorrType)rc;
}
if (rc < 0) {
std::cerr << "UR : \x1B[31m rx fail \033[0m @ toa:" << ebp.toa << " " << e.gsmts.FN() << ":"
<< e.gsmts.TN() << std::endl;
return false;
}
SoftVector *bits = demodAnyBurst(sv, type, 4, &ebp);
SoftVector::const_iterator burstItr = bits->begin();
// invert and fix to +-127 sbits
for (int ii = 0; ii < 148; ii++) {
demodded_softbits[ii] = *burstItr++ > 0.0f ? -127 : 127;
}
delete bits;
#endif
RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
// FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway...
timingOffset = (int)round(0);

View File

@ -129,6 +129,8 @@ struct PulseSequence {
static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
static CorrelationSequence *gSCHSequence = NULL;
static CorrelationSequence *gDummySequence = NULL;
static PulseSequence *GSMPulse1 = NULL;
static PulseSequence *GSMPulse4 = NULL;
@ -151,6 +153,12 @@ void sigProcLibDestroy()
gRACHSequences[i] = NULL;
}
delete gSCHSequence;
gSCHSequence = NULL;
delete gDummySequence;
gDummySequence = NULL;
delete GMSKRotation1;
delete GMSKReverseRotation1;
delete GMSKRotation4;
@ -315,6 +323,7 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
append = true;
break;
case CUSTOM:
// FIXME: x->getstart?
if (start < h->size() - 1) {
head = h->size() - start;
append = true;
@ -1289,6 +1298,77 @@ release:
return status;
}
static bool generateDummyMidamble(int sps)
{
bool status = true;
float toa;
complex *data = NULL;
signalVector *autocorr = NULL, *midamble = NULL;
signalVector *midMidamble = NULL, *_midMidamble = NULL;
delete gDummySequence;
/* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */
midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true);
if (!midMidamble)
return false;
/* Simulated receive sequence is pulse shaped */
midamble = modulateBurst(gDummyBurstTSC, 0, sps, false);
if (!midamble) {
status = false;
goto release;
}
// NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst,
// the ideal TSC has an + 180 degree phase shift,
// due to the pi/2 frequency shift, that
// needs to be accounted for.
// 26-midamble is 61 symbols into burst, has +90 degree phase shift.
scaleVector(*midMidamble, complex(-1.0, 0.0));
scaleVector(*midamble, complex(0.0, 1.0));
conjugateVector(*midMidamble);
/* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
data = (complex *) convolve_h_alloc(midMidamble->size());
_midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free);
_midMidamble->setAligned(true);
midMidamble->copyTo(*_midMidamble);
autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
if (!autocorr) {
status = false;
goto release;
}
gDummySequence = new CorrelationSequence;
gDummySequence->sequence = _midMidamble;
gDummySequence->gain = peakDetect(*autocorr, &toa, NULL);
/* For 1 sps only
* (Half of correlation length - 1) + midpoint of pulse shape + remainder
* 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2
*/
if (sps == 1)
gDummySequence->toa = toa - 13.5;
else
gDummySequence->toa = 0;
release:
delete autocorr;
delete midamble;
delete midMidamble;
if (!status) {
delete _midMidamble;
free(data);
gDummySequence = NULL;
}
return status;
}
static CorrelationSequence *generateEdgeMidamble(int tsc)
{
complex *data = NULL;
@ -1384,6 +1464,69 @@ release:
return status;
}
bool generateSCHSequence(int sps)
{
bool status = true;
float toa;
complex *data = NULL;
signalVector *autocorr = NULL;
signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
delete gSCHSequence;
seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false);
if (!seq0)
return false;
seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true);
if (!seq1) {
status = false;
goto release;
}
conjugateVector(*seq1);
/* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
data = (complex *) convolve_h_alloc(seq1->size());
_seq1 = new signalVector(data, 0, seq1->size(), convolve_h_alloc, free);
_seq1->setAligned(true);
seq1->copyTo(*_seq1);
autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
if (!autocorr) {
status = false;
goto release;
}
gSCHSequence = new CorrelationSequence;
gSCHSequence->sequence = _seq1;
gSCHSequence->buffer = data;
gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL);
/* For 1 sps only
* (Half of correlation length - 1) + midpoint of pulse shaping filer
* 20.5 = (64 / 2 - 1) + 1.5
*/
if (sps == 1)
gSCHSequence->toa = toa - 32.5;
else
gSCHSequence->toa = 0.0;
release:
delete autocorr;
delete seq0;
delete seq1;
if (!status) {
delete _seq1;
free(data);
gSCHSequence = NULL;
}
return status;
}
/*
* Peak-to-average computation +/- range from peak in symbols
*/
@ -1441,14 +1584,15 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength)
return energy/windowLength;
}
static signalVector *downsampleBurst(const signalVector &burst)
static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN,
int out_len = DOWNSAMPLE_OUT_LEN)
{
signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
signalVector in(in_len, dnsampler->len());
// gSCHSequence->sequence->size(), ensure next conv has no realloc
signalVector *out = new signalVector(out_len, 64);
burst.copyToSegment(in, 0, in_len);
if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
(float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
delete out;
out = NULL;
}
@ -1469,6 +1613,12 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn
/* Integer position where the sequence starts */
const int ps = start + 1 - N + (int)roundf(toa);
if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
return 0;
if (ps + N > (int)burst->size())
return 0;
/* Estimate Signal power */
S = 0.0f;
for (int i=0, j=ps; i<(int)N; i++,j++)
@ -1652,6 +1802,80 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
return rc;
}
int detectSCHBurst(signalVector &burst,
float thresh,
int sps,
sch_detect_type state, struct estim_burst_params *ebp)
{
int rc, start, target, head, tail, len;
complex _amp;
CorrelationSequence *sync;
if ((sps != 1) && (sps != 4))
return -1;
target = 3 + 39 + 64;
switch (state) {
case sch_detect_type::SCH_DETECT_NARROW:
head = 4;
tail = 4;
break;
case sch_detect_type::SCH_DETECT_BUFFER:
target = 1;
head = 0;
tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps
break;
case sch_detect_type::SCH_DETECT_FULL:
default:
head = target - 1;
tail = 39 + 3 + 9;
break;
}
start = (target - head) * 1 - 1;
len = (head + tail) * 1;
sync = gSCHSequence;
signalVector corr(len);
signalVector *dec = downsampleBurst(burst, len * 4, len);
rc = detectBurst(*dec, corr, sync, thresh, 1, start, len, ebp);
delete dec;
if (rc < 0) {
return -1;
} else if (!rc) {
ebp->amp = 0.0f;
ebp->toa = 0.0f;
return 0;
}
if (state == sch_detect_type::SCH_DETECT_BUFFER)
ebp->toa = ebp->toa - (3 + 39 + 64);
else {
/* Subtract forward search bits from delay */
ebp->toa = ebp->toa - head;
}
return rc;
}
static int detectDummyBurst(const signalVector &burst, float threshold,
int sps, unsigned max_toa, struct estim_burst_params *ebp)
{
int rc, target, head, tail;
CorrelationSequence *sync;
target = 3 + 58 + 16 + 5;
head = 10;
tail = 6 + max_toa;
sync = gDummySequence;
ebp->tsc = 0;
rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
return rc;
}
/*
* Normal burst detection
*
@ -1670,7 +1894,7 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th
return -SIGERR_UNSUPPORTED;
target = 3 + 58 + 16 + 5;
head = 6;
head = 10;
tail = 6 + max_toa;
sync = gMidambles[tsc];
@ -1719,6 +1943,9 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold,
case RACH:
rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
break;
case IDLE:
rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp);
break;
default:
LOG(ERR) << "Invalid correlation type";
}
@ -1921,6 +2148,9 @@ bool sigProcLibSetup()
generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
generateSCHSequence(1);
generateDummyMidamble(1);
for (int tsc = 0; tsc < 8; tsc++) {
generateMidamble(1, tsc);
gEdgeMidambles[tsc] = generateEdgeMidamble(tsc);

View File

@ -31,6 +31,7 @@ enum CorrType{
TSC, ///< timeslot should contain a normal burst
EXT_RACH, ///< timeslot should contain an extended access burst
RACH, ///< timeslot should contain an access burst
SCH,
EDGE, ///< timeslot should contain an EDGE burst
IDLE ///< timeslot is an idle (or dummy) burst
};
@ -93,6 +94,8 @@ signalVector *generateDummyBurst(int sps, int tn);
void scaleVector(signalVector &x,
complex scale);
signalVector *delayVector(const signalVector *in, signalVector *out, float delay);
/**
Rough energy estimator.
@param rxBurst A GSM burst.
@ -133,6 +136,17 @@ int detectAnyBurst(const signalVector &burst,
unsigned max_toa,
struct estim_burst_params *ebp);
enum class sch_detect_type {
SCH_DETECT_FULL,
SCH_DETECT_NARROW,
SCH_DETECT_BUFFER,
};
int detectSCHBurst(signalVector &rxBurst,
float detectThreshold,
int sps,
sch_detect_type state, struct estim_burst_params *ebp);
/** Demodulate burst basde on type and output soft bits */
SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
int sps, struct estim_burst_params *ebp);