Compare commits

...

7 Commits

Author SHA1 Message Date
Alexander Chemeris
2061843c3d osmo-trx-dec: Offline demodulation tool.
Change-Id: Ic5b59c7fe1a0c02d962b36de2fd5d7fc9a02f266
2017-06-01 21:19:53 +03:00
Alexander Chemeris
63d0f2a496 WIP:sigProcLib: Reduce burst detection window for NB.
Otherwise we detect bursts with search window far beyond specified.

Change-Id: If3cb40d2311504a13c03e1fbccad663ac201d9a4
2017-06-01 21:19:53 +03:00
Alexander Chemeris
6459ddc55c transceiver: RSSI was calculated reverse. 2017-06-01 21:19:53 +03:00
Alexander Chemeris
7a33c7221b osmo-trx-gen: generates waveform files aka IQ binary files in a number of formats.
Generated files can be used as an input to osmo-trx-dec or load them into signal
generators.

Change-Id: I555d99a632755b5bfcbaf3501a501613c2859d4e
2017-06-01 21:19:53 +03:00
Alexander Chemeris
dd62d4baa0 WIP:sigProcLib: Use a known PRBS to generate random Normal Bursts.
ToDo:
1) Add seed randomization.
2) An option to use a longer PRBS?
3) Use a known PRBS for other types of bursts.

Change-Id: Ib7fdf2f415457da38b78129532d5b80a4a94ecd3
2017-06-01 00:51:24 +03:00
Alexander Chemeris
09130c81a7 osmo-trx: Separate command line switch to enable EDGE filler.
Now -r comand line switch always enables GMSK filler even when EDGE mode is
enabled with -e switch. If you want to enable EDGE filler, use -E switch.

Change-Id: Ic8808bbe3f06740ef3fec1d1865ecb57fbcfabab
2017-06-01 00:51:24 +03:00
Alexander Chemeris
0c4e24d197 PRBS: a Pseudo-random binary sequence (PRBS) generator class.
Implemeted with a Galois LFSR for speed and flexibility compared to Fibonacci version.

Aliases for three popular PRBS' are added for convenience - PRBS9, PRBS15 and PRBS64.

Note that we can't test PRBS64 completely, because the sequence is too long to
be generated.

Change-Id: Ib5331ba5d0b5819929541686fdd87905e2177b74
2017-05-31 17:49:09 +03:00
13 changed files with 1055 additions and 13 deletions

3
.gitignore vendored
View File

@@ -3,6 +3,8 @@
*.lo
*.la
Transceiver52M/osmo-trx
Transceiver52M/osmo-trx-gen
Transceiver52M/osmo-trx-dec
# tests
CommonLibs/BitVectorTest
@@ -15,6 +17,7 @@ CommonLibs/SocketsTest
CommonLibs/TimevalTest
CommonLibs/URLEncodeTest
CommonLibs/VectorTest
CommonLibs/PRBSTest
# automake/autoconf
*.in

View File

@@ -42,6 +42,7 @@ libcommon_la_SOURCES = \
noinst_PROGRAMS = \
BitVectorTest \
PRBSTest \
InterthreadTest \
SocketsTest \
TimevalTest \
@@ -53,6 +54,7 @@ noinst_PROGRAMS = \
noinst_HEADERS = \
BitVector.h \
PRBS.h \
Interthread.h \
LinkedLists.h \
Sockets.h \
@@ -66,6 +68,8 @@ noinst_HEADERS = \
BitVectorTest_SOURCES = BitVectorTest.cpp
BitVectorTest_LDADD = libcommon.la $(SQLITE3_LIBS)
PRBSTest_SOURCES = PRBSTest.cpp
InterthreadTest_SOURCES = InterthreadTest.cpp
InterthreadTest_LDADD = libcommon.la
InterthreadTest_LDFLAGS = -lpthread

110
CommonLibs/PRBS.h Normal file
View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef PRBS_H
#define PRBS_H
#include <stdint.h>
#include <assert.h>
/** Pseudo-random binary sequence (PRBS) generator (a Galois LFSR implementation). */
class PRBS {
public:
PRBS(unsigned wLen, uint64_t wCoeff, uint64_t wState = 0x01)
: mCoeff(wCoeff), mStartState(wState), mState(wState), mLen(wLen)
{ assert(wLen<=64); }
/**@name Accessors */
//@{
uint64_t coeff() const { return mCoeff; }
uint64_t state() const { return mState; }
void state(uint64_t state) { mState = state & mask(); }
unsigned size() const { return mLen; }
//@}
/**
Calculate one bit of a PRBS
*/
unsigned generateBit()
{
const unsigned result = mState & 0x01;
processBit(result);
return result;
}
/**
Update the generator state by one bit.
If you want to synchronize your PRBS to a known state, call this function
size() times passing your PRBS to it bit by bit.
*/
void processBit(unsigned inBit)
{
mState >>= 1;
if (inBit) mState ^= mCoeff;
}
/** Return true when PRBS is wrapping through initial state */
bool isFinished() const { return mStartState == mState; }
protected:
uint64_t mCoeff; ///< polynomial coefficients. LSB is zero exponent.
uint64_t mStartState; ///< initial shift register state.
uint64_t mState; ///< shift register state.
unsigned mLen; ///< number of bits used in shift register
/** Return mask for the state register */
uint64_t mask() const { return (mLen==64)?0xFFFFFFFFFFFFFFFFUL:((1<<mLen)-1); }
};
/**
A standard 9-bit based pseudorandom binary sequence (PRBS) generator.
Polynomial: x^9 + x^5 + 1
*/
class PRBS9 : public PRBS {
public:
PRBS9(uint64_t wState = 0x01)
: PRBS(9, 0x0110, wState)
{}
};
/**
A standard 15-bit based pseudorandom binary sequence (PRBS) generator.
Polynomial: x^15 + x^14 + 1
*/
class PRBS15 : public PRBS {
public:
PRBS15(uint64_t wState = 0x01)
: PRBS(15, 0x6000, wState)
{}
};
/**
A standard 64-bit based pseudorandom binary sequence (PRBS) generator.
Polynomial: x^64 + x^63 + x^61 + x^60 + 1
*/
class PRBS64 : public PRBS {
public:
PRBS64(uint64_t wState = 0x01)
: PRBS(64, 0xD800000000000000ULL, wState)
{}
};
#endif // PRBS_H

42
CommonLibs/PRBSTest.cpp Normal file
View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PRBS.h"
#include <iostream>
#include <cstdlib>
#include <assert.h>
void testPrbs(PRBS &prbs, uint64_t expectedPeriod)
{
uint64_t period = 0;
do {
std::cout << prbs.generateBit();
period++;
} while (!prbs.isFinished());
std::cout << std::endl;
std::cout << "Period: " << period << std::endl;
assert(period == expectedPeriod);
}
int main(int argc, char *argv[])
{
PRBS9 prbs9(0x01);
testPrbs(prbs9, (1<<9)-1);
PRBS15 prbs15(0x01);
testPrbs(prbs15, (1<<15)-1);
}

View File

@@ -69,7 +69,10 @@ libtransceiver_la_SOURCES = \
radioInterfaceResamp.cpp \
radioInterfaceMulti.cpp
bin_PROGRAMS = osmo-trx
bin_PROGRAMS = \
osmo-trx \
osmo-trx-gen \
osmo-trx-dec
noinst_HEADERS = \
Complex.h \
@@ -99,6 +102,20 @@ osmo_trx_LDADD = \
$(GSM_LA) \
$(COMMON_LA) $(SQLITE3_LIBS)
osmo_trx_gen_SOURCES = osmo-trx-gen.cpp
osmo_trx_gen_LDADD = \
libtransceiver.la \
$(ARCH_LA) \
$(GSM_LA) \
$(COMMON_LA) $(SQLITE_LA)
osmo_trx_dec_SOURCES = osmo-trx-dec.cpp
osmo_trx_dec_LDADD = \
libtransceiver.la \
$(ARCH_LA) \
$(GSM_LA) \
$(COMMON_LA) $(SQLITE_LA)
if USRP1
libtransceiver_la_SOURCES += USRPDevice.cpp
osmo_trx_LDADD += $(USRP_LIBS)

View File

@@ -85,7 +85,7 @@ bool TransceiverState::init(int filler, size_t sps, float scale, size_t rtsc, un
burst = generateDummyBurst(sps, n);
break;
case Transceiver::FILLER_NORM_RAND:
burst = genRandNormalBurst(rtsc, sps, n);
burst = genRandNormalBurst(rtsc, sps, n, mPrbs);
break;
case Transceiver::FILLER_EDGE_RAND:
burst = generateEdgeBurst(rtsc);
@@ -607,7 +607,7 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime, double &RSSI, bool &i
avg = sqrt(avg / radio_burst->chans());
wTime = time;
RSSI = 20.0 * log10(rxFullScale / avg);
RSSI = 20.0 * log10(avg / rxFullScale);
/* RSSI estimation are valid */
isRssiValid = true;

View File

@@ -84,6 +84,9 @@ struct TransceiverState {
/* Shadowed downlink attenuation */
int mPower;
/* Pseudorandom bit sequence */
PRBS9 mPrbs;
};
/** The Transceiver class, responsible for physical layer of basestation */

View File

@@ -0,0 +1,520 @@
/*
* Copyright (C) 2016-2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <limits.h>
#include <fstream>
#include <iomanip>
#include "Logger.h"
#include "sigProcLib.h"
#include "signalVector.h"
#include "Transceiver.h"
#include "Configuration.h"
extern "C" {
#include "convolve.h"
#include "convert.h"
}
#define DEFAULT_RX_SPS 1
#define DEFAULT_SEARCH_WINDOW 30
// Tail + data + stealing + midamble + guard (without the last 0.25)
#define BURST_LEN_FULL 156
// Tail + data + stealing + midamble
#define BURST_LEN_ACTIVE 148
// Tail + data + stealing + midamble - 2*0.5
#define BURST_LEN_USEFUL 147
// Size of a sample in bytes as stores in a file
#define SAMPLE_SIZE_BYTES (2 * sizeof(float))
// Burst length in bytes as stored in a file
#define BURST_LEN_BYTES (BURST_LEN_FULL * SAMPLE_SIZE_BYTES)
ConfigurationTable gConfig;
struct trx_config {
std::string log_level;
unsigned sps;
unsigned tsc;
unsigned max_expected_delay_nb;
unsigned max_expected_delay_ab;
double full_scale;
bool edge;
CorrType type;
std::string filename;
unsigned ber_burst_avg; ///< Average BER over this many bursts.
///< Set to 0 to average for the whole duration.
};
class NormalBurstSoftbitMask {
public:
NormalBurstSoftbitMask(SoftVector &softBits)
: mSoftBits(softBits)
{
}
SoftVector &bits() { return mSoftBits; }
SoftVector tailBitsL() { return mSoftBits.segment(0,3); }
SoftVector dataBitsL() { return mSoftBits.segment(3,57); }
SoftVector stealingBitsL() { return mSoftBits.segment(60, 1); }
SoftVector midambleBits() { return mSoftBits.segment(61, 26); }
SoftVector stealingBitsR() { return mSoftBits.segment(87, 1); }
SoftVector dataBitsR() { return mSoftBits.segment(88,57); }
SoftVector tailBitsR() { return mSoftBits.segment(145,3); }
SoftVector guardBits() { return mSoftBits.segment(148,8); }
protected:
SoftVector &mSoftBits;
};
class SoftBurst {
public:
SoftBurst(SoftVector *softBits, double toa=0)
: mSoftBits(softBits), mTOA(toa)
{
assert(mSoftBits != NULL);
}
~SoftBurst()
{
delete mSoftBits;
}
void TOA(double TOA) { mTOA = TOA; }
double TOA() { return mTOA; }
NormalBurstSoftbitMask normalBurstMask() { return NormalBurstSoftbitMask(*mSoftBits); }
protected:
SoftVector *mSoftBits;
double mTOA;
};
class BEREstimator {
public:
BEREstimator(const PRBS& prbs)
: mPRBS(prbs), mTotalBits(0), mErrorBits(0), mSynchronized(false)
{}
unsigned synchronize(const BitVector &bits)
{
for (unsigned i=0; i<mPRBS.size(); i++) {
mPRBS.processBit(bits[i]);
}
mSynchronized = true;
return mPRBS.size();
}
void process(const BitVector &bits, size_t start_from = 0)
{
for (size_t i=start_from; i<bits.size(); i++) {
mTotalBits++;
if (mPRBS.generateBit() != bits.bit(i)) {
mErrorBits++;
}
}
}
void sync_and_process(const BitVector &bits)
{
unsigned skip = 0;
if (!mSynchronized) {
skip = synchronize(bits);
}
process(bits, skip);
}
void skip(size_t num)
{
for (size_t i=0; i<num; i++) {
mTotalBits++;
mErrorBits++;
mPRBS.generateBit();
}
}
void reset()
{
mTotalBits = 0;
mErrorBits = 0;
}
unsigned totalBits() const { return mTotalBits; }
unsigned errorBits() const { return mErrorBits; }
double BER() const { return mErrorBits/(double)mTotalBits; }
bool isSynchronized() const {return mSynchronized; }
protected:
PRBS mPRBS;
unsigned mTotalBits;
unsigned mErrorBits;
bool mSynchronized;
};
double getBurstRSSI(const signalVector &burst, unsigned sps, double full_scale)
{
/* Calculate average power of the burst */
float avg = energyDetect(burst, 20 * sps);
return 20.0 * log10(sqrt(avg) / full_scale);
}
void printDetectionResult(int rc)
{
if (rc > 0) {
std::cout << "Detected correlation type: " << (CorrType)rc << std::endl;
} else {
if (rc == -SIGERR_CLIP) {
std::cout << "Clipping detected on received RACH or Normal Burst" << std::endl;
} else if (rc != SIGERR_NONE) {
std::cout << "Unhandled RACH or Normal Burst detection error" << std::endl;
} else {
// std::cout << "No burst detected" << std::endl;
}
}
}
SoftVector *demodulateBurst(const signalVector &burst,
CorrType expected_type,
unsigned sps, unsigned tsc,
unsigned max_expected_delay,
double &timingOffset)
{
complex amp;
float toa;
int rc;
CorrType detected_type;
/* Detect normal or RACH bursts */
rc = detectAnyBurst(burst, tsc, BURST_THRESH, sps, expected_type, amp, toa,
max_expected_delay);
printDetectionResult(rc);
if (rc <= 0) {
return NULL;
}
// Convert samples to symbols
timingOffset = toa / sps;
// rc > 0 means it's a detected CorrType
detected_type = (CorrType)rc;
return demodAnyBurst(burst, sps, amp, toa, detected_type);
}
static bool processBurst(const trx_config &config, signalVector &burst,
unsigned max_expected_delay,
double &RSSI,
double &timingOffset,
BEREstimator &berEstimator)
{
RSSI = getBurstRSSI(burst, config.sps, config.full_scale);
SoftVector *softBits = demodulateBurst(burst, config.type, config.sps,config.tsc,
max_expected_delay, timingOffset);
/* Print burst information and content */
if (softBits == NULL) {
std::cout << "Skipped frame" << std::endl;
// TODO: This is different for EDGE
berEstimator.skip(57*2);
return false;
}
SoftBurst softBurst(softBits, timingOffset);
NormalBurstSoftbitMask nb = softBurst.normalBurstMask();
berEstimator.sync_and_process(nb.dataBitsL().sliced());
berEstimator.sync_and_process(nb.dataBitsR().sliced());
std::cout << "TOA: " << softBurst.TOA() << " symbols" << std::endl;
// Exclude tail and guard bits from the energy calculation
std::cout << "Energy: " << softBits->segment(3,142).getEnergy() << std::endl;
//std::cout << "Demodulated burst: " << *softBits << std::endl;
std::cout << " tail|--------------------------data---------------------------|f|--------midamble----------|f|--------------------------data---------------------------|tai|-guard--" << std::endl;
// " 000 010001011011110011101001100100000001010001011000100100010 0 11101111000100101110111100 0 011010111011101010011010111000101100001110101011011001011 000 1''..---"
std::cout << "Demodulated burst:"
<< " " << nb.tailBitsL()
<< " " << nb.dataBitsL()
<< " " << nb.stealingBitsL()
<< " " << nb.midambleBits()
<< " " << nb.stealingBitsR()
<< " " << nb.dataBitsR()
<< " " << nb.tailBitsR()
<< " " << nb.guardBits()
<< std::endl;
return true;
}
// Setup configuration values
static void print_config(struct trx_config *config)
{
std::ostringstream ost("");
ost << "Config Settings" << std::endl;
ost << " Source file name............. " << config->filename << std::endl;
ost << " Log Level.................... " << config->log_level << std::endl;
ost << " Rx Samples-per-Symbol........ " << config->sps << std::endl;
ost << " EDGE support................. " << (config->edge ? "Enabled" : "Disabled") << std::endl;
ost << " Burst type................... " << config->type << std::endl;
ost << " Burst TSC.................... " << config->tsc << std::endl;
ost << " Normal Burst search window... " << config->max_expected_delay_nb << std::endl;
ost << " Access Burst search window... " << config->max_expected_delay_ab << std::endl;
ost << " Signal full scale............ " << config->full_scale << std::endl;
ost << " BER average window (bursts).. " << config->ber_burst_avg << std::endl;
std::cout << ost << std::endl;
}
static void print_help()
{
fprintf(stdout, "Options:\n"
" -h This text\n"
" -l LEVEL Logging level (%s)\n"
" -e Enable EDGE receiver\n"
" -s SPS Samples-per-symbol (1 or 4, default: %d)\n"
" -t TSC Burst training sequence (0 to 7, default: 0)\n"
" -f FILE File to read\n"
" -w SYMBOLS Normal Burst search window (0 to 156, default: %d)\n"
" -W SYMBOLS Access Burst search window (0 to 156, default: %d)\n"
" -b BURSTS BER average window. Set to 0 to average over the whole file (default: 1)\n",
"EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG",
DEFAULT_RX_SPS,
DEFAULT_SEARCH_WINDOW, DEFAULT_SEARCH_WINDOW);
}
static bool handle_options(int argc, char **argv, struct trx_config *config)
{
int option;
config->log_level = "NOTICE";
config->sps = DEFAULT_RX_SPS;
config->tsc = 0;
config->max_expected_delay_nb = DEFAULT_SEARCH_WINDOW;
config->max_expected_delay_ab = DEFAULT_SEARCH_WINDOW;
config->full_scale = SHRT_MAX;
config->edge = false;
config->type = TSC;
config->ber_burst_avg = 1;
while ((option = getopt(argc, argv, "ls:et:f:w:W:b:h")) != -1) {
switch (option) {
case 'l':
config->log_level = optarg;
break;
case 's':
config->sps = atoi(optarg);
break;
case 'e':
config->edge = true;
break;
case 't':
config->tsc = atoi(optarg);
break;
case 'f':
config->filename = optarg;
break;
case 'w':
config->max_expected_delay_nb = atoi(optarg);
break;
case 'W':
config->max_expected_delay_ab = atoi(optarg);
break;
case 'b':
config->ber_burst_avg = atoi(optarg);
break;
case 'h':
default:
print_help();
exit(0);
}
}
if ((config->sps != 1) && (config->sps != 4)) {
printf("ERROR: Unsupported samples-per-symbol %i\n\n", config->sps);
return false;
}
if (config->edge && (config->sps != 4)) {
printf("ERROR: EDGE only supported at 4 samples per symbol\n\n");
return false;
}
if (config->tsc > 7) {
printf("ERROR: Invalid training sequence %i\n\n", config->tsc);
return false;
}
if (config->filename.length() == 0) {
printf("ERROR: No input file specified\n\n");
return false;
}
if (config->max_expected_delay_nb > 156 || config->max_expected_delay_nb < 0 ||
config->max_expected_delay_ab > 156 || config->max_expected_delay_ab < 0) {
printf("ERROR: Invalid search window size, must be withit [1..156] range\n\n");
return false;
}
return true;
}
int main(int argc, char *argv[])
{
struct trx_config config;
#ifdef HAVE_SSE3
printf("Info: SSE3 support compiled in");
if (__builtin_cpu_supports("sse3"))
printf(" and supported by CPU\n");
else
printf(", but not supported by CPU\n");
#endif
#ifdef HAVE_SSE4_1
printf("Info: SSE4.1 support compiled in");
if (__builtin_cpu_supports("sse4.1"))
printf(" and supported by CPU\n");
else
printf(", but not supported by CPU\n");
#endif
convolve_init();
convert_init();
// Process command line options and print config to screen
if (!handle_options(argc, argv, &config)) {
print_help();
exit(0);
}
print_config(&config);
gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7);
if (!sigProcLibSetup()) {
LOG(ALERT) << "Failed to initialize signal processing library";
return -1;
}
double RSSI;
double timingOffset, timingOffsetPrev = 0.0;
signalVector burst(2*BURST_LEN_FULL);
GSM::Time gsmTime;
bool syncedTo157bits = false; // We should syncronize to 156-157 frame structure only once
bool burst156_157 = false; // Set to true to enable 156-156-156-157 frame
int bitsReadExtra = 0; // set to 1 every 4 bursts and when TOA>1.0
int bitsToSkip = 0; // set to 1 when TOA<0.0
unsigned berBurstsAveraged = 0;
PRBS9 prbs;
BEREstimator berEstimator(prbs);
// Configure output stream
std::cout << std::fixed;
std::cout << std::setprecision(2);
std::ifstream file (config.filename.c_str(), std::ifstream::binary);
// Read the first burst, but do not process it, because we need at least two bursts
// worth of data for reliable initial detection.
file.read((char*)burst.begin(), config.sps * BURST_LEN_BYTES);
{signalVector t = burst.segment(0, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); }
#if 0
/* Distort signal */
{
signalVector burst_read = burst.segment(85,156);
std::ifstream file (config.filename.c_str(), std::ifstream::binary);
file.read((char*)burst_read.begin(), burst_read.size() * 2 * sizeof(float));
file.close();
}
#endif
#if 1
// Read more data and try burst detection until successful
while(file.read((char*)(burst.begin()+config.sps*BURST_LEN_FULL), config.sps*BURST_LEN_BYTES))
{
{signalVector t = burst.segment(BURST_LEN_FULL, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); }
bool found = processBurst(config, burst, BURST_LEN_FULL, RSSI, timingOffset, berEstimator);
std::cout << "RSSI: " << RSSI << " dBFS" << std::endl;
if (found) {
gsmTime.incTN();
berBurstsAveraged++;
break;
}
burst.segmentMove(config.sps*BURST_LEN_FULL, 0, config.sps*BURST_LEN_FULL);
}
// Align stream to burst
int offsetInt = (int)timingOffset;
burst.segmentMove(config.sps*(BURST_LEN_FULL+offsetInt), 0, config.sps*(BURST_LEN_FULL-offsetInt));
{signalVector t = burst.segment(0, BURST_LEN_FULL-offsetInt); scaleVector(t, complex(1.0/SHRT_MAX)); }
file.read((char*)(burst.begin()+config.sps*(BURST_LEN_FULL-offsetInt)), config.sps*offsetInt*SAMPLE_SIZE_BYTES);
#endif
// Resize burst vector to hold only one burst, because demodulation code
// always decode the full vector size.
burst.shrink(BURST_LEN_FULL+1);
// Process the rest of the stream
do {
{signalVector t = burst.segment(0, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); }
processBurst(config, burst, (config.type==RACH)?config.max_expected_delay_ab:config.max_expected_delay_ab,
RSSI, timingOffset, berEstimator);
if (burst156_157 && !syncedTo157bits && timingOffset - timingOffsetPrev > .75) {
std::cout << "TOA adjust: Found a 157-bit burst, reset TN to mark it" << std::endl;
gsmTime.TN(2);
timingOffset -= 1.0;
// Make sure we do this adjustment only once.
syncedTo157bits = true;
} else {
gsmTime.incTN();
}
bitsToSkip = 0;
bitsReadExtra = 0;
if (timingOffset < 0.0) {
std::cout << "TOA adjust: skip a bit" << std::endl;
burst[0] = 0;
bitsToSkip = 1;
bitsReadExtra--;
}
bitsReadExtra += (gsmTime.TN()%4 == 0);
if (timingOffset > 1.1) {
std::cout << "TOA adjust: add extra bit" << std::endl;
bitsReadExtra++;
}
std::cout << "Clock: " << gsmTime;
std::cout << " RSSI: " << RSSI << " dBFS";
std::cout << " Error bits: " << berEstimator.errorBits() << " Total bits: " << berEstimator.totalBits()
<< " BER: " << 100.0*berEstimator.errorBits() / berEstimator.totalBits() << "%" << std::endl;
berBurstsAveraged++;
// Never reset if config.ber_burst_avg is 0
if (config.ber_burst_avg > 0 && berBurstsAveraged >= config.ber_burst_avg) {
berBurstsAveraged = 0;
berEstimator.reset();
}
std::cout << "bitsReadExtra: " << bitsReadExtra << " bitsToSkip: " << bitsToSkip << std::endl;
timingOffsetPrev = timingOffset;
} while(file.read((char*)(burst.begin()+bitsToSkip), config.sps*(BURST_LEN_BYTES+SAMPLE_SIZE_BYTES*bitsReadExtra)));
std::cout << "End of file reached" << std::endl;
file.close();
return 0;
}

View File

@@ -0,0 +1,334 @@
/*
* Copyright (C) 2017 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <limits.h>
#include <fstream>
#include <iomanip>
#include <endian.h> // for byte order manipulation
#include "Logger.h"
#include "sigProcLib.h"
#include "GSMCommon.h"
#include "BitVector.h"
#include "Configuration.h"
extern "C" {
#include "convolve.h"
#include "convert.h"
}
#define DEFAULT_SPS 4
#define DEFAULT_SEARCH_WINDOW 30
// Tail + data + stealing + midamble + guard (without the last 0.25)
#define BURST_LEN_FULL 156
// Tail + data + stealing + midamble
#define BURST_LEN_ACTIVE 148
// Tail + data + stealing + midamble - 2*0.5
#define BURST_LEN_USEFUL 147
// Size of a sample in bytes as stores in a file
#define SAMPLE_SIZE_BYTES (2 * sizeof(float))
// Burst length in bytes as stored in a file
#define BURST_LEN_BYTES (BURST_LEN_FULL * SAMPLE_SIZE_BYTES)
ConfigurationTable gConfig;
enum FileType {
FLOAT_NORM_LE, ///< Float -1..+1 Little Endian
FLOAT16_LE, ///< Float -32767..+32767 Little Endian
SIGNED16_LE, ///< Integer -32767..+32767 Little Endian
SIGNED16_BE, ///< Integer -32767..+32767 Big Endian (Keysight waveform format)
};
struct trx_config {
std::string log_level;
unsigned sps;
unsigned tsc;
double full_scale;
bool edge;
CorrType type;
std::string filename;
FileType file_type;
};
std::ostream& operator<<(std::ostream& os, FileType ftype)
{
switch(ftype)
{
case FLOAT_NORM_LE:
os << "float";
break;
case FLOAT16_LE:
os << "float16";
break;
case SIGNED16_LE:
os << "signed16";
break;
case SIGNED16_BE:
os << "signed16be";
break;
default:
assert(!"unknown file type");
}
return os;
}
void writeBurstFloatNorm(std::ofstream& os, const signalVector& v)
{
os.write((char*)v.begin(), v.size() * 2 * sizeof(float));
}
void writeBurstFloat16LE(std::ofstream& os, const signalVector& v)
{
const complex *c = v.begin();
for (size_t i=0; i<v.size(); i++, c++) {
float iq[2];
iq[0] = c->real()*SHRT_MAX;
iq[1] = c->imag()*SHRT_MAX;
os.write((char*)&iq, 2*sizeof(float));
}
}
void writeBurstSigned16LE(std::ofstream& os, const signalVector& v)
{
const complex *c = v.begin();
for (size_t i=0; i<v.size(); i++, c++) {
int16_t iq[2];
iq[0] = c->real()*SHRT_MAX;
iq[1] = c->imag()*SHRT_MAX;
iq[0] = htole16(iq[0]);
iq[1] = htole16(iq[1]);
os.write((char*)&iq, 2*sizeof(int16_t));
}
}
void writeBurstSigned16BE(std::ofstream& os, const signalVector& v)
{
const complex *c = v.begin();
for (size_t i=0; i<v.size(); i++, c++) {
int16_t iq[2];
iq[0] = c->real()*SHRT_MAX;
iq[1] = c->imag()*SHRT_MAX;
iq[0] = htobe16(iq[0]);
iq[1] = htobe16(iq[1]);
os.write((char*)&iq, 2*sizeof(int16_t));
}
}
void writeBurst(std::ofstream& os, const signalVector& v, FileType ftype)
{
switch(ftype)
{
case FLOAT_NORM_LE:
writeBurstFloatNorm(os, v);
break;
case FLOAT16_LE:
writeBurstFloat16LE(os, v);
break;
case SIGNED16_LE:
writeBurstSigned16LE(os, v);
break;
case SIGNED16_BE:
writeBurstSigned16BE(os, v);
break;
default:
assert(!"unknown file type");
}
}
// Setup configuration values
static void print_config(struct trx_config *config)
{
std::ostringstream ost("");
ost << "Config Settings" << std::endl;
ost << " Destination file name........ " << config->filename << std::endl;
ost << " Destination file type........ " << config->file_type << std::endl;
ost << " Log Level.................... " << config->log_level << std::endl;
ost << " Tx Samples-per-Symbol........ " << config->sps << std::endl;
ost << " EDGE support................. " << (config->edge ? "Enabled" : "Disabled") << std::endl;
ost << " Burst type................... " << config->type << std::endl;
ost << " Burst TSC.................... " << config->tsc << std::endl;
ost << " Signal full scale............ " << config->full_scale << std::endl;
std::cout << ost << std::endl;
}
static void print_help()
{
fprintf(stdout,
"This utility generates waveform files aka IQ binary files in a number of formats"
"to use them as input to osmo-trx-dec or load them into signal generators.\n"
"\n"
"Options:\n"
" -h This text\n"
" -l LEVEL Logging level (%s)\n"
" -e Enable EDGE receiver\n"
" -s SPS Samples-per-symbol (1 or 4, default: %d)\n"
" -t TSC Burst training sequence (0 to 7, default: 0)\n"
" -f FILE File to write generated bursts to\n"
" -F FILETYPE Format of the file - float, float16, signed16, signed16be (default: f16)\n"
" Note: Keysight waveform format is signed16be. osmo-trx-dec accepts float16.\n",
"EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG",
DEFAULT_SPS);
}
FileType option_to_file_type(const std::string &optarg)
{
if (optarg == "float") {
return FLOAT_NORM_LE;
} else if (optarg == "float16") {
return FLOAT16_LE;
} else if (optarg == "signed16") {
return SIGNED16_LE;
} else if (optarg == "signed16be") {
return SIGNED16_BE;
} else {
return (FileType)-1;
}
}
static bool handle_options(int argc, char **argv, struct trx_config *config)
{
int option;
config->log_level = "NOTICE";
config->sps = DEFAULT_SPS;
config->tsc = 0;
config->full_scale = SHRT_MAX;
config->edge = false;
config->type = TSC;
config->file_type = FLOAT16_LE;
while ((option = getopt(argc, argv, "ls:et:f:F:h")) != -1) {
switch (option) {
case 'l':
config->log_level = optarg;
break;
case 's':
config->sps = atoi(optarg);
break;
case 'e':
config->edge = true;
break;
case 't':
config->tsc = atoi(optarg);
break;
case 'f':
config->filename = optarg;
break;
case 'F':
config->file_type = option_to_file_type(optarg);
break;
case 'h':
default:
print_help();
exit(0);
}
}
if ((config->sps != 1) && (config->sps != 4)) {
printf("ERROR: Unsupported samples-per-symbol %i\n\n", config->sps);
return false;
}
if (config->edge && (config->sps != 4)) {
printf("ERROR: EDGE only supported at 4 samples per symbol\n\n");
return false;
}
if (config->tsc > 7) {
printf("ERROR: Invalid training sequence %i\n\n", config->tsc);
return false;
}
if (config->filename.length() == 0) {
printf("ERROR: No output file name specified\n\n");
return false;
}
if (config->file_type < 0) {
printf("ERROR: Wrong output file format\n\n");
}
return true;
}
int main(int argc, char *argv[])
{
struct trx_config config;
#ifdef HAVE_SSE3
printf("Info: SSE3 support compiled in");
if (__builtin_cpu_supports("sse3"))
printf(" and supported by CPU\n");
else
printf(", but not supported by CPU\n");
#endif
#ifdef HAVE_SSE4_1
printf("Info: SSE4.1 support compiled in");
if (__builtin_cpu_supports("sse4.1"))
printf(" and supported by CPU\n");
else
printf(", but not supported by CPU\n");
#endif
convolve_init();
convert_init();
// Process command line options and print config to screen
if (!handle_options(argc, argv, &config)) {
print_help();
exit(0);
}
print_config(&config);
gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7);
if (!sigProcLibSetup()) {
LOG(ALERT) << "Failed to initialize signal processing library";
return -1;
}
signalVector burst(2*BURST_LEN_FULL);
GSM::Time gsmTime;
PRBS9 prbs;
// Configure output stream
std::cout << std::fixed;
std::cout << std::setprecision(2);
std::ofstream file (config.filename.c_str(), std::ifstream::binary);
for (int i=0; i<511; i++) {
signalVector *signal = genRandNormalBurst(config.tsc, config.sps, gsmTime.TN(), prbs);
writeBurst(file, *signal, config.file_type);
gsmTime.incTN();
}
file.close();
std::cout << "Done!" << std::endl;
return 0;
}

View File

@@ -256,7 +256,8 @@ static void print_help()
" -c Number of ARFCN channels (default=1)\n"
" -f Enable C0 filler table\n"
" -o Set baseband frequency offset (default=auto)\n"
" -r Random Normal Burst test mode with TSC\n"
" -r Random GMSK Normal Burst test mode with given TSC\n"
" -E Random 8-PSK Normal Burst test mode with given TSC\n"
" -A Random Access Burst test mode with delay\n"
" -R RSSI to dBm offset in dB (default=0)\n"
" -S Swap channels (UmTRX only)\n",
@@ -284,7 +285,7 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
config->swap_channels = false;
config->edge = false;
while ((option = getopt(argc, argv, "ha:l:i:p:c:dmxgfo:s:b:r:A:R:Se")) != -1) {
while ((option = getopt(argc, argv, "ha:l:i:p:c:dmxgfo:s:b:r:E:A:R:Se")) != -1) {
switch (option) {
case 'h':
print_help();
@@ -330,6 +331,10 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
config->rtsc = atoi(optarg);
config->filler = Transceiver::FILLER_NORM_RAND;
break;
case 'E':
config->rtsc = atoi(optarg);
config->filler = Transceiver::FILLER_EDGE_RAND;
break;
case 'A':
config->rach_delay = atoi(optarg);
config->filler = Transceiver::FILLER_ACCESS_RAND;
@@ -360,8 +365,10 @@ static void handle_options(int argc, char **argv, struct trx_config *config)
goto bad_config;
}
if (config->edge && (config->filler == Transceiver::FILLER_NORM_RAND))
config->filler = Transceiver::FILLER_EDGE_RAND;
if (!config->edge && (config->filler == Transceiver::FILLER_EDGE_RAND)) {
printf("Can't enable EDGE filler when EDGE mode is disabled\n\n");
goto bad_config;
}
if ((config->tx_sps != 1) && (config->tx_sps != 4) &&
(config->rx_sps != 1) && (config->rx_sps != 4)) {

View File

@@ -22,6 +22,7 @@
#include "radioInterface.h"
#include "Resampler.h"
#include <Logger.h>
#include <PRBS.h>
extern "C" {
#include "convert.h"

View File

@@ -915,7 +915,7 @@ static signalVector *shapeEdgeBurst(const signalVector &symbols)
/*
* Generate a random GSM normal burst.
*/
signalVector *genRandNormalBurst(int tsc, int sps, int tn)
signalVector *genRandNormalBurst(int tsc, int sps, int tn, PRBS &prbs)
{
if ((tsc < 0) || (tsc > 7) || (tn < 0) || (tn > 7))
return NULL;
@@ -932,7 +932,7 @@ signalVector *genRandNormalBurst(int tsc, int sps, int tn)
/* Random bits */
for (; i < 60; i++)
(*bits)[i] = rand() % 2;
(*bits)[i] = prbs.generateBit();
/* Stealing bit */
(*bits)[i++] = 0;
@@ -946,7 +946,7 @@ signalVector *genRandNormalBurst(int tsc, int sps, int tn)
/* Random bits */
for (; i < 145; i++)
(*bits)[i] = rand() % 2;
(*bits)[i] = prbs.generateBit();
/* Tail bits */
for (; i < 148; i++)
@@ -1855,8 +1855,8 @@ int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float threshold
return -SIGERR_UNSUPPORTED;
target = 3 + 58 + 16 + 5;
head = 6;
tail = 6 + max_toa;
head = 3;
tail = 3 + max_toa;
sync = gMidambles[tsc];
rc = detectGeneralBurst(burst, threshold, sps, amplitude, toa,

View File

@@ -18,6 +18,7 @@
#include "Vector.h"
#include "Complex.h"
#include "BitVector.h"
#include "PRBS.h"
#include "signalVector.h"
/* Burst lengths */
@@ -140,7 +141,7 @@ signalVector *generateEdgeBurst(int tsc);
signalVector *generateEmptyBurst(int sps, int tn);
/** Generate a normal GSM burst with random payload - 4 or 1 SPS */
signalVector *genRandNormalBurst(int tsc, int sps, int tn);
signalVector *genRandNormalBurst(int tsc, int sps, int tn, PRBS &prbs);
/** Generate an access GSM burst with random payload - 4 or 1 SPS */
signalVector *genRandAccessBurst(int delay, int sps, int tn);