TransceiverUHD: Add transceiver support for UHD devices

Supported devices includes USRP N200/N210/USRP2, B200/B210, X300/X310.
Other Ettus devices are not supported due to bandwidth limitations.
There is no direct embedded device support at this time.

The UHD transceiver device operating rate is fixed at 6.25 Msps, which
interfaces with the UMTS chip rate of 3.84 Mcps through a combined
polyphase resampling and RRC pulse-shaping filterbank. The effective
oversampling factor is approximate 1.63 samples per symbol.

Tested against Agilent 89600 VSA for appropriate EVM and ACP values.

Signed-off-by: Tom Tsou <tom@tsou.cc>
This commit is contained in:
Tom Tsou
2014-11-25 16:55:13 -08:00
committed by Michael Iedema
parent 274bda43a8
commit 00e302f32a
17 changed files with 4020 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
if HAVE_UHD
include $(top_srcdir)/Makefile.common
noinst_LTLIBRARIES = libumtstransceiver.la
noinst_PROGRAMS = transceiver
noinst_HEADERS = \
RadioInterface.h \
RadioDevice.h \
Transceiver.h \
SampleBuffer.h \
UHDDevice.h \
Resampler.h \
convolve.h \
convert.h
libumtstransceiver_la_CFLAGS = -Wall $(AM_CFLAGS) -std=gnu99 -march=native
libumtstransceiver_la_CPPFLAGS = -Wall $(AM_CPPFLAGS) $(UHD_CPPFLAGS)
libumtstransceiver_la_CXXFLAGS = -Wall $(AM_CXXFLAGS) $(UHD_CXXFLAGS)
libumtstransceiver_la_SOURCES = \
RadioInterface.cpp \
Transceiver.cpp \
UHDDevice.cpp \
SampleBuffer.cpp \
Resampler.cpp \
convolve.c \
convert.c
transceiver_SOURCES = runTransceiver.cpp ../apps/GetConfigurationKeys.cpp
transceiver_LDADD = libumtstransceiver.la $(UHD_LIBS) $(UMTS_LA) $(GSM_LA) $(COMMON_LA) $(SQLITE_LA)
install: transceiver
mkdir -p "$(DESTDIR)/OpenBTS/"
install transceiver "$(DESTDIR)/OpenBTS/"
endif

View File

@@ -0,0 +1,100 @@
#ifndef __RADIO_DEVICE_H__
#define __RADIO_DEVICE_H__
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */
class RadioDevice {
public:
virtual ~RadioDevice() { }
/* Available transport bus types */
enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED };
/** Start the USRP */
virtual bool start()=0;
/** Stop the USRP */
virtual bool stop()=0;
/** Get the Tx window type */
virtual enum TxWindowType getWindowType()=0;
/** Enable thread priority */
virtual void setPriority()=0;
/**
Read samples from the radio.
@param buf preallocated buf to contain read result
@param len number of samples desired
@param overrun Set if read buffer has been overrun, e.g. data not being read fast enough
@param timestamp The timestamp of the first samples to be read
@param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough
@param RSSI The received signal strength of the read result
@return The number of samples actually read
*/
virtual int readSamples(short *buf, int len, bool *overrun,
long long timestamp, bool *underrun,
unsigned *RSSI=NULL)=0;
/**
Write samples to the radio.
@param buf Contains the data to be written.
@param len number of samples to write.
@param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough
@param timestamp The timestamp of the first sample of the data buffer.
@param isControl Set if data is a control packet, e.g. a ping command
@return The number of samples actually written
*/
virtual int writeSamples(short *buf, int len, bool *underrun,
long long timestamp)=0;
/** Set the transmitter frequency */
virtual bool setTxFreq(double wFreq)=0;
/** Set the receiver frequency */
virtual bool setRxFreq(double wFreq)=0;
/** Returns the starting write Timestamp*/
virtual long long initialWriteTimestamp(void)=0;
/** Returns the starting read Timestamp*/
virtual long long initialReadTimestamp(void)=0;
/** returns the full-scale transmit amplitude **/
virtual double fullScaleInputValue()=0;
/** returns the full-scale receive amplitude **/
virtual double fullScaleOutputValue()=0;
/** sets the receive chan gain, returns the gain setting **/
virtual double setRxGain(double dB)=0;
/** gets the current receive gain **/
virtual double getRxGain(void)=0;
/** return maximum Rx Gain **/
virtual double maxRxGain(void) = 0;
/** return minimum Rx Gain **/
virtual double minRxGain(void) = 0;
/** sets the transmit chan gain, returns the gain setting **/
virtual double setTxGain(double dB)=0;
/** return maximum Tx Gain **/
virtual double maxTxGain(void) = 0;
/** return minimum Tx Gain **/
virtual double minTxGain(void) = 0;
/** Return internal status values */
virtual double getTxFreq()=0;
virtual double getRxFreq()=0;
virtual double getSampleRate()=0;
virtual double numberRead()=0;
virtual double numberWritten()=0;
};
#endif /* __RADIO_DEVICE_H__ */

View File

@@ -0,0 +1,410 @@
/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2014 Range Networks, Inc.
* Copyright 2014 Ettus Research LLC
*
* This software is distributed under the terms of the GNU General Public
* License version 3. See the COPYING and NOTICE files in the current
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include "RadioInterface.h"
#include <Logger.h>
extern "C" {
#include "convert.h"
}
/*
* Downlink digital gain scaling
*
* This value scales floating point transmit values prior fixed-point
* conversion. The current value provides a significant amount of backoff to
* avoid reaching compression across general device-daughterboard combinations.
* More optimal values to target peak gain or maximum dynamic range will be
* device and daughterboard combination specific.
* */
#define TX_BASE_SCALING 150.0f
/*
* Resampling ratio for 25 MHz base clocking
*
* This resampling factor absorbs the sample rate differences between UMTS chip
* rate of 3.84 Mcps and the device clocking rate. USRP N200 clock operates at a
* rate of 100 MHz with factor of 16 downsampling. B200 uses 25 MHz FPGA
* clocking with downsampling by 4.
*/
#define RESAMP_INRATE 384
#define RESAMP_OUTRATE 625
/*
* Number of taps per resampler-RRC filter partitions
*
* The number of taps per polyphase partition filter can be adjusted to suit
* processing or spectrum requirements. Note that the filter length, and
* associated group delay, affects timing.
*/
#define RESAMP_TAP_LEN 20
/* Chunk multiplier with resampler rates determine the chunk sizes */
#define CHUNK_MUL 2
/* Universal resampling parameters */
#define NUMCHUNKS 32
UMTS::Time VectorQueue::nextTime() const
{
UMTS::Time retVal;
ScopedLock lock(mLock);
while (!mQ.size())
mWriteSignal.wait(mLock);
return mQ.top()->time();
}
radioVector* VectorQueue::getStaleBurst(const UMTS::Time& targTime)
{
ScopedLock lock(mLock);
if (!mQ.size())
return NULL;
if (mQ.top()->time() < targTime) {
radioVector* retVal = mQ.top();
mQ.pop();
return retVal;
}
return NULL;
}
radioVector* VectorQueue::getCurrentBurst(const UMTS::Time& targTime)
{
ScopedLock lock(mLock);
if (!mQ.size())
return NULL;
if (mQ.top()->time() == targTime) {
radioVector* retVal = mQ.top();
mQ.pop();
return retVal;
}
return NULL;
}
RadioInterface::RadioInterface(RadioDevice *wRadio, int wReceiveOffset,
UMTS::Time wStartTime)
: mRadio(wRadio), sendCursor(0), recvCursor(0),
underrun(false), receiveOffset(wReceiveOffset),
mOn(false), powerScaling(TX_BASE_SCALING),
finalVec(NULL), upsampler(NULL), dnsampler(NULL),
innerSendBuffer(NULL), innerRecvBuffer(NULL),
outerSendBuffer(NULL), outerRecvBuffer(NULL),
convertSendBuffer(NULL), convertRecvBuffer(NULL)
{
mClock.set(wStartTime);
inchunk = RESAMP_INRATE * CHUNK_MUL;
outchunk = RESAMP_OUTRATE * CHUNK_MUL;
}
RadioInterface::~RadioInterface(void)
{
delete convertSendBuffer;
delete convertRecvBuffer;
delete innerSendBuffer;
delete outerSendBuffer;
delete innerRecvBuffer;
delete outerRecvBuffer;
delete dnsampler;
delete upsampler;
}
/*
* Allocate resamplers and I/O buffers
*
* Setup multiple chunks on buffers except for the outer receive buffer, which
* always receives a fixed number of samples. Conversion buffers are fixed point
* used directly by the device interface.
*/
bool RadioInterface::init()
{
dnsampler = new Resampler(RESAMP_INRATE, RESAMP_OUTRATE, RESAMP_TAP_LEN);
if (!dnsampler->init(Resampler::FILTER_TYPE_RRC)) {
LOG(ALERT) << "Rx resampler failed to initialize";
return false;
}
upsampler = new Resampler(RESAMP_OUTRATE, RESAMP_INRATE, RESAMP_TAP_LEN);
if (!upsampler->init(Resampler::FILTER_TYPE_RRC)) {
LOG(ALERT) << "Tx resampler failed to initialize";
return false;
}
outerSendBuffer = new signalVector(NUMCHUNKS * outchunk);
innerRecvBuffer = new signalVector(NUMCHUNKS * inchunk);
/* Buffers that feed the resampler require filter length headroom */
innerSendBuffer = new signalVector(NUMCHUNKS * inchunk + upsampler->len());
outerRecvBuffer = new signalVector(outchunk + dnsampler->len());
convertSendBuffer = new short[outerSendBuffer->size() * 2];
convertRecvBuffer = new short[outerRecvBuffer->size() * 2];
return true;
}
/*
* Start the radio device
*
* When the device start returns, initialize the timestamp counters with values
* reported by the device. This avoids having to reset the device clock (if even
* possible) and guessing at latency and corresponding initial values.
*
* Note that we do not advance the initial timestamp relative to receive here
* so the initial data chunk will submit with a late timestamp. Some timing
* compensation will occur with the TX_PACKET_SYNC alignment, which removes
* packets from the downlink stream, while the remaining timing adjustment will
* be absorbed by UHD and on the device itself.
*/
bool RadioInterface::start()
{
if (mOn)
return true;
LOG(INFO) << "Starting radio device";
if (!mRadio->start())
return false;
sendCursor = 0;
recvCursor = 0;
writeTimestamp = mRadio->initialWriteTimestamp();
readTimestamp = mRadio->initialReadTimestamp();
mOn = true;
LOG(INFO) << "Radio started";
return true;
}
/*
* Stop the radio device
*
* This is a pass-through call to the device interface. Because the underlying
* stop command issuance generally doesn't return confirmation on device status,
* this call will only return false if the device is already stopped.
*/
bool RadioInterface::stop()
{
if (!mOn || !mRadio->stop())
return false;
mOn = false;
return true;
}
double RadioInterface::fullScaleInputValue(void)
{
return mRadio->fullScaleInputValue();
}
double RadioInterface::fullScaleOutputValue(void)
{
return mRadio->fullScaleOutputValue();
}
int RadioInterface::setPowerAttenuation(int atten)
{
double rfGain, digAtten;
if (atten < 0)
atten = 0;
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - (double) atten);
digAtten = (double) atten - mRadio->maxTxGain() + rfGain;
if (digAtten < 1.0)
powerScaling = TX_BASE_SCALING;
else
powerScaling = TX_BASE_SCALING / sqrt(pow(10, (digAtten / 10.0)));
return atten;
}
int RadioInterface::radioifyVector(signalVector &wVector,
float *retVector, bool zero)
{
if (zero)
memset(retVector, 0, wVector.size() * sizeof(Complex<float>));
else
memcpy(retVector, wVector.begin(), wVector.size() * sizeof(Complex<float>));
return wVector.size();
}
void RadioInterface::unRadioifyVector(float *floatVector,
signalVector& newVector)
{
memcpy(newVector.begin(), floatVector,
newVector.size() * sizeof(Complex<float>));
}
bool RadioInterface::pushBuffer(void)
{
int rc, chunks;
int inner_len, outer_len;
if (sendCursor < inchunk)
return true;
if (sendCursor > innerSendBuffer->size()) {
LOG(ALERT) << "Send buffer overflow";
}
chunks = sendCursor / inchunk;
inner_len = chunks * inchunk;
outer_len = chunks * outchunk;
/* Input from the buffer with number of taps length headroom */
float *resamp_in = (float *) (innerSendBuffer->begin() + upsampler->len());
float *resamp_out = (float *) outerSendBuffer->begin();
rc = upsampler->rotate(resamp_in, inner_len, resamp_out, outer_len);
if (rc < 0) {
LOG(ALERT) << "Sample rate downsampling error";
return false;
}
convert_float_short(convertSendBuffer,
(float *) outerSendBuffer->begin(),
powerScaling, outer_len * 2);
mRadio->writeSamples(convertSendBuffer, outer_len,
&underrun, writeTimestamp);
/* Shift remaining samples to beginning of buffer */
memmove(innerSendBuffer->begin() + upsampler->len(),
innerSendBuffer->begin() + upsampler->len() + inner_len,
(sendCursor - inner_len) * 2 * sizeof(float));
writeTimestamp += outer_len;
sendCursor -= inner_len;
return true;
}
bool RadioInterface::pullBuffer(void)
{
bool localUnderrun;
if (recvCursor > innerRecvBuffer->size() - inchunk)
return true;
/* Outer buffer access size is fixed */
size_t num_recv = mRadio->readSamples(convertRecvBuffer,
outchunk,
&overrun,
readTimestamp,
&localUnderrun);
if (num_recv != outchunk) {
LOG(ALERT) << "Receive error " << num_recv;
return false;
}
short *convert_in = convertRecvBuffer;
float *convert_out = (float *) (outerRecvBuffer->begin() + dnsampler->len());
convert_short_float(convert_out, convert_in, outchunk * 2);
underrun |= localUnderrun;
readTimestamp += outchunk;
/* Write to the end of the inner receive buffer */
float *resamp_in = (float *) (outerRecvBuffer->begin() + dnsampler->len());
float *resamp_out = (float *) (innerRecvBuffer->begin() + recvCursor);
int rc = dnsampler->rotate(resamp_in, outchunk,
resamp_out, inchunk);
if (rc < 0) {
LOG(ALERT) << "Sample rate upsampling error";
return false;
}
recvCursor += inchunk;
return true;
}
bool RadioInterface::tuneTx(double freq)
{
return mRadio->setTxFreq(freq);
}
bool RadioInterface::tuneRx(double freq)
{
return mRadio->setRxFreq(freq);
}
void RadioInterface::driveTransmitRadio(signalVector &radioBurst,
bool zeroBurst)
{
if (!mOn)
return;
/* Buffer write position */
float *pos = (float *) (innerSendBuffer->begin() +
upsampler->len() + sendCursor);
radioifyVector(radioBurst, pos, zeroBurst);
sendCursor += radioBurst.size();
pushBuffer();
}
void RadioInterface::driveReceiveRadio(int guardPeriod)
{
if (!mOn)
return;
pullBuffer();
UMTS::Time recvClock = mClock.get();
int recvSz = recvCursor;
int readSz = 0;
const int symbolsPerSlot = UMTS::gSlotLen;
// while there's enough data in receive buffer, form received
// UMTS bursts and pass up to Transceiver
int vecSz = symbolsPerSlot + guardPeriod;
while (recvSz > vecSz) {
UMTS::Time tmpTime = recvClock;
mClock.incTN();
recvClock.incTN();
if (recvClock.FN() >= 0) {
radioVector *rxBurst = new radioVector(vecSz, tmpTime);
memcpy(rxBurst->begin(),
innerRecvBuffer->begin() + readSz,
rxBurst->size() * sizeof(Complex<float>));
mReceiveFIFO.put(rxBurst);
}
readSz += symbolsPerSlot;
recvSz -= symbolsPerSlot;
}
if (!readSz)
return;
memmove(innerRecvBuffer->begin(),
innerRecvBuffer->begin() + readSz,
(recvCursor - readSz) * sizeof(Complex<float>));
recvCursor -= readSz;
}

View File

@@ -0,0 +1,261 @@
#ifndef __RADIOINTERFACE_H__
#define __RADIOINTERFACE_H__
#include "signalVector.h"
#include "LinkedLists.h"
#include "RadioDevice.h"
#include "Resampler.h"
#include <UMTSCommon.h>
#include <Interthread.h>
/** class used to organize UMTS bursts by UMTS timestamps */
class radioVector : public signalVector {
private:
UMTS::Time mTime; ///< the burst's UMTS timestamp
public:
/** constructor */
radioVector(const signalVector& wVector,
UMTS::Time& wTime): signalVector(wVector),mTime(wTime) { };
radioVector(unsigned wSz, UMTS::Time& wTime)
: signalVector(wSz), mTime(wTime) { };
/** timestamp read and write operators */
UMTS::Time time() const { return mTime; }
void time(const UMTS::Time& wTime) { mTime = wTime; }
/** comparison operator, used for sorting */
bool operator>(const radioVector& other) const
{
return mTime > other.mTime;
}
};
/** a priority queue of radioVectors, i.e. UMTS bursts, sorted so that earliest element is at top */
class VectorQueue : public InterthreadPriorityQueue<radioVector> {
public:
/** the top element of the queue */
UMTS::Time nextTime() const;
/**
Get stale burst, if any.
@param targTime The target time.
@return Pointer to burst older than target time, removed from queue, or NULL.
*/
radioVector* getStaleBurst(const UMTS::Time& targTime);
/**
Get current burst, if any.
@param targTime The target time.
@return Pointer to burst at the target time, removed from queue, or NULL.
*/
radioVector* getCurrentBurst(const UMTS::Time& targTime);
};
/** a FIFO of radioVectors */
class VectorFIFO {
private:
PointerFIFO mQ;
Mutex mLock;
public:
unsigned size() { return mQ.size(); }
void put(radioVector *ptr)
{
ScopedLock lock(mLock);
mQ.put((void*) ptr);
}
radioVector *get()
{
ScopedLock lock(mLock);
return (radioVector*) mQ.get();
}
};
/** FIFO to store */
class RadioBurstFIFO : public InterthreadQueueWithWait<radioVector> { };
/** the basestation clock class */
class RadioClock {
private:
UMTS::Time mClock;
Mutex mLock;
Signal updateSignal;
public:
/** Set clock */
void set(const UMTS::Time& wTime)
{
ScopedLock lock(mLock);
mClock = wTime;
updateSignal.signal();
}
/** Increment clock */
void incTN()
{
ScopedLock lock(mLock);
mClock.incTN();
updateSignal.signal();
}
/** Get clock value */
UMTS::Time get()
{
ScopedLock lock(mLock);
return mClock;
}
void wait()
{
ScopedLock lock(mLock);
updateSignal.wait(mLock);
}
void signal()
{
ScopedLock lock(mLock);
updateSignal.signal();
}
};
/** class to interface the transceiver with the USRP */
class RadioInterface {
private:
VectorFIFO mReceiveFIFO;
RadioDevice *mRadio;
size_t sendCursor;
size_t recvCursor;
size_t inchunk, outchunk;
bool underrun;
bool overrun;
long long writeTimestamp;
long long readTimestamp;
RadioClock mClock;
int receiveOffset;
bool mOn;
/** base scaling assumes no digital attenuation */
double baseScaling;
/** digital scaling attenuation factor */
double powerScaling;
signalVector *finalVec;
Resampler *upsampler;
Resampler *dnsampler;
signalVector *innerSendBuffer;
signalVector *innerRecvBuffer;
signalVector *outerSendBuffer;
signalVector *outerRecvBuffer;
short *convertSendBuffer;
short *convertRecvBuffer;
/** format samples to USRP */
int radioifyVector(signalVector &wVector, float *shortVector, bool zero);
/** format samples from USRP */
void unRadioifyVector(float *floatVector, signalVector &wVector);
/** push UMTS bursts into the transmit buffer */
bool pushBuffer(void);
/** pull UMTS bursts from the receive buffer */
bool pullBuffer(void);
public:
/** constructor */
RadioInterface(RadioDevice* wRadio = NULL, int receiveOffset = 3,
UMTS::Time wStartTime = UMTS::Time(0));
/** destructor */
~RadioInterface();
bool init();
/** start the interface */
bool start();
bool stop();
/** check for underrun, resets underrun value */
bool isUnderrun()
{
bool retVal = underrun;
underrun = false;
return retVal;
}
/** return the receive FIFO */
VectorFIFO* receiveFIFO() { return &mReceiveFIFO;}
/** return the basestation clock */
RadioClock* getClock(void) { return &mClock;};
/** set receive gain */
double setRxGain(double dB)
{
if (mRadio)
return mRadio->setRxGain(dB);
else
return -1;
}
/** get receive gain */
double getRxGain(void)
{
if (mRadio)
return mRadio->getRxGain();
else
return -1;
}
/** set transmit frequency */
bool tuneTx(double freq);
/** set receive frequency */
bool tuneRx(double freq);
/** drive transmission of UMTS bursts */
void driveTransmitRadio(signalVector &radioBurst, bool zeroBurst);
/** drive reception of UMTS bursts */
void driveReceiveRadio(int guardPeriod = 0);
int setPowerAttenuation(int atten);
/** returns the full-scale transmit amplitude **/
double fullScaleInputValue();
/** returns the full-scale receive amplitude **/
double fullScaleOutputValue();
/** set thread priority on current thread */
void setPriority() { mRadio->setPriority(); }
/** get transport window type of attached device */
enum RadioDevice::TxWindowType getWindowType()
{
return mRadio->getWindowType();
}
protected:
/** drive synchronization of Tx/Rx of USRP */
void alignRadio();
/** reset the interface */
void reset();
};
#endif /* __RADIOINTERFACE_H__ */

View File

@@ -0,0 +1,302 @@
/*
* Rational Sample Rate Conversion
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
* Copyright (C) 2014 Ettus Research LLC
*
* 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 <stdlib.h>
#include <math.h>
#include <string.h>
#include <malloc.h>
#include <iostream>
#include "Resampler.h"
extern "C" {
#include "convolve.h"
}
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327f
#endif
#define MAX_OUTPUT_LEN 8192
static float sinc(float x)
{
if (x == 0.0)
return 0.9999999999;
return sin(M_PI * x) / (M_PI * x);
}
/*
* Generate sinc prototype filter with a Blackman-harris window.
* Scale coefficients with DC filter gain set to unity divided
* by the number of filter partitions.
*/
static float gen_windowed_sinc(float *buf, size_t len, int p, int q, float bw)
{
float sum = 0.0f;
float a0 = 0.35875f;
float a1 = 0.48829f;
float a2 = 0.14128f;
float a3 = 0.01168f;
float cutoff;
float midpt = ((float) len - 1.0f) / 2.0f;
if (p > q)
cutoff = (float) p;
else
cutoff = (float) q;
for (size_t i = 0; i < len; i++) {
buf[i] = sinc(((float) i - midpt) / cutoff * bw);
buf[i] *= a0 -
a1 * cos(2.0f * M_PI * i / (float) (len - 1)) +
a2 * cos(4.0f * M_PI * i / (float) (len - 1)) -
a3 * cos(6.0f * M_PI * i / (float) (len - 1));
sum += buf[i];
}
return (float) p / sum;
}
/*
* Generate UMTS root raised cosine prototype filter.
*/
static float gen_umts_rrc_pulse(float *buf, size_t len, int p, int q, float bw)
{
float alpha = 0.22f;
float Tc = 1.0f;
float sum = 0.0f;
float midpt = ((float) len - 1.0f) / 2.0f;
for (size_t i = 0; i < len; i++) {
float t, a, b, c;
t = ((float) i - midpt) / (float) p * bw;
a = sinf(M_PI * t / Tc * (1.0f - alpha));
b = 4.0f * alpha * t / Tc * cosf(M_PI * t / Tc * (1 + alpha));
c = 4.0f * alpha * t / Tc;
buf[i] = (a + b) / (M_PI * t / Tc * (1.0f - c * c));
sum += buf[i];
}
return (float) p / sum;
}
bool Resampler::initFilters(int type, float bw)
{
size_t proto_len = p * filt_len;
float *proto, scale;
/*
* Allocate partition filters and the temporary prototype filter
* according to numerator of the rational rate. Coefficients are
* real only and must be 16-byte memory aligned for SSE usage.
*/
proto = new float[proto_len];
if (!proto)
return false;
partitions = (float **) malloc(sizeof(float *) * p);
if (!partitions) {
delete proto;
return false;
}
for (size_t i = 0; i < p; i++) {
partitions[i] = (float *)
memalign(16, filt_len * 2 * sizeof(float));
}
/* Filter type selection */
if (type == FILTER_TYPE_RRC) {
scale = gen_umts_rrc_pulse(proto, proto_len, p, q, bw);
} else if (type == FILTER_TYPE_SINC) {
scale = gen_windowed_sinc(proto, proto_len, p, q, bw);
} else {
delete proto;
return false;
}
/* Populate filter partitions from the prototype filter */
for (size_t i = 0; i < filt_len; i++) {
for (size_t n = 0; n < p; n++) {
partitions[n][2 * i + 0] = proto[i * p + n] * scale;
partitions[n][2 * i + 1] = 0.0f;
}
}
/* Reverse filter taps for convolution */
for (size_t n = 0; n < p; n++) {
for (size_t i = 0; i < filt_len / 2; i++) {
float a = partitions[n][2 * i];
float b = partitions[n][2 * (filt_len - 1 - i)];
partitions[n][2 * i] = b;
partitions[n][2 * (filt_len - 1 - i)] = a;
}
}
delete proto;
return true;
}
void Resampler::releaseFilters()
{
if (partitions) {
for (size_t i = 0; i < p; i++)
free(partitions[i]);
}
free(partitions);
partitions = NULL;
}
bool Resampler::checkLen(size_t in_len, size_t out_len)
{
if (in_len % q) {
#ifdef DEBUG
std::cerr << "Invalid input length " << in_len
<< " is not multiple of " << q << std::endl;
#endif
return false;
}
if (out_len % p) {
#ifdef DEBUG
std::cerr << "Invalid output length " << out_len
<< " is not multiple of " << p << std::endl;
#endif
return false;
}
if ((in_len / q) != (out_len / p)) {
#ifdef DEBUG
std::cerr << "Input/output block length mismatch" << std::endl;
std::cerr << "P = " << p << ", Q = " << q << std::endl;
std::cerr << "Input len: " << in_len << std::endl;
std::cerr << "Output len: " << out_len << std::endl;
#endif
return false;
}
if (out_len > path_len) {
#ifdef DEBUG
std::cerr << "Block length of " << out_len
<< " exceeds max of " << path_len << std::endl;
std::cerr << "Resizing" << std::endl;
#endif
computePaths(out_len * 2);
}
return true;
}
int Resampler::rotate(float *in, size_t in_len, float *out, size_t out_len)
{
int n, path;
int hist_len = filt_len - 1;
if (!checkLen(in_len, out_len))
return -1;
/* Insert history */
memcpy(&in[-2 * hist_len], history, hist_len * 2 * sizeof(float));
/* Generate output from precomputed input/output paths */
for (size_t i = 0; i < out_len; i++) {
n = in_index[i];
path = out_path[i];
convolve_real(in, in_len,
partitions[path], filt_len,
&out[2 * i], out_len - i,
n, 1, 1, 0);
}
/* Save history */
memcpy(history, &in[2 * (in_len - hist_len)],
hist_len * 2 * sizeof(float));
return out_len;
}
bool Resampler::init(int type, float bw)
{
size_t hist_len = filt_len - 1;
/* Filterbank filter internals */
if (!initFilters(type, bw))
return false;
/* History buffer */
history = new float[2 * hist_len];
memset(history, 0, 2 * hist_len * sizeof(float));
computePaths(path_len);
return true;
}
bool Resampler::computePaths(int len)
{
if (len <= 0)
return false;
delete in_index;
delete out_path;
/* Precompute filterbank paths */
in_index = new size_t[len];
out_path = new size_t[len];
for (int i = 0; i < len; i++) {
in_index[i] = (q * i) / p;
out_path[i] = (q * i) % p;
}
path_len = len;
return true;
}
size_t Resampler::len()
{
return filt_len;
}
Resampler::Resampler(size_t p, size_t q, size_t filt_len)
: in_index(NULL), out_path(NULL), partitions(NULL), history(NULL)
{
this->p = p;
this->q = q;
this->filt_len = filt_len;
this->path_len = MAX_OUTPUT_LEN;
}
Resampler::~Resampler()
{
releaseFilters();
delete history;
delete in_index;
delete out_path;
}

View File

@@ -0,0 +1,65 @@
#ifndef _RESAMPLER_H_
#define _RESAMPLER_H_
class Resampler {
public:
/* Constructor for rational sample rate conversion
* @param p numerator of resampling ratio
* @param q denominator of resampling ratio
* @param filt_len length of each polyphase subfilter
*/
Resampler(size_t p, size_t q, size_t filt_len = 16);
~Resampler();
/* Initilize resampler filterbank.
* @param bw bandwidth factor on filter generation (pre-window)
* @return false on error, zero otherwise
*
* Automatic setting is to compute the filter to prevent aliasing with
* a Blackman-Harris window. Adjustment is made through a bandwith
* factor to shift the cutoff and/or the constituent filter lengths.
* Calculation of specific rolloff factors or 3-dB cutoff points is
* left as an excersize for the reader.
*/
bool init(int type = FILTER_TYPE_SINC, float bw = 1.0f);
/* Rotate "commutator" and drive samples through filterbank
* @param in continuous buffer of input complex float values
* @param in_len input buffer length
* @param out continuous buffer of output complex float values
* @param out_len output buffer length
* @return number of samples outputted, negative on error
*
* Input and output vector lengths must of be equal multiples of the
* rational conversion rate denominator and numerator respectively.
*/
int rotate(float *in, size_t in_len, float *out, size_t out_len);
/* Get filter length
* @return number of taps in each filter partition
*/
size_t len();
enum {
FILTER_TYPE_SINC,
FILTER_TYPE_RRC,
};
private:
size_t p;
size_t q;
size_t filt_len;
size_t path_len;
size_t *in_index;
size_t *out_path;
float **partitions;
float *history;
bool initFilters(int type, float bw);
void releaseFilters();
bool computePaths(int len);
bool checkLen(size_t in_len, size_t out_len);
};
#endif /* _RESAMPLER_H_ */

View File

@@ -0,0 +1,169 @@
/*
* Timestamped ring buffer implementation
* Written by Tom Tsou <tom@tsou.cc>
*
* Copyright 2010-2011 Free Software Foundation, Inc.
* Copyright 2014 Ettus Research LLC
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#include <string.h>
#include "SampleBuffer.h"
SampleBuffer::SampleBuffer(int len, double rate)
: clock_rate(rate), time_start(0), time_end(0),
data_start(0), data_end(0)
{
this->len = len;
data = new std::complex<short>[len];
}
SampleBuffer::~SampleBuffer()
{
delete[] data;
}
int SampleBuffer::avail_smpls(long long ts) const
{
if (ts < time_start)
return ERROR_TIMESTAMP;
else if (ts >= time_end)
return 0;
else
return time_end - ts;
}
int SampleBuffer::avail_smpls(uhd::time_spec_t ts) const
{
return avail_smpls(ts.to_ticks(clock_rate));
}
int SampleBuffer::read(void *buf, int len, long long ts)
{
int type_size = sizeof(std::complex<short>);
/* Check for valid read */
if (ts < time_start)
return ERROR_TIMESTAMP;
if (ts >= time_end)
return 0;
if (len >= this->len)
return ERROR_READ;
/* How many samples should be copied */
int num_smpls = time_end - ts;
if (num_smpls > len)
num_smpls = len;
/* Starting index */
int read_start = (data_start + (ts - time_start)) % this->len;
/* Read it */
if (read_start + num_smpls < this->len) {
int numBytes = len * type_size;
memcpy(buf, data + read_start, numBytes);
} else {
int first_cp = (this->len - read_start) * type_size;
int second_cp = len * type_size - first_cp;
memcpy(buf, data + read_start, first_cp);
memcpy((char*) buf + first_cp, data, second_cp);
}
data_start = (read_start + len) % this->len;
time_start = ts + len;
if (time_start > time_end)
return ERROR_READ;
else
return num_smpls;
}
int SampleBuffer::read(void *buf, int len, uhd::time_spec_t ts)
{
return read(buf, len, ts.to_ticks(clock_rate));
}
int SampleBuffer::write(void *buf, int len, long long ts)
{
int type_size = sizeof(std::complex<short>);
/* Check for valid write */
if ((len == 0) || (len >= this->len))
return ERROR_WRITE;
if ((ts + len) <= time_end)
return ERROR_TIMESTAMP;
/* Starting index */
int write_start = (data_start + (ts - time_start)) % this->len;
/* Write it */
if ((write_start + len) < this->len) {
int numBytes = len * type_size;
memcpy(data + write_start, buf, numBytes);
} else {
int first_cp = (this->len - write_start) * type_size;
int second_cp = len * type_size - first_cp;
memcpy(data + write_start, buf, first_cp);
memcpy(data, (char*) buf + first_cp, second_cp);
}
data_end = (write_start + len) % this->len;
time_end = ts + len;
if (((write_start + len) > this->len) && (data_end > data_start))
return ERROR_OVERFLOW;
else if (time_end <= time_start)
return ERROR_WRITE;
else
return len;
}
int SampleBuffer::write(void *buf, int len, uhd::time_spec_t ts)
{
return write(buf, len, ts.to_ticks(clock_rate));
}
std::string SampleBuffer::str_status(long long ts) const
{
std::ostringstream ost("Sample buffer: ");
ost << "ts = " << ts;
ost << ", len = " << this->len;
ost << ", time_start = " << time_start;
ost << ", time_end = " << time_end;
ost << ", data_start = " << data_start;
ost << ", data_end = " << data_end;
return ost.str();
}
std::string SampleBuffer::str_code(int code)
{
switch (code) {
case ERROR_TIMESTAMP:
return "Sample buffer: Requested timestamp not valid";
case ERROR_READ:
return "Sample buffer: Read error";
case ERROR_WRITE:
return "Sample buffer: Write error";
case ERROR_OVERFLOW:
return "Sample buffer: Overrun";
default:
return "Sample buffer: Unknown error";
}
}

View File

@@ -0,0 +1,53 @@
#ifndef UHD_BUFFER_H
#define UHD_BUFFER_H
#include <string>
#include <complex>
#include <uhd/types/time_spec.hpp>
/*
* Sample Buffer
*
* Allows reading and writing of timed samples using sample ticks or UHD
* timespec values.
*/
class SampleBuffer {
public:
SampleBuffer(int len, double rate);
~SampleBuffer();
/* Return number of samples available for a given ts */
int avail_smpls(long long ts) const;
int avail_smpls(uhd::time_spec_t ts) const;
int read(void *buf, int len, long long ts);
int read(void *buf, int len, uhd::time_spec_t ts);
int write(void *buf, int len, long long ts);
int write(void *buf, int len, uhd::time_spec_t ts);
/* Return formatted string describing internal buffer state */
std::string str_status(long long ts) const;
/* Formatted error code string */
static std::string str_code(int code);
enum err_code {
ERROR_TIMESTAMP = -1,
ERROR_READ = -2,
ERROR_WRITE = -3,
ERROR_OVERFLOW = -4
};
private:
std::complex<short> *data;
int len;
double clock_rate;
long long time_start;
long long time_end;
long long data_start;
long long data_end;
};
#endif /* UHD_BUFFER_H */

View File

@@ -0,0 +1,487 @@
/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2014 Range Networks, Inc.
* Copyright 2014 Ettus Research LLC
*
* This software is distributed under the terms of the GNU General Public
* License version 3. See the COPYING and NOTICE files in the current
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include <stdio.h>
#include <Logger.h>
#include "Transceiver.h"
/* Clock indication reporting interval in frames */
#define CLK_IND_INTERVAL 100
/* Default attenuation value in dB */
#define DEFAULT_ATTEN 20
Transceiver::Transceiver(int wBasePort,
const char *wTRXAddress,
UMTS::Time wTransmitLatency,
RadioInterface *wRadioInterface)
: mDataSocket(wBasePort + 2, wTRXAddress, wBasePort + 102),
mControlSocket(wBasePort + 1, wTRXAddress, wBasePort + 101),
mClockSocket(wBasePort, wTRXAddress, wBasePort + 100),
mTxServiceLoopThread(NULL), mRxServiceLoopThread(NULL),
mTransmitPriorityQueueServiceLoopThread(NULL),
mControlServiceLoopThread(NULL), mOn(false), mPower(DEFAULT_ATTEN),
mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface)
{
signalVector emptyVector(UMTS::gSlotLen);
UMTS::Time emptyTime(0, 0);
mEmptyTransmitBurst = new radioVector((const signalVector&) emptyVector,
(UMTS::Time&) emptyTime);
}
Transceiver::~Transceiver()
{
stop();
if (mControlServiceLoopThread) {
mControlServiceLoopThread->cancel();
mControlServiceLoopThread->join();
delete mControlServiceLoopThread;
}
delete mEmptyTransmitBurst;
}
/*
* Initialize transceiver
*
* Start or restart the control loop. Any further control is handled through the
* socket API. Randomize the central radio clock set the downlink burst
* counters. Note that the clock will not update until the radio starts, but we
* are still expected to report clock indications through control channel
* activity.
*/
void Transceiver::init(int wDelaySpread)
{
if (wDelaySpread < 0)
wDelaySpread = 0;
stop();
if (mControlServiceLoopThread) {
mControlServiceLoopThread->cancel();
mControlServiceLoopThread->join();
delete mControlServiceLoopThread;
}
UMTS::Time time(random() % 1024, 0);
mRadioInterface->getClock()->set(time);
mTransmitDeadlineClock = time;
mLastClockUpdateTime = time;
mLatencyUpdateTime = time;
mDelaySpread = wDelaySpread;
mPower = mRadioInterface->setPowerAttenuation(mPower);
mControlServiceLoopThread = new Thread(32768);
mControlServiceLoopThread->start((void * (*)(void*))
ControlServiceLoopAdapter, (void*) this);
}
/*
* Start the transceiver
*
* Submit command(s) to the radio device to commence streaming samples and
* launch threads to handle sample I/O. Re-synchronize the transmit burst
* counters to the central radio clock here as well. Perform locking because the
* stop request can occur simultaneously (i.e. shutdown).
*/
bool Transceiver::start()
{
ScopedLock lock(mLock);
if (mOn) {
LOG(ERR) << "Transceiver already running";
return false;
}
LOG(NOTICE) << "Starting the transceiver";
UMTS::Time time = mRadioInterface->getClock()->get();
mTransmitDeadlineClock = time;
mLastClockUpdateTime = time;
mLatencyUpdateTime = time;
if (!mRadioInterface->start()) {
LOG(ALERT) << "Device failed to start";
return false;
}
/* Device is running - launch I/O threads */
mTxServiceLoopThread = new Thread(8 * 32768);
mRxServiceLoopThread = new Thread(8 * 32768);
mTransmitPriorityQueueServiceLoopThread = new Thread(8 * 32768);
mTxServiceLoopThread->start((void * (*)(void*))
TxServiceLoopAdapter, (void*) this);
mRxServiceLoopThread->start((void * (*)(void*))
RxServiceLoopAdapter, (void*) this);
mTransmitPriorityQueueServiceLoopThread->start((void * (*)(void*))
TransmitPriorityQueueServiceLoopAdapter, (void*) this);
writeClockInterface();
mOn = true;
LOG(NOTICE) << "Transceiver running";
return true;
}
/*
* Stop the transceiver
*
* Perform stopping by disabling receive streaming and issuing cancellation
* requests to running threads. Threads will timeout and terminate at the
* cancellation points once the device is disabled. Perform locking since the
* stop request can come from the destructor (i.e. shutdown) in addition to the
* control thread.
*/
void Transceiver::stop()
{
ScopedLock lock(mLock);
if (!mOn)
return;
LOG(NOTICE) << "Stopping the transceiver";
mTxServiceLoopThread->cancel();
mRxServiceLoopThread->cancel();
mTransmitPriorityQueueServiceLoopThread->cancel();
LOG(INFO) << "Stopping the device";
mRadioInterface->stop();
LOG(INFO) << "Terminating threads";
mRxServiceLoopThread->join();
mTxServiceLoopThread->join();
mTransmitPriorityQueueServiceLoopThread->join();
delete mTxServiceLoopThread;
delete mRxServiceLoopThread;
delete mTransmitPriorityQueueServiceLoopThread;
mTransmitPriorityQueue.clear();
mOn = false;
LOG(NOTICE) << "Transceiver stopped";
}
void Transceiver::addRadioVector(signalVector &burst, UMTS::Time &wTime)
{
// modulate and stick into queue
radioVector *vec = new radioVector(burst, wTime);
RN_MEMLOG(radioVector, vec);
mTransmitPriorityQueue.write(vec);
}
void Transceiver::pushRadioVector(UMTS::Time &now)
{
radioVector *stale, *next;
// dump stale bursts, if any
while ((stale = mTransmitPriorityQueue.getStaleBurst(now))) {
LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface burst:"
<< stale->time() << " now:" << now;
writeClockInterface();
delete stale;
}
// if queue contains data at the desired timestamp, stick it into FIFO
if ((next = (radioVector*) mTransmitPriorityQueue.getCurrentBurst(now))) {
mRadioInterface->driveTransmitRadio(*(next), false);
delete next;
return;
}
// Extremely rare that we get here. We need to send a blank burst to the
// radio interface to update the timestamp.
LOG(INFO) << "Sending empty burst at " << now;
mRadioInterface->driveTransmitRadio(*(mEmptyTransmitBurst), true);
}
void Transceiver::reset()
{
mTransmitPriorityQueue.clear();
}
void Transceiver::driveControl()
{
int MAX_PACKET_LENGTH = 100;
// check control socket
char buffer[MAX_PACKET_LENGTH];
int msgLen = -1;
buffer[0] = '\0';
msgLen = mControlSocket.read(buffer, 1000);
if (msgLen < 1)
return;
char cmdcheck[4];
char command[MAX_PACKET_LENGTH];
char response[MAX_PACKET_LENGTH];
sscanf(buffer, "%3s %s", cmdcheck, command);
writeClockInterface();
if (strcmp(cmdcheck, "CMD") != 0) {
LOG(WARNING) << "bogus message on control interface";
return;
}
LOG(NOTICE) << "command is " << buffer;
if (!strcmp(command, "POWEROFF")) {
stop();
sprintf(response, "RSP POWEROFF 0");
}
else if (!strcmp(command, "POWERON")) {
if (!start())
sprintf(response, "RSP POWERON 1");
else
sprintf(response, "RSP POWERON 0");
}
else if (!strcmp(command, "SETRXGAIN")) {
int newGain;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &newGain);
newGain = mRadioInterface->setRxGain(newGain);
sprintf(response, "RSP SETRXGAIN 0 %d", newGain);
}
else if (!strcmp(command, "SETTXATTEN")) {
int power;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &power);
mPower = mRadioInterface->setPowerAttenuation(power);
sprintf(response, "RSP SETTXATTEN 0 %d", mPower);
}
else if (!strcmp(command, "SETFREQOFFSET")) {
sprintf(response, "RSP SETFREQOFFSET 1");
}
else if (!strcmp(command, "SETPOWER")) {
int power;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &power);
mPower = mRadioInterface->setPowerAttenuation(power);
sprintf(response, "RSP SETPOWER 0 %d", mPower);
}
else if (!strcmp(command, "ADJPOWER")) {
int step;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &step);
mPower = mRadioInterface->setPowerAttenuation(mPower + step);
sprintf(response, "RSP ADJPOWER 0 %d", mPower);
}
else if (!strcmp(command, "RXTUNE")) {
int freqkhz;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqkhz);
if (!mRadioInterface->tuneRx(freqkhz * 1e3)) {
LOG(ALERT) << "RX failed to tune";
sprintf(response, "RSP RXTUNE 1 %d", freqkhz);
}
else
sprintf(response, "RSP RXTUNE 0 %d", freqkhz);
}
else if (!strcmp(command, "TXTUNE")) {
int freqkhz;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqkhz);
if (!mRadioInterface->tuneTx(freqkhz * 1e3)) {
LOG(ALERT) << "TX failed to tune";
sprintf(response, "RSP TXTUNE 1 %d", freqkhz);
}
else
sprintf(response, "RSP TXTUNE 0 %d", freqkhz);
}
else if (!strcmp(command, "SETFREQOFFSET")) {
sprintf(response, "RSP SETFREQOFFSET 1");
}
else {
LOG(WARNING) << "bogus command " << command << " on control interface.";
}
LOG(DEBUG) << "response: " << response;
mControlSocket.write(response, strlen(response) + 1);
}
bool Transceiver::driveTransmitPriorityQueue()
{
char buffer[MAX_UDP_LENGTH];
// check data socket
int msgLen = mDataSocket.read(buffer, 1000);
if (msgLen < 0)
return false;
if (msgLen != 2 * UMTS::gSlotLen + 4) {
LOG(ERR) << "badly formatted packet on UMTS->TRX interface";
return false;
}
int timeSlot = (int) buffer[0];
uint16_t frameNum = 0;
for (int i = 0; i < 2; i++)
frameNum = (frameNum << 8) | (0x0ff & buffer[i + 1]);
static signalVector newBurst(UMTS::gSlotLen);
signalVector::iterator itr = newBurst.begin();
signed char *bufferItr = (signed char *) (buffer + 3);
while (itr < newBurst.end()) {
*itr++ = complex((float) *(bufferItr + 0),
(float) *(bufferItr + 1));
bufferItr++;
bufferItr++;
}
UMTS::Time currTime = UMTS::Time(frameNum, timeSlot);
addRadioVector(newBurst, currTime);
return true;
}
void Transceiver::driveReceiveFIFO()
{
radioVector *rxBurst = NULL;
int RSSI = 0;
UMTS::Time burstTime;
mRadioInterface->driveReceiveRadio(1024 + mDelaySpread);
rxBurst = (radioVector *) mReceiveFIFO->get();
if (!rxBurst)
return;
burstTime = rxBurst->time();
size_t burstSize = 2 * rxBurst->size() + 3 + 1 + 1;
char burstString[burstSize];
burstString[0] = burstTime.TN();
for (size_t i = 0; i < 2; i++)
burstString[1 + i] = (burstTime.FN() >> ((1 - i) * 8)) & 0x0ff;
burstString[3] = RSSI;
radioVector::iterator burstItr = rxBurst->begin();
char *burstPtr = burstString + 4;
for (size_t i = 0; i < UMTS::gSlotLen + 1024 + mDelaySpread; i++) {
*burstPtr++ = (char) (int8_t) burstItr->real();
*burstPtr++ = (char) -(int8_t) burstItr->imag();
burstItr++;
}
burstString[burstSize - 1] = '\0';
delete rxBurst;
if (!burstTime.TN() && !(burstTime.FN() % CLK_IND_INTERVAL))
writeClockInterface();
mDataSocket.write(burstString, burstSize);
}
/*
* Features a carefully controlled latency mechanism, to
* assure that transmit packets arrive at the radio/USRP
* before they need to be transmitted.
*
* Deadline clock indicates the burst that needs to be
* pushed into the FIFO right NOW. If transmit queue does
* not have a burst, stick in filler data.
*/
void Transceiver::driveTransmitFIFO()
{
RadioClock *radioClock = mRadioInterface->getClock();
if (!mOn)
return;
radioClock->wait();
while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
// if underrun, then we're not providing bursts to radio/USRP fast
// enough. Need to increase latency by one UMTS frame.
if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) {
if (mRadioInterface->isUnderrun()) {
// only do latency update every 10 frames, so we don't over update
if (radioClock->get() > mLatencyUpdateTime + UMTS::Time(10, 0)) {
mTransmitLatency = mTransmitLatency + UMTS::Time(1, 0);
if (mTransmitLatency > UMTS::Time(15, 0))
mTransmitLatency = UMTS::Time(15, 0);
LOG(INFO) << "new latency: " << mTransmitLatency;
mLatencyUpdateTime = radioClock->get();
}
} else {
// if underrun hasn't occurred in the last sec (100 frames) drop
// transmit latency by a timeslot
if (mTransmitLatency > UMTS::Time(1, 0)) {
if (radioClock->get() > mLatencyUpdateTime + UMTS::Time(100, 0)) {
mTransmitLatency.decTN();
LOG(INFO) << "reduced latency: " << mTransmitLatency;
mLatencyUpdateTime = radioClock->get();
}
}
}
}
// time to push burst to transmit FIFO
pushRadioVector(mTransmitDeadlineClock);
mTransmitDeadlineClock.incTN();
}
}
void Transceiver::writeClockInterface()
{
char command[50];
sprintf(command, "IND CLOCK %llu",
(unsigned long long) (mTransmitDeadlineClock.FN() + 8));
LOG(INFO) << "ClockInterface: sending " << command;
mClockSocket.write(command, strlen(command) + 1);
mLastClockUpdateTime = mTransmitDeadlineClock;
}
void *RxServiceLoopAdapter(Transceiver *transceiver)
{
while (1) {
transceiver->driveReceiveFIFO();
pthread_testcancel();
}
return NULL;
}
void *TxServiceLoopAdapter(Transceiver *transceiver)
{
while (1) {
transceiver->driveTransmitFIFO();
pthread_testcancel();
}
return NULL;
}
void *ControlServiceLoopAdapter(Transceiver *transceiver)
{
while (1) {
transceiver->driveControl();
pthread_testcancel();
}
return NULL;
}
void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *transceiver)
{
while (1) {
transceiver->driveTransmitPriorityQueue();
pthread_testcancel();
}
return NULL;
}

View File

@@ -0,0 +1,121 @@
#ifndef TRANSCEIVER_H
#define TRANSCEIVER_H
#include "RadioInterface.h"
#include "Interthread.h"
#include "UMTSCommon.h"
#include "Sockets.h"
#include <sys/types.h>
#include <sys/socket.h>
/** The Transceiver class, responsible for physical layer of basestation */
class Transceiver {
private:
UDPSocket mDataSocket; ///< socket for writing to/reading from UMTS core
UDPSocket mControlSocket; ///< socket for writing/reading control commands from UMTS core
UDPSocket mClockSocket; ///< socket for writing clock updates to UMTS core
VectorQueue mTransmitPriorityQueue; ///< priority queue of transmit bursts received from UMTS core
VectorFIFO* mTransmitFIFO; ///< radioInterface FIFO of transmit bursts
VectorFIFO* mReceiveFIFO; ///< radioInterface FIFO of receive bursts
RadioBurstFIFO mT;
RadioBurstFIFO mR;
Thread *mTxServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO
Thread *mRxServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO
Thread *mTransmitPriorityQueueServiceLoopThread;///< thread to process transmit bursts from UMTS core
Thread *mControlServiceLoopThread; ///< thread to process control messages from UMTS core
bool mOn; ///< flag to indicate that transceiver is powered on
int mPower; ///< the transmit power in dB
int mDelaySpread; ///< maximum expected delay spread, i.e. extend buffer when sending upstream
UMTS::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
UMTS::Time mLatencyUpdateTime; ///< last time latency was updated
UMTS::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
UMTS::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
radioVector *mEmptyTransmitBurst;
RadioInterface *mRadioInterface; ///< associated radioInterface object
/** modulate and add a burst to the transmit queue */
void addRadioVector(signalVector &burst, UMTS::Time &wTime);
/** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
void pushRadioVector(UMTS::Time &nowTime);
/** send messages over the clock socket */
void writeClockInterface(void);
/** Start and stop I/O threads through the control socket API */
bool start();
void stop();
/** Protect destructor accessable stop call */
Mutex mLock;
public:
/** Transceiver constructor
@param wBasePort base port number of UDP sockets
@param TRXAddress IP address of the TRX manager, as a string
@param wTransmitLatency initial setting of transmit latency
@param radioInterface associated radioInterface object
*/
Transceiver(int wBasePort,
const char *TRXAddress,
UMTS::Time wTransmitLatency,
RadioInterface *wRadioInterface);
/** Destructor */
~Transceiver();
/** Start the control loop */
void init(int wDelaySpread);
/** attach the radioInterface receive FIFO */
void receiveFIFO(VectorFIFO *wFIFO) { mReceiveFIFO = wFIFO; }
/** attach the radioInterface transmit FIFO */
void transmitFIFO(VectorFIFO *wFIFO) { mTransmitFIFO = wFIFO; }
RadioBurstFIFO* highSideTransmitFIFO(void) { return &mT; }
RadioBurstFIFO* highSideReceiveFIFO(void) { return &mR; }
protected:
/** drive reception and demodulation of UMTS bursts */
void driveReceiveFIFO();
/** drive transmission of UMTS bursts */
void driveTransmitFIFO();
/** drive handling of control messages from UMTS core */
void driveControl();
/**
drive modulation and sorting of UMTS bursts from UMTS core
@return true if a burst was transferred successfully
*/
bool driveTransmitPriorityQueue();
friend void *TxServiceLoopAdapter(Transceiver *);
friend void *RxServiceLoopAdapter(Transceiver *);
friend void *ControlServiceLoopAdapter(Transceiver *);
friend void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);
void reset();
};
/** FIFO thread loop */
void *TxServiceLoopAdapter(Transceiver *);
void *RxServiceLoopAdapter(Transceiver *);
/** control message handler thread loop */
void *ControlServiceLoopAdapter(Transceiver *);
/** transmit queueing thread loop */
void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);
#endif /* TRANSCEIVER_H */

View File

@@ -0,0 +1,729 @@
/*
* Device support for Ettus Research UHD driver
* Written by Tom Tsou <tom@tsou.cc>
*
* Copyright 2010-2011 Free Software Foundation, Inc.
* Copyright 2014 Ettus Research LLC
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#include <uhd/version.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/utils/thread_priority.hpp>
#include <uhd/utils/msg.hpp>
#include "Threads.h"
#include "Logger.h"
#include "UHDDevice.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/*
* Transmit packet synchronization count
*
* During device start and periods of underrun recovery, we need to prevent
* flooding the device with buffered late packets. This parameter is the number
* of packets to remove from the beginning of a transmit stream so that we get
* close to an on-time packet arrival.
*/
#define TX_PACKET_SYNC 30
/*
* B200/B210 FPGA clocking rate
*
* Rate derived from the fixed 100 MHz N200 clocking rate. We use a device
* sample rate of 6.25 Msps, or approximately 1.63 samples-per-chip based on the
* 3.84 Mcps UMTS rate. This provides a balance of sufficient filtering and
* device compatibility.
*/
#define B2XX_CLK_RT 25e6
/*
* Device clocking offset limit
*
* Error if the actual device clocking rate differs from the requested rate by
* more than this limit.
*/
#define MASTER_CLK_LIMIT 10.0
/* Timestamped ring buffer size */
#define SAMPLE_BUF_SZ (1 << 20)
struct uhd_dev_offset {
enum uhd_dev_type type;
int offset;
};
/*
* Tx/Rx sample offset values
*
* Timing adjustment in samples. This value accounts is empiracally measured
* and accounts for overall group delay digital filters and analog components.
*/
static struct uhd_dev_offset uhd_offsets[NUM_USRP_TYPES] = {
{ USRP1, 0 },
{ USRP2, 61 },
{ B100, 0 },
{ B2XX, 99 },
{ X300, 73 },
{ UMTRX, 0 },
};
static int get_dev_offset(enum uhd_dev_type type)
{
if ((type != B2XX) && (type != USRP2) && (type != X300)) {
LOG(ALERT) << "Unsupported device type";
return 0;
}
for (int i = 0; i < NUM_USRP_TYPES; i++) {
if (type == uhd_offsets[i].type)
return uhd_offsets[i].offset;
}
return 0;
}
static void *async_event_loop(UHDDevice *dev)
{
while (1) {
dev->recv_async_msg();
pthread_testcancel();
}
return NULL;
}
/*
* Catch and drop underrun 'U' and overrun 'O' messages from stdout
* since we already report using the logging facility. Direct
* everything else appropriately.
*/
void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
{
switch (type) {
case uhd::msg::status:
LOG(INFO) << msg;
break;
case uhd::msg::warning:
LOG(WARNING) << msg;
break;
case uhd::msg::error:
LOG(ERR) << msg;
break;
case uhd::msg::fastpath:
break;
}
}
static void thread_enable_cancel(bool cancel)
{
cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
}
UHDDevice::UHDDevice(double rate)
: tx_gain_min(0.0), tx_gain_max(0.0),
rx_gain_min(0.0), rx_gain_max(0.0),
tx_rate(rate), rx_rate(rate), tx_freq(0.0), rx_freq(0.0),
started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0),
prev_ts(0), ts_offset(0), rx_buffer(NULL)
{
}
UHDDevice::~UHDDevice()
{
stop();
delete rx_buffer;
}
void UHDDevice::init_gains()
{
uhd::gain_range_t range;
range = usrp_dev->get_tx_gain_range();
tx_gain_min = range.start();
tx_gain_max = range.stop();
range = usrp_dev->get_rx_gain_range();
rx_gain_min = range.start();
rx_gain_max = range.stop();
usrp_dev->set_tx_gain((tx_gain_min + tx_gain_max) / 2);
usrp_dev->set_rx_gain((rx_gain_min + rx_gain_max) / 2);
return;
}
int UHDDevice::set_master_clk(double clk_rate)
{
double actual, offset, limit = 10.0;
try {
usrp_dev->set_master_clock_rate(clk_rate);
} catch (const std::exception &ex) {
LOG(ALERT) << "UHD clock rate setting failed: " << clk_rate;
LOG(ALERT) << ex.what();
return -1;
}
actual = usrp_dev->get_master_clock_rate();
offset = fabs(clk_rate - actual);
if (offset > limit) {
LOG(ALERT) << "Failed to set master clock rate";
LOG(ALERT) << "Requested clock rate " << clk_rate;
LOG(ALERT) << "Actual clock rate " << actual;
return -1;
}
return 0;
}
int UHDDevice::set_rates(double tx_rate, double rx_rate)
{
double offset_limit = 1.0;
double tx_offset, rx_offset;
/* B2XX is the only device where we set FPGA clocking */
if (dev_type == B2XX) {
if (set_master_clk(B2XX_CLK_RT) < 0)
return -1;
}
try {
usrp_dev->set_tx_rate(tx_rate);
usrp_dev->set_rx_rate(rx_rate);
} catch (const std::exception &ex) {
LOG(ALERT) << "UHD rate setting failed";
LOG(ALERT) << ex.what();
return -1;
}
this->tx_rate = usrp_dev->get_tx_rate();
this->rx_rate = usrp_dev->get_rx_rate();
tx_offset = fabs(this->tx_rate - tx_rate);
rx_offset = fabs(this->rx_rate - rx_rate);
if ((tx_offset > offset_limit) || (rx_offset > offset_limit)) {
LOG(ALERT) << "Actual sample rate differs from desired rate";
LOG(ALERT) << "Tx/Rx (" << this->tx_rate << "/"
<< this->rx_rate << ")" << std::endl;
return -1;
}
return 0;
}
double UHDDevice::setTxGain(double db)
{
usrp_dev->set_tx_gain(db);
double tx_gain = usrp_dev->get_tx_gain();
LOG(INFO) << "Set TX gain to " << tx_gain << "dB";
return tx_gain;
}
double UHDDevice::setRxGain(double db)
{
usrp_dev->set_rx_gain(db);
double rx_gain = usrp_dev->get_rx_gain();
LOG(INFO) << "Set RX gain to " << rx_gain << "dB";
return rx_gain;
}
/*
* Parse the UHD device tree and mboard name to find out what device we're
* dealing with. We need the window type so that the transceiver knows how to
* deal with the transport latency. Reject the USRP1 because UHD doesn't
* support timestamped samples with it.
*/
bool UHDDevice::parse_dev_type()
{
std::string mboard_str, dev_str;
uhd::property_tree::sptr prop_tree;
size_t usrp2_str, b200_str, b210_str, x300_str, x310_str;
prop_tree = usrp_dev->get_device()->get_tree();
dev_str = prop_tree->access<std::string>("/name").get();
mboard_str = usrp_dev->get_mboard_name();
usrp2_str = dev_str.find("USRP2");
b200_str = mboard_str.find("B200");
b210_str = mboard_str.find("B210");
x300_str = mboard_str.find("X300");
x310_str = mboard_str.find("X310");
if (b200_str != std::string::npos) {
dev_type = B2XX;
} else if (b210_str != std::string::npos) {
dev_type = B2XX;
} else if (usrp2_str != std::string::npos) {
dev_type = USRP2;
} else if (x300_str != std::string::npos) {
dev_type = X300;
} else if (x310_str != std::string::npos) {
dev_type = X300;
} else {
goto nosupport;
}
tx_window = TX_WINDOW_FIXED;
LOG(INFO) << "Using fixed transmit window for "
<< dev_str << " " << mboard_str;
return true;
nosupport:
LOG(ALERT) << "Device not supported by OpenBTS-UMTS";
return false;
}
bool UHDDevice::open(const std::string &args, bool extref)
{
/* Find UHD devices */
uhd::device_addr_t addr(args);
uhd::device_addrs_t dev_addrs = uhd::device::find(addr);
if (dev_addrs.size() == 0) {
LOG(ALERT) << "No UHD devices found with address '" << args << "'";
return false;
}
/* Use the first found device */
LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string();
try {
usrp_dev = uhd::usrp::multi_usrp::make(dev_addrs[0]);
} catch(...) {
LOG(ALERT) << "UHD make failed, device " << dev_addrs[0].to_string();
return false;
}
/* Check for a valid device type and set bus type */
if (!parse_dev_type())
return false;
if (extref)
usrp_dev->set_clock_source("external");
/* Create TX and RX streamers */
uhd::stream_args_t stream_args("sc16");
tx_stream = usrp_dev->get_tx_stream(stream_args);
rx_stream = usrp_dev->get_rx_stream(stream_args);
/* Number of samples per over-the-wire packet */
tx_spp = tx_stream->get_max_num_samps();
rx_spp = rx_stream->get_max_num_samps();
set_rates(tx_rate, rx_rate);
/* Create receive buffer */
size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
rx_buffer = new SampleBuffer(buf_len, rx_rate);
/* Set receive chain sample offset */
ts_offset = get_dev_offset(dev_type);
/* Initialize and shadow gain values */
init_gains();
LOG(INFO) << "\n" << usrp_dev->get_pp_string();
return true;
}
bool UHDDevice::flush_recv(size_t num_pkts)
{
uhd::rx_metadata_t md;
size_t num_smpls, chans = 1;
float timeout = 0.5f;
std::vector<std::vector<short> >
pkt_bufs(chans, std::vector<short>(2 * rx_spp));
std::vector<short *> pkt_ptrs;
for (size_t i = 0; i < pkt_bufs.size(); i++)
pkt_ptrs.push_back(&pkt_bufs[i].front());
ts_initial = 0;
while (!ts_initial || (num_pkts-- > 0)) {
num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, md,
timeout, true);
if (!num_smpls) {
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
LOG(ALERT) << "Device timed out";
return false;
default:
continue;
}
}
ts_initial = md.time_spec.to_ticks(rx_rate);
}
LOG(INFO) << "Initial timestamp " << ts_initial << std::endl;
return true;
}
bool UHDDevice::restart()
{
double delay = 0.1;
aligned = false;
uhd::time_spec_t current = usrp_dev->get_time_now();
uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
cmd.stream_now = false;
cmd.time_spec = uhd::time_spec_t(current.get_real_secs() + delay);
usrp_dev->issue_stream_cmd(cmd);
return flush_recv(10);
}
bool UHDDevice::start()
{
LOG(INFO) << "Starting USRP...";
if (started) {
LOG(ERR) << "Device already running";
return false;
}
setPriority();
/* Register msg handler */
uhd::msg::register_handler(&uhd_msg_handler);
/* Start receive streaming */
if (!restart())
return false;
/* Start asynchronous event (underrun check) loop */
async_event_thrd = new Thread();
async_event_thrd->start((void * (*)(void*))async_event_loop, (void*) this);
/* Display USRP time */
double time_now = usrp_dev->get_time_now().get_real_secs();
LOG(INFO) << "The current time is " << time_now << " seconds";
started = true;
return true;
}
/*
* Stop the UHD device
*
* Issue a stop streaming command and cancel the asynchronous message loop.
* Deallocate the asynchronous thread object since we can't reuse it anyways.
* A new thread object is allocated on device start.
*/
bool UHDDevice::stop()
{
if (!started) {
LOG(ERR) << "Device not running";
return false;
}
uhd::stream_cmd_t stream_cmd =
uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
usrp_dev->issue_stream_cmd(stream_cmd);
async_event_thrd->cancel();
async_event_thrd->join();
delete async_event_thrd;
started = false;
return true;
}
void UHDDevice::setPriority()
{
uhd::set_thread_priority_safe();
return;
}
int UHDDevice::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
{
long long ts;
if (!num_smpls) {
LOG(ERR) << str_code(md);
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
LOG(ALERT) << "UHD: Receive timed out";
case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET:
default:
return ERROR_UNHANDLED;
}
}
/* Missing timestamp */
if (!md.has_time_spec) {
LOG(ALERT) << "UHD: Received packet missing timestamp";
return ERROR_UNRECOVERABLE;
}
ts = md.time_spec.to_ticks(rx_rate);
/* Monotonicity check */
if (ts < prev_ts) {
LOG(ALERT) << "UHD: Loss of monotonic time";
LOG(ALERT) << "Current time: " << ts << ", "
<< "Previous time: " << prev_ts;
return ERROR_TIMING;
} else {
prev_ts = ts;
}
return 0;
}
int UHDDevice::readSamples(short *buf, int len, bool *overrun,
long long timestamp, bool *underrun, unsigned *RSSI)
{
ssize_t rc;
uhd::time_spec_t timespec;
uhd::rx_metadata_t metadata;
uint32_t pkt_buf[rx_spp];
*overrun = false;
*underrun = false;
/* Align read time sample timing with respect to transmit clock */
timestamp += ts_offset;
timespec = uhd::time_spec_t::from_ticks(timestamp, rx_rate);
LOG(DEBUG) << "Requested timestamp = " << timestamp;
/* Check that timestamp is valid */
rc = rx_buffer->avail_smpls(timestamp);
if (rc < 0) {
LOG(ERR) << rx_buffer->str_code(rc);
LOG(ERR) << rx_buffer->str_status(timestamp);
return 0;
}
/* Receive samples from the usrp until we have enough */
while (rx_buffer->avail_smpls(timestamp) < len) {
thread_enable_cancel(false);
size_t num_smpls = rx_stream->recv(
(void *) pkt_buf,
rx_spp,
metadata,
0.1,
true);
thread_enable_cancel(true);
rx_pkt_cnt++;
/* Check for errors */
rc = check_rx_md_err(metadata, num_smpls);
switch (rc) {
case ERROR_UNRECOVERABLE:
LOG(ALERT) << "UHD: Version " << uhd::get_version_string();
LOG(ALERT) << "UHD: Unrecoverable error, exiting...";
exit(-1);
case ERROR_TIMING:
case ERROR_UNHANDLED:
continue;
}
timespec = metadata.time_spec;
LOG(DEBUG) << "Received timestamp = " << timespec.to_ticks(rx_rate);
rc = rx_buffer->write(pkt_buf,
num_smpls,
metadata.time_spec);
/* Continue on local overrun, exit on other errors */
if ((rc < 0)) {
LOG(ERR) << rx_buffer->str_code(rc);
LOG(ERR) << rx_buffer->str_status(timestamp);
if (rc != SampleBuffer::ERROR_OVERFLOW)
return 0;
}
}
/* We have enough samples */
rc = rx_buffer->read(buf, len, timestamp);
if ((rc < 0) || (rc != len)) {
LOG(ERR) << rx_buffer->str_code(rc);
LOG(ERR) << rx_buffer->str_status(timestamp);
return 0;
}
return len;
}
int UHDDevice::writeSamples(short *buf, int len,
bool *underrun, long long ts)
{
uhd::tx_metadata_t metadata;
metadata.has_time_spec = true;
metadata.start_of_burst = false;
metadata.end_of_burst = false;
metadata.time_spec = uhd::time_spec_t::from_ticks(ts, tx_rate);
*underrun = false;
/* Drop a fixed number of packets */
if (!aligned) {
drop_cnt++;
if (drop_cnt == 1) {
LOG(DEBUG) << "Aligning transmitter: stop burst";
*underrun = true;
metadata.end_of_burst = true;
} else if (drop_cnt < TX_PACKET_SYNC) {
LOG(DEBUG) << "Aligning transmitter: packet advance";
return len;
} else {
LOG(DEBUG) << "Aligning transmitter: start burst";
metadata.start_of_burst = true;
aligned = true;
drop_cnt = 0;
}
}
thread_enable_cancel(false);
size_t num_smpls = tx_stream->send(buf, len, metadata);
thread_enable_cancel(true);
if (num_smpls != (unsigned) len) {
LOG(ALERT) << "UHD: Device send timed out";
}
return num_smpls;
}
bool UHDDevice::setTxFreq(double wFreq)
{
uhd::tune_result_t tr = usrp_dev->set_tx_freq(wFreq);
LOG(INFO) << "\n" << tr.to_pp_string();
tx_freq = usrp_dev->get_tx_freq();
return true;
}
bool UHDDevice::setRxFreq(double wFreq)
{
uhd::tune_result_t tr = usrp_dev->set_rx_freq(wFreq);
LOG(INFO) << "\n" << tr.to_pp_string();
rx_freq = usrp_dev->get_rx_freq();
return true;
}
bool UHDDevice::recv_async_msg()
{
uhd::async_metadata_t md;
thread_enable_cancel(false);
bool rc = tx_stream->recv_async_msg(md, 0.1);
thread_enable_cancel(true);
if (!rc)
return false;
/* Assume that any error requires resynchronization */
if (md.event_code != uhd::async_metadata_t::EVENT_CODE_BURST_ACK) {
aligned = false;
if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) &&
(md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) {
LOG(ERR) << str_code(md);
}
}
return true;
}
std::string UHDDevice::str_code(uhd::rx_metadata_t metadata)
{
std::ostringstream ost("UHD: ");
switch (metadata.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_NONE:
ost << "No error";
break;
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
ost << "No packet received, implementation timed-out";
break;
case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
ost << "A stream command was issued in the past";
break;
case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
ost << "Expected another stream command";
break;
case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
ost << "An internal receive buffer has filled";
break;
case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET:
ost << "The packet could not be parsed";
break;
default:
ost << "Unknown error " << metadata.error_code;
}
if (metadata.has_time_spec)
ost << " at " << metadata.time_spec.get_real_secs() << " sec.";
return ost.str();
}
std::string UHDDevice::str_code(uhd::async_metadata_t metadata)
{
std::ostringstream ost("UHD: ");
switch (metadata.event_code) {
case uhd::async_metadata_t::EVENT_CODE_BURST_ACK:
ost << "A packet was successfully transmitted";
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW:
ost << "An internal send buffer has emptied";
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR:
ost << "Packet loss between host and device";
break;
case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR:
ost << "Packet time was too late or too early";
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET:
ost << "Underflow occurred inside a packet";
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST:
ost << "Packet loss within a burst";
break;
default:
ost << "Unknown error " << metadata.event_code;
}
if (metadata.has_time_spec)
ost << " at " << metadata.time_spec.get_real_secs() << " sec.";
return ost.str();
}

116
TransceiverUHD/UHDDevice.h Normal file
View File

@@ -0,0 +1,116 @@
#ifndef UHD_DEVICE_H
#define UHD_DEVICE_H
#include <uhd/usrp/multi_usrp.hpp>
#include "RadioDevice.h"
#include "SampleBuffer.h"
#define TX_AMPL 0.3
enum uhd_dev_type {
USRP1,
USRP2,
B100,
B2XX,
X300,
UMTRX,
NUM_USRP_TYPES,
};
class UHDDevice : public RadioDevice {
public:
UHDDevice(double rate);
~UHDDevice();
bool open(const std::string &args, bool extref);
bool start();
bool stop();
bool restart();
void setPriority();
enum TxWindowType getWindowType() { return tx_window; }
int readSamples(short *buf, int len, bool *overrun,
long long timestamp, bool *underrun, unsigned *RSSI);
int writeSamples(short *buf, int len,
bool *underrun, long long timestamp);
bool setVCTCXO(unsigned int) { return true; };
bool setTxFreq(double wFreq);
bool setRxFreq(double wFreq);
inline long long initialWriteTimestamp() { return ts_initial; }
inline long long initialReadTimestamp() { return ts_initial; }
inline double fullScaleInputValue() { return 32000 * TX_AMPL; }
inline double fullScaleOutputValue() { return 32000; }
double setRxGain(double db);
double getRxGain(void) { return rx_gain; }
double maxRxGain(void) { return rx_gain_max; }
double minRxGain(void) { return rx_gain_min; }
double setTxGain(double db);
double maxTxGain(void) { return tx_gain_max; }
double minTxGain(void) { return tx_gain_min; }
double getTxFreq() { return tx_freq; }
double getRxFreq() { return rx_freq; }
inline double getSampleRate() { return tx_rate; }
inline double numberRead() { return rx_pkt_cnt; }
inline double numberWritten() { return 0; }
/** Receive and process asynchronous message
@return true if message received or false on timeout or error
*/
bool recv_async_msg();
enum err_code {
ERROR_TIMING = -1,
ERROR_UNRECOVERABLE = -2,
ERROR_UNHANDLED = -3,
};
private:
uhd::usrp::multi_usrp::sptr usrp_dev;
uhd::tx_streamer::sptr tx_stream;
uhd::rx_streamer::sptr rx_stream;
enum TxWindowType tx_window;
enum uhd_dev_type dev_type;
int sps;
int tx_spp, rx_spp;
double tx_gain, tx_gain_min, tx_gain_max;
double rx_gain, rx_gain_min, rx_gain_max;
double tx_rate, rx_rate;
double tx_freq, rx_freq;
bool started;
bool aligned;
size_t rx_pkt_cnt;
size_t drop_cnt;
long long prev_ts;
long long ts_initial, ts_offset;
SampleBuffer *rx_buffer;
void init_gains();
void set_ref_clk(bool ext_clk);
int set_master_clk(double rate);
int set_rates(double tx_rate, double rx_rate);
bool parse_dev_type();
bool flush_recv(size_t num_pkts);
int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls);
std::string str_code(uhd::rx_metadata_t metadata);
std::string str_code(uhd::async_metadata_t metadata);
Thread *async_event_thrd;
};
#endif /* UHD_DEVICE_H */

199
TransceiverUHD/convert.c Normal file
View File

@@ -0,0 +1,199 @@
/*
* SSE type conversions
* Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
*
* 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 <malloc.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SSE3
#include <xmmintrin.h>
#include <emmintrin.h>
#ifdef HAVE_SSE4_1
#include <smmintrin.h>
/* 16*N 16-bit signed integer converted to single precision floats */
static void _sse_convert_si16_ps_16n(float *restrict out,
short *restrict in,
int len)
{
__m128i m0, m1, m2, m3, m4, m5;
__m128 m6, m7, m8, m9;
for (int i = 0; i < len / 16; i++) {
/* Load (unaligned) packed floats */
m0 = _mm_loadu_si128((__m128i *) &in[16 * i + 0]);
m1 = _mm_loadu_si128((__m128i *) &in[16 * i + 8]);
/* Unpack */
m2 = _mm_cvtepi16_epi32(m0);
m4 = _mm_cvtepi16_epi32(m1);
m0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1, 0, 3, 2));
m1 = _mm_shuffle_epi32(m1, _MM_SHUFFLE(1, 0, 3, 2));
m3 = _mm_cvtepi16_epi32(m0);
m5 = _mm_cvtepi16_epi32(m1);
/* Convert */
m6 = _mm_cvtepi32_ps(m2);
m7 = _mm_cvtepi32_ps(m3);
m8 = _mm_cvtepi32_ps(m4);
m9 = _mm_cvtepi32_ps(m5);
/* Store */
_mm_storeu_ps(&out[16 * i + 0], m6);
_mm_storeu_ps(&out[16 * i + 4], m7);
_mm_storeu_ps(&out[16 * i + 8], m8);
_mm_storeu_ps(&out[16 * i + 12], m9);
}
}
/* 16*N 16-bit signed integer conversion with remainder */
static void _sse_convert_si16_ps(float *restrict out,
short *restrict in,
int len)
{
int start = len / 16 * 16;
_sse_convert_si16_ps_16n(out, in, len);
for (int i = 0; i < len % 16; i++)
out[start + i] = in[start + i];
}
#endif /* HAVE_SSE4_1 */
/* 8*N single precision floats scaled and converted to 16-bit signed integer */
static void _sse_convert_scale_ps_si16_8n(short *restrict out,
float *restrict in,
float scale, int len)
{
__m128 m0, m1, m2;
__m128i m4, m5;
for (int i = 0; i < len / 8; i++) {
/* Load (unaligned) packed floats */
m0 = _mm_loadu_ps(&in[8 * i + 0]);
m1 = _mm_loadu_ps(&in[8 * i + 4]);
m2 = _mm_load1_ps(&scale);
/* Scale */
m0 = _mm_mul_ps(m0, m2);
m1 = _mm_mul_ps(m1, m2);
/* Convert */
m4 = _mm_cvtps_epi32(m0);
m5 = _mm_cvtps_epi32(m1);
/* Pack and store */
m5 = _mm_packs_epi32(m4, m5);
_mm_storeu_si128((__m128i *) &out[8 * i], m5);
}
}
/* 8*N single precision floats scaled and converted with remainder */
static void _sse_convert_scale_ps_si16(short *restrict out,
float *restrict in,
float scale, int len)
{
int start = len / 8 * 8;
_sse_convert_scale_ps_si16_8n(out, in, scale, len);
for (int i = 0; i < len % 8; i++)
out[start + i] = in[start + i] * scale;
}
/* 16*N single precision floats scaled and converted to 16-bit signed integer */
static void _sse_convert_scale_ps_si16_16n(short *restrict out,
float *restrict in,
float scale, int len)
{
__m128 m0, m1, m2, m3, m4;
__m128i m5, m6, m7, m8;
for (int i = 0; i < len / 16; i++) {
/* Load (unaligned) packed floats */
m0 = _mm_loadu_ps(&in[16 * i + 0]);
m1 = _mm_loadu_ps(&in[16 * i + 4]);
m2 = _mm_loadu_ps(&in[16 * i + 8]);
m3 = _mm_loadu_ps(&in[16 * i + 12]);
m4 = _mm_load1_ps(&scale);
/* Scale */
m0 = _mm_mul_ps(m0, m4);
m1 = _mm_mul_ps(m1, m4);
m2 = _mm_mul_ps(m2, m4);
m3 = _mm_mul_ps(m3, m4);
/* Convert */
m5 = _mm_cvtps_epi32(m0);
m6 = _mm_cvtps_epi32(m1);
m7 = _mm_cvtps_epi32(m2);
m8 = _mm_cvtps_epi32(m3);
/* Pack and store */
m5 = _mm_packs_epi32(m5, m6);
m7 = _mm_packs_epi32(m7, m8);
_mm_storeu_si128((__m128i *) &out[16 * i + 0], m5);
_mm_storeu_si128((__m128i *) &out[16 * i + 8], m7);
}
}
#else /* HAVE_SSE3 */
static void convert_scale_ps_si16(short *out, float *in, float scale, int len)
{
for (int i = 0; i < len; i++)
out[i] = in[i] * scale;
}
#endif
#ifndef HAVE_SSE4_1
static void convert_si16_ps(float *out, short *in, int len)
{
for (int i = 0; i < len; i++)
out[i] = in[i];
}
#endif
void convert_float_short(short *out, float *in, float scale, int len)
{
#ifdef HAVE_SSE3
if (!(len % 16))
_sse_convert_scale_ps_si16_16n(out, in, scale, len);
else if (!(len % 8))
_sse_convert_scale_ps_si16_8n(out, in, scale, len);
else
_sse_convert_scale_ps_si16(out, in, scale, len);
#else
convert_scale_ps_si16(out, in, scale, len);
#endif
}
void convert_short_float(float *out, short *in, int len)
{
#ifdef HAVE_SSE4_1
if (!(len % 16))
_sse_convert_si16_ps_16n(out, in, len);
else
_sse_convert_si16_ps(out, in, len);
#else
convert_si16_ps(out, in, len);
#endif
}

7
TransceiverUHD/convert.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef _CONVERT_H_
#define _CONVERT_H_
void convert_float_short(short *out, float *in, float scale, int len);
void convert_short_float(float *out, short *in, int len);
#endif /* _CONVERT_H_ */

714
TransceiverUHD/convolve.c Normal file
View File

@@ -0,0 +1,714 @@
/*
* SSE Convolution
* Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
*
* 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 <malloc.h>
#include <string.h>
#include <stdio.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SSE3
#include <xmmintrin.h>
#include <pmmintrin.h>
/* 4-tap SSE complex-real convolution */
static void sse_conv_real4(float *restrict x,
float *restrict h,
float *restrict y,
int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[0]);
m1 = _mm_load_ps(&h[4]);
m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
for (int i = 0; i < len; i++) {
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 4]);
m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m4 = _mm_mul_ps(m2, m7);
m5 = _mm_mul_ps(m3, m7);
/* Sum and store */
m6 = _mm_hadd_ps(m4, m5);
m0 = _mm_hadd_ps(m6, m6);
_mm_store_ss(&y[2 * i + 0], m0);
m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m0);
}
}
/* 8-tap SSE complex-real convolution */
static void sse_conv_real8(float *restrict x,
float *restrict h,
float *restrict y,
int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7, m8, m9;
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[0]);
m1 = _mm_load_ps(&h[4]);
m2 = _mm_load_ps(&h[8]);
m3 = _mm_load_ps(&h[12]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
for (int i = 0; i < len; i++) {
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 4]);
m2 = _mm_loadu_ps(&x[2 * i + 8]);
m3 = _mm_loadu_ps(&x[2 * i + 12]);
m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m6 = _mm_mul_ps(m6, m4);
m7 = _mm_mul_ps(m7, m4);
m8 = _mm_mul_ps(m8, m5);
m9 = _mm_mul_ps(m9, m5);
/* Sum and store */
m6 = _mm_add_ps(m6, m8);
m7 = _mm_add_ps(m7, m9);
m6 = _mm_hadd_ps(m6, m7);
m6 = _mm_hadd_ps(m6, m6);
_mm_store_ss(&y[2 * i + 0], m6);
m6 = _mm_shuffle_ps(m6, m6, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m6);
}
}
/* 12-tap SSE complex-real convolution */
static void sse_conv_real12(float *restrict x,
float *restrict h,
float *restrict y,
int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
__m128 m8, m9, m10, m11, m12, m13, m14;
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[0]);
m1 = _mm_load_ps(&h[4]);
m2 = _mm_load_ps(&h[8]);
m3 = _mm_load_ps(&h[12]);
m4 = _mm_load_ps(&h[16]);
m5 = _mm_load_ps(&h[20]);
m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
for (int i = 0; i < len; i++) {
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 4]);
m2 = _mm_loadu_ps(&x[2 * i + 8]);
m3 = _mm_loadu_ps(&x[2 * i + 12]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
m0 = _mm_loadu_ps(&x[2 * i + 16]);
m1 = _mm_loadu_ps(&x[2 * i + 20]);
m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m0 = _mm_mul_ps(m4, m12);
m1 = _mm_mul_ps(m5, m12);
m2 = _mm_mul_ps(m6, m13);
m3 = _mm_mul_ps(m7, m13);
m4 = _mm_mul_ps(m8, m14);
m5 = _mm_mul_ps(m9, m14);
/* Sum and store */
m8 = _mm_add_ps(m0, m2);
m9 = _mm_add_ps(m1, m3);
m10 = _mm_add_ps(m8, m4);
m11 = _mm_add_ps(m9, m5);
m2 = _mm_hadd_ps(m10, m11);
m3 = _mm_hadd_ps(m2, m2);
_mm_store_ss(&y[2 * i + 0], m3);
m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m3);
}
}
/* 16-tap SSE complex-real convolution */
static void sse_conv_real16(float *restrict x,
float *restrict h,
float *restrict y,
int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
__m128 m8, m9, m10, m11, m12, m13, m14, m15;
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[0]);
m1 = _mm_load_ps(&h[4]);
m2 = _mm_load_ps(&h[8]);
m3 = _mm_load_ps(&h[12]);
m4 = _mm_load_ps(&h[16]);
m5 = _mm_load_ps(&h[20]);
m6 = _mm_load_ps(&h[24]);
m7 = _mm_load_ps(&h[28]);
m12 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m13 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m14 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
m15 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2));
for (int i = 0; i < len; i++) {
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 4]);
m2 = _mm_loadu_ps(&x[2 * i + 8]);
m3 = _mm_loadu_ps(&x[2 * i + 12]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
m0 = _mm_loadu_ps(&x[2 * i + 16]);
m1 = _mm_loadu_ps(&x[2 * i + 20]);
m2 = _mm_loadu_ps(&x[2 * i + 24]);
m3 = _mm_loadu_ps(&x[2 * i + 28]);
m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m0 = _mm_mul_ps(m4, m12);
m1 = _mm_mul_ps(m5, m12);
m2 = _mm_mul_ps(m6, m13);
m3 = _mm_mul_ps(m7, m13);
m4 = _mm_mul_ps(m8, m14);
m5 = _mm_mul_ps(m9, m14);
m6 = _mm_mul_ps(m10, m15);
m7 = _mm_mul_ps(m11, m15);
/* Sum and store */
m8 = _mm_add_ps(m0, m2);
m9 = _mm_add_ps(m1, m3);
m10 = _mm_add_ps(m4, m6);
m11 = _mm_add_ps(m5, m7);
m0 = _mm_add_ps(m8, m10);
m1 = _mm_add_ps(m9, m11);
m2 = _mm_hadd_ps(m0, m1);
m3 = _mm_hadd_ps(m2, m2);
_mm_store_ss(&y[2 * i + 0], m3);
m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m3);
}
}
/* 20-tap SSE complex-real convolution */
static void sse_conv_real20(float *restrict x,
float *restrict h,
float *restrict y,
int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
__m128 m8, m9, m11, m12, m13, m14, m15;
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[0]);
m1 = _mm_load_ps(&h[4]);
m2 = _mm_load_ps(&h[8]);
m3 = _mm_load_ps(&h[12]);
m4 = _mm_load_ps(&h[16]);
m5 = _mm_load_ps(&h[20]);
m6 = _mm_load_ps(&h[24]);
m7 = _mm_load_ps(&h[28]);
m8 = _mm_load_ps(&h[32]);
m9 = _mm_load_ps(&h[36]);
m11 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m12 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m13 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
m14 = _mm_shuffle_ps(m6, m7, _MM_SHUFFLE(0, 2, 0, 2));
m15 = _mm_shuffle_ps(m8, m9, _MM_SHUFFLE(0, 2, 0, 2));
for (int i = 0; i < len; i++) {
/* Multiply-accumulate first 12 taps */
m0 = _mm_loadu_ps(&x[2 * i + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 4]);
m2 = _mm_loadu_ps(&x[2 * i + 8]);
m3 = _mm_loadu_ps(&x[2 * i + 12]);
m4 = _mm_loadu_ps(&x[2 * i + 16]);
m5 = _mm_loadu_ps(&x[2 * i + 20]);
m6 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m8 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m9 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
m0 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(0, 2, 0, 2));
m1 = _mm_shuffle_ps(m4, m5, _MM_SHUFFLE(1, 3, 1, 3));
m2 = _mm_mul_ps(m6, m11);
m3 = _mm_mul_ps(m7, m11);
m4 = _mm_mul_ps(m8, m12);
m5 = _mm_mul_ps(m9, m12);
m6 = _mm_mul_ps(m0, m13);
m7 = _mm_mul_ps(m1, m13);
m0 = _mm_add_ps(m2, m4);
m1 = _mm_add_ps(m3, m5);
m8 = _mm_add_ps(m0, m6);
m9 = _mm_add_ps(m1, m7);
/* Multiply-accumulate last 8 taps */
m0 = _mm_loadu_ps(&x[2 * i + 24]);
m1 = _mm_loadu_ps(&x[2 * i + 28]);
m2 = _mm_loadu_ps(&x[2 * i + 32]);
m3 = _mm_loadu_ps(&x[2 * i + 36]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
m0 = _mm_mul_ps(m4, m14);
m1 = _mm_mul_ps(m5, m14);
m2 = _mm_mul_ps(m6, m15);
m3 = _mm_mul_ps(m7, m15);
m4 = _mm_add_ps(m0, m2);
m5 = _mm_add_ps(m1, m3);
/* Final sum and store */
m0 = _mm_add_ps(m8, m4);
m1 = _mm_add_ps(m9, m5);
m2 = _mm_hadd_ps(m0, m1);
m3 = _mm_hadd_ps(m2, m2);
_mm_store_ss(&y[2 * i + 0], m3);
m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m3);
}
}
/* 4*N-tap SSE complex-real convolution */
static void sse_conv_real4n(float *x, float *h, float *y, int h_len, int len)
{
__m128 m0, m1, m2, m4, m5, m6, m7;
for (int i = 0; i < len; i++) {
/* Zero */
m6 = _mm_setzero_ps();
m7 = _mm_setzero_ps();
for (int n = 0; n < h_len / 4; n++) {
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[8 * n + 0]);
m1 = _mm_load_ps(&h[8 * n + 4]);
m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 8 * n + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 8 * n + 4]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m0 = _mm_mul_ps(m2, m4);
m1 = _mm_mul_ps(m2, m5);
/* Accumulate */
m6 = _mm_add_ps(m6, m0);
m7 = _mm_add_ps(m7, m1);
}
m0 = _mm_hadd_ps(m6, m7);
m0 = _mm_hadd_ps(m0, m0);
_mm_store_ss(&y[2 * i + 0], m0);
m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m0);
}
}
/* 4*N-tap SSE complex-complex convolution */
static void sse_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
for (int i = 0; i < len; i++) {
/* Zero */
m6 = _mm_setzero_ps();
m7 = _mm_setzero_ps();
for (int n = 0; n < h_len / 4; n++) {
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[8 * n + 0]);
m1 = _mm_load_ps(&h[8 * n + 4]);
m2 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m3 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 8 * n + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 8 * n + 4]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m0 = _mm_mul_ps(m2, m4);
m1 = _mm_mul_ps(m3, m5);
m2 = _mm_mul_ps(m2, m5);
m3 = _mm_mul_ps(m3, m4);
/* Sum */
m0 = _mm_sub_ps(m0, m1);
m2 = _mm_add_ps(m2, m3);
/* Accumulate */
m6 = _mm_add_ps(m6, m0);
m7 = _mm_add_ps(m7, m2);
}
m0 = _mm_hadd_ps(m6, m7);
m0 = _mm_hadd_ps(m0, m0);
_mm_store_ss(&y[2 * i + 0], m0);
m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m0);
}
}
/* 8*N-tap SSE complex-complex convolution */
static void sse_conv_cmplx_8n(float *x, float *h, float *y, int h_len, int len)
{
__m128 m0, m1, m2, m3, m4, m5, m6, m7;
__m128 m8, m9, m10, m11, m12, m13, m14, m15;
for (int i = 0; i < len; i++) {
/* Zero */
m12 = _mm_setzero_ps();
m13 = _mm_setzero_ps();
m14 = _mm_setzero_ps();
m15 = _mm_setzero_ps();
for (int n = 0; n < h_len / 8; n++) {
/* Load (aligned) filter taps */
m0 = _mm_load_ps(&h[16 * n + 0]);
m1 = _mm_load_ps(&h[16 * n + 4]);
m2 = _mm_load_ps(&h[16 * n + 8]);
m3 = _mm_load_ps(&h[16 * n + 12]);
m4 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m5 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m6 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m7 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
/* Load (unaligned) input data */
m0 = _mm_loadu_ps(&x[2 * i + 16 * n + 0]);
m1 = _mm_loadu_ps(&x[2 * i + 16 * n + 4]);
m2 = _mm_loadu_ps(&x[2 * i + 16 * n + 8]);
m3 = _mm_loadu_ps(&x[2 * i + 16 * n + 12]);
m8 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(0, 2, 0, 2));
m9 = _mm_shuffle_ps(m0, m1, _MM_SHUFFLE(1, 3, 1, 3));
m10 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(0, 2, 0, 2));
m11 = _mm_shuffle_ps(m2, m3, _MM_SHUFFLE(1, 3, 1, 3));
/* Quad multiply */
m0 = _mm_mul_ps(m4, m8);
m1 = _mm_mul_ps(m5, m9);
m2 = _mm_mul_ps(m6, m10);
m3 = _mm_mul_ps(m7, m11);
m4 = _mm_mul_ps(m4, m9);
m5 = _mm_mul_ps(m5, m8);
m6 = _mm_mul_ps(m6, m11);
m7 = _mm_mul_ps(m7, m10);
/* Sum */
m0 = _mm_sub_ps(m0, m1);
m2 = _mm_sub_ps(m2, m3);
m4 = _mm_add_ps(m4, m5);
m6 = _mm_add_ps(m6, m7);
/* Accumulate */
m12 = _mm_add_ps(m12, m0);
m13 = _mm_add_ps(m13, m2);
m14 = _mm_add_ps(m14, m4);
m15 = _mm_add_ps(m15, m6);
}
m0 = _mm_add_ps(m12, m13);
m1 = _mm_add_ps(m14, m15);
m2 = _mm_hadd_ps(m0, m1);
m2 = _mm_hadd_ps(m2, m2);
_mm_store_ss(&y[2 * i + 0], m2);
m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(0, 3, 2, 1));
_mm_store_ss(&y[2 * i + 1], m2);
}
}
#endif
/* Base multiply and accumulate complex-real */
static void mac_real(float *x, float *h, float *y)
{
y[0] += x[0] * h[0];
y[1] += x[1] * h[0];
}
/* Base multiply and accumulate complex-complex */
static void mac_cmplx(float *x, float *h, float *y)
{
y[0] += x[0] * h[0] - x[1] * h[1];
y[1] += x[0] * h[1] + x[1] * h[0];
}
/* Base vector complex-complex multiply and accumulate */
static void mac_real_vec_n(float *x, float *h, float *y,
int len, int step, int offset)
{
for (int i = offset; i < len; i += step)
mac_real(&x[2 * i], &h[2 * i], y);
}
/* Base vector complex-complex multiply and accumulate */
static void mac_cmplx_vec_n(float *x, float *h, float *y,
int len, int step, int offset)
{
for (int i = offset; i < len; i += step)
mac_cmplx(&x[2 * i], &h[2 * i], y);
}
/* Base complex-real convolution */
static int _base_convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
for (int i = 0; i < len; i++) {
mac_real_vec_n(&x[2 * (i - (h_len - 1) + start)],
h,
&y[2 * i], h_len,
step, offset);
}
return len;
}
/* Base complex-complex convolution */
static int _base_convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
for (int i = 0; i < len; i++) {
mac_cmplx_vec_n(&x[2 * (i - (h_len - 1) + start)],
h,
&y[2 * i],
h_len, step, offset);
}
return len;
}
/* Buffer validity checks */
static int bounds_check(int x_len, int h_len, int y_len,
int start, int len, int step)
{
if ((x_len < 1) || (h_len < 1) ||
(y_len < 1) || (len < 1) || (step < 1)) {
fprintf(stderr, "Convolve: Invalid input\n");
return -1;
}
if ((start + len > x_len) || (len > y_len) || (x_len < h_len)) {
fprintf(stderr, "Convolve: Boundary exception\n");
fprintf(stderr, "start: %i, len: %i, x: %i, h: %i, y: %i\n",
start, len, x_len, h_len, y_len);
return -1;
}
return 0;
}
/* API: Aligned complex-real */
int convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
void (*conv_func)(float *, float *, float *, int) = NULL;
void (*conv_func_n)(float *, float *, float *, int, int) = NULL;
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
#ifdef HAVE_SSE3
if (step <= 4) {
switch (h_len) {
case 4:
conv_func = sse_conv_real4;
break;
case 8:
conv_func = sse_conv_real8;
break;
case 12:
conv_func = sse_conv_real12;
break;
case 16:
conv_func = sse_conv_real16;
break;
case 20:
conv_func = sse_conv_real20;
break;
default:
if (!(h_len % 4))
conv_func_n = sse_conv_real4n;
}
}
#endif
if (conv_func) {
conv_func(&x[2 * (-(h_len - 1) + start)],
h, y, len);
} else if (conv_func_n) {
conv_func_n(&x[2 * (-(h_len - 1) + start)],
h, y, h_len, len);
} else {
_base_convolve_real(x, x_len,
h, h_len,
y, y_len,
start, len, step, offset);
}
return len;
}
/* API: Aligned complex-complex */
int convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
void (*conv_func)(float *, float *, float *, int, int) = NULL;
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
#ifdef HAVE_SSE3
if (step <= 4) {
if (!(h_len % 8))
conv_func = sse_conv_cmplx_8n;
else if (!(h_len % 4))
conv_func = sse_conv_cmplx_4n;
}
#endif
if (conv_func) {
conv_func(&x[2 * (-(h_len - 1) + start)],
h, y, h_len, len);
} else {
_base_convolve_complex(x, x_len,
h, h_len,
y, y_len,
start, len, step, offset);
}
return len;
}
/* API: Non-aligned (no SSE) complex-real */
int base_convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
return _base_convolve_real(x, x_len,
h, h_len,
y, y_len,
start, len, step, offset);
}
/* API: Non-aligned (no SSE) complex-complex */
int base_convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset)
{
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
return -1;
memset(y, 0, len * 2 * sizeof(float));
return _base_convolve_complex(x, x_len,
h, h_len,
y, y_len,
start, len, step, offset);
}
/* Aligned filter tap allocation */
void *convolve_h_alloc(int len)
{
#ifdef HAVE_SSE3
return memalign(16, len * 2 * sizeof(float));
#else
return malloc(len * 2 * sizeof(float));
#endif
}

30
TransceiverUHD/convolve.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _CONVOLVE_H_
#define _CONVOLVE_H_
void *convolve_h_alloc(int num);
int convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset);
int convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset);
int base_convolve_real(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset);
int base_convolve_complex(float *x, int x_len,
float *h, int h_len,
float *y, int y_len,
int start, int len,
int step, int offset);
#endif /* _CONVOLVE_H_ */

View File

@@ -0,0 +1,223 @@
/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2014 Range Networks, Inc.
*
* This software is distributed under the terms of the GNU General Public
* License version 3. See the COPYING and NOTICE files in the current
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <Logger.h>
#include <UMTSCommon.h>
#include <Configuration.h>
#include "Transceiver.h"
#include "UHDDevice.h"
/* Default maximum expected delay spread in symbols */
#define DEFAULT_MAX_DELAY 50
/* Sample rate for all devices */
#define DEVICE_RATE 6.25e6
ConfigurationKeyMap getConfigurationKeys2();
ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS-UMTS.db",
"transceiver", getConfigurationKeys2());
volatile bool gbShutdown = false;
static void shutdown_handler(int signo)
{
std::cout << std::endl << "** Received shutdown signal" << std::endl;
gbShutdown = true;
}
static void register_signal_handlers()
{
if (signal(SIGINT, shutdown_handler) == SIG_ERR) {
std::cerr << "** Failed to install SIGINT signal handler" << std::endl;
exit(1);
}
if (signal(SIGTERM, shutdown_handler) == SIG_ERR) {
std::cerr << "** Failed to install SIGTERM signal handler" << std::endl;
exit(1);
}
}
/* Logging check in the configuration is mandatory */
static bool init_config()
{
try {
std::cout << "** Configuring logger" << std::endl;
gLogInit("transceiver", gConfig.getStr("Log.Level").c_str(), LOG_LOCAL7);
} catch (ConfigurationTableKeyNotFound e) {
LOG(EMERG) << "** Required configuration parameter " << e.key()
<< " not defined, aborting";
return false;
}
return true;
}
/* Optional expected delay spread (default 50 symbols) */
static int init_max_delay()
{
int max_delay;
try {
max_delay = gConfig.getNum("UMTS.Radio.MaxExpectedDelaySpread");
} catch (ConfigurationTableKeyNotFound e) {
max_delay = DEFAULT_MAX_DELAY;
}
return max_delay;
}
/* Optional external reference enable (default off) */
static bool init_extref()
{
int enable;
try {
enable = gConfig.getNum("TRX.Reference");
} catch (ConfigurationTableKeyNotFound e) {
enable = 0;
}
return enable != 0;
}
/* Optional device hint (default none) */
static std::string init_devaddr()
{
std::string addr;
try {
addr = gConfig.getNum("UMTS.Radio.UHD.DeviceAddress");
} catch (ConfigurationTableKeyNotFound e) {
addr = "";
}
return addr;
}
int main(int argc, char *argv[])
{
UHDDevice *usrp = NULL;
RadioDevice *dev = NULL;
Transceiver *trx = NULL;
RadioInterface *radio = NULL;
int max_delay;
bool found, extref;
std::string devaddr;
/* Capture termination signals */
register_signal_handlers();
/* Fail if we don't have logging */
if (!init_config())
goto shutdown;
/* Optional parameters */
max_delay = init_max_delay();
extref = init_extref();
devaddr = init_devaddr();
srandom(time(NULL));
if (extref)
std::cout << "** Using external clock reference" << std::endl;
else
std::cout << "** Using internal clock reference" << std::endl;
std::cout << "** Searching for USRP device " << devaddr << std::endl;
usrp = new UHDDevice(DEVICE_RATE);
found = usrp->open(devaddr, extref);
if (found) {
std::cout << "** Device ready" << std::endl;
dev = (RadioDevice *) usrp;
} else {
std::cout << "** Device not available" << std::endl;
goto shutdown;
}
radio = new RadioInterface(dev, 0);
if (!radio->init()) {
std::cout << "** Radio failed to initialize" << std::endl;
goto shutdown;
}
trx = new Transceiver(5700, "127.0.0.1", UMTS::Time(4, 0), radio);
trx->receiveFIFO(radio->receiveFIFO());
trx->init(max_delay);
while (!gbShutdown)
sleep(1);
shutdown:
delete trx;
delete radio;
delete usrp;
return 0;
}
ConfigurationKeyMap getConfigurationKeys2()
{
extern ConfigurationKeyMap getConfigurationKeys();
ConfigurationKeyMap map = getConfigurationKeys();
ConfigurationKey *tmp;
tmp = new ConfigurationKey("TRX.RadioFrequencyOffset","128",
"~170Hz steps",
ConfigurationKey::FACTORY,
ConfigurationKey::VALRANGE,
"96:160",// educated guess
true,
"Fine-tuning adjustment for the transceiver master clock. "
"Roughly 170 Hz/step. "
"Set at the factory. "
"Do not adjust without proper calibration."
);
map[tmp->getName()] = *tmp;
delete tmp;
tmp = new ConfigurationKey("TRX.TxAttenOffset","0",
"dB of attenuation",
ConfigurationKey::FACTORY,
ConfigurationKey::VALRANGE,
"0:100",// educated guess
true,
"Hardware-specific gain adjustment for transmitter, matched to the power amplifier, expessed as an attenuationi in dB. "
"Set at the factory. "
"Do not adjust without proper calibration."
);
map[tmp->getName()] = *tmp;
delete tmp;
tmp = new ConfigurationKey("TRX.RadioNumber","0",
"",
ConfigurationKey::FACTORY,
ConfigurationKey::VALRANGE,
"0:9", // Not likely to have 10 radios on the same computer. Not likely to have >1
true,
"If non-0, use multiple radios on the same cpu, numbered 1-9. Must change TRX.Port also. Provide a separate config file for each OpenBTS+Radio combination using the environment variable or --config command line option."
);
map[tmp->getName()] = *tmp;
delete(tmp);
return map;
}