mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
synced 2025-10-23 08:22:00 +00:00
Compare commits
108 Commits
achemeris/
...
ms
Author | SHA1 | Date | |
---|---|---|---|
|
774a06369a | ||
|
94ce835050 | ||
|
09befd7a06 | ||
|
1303376ad1 | ||
|
25021dfe5a | ||
|
e287598e6b | ||
|
98b1af896c | ||
|
14bb9c923d | ||
|
c7f36c282a | ||
|
f31e4bb089 | ||
|
1189019c30 | ||
|
187225cf33 | ||
|
ccb73e15f3 | ||
|
a5c83aeb56 | ||
|
c283916e41 | ||
|
cecfb2e8f9 | ||
|
8e17df7374 | ||
|
3ebc772dc2 | ||
|
18d3b833bc | ||
|
e788239fba | ||
|
635f1bf2af | ||
|
15d743efaf | ||
|
af506441b3 | ||
|
4de70be9a6 | ||
|
fb827d04ba | ||
|
2c79110969 | ||
|
85b179d125 | ||
|
2e622ff131 | ||
|
3f32ab5afa | ||
|
8c33679fa5 | ||
|
477b77c558 | ||
|
d3fccea05f | ||
|
cb269a32dd | ||
|
1882099d15 | ||
|
a0179e37f8 | ||
|
ef25dba4e7 | ||
|
2d0c00bd3d | ||
|
e90a42becc | ||
|
30421a7e25 | ||
|
34bbef754f | ||
|
2c1f85a10c | ||
|
a2fe91a688 | ||
|
e1ce92599a | ||
|
b075dd2f73 | ||
|
94edaaeee6 | ||
|
0e0e1f4363 | ||
|
d0f3ca3e94 | ||
|
f8bc7c351f | ||
|
a4cf48cf8b | ||
|
7940335586 | ||
|
f79c4d06ff | ||
|
20eb6d64fd | ||
|
e0fa2bfd93 | ||
|
6f4906e375 | ||
|
0a3dc4c210 | ||
|
acc22fa3ff | ||
|
7553aa973f | ||
|
7e4e536b1b | ||
|
204a9f135a | ||
|
0169b310de | ||
|
66e2dd2543 | ||
|
f078273a8a | ||
|
d647ec5dc1 | ||
|
c289d7a409 | ||
|
035bee5c32 | ||
|
cf910dcdda | ||
|
69762fd7c6 | ||
|
fffd987f22 | ||
|
17bbb9b755 | ||
|
a1a3ab4bab | ||
|
fa3a787ccb | ||
|
010fff783b | ||
|
61b4a6ad9f | ||
|
de1648ca6b | ||
|
3952d80d05 | ||
|
fe269fe31d | ||
|
c064124429 | ||
|
69d14c9a14 | ||
|
c1f7c42a33 | ||
|
2c282f5e12 | ||
|
92c16df875 | ||
|
d5a80c3dc6 | ||
|
dafb33700e | ||
|
0e44ab360e | ||
|
092f757ec2 | ||
|
a57bc8a3b9 | ||
|
84c60f6679 | ||
|
865bca42d6 | ||
|
c88d8d53f8 | ||
|
9ccd9f2c3c | ||
|
8181b0104a | ||
|
9471d7635a | ||
|
03e6ecf977 | ||
|
3eaae80c90 | ||
|
e57004d0c3 | ||
|
6aa1f18f41 | ||
|
e5dcfc4f80 | ||
|
83e0689e76 | ||
|
d24cc2cd96 | ||
|
ddd6defb43 | ||
|
96794cb746 | ||
|
7e06806ff0 | ||
|
cb69f08410 | ||
|
312e387630 | ||
|
f2293b8cfa | ||
|
e3e8814948 | ||
|
02d88d1380 | ||
|
b4cb4e23c0 |
@@ -45,6 +45,8 @@ const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000
|
||||
|
||||
const BitVector GSM::gRACHSynchSequence("01001011011111111001100110101010001111000");
|
||||
|
||||
const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
|
||||
|
||||
|
||||
int32_t GSM::FNDelta(int32_t v1, int32_t v2)
|
||||
{
|
||||
|
@@ -53,6 +53,8 @@ extern const BitVector gDummyBurst;
|
||||
/** Random access burst synch. sequence */
|
||||
extern const BitVector gRACHSynchSequence;
|
||||
|
||||
/** Synchronization burst sync sequence */
|
||||
extern const BitVector gSCHSynchSequence;
|
||||
|
||||
/**@name Modulus operations for frame numbers. */
|
||||
//@{
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
ACLOCAL_AMFLAGS = -I config
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USB_INCLUDES) $(WITH_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall -pthread -ldl
|
||||
#AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread -ldl
|
||||
|
@@ -18,7 +18,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
#hack to get around symlink svn:externals in git -kurtis
|
||||
top_srcdir = $(abs_top_srcdir)
|
||||
top_builddir = $(abs_top_builddir)
|
||||
|
||||
@@ -26,8 +25,6 @@ COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs
|
||||
GSM_INCLUDEDIR = $(top_srcdir)/GSM
|
||||
SQLITE_INCLUDEDIR = $(top_srcdir)/sqlite3
|
||||
|
||||
SVNDEV = -D'SVN_REV="$(shell svnversion -n $(top_builddir))"'
|
||||
|
||||
STD_DEFINES_AND_INCLUDES = \
|
||||
$(SVNDEV) \
|
||||
-I$(COMMON_INCLUDEDIR) \
|
||||
|
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Compilation Flags
|
||||
|
||||
SWLOOPBACK compile for software loopback testing
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "Threads.h"
|
||||
#include "DummyLoad.h"
|
||||
|
||||
#include <Logger.h>
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
int DummyLoad::loadBurst(short *wDummyBurst, int len) {
|
||||
dummyBurst = wDummyBurst;
|
||||
dummyBurstSz = len;
|
||||
}
|
||||
|
||||
|
||||
DummyLoad::DummyLoad (double _desiredSampleRate)
|
||||
{
|
||||
LOG(INFO) << "creating USRP device...";
|
||||
sampleRate = _desiredSampleRate;
|
||||
}
|
||||
|
||||
void DummyLoad::updateTime(void) {
|
||||
gettimeofday(&currTime,NULL);
|
||||
double timeElapsed = (currTime.tv_sec - startTime.tv_sec)*1.0e6 +
|
||||
(currTime.tv_usec - startTime.tv_usec);
|
||||
currstamp = (TIMESTAMP) floor(timeElapsed/(1.0e6/sampleRate));
|
||||
}
|
||||
|
||||
bool DummyLoad::make(bool wSkipRx)
|
||||
{
|
||||
|
||||
samplesRead = 0;
|
||||
samplesWritten = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DummyLoad::start()
|
||||
{
|
||||
LOG(INFO) << "starting USRP...";
|
||||
underrun = false;
|
||||
gettimeofday(&startTime,NULL);
|
||||
dummyBurstCursor = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DummyLoad::stop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: Assumes sequential reads
|
||||
int DummyLoad::readSamples(short *buf, int len, bool *overrun,
|
||||
TIMESTAMP timestamp,
|
||||
bool *wUnderrun,
|
||||
unsigned *RSSI)
|
||||
{
|
||||
updateTime();
|
||||
underrunLock.lock();
|
||||
*wUnderrun = underrun;
|
||||
underrunLock.unlock();
|
||||
if (currstamp+len < timestamp) {
|
||||
usleep(100);
|
||||
return 0;
|
||||
}
|
||||
else if (currstamp < timestamp) {
|
||||
usleep(100);
|
||||
return 0;
|
||||
}
|
||||
else if (timestamp+len < currstamp) {
|
||||
memcpy(buf,dummyBurst+dummyBurstCursor*2,sizeof(short)*2*(dummyBurstSz-dummyBurstCursor));
|
||||
int retVal = dummyBurstSz-dummyBurstCursor;
|
||||
dummyBurstCursor = 0;
|
||||
return retVal;
|
||||
}
|
||||
else if (timestamp + len > currstamp) {
|
||||
int amount = timestamp + len - currstamp;
|
||||
if (amount < dummyBurstSz-dummyBurstCursor) {
|
||||
memcpy(buf,dummyBurst+dummyBurstCursor*2,sizeof(short)*2*amount);
|
||||
dummyBurstCursor += amount;
|
||||
return amount;
|
||||
}
|
||||
else {
|
||||
memcpy(buf,dummyBurst+dummyBurstCursor*2,sizeof(short)*2*(dummyBurstSz-dummyBurstCursor));
|
||||
int retVal = dummyBurstSz-dummyBurstCursor;
|
||||
dummyBurstCursor = 0;
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DummyLoad::writeSamples(short *buf, int len, bool *wUnderrun,
|
||||
unsigned long long timestamp,
|
||||
bool isControl)
|
||||
{
|
||||
updateTime();
|
||||
underrunLock.lock();
|
||||
underrun |= (currstamp+len < timestamp);
|
||||
underrunLock.unlock();
|
||||
return len;
|
||||
}
|
||||
|
||||
bool DummyLoad::updateAlignment(TIMESTAMP timestamp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DummyLoad::setTxFreq(double wFreq) { return true;};
|
||||
bool DummyLoad::setRxFreq(double wFreq) { return true;};
|
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "radioDevice.h"
|
||||
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */
|
||||
class DummyLoad: public RadioDevice {
|
||||
|
||||
private:
|
||||
|
||||
double sampleRate; ///< the desired sampling rate
|
||||
unsigned long long samplesRead; ///< number of samples read from USRP
|
||||
unsigned long long samplesWritten; ///< number of samples sent to USRP
|
||||
|
||||
Mutex underrunLock;
|
||||
|
||||
struct timeval startTime, currTime;
|
||||
|
||||
TIMESTAMP currstamp;
|
||||
short *dummyBurst;
|
||||
int dummyBurstSz;
|
||||
int dummyBurstCursor;
|
||||
bool underrun;
|
||||
|
||||
void updateTime(void);
|
||||
|
||||
public:
|
||||
|
||||
/** Object constructor */
|
||||
DummyLoad (double _desiredSampleRate);
|
||||
|
||||
int loadBurst(short *wDummyBurst, int len);
|
||||
|
||||
/** Instantiate the USRP */
|
||||
bool make(bool skipRx = false);
|
||||
|
||||
/** Start the USRP */
|
||||
bool start();
|
||||
|
||||
/** Stop the USRP */
|
||||
bool stop();
|
||||
|
||||
/**
|
||||
Read samples from the USRP.
|
||||
@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 USRP 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
|
||||
*/
|
||||
int readSamples(short *buf, int len, bool *overrun,
|
||||
TIMESTAMP timestamp = 0xffffffff,
|
||||
bool *underrun = NULL,
|
||||
unsigned *RSSI = NULL);
|
||||
/**
|
||||
Write samples to the USRP.
|
||||
@param buf Contains the data to be written.
|
||||
@param len number of samples to write.
|
||||
@param underrun Set if USRP 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
|
||||
*/
|
||||
int writeSamples(short *buf, int len, bool *underrun,
|
||||
TIMESTAMP timestamp = 0xffffffff,
|
||||
bool isControl = false);
|
||||
|
||||
/** Update the alignment between the read and write timestamps */
|
||||
bool updateAlignment(TIMESTAMP timestamp);
|
||||
|
||||
/** Set the transmitter frequency */
|
||||
bool setTxFreq(double wFreq);
|
||||
|
||||
/** Set the receiver frequency */
|
||||
bool setRxFreq(double wFreq);
|
||||
|
||||
/** Returns the starting write Timestamp*/
|
||||
TIMESTAMP initialWriteTimestamp(void) { return 20000;}
|
||||
|
||||
/** Returns the starting read Timestamp*/
|
||||
TIMESTAMP initialReadTimestamp(void) { return 20000;}
|
||||
|
||||
/** returns the full-scale transmit amplitude **/
|
||||
double fullScaleInputValue() {return 13500.0;}
|
||||
|
||||
/** returns the full-scale receive amplitude **/
|
||||
double fullScaleOutputValue() {return 9450.0;}
|
||||
|
||||
/** Return internal status values */
|
||||
inline double getTxFreq() { return 0;}
|
||||
inline double getRxFreq() { return 0;}
|
||||
inline double getSampleRate() {return sampleRate;}
|
||||
inline double numberRead() { return samplesRead; }
|
||||
inline double numberWritten() { return samplesWritten;}
|
||||
|
||||
};
|
||||
|
@@ -21,20 +21,23 @@
|
||||
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
#UHD wins if both are defined
|
||||
if UHD
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(UHD_CFLAGS)
|
||||
else
|
||||
if USRP1
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(USRP_CFLAGS)
|
||||
else
|
||||
#we should never be here, as this doesn't build if one of the above
|
||||
#doesn't exist
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
endif
|
||||
endif
|
||||
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I./common
|
||||
AM_CXXFLAGS = -ldl -lpthread
|
||||
|
||||
SUBDIRS = arm x86
|
||||
|
||||
if ARCH_ARM
|
||||
ARCH_LA = arm/libarch.la
|
||||
else
|
||||
ARCH_LA = x86/libarch.la
|
||||
endif
|
||||
|
||||
if USRP1
|
||||
AM_CPPFLAGS += $(USRP_CFLAGS)
|
||||
else
|
||||
AM_CPPFLAGS += $(UHD_CFLAGS)
|
||||
endif
|
||||
|
||||
rev2dir = $(datadir)/usrp/rev2
|
||||
rev4dir = $(datadir)/usrp/rev4
|
||||
|
||||
@@ -52,23 +55,17 @@ COMMON_SOURCES = \
|
||||
radioVector.cpp \
|
||||
radioClock.cpp \
|
||||
sigProcLib.cpp \
|
||||
signalVector.cpp \
|
||||
Transceiver.cpp \
|
||||
DummyLoad.cpp
|
||||
sch.c
|
||||
|
||||
if RESAMPLE
|
||||
libtransceiver_la_SOURCES = \
|
||||
$(COMMON_SOURCES) \
|
||||
radioIOResamp.cpp
|
||||
else
|
||||
libtransceiver_la_SOURCES = \
|
||||
$(COMMON_SOURCES) \
|
||||
radioIO.cpp
|
||||
endif
|
||||
Resampler.cpp \
|
||||
radioInterfaceResamp.cpp \
|
||||
radioInterfaceDiversity.cpp
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
USRPping \
|
||||
transceiver \
|
||||
sigProcLibTest
|
||||
bin_PROGRAMS = osmo-trx
|
||||
|
||||
noinst_HEADERS = \
|
||||
Complex.h \
|
||||
@@ -77,52 +74,28 @@ noinst_HEADERS = \
|
||||
radioClock.h \
|
||||
radioDevice.h \
|
||||
sigProcLib.h \
|
||||
signalVector.h \
|
||||
Transceiver.h \
|
||||
USRPDevice.h \
|
||||
DummyLoad.h \
|
||||
rcvLPF_651.h \
|
||||
sendLPF_961.h
|
||||
Resampler.h \
|
||||
common/convolve.h \
|
||||
common/convert.h \
|
||||
common/scale.h \
|
||||
common/mult.h
|
||||
|
||||
USRPping_SOURCES = USRPping.cpp
|
||||
USRPping_LDADD = \
|
||||
libtransceiver.la \
|
||||
$(COMMON_LA) $(SQLITE_LA)
|
||||
|
||||
transceiver_SOURCES = runTransceiver.cpp
|
||||
transceiver_LDADD = \
|
||||
osmo_trx_SOURCES = osmo-trx.cpp
|
||||
osmo_trx_LDADD = \
|
||||
libtransceiver.la \
|
||||
$(ARCH_LA) \
|
||||
$(GSM_LA) \
|
||||
$(COMMON_LA) $(SQLITE_LA)
|
||||
$(COMMON_LA) \
|
||||
$(SQLITE_LA) \
|
||||
$(LIBOSMOCORE_LIBS)
|
||||
|
||||
sigProcLibTest_SOURCES = sigProcLibTest.cpp
|
||||
sigProcLibTest_LDADD = \
|
||||
libtransceiver.la \
|
||||
$(GSM_LA) \
|
||||
$(COMMON_LA) $(SQLITE_LA)
|
||||
|
||||
#uhd wins
|
||||
if UHD
|
||||
libtransceiver_la_SOURCES += UHDDevice.cpp
|
||||
transceiver_LDADD += $(UHD_LIBS)
|
||||
USRPping_LDADD += $(UHD_LIBS)
|
||||
sigProcLibTest_LDADD += $(UHD_LIBS)
|
||||
else
|
||||
if USRP1
|
||||
if USRP1
|
||||
libtransceiver_la_SOURCES += USRPDevice.cpp
|
||||
transceiver_LDADD += $(USRP_LIBS)
|
||||
USRPping_LDADD += $(USRP_LIBS)
|
||||
sigProcLibTest_LDADD += $(USRP_LIBS)
|
||||
osmo_trx_LDADD += $(USRP_LIBS)
|
||||
else
|
||||
#we should never be here, as one of the above mustbe defined for us to build
|
||||
libtransceiver_la_SOURCES += UHDDevice.cpp
|
||||
osmo_trx_LDADD += $(UHD_LIBS)
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
MOSTLYCLEANFILES +=
|
||||
|
||||
#radioInterface.cpp
|
||||
#ComplexTest.cpp
|
||||
#sigProcLibTest.cpp
|
||||
#sweepGenerator.cpp
|
||||
#testRadio.cpp
|
||||
|
||||
|
@@ -1,15 +0,0 @@
|
||||
Basic model:
|
||||
|
||||
Have channel H = {h_0, h_1, ..., h_{K-1}}.
|
||||
Have received sequence Y = {y_0, ..., y_{K+N}}.
|
||||
Have transmitted sequence X = {x_0, ..., x_{N-1}}.
|
||||
Denote state S_n = {x_n, x_{n-1}, ..., x_{n-L}}.
|
||||
|
||||
Define a bag as an unordered collection with two operations, add and take.
|
||||
We have three bags:
|
||||
S: a bag of survivors.
|
||||
F: a bag of available data structures.
|
||||
|
||||
At time n, start with a non-empty bag S of survivors from time n-1.
|
||||
Take a member out of S, and create all possible branches and their corresponding metrics. If metric ratio is above T, discard branch. Otherwise, check branch against entry in pruning table P. If branch metric is smaller than the existing entry's metric in P, then replace entry with branch. Otherwise, discard branch.
|
||||
Once all possible branches of S have been created and pruned, S should be empty.Empty pruning table back into S, thus P is now empty. Repeat.
|
239
Transceiver52M/Resampler.cpp
Normal file
239
Transceiver52M/Resampler.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Rational Sample Rate Conversion
|
||||
* 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 <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 4096
|
||||
|
||||
static float sinc(float x)
|
||||
{
|
||||
if (x == 0.0)
|
||||
return 0.9999999999;
|
||||
|
||||
return sin(M_PI * x) / (M_PI * x);
|
||||
}
|
||||
|
||||
bool Resampler::initFilters(float bw)
|
||||
{
|
||||
size_t proto_len = p * filt_len;
|
||||
float *proto, val, cutoff;
|
||||
float sum = 0.0f, scale = 0.0f;
|
||||
float midpt = (float) (proto_len - 1.0) / 2.0;
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
free(proto);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < p; i++) {
|
||||
partitions[i] = (float *)
|
||||
memalign(16, filt_len * 2 * sizeof(float));
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the prototype filter with a Blackman-harris window.
|
||||
* Scale coefficients with DC filter gain set to unity divided
|
||||
* by the number of filter partitions.
|
||||
*/
|
||||
float a0 = 0.35875;
|
||||
float a1 = 0.48829;
|
||||
float a2 = 0.14128;
|
||||
float a3 = 0.01168;
|
||||
|
||||
if (p > q)
|
||||
cutoff = (float) p;
|
||||
else
|
||||
cutoff = (float) q;
|
||||
|
||||
for (size_t i = 0; i < proto_len; i++) {
|
||||
proto[i] = sinc(((float) i - midpt) / cutoff * bw);
|
||||
proto[i] *= a0 -
|
||||
a1 * cos(2 * M_PI * i / (proto_len - 1)) +
|
||||
a2 * cos(4 * M_PI * i / (proto_len - 1)) -
|
||||
a3 * cos(6 * M_PI * i / (proto_len - 1));
|
||||
sum += proto[i];
|
||||
}
|
||||
scale = p / sum;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* For convolution, we store the filter taps in reverse */
|
||||
for (size_t n = 0; n < p; n++) {
|
||||
for (size_t i = 0; i < filt_len / 2; i++) {
|
||||
val = partitions[n][2 * i];
|
||||
partitions[n][2 * i] = partitions[n][2 * (filt_len - 1 - i)];
|
||||
partitions[n][2 * (filt_len - 1 - i)] = val;
|
||||
}
|
||||
}
|
||||
|
||||
delete proto;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Resampler::releaseFilters()
|
||||
{
|
||||
if (partitions) {
|
||||
for (size_t i = 0; i < p; i++)
|
||||
free(partitions[i]);
|
||||
}
|
||||
|
||||
free(partitions);
|
||||
partitions = NULL;
|
||||
}
|
||||
|
||||
static bool check_vec_len(int in_len, int out_len, int p, int q)
|
||||
{
|
||||
if (in_len % q) {
|
||||
std::cerr << "Invalid input length " << in_len
|
||||
<< " is not multiple of " << q << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_len % p) {
|
||||
std::cerr << "Invalid output length " << out_len
|
||||
<< " is not multiple of " << p << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((in_len / q) != (out_len / p)) {
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_len > MAX_OUTPUT_LEN) {
|
||||
std::cerr << "Block length of " << out_len
|
||||
<< " exceeds max of " << MAX_OUTPUT_LEN << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Resampler::computePath()
|
||||
{
|
||||
for (int i = 0; i < MAX_OUTPUT_LEN; i++) {
|
||||
in_index[i] = (q * i) / p;
|
||||
out_path[i] = (q * i) % p;
|
||||
}
|
||||
}
|
||||
|
||||
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 (!check_vec_len(in_len, out_len, p, q))
|
||||
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(float bw)
|
||||
{
|
||||
size_t hist_len = filt_len - 1;
|
||||
|
||||
/* Filterbank filter internals */
|
||||
if (initFilters(bw) < 0)
|
||||
return false;
|
||||
|
||||
/* History buffer */
|
||||
history = new float[2 * hist_len];
|
||||
memset(history, 0, 2 * hist_len * sizeof(float));
|
||||
|
||||
/* Precompute filterbank paths */
|
||||
in_index = new size_t[MAX_OUTPUT_LEN];
|
||||
out_path = new size_t[MAX_OUTPUT_LEN];
|
||||
computePath();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Resampler::~Resampler()
|
||||
{
|
||||
releaseFilters();
|
||||
|
||||
delete history;
|
||||
delete in_index;
|
||||
delete out_path;
|
||||
}
|
77
Transceiver52M/Resampler.h
Normal file
77
Transceiver52M/Resampler.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Rational Sample Rate Conversion
|
||||
* 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
|
||||
*/
|
||||
|
||||
#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(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();
|
||||
|
||||
private:
|
||||
size_t p;
|
||||
size_t q;
|
||||
size_t filt_len;
|
||||
size_t *in_index;
|
||||
size_t *out_path;
|
||||
|
||||
float **partitions;
|
||||
float *history;
|
||||
|
||||
bool initFilters(float bw);
|
||||
void releaseFilters();
|
||||
void computePath();
|
||||
};
|
||||
|
||||
#endif /* _RESAMPLER_H_ */
|
File diff suppressed because it is too large
Load Diff
@@ -22,13 +22,6 @@
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Compilation switches
|
||||
TRANSMIT_LOGGING write every burst on the given slot to a log
|
||||
*/
|
||||
|
||||
#include "radioInterface.h"
|
||||
#include "Interthread.h"
|
||||
#include "GSMCommon.h"
|
||||
@@ -37,28 +30,85 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
/** Define this to be the slot number to be logged. */
|
||||
//#define TRANSMIT_LOGGING 1
|
||||
class Transceiver;
|
||||
|
||||
/** Channel descriptor for transceiver object and channel number pair */
|
||||
struct TransceiverChannel {
|
||||
TransceiverChannel(Transceiver *trx, int num)
|
||||
{
|
||||
this->trx = trx;
|
||||
this->num = num;
|
||||
}
|
||||
|
||||
~TransceiverChannel()
|
||||
{
|
||||
}
|
||||
|
||||
Transceiver *trx;
|
||||
size_t num;
|
||||
};
|
||||
|
||||
/** Internal transceiver state variables */
|
||||
struct TransceiverState {
|
||||
TransceiverState();
|
||||
~TransceiverState();
|
||||
|
||||
/* Initialize a multiframe slot in the filler table */
|
||||
void init(size_t slot, signalVector *burst, bool fill);
|
||||
|
||||
int chanType[8];
|
||||
|
||||
/* Last timestamp of each timeslot's channel estimate */
|
||||
GSM::Time chanEstimateTime[8];
|
||||
|
||||
/* The filler table */
|
||||
signalVector *fillerTable[102][8];
|
||||
int fillerModulus[8];
|
||||
bool mRetrans;
|
||||
|
||||
/* Most recent channel estimate of all timeslots */
|
||||
signalVector *chanResponse[8];
|
||||
|
||||
/* Most recent DFE feedback filter of all timeslots */
|
||||
signalVector *DFEForward[8];
|
||||
signalVector *DFEFeedback[8];
|
||||
|
||||
/* Most recent SNR, timing, and channel amplitude estimates */
|
||||
float SNRestimate[8];
|
||||
float chanRespOffset[8];
|
||||
complex chanRespAmplitude[8];
|
||||
|
||||
/* Received noise energy levels */
|
||||
float mNoiseLev;
|
||||
avgVector mNoises;
|
||||
avgVector mFreqOffsets;
|
||||
|
||||
/* Store pointers to previous frame */
|
||||
radioVector *prevFrame[8];
|
||||
|
||||
/* Transceiver mode */
|
||||
int mode;
|
||||
};
|
||||
|
||||
/** The Transceiver class, responsible for physical layer of basestation */
|
||||
class Transceiver {
|
||||
|
||||
private:
|
||||
|
||||
int mBasePort;
|
||||
std::string mAddr;
|
||||
GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock
|
||||
GSM::Time mLatencyUpdateTime; ///< last time latency was updated
|
||||
|
||||
UDPSocket mDataSocket; ///< socket for writing to/reading from GSM core
|
||||
UDPSocket mControlSocket; ///< socket for writing/reading control commands from GSM core
|
||||
UDPSocket mClockSocket; ///< socket for writing clock updates to GSM core
|
||||
std::vector<UDPSocket *> mDataSockets; ///< socket for writing to/reading from GSM core
|
||||
std::vector<UDPSocket *> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
|
||||
UDPSocket *mClockSocket; ///< socket for writing clock updates to GSM core
|
||||
|
||||
VectorQueue mTransmitPriorityQueue; ///< priority queue of transmit bursts received from GSM core
|
||||
VectorFIFO* mTransmitFIFO; ///< radioInterface FIFO of transmit bursts
|
||||
VectorFIFO* mReceiveFIFO; ///< radioInterface FIFO of receive bursts
|
||||
std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core
|
||||
std::vector<VectorFIFO *> mReceiveFIFO; ///< radioInterface FIFO of receive bursts
|
||||
|
||||
Thread *mFIFOServiceLoopThread; ///< thread to push/pull bursts into transmit/receive FIFO
|
||||
Thread *mControlServiceLoopThread; ///< thread to process control messages from GSM core
|
||||
Thread *mTransmitPriorityQueueServiceLoopThread;///< thread to process transmit bursts from GSM core
|
||||
std::vector<Thread *> mRxServiceLoopThreads; ///< thread to pull bursts into receive FIFO
|
||||
Thread *mLowerLoopThread; ///< thread to pull bursts into receive FIFO
|
||||
std::vector<Thread *> mControlServiceLoopThreads; ///< thread to process control messages from GSM core
|
||||
std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core
|
||||
|
||||
GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO
|
||||
GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core
|
||||
@@ -72,9 +122,105 @@ private:
|
||||
OFF, ///< timeslot is off
|
||||
TSC, ///< timeslot should contain a normal burst
|
||||
RACH, ///< timeslot should contain an access burst
|
||||
SCH, ///< timeslot should contain a SCH burst
|
||||
IDLE ///< timeslot is an idle (or dummy) burst
|
||||
} CorrType;
|
||||
|
||||
/** modulate and add a burst to the transmit queue */
|
||||
void addRadioVector(size_t chan, BitVector &bits,
|
||||
int RSSI, GSM::Time &wTime);
|
||||
|
||||
/** Update filler table */
|
||||
void updateFillerTable(size_t chan, radioVector *burst);
|
||||
|
||||
/** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
|
||||
void pushRadioVector(GSM::Time &nowTime);
|
||||
|
||||
/** Pull and demodulate a burst from the receive FIFO */
|
||||
SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI,
|
||||
int &timingOffset, size_t chan = 0);
|
||||
|
||||
/** Set modulus for specific timeslot */
|
||||
void setModulus(size_t timeslot, size_t chan);
|
||||
|
||||
/** return the expected burst type for the specified timestamp */
|
||||
CorrType expectedCorrType(GSM::Time currTime, size_t chan);
|
||||
|
||||
/** send messages over the clock socket */
|
||||
void writeClockInterface(void);
|
||||
|
||||
/** Detect RACH bursts */
|
||||
bool detectRACH(TransceiverState *state,
|
||||
signalVector &burst,
|
||||
complex &, float &toa);
|
||||
|
||||
bool detectSCH(TransceiverState *state,
|
||||
signalVector &burst,
|
||||
complex &, float &toa);
|
||||
|
||||
bool decodeSCH(SoftVector *burst, GSM::Time *time);
|
||||
bool correctFCCH(TransceiverState *state, signalVector *burst);
|
||||
|
||||
/** Detect normal bursts */
|
||||
bool detectTSC(TransceiverState *state,
|
||||
signalVector &burst,
|
||||
complex &, float &toa, GSM::Time &time);
|
||||
|
||||
/** Demodulat burst and output soft bits */
|
||||
SoftVector *demodulate(TransceiverState *state,
|
||||
signalVector &burst, complex amp,
|
||||
float toa, size_t tn, bool equalize);
|
||||
|
||||
|
||||
int mSPSTx; ///< number of samples per Tx symbol
|
||||
int mSPSRx; ///< number of samples per Rx symbol
|
||||
size_t mChans;
|
||||
|
||||
bool mOn; ///< flag to indicate that transceiver is powered on
|
||||
double mTxFreq; ///< the transmit frequency
|
||||
double mRxFreq; ///< the receive frequency
|
||||
int mPower; ///< the transmit power in dB
|
||||
unsigned mTSC; ///< the midamble sequence code
|
||||
unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols
|
||||
unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask
|
||||
int mBSIC; ///< MS - detected BSIC
|
||||
|
||||
std::vector<TransceiverState> mStates;
|
||||
|
||||
public:
|
||||
|
||||
/** Transceiver constructor
|
||||
@param wBasePort base port number of UDP sockets
|
||||
@param TRXAddress IP address of the TRX manager, as a string
|
||||
@param wSPS number of samples per GSM symbol
|
||||
@param wTransmitLatency initial setting of transmit latency
|
||||
@param radioInterface associated radioInterface object
|
||||
*/
|
||||
Transceiver(int wBasePort,
|
||||
const char *TRXAddress,
|
||||
size_t wSPS, size_t chans,
|
||||
GSM::Time wTransmitLatency,
|
||||
RadioInterface *wRadioInterface);
|
||||
|
||||
/** Destructor */
|
||||
~Transceiver();
|
||||
|
||||
/** start the Transceiver */
|
||||
void start();
|
||||
bool init(bool filler);
|
||||
|
||||
/** attach the radioInterface receive FIFO */
|
||||
bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
|
||||
{
|
||||
if (chan >= mReceiveFIFO.size())
|
||||
return false;
|
||||
|
||||
mReceiveFIFO[chan] = wFIFO;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** accessor for number of channels */
|
||||
size_t numChans() const { return mChans; };
|
||||
|
||||
/** Codes for channel combinations */
|
||||
typedef enum {
|
||||
@@ -96,121 +242,56 @@ private:
|
||||
LOOPBACK ///< similar go VII, used in loopback testing
|
||||
} ChannelCombination;
|
||||
|
||||
|
||||
/** unmodulate a modulated burst */
|
||||
#ifdef TRANSMIT_LOGGING
|
||||
void unModulateVector(signalVector wVector);
|
||||
#endif
|
||||
|
||||
/** modulate and add a burst to the transmit queue */
|
||||
void addRadioVector(BitVector &burst,
|
||||
int RSSI,
|
||||
GSM::Time &wTime);
|
||||
|
||||
/** Push modulated burst into transmit FIFO corresponding to a particular timestamp */
|
||||
void pushRadioVector(GSM::Time &nowTime);
|
||||
|
||||
/** Pull and demodulate a burst from the receive FIFO */
|
||||
SoftVector *pullRadioVector(GSM::Time &wTime,
|
||||
int &RSSI,
|
||||
int &timingOffset);
|
||||
|
||||
/** Set modulus for specific timeslot */
|
||||
void setModulus(int timeslot);
|
||||
|
||||
/** return the expected burst type for the specified timestamp */
|
||||
CorrType expectedCorrType(GSM::Time currTime);
|
||||
|
||||
/** send messages over the clock socket */
|
||||
void writeClockInterface(void);
|
||||
|
||||
signalVector *gsmPulse; ///< the GSM shaping pulse for modulation
|
||||
|
||||
int mSamplesPerSymbol; ///< number of samples per GSM symbol
|
||||
|
||||
bool mOn; ///< flag to indicate that transceiver is powered on
|
||||
ChannelCombination mChanType[8]; ///< channel types for all timeslots
|
||||
double mTxFreq; ///< the transmit frequency
|
||||
double mRxFreq; ///< the receive frequency
|
||||
int mPower; ///< the transmit power in dB
|
||||
unsigned mTSC; ///< the midamble sequence code
|
||||
double mEnergyThreshold; ///< threshold to determine if received data is potentially a GSM burst
|
||||
GSM::Time prevFalseDetectionTime; ///< last timestamp of a false energy detection
|
||||
int fillerModulus[8]; ///< modulus values of all timeslots, in frames
|
||||
signalVector *fillerTable[102][8]; ///< table of modulated filler waveforms for all timeslots
|
||||
unsigned mMaxExpectedDelay; ///< maximum expected time-of-arrival offset in GSM symbols
|
||||
|
||||
GSM::Time channelEstimateTime[8]; ///< last timestamp of each timeslot's channel estimate
|
||||
signalVector *channelResponse[8]; ///< most recent channel estimate of all timeslots
|
||||
float SNRestimate[8]; ///< most recent SNR estimate of all timeslots
|
||||
signalVector *DFEForward[8]; ///< most recent DFE feedforward filter of all timeslots
|
||||
signalVector *DFEFeedback[8]; ///< most recent DFE feedback filter of all timeslots
|
||||
float chanRespOffset[8]; ///< most recent timing offset, e.g. TOA, of all timeslots
|
||||
complex chanRespAmplitude[8]; ///< most recent channel amplitude of all timeslots
|
||||
|
||||
public:
|
||||
|
||||
/** Transceiver constructor
|
||||
@param wBasePort base port number of UDP sockets
|
||||
@param TRXAddress IP address of the TRX manager, as a string
|
||||
@param wSamplesPerSymbol number of samples per GSM symbol
|
||||
@param wTransmitLatency initial setting of transmit latency
|
||||
@param radioInterface associated radioInterface object
|
||||
*/
|
||||
Transceiver(int wBasePort,
|
||||
const char *TRXAddress,
|
||||
int wSamplesPerSymbol,
|
||||
GSM::Time wTransmitLatency,
|
||||
RadioInterface *wRadioInterface);
|
||||
|
||||
/** Destructor */
|
||||
~Transceiver();
|
||||
|
||||
/** start the Transceiver */
|
||||
void start();
|
||||
|
||||
/** attach the radioInterface receive FIFO */
|
||||
void receiveFIFO(VectorFIFO *wFIFO) { mReceiveFIFO = wFIFO;}
|
||||
|
||||
/** attach the radioInterface transmit FIFO */
|
||||
void transmitFIFO(VectorFIFO *wFIFO) { mTransmitFIFO = wFIFO;}
|
||||
enum {
|
||||
TRX_MODE_OFF,
|
||||
TRX_MODE_BTS,
|
||||
TRX_MODE_MS_ACQUIRE,
|
||||
TRX_MODE_MS_TRACK,
|
||||
};
|
||||
|
||||
protected:
|
||||
/** drive lower receive I/O and burst generation */
|
||||
void driveReceiveRadio();
|
||||
|
||||
/** drive reception and demodulation of GSM bursts */
|
||||
void driveReceiveFIFO();
|
||||
/** drive demodulation of GSM bursts */
|
||||
void driveReceiveFIFO(size_t chan);
|
||||
|
||||
/** drive transmission of GSM bursts */
|
||||
void driveTransmitFIFO();
|
||||
void driveTxFIFO();
|
||||
|
||||
/** drive handling of control messages from GSM core */
|
||||
void driveControl();
|
||||
void driveControl(size_t chan);
|
||||
|
||||
/**
|
||||
drive modulation and sorting of GSM bursts from GSM core
|
||||
@return true if a burst was transferred successfully
|
||||
*/
|
||||
bool driveTransmitPriorityQueue();
|
||||
bool driveTxPriorityQueue(size_t chan);
|
||||
|
||||
friend void *FIFOServiceLoopAdapter(Transceiver *);
|
||||
friend void *RxUpperLoopAdapter(TransceiverChannel *);
|
||||
|
||||
friend void *ControlServiceLoopAdapter(Transceiver *);
|
||||
friend void *TxUpperLoopAdapter(TransceiverChannel *);
|
||||
|
||||
friend void *LowerLoopAdapter(Transceiver *);
|
||||
|
||||
friend void *ControlServiceLoopAdapter(TransceiverChannel *);
|
||||
|
||||
friend void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);
|
||||
|
||||
void reset();
|
||||
|
||||
/** set priority on current thread */
|
||||
void setPriority() { mRadioInterface->setPriority(); }
|
||||
void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); }
|
||||
|
||||
};
|
||||
|
||||
/** FIFO thread loop */
|
||||
void *FIFOServiceLoopAdapter(Transceiver *);
|
||||
void *RxUpperLoopAdapter(TransceiverChannel *);
|
||||
|
||||
/** Main drive threads */
|
||||
void *LowerLoopAdapter(Transceiver *);
|
||||
|
||||
/** control message handler thread loop */
|
||||
void *ControlServiceLoopAdapter(Transceiver *);
|
||||
void *ControlServiceLoopAdapter(TransceiverChannel *);
|
||||
|
||||
/** transmit queueing thread loop */
|
||||
void *TransmitPriorityQueueServiceLoopAdapter(Transceiver *);
|
||||
void *TxUpperLoopAdapter(TransceiverChannel *);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -59,14 +59,28 @@ const dboardConfigType dboardConfig = TXA_RXB;
|
||||
|
||||
const double USRPDevice::masterClockRate = 52.0e6;
|
||||
|
||||
USRPDevice::USRPDevice (double _desiredSampleRate, bool skipRx)
|
||||
: skipRx(skipRx)
|
||||
USRPDevice::USRPDevice(size_t sps, size_t, bool)
|
||||
{
|
||||
LOG(INFO) << "creating USRP device...";
|
||||
decimRate = (unsigned int) round(masterClockRate/_desiredSampleRate);
|
||||
|
||||
this->sps = sps;
|
||||
decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) sps));
|
||||
actualSampleRate = masterClockRate/decimRate;
|
||||
rxGain = 0;
|
||||
|
||||
/*
|
||||
* Undetermined delay b/w ping response timestamp and true
|
||||
* receive timestamp. Values are empirically measured. With
|
||||
* split sample rate Tx/Rx - 4/1 sps we need to need to
|
||||
* compensate for advance rather than delay.
|
||||
*/
|
||||
if (sps == 1)
|
||||
pingOffset = 272;
|
||||
else if (sps == 4)
|
||||
pingOffset = 269 - 7500;
|
||||
else
|
||||
pingOffset = 0;
|
||||
|
||||
#ifdef SWLOOPBACK
|
||||
samplePeriod = 1.0e6/actualSampleRate;
|
||||
loopbackBufferSize = 0;
|
||||
@@ -75,7 +89,7 @@ USRPDevice::USRPDevice (double _desiredSampleRate, bool skipRx)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool USRPDevice::open(const std::string &)
|
||||
int USRPDevice::open(const std::string &, bool)
|
||||
{
|
||||
writeLock.unlock();
|
||||
|
||||
@@ -86,18 +100,17 @@ bool USRPDevice::open(const std::string &)
|
||||
m_uRx.reset();
|
||||
if (!skipRx) {
|
||||
try {
|
||||
m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(0,decimRate,1,-1,
|
||||
usrp_standard_rx::FPGA_MODE_NORMAL,
|
||||
1024,16*8,rbf));
|
||||
#ifdef HAVE_LIBUSRP_3_2
|
||||
m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(
|
||||
0, decimRate * sps, 1, -1,
|
||||
usrp_standard_rx::FPGA_MODE_NORMAL,
|
||||
1024, 16 * 8, rbf));
|
||||
m_uRx->set_fpga_master_clock_freq(masterClockRate);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
catch(...) {
|
||||
LOG(ALERT) << "make failed on Rx";
|
||||
m_uRx.reset();
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m_uRx->fpga_master_clock_freq() != masterClockRate)
|
||||
@@ -105,22 +118,21 @@ bool USRPDevice::open(const std::string &)
|
||||
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq()
|
||||
<< ", desired clock freq = " << masterClockRate;
|
||||
m_uRx.reset();
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1,
|
||||
1024,16*8,rbf));
|
||||
#ifdef HAVE_LIBUSRP_3_2
|
||||
m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(
|
||||
0, decimRate * 2, 1, -1,
|
||||
1024, 16 * 8, rbf));
|
||||
m_uTx->set_fpga_master_clock_freq(masterClockRate);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
catch(...) {
|
||||
LOG(ALERT) << "make failed on Tx";
|
||||
m_uTx.reset();
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m_uTx->fpga_master_clock_freq() != masterClockRate)
|
||||
@@ -128,7 +140,7 @@ bool USRPDevice::open(const std::string &)
|
||||
LOG(ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq()
|
||||
<< ", desired clock freq = " << masterClockRate;
|
||||
m_uTx.reset();
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!skipRx) m_uRx->stop();
|
||||
@@ -165,7 +177,7 @@ bool USRPDevice::open(const std::string &)
|
||||
samplesWritten = 0;
|
||||
started = false;
|
||||
|
||||
return true;
|
||||
return NORMAL;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,49 +265,66 @@ double USRPDevice::minRxGain()
|
||||
return m_dbRx->gain_min();
|
||||
}
|
||||
|
||||
double USRPDevice::setTxGain(double dB) {
|
||||
|
||||
writeLock.lock();
|
||||
if (dB > maxTxGain()) dB = maxTxGain();
|
||||
if (dB < minTxGain()) dB = minTxGain();
|
||||
double USRPDevice::setTxGain(double dB, size_t chan)
|
||||
{
|
||||
if (chan) {
|
||||
LOG(ALERT) << "Invalid channel " << chan;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
|
||||
writeLock.lock();
|
||||
if (dB > maxTxGain())
|
||||
dB = maxTxGain();
|
||||
if (dB < minTxGain())
|
||||
dB = minTxGain();
|
||||
|
||||
if (!m_dbTx->set_gain(dB))
|
||||
LOG(ERR) << "Error setting TX gain";
|
||||
LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
return dB;
|
||||
if (!m_dbTx->set_gain(dB))
|
||||
LOG(ERR) << "Error setting TX gain";
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
return dB;
|
||||
}
|
||||
|
||||
|
||||
double USRPDevice::setRxGain(double dB) {
|
||||
double USRPDevice::setRxGain(double dB, size_t chan)
|
||||
{
|
||||
if (chan) {
|
||||
LOG(ALERT) << "Invalid channel " << chan;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
writeLock.lock();
|
||||
if (dB > maxRxGain()) dB = maxRxGain();
|
||||
if (dB < minRxGain()) dB = minRxGain();
|
||||
|
||||
LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
|
||||
dB = 47.0;
|
||||
|
||||
if (!m_dbRx->set_gain(dB))
|
||||
LOG(ERR) << "Error setting RX gain";
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
return dB;
|
||||
writeLock.lock();
|
||||
if (dB > maxRxGain())
|
||||
dB = maxRxGain();
|
||||
if (dB < minRxGain())
|
||||
dB = minRxGain();
|
||||
|
||||
LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
|
||||
|
||||
if (!m_dbRx->set_gain(dB))
|
||||
LOG(ERR) << "Error setting RX gain";
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
return dB;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: Assumes sequential reads
|
||||
int USRPDevice::readSamples(short *buf, int len, bool *overrun,
|
||||
TIMESTAMP timestamp,
|
||||
bool *underrun,
|
||||
unsigned *RSSI)
|
||||
int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
|
||||
TIMESTAMP timestamp, bool *underrun, unsigned *RSSI)
|
||||
{
|
||||
#ifndef SWLOOPBACK
|
||||
if (!m_uRx) return 0;
|
||||
|
||||
if (!m_uRx)
|
||||
return 0;
|
||||
|
||||
short *buf = bufs[0];
|
||||
|
||||
timestamp += timestampOffset;
|
||||
|
||||
if (timestamp + len < timeStart) {
|
||||
@@ -341,7 +370,7 @@ int USRPDevice::readSamples(short *buf, int len, bool *overrun,
|
||||
uint32_t word2 = usrp_to_host_u32(tmpBuf[2]);
|
||||
if ((word2 >> 16) == ((0x01 << 8) | 0x02)) {
|
||||
timestamp -= timestampOffset;
|
||||
timestampOffset = pktTimestamp - pingTimestamp + PINGOFFSET;
|
||||
timestampOffset = pktTimestamp - pingTimestamp + pingOffset;
|
||||
LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset;
|
||||
timestamp += timestampOffset;
|
||||
isAligned = true;
|
||||
@@ -437,17 +466,20 @@ int USRPDevice::readSamples(short *buf, int len, bool *overrun,
|
||||
#endif
|
||||
}
|
||||
|
||||
int USRPDevice::writeSamples(short *buf, int len, bool *underrun,
|
||||
unsigned long long timestamp,
|
||||
bool isControl)
|
||||
int USRPDevice::writeSamples(std::vector<short *> &bufs, int len,
|
||||
bool *underrun, unsigned long long timestamp,
|
||||
bool isControl)
|
||||
{
|
||||
writeLock.lock();
|
||||
|
||||
#ifndef SWLOOPBACK
|
||||
if (!m_uTx) return 0;
|
||||
|
||||
if (!m_uTx)
|
||||
return 0;
|
||||
|
||||
short *buf = bufs[0];
|
||||
|
||||
static uint32_t outData[128*20];
|
||||
|
||||
|
||||
for (int i = 0; i < len*2; i++) {
|
||||
buf[i] = host_to_usrp_short(buf[i]);
|
||||
}
|
||||
@@ -499,7 +531,9 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
|
||||
uint32_t *wordPtr = (uint32_t *) data;
|
||||
*wordPtr = host_to_usrp_u32(*wordPtr);
|
||||
bool tmpUnderrun;
|
||||
if (writeSamples((short *) data,1,&tmpUnderrun,timestamp & 0x0ffffffffll,true)) {
|
||||
|
||||
std::vector<short *> buf(1, data);
|
||||
if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) {
|
||||
pingTimestamp = timestamp;
|
||||
return true;
|
||||
}
|
||||
@@ -510,10 +544,15 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp)
|
||||
}
|
||||
|
||||
#ifndef SWLOOPBACK
|
||||
bool USRPDevice::setTxFreq(double wFreq)
|
||||
bool USRPDevice::setTxFreq(double wFreq, size_t chan)
|
||||
{
|
||||
usrp_tune_result result;
|
||||
|
||||
if (chan) {
|
||||
LOG(ALERT) << "Invalid channel " << chan;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) {
|
||||
LOG(INFO) << "set TX: " << wFreq << std::endl
|
||||
<< " baseband freq: " << result.baseband_freq << std::endl
|
||||
@@ -530,10 +569,15 @@ bool USRPDevice::setTxFreq(double wFreq)
|
||||
}
|
||||
}
|
||||
|
||||
bool USRPDevice::setRxFreq(double wFreq)
|
||||
bool USRPDevice::setRxFreq(double wFreq, size_t chan)
|
||||
{
|
||||
usrp_tune_result result;
|
||||
|
||||
if (chan) {
|
||||
LOG(ALERT) << "Invalid channel " << chan;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_uRx->tune(0, m_dbRx, wFreq, &result)) {
|
||||
LOG(INFO) << "set RX: " << wFreq << std::endl
|
||||
<< " baseband freq: " << result.baseband_freq << std::endl
|
||||
@@ -556,7 +600,7 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;};
|
||||
bool USRPDevice::setRxFreq(double wFreq) { return true;};
|
||||
#endif
|
||||
|
||||
RadioDevice *RadioDevice::make(double desiredSampleRate, bool skipRx)
|
||||
RadioDevice *RadioDevice::make(size_t sps, size_t chans, bool diversity)
|
||||
{
|
||||
return new USRPDevice(desiredSampleRate, skipRx);
|
||||
return new USRPDevice(sps, chans, diversity);
|
||||
}
|
||||
|
@@ -21,29 +21,17 @@
|
||||
|
||||
#include "radioDevice.h"
|
||||
|
||||
#ifdef HAVE_LIBUSRP_3_3 // [
|
||||
# include <usrp/usrp_standard.h>
|
||||
# include <usrp/usrp_bytesex.h>
|
||||
# include <usrp/usrp_prims.h>
|
||||
#else // HAVE_LIBUSRP_3_3 ][
|
||||
# include "usrp_standard.h"
|
||||
# include "usrp_bytesex.h"
|
||||
# include "usrp_prims.h"
|
||||
#endif // !HAVE_LIBUSRP_3_3 ]
|
||||
#include <usrp/usrp_standard.h>
|
||||
#include <usrp/usrp_bytesex.h>
|
||||
#include <usrp/usrp_prims.h>
|
||||
#include <sys/time.h>
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
/** Define types which are not defined in libusrp-3.1 */
|
||||
#ifndef HAVE_LIBUSRP_3_2
|
||||
#include <boost/shared_ptr.hpp>
|
||||
typedef boost::shared_ptr<usrp_standard_tx> usrp_standard_tx_sptr;
|
||||
typedef boost::shared_ptr<usrp_standard_rx> usrp_standard_rx_sptr;
|
||||
#endif // HAVE_LIBUSRP_3_2
|
||||
|
||||
|
||||
|
||||
/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */
|
||||
class USRPDevice: public RadioDevice {
|
||||
@@ -60,6 +48,7 @@ private:
|
||||
usrp_subdev_spec rxSubdevSpec;
|
||||
usrp_subdev_spec txSubdevSpec;
|
||||
|
||||
int sps;
|
||||
double actualSampleRate; ///< the actual USRP sampling rate
|
||||
unsigned int decimRate; ///< the USRP decimation rate
|
||||
|
||||
@@ -87,7 +76,8 @@ private:
|
||||
TIMESTAMP timestampOffset; ///< timestamp offset b/w Tx and Rx blocks
|
||||
TIMESTAMP latestWriteTimestamp; ///< timestamp of most recent ping command
|
||||
TIMESTAMP pingTimestamp; ///< timestamp of most recent ping response
|
||||
static const TIMESTAMP PINGOFFSET = 272; ///< undetermined delay b/w ping response timestamp and true receive timestamp
|
||||
|
||||
long long pingOffset;
|
||||
unsigned long hi32Timestamp;
|
||||
unsigned long lastPktTimestamp;
|
||||
|
||||
@@ -103,19 +93,13 @@ private:
|
||||
bool firstRead;
|
||||
#endif
|
||||
|
||||
/** Set the transmission frequency */
|
||||
bool tx_setFreq(double freq, double *actual_freq);
|
||||
|
||||
/** Set the receiver frequency */
|
||||
bool rx_setFreq(double freq, double *actual_freq);
|
||||
|
||||
public:
|
||||
|
||||
/** Object constructor */
|
||||
USRPDevice (double _desiredSampleRate, bool skipRx);
|
||||
USRPDevice(size_t sps, size_t chans = 1, bool diversity = false);
|
||||
|
||||
/** Instantiate the USRP */
|
||||
bool open(const std::string &);
|
||||
int open(const std::string &, bool);
|
||||
|
||||
/** Start the USRP */
|
||||
bool start();
|
||||
@@ -124,10 +108,9 @@ private:
|
||||
bool stop();
|
||||
|
||||
/** Set priority not supported */
|
||||
void setPriority() { return; }
|
||||
void setPriority(float prio = 0.5) { }
|
||||
|
||||
/** Only USB bus supported */
|
||||
busType getBus() { return USB; }
|
||||
enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; }
|
||||
|
||||
/**
|
||||
Read samples from the USRP.
|
||||
@@ -139,10 +122,9 @@ private:
|
||||
@param RSSI The received signal strength of the read result
|
||||
@return The number of samples actually read
|
||||
*/
|
||||
int readSamples(short *buf, int len, bool *overrun,
|
||||
TIMESTAMP timestamp = 0xffffffff,
|
||||
bool *underrun = NULL,
|
||||
unsigned *RSSI = NULL);
|
||||
int readSamples(std::vector<short *> &buf, int len, bool *overrun,
|
||||
TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL,
|
||||
unsigned *RSSI = NULL);
|
||||
/**
|
||||
Write samples to the USRP.
|
||||
@param buf Contains the data to be written.
|
||||
@@ -152,18 +134,17 @@ private:
|
||||
@param isControl Set if data is a control packet, e.g. a ping command
|
||||
@return The number of samples actually written
|
||||
*/
|
||||
int writeSamples(short *buf, int len, bool *underrun,
|
||||
TIMESTAMP timestamp = 0xffffffff,
|
||||
bool isControl = false);
|
||||
|
||||
int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
|
||||
TIMESTAMP timestamp = 0xffffffff, bool isControl = false);
|
||||
|
||||
/** Update the alignment between the read and write timestamps */
|
||||
bool updateAlignment(TIMESTAMP timestamp);
|
||||
|
||||
|
||||
/** Set the transmitter frequency */
|
||||
bool setTxFreq(double wFreq);
|
||||
bool setTxFreq(double wFreq, size_t chan = 0);
|
||||
|
||||
/** Set the receiver frequency */
|
||||
bool setRxFreq(double wFreq);
|
||||
bool setRxFreq(double wFreq, size_t chan = 0);
|
||||
|
||||
/** Returns the starting write Timestamp*/
|
||||
TIMESTAMP initialWriteTimestamp(void) { return 20000;}
|
||||
@@ -178,10 +159,10 @@ private:
|
||||
double fullScaleOutputValue() {return 9450.0;}
|
||||
|
||||
/** sets the receive chan gain, returns the gain setting **/
|
||||
double setRxGain(double dB);
|
||||
double setRxGain(double dB, size_t chan = 0);
|
||||
|
||||
/** get the current receive gain */
|
||||
double getRxGain(void) {return rxGain;}
|
||||
double getRxGain(size_t chan = 0) { return rxGain; }
|
||||
|
||||
/** return maximum Rx Gain **/
|
||||
double maxRxGain(void);
|
||||
@@ -190,7 +171,7 @@ private:
|
||||
double minRxGain(void);
|
||||
|
||||
/** sets the transmit chan gain, returns the gain setting **/
|
||||
double setTxGain(double dB);
|
||||
double setTxGain(double dB, size_t chan = 0);
|
||||
|
||||
/** return maximum Tx Gain **/
|
||||
double maxTxGain(void);
|
||||
@@ -198,13 +179,12 @@ private:
|
||||
/** return minimum Rx Gain **/
|
||||
double minTxGain(void);
|
||||
|
||||
|
||||
/** Return internal status values */
|
||||
inline double getTxFreq() { return 0;}
|
||||
inline double getRxFreq() { return 0;}
|
||||
inline double getSampleRate() {return actualSampleRate;}
|
||||
inline double getTxFreq(size_t chan = 0) { return 0; }
|
||||
inline double getRxFreq(size_t chan = 0) { return 0; }
|
||||
inline double getSampleRate() { return actualSampleRate; }
|
||||
inline double numberRead() { return samplesRead; }
|
||||
inline double numberWritten() { return samplesWritten;}
|
||||
inline double numberWritten() { return samplesWritten; }
|
||||
|
||||
};
|
||||
|
||||
|
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <Logger.h>
|
||||
#include <Configuration.h>
|
||||
#include "radioDevice.h"
|
||||
|
||||
ConfigurationTable gConfig;
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
// Configure logger.
|
||||
if (argc>1) gLogInit(argv[1]);
|
||||
else gLogInit("DEBUG");
|
||||
//if (argc>2) gSetLogFile(argv[2]);
|
||||
|
||||
RadioDevice *usrp = RadioDevice::make(52.0e6/192.0);
|
||||
|
||||
usrp->open("");
|
||||
|
||||
TIMESTAMP timestamp;
|
||||
|
||||
usrp->setTxFreq(825.4e6);
|
||||
usrp->setRxFreq(825.4e6);
|
||||
|
||||
usrp->start();
|
||||
|
||||
usrp->setRxGain(57);
|
||||
|
||||
LOG(INFO) << "Looping...";
|
||||
bool underrun;
|
||||
|
||||
short data[]={0x00,0x02};
|
||||
|
||||
usrp->updateAlignment(20000);
|
||||
usrp->updateAlignment(21000);
|
||||
|
||||
int numpkts = 1;
|
||||
short data2[512*2*numpkts];
|
||||
for (int i = 0; i < 512*numpkts; i++) {
|
||||
data2[i<<1] = 10000;//4096*cos(2*3.14159*(i % 126)/126);
|
||||
data2[(i<<1) + 1] = 10000;//4096*sin(2*3.14159*(i % 126)/126);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1; i++)
|
||||
usrp->writeSamples((short*) data2,512*numpkts,&underrun,102000+i*1000);
|
||||
|
||||
timestamp = 19000;
|
||||
double sum = 0.0;
|
||||
unsigned long num = 0;
|
||||
while (1) {
|
||||
short readBuf[512*2];
|
||||
int rd = usrp->readSamples(readBuf,512,&underrun,timestamp);
|
||||
if (rd) {
|
||||
LOG(INFO) << "rcvd. data@:" << timestamp;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
uint32_t *wordPtr = (uint32_t *) &readBuf[2*i];
|
||||
printf ("%llu: %d %d\n", timestamp+i,readBuf[2*i],readBuf[2*i+1]);
|
||||
sum += (readBuf[2*i+1]*readBuf[2*i+1] + readBuf[2*i]*readBuf[2*i]);
|
||||
num++;
|
||||
//if (num % 10000 == 0) printf("avg pwr: %f\n",sum/num);
|
||||
}
|
||||
timestamp += rd;
|
||||
//usrp->writeSamples((short*) data2,512*numpkts,&underrun,timestamp+1000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
23
Transceiver52M/arm/Makefile.am
Normal file
23
Transceiver52M/arm/Makefile.am
Normal file
@@ -0,0 +1,23 @@
|
||||
if ARCH_ARM
|
||||
if ARCH_ARM_A15
|
||||
ARCH_FLAGS = -mfpu=neon-vfpv4
|
||||
else
|
||||
ARCH_FLAGS = -mfpu=neon
|
||||
endif
|
||||
|
||||
AM_CFLAGS = -Wall $(ARCH_FLAGS) -std=gnu99 -I../common
|
||||
AM_CCASFLAGS = $(ARCH_FLAGS)
|
||||
|
||||
noinst_LTLIBRARIES = libarch.la
|
||||
|
||||
libarch_la_SOURCES = \
|
||||
../common/convolve_base.c \
|
||||
convert.c \
|
||||
convert_neon.S \
|
||||
convolve.c \
|
||||
convolve_neon.S \
|
||||
scale.c \
|
||||
scale_neon.S \
|
||||
mult.c \
|
||||
mult_neon.S
|
||||
endif
|
96
Transceiver52M/arm/convert.c
Normal file
96
Transceiver52M/arm/convert.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* NEON type conversions
|
||||
* 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 "convert.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
void neon_convert_ps_si16_4n(short *, float *, float *, int);
|
||||
void neon_convert_si16_ps_4n(float *, short *, int);
|
||||
|
||||
#ifndef HAVE_NEON
|
||||
static void convert_si16_ps(float *out, short *in, int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
out[i] = in[i];
|
||||
}
|
||||
|
||||
static void convert_ps_si16(short *out, float *in, float scale, int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
out[i] = in[i] * scale;
|
||||
}
|
||||
#else
|
||||
/* 4*N 16-bit signed integer conversion with remainder */
|
||||
static void neon_convert_si16_ps(float *restrict out,
|
||||
short *restrict in,
|
||||
int len)
|
||||
{
|
||||
int start = len / 4 * 4;
|
||||
|
||||
neon_convert_si16_ps_4n(out, in, len >> 2);
|
||||
|
||||
for (int i = 0; i < len % 4; i++)
|
||||
out[start + i] = (float) in[start + i];
|
||||
}
|
||||
|
||||
/* 4*N 16-bit signed integer conversion with remainder */
|
||||
static void neon_convert_ps_si16(short *restrict out,
|
||||
float *restrict in,
|
||||
float *restrict scale,
|
||||
int len)
|
||||
{
|
||||
int start = len / 4 * 4;
|
||||
|
||||
neon_convert_ps_si16_4n(out, in, scale, len >> 2);
|
||||
|
||||
for (int i = 0; i < len % 4; i++)
|
||||
out[start + i] = (short) (in[start + i] * (*scale));
|
||||
}
|
||||
#endif
|
||||
|
||||
void convert_float_short(short *out, float *in, float scale, int len)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
float q[4] = { scale, scale, scale, scale };
|
||||
|
||||
if (len % 4)
|
||||
neon_convert_ps_si16(out, in, q, len);
|
||||
else
|
||||
neon_convert_ps_si16_4n(out, in, q, len >> 2);
|
||||
#else
|
||||
convert_ps_si16(out, in, scale, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
void convert_short_float(float *out, short *in, int len)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if (len % 4)
|
||||
neon_convert_si16_ps(out, in, len);
|
||||
else
|
||||
neon_convert_si16_ps_4n(out, in, len >> 2);
|
||||
#else
|
||||
convert_si16_ps(out, in, len);
|
||||
#endif
|
||||
}
|
51
Transceiver52M/arm/convert_neon.S
Normal file
51
Transceiver52M/arm/convert_neon.S
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* NEON type conversions
|
||||
* 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
|
||||
*/
|
||||
|
||||
.syntax unified
|
||||
.text
|
||||
.align 2
|
||||
.global neon_convert_ps_si16_4n
|
||||
.type neon_convert_ps_si16_4n, %function
|
||||
neon_convert_ps_si16_4n:
|
||||
vld1.32 {q1}, [r2]
|
||||
.loop_fltint:
|
||||
vld1.64 {d0-d1}, [r1]!
|
||||
vmul.f32 q0, q1
|
||||
vcvt.s32.f32 q2, q0
|
||||
vqmovn.s32 d0, q2
|
||||
vst1.64 {d0}, [r0]!
|
||||
subs r3, #1
|
||||
bne .loop_fltint
|
||||
bx lr
|
||||
.size neon_convert_ps_si16_4n, .-neon_convert_ps_si16_4n
|
||||
.text
|
||||
.align 2
|
||||
.global neon_convert_si16_ps_4n
|
||||
.type neon_convert_si16_ps_4n, %function
|
||||
neon_convert_si16_ps_4n:
|
||||
.loop_intflt:
|
||||
vld1.64 {d0}, [r1]!
|
||||
vmovl.s16 q1, d0
|
||||
vcvt.f32.s32 q0, q1
|
||||
vst1.64 {q0}, [r0]!
|
||||
subs r2, #1
|
||||
bne .loop_intflt
|
||||
bx lr
|
||||
.size neon_convert_si16_ps_4n, .-neon_convert_si16_ps_4n
|
||||
.section .note.GNU-stack,"",%progbits
|
139
Transceiver52M/arm/convolve.c
Normal file
139
Transceiver52M/arm/convolve.c
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* NEON 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
|
||||
|
||||
/* Forward declarations from base implementation */
|
||||
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);
|
||||
|
||||
int bounds_check(int x_len, int h_len, int y_len,
|
||||
int start, int len, int step);
|
||||
|
||||
#ifdef HAVE_NEON
|
||||
/* Calls into NEON assembler */
|
||||
void neon_conv_real4(float *x, float *h, float *y, int len);
|
||||
void neon_conv_real8(float *x, float *h, float *y, int len);
|
||||
void neon_conv_real12(float *x, float *h, float *y, int len);
|
||||
void neon_conv_real16(float *x, float *h, float *y, int len);
|
||||
void neon_conv_real20(float *x, float *h, float *y, int len);
|
||||
void mac_cx_neon4(float *x, float *h, float *y, int len);
|
||||
|
||||
/* Complex-complex convolution */
|
||||
static void neon_conv_cmplx_4n(float *x, float *h, float *y, int h_len, int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
mac_cx_neon4(&x[2 * i], h, &y[2 * i], h_len >> 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
|
||||
if (bounds_check(x_len, h_len, y_len, start, len, step) < 0)
|
||||
return -1;
|
||||
|
||||
memset(y, 0, len * 2 * sizeof(float));
|
||||
|
||||
#ifdef HAVE_NEON
|
||||
if (step <= 4) {
|
||||
switch (h_len) {
|
||||
case 4:
|
||||
conv_func = neon_conv_real4;
|
||||
break;
|
||||
case 8:
|
||||
conv_func = neon_conv_real8;
|
||||
break;
|
||||
case 12:
|
||||
conv_func = neon_conv_real12;
|
||||
break;
|
||||
case 16:
|
||||
conv_func = neon_conv_real16;
|
||||
break;
|
||||
case 20:
|
||||
conv_func = neon_conv_real20;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (conv_func) {
|
||||
conv_func(&x[2 * (-(h_len - 1) + start)],
|
||||
h, y, 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_NEON
|
||||
if (step <= 4 && !(h_len % 4))
|
||||
conv_func = neon_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;
|
||||
}
|
277
Transceiver52M/arm/convolve_neon.S
Normal file
277
Transceiver52M/arm/convolve_neon.S
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* NEON 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
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
.syntax unified
|
||||
.text
|
||||
.align 2
|
||||
.global neon_conv_real4
|
||||
.type neon_conv_real4, %function
|
||||
neon_conv_real4:
|
||||
push {r4, lr}
|
||||
vpush {q4-q7}
|
||||
vld2.32 {q0-q1}, [r1]
|
||||
ldr r4, =8
|
||||
.neon_conv_loop4:
|
||||
vld2.32 {q2-q3}, [r0], r4
|
||||
vmul.f32 q4, q2, q0
|
||||
vmul.f32 q5, q3, q0
|
||||
vpadd.f32 d12, d8, d9
|
||||
vpadd.f32 d13, d10, d11
|
||||
vpadd.f32 d14, d12, d13
|
||||
vst1.64 {d14}, [r2]!
|
||||
subs r3, r3, #1
|
||||
bne .neon_conv_loop4
|
||||
vpop {q4-q7}
|
||||
pop {r4, pc}
|
||||
.size neon_conv_real4, .-neon_conv_real4
|
||||
.align 2
|
||||
.p2align 4,,15
|
||||
.global neon_conv_real8
|
||||
.type neon_conv_real8, %function
|
||||
neon_conv_real8:
|
||||
push {r4-r5, lr}
|
||||
vpush {q4-q7}
|
||||
vld2.32 {q0-q1}, [r1]!
|
||||
vld2.32 {q2-q3}, [r1]
|
||||
add r4, r0, #32
|
||||
ldr r5, =8
|
||||
.neon_conv_loop8:
|
||||
vld2.32 {q4-q5}, [r0], r5
|
||||
vld2.32 {q6-q7}, [r4], r5
|
||||
vmul.f32 q8, q4, q0
|
||||
vmul.f32 q9, q5, q0
|
||||
vmul.f32 q10, q6, q2
|
||||
vmul.f32 q11, q7, q2
|
||||
|
||||
vadd.f32 q12, q8, q10
|
||||
vadd.f32 q13, q9, q11
|
||||
|
||||
vpadd.f32 d22, d24, d25
|
||||
vpadd.f32 d23, d26, d27
|
||||
vpadd.f32 d24, d22, d23
|
||||
vst1.64 {d24}, [r2]!
|
||||
subs r3, r3, #1
|
||||
bne .neon_conv_loop8
|
||||
vpop {q4-q7}
|
||||
pop {r4-r5, pc}
|
||||
.size neon_conv_real8, .-neon_conv_real8
|
||||
.align 2
|
||||
.global neon_conv_real12
|
||||
.type neon_conv_real12, %function
|
||||
neon_conv_real12:
|
||||
push {r4-r6, lr}
|
||||
vpush {q4-q7}
|
||||
vld2.32 {q0-q1}, [r1]!
|
||||
vld2.32 {q2-q3}, [r1]!
|
||||
vld2.32 {q4-q5}, [r1]!
|
||||
add r4, r0, #32
|
||||
add r5, r0, #64
|
||||
ldr r6, =8
|
||||
.neon_conv_loop12:
|
||||
vld2.32 {q6-q7}, [r0], r6
|
||||
vld2.32 {q8-q9}, [r4], r6
|
||||
vld2.32 {q10-q11}, [r5], r6
|
||||
#ifdef HAVE_NEON_FMA
|
||||
vfma.f32 q1, q6, q0
|
||||
vfma.f32 q3, q7, q0
|
||||
vfma.f32 q1, q8, q2
|
||||
vfma.f32 q3, q9, q2
|
||||
vfma.f32 q1, q10, q4
|
||||
vfma.f32 q3, q11, q4
|
||||
#else
|
||||
vmul.f32 q12, q6, q0
|
||||
vmul.f32 q13, q7, q0
|
||||
vmul.f32 q14, q8, q2
|
||||
vmul.f32 q15, q9, q2
|
||||
vmul.f32 q1, q10, q4
|
||||
vmul.f32 q3, q11, q4
|
||||
|
||||
vadd.f32 q5, q12, q14
|
||||
vadd.f32 q6, q13, q15
|
||||
vadd.f32 q1, q5, q1
|
||||
vadd.f32 q3, q6, q3
|
||||
#endif
|
||||
vpadd.f32 d2, d2, d3
|
||||
vpadd.f32 d3, d6, d7
|
||||
vpadd.f32 d6, d2, d3
|
||||
vst1.64 {d6}, [r2]!
|
||||
subs r3, r3, #1
|
||||
bne .neon_conv_loop12
|
||||
vpop {q4-q7}
|
||||
pop {r4-r6, pc}
|
||||
.size neon_conv_real12, .-neon_conv_real12
|
||||
.align 2
|
||||
.global neon_conv_real16
|
||||
.type neon_conv_real16, %function
|
||||
neon_conv_real16:
|
||||
push {r4-r7, lr}
|
||||
vpush {q4-q7}
|
||||
vld2.32 {q0-q1}, [r1]!
|
||||
vld2.32 {q2-q3}, [r1]!
|
||||
vld2.32 {q4-q5}, [r1]!
|
||||
vld2.32 {q6-q7}, [r1]
|
||||
add r4, r0, #32
|
||||
add r5, r0, #64
|
||||
add r6, r0, #96
|
||||
ldr r7, =8
|
||||
.neon_conv_loop16:
|
||||
vld2.32 {q8-q9}, [r0], r7
|
||||
vld2.32 {q10-q11}, [r4], r7
|
||||
vld2.32 {q12-q13}, [r5], r7
|
||||
vld2.32 {q14-q15}, [r6], r7
|
||||
#ifdef HAVE_NEON_FMA
|
||||
vmul.f32 q1, q8, q0
|
||||
vmul.f32 q3, q9, q0
|
||||
vfma.f32 q1, q10, q2
|
||||
vfma.f32 q3, q11, q2
|
||||
vfma.f32 q1, q12, q4
|
||||
vfma.f32 q3, q13, q4
|
||||
vfma.f32 q1, q14, q6
|
||||
vfma.f32 q3, q15, q6
|
||||
#else
|
||||
vmul.f32 q1, q8, q0
|
||||
vmul.f32 q3, q9, q0
|
||||
vmul.f32 q5, q10, q2
|
||||
vmul.f32 q7, q11, q2
|
||||
vmul.f32 q8, q12, q4
|
||||
vmul.f32 q9, q13, q4
|
||||
vmul.f32 q10, q14, q6
|
||||
vmul.f32 q11, q15, q6
|
||||
|
||||
vadd.f32 q1, q1, q5
|
||||
vadd.f32 q3, q3, q7
|
||||
vadd.f32 q5, q8, q10
|
||||
vadd.f32 q7, q9, q11
|
||||
vadd.f32 q1, q1, q5
|
||||
vadd.f32 q3, q3, q7
|
||||
#endif
|
||||
vpadd.f32 d2, d2, d3
|
||||
vpadd.f32 d3, d6, d7
|
||||
vpadd.f32 d6, d2, d3
|
||||
vst1.64 {d6}, [r2]!
|
||||
subs r3, r3, #1
|
||||
bne .neon_conv_loop16
|
||||
vpop {q4-q7}
|
||||
pop {r4-r7, pc}
|
||||
.size neon_conv_real16, .-neon_conv_real16
|
||||
.align 2
|
||||
.global neon_conv_real20
|
||||
.type neon_conv_real20, %function
|
||||
neon_conv_real20:
|
||||
push {r4-r8, lr}
|
||||
vpush {q4-q7}
|
||||
vld2.32 {q0-q1}, [r1]!
|
||||
vld2.32 {q2-q3}, [r1]!
|
||||
vld2.32 {q4-q5}, [r1]!
|
||||
vld2.32 {q6-q7}, [r1]!
|
||||
vld2.32 {q8-q9}, [r1]
|
||||
add r4, r0, #32
|
||||
add r5, r0, #64
|
||||
add r6, r0, #96
|
||||
add r7, r0, #128
|
||||
ldr r8, =8
|
||||
.neon_conv_loop20:
|
||||
vld2.32 {q10-q11}, [r0], r8
|
||||
vld2.32 {q12-q13}, [r4], r8
|
||||
vld2.32 {q14-q15}, [r5], r8
|
||||
#ifdef HAVE_NEON_FMA
|
||||
vmul.f32 q1, q10, q0
|
||||
vfma.f32 q1, q12, q2
|
||||
vfma.f32 q1, q14, q4
|
||||
vmul.f32 q3, q11, q0
|
||||
vfma.f32 q3, q13, q2
|
||||
vfma.f32 q3, q15, q4
|
||||
|
||||
vld2.32 {q12-q13}, [r6], r8
|
||||
vld2.32 {q14-q15}, [r7], r8
|
||||
|
||||
vfma.f32 q1, q12, q6
|
||||
vfma.f32 q3, q13, q6
|
||||
vfma.f32 q1, q14, q8
|
||||
vfma.f32 q3, q15, q8
|
||||
#else
|
||||
vmul.f32 q1, q10, q0
|
||||
vmul.f32 q3, q12, q2
|
||||
vmul.f32 q5, q14, q4
|
||||
vmul.f32 q7, q11, q0
|
||||
vmul.f32 q9, q13, q2
|
||||
vmul.f32 q10, q15, q4
|
||||
vadd.f32 q1, q1, q3
|
||||
vadd.f32 q3, q7, q9
|
||||
vadd.f32 q9, q1, q5
|
||||
vadd.f32 q10, q3, q10
|
||||
|
||||
vld2.32 {q12-q13}, [r6], r8
|
||||
vld2.32 {q14-q15}, [r7], r8
|
||||
|
||||
vmul.f32 q1, q12, q6
|
||||
vmul.f32 q3, q13, q6
|
||||
vmul.f32 q5, q14, q8
|
||||
vmul.f32 q7, q15, q8
|
||||
vadd.f32 q12, q1, q9
|
||||
vadd.f32 q14, q3, q10
|
||||
vadd.f32 q1, q12, q5
|
||||
vadd.f32 q3, q14, q7
|
||||
#endif
|
||||
vpadd.f32 d2, d2, d3
|
||||
vpadd.f32 d3, d6, d7
|
||||
vpadd.f32 d6, d2, d3
|
||||
vst1.64 {d6}, [r2]!
|
||||
subs r3, r3, #1
|
||||
bne .neon_conv_loop20
|
||||
vpop {q4-q7}
|
||||
pop {r4-r8, pc}
|
||||
.size neon_conv_real20, .-neon_conv_real20
|
||||
.align 2
|
||||
.global mac_cx_neon4
|
||||
.type mac_cx_neon4, %function
|
||||
mac_cx_neon4:
|
||||
push {r4, lr}
|
||||
ldr r4, =32
|
||||
veor q14, q14
|
||||
veor q15, q15
|
||||
.neon_conv_loop_mac4:
|
||||
vld2.32 {q0-q1}, [r0], r4
|
||||
vld2.32 {q2-q3}, [r1]!
|
||||
|
||||
vmul.f32 q10, q0, q2
|
||||
vmul.f32 q11, q1, q3
|
||||
vmul.f32 q12, q0, q3
|
||||
vmul.f32 q13, q2, q1
|
||||
vsub.f32 q8, q10, q11
|
||||
vadd.f32 q9, q12, q13
|
||||
|
||||
vadd.f32 q14, q8
|
||||
vadd.f32 q15, q9
|
||||
subs r3, #1
|
||||
bne .neon_conv_loop_mac4
|
||||
|
||||
vld1.64 d0, [r2]
|
||||
vpadd.f32 d28, d28, d29
|
||||
vpadd.f32 d30, d30, d31
|
||||
vpadd.f32 d1, d28, d30
|
||||
vadd.f32 d1, d0
|
||||
vst1.64 d1, [r2]
|
||||
pop {r4, pc}
|
||||
.size mac_cx_neon4, .-mac_cx_neon4
|
||||
.section .note.GNU-stack,"",%progbits
|
56
Transceiver52M/arm/mult.c
Normal file
56
Transceiver52M/arm/mult.c
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* NEON scaling
|
||||
* 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 <mult.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
void neon_cmplx_mul_4n(float *, float *, float *, int);
|
||||
|
||||
static void cmplx_mul_ps(float *out, float *a, float *b, int len)
|
||||
{
|
||||
float ai, aq, bi, bq;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
ai = a[2 * i + 0];
|
||||
aq = a[2 * i + 1];
|
||||
|
||||
bi = b[2 * i + 0];
|
||||
bq = b[2 * i + 1];
|
||||
|
||||
out[2 * i + 0] = ai * bi - aq * bq;
|
||||
out[2 * i + 1] = ai * bq + aq * bi;
|
||||
}
|
||||
}
|
||||
|
||||
void mul_complex(float *out, float *a, float *b, int len)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if (len % 4)
|
||||
cmplx_mul_ps(out, a, b, len);
|
||||
else
|
||||
neon_cmplx_mul_4n(out, a, b, len >> 2);
|
||||
#else
|
||||
cmplx_mul_ps(out, a, b, len);
|
||||
#endif
|
||||
}
|
42
Transceiver52M/arm/mult_neon.S
Normal file
42
Transceiver52M/arm/mult_neon.S
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* NEON complex multiplication
|
||||
* 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
|
||||
*/
|
||||
|
||||
.syntax unified
|
||||
.text
|
||||
.align 2
|
||||
.global neon_cmplx_mul_4n
|
||||
.type neon_cmplx_mul_4n, %function
|
||||
neon_cmplx_mul_4n:
|
||||
vpush {q4-q7}
|
||||
.loop_mul:
|
||||
vld2.32 {q0-q1}, [r1]!
|
||||
vld2.32 {q2-q3}, [r2]!
|
||||
vmul.f32 q4, q0, q2
|
||||
vmul.f32 q5, q1, q3
|
||||
vmul.f32 q6, q0, q3
|
||||
vmul.f32 q7, q2, q1
|
||||
vsub.f32 q8, q4, q5
|
||||
vadd.f32 q9, q6, q7
|
||||
vst2.32 {q8-q9}, [r0]!
|
||||
subs r3, #1
|
||||
bne .loop_mul
|
||||
vpop {q4-q7}
|
||||
bx lr
|
||||
.size neon_cmplx_mul_4n, .-neon_cmplx_mul_4n
|
||||
.section .note.GNU-stack,"",%progbits
|
56
Transceiver52M/arm/scale.c
Normal file
56
Transceiver52M/arm/scale.c
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* NEON scaling
|
||||
* 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 <scale.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
void neon_scale_4n(float *, float *, float *, int);
|
||||
|
||||
static void scale_ps(float *out, float *in, float *scale, int len)
|
||||
{
|
||||
float ai, aq, bi, bq;
|
||||
|
||||
bi = scale[0];
|
||||
bq = scale[1];
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
ai = in[2 * i + 0];
|
||||
aq = in[2 * i + 1];
|
||||
|
||||
out[2 * i + 0] = ai * bi - aq * bq;
|
||||
out[2 * i + 1] = ai * bq + aq * bi;
|
||||
}
|
||||
}
|
||||
|
||||
void scale_complex(float *out, float *in, float* scale, int len)
|
||||
{
|
||||
#ifdef HAVE_NEON
|
||||
if (len % 4)
|
||||
scale_ps(out, in, scale, len);
|
||||
else
|
||||
neon_scale_4n(in, scale, out, len >> 2);
|
||||
#else
|
||||
scale_ps(out, in, scale, len);
|
||||
#endif
|
||||
}
|
50
Transceiver52M/arm/scale_neon.S
Normal file
50
Transceiver52M/arm/scale_neon.S
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* ARM NEON Scaling
|
||||
* 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
|
||||
*/
|
||||
|
||||
.syntax unified
|
||||
.text
|
||||
.align 2
|
||||
.global neon_scale_4n
|
||||
.type neon_scale_4n, %function
|
||||
neon_scale_4n:
|
||||
push {r4, lr}
|
||||
ldr r4, =32
|
||||
|
||||
vld1.64 d0, [r1]
|
||||
vmov.32 s4, s1
|
||||
vmov.32 s1, s0
|
||||
vmov.64 d1, d0
|
||||
vmov.32 s5, s4
|
||||
vmov.64 d3, d2
|
||||
.loop_mul_const:
|
||||
vld2.32 {q2-q3}, [r0], r4
|
||||
|
||||
vmul.f32 q8, q0, q2
|
||||
vmul.f32 q9, q1, q3
|
||||
vmul.f32 q10, q0, q3
|
||||
vmul.f32 q11, q1, q2
|
||||
vsub.f32 q8, q8, q9
|
||||
vadd.f32 q9, q10, q11
|
||||
|
||||
vst2.32 {q8-q9}, [r2]!
|
||||
subs r3, #1
|
||||
bne .loop_mul_const
|
||||
pop {r4, pc}
|
||||
.size neon_scale_4n, .-neon_scale_4n
|
||||
.section .note.GNU-stack,"",%progbits
|
7
Transceiver52M/common/convert.h
Normal file
7
Transceiver52M/common/convert.h
Normal 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_ */
|
30
Transceiver52M/common/convolve.h
Normal file
30
Transceiver52M/common/convolve.h
Normal 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_ */
|
156
Transceiver52M/common/convolve_base.c
Normal file
156
Transceiver52M/common/convolve_base.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/* 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 */
|
||||
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 */
|
||||
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 */
|
||||
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: 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
|
||||
}
|
6
Transceiver52M/common/mult.h
Normal file
6
Transceiver52M/common/mult.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef _MULT_H_
|
||||
#define _MULT_H_
|
||||
|
||||
void mul_complex(float *out, float *a, float *b, int len);
|
||||
|
||||
#endif /* _MULT_H_ */
|
6
Transceiver52M/common/scale.h
Normal file
6
Transceiver52M/common/scale.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef _SCALE_H_
|
||||
#define _SCALE_H_
|
||||
|
||||
void scale_complex(float *out, float *in, float *scale, int len);
|
||||
|
||||
#endif /* _SCALE_H_ */
|
83
Transceiver52M/laurent.m
Normal file
83
Transceiver52M/laurent.m
Normal file
@@ -0,0 +1,83 @@
|
||||
%
|
||||
% Laurent decomposition of GMSK signals
|
||||
% Generates C0, C1, and C2 pulse shapes
|
||||
%
|
||||
% Pierre Laurent, "Exact and Approximate Construction of Digital Phase
|
||||
% Modulations by Superposition of Amplitude Modulated Pulses", IEEE
|
||||
% Transactions of Communications, Vol. 34, No. 2, Feb 1986.
|
||||
%
|
||||
% Author: Thomas Tsou <tom@tsou.cc>
|
||||
%
|
||||
|
||||
% Modulation parameters
|
||||
oversamp = 16;
|
||||
L = 3;
|
||||
f = 270.83333e3;
|
||||
T = 1/f;
|
||||
h = 0.5;
|
||||
BT = 0.30;
|
||||
B = BT / T;
|
||||
|
||||
% Generate sampling points for L symbol periods
|
||||
t = -(L*T/2):T/oversamp:(L*T/2);
|
||||
t = t(1:end-1) + (T/oversamp/2);
|
||||
|
||||
% Generate Gaussian pulse
|
||||
g = qfunc(2*pi*B*(t - T/2)/(log(2)^.5)) - qfunc(2*pi*B*(t + T/2)/(log(2)^.5));
|
||||
g = g / sum(g) * pi/2;
|
||||
g = [0 g];
|
||||
|
||||
% Integrate phase
|
||||
q = 0;
|
||||
for i = 1:size(g,2);
|
||||
q(i) = sum(g(1:i));
|
||||
end
|
||||
|
||||
% Compute two sided "generalized phase pulse" function
|
||||
s = 0;
|
||||
for i = 1:size(g,2);
|
||||
s(i) = sin(q(i)) / sin(pi*h);
|
||||
end
|
||||
for i = (size(g,2) + 1):(2 * size(g,2) - 1);
|
||||
s(i) = sin(pi*h - q(i - (size(g,2) - 1))) / sin(pi*h);
|
||||
end
|
||||
|
||||
% Compute C0 pulse: valid for all L values
|
||||
c0 = s(1:end-(oversamp*(L-1)));
|
||||
for i = 1:L-1;
|
||||
c0 = c0 .* s((1 + i*oversamp):end-(oversamp*(L - 1 - i)));
|
||||
end
|
||||
|
||||
% Compute C1 pulse: valid for L = 3 only!
|
||||
% C1 = S0 * S4 * S2
|
||||
c1 = s(1:end-(oversamp*(4)));
|
||||
c1 = c1 .* s((1 + 4*oversamp):end-(oversamp*(4 - 1 - 3)));
|
||||
c1 = c1 .* s((1 + 2*oversamp):end-(oversamp*(4 - 1 - 1)));
|
||||
|
||||
% Compute C2 pulse: valid for L = 3 only!
|
||||
% C2 = S0 * S1 * S5
|
||||
c2 = s(1:end-(oversamp*(5)));
|
||||
c2 = c2 .* s((1 + 1*oversamp):end-(oversamp*(5 - 1 - 0)));
|
||||
c2 = c2 .* s((1 + 5*oversamp):end-(oversamp*(5 - 1 - 4)));
|
||||
|
||||
% Plot C0, C1, C2 Laurent pulse series
|
||||
figure(1);
|
||||
hold off;
|
||||
plot((0:size(c0,2)-1)/oversamp - 2,c0, 'b');
|
||||
hold on;
|
||||
plot((0:size(c1,2)-1)/oversamp - 2,c1, 'r');
|
||||
plot((0:size(c2,2)-1)/oversamp - 2,c2, 'g');
|
||||
|
||||
% Generate OpenBTS pulse
|
||||
numSamples = size(c0,2);
|
||||
centerPoint = (numSamples - 1)/2;
|
||||
i = ((0:numSamples) - centerPoint) / oversamp;
|
||||
xP = .96*exp(-1.1380*i.^2 - 0.527*i.^4);
|
||||
xP = xP / max(xP) * max(c0);
|
||||
|
||||
% Plot C0 pulse compared to OpenBTS pulse
|
||||
figure(2);
|
||||
hold off;
|
||||
plot((0:size(c0,2)-1)/oversamp, c0, 'b');
|
||||
hold on;
|
||||
plot((0:size(xP,2)-1)/oversamp, xP, 'r');
|
431
Transceiver52M/osmo-trx.cpp
Normal file
431
Transceiver52M/osmo-trx.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "Transceiver.h"
|
||||
#include "radioDevice.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <GSMCommon.h>
|
||||
#include <Logger.h>
|
||||
#include <Configuration.h>
|
||||
|
||||
/* Samples-per-symbol for downlink path
|
||||
* 4 - Uses precision modulator (more computation, less distortion)
|
||||
* 1 - Uses minimized modulator (less computation, more distortion)
|
||||
*
|
||||
* Other values are invalid. Receive path (uplink) is always
|
||||
* downsampled to 1 sps. Default to 4 sps for all cases except for
|
||||
* ARM and non-SIMD enabled architectures.
|
||||
*/
|
||||
#if defined(HAVE_NEON) || !defined(HAVE_SSE3)
|
||||
#define DEFAULT_SPS 1
|
||||
#else
|
||||
#define DEFAULT_SPS 4
|
||||
#endif
|
||||
|
||||
/* Default configuration parameters
|
||||
* Note that these values are only used if the particular key does not
|
||||
* exist in the configuration database. IP port and address values will
|
||||
* typically be overwritten by the OpenBTS.db values. Other values will
|
||||
* not be in the database by default.
|
||||
*/
|
||||
#define DEFAULT_TRX_PORT 5700
|
||||
#define DEFAULT_TRX_IP "127.0.0.1"
|
||||
#define DEFAULT_EXTREF false
|
||||
#define DEFAULT_DIVERSITY false
|
||||
#define DEFAULT_CHANS 1
|
||||
|
||||
struct trx_config {
|
||||
std::string log_level;
|
||||
std::string addr;
|
||||
std::string dev_args;
|
||||
unsigned port;
|
||||
unsigned sps;
|
||||
unsigned chans;
|
||||
bool extref;
|
||||
bool filler;
|
||||
bool diversity;
|
||||
bool ms;
|
||||
double offset;
|
||||
};
|
||||
|
||||
ConfigurationTable gConfig;
|
||||
|
||||
volatile bool gshutdown = false;
|
||||
|
||||
/* Run sanity check on configuration table
|
||||
* The global table constructor cannot provide notification in the
|
||||
* event of failure. Make sure that we can access the database,
|
||||
* write to it, and that it contains the bare minimum required keys.
|
||||
*/
|
||||
bool testConfig()
|
||||
{
|
||||
int val = 9999;
|
||||
std::string test = "asldfkjsaldkf";
|
||||
const char *key = "Log.Level";
|
||||
|
||||
/* Attempt to query */
|
||||
try {
|
||||
gConfig.getStr(key);
|
||||
} catch (...) {
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Config: Failed query required key " << key
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Attempt to set a test value in the global config */
|
||||
if (!gConfig.set(test, val)) {
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Config: Failed to set test key" << std::endl;
|
||||
return false;
|
||||
} else {
|
||||
gConfig.remove(test);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Setup configuration values
|
||||
* Don't query the existence of the Log.Level because it's a
|
||||
* mandatory value. That is, if it doesn't exist, the configuration
|
||||
* table will crash or will have already crashed. Everything else we
|
||||
* can survive without and use default values if the database entries
|
||||
* are empty.
|
||||
*/
|
||||
bool trx_setup_config(struct trx_config *config)
|
||||
{
|
||||
std::string refstr, fillstr, divstr, msstr;
|
||||
|
||||
if (!testConfig())
|
||||
return false;
|
||||
|
||||
if (config->log_level == "")
|
||||
config->log_level = gConfig.getStr("Log.Level");
|
||||
|
||||
if (!config->port) {
|
||||
if (gConfig.defines("TRX.Port"))
|
||||
config->port = gConfig.getNum("TRX.Port");
|
||||
else
|
||||
config->port = DEFAULT_TRX_PORT;
|
||||
}
|
||||
|
||||
if (config->addr == "") {
|
||||
if (gConfig.defines("TRX.IP"))
|
||||
config->addr = gConfig.getStr("TRX.IP");
|
||||
else
|
||||
config->addr = DEFAULT_TRX_IP;
|
||||
}
|
||||
|
||||
if (!config->extref) {
|
||||
if (gConfig.defines("TRX.Reference"))
|
||||
config->extref = gConfig.getNum("TRX.Reference");
|
||||
else
|
||||
config->extref = DEFAULT_EXTREF;
|
||||
}
|
||||
|
||||
if (!config->diversity) {
|
||||
if (gConfig.defines("TRX.Diversity"))
|
||||
config->diversity = gConfig.getNum("TRX.Diversity");
|
||||
else
|
||||
config->diversity = DEFAULT_DIVERSITY;
|
||||
}
|
||||
|
||||
if (!config->sps)
|
||||
config->sps = DEFAULT_SPS;
|
||||
|
||||
if (!config->chans)
|
||||
config->chans = DEFAULT_CHANS;
|
||||
|
||||
/* Diversity only supported on 2 channels */
|
||||
if (config->diversity)
|
||||
config->chans = 2;
|
||||
|
||||
refstr = config->extref ? "Enabled" : "Disabled";
|
||||
fillstr = config->filler ? "Enabled" : "Disabled";
|
||||
divstr = config->diversity ? "Enabled" : "Disabled";
|
||||
msstr = config->ms ? "Enabled" : "Disabled";
|
||||
|
||||
std::ostringstream ost("");
|
||||
ost << "Config Settings" << std::endl;
|
||||
ost << " Log Level............... " << config->log_level << std::endl;
|
||||
ost << " Device args............. " << config->dev_args << std::endl;
|
||||
ost << " TRX Base Port........... " << config->port << std::endl;
|
||||
ost << " TRX Address............. " << config->addr << std::endl;
|
||||
ost << " Channels................ " << config->chans << std::endl;
|
||||
ost << " Samples-per-Symbol...... " << config->sps << std::endl;
|
||||
ost << " External Reference...... " << refstr << std::endl;
|
||||
ost << " C0 Filler Table......... " << fillstr << std::endl;
|
||||
ost << " Diversity............... " << divstr << std::endl;
|
||||
ost << " MS Mode................. " << msstr << std::endl;
|
||||
ost << " Tuning offset........... " << config->offset << std::endl;
|
||||
std::cout << ost << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Create radio interface
|
||||
* The interface consists of sample rate changes, frequency shifts,
|
||||
* channel multiplexing, and other conversions. The transceiver core
|
||||
* accepts input vectors sampled at multiples of the GSM symbol rate.
|
||||
* The radio interface connects the main transceiver with the device
|
||||
* object, which may be operating some other rate.
|
||||
*/
|
||||
RadioInterface *makeRadioInterface(struct trx_config *config,
|
||||
RadioDevice *usrp, int type)
|
||||
{
|
||||
RadioInterface *radio = NULL;
|
||||
size_t div = 1;
|
||||
int offset = 3;
|
||||
|
||||
if (config->ms) {
|
||||
if (type != RadioDevice::NORMAL) {
|
||||
LOG(ALERT) << "Unsupported configuration";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
offset *= -1;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case RadioDevice::NORMAL:
|
||||
radio = new RadioInterface(usrp, config->sps,
|
||||
config->chans, div, offset);
|
||||
break;
|
||||
case RadioDevice::RESAMP_64M:
|
||||
case RadioDevice::RESAMP_100M:
|
||||
radio = new RadioInterfaceResamp(usrp,
|
||||
config->sps, config->chans);
|
||||
break;
|
||||
case RadioDevice::DIVERSITY:
|
||||
radio = new RadioInterfaceDiversity(usrp,
|
||||
config->sps, config->chans);
|
||||
break;
|
||||
default:
|
||||
LOG(ALERT) << "Unsupported radio interface configuration";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!radio->init(type)) {
|
||||
LOG(ALERT) << "Failed to initialize radio interface";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return radio;
|
||||
}
|
||||
|
||||
/* Create transceiver core
|
||||
* The multi-threaded modem core operates at multiples of the GSM rate of
|
||||
* 270.8333 ksps and consists of GSM specific modulation, demodulation,
|
||||
* and decoding schemes. Also included are the socket interfaces for
|
||||
* connecting to the upper layer stack.
|
||||
*/
|
||||
Transceiver *makeTransceiver(struct trx_config *config, RadioInterface *radio)
|
||||
{
|
||||
Transceiver *trx;
|
||||
VectorFIFO *fifo;
|
||||
|
||||
trx = new Transceiver(config->port, config->addr.c_str(), config->sps,
|
||||
config->chans, GSM::Time(3,0), radio);
|
||||
if (!trx->init(config->filler)) {
|
||||
LOG(ALERT) << "Failed to initialize transceiver";
|
||||
delete trx;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < config->chans; i++) {
|
||||
fifo = radio->receiveFIFO(i);
|
||||
if (fifo && trx->receiveFIFO(fifo, i))
|
||||
continue;
|
||||
|
||||
LOG(ALERT) << "Could not attach FIFO to channel " << i;
|
||||
delete trx;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return trx;
|
||||
}
|
||||
|
||||
static void sig_handler(int signo)
|
||||
{
|
||||
fprintf(stdout, "Received shutdown signal");
|
||||
gshutdown = true;
|
||||
}
|
||||
|
||||
static void setup_signal_handlers()
|
||||
{
|
||||
if (signal(SIGINT, sig_handler) == SIG_ERR) {
|
||||
fprintf(stderr, "Failed to install SIGINT signal handler\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (signal(SIGTERM, sig_handler) == SIG_ERR) {
|
||||
fprintf(stderr, "Couldn't install SIGTERM signal handler\n");
|
||||
exit( EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
fprintf(stdout, "Options:\n"
|
||||
" -h This text\n"
|
||||
" -a UHD device args\n"
|
||||
" -l Logging level (%s)\n"
|
||||
" -i IP address of GSM core\n"
|
||||
" -p Base port number\n"
|
||||
" -d Enable dual channel diversity receiver\n"
|
||||
" -x Enable external 10 MHz reference\n"
|
||||
" -s Samples-per-symbol (1 or 4)\n"
|
||||
" -c Number of ARFCN channels (default=1)\n"
|
||||
" -f Enable C0 filler table\n"
|
||||
" -m Enable MS mode\n"
|
||||
" -o Set baseband frequency offset (default=auto)\n",
|
||||
"EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG");
|
||||
}
|
||||
|
||||
static void handle_options(int argc, char **argv, struct trx_config *config)
|
||||
{
|
||||
int option;
|
||||
|
||||
config->port = 0;
|
||||
config->sps = 0;
|
||||
config->chans = 0;
|
||||
config->extref = false;
|
||||
config->filler = false;
|
||||
config->diversity = false;
|
||||
config->ms = false;
|
||||
config->offset = 0.0;
|
||||
|
||||
while ((option = getopt(argc, argv, "ha:l:i:p:c:dxfo:ms:")) != -1) {
|
||||
switch (option) {
|
||||
case 'h':
|
||||
print_help();
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
config->dev_args = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
config->log_level = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
config->addr = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
config->port = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
config->chans = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
config->diversity = true;
|
||||
break;
|
||||
case 'x':
|
||||
config->extref = true;
|
||||
break;
|
||||
case 'f':
|
||||
config->filler = true;
|
||||
break;
|
||||
case 'o':
|
||||
config->offset = atof(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
config->ms = true;
|
||||
break;
|
||||
case 's':
|
||||
config->sps = atoi(optarg);
|
||||
if ((config->sps != 1) && (config->sps != 4)) {
|
||||
printf("Unsupported samples-per-symbol\n\n");
|
||||
print_help();
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print_help();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int type, chans;
|
||||
RadioDevice *usrp;
|
||||
RadioInterface *radio = NULL;
|
||||
Transceiver *trx = NULL;
|
||||
struct trx_config config;
|
||||
|
||||
handle_options(argc, argv, &config);
|
||||
|
||||
setup_signal_handlers();
|
||||
|
||||
/* Check database sanity */
|
||||
if (!trx_setup_config(&config)) {
|
||||
std::cerr << "Config: Database failure - exiting" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7);
|
||||
|
||||
srandom(time(NULL));
|
||||
|
||||
/* Create the low level device object */
|
||||
usrp = RadioDevice::make(config.sps, config.chans,
|
||||
config.diversity, config.offset);
|
||||
type = usrp->open(config.dev_args, config.extref);
|
||||
if (type < 0) {
|
||||
LOG(ALERT) << "Failed to create radio device" << std::endl;
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
/* Setup the appropriate device interface */
|
||||
radio = makeRadioInterface(&config, usrp, type);
|
||||
if (!radio)
|
||||
goto shutdown;
|
||||
|
||||
/* Create the transceiver core */
|
||||
trx = makeTransceiver(&config, radio);
|
||||
if (!trx)
|
||||
goto shutdown;
|
||||
|
||||
trx->start();
|
||||
|
||||
chans = trx->numChans();
|
||||
std::cout << "-- Transceiver active with "
|
||||
<< chans << " channel(s)" << std::endl;
|
||||
|
||||
while (!gshutdown)
|
||||
sleep(1);
|
||||
|
||||
shutdown:
|
||||
std::cout << "Shutting down transceiver..." << std::endl;
|
||||
|
||||
delete trx;
|
||||
delete radio;
|
||||
delete usrp;
|
||||
|
||||
return 0;
|
||||
}
|
@@ -29,6 +29,40 @@ void RadioClock::set(const GSM::Time& wTime)
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
GSM::Time RadioClock::adjust(GSM::Time &wBase, GSM::Time &wOffset)
|
||||
{
|
||||
int tn_diff, fn_diff = 0;
|
||||
|
||||
/* Modulo TN adustment */
|
||||
tn_diff = wBase.TN() + wOffset.TN();
|
||||
if (tn_diff < 0) {
|
||||
tn_diff += 8;
|
||||
fn_diff--;
|
||||
} else if (tn_diff >= 8) {
|
||||
tn_diff -= 8;
|
||||
fn_diff++;
|
||||
}
|
||||
|
||||
/* Modulo FN adjustment */
|
||||
fn_diff += wBase.FN() + wOffset.FN();
|
||||
if (fn_diff < 0)
|
||||
fn_diff += GSM::gHyperframe;
|
||||
else if ((unsigned) fn_diff >= GSM::gHyperframe)
|
||||
fn_diff = fn_diff - GSM::gHyperframe;
|
||||
|
||||
return GSM::Time(fn_diff, tn_diff);
|
||||
}
|
||||
|
||||
void RadioClock::adjust(GSM::Time& wOffset)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
mClock = adjust(mClock, wOffset);
|
||||
updateSignal.signal();
|
||||
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
void RadioClock::incTN()
|
||||
{
|
||||
mLock.lock();
|
||||
|
@@ -26,7 +26,10 @@
|
||||
|
||||
class RadioClock {
|
||||
public:
|
||||
static GSM::Time adjust(GSM::Time &base, GSM::Time &offset);
|
||||
|
||||
void set(const GSM::Time& wTime);
|
||||
void adjust(GSM::Time &wOffset);
|
||||
void incTN();
|
||||
GSM::Time get();
|
||||
void wait();
|
||||
|
@@ -16,11 +16,14 @@
|
||||
#define __RADIO_DEVICE_H__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define GSMRATE 1625e3/6
|
||||
|
||||
/** a 64-bit virtual timestamp for radio data */
|
||||
typedef unsigned long long TIMESTAMP;
|
||||
|
||||
@@ -29,12 +32,18 @@ class RadioDevice {
|
||||
|
||||
public:
|
||||
/* Available transport bus types */
|
||||
enum busType { USB, NET };
|
||||
enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED };
|
||||
|
||||
static RadioDevice *make(double desiredSampleRate, bool skipRx = false);
|
||||
/* Radio interface types */
|
||||
enum RadioInterfaceType { NORMAL, RESAMP_64M, RESAMP_100M, DIVERSITY };
|
||||
|
||||
static RadioDevice *make(size_t sps, size_t chans = 1,
|
||||
bool diversity = false, double offset = 0.0);
|
||||
|
||||
/** Initialize the USRP */
|
||||
virtual bool open(const std::string &args)=0;
|
||||
virtual int open(const std::string &args = "", bool extref = false)=0;
|
||||
|
||||
virtual ~RadioDevice() { }
|
||||
|
||||
/** Start the USRP */
|
||||
virtual bool start()=0;
|
||||
@@ -42,11 +51,11 @@ class RadioDevice {
|
||||
/** Stop the USRP */
|
||||
virtual bool stop()=0;
|
||||
|
||||
/** Get the bus type */
|
||||
virtual enum busType getBus()=0;
|
||||
/** Get the Tx window type */
|
||||
virtual enum TxWindowType getWindowType()=0;
|
||||
|
||||
/** Enable thread priority */
|
||||
virtual void setPriority()=0;
|
||||
virtual void setPriority(float prio = 0.5) = 0;
|
||||
|
||||
/**
|
||||
Read samples from the radio.
|
||||
@@ -58,10 +67,9 @@ class RadioDevice {
|
||||
@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,
|
||||
TIMESTAMP timestamp = 0xffffffff,
|
||||
bool *underrun = 0,
|
||||
unsigned *RSSI = 0)=0;
|
||||
virtual int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
|
||||
TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0,
|
||||
unsigned *RSSI = 0) = 0;
|
||||
/**
|
||||
Write samples to the radio.
|
||||
@param buf Contains the data to be written.
|
||||
@@ -71,18 +79,20 @@ class RadioDevice {
|
||||
@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,
|
||||
TIMESTAMP timestamp,
|
||||
bool isControl=false)=0;
|
||||
|
||||
virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
|
||||
TIMESTAMP timestamp, bool isControl = false) = 0;
|
||||
|
||||
/** Update the alignment between the read and write timestamps */
|
||||
virtual bool updateAlignment(TIMESTAMP timestamp)=0;
|
||||
|
||||
|
||||
/** Set the transmitter frequency */
|
||||
virtual bool setTxFreq(double wFreq)=0;
|
||||
virtual bool setTxFreq(double wFreq, size_t chan = 0) = 0;
|
||||
|
||||
/** Set the receiver frequency */
|
||||
virtual bool setRxFreq(double wFreq)=0;
|
||||
virtual bool setRxFreq(double wFreq, size_t chan = 0) = 0;
|
||||
|
||||
/** Adjust the receiver offset */
|
||||
virtual bool setRxOffset(double wOffset, size_t chan = 0) = 0;
|
||||
|
||||
/** Returns the starting write Timestamp*/
|
||||
virtual TIMESTAMP initialWriteTimestamp(void)=0;
|
||||
@@ -97,10 +107,10 @@ class RadioDevice {
|
||||
virtual double fullScaleOutputValue()=0;
|
||||
|
||||
/** sets the receive chan gain, returns the gain setting **/
|
||||
virtual double setRxGain(double dB)=0;
|
||||
virtual double setRxGain(double dB, size_t chan = 0) = 0;
|
||||
|
||||
/** gets the current receive gain **/
|
||||
virtual double getRxGain(void)=0;
|
||||
virtual double getRxGain(size_t chan = 0) = 0;
|
||||
|
||||
/** return maximum Rx Gain **/
|
||||
virtual double maxRxGain(void) = 0;
|
||||
@@ -109,7 +119,7 @@ class RadioDevice {
|
||||
virtual double minRxGain(void) = 0;
|
||||
|
||||
/** sets the transmit chan gain, returns the gain setting **/
|
||||
virtual double setTxGain(double dB)=0;
|
||||
virtual double setTxGain(double dB, size_t chan = 0) = 0;
|
||||
|
||||
/** return maximum Tx Gain **/
|
||||
virtual double maxTxGain(void) = 0;
|
||||
@@ -118,8 +128,8 @@ class RadioDevice {
|
||||
virtual double minTxGain(void) = 0;
|
||||
|
||||
/** Return internal status values */
|
||||
virtual double getTxFreq()=0;
|
||||
virtual double getRxFreq()=0;
|
||||
virtual double getTxFreq(size_t chan = 0) = 0;
|
||||
virtual double getRxFreq(size_t chan = 0) = 0;
|
||||
virtual double getSampleRate()=0;
|
||||
virtual double numberRead()=0;
|
||||
virtual double numberWritten()=0;
|
||||
|
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Radio device I/O interface
|
||||
* Written by Thomas Tsou <ttsou@vt.edu>
|
||||
*
|
||||
* Copyright 2011 Free Software Foundation, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero 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 <radioInterface.h>
|
||||
#include <Logger.h>
|
||||
|
||||
/* Device side buffers */
|
||||
static short rx_buf[OUTCHUNK * 2 * 2];
|
||||
static short tx_buf[INCHUNK * 2 * 2];
|
||||
|
||||
/* Complex float to short conversion */
|
||||
static int float_to_short(short *shrt_out, float *flt_in, int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
shrt_out[2 * i + 0] = flt_in[2 * i + 0];
|
||||
shrt_out[2 * i + 1] = flt_in[2 * i + 1];
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Comlpex short to float conversion */
|
||||
static int short_to_float(float *flt_out, short *shrt_in, int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
flt_out[2 * i + 0] = shrt_in[2 * i + 0];
|
||||
flt_out[2 * i + 1] = shrt_in[2 * i + 1];
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Receive a timestamped chunk from the device */
|
||||
void RadioInterface::pullBuffer()
|
||||
{
|
||||
bool local_underrun;
|
||||
|
||||
/* Read samples. Fail if we don't get what we want. */
|
||||
int num_rd = mRadio->readSamples(rx_buf, OUTCHUNK, &overrun,
|
||||
readTimestamp, &local_underrun);
|
||||
|
||||
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
|
||||
assert(num_rd == OUTCHUNK);
|
||||
|
||||
underrun |= local_underrun;
|
||||
readTimestamp += (TIMESTAMP) num_rd;
|
||||
|
||||
short_to_float(rcvBuffer + 2 * rcvCursor, rx_buf, num_rd);
|
||||
rcvCursor += num_rd;
|
||||
}
|
||||
|
||||
/* Send timestamped chunk to the device with arbitrary size */
|
||||
void RadioInterface::pushBuffer()
|
||||
{
|
||||
if (sendCursor < INCHUNK)
|
||||
return;
|
||||
|
||||
float_to_short(tx_buf, sendBuffer, sendCursor);
|
||||
|
||||
/* Write samples. Fail if we don't get what we want. */
|
||||
int num_smpls = mRadio->writeSamples(tx_buf,
|
||||
sendCursor,
|
||||
&underrun,
|
||||
writeTimestamp);
|
||||
assert(num_smpls == sendCursor);
|
||||
|
||||
writeTimestamp += (TIMESTAMP) num_smpls;
|
||||
sendCursor = 0;
|
||||
}
|
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Radio device interface with sample rate conversion
|
||||
* Written by Thomas Tsou <ttsou@vt.edu>
|
||||
*
|
||||
* Copyright 2011 Free Software Foundation, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero 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 <radioInterface.h>
|
||||
#include <Logger.h>
|
||||
|
||||
/* New chunk sizes for resampled rate */
|
||||
#ifdef INCHUNK
|
||||
#undef INCHUNK
|
||||
#endif
|
||||
#ifdef OUTCHUNK
|
||||
#undef OUTCHUNK
|
||||
#endif
|
||||
|
||||
/* Resampling parameters */
|
||||
#define INRATE 65 * SAMPSPERSYM
|
||||
#define INHISTORY INRATE * 2
|
||||
#define INCHUNK INRATE * 9
|
||||
|
||||
#define OUTRATE 96 * SAMPSPERSYM
|
||||
#define OUTHISTORY OUTRATE * 2
|
||||
#define OUTCHUNK OUTRATE * 9
|
||||
|
||||
/* Resampler low pass filters */
|
||||
signalVector *tx_lpf = 0;
|
||||
signalVector *rx_lpf = 0;
|
||||
|
||||
/* Resampler history */
|
||||
signalVector *tx_hist = 0;
|
||||
signalVector *rx_hist = 0;
|
||||
|
||||
/* Resampler input buffer */
|
||||
signalVector *tx_vec = 0;
|
||||
signalVector *rx_vec = 0;
|
||||
|
||||
/*
|
||||
* High rate (device facing) buffers
|
||||
*
|
||||
* Transmit side samples are pushed after each burst so accomodate
|
||||
* a resampled burst plus up to a chunk left over from the previous
|
||||
* resampling operation.
|
||||
*
|
||||
* Receive side samples always pulled with a fixed size.
|
||||
*/
|
||||
short tx_buf[INCHUNK * 2 * 4];
|
||||
short rx_buf[OUTCHUNK * 2 * 2];
|
||||
|
||||
/*
|
||||
* Utilities and Conversions
|
||||
*
|
||||
* Manipulate signal vectors dynamically for two reasons. For one,
|
||||
* it's simpler. And two, it doesn't make any reasonable difference
|
||||
* relative to the high overhead generated by the resampling.
|
||||
*/
|
||||
|
||||
/* Concatenate signal vectors. Deallocate input vectors. */
|
||||
signalVector *concat(signalVector *a, signalVector *b)
|
||||
{
|
||||
signalVector *vec = new signalVector(*a, *b);
|
||||
delete a;
|
||||
delete b;
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/* Segment a signal vector. Deallocate the input vector. */
|
||||
signalVector *segment(signalVector *a, int indx, int sz)
|
||||
{
|
||||
signalVector *vec = new signalVector(sz);
|
||||
a->segmentCopyTo(*vec, indx, sz);
|
||||
delete a;
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/* Create a new signal vector from a short array. */
|
||||
signalVector *short_to_sigvec(short *smpls, size_t sz)
|
||||
{
|
||||
int i;
|
||||
signalVector *vec = new signalVector(sz);
|
||||
signalVector::iterator itr = vec->begin();
|
||||
|
||||
for (i = 0; i < sz; i++) {
|
||||
*itr++ = Complex<float>(smpls[2 * i + 0], smpls[2 * i + 1]);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/* Convert and deallocate a signal vector into a short array. */
|
||||
int sigvec_to_short(signalVector *vec, short *smpls)
|
||||
{
|
||||
int i;
|
||||
signalVector::iterator itr = vec->begin();
|
||||
|
||||
for (i = 0; i < vec->size(); i++) {
|
||||
smpls[2 * i + 0] = itr->real();
|
||||
smpls[2 * i + 1] = itr->imag();
|
||||
itr++;
|
||||
}
|
||||
delete vec;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Create a new signal vector from a float array. */
|
||||
signalVector *float_to_sigvec(float *smpls, int sz)
|
||||
{
|
||||
int i;
|
||||
signalVector *vec = new signalVector(sz);
|
||||
signalVector::iterator itr = vec->begin();
|
||||
|
||||
for (i = 0; i < sz; i++) {
|
||||
*itr++ = Complex<float>(smpls[2 * i + 0], smpls[2 * i + 1]);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/* Convert and deallocate a signal vector into a float array. */
|
||||
int sigvec_to_float(signalVector *vec, float *smpls)
|
||||
{
|
||||
int i;
|
||||
signalVector::iterator itr = vec->begin();
|
||||
|
||||
for (i = 0; i < vec->size(); i++) {
|
||||
smpls[2 * i + 0] = itr->real();
|
||||
smpls[2 * i + 1] = itr->imag();
|
||||
itr++;
|
||||
}
|
||||
delete vec;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Initialize resampling signal vectors */
|
||||
void init_resampler(signalVector **lpf,
|
||||
signalVector **buf,
|
||||
signalVector **hist,
|
||||
int tx)
|
||||
{
|
||||
int P, Q, taps, hist_len;
|
||||
float cutoff_freq;
|
||||
|
||||
if (tx) {
|
||||
LOG(INFO) << "Initializing Tx resampler";
|
||||
P = OUTRATE;
|
||||
Q = INRATE;
|
||||
taps = 651;
|
||||
hist_len = INHISTORY;
|
||||
} else {
|
||||
LOG(INFO) << "Initializing Rx resampler";
|
||||
P = INRATE;
|
||||
Q = OUTRATE;
|
||||
taps = 961;
|
||||
hist_len = OUTHISTORY;
|
||||
}
|
||||
|
||||
if (!*lpf) {
|
||||
cutoff_freq = (P < Q) ? (1.0/(float) Q) : (1.0/(float) P);
|
||||
*lpf = createLPF(cutoff_freq, taps, P);
|
||||
}
|
||||
|
||||
if (!*buf) {
|
||||
*buf = new signalVector();
|
||||
}
|
||||
|
||||
if (!*hist);
|
||||
*hist = new signalVector(hist_len);
|
||||
}
|
||||
|
||||
/* Resample a signal vector
|
||||
*
|
||||
* The input vector is deallocated and the pointer returned with a vector
|
||||
* of any unconverted samples.
|
||||
*/
|
||||
signalVector *resmpl_sigvec(signalVector *hist, signalVector **vec,
|
||||
signalVector *lpf, double in_rate,
|
||||
double out_rate, int chunk_sz)
|
||||
{
|
||||
signalVector *resamp_vec;
|
||||
int num_chunks = (*vec)->size() / chunk_sz;
|
||||
|
||||
/* Truncate to a chunk multiple */
|
||||
signalVector trunc_vec(num_chunks * chunk_sz);
|
||||
(*vec)->segmentCopyTo(trunc_vec, 0, num_chunks * chunk_sz);
|
||||
|
||||
/* Update sample buffer with remainder */
|
||||
*vec = segment(*vec, trunc_vec.size(), (*vec)->size() - trunc_vec.size());
|
||||
|
||||
/* Add history and resample */
|
||||
signalVector input_vec(*hist, trunc_vec);
|
||||
resamp_vec = polyphaseResampleVector(input_vec, in_rate,
|
||||
out_rate, lpf);
|
||||
|
||||
/* Update history */
|
||||
trunc_vec.segmentCopyTo(*hist, trunc_vec.size() - hist->size(),
|
||||
hist->size());
|
||||
return resamp_vec;
|
||||
}
|
||||
|
||||
/* Wrapper for receive-side integer-to-float array resampling */
|
||||
int rx_resmpl_int_flt(float *smpls_out, short *smpls_in, int num_smpls)
|
||||
{
|
||||
int num_resmpld, num_chunks;
|
||||
signalVector *convert_vec, *resamp_vec, *trunc_vec;
|
||||
|
||||
if (!rx_lpf || !rx_vec || !rx_hist)
|
||||
init_resampler(&rx_lpf, &rx_vec, &rx_hist, false);
|
||||
|
||||
/* Convert and add samples to the receive buffer */
|
||||
convert_vec = short_to_sigvec(smpls_in, num_smpls);
|
||||
rx_vec = concat(rx_vec, convert_vec);
|
||||
|
||||
num_chunks = rx_vec->size() / OUTCHUNK;
|
||||
if (num_chunks < 1)
|
||||
return 0;
|
||||
|
||||
/* Resample */
|
||||
resamp_vec = resmpl_sigvec(rx_hist, &rx_vec, rx_lpf,
|
||||
INRATE, OUTRATE, OUTCHUNK);
|
||||
/* Truncate */
|
||||
trunc_vec = segment(resamp_vec, INHISTORY,
|
||||
resamp_vec->size() - INHISTORY);
|
||||
/* Convert */
|
||||
num_resmpld = sigvec_to_float(trunc_vec, smpls_out);
|
||||
|
||||
return num_resmpld;
|
||||
}
|
||||
|
||||
/* Wrapper for transmit-side float-to-int array resampling */
|
||||
int tx_resmpl_flt_int(short *smpls_out, float *smpls_in, int num_smpls)
|
||||
{
|
||||
int num_resmpl, num_chunks;
|
||||
signalVector *convert_vec, *resamp_vec;
|
||||
|
||||
if (!tx_lpf || !tx_vec || !tx_hist)
|
||||
init_resampler(&tx_lpf, &tx_vec, &tx_hist, true);
|
||||
|
||||
/* Convert and add samples to the transmit buffer */
|
||||
convert_vec = float_to_sigvec(smpls_in, num_smpls);
|
||||
tx_vec = concat(tx_vec, convert_vec);
|
||||
|
||||
num_chunks = tx_vec->size() / INCHUNK;
|
||||
if (num_chunks < 1)
|
||||
return 0;
|
||||
|
||||
/* Resample and convert to an integer array */
|
||||
resamp_vec = resmpl_sigvec(tx_hist, &tx_vec, tx_lpf,
|
||||
OUTRATE, INRATE, INCHUNK);
|
||||
num_resmpl = sigvec_to_short(resamp_vec, smpls_out);
|
||||
|
||||
return num_resmpl;
|
||||
}
|
||||
|
||||
/* Receive a timestamped chunk from the device */
|
||||
void RadioInterface::pullBuffer()
|
||||
{
|
||||
int num_cv, num_rd;
|
||||
bool local_underrun;
|
||||
|
||||
/* Read samples. Fail if we don't get what we want. */
|
||||
num_rd = mRadio->readSamples(rx_buf, OUTCHUNK, &overrun,
|
||||
readTimestamp, &local_underrun);
|
||||
|
||||
LOG(DEBUG) << "Rx read " << num_rd << " samples from device";
|
||||
assert(num_rd == OUTCHUNK);
|
||||
|
||||
underrun |= local_underrun;
|
||||
readTimestamp += (TIMESTAMP) num_rd;
|
||||
|
||||
/* Convert and resample */
|
||||
num_cv = rx_resmpl_int_flt(rcvBuffer + 2 * rcvCursor,
|
||||
rx_buf, num_rd);
|
||||
|
||||
LOG(DEBUG) << "Rx read " << num_cv << " samples from resampler";
|
||||
|
||||
rcvCursor += num_cv;
|
||||
}
|
||||
|
||||
/* Send a timestamped chunk to the device */
|
||||
void RadioInterface::pushBuffer()
|
||||
{
|
||||
int num_cv, num_wr;
|
||||
|
||||
if (sendCursor < INCHUNK)
|
||||
return;
|
||||
|
||||
LOG(DEBUG) << "Tx wrote " << sendCursor << " samples to resampler";
|
||||
|
||||
/* Resample and convert */
|
||||
num_cv = tx_resmpl_flt_int(tx_buf, sendBuffer, sendCursor);
|
||||
assert(num_cv > sendCursor);
|
||||
|
||||
/* Write samples. Fail if we don't get what we want. */
|
||||
num_wr = mRadio->writeSamples(tx_buf + OUTHISTORY * 2,
|
||||
num_cv - OUTHISTORY,
|
||||
&underrun,
|
||||
writeTimestamp);
|
||||
|
||||
LOG(DEBUG) << "Tx wrote " << num_wr << " samples to device";
|
||||
assert(num_wr == num_wr);
|
||||
|
||||
writeTimestamp += (TIMESTAMP) num_wr;
|
||||
sendCursor = 0;
|
||||
}
|
@@ -23,27 +23,80 @@
|
||||
*/
|
||||
|
||||
#include "radioInterface.h"
|
||||
#include "Resampler.h"
|
||||
#include <Logger.h>
|
||||
|
||||
bool started = false;
|
||||
extern "C" {
|
||||
#include "convert.h"
|
||||
}
|
||||
|
||||
#define CHUNK 625
|
||||
#define NUMCHUNKS 4
|
||||
|
||||
RadioInterface::RadioInterface(RadioDevice *wRadio,
|
||||
int wReceiveOffset,
|
||||
int wRadioOversampling,
|
||||
int wTransceiverOversampling,
|
||||
GSM::Time wStartTime)
|
||||
: underrun(false), sendCursor(0), rcvCursor(0), mOn(false),
|
||||
mRadio(wRadio), receiveOffset(wReceiveOffset),
|
||||
samplesPerSymbol(wRadioOversampling), powerScaling(1.0),
|
||||
loadTest(false)
|
||||
size_t sps, size_t chans, size_t diversity,
|
||||
int wReceiveOffset, GSM::Time wStartTime)
|
||||
: mRadio(wRadio), mSPSTx(sps), mSPSRx(1), mChans(chans), mMIMO(diversity),
|
||||
sendCursor(0), recvCursor(0), underrun(false), overrun(false),
|
||||
receiveOffset(wReceiveOffset), shiftOffset(0), shiftUpdate(false),
|
||||
mOn(false)
|
||||
{
|
||||
mClock.set(wStartTime);
|
||||
}
|
||||
|
||||
RadioInterface::~RadioInterface(void)
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
RadioInterface::~RadioInterface(void) {
|
||||
if (rcvBuffer!=NULL) delete rcvBuffer;
|
||||
//mReceiveFIFO.clear();
|
||||
bool RadioInterface::init(int type)
|
||||
{
|
||||
if ((type != RadioDevice::NORMAL) || (mMIMO > 1) || !mChans) {
|
||||
LOG(ALERT) << "Invalid configuration";
|
||||
return false;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
sendBuffer.resize(mChans);
|
||||
recvBuffer.resize(mChans);
|
||||
convertSendBuffer.resize(mChans);
|
||||
convertRecvBuffer.resize(mChans);
|
||||
mReceiveFIFO.resize(mChans);
|
||||
powerScaling.resize(mChans);
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
sendBuffer[i] = new signalVector(CHUNK * mSPSTx);
|
||||
recvBuffer[i] = new signalVector(NUMCHUNKS * CHUNK * mSPSRx);
|
||||
|
||||
convertSendBuffer[i] = new short[sendBuffer[i]->size() * 2];
|
||||
convertRecvBuffer[i] = new short[recvBuffer[i]->size() * 2];
|
||||
}
|
||||
|
||||
sendCursor = 0;
|
||||
recvCursor = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RadioInterface::close()
|
||||
{
|
||||
for (size_t i = 0; i < sendBuffer.size(); i++)
|
||||
delete sendBuffer[i];
|
||||
|
||||
for (size_t i = 0; i < recvBuffer.size(); i++)
|
||||
delete recvBuffer[i];
|
||||
|
||||
for (size_t i = 0; i < convertSendBuffer.size(); i++)
|
||||
delete convertSendBuffer[i];
|
||||
|
||||
for (size_t i = 0; i < convertRecvBuffer.size(); i++)
|
||||
delete convertRecvBuffer[i];
|
||||
|
||||
sendBuffer.resize(0);
|
||||
recvBuffer.resize(0);
|
||||
convertSendBuffer.resize(0);
|
||||
convertRecvBuffer.resize(0);
|
||||
}
|
||||
|
||||
double RadioInterface::fullScaleInputValue(void) {
|
||||
@@ -55,37 +108,34 @@ double RadioInterface::fullScaleOutputValue(void) {
|
||||
}
|
||||
|
||||
|
||||
void RadioInterface::setPowerAttenuation(double atten)
|
||||
void RadioInterface::setPowerAttenuation(double atten, size_t chan)
|
||||
{
|
||||
double rfGain, digAtten;
|
||||
|
||||
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten);
|
||||
if (chan >= mChans) {
|
||||
LOG(ALERT) << "Invalid channel requested";
|
||||
return;
|
||||
}
|
||||
|
||||
rfGain = mRadio->setTxGain(mRadio->maxTxGain() - atten, chan);
|
||||
digAtten = atten - mRadio->maxTxGain() + rfGain;
|
||||
|
||||
if (digAtten < 1.0)
|
||||
powerScaling = 1.0;
|
||||
powerScaling[chan] = 1.0;
|
||||
else
|
||||
powerScaling = 1.0/sqrt(pow(10, (digAtten/10.0)));
|
||||
powerScaling[chan] = 1.0 / sqrt(pow(10, digAtten / 10.0));
|
||||
}
|
||||
|
||||
int RadioInterface::radioifyVector(signalVector &wVector,
|
||||
float *retVector,
|
||||
float scale,
|
||||
bool zero)
|
||||
{
|
||||
int i;
|
||||
signalVector::iterator itr = wVector.begin();
|
||||
|
||||
if (zero) {
|
||||
memset(retVector, 0, wVector.size() * 2 * sizeof(float));
|
||||
return wVector.size();
|
||||
}
|
||||
|
||||
for (i = 0; i < wVector.size(); i++) {
|
||||
retVector[2 * i + 0] = itr->real() * scale;
|
||||
retVector[2 * i + 1] = itr->imag() * scale;
|
||||
itr++;
|
||||
}
|
||||
memcpy(retVector, wVector.begin(), wVector.size() * 2 * sizeof(float));
|
||||
|
||||
return wVector.size();
|
||||
}
|
||||
@@ -93,10 +143,14 @@ int RadioInterface::radioifyVector(signalVector &wVector,
|
||||
int RadioInterface::unRadioifyVector(float *floatVector,
|
||||
signalVector& newVector)
|
||||
{
|
||||
int i;
|
||||
signalVector::iterator itr = newVector.begin();
|
||||
|
||||
for (i = 0; i < newVector.size(); i++) {
|
||||
if (newVector.size() > recvCursor) {
|
||||
LOG(ALERT) << "Insufficient number of samples in receive buffer";
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < newVector.size(); i++) {
|
||||
*itr++ = Complex<float>(floatVector[2 * i + 0],
|
||||
floatVector[2 * i + 1]);
|
||||
}
|
||||
@@ -104,36 +158,45 @@ int RadioInterface::unRadioifyVector(float *floatVector,
|
||||
return newVector.size();
|
||||
}
|
||||
|
||||
bool RadioInterface::tuneTx(double freq)
|
||||
void RadioInterface::adjustClock(GSM::Time &offset)
|
||||
{
|
||||
return mRadio->setTxFreq(freq);
|
||||
mClock.adjust(offset);
|
||||
}
|
||||
|
||||
bool RadioInterface::tuneRx(double freq)
|
||||
bool RadioInterface::tuneTx(double freq, size_t chan)
|
||||
{
|
||||
return mRadio->setRxFreq(freq);
|
||||
return mRadio->setTxFreq(freq, chan);
|
||||
}
|
||||
|
||||
bool RadioInterface::tuneRx(double freq, size_t chan)
|
||||
{
|
||||
return mRadio->setRxFreq(freq, chan);
|
||||
}
|
||||
|
||||
bool RadioInterface::tuneRxOffset(double offset, size_t chan)
|
||||
{
|
||||
return mRadio->setRxOffset(offset, chan);
|
||||
}
|
||||
|
||||
void RadioInterface::start()
|
||||
{
|
||||
LOG(INFO) << "starting radio interface...";
|
||||
LOG(INFO) << "Starting radio";
|
||||
#ifdef USRP1
|
||||
mAlignRadioServiceLoopThread.start((void * (*)(void*))AlignRadioServiceLoopAdapter,
|
||||
(void*)this);
|
||||
#endif
|
||||
mRadio->start();
|
||||
writeTimestamp = mRadio->initialWriteTimestamp();
|
||||
readTimestamp = mRadio->initialReadTimestamp();
|
||||
mRadio->start();
|
||||
LOG(DEBUG) << "Radio started";
|
||||
mRadio->updateAlignment(writeTimestamp-10000);
|
||||
|
||||
mRadio->updateAlignment(writeTimestamp-10000);
|
||||
mRadio->updateAlignment(writeTimestamp-10000);
|
||||
|
||||
sendBuffer = new float[2*2*INCHUNK*samplesPerSymbol];
|
||||
rcvBuffer = new float[2*2*OUTCHUNK*samplesPerSymbol];
|
||||
|
||||
mOn = true;
|
||||
|
||||
LOG(INFO) << "Radio started";
|
||||
}
|
||||
|
||||
#ifdef USRP1
|
||||
void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface)
|
||||
{
|
||||
while (1) {
|
||||
@@ -147,67 +210,92 @@ void RadioInterface::alignRadio() {
|
||||
sleep(60);
|
||||
mRadio->updateAlignment(writeTimestamp+ (TIMESTAMP) 10000);
|
||||
}
|
||||
#endif
|
||||
|
||||
void RadioInterface::driveTransmitRadio(signalVector &radioBurst, bool zeroBurst) {
|
||||
void RadioInterface::driveTransmitRadio(std::vector<signalVector *> &bursts,
|
||||
std::vector<bool> &zeros)
|
||||
{
|
||||
if (!mOn)
|
||||
return;
|
||||
|
||||
if (!mOn) return;
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
radioifyVector(*bursts[i],
|
||||
(float *) (sendBuffer[i]->begin() + sendCursor), zeros[i]);
|
||||
}
|
||||
|
||||
radioifyVector(radioBurst, sendBuffer + 2 * sendCursor, powerScaling, zeroBurst);
|
||||
|
||||
sendCursor += radioBurst.size();
|
||||
sendCursor += bursts[0]->size();
|
||||
|
||||
pushBuffer();
|
||||
}
|
||||
|
||||
void RadioInterface::driveReceiveRadio() {
|
||||
bool RadioInterface::driveReceiveRadio()
|
||||
{
|
||||
radioVector *burst = NULL;
|
||||
|
||||
if (!mOn) return;
|
||||
|
||||
if (mReceiveFIFO.size() > 8) return;
|
||||
if (!mOn)
|
||||
return false;
|
||||
|
||||
pullBuffer();
|
||||
|
||||
GSM::Time rcvClock = mClock.get();
|
||||
rcvClock.decTN(receiveOffset);
|
||||
if (receiveOffset < 0)
|
||||
rcvClock.incTN(-receiveOffset);
|
||||
else
|
||||
rcvClock.decTN(receiveOffset);
|
||||
|
||||
unsigned tN = rcvClock.TN();
|
||||
int rcvSz = rcvCursor;
|
||||
int recvSz = recvCursor;
|
||||
int readSz = 0;
|
||||
const int symbolsPerSlot = gSlotLen + 8;
|
||||
int burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
|
||||
|
||||
// while there's enough data in receive buffer, form received
|
||||
// GSM bursts and pass up to Transceiver
|
||||
// Using the 157-156-156-156 symbols per timeslot format.
|
||||
while (rcvSz > (symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol) {
|
||||
signalVector rxVector((symbolsPerSlot + (tN % 4 == 0))*samplesPerSymbol);
|
||||
unRadioifyVector(rcvBuffer+readSz*2,rxVector);
|
||||
GSM::Time tmpTime = rcvClock;
|
||||
if (rcvClock.FN() >= 0) {
|
||||
//LOG(DEBUG) << "FN: " << rcvClock.FN();
|
||||
radioVector *rxBurst = NULL;
|
||||
if (!loadTest)
|
||||
rxBurst = new radioVector(rxVector,tmpTime);
|
||||
else {
|
||||
if (tN % 4 == 0)
|
||||
rxBurst = new radioVector(*finalVec9,tmpTime);
|
||||
else
|
||||
rxBurst = new radioVector(*finalVec,tmpTime);
|
||||
/*
|
||||
* Pre-allocate head room for the largest correlation size
|
||||
* so we can later avoid a re-allocation and copy
|
||||
* */
|
||||
size_t head = GSM::gRACHSynchSequence.size();
|
||||
|
||||
/*
|
||||
* Form receive bursts and pass up to transceiver. Use repeating
|
||||
* pattern of 157-156-156-156 symbols per timeslot
|
||||
*/
|
||||
while (recvSz > burstSize) {
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
burst = new radioVector(rcvClock, burstSize, head, mMIMO);
|
||||
|
||||
for (size_t n = 0; n < mMIMO; n++) {
|
||||
unRadioifyVector((float *)
|
||||
(recvBuffer[mMIMO * i + n]->begin() + readSz),
|
||||
*burst->getVector(n));
|
||||
}
|
||||
mReceiveFIFO.put(rxBurst);
|
||||
|
||||
if (mReceiveFIFO[i].size() < 32)
|
||||
mReceiveFIFO[i].write(burst);
|
||||
else
|
||||
delete burst;
|
||||
}
|
||||
mClock.incTN();
|
||||
|
||||
mClock.incTN();
|
||||
rcvClock.incTN();
|
||||
//if (mReceiveFIFO.size() >= 16) mReceiveFIFO.wait(8);
|
||||
//LOG(DEBUG) << "receiveFIFO: wrote radio vector at time: " << mClock.get() << ", new size: " << mReceiveFIFO.size() ;
|
||||
readSz += (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol;
|
||||
rcvSz -= (symbolsPerSlot+(tN % 4 == 0))*samplesPerSymbol;
|
||||
readSz += burstSize;
|
||||
recvSz -= burstSize;
|
||||
|
||||
tN = rcvClock.TN();
|
||||
|
||||
burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
|
||||
}
|
||||
|
||||
if (readSz > 0) {
|
||||
rcvCursor -= readSz;
|
||||
memmove(rcvBuffer,rcvBuffer+2*readSz,sizeof(float) * 2 * rcvCursor);
|
||||
for (size_t i = 0; i < recvBuffer.size(); i++) {
|
||||
memmove(recvBuffer[i]->begin(),
|
||||
recvBuffer[i]->begin() + readSz,
|
||||
(recvCursor - readSz) * 2 * sizeof(float));
|
||||
}
|
||||
|
||||
recvCursor -= readSz;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioInterface::isUnderrun()
|
||||
@@ -218,26 +306,95 @@ bool RadioInterface::isUnderrun()
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void RadioInterface::attach(RadioDevice *wRadio, int wRadioOversampling)
|
||||
void RadioInterface::applyOffset(int offset)
|
||||
{
|
||||
if (!mOn) {
|
||||
mRadio = wRadio;
|
||||
mRadioOversampling = SAMPSPERSYM;
|
||||
shiftOffset += offset;
|
||||
shiftUpdate = true;
|
||||
}
|
||||
|
||||
VectorFIFO* RadioInterface::receiveFIFO(size_t chan)
|
||||
{
|
||||
if (chan >= mReceiveFIFO.size())
|
||||
return NULL;
|
||||
|
||||
return &mReceiveFIFO[chan];
|
||||
}
|
||||
|
||||
double RadioInterface::setRxGain(double dB, size_t chan)
|
||||
{
|
||||
if (mRadio)
|
||||
return mRadio->setRxGain(dB, chan);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
double RadioInterface::getRxGain(size_t chan)
|
||||
{
|
||||
if (mRadio)
|
||||
return mRadio->getRxGain(chan);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Receive a timestamped chunk from the device */
|
||||
void RadioInterface::pullBuffer()
|
||||
{
|
||||
bool local_underrun;
|
||||
int num_recv;
|
||||
float *output;
|
||||
|
||||
if (recvCursor > recvBuffer[0]->size() - CHUNK)
|
||||
return;
|
||||
|
||||
/* Outer buffer access size is fixed */
|
||||
num_recv = mRadio->readSamples(convertRecvBuffer,
|
||||
CHUNK,
|
||||
&overrun,
|
||||
readTimestamp + shiftOffset,
|
||||
&local_underrun);
|
||||
if (num_recv != CHUNK) {
|
||||
LOG(ALERT) << "Receive error " << num_recv;
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
output = (float *) (recvBuffer[i]->begin() + recvCursor);
|
||||
convert_short_float(output, convertRecvBuffer[i], 2 * num_recv);
|
||||
}
|
||||
|
||||
underrun |= local_underrun;
|
||||
|
||||
readTimestamp += num_recv;
|
||||
recvCursor += num_recv;
|
||||
}
|
||||
|
||||
double RadioInterface::setRxGain(double dB)
|
||||
/* Send timestamped chunk to the device with arbitrary size */
|
||||
void RadioInterface::pushBuffer()
|
||||
{
|
||||
if (mRadio)
|
||||
return mRadio->setRxGain(dB);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int num_sent;
|
||||
|
||||
double RadioInterface::getRxGain()
|
||||
{
|
||||
if (mRadio)
|
||||
return mRadio->getRxGain();
|
||||
else
|
||||
return -1;
|
||||
if (sendCursor < CHUNK)
|
||||
return;
|
||||
|
||||
if (sendCursor > sendBuffer[0]->size())
|
||||
LOG(ALERT) << "Send buffer overflow";
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
convert_float_short(convertSendBuffer[i],
|
||||
(float *) sendBuffer[i]->begin(),
|
||||
powerScaling[i], 2 * sendCursor);
|
||||
}
|
||||
|
||||
if (shiftUpdate) {
|
||||
mRadio->updateAlignment(0);
|
||||
shiftUpdate = false;
|
||||
}
|
||||
|
||||
/* Send the all samples in the send buffer */
|
||||
num_sent = mRadio->writeSamples(convertSendBuffer,
|
||||
sendCursor,
|
||||
&underrun,
|
||||
writeTimestamp + mSPSTx * shiftOffset);
|
||||
writeTimestamp += num_sent;
|
||||
sendCursor = 0;
|
||||
}
|
||||
|
@@ -20,31 +20,34 @@
|
||||
#include "radioDevice.h"
|
||||
#include "radioVector.h"
|
||||
#include "radioClock.h"
|
||||
|
||||
/** samples per GSM symbol */
|
||||
#define SAMPSPERSYM 1
|
||||
#define INCHUNK (625)
|
||||
#define OUTCHUNK (625)
|
||||
#include "Resampler.h"
|
||||
|
||||
static const unsigned gSlotLen = 148; ///< number of symbols per slot, not counting guard periods
|
||||
|
||||
/** class to interface the transceiver with the USRP */
|
||||
class RadioInterface {
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections
|
||||
|
||||
VectorFIFO mReceiveFIFO; ///< FIFO that holds receive bursts
|
||||
std::vector<VectorFIFO> mReceiveFIFO; ///< FIFO that holds receive bursts
|
||||
|
||||
RadioDevice *mRadio; ///< the USRP object
|
||||
|
||||
float *sendBuffer;
|
||||
unsigned sendCursor;
|
||||
|
||||
float *rcvBuffer;
|
||||
unsigned rcvCursor;
|
||||
|
||||
size_t mSPSTx;
|
||||
size_t mSPSRx;
|
||||
size_t mChans;
|
||||
size_t mMIMO;
|
||||
|
||||
std::vector<signalVector *> sendBuffer;
|
||||
std::vector<signalVector *> recvBuffer;
|
||||
unsigned sendCursor;
|
||||
unsigned recvCursor;
|
||||
|
||||
std::vector<short *> convertRecvBuffer;
|
||||
std::vector<short *> convertSendBuffer;
|
||||
std::vector<float> powerScaling;
|
||||
bool underrun; ///< indicates writes to USRP are too slow
|
||||
bool overrun; ///< indicates reads from USRP are too slow
|
||||
TIMESTAMP writeTimestamp; ///< sample timestamp of next packet written to USRP
|
||||
@@ -52,84 +55,80 @@ private:
|
||||
|
||||
RadioClock mClock; ///< the basestation clock!
|
||||
|
||||
int samplesPerSymbol; ///< samples per GSM symbol
|
||||
int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots
|
||||
int mRadioOversampling;
|
||||
int mTransceiverOversampling;
|
||||
|
||||
int shiftOffset;
|
||||
bool shiftUpdate;
|
||||
bool mOn; ///< indicates radio is on
|
||||
|
||||
double powerScaling;
|
||||
|
||||
bool loadTest;
|
||||
int mNumARFCNs;
|
||||
signalVector *finalVec, *finalVec9;
|
||||
private:
|
||||
|
||||
/** format samples to USRP */
|
||||
int radioifyVector(signalVector &wVector,
|
||||
float *floatVector,
|
||||
float scale,
|
||||
bool zero);
|
||||
|
||||
/** format samples from USRP */
|
||||
int unRadioifyVector(float *floatVector, signalVector &wVector);
|
||||
|
||||
/** push GSM bursts into the transmit buffer */
|
||||
void pushBuffer(void);
|
||||
virtual void pushBuffer(void);
|
||||
|
||||
/** pull GSM bursts from the receive buffer */
|
||||
void pullBuffer(void);
|
||||
virtual void pullBuffer(void);
|
||||
|
||||
public:
|
||||
|
||||
/** start the interface */
|
||||
void start();
|
||||
|
||||
/** intialization */
|
||||
virtual bool init(int type);
|
||||
virtual void close();
|
||||
|
||||
/** constructor */
|
||||
RadioInterface(RadioDevice* wRadio = NULL,
|
||||
int receiveOffset = 3,
|
||||
int wRadioOversampling = SAMPSPERSYM,
|
||||
int wTransceiverOversampling = SAMPSPERSYM,
|
||||
GSM::Time wStartTime = GSM::Time(0));
|
||||
|
||||
size_t sps = 4, size_t chans = 1, size_t diversity = 1,
|
||||
int receiveOffset = 3, GSM::Time wStartTime = GSM::Time(0));
|
||||
|
||||
/** destructor */
|
||||
~RadioInterface();
|
||||
|
||||
void setSamplesPerSymbol(int wSamplesPerSymbol) {if (!mOn) samplesPerSymbol = wSamplesPerSymbol;}
|
||||
|
||||
int getSamplesPerSymbol() { return samplesPerSymbol;}
|
||||
virtual ~RadioInterface();
|
||||
|
||||
/** check for underrun, resets underrun value */
|
||||
bool isUnderrun();
|
||||
|
||||
/** attach an existing USRP to this interface */
|
||||
void attach(RadioDevice *wRadio, int wRadioOversampling);
|
||||
void applyOffset(int offset);
|
||||
|
||||
/** return the receive FIFO */
|
||||
VectorFIFO* receiveFIFO() { return &mReceiveFIFO;}
|
||||
VectorFIFO* receiveFIFO(size_t chan = 0);
|
||||
|
||||
/** return the basestation clock */
|
||||
RadioClock* getClock(void) { return &mClock;};
|
||||
|
||||
/** apply an offset to the main clock */
|
||||
void adjustClock(GSM::Time &offset);
|
||||
|
||||
/** set transmit frequency */
|
||||
bool tuneTx(double freq);
|
||||
bool tuneTx(double freq, size_t chan = 0);
|
||||
|
||||
/** set receive frequency */
|
||||
bool tuneRx(double freq);
|
||||
virtual bool tuneRx(double freq, size_t chan = 0);
|
||||
|
||||
/** set frequency correction */
|
||||
virtual bool tuneRxOffset(double offset, size_t chan = 0);
|
||||
|
||||
/** set receive gain */
|
||||
double setRxGain(double dB);
|
||||
double setRxGain(double dB, size_t chan = 0);
|
||||
|
||||
/** get receive gain */
|
||||
double getRxGain(void);
|
||||
double getRxGain(size_t chan = 0);
|
||||
|
||||
/** drive transmission of GSM bursts */
|
||||
void driveTransmitRadio(signalVector &radioBurst, bool zeroBurst);
|
||||
void driveTransmitRadio(std::vector<signalVector *> &bursts,
|
||||
std::vector<bool> &zeros);
|
||||
|
||||
/** drive reception of GSM bursts */
|
||||
void driveReceiveRadio();
|
||||
bool driveReceiveRadio();
|
||||
|
||||
void setPowerAttenuation(double atten);
|
||||
void setPowerAttenuation(double atten, size_t chan = 0);
|
||||
|
||||
/** returns the full-scale transmit amplitude **/
|
||||
double fullScaleInputValue();
|
||||
@@ -138,22 +137,66 @@ public:
|
||||
double fullScaleOutputValue();
|
||||
|
||||
/** set thread priority on current thread */
|
||||
void setPriority() { mRadio->setPriority(); }
|
||||
void setPriority(float prio = 0.5) { mRadio->setPriority(prio); }
|
||||
|
||||
/** get transport bus type of attached device */
|
||||
enum RadioDevice::busType getBus() { return mRadio->getBus(); }
|
||||
/** get transport window type of attached device */
|
||||
enum RadioDevice::TxWindowType getWindowType() { return mRadio->getWindowType(); }
|
||||
|
||||
#if USRP1
|
||||
protected:
|
||||
|
||||
/** drive synchronization of Tx/Rx of USRP */
|
||||
void alignRadio();
|
||||
|
||||
/** reset the interface */
|
||||
void reset();
|
||||
|
||||
friend void *AlignRadioServiceLoopAdapter(RadioInterface*);
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
#if USRP1
|
||||
/** synchronization thread loop */
|
||||
void *AlignRadioServiceLoopAdapter(RadioInterface*);
|
||||
#endif
|
||||
|
||||
class RadioInterfaceResamp : public RadioInterface {
|
||||
|
||||
private:
|
||||
signalVector *innerSendBuffer;
|
||||
signalVector *outerSendBuffer;
|
||||
signalVector *innerRecvBuffer;
|
||||
signalVector *outerRecvBuffer;
|
||||
|
||||
void pushBuffer();
|
||||
void pullBuffer();
|
||||
|
||||
public:
|
||||
|
||||
RadioInterfaceResamp(RadioDevice* wRadio, size_t wSPS = 4, size_t chans = 1);
|
||||
|
||||
~RadioInterfaceResamp();
|
||||
|
||||
bool init(int type);
|
||||
void close();
|
||||
};
|
||||
|
||||
class RadioInterfaceDiversity : public RadioInterface {
|
||||
public:
|
||||
RadioInterfaceDiversity(RadioDevice* wRadio,
|
||||
size_t sps = 4, size_t chans = 2);
|
||||
|
||||
~RadioInterfaceDiversity();
|
||||
|
||||
bool init(int type);
|
||||
void close();
|
||||
bool tuneRx(double freq, size_t chan);
|
||||
|
||||
private:
|
||||
std::vector<Resampler *> dnsamplers;
|
||||
std::vector<float> phases;
|
||||
signalVector *outerRecvBuffer;
|
||||
|
||||
bool mDiversity;
|
||||
double mFreqSpacing;
|
||||
|
||||
bool setupDiversityChannels();
|
||||
void pullBuffer();
|
||||
};
|
||||
|
248
Transceiver52M/radioInterfaceDiversity.cpp
Normal file
248
Transceiver52M/radioInterfaceDiversity.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* SSE Convolution
|
||||
* 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 <radioInterface.h>
|
||||
#include <Logger.h>
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
extern "C" {
|
||||
#include "convert.h"
|
||||
}
|
||||
|
||||
/* Resampling parameters for 64 MHz clocking */
|
||||
#define RESAMP_64M_INRATE 20
|
||||
#define RESAMP_64M_OUTRATE 80
|
||||
|
||||
/* Downlink block size */
|
||||
#define CHUNK 625
|
||||
|
||||
/* Universal resampling parameters */
|
||||
#define NUMCHUNKS 48
|
||||
|
||||
/*
|
||||
* Resampling filter bandwidth scaling factor
|
||||
* This narrows the filter cutoff relative to the output bandwidth
|
||||
* of the polyphase resampler. At 4 samples-per-symbol using the
|
||||
* 2 pulse Laurent GMSK approximation gives us below 0.5 degrees
|
||||
* RMS phase error at the resampler output.
|
||||
*/
|
||||
#define RESAMP_TX4_FILTER 0.45
|
||||
|
||||
static size_t resamp_inrate = 0;
|
||||
static size_t resamp_inchunk = 0;
|
||||
static size_t resamp_outrate = 0;
|
||||
static size_t resamp_outchunk = 0;
|
||||
|
||||
RadioInterfaceDiversity::RadioInterfaceDiversity(RadioDevice *wRadio,
|
||||
size_t sps, size_t chans)
|
||||
: RadioInterface(wRadio, sps, chans, 2), outerRecvBuffer(NULL),
|
||||
mDiversity(false), mFreqSpacing(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
RadioInterfaceDiversity::~RadioInterfaceDiversity()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void RadioInterfaceDiversity::close()
|
||||
{
|
||||
delete outerRecvBuffer;
|
||||
|
||||
outerRecvBuffer = NULL;
|
||||
|
||||
for (size_t i = 0; i < dnsamplers.size(); i++) {
|
||||
delete dnsamplers[i];
|
||||
dnsamplers[i] = NULL;
|
||||
}
|
||||
|
||||
if (recvBuffer.size())
|
||||
recvBuffer[0] = NULL;
|
||||
|
||||
RadioInterface::close();
|
||||
}
|
||||
|
||||
bool RadioInterfaceDiversity::setupDiversityChannels()
|
||||
{
|
||||
size_t inner_rx_len;
|
||||
|
||||
/* Inner and outer rates */
|
||||
resamp_inrate = RESAMP_64M_INRATE;
|
||||
resamp_outrate = RESAMP_64M_OUTRATE;
|
||||
resamp_inchunk = resamp_inrate * 4;
|
||||
resamp_outchunk = resamp_outrate * 4;
|
||||
|
||||
/* Buffer lengths */
|
||||
inner_rx_len = NUMCHUNKS * resamp_inchunk;
|
||||
|
||||
/* Inside buffer must hold at least 2 bursts */
|
||||
if (inner_rx_len < 157 * mSPSRx * 2) {
|
||||
LOG(ALERT) << "Invalid inner buffer size " << inner_rx_len;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* One Receive buffer and downsampler per diversity channel */
|
||||
for (size_t i = 0; i < mMIMO * mChans; i++) {
|
||||
dnsamplers[i] = new Resampler(resamp_inrate, resamp_outrate);
|
||||
if (!dnsamplers[i]->init()) {
|
||||
LOG(ALERT) << "Rx resampler failed to initialize";
|
||||
return false;
|
||||
}
|
||||
|
||||
recvBuffer[i] = new signalVector(inner_rx_len);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initialize I/O specific objects */
|
||||
bool RadioInterfaceDiversity::init(int type)
|
||||
{
|
||||
int tx_len, outer_rx_len;
|
||||
|
||||
if ((mMIMO != 2) || (mChans != 2)) {
|
||||
LOG(ALERT) << "Unsupported channel configuration " << mChans;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Resize for channel combination */
|
||||
sendBuffer.resize(mChans);
|
||||
recvBuffer.resize(mChans * mMIMO);
|
||||
convertSendBuffer.resize(mChans);
|
||||
convertRecvBuffer.resize(mChans);
|
||||
mReceiveFIFO.resize(mChans);
|
||||
dnsamplers.resize(mChans * mMIMO);
|
||||
phases.resize(mChans);
|
||||
|
||||
if (!setupDiversityChannels())
|
||||
return false;
|
||||
|
||||
tx_len = CHUNK * mSPSTx;
|
||||
outer_rx_len = resamp_outchunk;
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
/* Full rate float and integer outer receive buffers */
|
||||
convertRecvBuffer[i] = new short[outer_rx_len * 2];
|
||||
|
||||
/* Send buffers (not-resampled) */
|
||||
sendBuffer[i] = new signalVector(tx_len);
|
||||
convertSendBuffer[i] = new short[tx_len * 2];
|
||||
}
|
||||
|
||||
outerRecvBuffer = new signalVector(outer_rx_len, dnsamplers[0]->len());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioInterfaceDiversity::tuneRx(double freq, size_t chan)
|
||||
{
|
||||
double f0, f1;
|
||||
|
||||
if (chan > 1)
|
||||
return false;
|
||||
|
||||
if (!mRadio->setRxFreq(freq, chan))
|
||||
return false;
|
||||
|
||||
f0 = mRadio->getRxFreq(0);
|
||||
f1 = mRadio->getRxFreq(1);
|
||||
|
||||
mFreqSpacing = f1 - f0;
|
||||
|
||||
if (abs(mFreqSpacing) <= 600e3)
|
||||
mDiversity = true;
|
||||
else
|
||||
mDiversity = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Receive a timestamped chunk from the device */
|
||||
void RadioInterfaceDiversity::pullBuffer()
|
||||
{
|
||||
bool local_underrun;
|
||||
int rc, num, path0, path1;
|
||||
signalVector *shift, *base;
|
||||
float *in, *out, rate = -mFreqSpacing * 2.0 * M_PI / 1.08333333e6;
|
||||
|
||||
if (recvCursor > recvBuffer[0]->size() - resamp_inchunk)
|
||||
return;
|
||||
|
||||
/* Outer buffer access size is fixed */
|
||||
num = mRadio->readSamples(convertRecvBuffer,
|
||||
resamp_outchunk,
|
||||
&overrun,
|
||||
readTimestamp + shiftOffset,
|
||||
&local_underrun);
|
||||
if ((size_t) num != resamp_outchunk) {
|
||||
LOG(ALERT) << "Receive error " << num;
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
convert_short_float((float *) outerRecvBuffer->begin(),
|
||||
convertRecvBuffer[i], 2 * resamp_outchunk);
|
||||
|
||||
if (!i) {
|
||||
path0 = 0;
|
||||
path1 = 2;
|
||||
} else {
|
||||
path0 = 3;
|
||||
path1 = 1;
|
||||
}
|
||||
|
||||
/* Diversity path 1 */
|
||||
base = outerRecvBuffer;
|
||||
in = (float *) base->begin();
|
||||
out = (float *) (recvBuffer[path0]->begin() + recvCursor);
|
||||
|
||||
rc = dnsamplers[2 * i + 0]->rotate(in, resamp_outchunk,
|
||||
out, resamp_inchunk);
|
||||
if (rc < 0) {
|
||||
LOG(ALERT) << "Sample rate downsampling error";
|
||||
}
|
||||
|
||||
/* Enable path 2 if Nyquist bandwidth is sufficient */
|
||||
if (!mDiversity)
|
||||
continue;
|
||||
|
||||
/* Diversity path 2 */
|
||||
shift = new signalVector(base->size(), base->getStart());
|
||||
in = (float *) shift->begin();
|
||||
out = (float *) (recvBuffer[path1]->begin() + recvCursor);
|
||||
|
||||
rate = i ? -rate : rate;
|
||||
if (!frequencyShift(shift, base, rate, phases[i], &phases[i])) {
|
||||
LOG(ALERT) << "Frequency shift failed";
|
||||
}
|
||||
|
||||
rc = dnsamplers[2 * i + 1]->rotate(in, resamp_outchunk,
|
||||
out, resamp_inchunk);
|
||||
if (rc < 0) {
|
||||
LOG(ALERT) << "Sample rate downsampling error";
|
||||
}
|
||||
|
||||
delete shift;
|
||||
}
|
||||
|
||||
underrun |= local_underrun;
|
||||
readTimestamp += (TIMESTAMP) resamp_outchunk;
|
||||
recvCursor += resamp_inchunk;
|
||||
}
|
260
Transceiver52M/radioInterfaceResamp.cpp
Normal file
260
Transceiver52M/radioInterfaceResamp.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Radio device interface with sample rate conversion
|
||||
* Written by Thomas Tsou <tom@tsou.cc>
|
||||
*
|
||||
* Copyright 2011, 2012, 2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero 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 <radioInterface.h>
|
||||
#include <Logger.h>
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
extern "C" {
|
||||
#include "convert.h"
|
||||
}
|
||||
|
||||
/* Resampling parameters for 64 MHz clocking */
|
||||
#define RESAMP_64M_INRATE 65
|
||||
#define RESAMP_64M_OUTRATE 96
|
||||
|
||||
/* Resampling parameters for 100 MHz clocking */
|
||||
#define RESAMP_100M_INRATE 52
|
||||
#define RESAMP_100M_OUTRATE 75
|
||||
|
||||
/* Universal resampling parameters */
|
||||
#define NUMCHUNKS 24
|
||||
|
||||
/*
|
||||
* Resampling filter bandwidth scaling factor
|
||||
* This narrows the filter cutoff relative to the output bandwidth
|
||||
* of the polyphase resampler. At 4 samples-per-symbol using the
|
||||
* 2 pulse Laurent GMSK approximation gives us below 0.5 degrees
|
||||
* RMS phase error at the resampler output.
|
||||
*/
|
||||
#define RESAMP_TX4_FILTER 0.45
|
||||
|
||||
static Resampler *upsampler = NULL;
|
||||
static Resampler *dnsampler = NULL;
|
||||
static size_t resamp_inrate = 0;
|
||||
static size_t resamp_inchunk = 0;
|
||||
static size_t resamp_outrate = 0;
|
||||
static size_t resamp_outchunk = 0;
|
||||
|
||||
RadioInterfaceResamp::RadioInterfaceResamp(RadioDevice *wRadio,
|
||||
size_t sps, size_t chans)
|
||||
: RadioInterface(wRadio, sps, chans),
|
||||
innerSendBuffer(NULL), outerSendBuffer(NULL),
|
||||
innerRecvBuffer(NULL), outerRecvBuffer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
RadioInterfaceResamp::~RadioInterfaceResamp()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void RadioInterfaceResamp::close()
|
||||
{
|
||||
delete innerSendBuffer;
|
||||
delete outerSendBuffer;
|
||||
delete innerRecvBuffer;
|
||||
delete outerRecvBuffer;
|
||||
|
||||
delete upsampler;
|
||||
delete dnsampler;
|
||||
|
||||
innerSendBuffer = NULL;
|
||||
outerSendBuffer = NULL;
|
||||
innerRecvBuffer = NULL;
|
||||
outerRecvBuffer = NULL;
|
||||
|
||||
upsampler = NULL;
|
||||
dnsampler = NULL;
|
||||
|
||||
if (sendBuffer.size())
|
||||
sendBuffer[0] = NULL;
|
||||
if (recvBuffer.size())
|
||||
recvBuffer[0] = NULL;
|
||||
|
||||
RadioInterface::close();
|
||||
}
|
||||
|
||||
/* Initialize I/O specific objects */
|
||||
bool RadioInterfaceResamp::init(int type)
|
||||
{
|
||||
float cutoff = 1.0f;
|
||||
|
||||
if (mChans != 1) {
|
||||
LOG(ALERT) << "Unsupported channel configuration " << mChans;
|
||||
return false;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
sendBuffer.resize(1);
|
||||
recvBuffer.resize(1);
|
||||
convertSendBuffer.resize(1);
|
||||
convertRecvBuffer.resize(1);
|
||||
mReceiveFIFO.resize(1);
|
||||
powerScaling.resize(1);
|
||||
|
||||
switch (type) {
|
||||
case RadioDevice::RESAMP_64M:
|
||||
resamp_inrate = RESAMP_64M_INRATE;
|
||||
resamp_outrate = RESAMP_64M_OUTRATE;
|
||||
break;
|
||||
case RadioDevice::RESAMP_100M:
|
||||
resamp_inrate = RESAMP_100M_INRATE;
|
||||
resamp_outrate = RESAMP_100M_OUTRATE;
|
||||
break;
|
||||
case RadioDevice::NORMAL:
|
||||
default:
|
||||
LOG(ALERT) << "Invalid device configuration";
|
||||
return false;
|
||||
}
|
||||
|
||||
resamp_inchunk = resamp_inrate * 4;
|
||||
resamp_outchunk = resamp_outrate * 4;
|
||||
|
||||
if (resamp_inchunk * NUMCHUNKS < 157 * mSPSTx * 2) {
|
||||
LOG(ALERT) << "Invalid inner chunk size " << resamp_inchunk;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSPSTx == 4)
|
||||
cutoff = RESAMP_TX4_FILTER;
|
||||
|
||||
dnsampler = new Resampler(resamp_inrate, resamp_outrate);
|
||||
if (!dnsampler->init()) {
|
||||
LOG(ALERT) << "Rx resampler failed to initialize";
|
||||
return false;
|
||||
}
|
||||
|
||||
upsampler = new Resampler(resamp_outrate, resamp_inrate);
|
||||
if (!upsampler->init(cutoff)) {
|
||||
LOG(ALERT) << "Tx resampler failed to initialize";
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate high and low rate buffers. The high rate receive
|
||||
* buffer and low rate transmit vectors feed into the resampler
|
||||
* and requires headroom equivalent to the filter length. Low
|
||||
* rate buffers are allocated in the main radio interface code.
|
||||
*/
|
||||
innerSendBuffer =
|
||||
new signalVector(NUMCHUNKS * resamp_inchunk, upsampler->len());
|
||||
outerSendBuffer =
|
||||
new signalVector(NUMCHUNKS * resamp_outchunk);
|
||||
outerRecvBuffer =
|
||||
new signalVector(resamp_outchunk, dnsampler->len());
|
||||
innerRecvBuffer =
|
||||
new signalVector(NUMCHUNKS * resamp_inchunk / mSPSTx);
|
||||
|
||||
convertSendBuffer[0] = new short[outerSendBuffer->size() * 2];
|
||||
convertRecvBuffer[0] = new short[outerRecvBuffer->size() * 2];
|
||||
|
||||
sendBuffer[0] = innerSendBuffer;
|
||||
recvBuffer[0] = innerRecvBuffer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Receive a timestamped chunk from the device */
|
||||
void RadioInterfaceResamp::pullBuffer()
|
||||
{
|
||||
bool local_underrun;
|
||||
int rc, num_recv;
|
||||
|
||||
if (recvCursor > innerRecvBuffer->size() - resamp_inchunk)
|
||||
return;
|
||||
|
||||
/* Outer buffer access size is fixed */
|
||||
num_recv = mRadio->readSamples(convertRecvBuffer,
|
||||
resamp_outchunk,
|
||||
&overrun,
|
||||
readTimestamp + shiftOffset,
|
||||
&local_underrun);
|
||||
if (num_recv != (int) resamp_outchunk) {
|
||||
LOG(ALERT) << "Receive error " << num_recv;
|
||||
return;
|
||||
}
|
||||
|
||||
convert_short_float((float *) outerRecvBuffer->begin(),
|
||||
convertRecvBuffer[0], 2 * resamp_outchunk);
|
||||
|
||||
underrun |= local_underrun;
|
||||
readTimestamp += (TIMESTAMP) resamp_outchunk;
|
||||
|
||||
/* Write to the end of the inner receive buffer */
|
||||
rc = dnsampler->rotate((float *) outerRecvBuffer->begin(),
|
||||
resamp_outchunk,
|
||||
(float *) (innerRecvBuffer->begin() + recvCursor),
|
||||
resamp_inchunk);
|
||||
if (rc < 0) {
|
||||
LOG(ALERT) << "Sample rate upsampling error";
|
||||
}
|
||||
|
||||
recvCursor += resamp_inchunk;
|
||||
}
|
||||
|
||||
/* Send a timestamped chunk to the device */
|
||||
void RadioInterfaceResamp::pushBuffer()
|
||||
{
|
||||
int rc, chunks, num_sent;
|
||||
int inner_len, outer_len;
|
||||
|
||||
if (sendCursor < resamp_inchunk)
|
||||
return;
|
||||
|
||||
if (sendCursor > innerSendBuffer->size())
|
||||
LOG(ALERT) << "Send buffer overflow";
|
||||
|
||||
chunks = sendCursor / resamp_inchunk;
|
||||
|
||||
inner_len = chunks * resamp_inchunk;
|
||||
outer_len = chunks * resamp_outchunk;
|
||||
|
||||
/* Always send from the beginning of the buffer */
|
||||
rc = upsampler->rotate((float *) innerSendBuffer->begin(), inner_len,
|
||||
(float *) outerSendBuffer->begin(), outer_len);
|
||||
if (rc < 0) {
|
||||
LOG(ALERT) << "Sample rate downsampling error";
|
||||
}
|
||||
|
||||
convert_float_short(convertSendBuffer[0],
|
||||
(float *) outerSendBuffer->begin(),
|
||||
powerScaling[0], 2 * outer_len);
|
||||
|
||||
num_sent = mRadio->writeSamples(convertSendBuffer,
|
||||
outer_len,
|
||||
&underrun,
|
||||
writeTimestamp);
|
||||
if (num_sent != outer_len) {
|
||||
LOG(ALERT) << "Transmit error " << num_sent;
|
||||
}
|
||||
|
||||
/* Shift remaining samples to beginning of buffer */
|
||||
memmove(innerSendBuffer->begin(),
|
||||
innerSendBuffer->begin() + inner_len,
|
||||
(sendCursor - inner_len) * 2 * sizeof(float));
|
||||
|
||||
writeTimestamp += outer_len;
|
||||
sendCursor -= inner_len;
|
||||
assert(sendCursor >= 0);
|
||||
}
|
@@ -21,9 +21,24 @@
|
||||
|
||||
#include "radioVector.h"
|
||||
|
||||
radioVector::radioVector(const signalVector& wVector, GSM::Time& wTime)
|
||||
: signalVector(wVector), mTime(wTime)
|
||||
radioVector::radioVector(GSM::Time &time, size_t size,
|
||||
size_t start, size_t chans)
|
||||
: vectors(chans), mTime(time)
|
||||
{
|
||||
for (size_t i = 0; i < vectors.size(); i++)
|
||||
vectors[i] = new signalVector(size, start);
|
||||
}
|
||||
|
||||
radioVector::radioVector(GSM::Time& wTime, signalVector *vector)
|
||||
: vectors(1), mTime(wTime)
|
||||
{
|
||||
vectors[0] = vector;
|
||||
}
|
||||
|
||||
radioVector::~radioVector()
|
||||
{
|
||||
for (size_t i = 0; i < vectors.size(); i++)
|
||||
delete vectors[i];
|
||||
}
|
||||
|
||||
GSM::Time radioVector::getTime() const
|
||||
@@ -41,19 +56,66 @@ bool radioVector::operator>(const radioVector& other) const
|
||||
return mTime > other.mTime;
|
||||
}
|
||||
|
||||
unsigned VectorFIFO::size()
|
||||
signalVector *radioVector::getVector(size_t chan) const
|
||||
{
|
||||
return mQ.size();
|
||||
if (chan >= vectors.size())
|
||||
return NULL;
|
||||
|
||||
return vectors[chan];
|
||||
}
|
||||
|
||||
void VectorFIFO::put(radioVector *ptr)
|
||||
bool radioVector::setVector(signalVector *vector, size_t chan)
|
||||
{
|
||||
mQ.put((void*) ptr);
|
||||
if (chan >= vectors.size())
|
||||
return false;
|
||||
|
||||
vectors[chan] = vector;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
radioVector *VectorFIFO::get()
|
||||
avgVector::avgVector(size_t max)
|
||||
: std::vector<float>(0), itr(0)
|
||||
{
|
||||
return (radioVector*) mQ.get();
|
||||
this->max = max;
|
||||
}
|
||||
|
||||
float avgVector::avg() const
|
||||
{
|
||||
float val = 0.0;
|
||||
|
||||
if (!size())
|
||||
return 0.0f;
|
||||
|
||||
for (size_t i = 0; i < size(); i++)
|
||||
val += (*this)[i];
|
||||
|
||||
return val / (float) size();
|
||||
}
|
||||
|
||||
bool avgVector::insert(float val)
|
||||
{
|
||||
if (size() < max) {
|
||||
push_back(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itr >= this->size())
|
||||
itr = 0;
|
||||
|
||||
(*this)[itr++] = val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool avgVector::full() const
|
||||
{
|
||||
return size() >= max;
|
||||
}
|
||||
|
||||
void avgVector::reset()
|
||||
{
|
||||
resize(0);
|
||||
}
|
||||
|
||||
GSM::Time VectorQueue::nextTime() const
|
||||
|
@@ -26,27 +26,41 @@
|
||||
#include "GSMCommon.h"
|
||||
#include "Interthread.h"
|
||||
|
||||
class radioVector : public signalVector {
|
||||
class radioVector {
|
||||
public:
|
||||
radioVector(const signalVector& wVector, GSM::Time& wTime);
|
||||
radioVector(GSM::Time& wTime, size_t size = 0,
|
||||
size_t start = 0, size_t chans = 1);
|
||||
|
||||
radioVector(GSM::Time& wTime, signalVector *vector);
|
||||
~radioVector();
|
||||
|
||||
GSM::Time getTime() const;
|
||||
void setTime(const GSM::Time& wTime);
|
||||
bool operator>(const radioVector& other) const;
|
||||
|
||||
signalVector *getVector(size_t chan = 0) const;
|
||||
bool setVector(signalVector *vector, size_t chan = 0);
|
||||
size_t chans() const { return vectors.size(); }
|
||||
private:
|
||||
std::vector<signalVector *> vectors;
|
||||
GSM::Time mTime;
|
||||
};
|
||||
|
||||
class VectorFIFO {
|
||||
class avgVector : std::vector<float> {
|
||||
public:
|
||||
unsigned size();
|
||||
void put(radioVector *ptr);
|
||||
radioVector *get();
|
||||
avgVector(size_t size = 0);
|
||||
bool insert(float val);
|
||||
bool full() const;
|
||||
float avg() const;
|
||||
void reset();
|
||||
|
||||
private:
|
||||
PointerFIFO mQ;
|
||||
size_t itr;
|
||||
size_t max;
|
||||
};
|
||||
|
||||
class VectorFIFO : public InterthreadQueue<radioVector> { };
|
||||
|
||||
class VectorQueue : public InterthreadPriorityQueue<radioVector> {
|
||||
public:
|
||||
GSM::Time nextTime() const;
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "Transceiver.h"
|
||||
#include "radioDevice.h"
|
||||
#include "DummyLoad.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <GSMCommon.h>
|
||||
#include <Logger.h>
|
||||
#include <Configuration.h>
|
||||
|
||||
#ifdef RESAMPLE
|
||||
#define DEVICERATE 400e3
|
||||
#else
|
||||
#define DEVICERATE 1625e3/6
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
ConfigurationTable gConfig("/etc/OpenBTS/OpenBTS.db");
|
||||
|
||||
|
||||
volatile bool gbShutdown = false;
|
||||
static void ctrlCHandler(int signo)
|
||||
{
|
||||
cout << "Received shutdown signal" << endl;;
|
||||
gbShutdown = true;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
std::string deviceArgs;
|
||||
|
||||
if (argc == 3)
|
||||
{
|
||||
deviceArgs = std::string(argv[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceArgs = "";
|
||||
}
|
||||
|
||||
if ( signal( SIGINT, ctrlCHandler ) == SIG_ERR )
|
||||
{
|
||||
cerr << "Couldn't install signal handler for SIGINT" << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ( signal( SIGTERM, ctrlCHandler ) == SIG_ERR )
|
||||
{
|
||||
cerr << "Couldn't install signal handler for SIGTERM" << endl;
|
||||
exit(1);
|
||||
}
|
||||
// Configure logger.
|
||||
gLogInit("transceiver",gConfig.getStr("Log.Level").c_str(),LOG_LOCAL7);
|
||||
|
||||
int numARFCN=1;
|
||||
|
||||
LOG(NOTICE) << "starting transceiver with " << numARFCN << " ARFCNs (argc=" << argc << ")";
|
||||
|
||||
srandom(time(NULL));
|
||||
|
||||
int mOversamplingRate = numARFCN/2 + numARFCN;
|
||||
RadioDevice *usrp = RadioDevice::make(DEVICERATE * SAMPSPERSYM);
|
||||
if (!usrp->open(deviceArgs)) {
|
||||
LOG(ALERT) << "Transceiver exiting..." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
RadioInterface* radio = new RadioInterface(usrp,3,SAMPSPERSYM,mOversamplingRate,false);
|
||||
Transceiver *trx = new Transceiver(gConfig.getNum("TRX.Port"),gConfig.getStr("TRX.IP").c_str(),SAMPSPERSYM,GSM::Time(3,0),radio);
|
||||
trx->receiveFIFO(radio->receiveFIFO());
|
||||
/*
|
||||
signalVector *gsmPulse = generateGSMPulse(2,1);
|
||||
BitVector normalBurstSeg = "0000101010100111110010101010010110101110011000111001101010000";
|
||||
BitVector normalBurst(BitVector(normalBurstSeg,gTrainingSequence[0]),normalBurstSeg);
|
||||
signalVector *modBurst = modulateBurst(normalBurst,*gsmPulse,8,1);
|
||||
signalVector *modBurst9 = modulateBurst(normalBurst,*gsmPulse,9,1);
|
||||
signalVector *interpolationFilter = createLPF(0.6/mOversamplingRate,6*mOversamplingRate,1);
|
||||
signalVector totalBurst1(*modBurst,*modBurst9);
|
||||
signalVector totalBurst2(*modBurst,*modBurst);
|
||||
signalVector totalBurst(totalBurst1,totalBurst2);
|
||||
scaleVector(totalBurst,usrp->fullScaleInputValue());
|
||||
double beaconFreq = -1.0*(numARFCN-1)*200e3;
|
||||
signalVector finalVec(625*mOversamplingRate);
|
||||
for (int j = 0; j < numARFCN; j++) {
|
||||
signalVector *frequencyShifter = new signalVector(625*mOversamplingRate);
|
||||
frequencyShifter->fill(1.0);
|
||||
frequencyShift(frequencyShifter,frequencyShifter,2.0*M_PI*(beaconFreq+j*400e3)/(1625.0e3/6.0*mOversamplingRate));
|
||||
signalVector *interpVec = polyphaseResampleVector(totalBurst,mOversamplingRate,1,interpolationFilter);
|
||||
multVector(*interpVec,*frequencyShifter);
|
||||
addVector(finalVec,*interpVec);
|
||||
}
|
||||
signalVector::iterator itr = finalVec.begin();
|
||||
short finalVecShort[2*finalVec.size()];
|
||||
short *shortItr = finalVecShort;
|
||||
while (itr < finalVec.end()) {
|
||||
*shortItr++ = (short) (itr->real());
|
||||
*shortItr++ = (short) (itr->imag());
|
||||
itr++;
|
||||
}
|
||||
usrp->loadBurst(finalVecShort,finalVec.size());
|
||||
*/
|
||||
trx->start();
|
||||
//int i = 0;
|
||||
while(!gbShutdown) { sleep(1); }//i++; if (i==60) break;}
|
||||
|
||||
cout << "Shutting down transceiver..." << endl;
|
||||
|
||||
// trx->stop();
|
||||
delete trx;
|
||||
// delete radio;
|
||||
}
|
219
Transceiver52M/sch.c
Normal file
219
Transceiver52M/sch.c
Normal file
@@ -0,0 +1,219 @@
|
||||
#include <complex.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/conv.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/crcgen.h>
|
||||
|
||||
#include "sch.h"
|
||||
|
||||
/* GSM 04.08, 9.1.30 Synchronization channel information */
|
||||
struct sch_packed_info {
|
||||
ubit_t t1_hi[2];
|
||||
ubit_t bsic[6];
|
||||
ubit_t t1_md[8];
|
||||
ubit_t t3p_hi[2];
|
||||
ubit_t t2[5];
|
||||
ubit_t t1_lo[1];
|
||||
ubit_t t3p_lo[1];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct sch_burst {
|
||||
sbit_t tail0[3];
|
||||
sbit_t data0[39];
|
||||
sbit_t etsc[64];
|
||||
sbit_t data1[39];
|
||||
sbit_t tail1[3];
|
||||
sbit_t guard[8];
|
||||
} __attribute__((packed));
|
||||
|
||||
static const uint8_t sch_next_output[][2] = {
|
||||
{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
|
||||
{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
|
||||
{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
|
||||
{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
|
||||
};
|
||||
|
||||
static const uint8_t sch_next_state[][2] = {
|
||||
{ 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
|
||||
{ 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
|
||||
{ 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 },
|
||||
{ 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
|
||||
};
|
||||
|
||||
static const struct osmo_conv_code gsm_conv_sch = {
|
||||
.N = 2,
|
||||
.K = 5,
|
||||
.len = GSM_SCH_UNCODED_LEN,
|
||||
.next_output = sch_next_output,
|
||||
.next_state = sch_next_state,
|
||||
};
|
||||
|
||||
const struct osmo_crc16gen_code gsm0503_sch_crc10 = {
|
||||
.bits = 10,
|
||||
.poly = 0x175,
|
||||
.init = 0x000,
|
||||
.remainder = 0x3ff,
|
||||
};
|
||||
|
||||
#define GSM_MAX_BURST_LEN 157
|
||||
#define GSM_SYM_RATE (1625e3 / 6)
|
||||
|
||||
/* Pre-generated FCCH measurement tone */
|
||||
static complex float fcch_ref[GSM_MAX_BURST_LEN];
|
||||
|
||||
int float_to_sbit(const float *in, sbit_t *out, float scale, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
out[i] = (in[i] - 0.5f) * scale;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if FN contains a SCH burst */
|
||||
int gsm_sch_check_fn(int fn)
|
||||
{
|
||||
int fn51 = fn % 51;
|
||||
|
||||
switch (fn51) {
|
||||
case 1:
|
||||
case 11:
|
||||
case 21:
|
||||
case 31:
|
||||
case 41:
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SCH (T1, T2, T3p) to full FN value */
|
||||
int gsm_sch_to_fn(struct sch_info *sch)
|
||||
{
|
||||
int t1 = sch->t1;
|
||||
int t2 = sch->t2;
|
||||
int t3p = sch->t3p;
|
||||
|
||||
if ((t1 < 0) || (t2 < 0) || (t3p < 0))
|
||||
return -1;
|
||||
int tt;
|
||||
int t3 = t3p * 10 + 1;
|
||||
|
||||
if (t3 < t2)
|
||||
tt = (t3 + 26) - t2;
|
||||
else
|
||||
tt = (t3 - t2) % 26;
|
||||
|
||||
return t1 * 51 * 26 + tt * 51 + t3;
|
||||
}
|
||||
|
||||
/* Parse encoded SCH message */
|
||||
int gsm_sch_parse(const uint8_t *info, struct sch_info *desc)
|
||||
{
|
||||
struct sch_packed_info *p = (struct sch_packed_info *) info;
|
||||
|
||||
desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) |
|
||||
(p->bsic[2] << 2) | (p->bsic[3] << 3) |
|
||||
(p->bsic[4] << 4);
|
||||
|
||||
desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) |
|
||||
(p->t1_md[1] << 2) | (p->t1_md[2] << 3) |
|
||||
(p->t1_md[3] << 4) | (p->t1_md[4] << 5) |
|
||||
(p->t1_md[5] << 6) | (p->t1_md[6] << 7) |
|
||||
(p->t1_md[7] << 8) | (p->t1_hi[0] << 9) |
|
||||
(p->t1_hi[1] << 10);
|
||||
|
||||
desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) |
|
||||
(p->t2[2] << 2) | (p->t2[3] << 3) |
|
||||
(p->t2[4] << 4);
|
||||
|
||||
desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) |
|
||||
(p->t3p_hi[1] << 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* From osmo-bts */
|
||||
int gsm_sch_decode(uint8_t *info, sbit_t *data)
|
||||
{
|
||||
int rc;
|
||||
ubit_t uncoded[GSM_SCH_UNCODED_LEN];
|
||||
|
||||
osmo_conv_decode(&gsm_conv_sch, data, uncoded);
|
||||
|
||||
rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10,
|
||||
uncoded, GSM_SCH_INFO_LEN,
|
||||
uncoded + GSM_SCH_INFO_LEN);
|
||||
if (rc)
|
||||
return -1;
|
||||
|
||||
memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define FCCH_TAIL_BITS_LEN 3
|
||||
#define FCCH_DATA_LEN 142
|
||||
|
||||
/* Compute FCCH frequency offset */
|
||||
double gsm_fcch_offset(float *burst, int len)
|
||||
{
|
||||
int i, start, end;
|
||||
float a, b, c, d, ang, avg = 0.0f;
|
||||
double freq;
|
||||
|
||||
if (len > GSM_MAX_BURST_LEN)
|
||||
len = GSM_MAX_BURST_LEN;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
a = burst[2 * i + 0];
|
||||
b = burst[2 * i + 1];
|
||||
c = crealf(fcch_ref[i]);
|
||||
d = cimagf(fcch_ref[i]);
|
||||
|
||||
burst[2 * i + 0] = a * c - b * d;
|
||||
burst[2 * i + 1] = a * d + b * c;
|
||||
}
|
||||
|
||||
start = FCCH_TAIL_BITS_LEN;
|
||||
end = start + FCCH_DATA_LEN;
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
a = cargf(burst[2 * (i - 1) + 0] +
|
||||
burst[2 * (i - 1) + 1] * I);
|
||||
b = cargf(burst[2 * i + 0] +
|
||||
burst[2 * i + 1] * I);
|
||||
|
||||
ang = b - a;
|
||||
|
||||
if (ang > M_PI)
|
||||
ang -= 2 * M_PI;
|
||||
else if (ang < -M_PI)
|
||||
ang += 2 * M_PI;
|
||||
|
||||
avg += ang;
|
||||
}
|
||||
|
||||
avg /= (float) (end - start);
|
||||
freq = avg / (2 * M_PI) * GSM_SYM_RATE;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/* Generate FCCH measurement tone */
|
||||
static __attribute__((constructor)) void init()
|
||||
{
|
||||
int i;
|
||||
double freq = 0.25;
|
||||
|
||||
for (i = 0; i < GSM_MAX_BURST_LEN; i++) {
|
||||
fcch_ref[i] = sin(2 * M_PI * freq * (double) i) +
|
||||
cos(2 * M_PI * freq * (double) i) * I;
|
||||
}
|
||||
}
|
26
Transceiver52M/sch.h
Normal file
26
Transceiver52M/sch.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _SCH_H_
|
||||
#define _SCH_H_
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
|
||||
struct sch_info {
|
||||
int bsic;
|
||||
int t1;
|
||||
int t2;
|
||||
int t3p;
|
||||
};
|
||||
|
||||
#define GSM_SCH_INFO_LEN 25
|
||||
#define GSM_SCH_UNCODED_LEN 35
|
||||
#define GSM_SCH_CODED_LEN 78
|
||||
|
||||
int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
|
||||
int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
|
||||
int gsm_sch_to_fn(struct sch_info *sch);
|
||||
int gsm_sch_check_fn(int fn);
|
||||
|
||||
double gsm_fcch_offset(float *burst, int len);
|
||||
|
||||
int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
|
||||
|
||||
#endif /* _SCH_H_ */
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -18,73 +18,14 @@
|
||||
#include "Vector.h"
|
||||
#include "Complex.h"
|
||||
#include "BitVector.h"
|
||||
|
||||
/** Indicated signalVector symmetry */
|
||||
enum Symmetry {
|
||||
NONE = 0,
|
||||
ABSSYM = 1
|
||||
};
|
||||
#include "signalVector.h"
|
||||
|
||||
/** Convolution type indicator */
|
||||
enum ConvType {
|
||||
FULL_SPAN = 0,
|
||||
OVERLAP_ONLY = 1,
|
||||
START_ONLY = 2,
|
||||
WITH_TAIL = 3,
|
||||
NO_DELAY = 4,
|
||||
CUSTOM = 5,
|
||||
UNDEFINED = 255
|
||||
};
|
||||
|
||||
/** the core data structure of the Transceiver */
|
||||
class signalVector: public Vector<complex>
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
Symmetry symmetry; ///< the symmetry of the vector
|
||||
bool realOnly; ///< true if vector is real-valued, not complex-valued
|
||||
|
||||
public:
|
||||
|
||||
/** Constructors */
|
||||
signalVector(int dSize=0, Symmetry wSymmetry = NONE):
|
||||
Vector<complex>(dSize),
|
||||
realOnly(false)
|
||||
{
|
||||
symmetry = wSymmetry;
|
||||
};
|
||||
|
||||
signalVector(complex* wData, size_t start,
|
||||
size_t span, Symmetry wSymmetry = NONE):
|
||||
Vector<complex>(NULL,wData+start,wData+start+span),
|
||||
realOnly(false)
|
||||
{
|
||||
symmetry = wSymmetry;
|
||||
};
|
||||
|
||||
signalVector(const signalVector &vec1, const signalVector &vec2):
|
||||
Vector<complex>(vec1,vec2),
|
||||
realOnly(false)
|
||||
{
|
||||
symmetry = vec1.symmetry;
|
||||
};
|
||||
|
||||
signalVector(const signalVector &wVector):
|
||||
Vector<complex>(wVector.size()),
|
||||
realOnly(false)
|
||||
{
|
||||
wVector.copyTo(*this);
|
||||
symmetry = wVector.getSymmetry();
|
||||
};
|
||||
|
||||
/** symmetry operators */
|
||||
Symmetry getSymmetry() const { return symmetry;};
|
||||
void setSymmetry(Symmetry wSymmetry) { symmetry = wSymmetry;};
|
||||
|
||||
/** real-valued operators */
|
||||
bool isRealOnly() const { return realOnly;};
|
||||
void isRealOnly(bool wOnly) { realOnly = wOnly;};
|
||||
START_ONLY,
|
||||
NO_DELAY,
|
||||
CUSTOM,
|
||||
UNDEFINED,
|
||||
};
|
||||
|
||||
/** Convert a linear number to a dB value */
|
||||
@@ -100,7 +41,7 @@ float vectorNorm2(const signalVector &x);
|
||||
float vectorPower(const signalVector &x);
|
||||
|
||||
/** Setup the signal processing library */
|
||||
void sigProcLibSetup(int samplesPerSymbol);
|
||||
bool sigProcLibSetup(int sps);
|
||||
|
||||
/** Destroy the signal processing library */
|
||||
void sigProcLibDestroy(void);
|
||||
@@ -110,23 +51,12 @@ void sigProcLibDestroy(void);
|
||||
@param a,b The vectors to be convolved.
|
||||
@param c, A preallocated vector to hold the convolution result.
|
||||
@param spanType The type/span of the convolution.
|
||||
@return The convolution result.
|
||||
@return The convolution result or NULL on error.
|
||||
*/
|
||||
signalVector* convolve(const signalVector *a,
|
||||
const signalVector *b,
|
||||
signalVector *c,
|
||||
ConvType spanType,
|
||||
unsigned startIx = 0,
|
||||
unsigned len = 0);
|
||||
|
||||
/**
|
||||
Generate the GSM pulse.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param symbolLength The size of the pulse.
|
||||
@return The GSM pulse.
|
||||
*/
|
||||
signalVector* generateGSMPulse(int samplesPerSymbol,
|
||||
int symbolLength);
|
||||
signalVector *convolve(const signalVector *a, const signalVector *b,
|
||||
signalVector *c, ConvType spanType,
|
||||
size_t start = 0, size_t len = 0,
|
||||
size_t step = 1, int offset = 0);
|
||||
|
||||
/**
|
||||
Frequency shift a vector.
|
||||
@@ -163,16 +93,14 @@ bool vectorSlicer(signalVector *x);
|
||||
|
||||
/** GMSK modulate a GSM burst of bits */
|
||||
signalVector *modulateBurst(const BitVector &wBurst,
|
||||
const signalVector &gsmPulse,
|
||||
int guardPeriodLength,
|
||||
int samplesPerSymbol);
|
||||
int sps, bool emptyPulse = false);
|
||||
|
||||
/** Sinc function */
|
||||
float sinc(float x);
|
||||
|
||||
/** Delay a vector */
|
||||
void delayVector(signalVector &wBurst,
|
||||
float delay);
|
||||
signalVector *delayVector(signalVector *in, signalVector *out, float delay);
|
||||
|
||||
/** Add two vectors in-place */
|
||||
bool addVector(signalVector &x,
|
||||
@@ -215,32 +143,22 @@ complex peakDetect(const signalVector &rxBurst,
|
||||
void scaleVector(signalVector &x,
|
||||
complex scale);
|
||||
|
||||
/**
|
||||
Add a constant offset to a vecotr.
|
||||
@param x The vector of interest.
|
||||
@param offset The offset.
|
||||
*/
|
||||
void offsetVector(signalVector &x,
|
||||
complex offset);
|
||||
|
||||
/**
|
||||
Generate a modulated GSM midamble, stored within the library.
|
||||
@param gsmPulse The GSM pulse used for modulation.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@param TSC The training sequence [0..7]
|
||||
@return Success.
|
||||
*/
|
||||
bool generateMidamble(signalVector &gsmPulse,
|
||||
int samplesPerSymbol,
|
||||
int TSC);
|
||||
bool generateMidamble(int sps, int tsc);
|
||||
/**
|
||||
Generate a modulated RACH sequence, stored within the library.
|
||||
@param gsmPulse The GSM pulse used for modulation.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@return Success.
|
||||
*/
|
||||
bool generateRACHSequence(signalVector &gsmPulse,
|
||||
int samplesPerSymbol);
|
||||
bool generateRACHSequence(int sps);
|
||||
bool generateSCHSequence(int sps);
|
||||
|
||||
/**
|
||||
Energy detector, checks to see if received burst energy is above a threshold.
|
||||
@@ -259,99 +177,72 @@ bool energyDetect(signalVector &rxBurst,
|
||||
RACH correlator/detector.
|
||||
@param rxBurst The received GSM burst of interest.
|
||||
@param detectThreshold The threshold that the received burst's post-correlator SNR is compared against to determine validity.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@param amplitude The estimated amplitude of received RACH burst.
|
||||
@param TOA The estimate time-of-arrival of received RACH burst.
|
||||
@return True if burst SNR is larger that the detectThreshold value.
|
||||
@return positive if threshold value is reached, negative on error, zero otherwise
|
||||
*/
|
||||
bool detectRACHBurst(signalVector &rxBurst,
|
||||
float detectThreshold,
|
||||
int samplesPerSymbol,
|
||||
complex *amplitude,
|
||||
float* TOA);
|
||||
int detectRACHBurst(signalVector &rxBurst,
|
||||
float detectThreshold,
|
||||
int sps,
|
||||
complex *amplitude,
|
||||
float* TOA);
|
||||
|
||||
enum {
|
||||
SCH_DETECT_FULL,
|
||||
SCH_DETECT_NARROW,
|
||||
};
|
||||
|
||||
int detectSCHBurst(signalVector &rxBurst,
|
||||
float detectThreshold,
|
||||
int sps,
|
||||
complex *amplitude,
|
||||
float* TOA, int state);
|
||||
|
||||
/**
|
||||
Normal burst correlator, detector, channel estimator.
|
||||
@param rxBurst The received GSM burst of interest.
|
||||
|
||||
@param detectThreshold The threshold that the received burst's post-correlator SNR is compared against to determine validity.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@param amplitude The estimated amplitude of received TSC burst.
|
||||
@param TOA The estimate time-of-arrival of received TSC burst.
|
||||
@param maxTOA The maximum expected time-of-arrival
|
||||
@param requestChannel Set to true if channel estimation is desired.
|
||||
@param channelResponse The estimated channel.
|
||||
@param channelResponseOffset The time offset b/w the first sample of the channel response and the reported TOA.
|
||||
@return True if burst SNR is larger that the detectThreshold value.
|
||||
@return positive if threshold value is reached, negative on error, zero otherwise
|
||||
*/
|
||||
bool analyzeTrafficBurst(signalVector &rxBurst,
|
||||
unsigned TSC,
|
||||
float detectThreshold,
|
||||
int samplesPerSymbol,
|
||||
complex *amplitude,
|
||||
float *TOA,
|
||||
unsigned maxTOA,
|
||||
bool requestChannel = false,
|
||||
signalVector** channelResponse = NULL,
|
||||
float *channelResponseOffset = NULL);
|
||||
int analyzeTrafficBurst(signalVector &rxBurst,
|
||||
unsigned TSC,
|
||||
float detectThreshold,
|
||||
int sps,
|
||||
complex *amplitude,
|
||||
float *TOA,
|
||||
unsigned maxTOA,
|
||||
bool requestChannel = false,
|
||||
signalVector** channelResponse = NULL,
|
||||
float *channelResponseOffset = NULL);
|
||||
|
||||
/**
|
||||
Decimate a vector.
|
||||
@param wVector The vector of interest.
|
||||
@param decimationFactor The amount of decimation, i.e. the decimation factor.
|
||||
@param factor Decimation factor.
|
||||
@return The decimated signal vector.
|
||||
*/
|
||||
signalVector *decimateVector(signalVector &wVector,
|
||||
int decimationFactor);
|
||||
signalVector *decimateVector(signalVector &wVector, size_t factor);
|
||||
|
||||
/**
|
||||
Demodulates a received burst using a soft-slicer.
|
||||
@param rxBurst The burst to be demodulated.
|
||||
@param gsmPulse The GSM pulse.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@param channel The amplitude estimate of the received burst.
|
||||
@param TOA The time-of-arrival of the received burst.
|
||||
@return The demodulated bit sequence.
|
||||
*/
|
||||
SoftVector *demodulateBurst(signalVector &rxBurst,
|
||||
const signalVector &gsmPulse,
|
||||
int samplesPerSymbol,
|
||||
complex channel,
|
||||
float TOA);
|
||||
|
||||
/**
|
||||
Creates a simple Kaiser-windowed low-pass FIR filter.
|
||||
@param cutoffFreq The digital 3dB bandwidth of the filter.
|
||||
@param filterLen The number of taps in the filter.
|
||||
@param gainDC The DC gain of the filter.
|
||||
@return The desired LPF
|
||||
*/
|
||||
signalVector *createLPF(float cutoffFreq,
|
||||
int filterLen,
|
||||
float gainDC = 1.0);
|
||||
|
||||
/**
|
||||
Change sampling rate of a vector via polyphase resampling.
|
||||
@param wVector The vector to be resampled.
|
||||
@param P The numerator, i.e. the amount of upsampling.
|
||||
@param Q The denominator, i.e. the amount of downsampling.
|
||||
@param LPF An optional low-pass filter used in the resampling process.
|
||||
@return A vector resampled at P/Q of the original sampling rate.
|
||||
*/
|
||||
signalVector *polyphaseResampleVector(signalVector &wVector,
|
||||
int P, int Q,
|
||||
signalVector *LPF);
|
||||
|
||||
/**
|
||||
Change the sampling rate of a vector via linear interpolation.
|
||||
@param wVector The vector to be resampled.
|
||||
@param expFactor Ratio of new sampling rate/original sampling rate.
|
||||
@param endPoint ???
|
||||
@return A vector resampled a expFactor*original sampling rate.
|
||||
*/
|
||||
signalVector *resampleVector(signalVector &wVector,
|
||||
float expFactor,
|
||||
complex endPoint);
|
||||
SoftVector *demodulateBurst(signalVector &rxBurst, int sps,
|
||||
complex channel, float TOA);
|
||||
|
||||
/**
|
||||
Design the necessary filters for a decision-feedback equalizer.
|
||||
@@ -372,14 +263,14 @@ bool designDFE(signalVector &channelResponse,
|
||||
Equalize/demodulate a received burst via a decision-feedback equalizer.
|
||||
@param rxBurst The received burst to be demodulated.
|
||||
@param TOA The time-of-arrival of the received burst.
|
||||
@param samplesPerSymbol The number of samples per GSM symbol.
|
||||
@param sps The number of samples per GSM symbol.
|
||||
@param w The feed forward filter of the DFE.
|
||||
@param b The feedback filter of the DFE.
|
||||
@return The demodulated bit sequence.
|
||||
*/
|
||||
SoftVector *equalizeBurst(signalVector &rxBurst,
|
||||
float TOA,
|
||||
int samplesPerSymbol,
|
||||
int sps,
|
||||
signalVector &w,
|
||||
signalVector &b);
|
||||
|
||||
|
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Contributors:
|
||||
Harvind S. Samra, hssamra@kestrelsp.com
|
||||
*/
|
||||
|
||||
|
||||
#include "sigProcLib.h"
|
||||
//#include "radioInterface.h"
|
||||
#include <Logger.h>
|
||||
#include <Configuration.h>
|
||||
#include <GSMCommon.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
|
||||
ConfigurationTable gConfig;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
gLogInit("sigProcLibTest","DEBUG");
|
||||
|
||||
int samplesPerSymbol = 1;
|
||||
|
||||
int TSC = 2;
|
||||
|
||||
sigProcLibSetup(samplesPerSymbol);
|
||||
|
||||
signalVector *gsmPulse = generateGSMPulse(2,samplesPerSymbol);
|
||||
cout << *gsmPulse << endl;
|
||||
|
||||
BitVector RACHBurstStart = "01010101";
|
||||
BitVector RACHBurstRest = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
BitVector RACHBurst(BitVector(RACHBurstStart,gRACHSynchSequence),RACHBurstRest);
|
||||
|
||||
|
||||
signalVector *RACHSeq = modulateBurst(RACHBurst,
|
||||
*gsmPulse,
|
||||
9,
|
||||
samplesPerSymbol);
|
||||
|
||||
generateRACHSequence(*gsmPulse,samplesPerSymbol);
|
||||
|
||||
complex a; float t;
|
||||
detectRACHBurst(*RACHSeq, 5, samplesPerSymbol,&a,&t);
|
||||
|
||||
//cout << *RACHSeq << endl;
|
||||
//signalVector *autocorr = correlate(RACHSeq,RACHSeq,NULL,NO_DELAY);
|
||||
|
||||
//cout << *autocorr;
|
||||
|
||||
//exit(1);
|
||||
|
||||
|
||||
/*signalVector x(6500);
|
||||
x.fill(1.0);
|
||||
|
||||
frequencyShift(&x,&x,0.48*M_PI);
|
||||
|
||||
signalVector *y = polyphaseResampleVector(x,96,65,NULL);
|
||||
|
||||
cout << *y << endl;
|
||||
|
||||
exit(1);*/
|
||||
|
||||
//CommSig normalBurstSeg = "0000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
BitVector normalBurstSeg = "0000101010100111110010101010010110101110011000111001101010000";
|
||||
|
||||
BitVector normalBurst(BitVector(normalBurstSeg,gTrainingSequence[TSC]),normalBurstSeg);
|
||||
|
||||
|
||||
generateMidamble(*gsmPulse,samplesPerSymbol,TSC);
|
||||
|
||||
|
||||
signalVector *modBurst = modulateBurst(normalBurst,*gsmPulse,
|
||||
0,samplesPerSymbol);
|
||||
|
||||
|
||||
//delayVector(*rsVector2,6.932);
|
||||
|
||||
complex ampl = 1;
|
||||
float TOA = 0;
|
||||
|
||||
//modBurst = rsVector2;
|
||||
//delayVector(*modBurst,0.8);
|
||||
|
||||
/*
|
||||
signalVector channelResponse(4);
|
||||
signalVector::iterator c=channelResponse.begin();
|
||||
*c = (complex) 9000.0; c++;
|
||||
*c = (complex) 0.4*9000.0; c++; c++;
|
||||
*c = (complex) -1.2*0;
|
||||
|
||||
signalVector *guhBurst = convolve(modBurst,&channelResponse,NULL,NO_DELAY);
|
||||
delete modBurst; modBurst = guhBurst;
|
||||
*/
|
||||
|
||||
signalVector *chanResp;
|
||||
/*
|
||||
double noisePwr = 0.001/sqrtf(2);
|
||||
signalVector *noise = gaussianNoise(modBurst->size(),noisePwr);
|
||||
*/
|
||||
float chanRespOffset;
|
||||
analyzeTrafficBurst(*modBurst,TSC,8.0,samplesPerSymbol,&l,&TOA,1,true,&chanResp,&chanRespOffset);
|
||||
//addVector(*modBurst,*noise);
|
||||
|
||||
cout << "ampl:" << ampl << endl;
|
||||
cout << "TOA: " << TOA << endl;
|
||||
//cout << "chanResp: " << *chanResp << endl;
|
||||
SoftVector *demodBurst = demodulateBurst(*modBurst,*gsmPulse,samplesPerSymbol,(complex) ampl, TOA);
|
||||
|
||||
cout << *demodBurst << endl;
|
||||
|
||||
/*
|
||||
COUT("chanResp: " << *chanResp);
|
||||
|
||||
signalVector *w,*b;
|
||||
designDFE(*chanResp,1.0/noisePwr,7,&w,&b);
|
||||
COUT("w: " << *w);
|
||||
COUT("b: " << *b);
|
||||
|
||||
|
||||
SoftSig *DFEBurst = equalizeBurst(*modBurst,TOA-chanRespOffset,samplesPerSymbol,*w,*b);
|
||||
COUT("DFEBurst: " << *DFEBurst);
|
||||
|
||||
delete gsmPulse;
|
||||
delete RACHSeq;
|
||||
delete modBurst;
|
||||
delete sendLPF;
|
||||
delete rcvLPF;
|
||||
delete rsVector;
|
||||
//delete rsVector2;
|
||||
delete autocorr;
|
||||
delete chanResp;
|
||||
delete noise;
|
||||
delete demodBurst;
|
||||
delete w;
|
||||
delete b;
|
||||
delete DFEBurst;
|
||||
*/
|
||||
|
||||
sigProcLibDestroy();
|
||||
|
||||
}
|
81
Transceiver52M/signalVector.cpp
Normal file
81
Transceiver52M/signalVector.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "signalVector.h"
|
||||
|
||||
signalVector::signalVector(size_t size)
|
||||
: Vector<complex>(size),
|
||||
real(false), aligned(false), symmetry(NONE)
|
||||
{
|
||||
}
|
||||
|
||||
signalVector::signalVector(size_t size, size_t start)
|
||||
: Vector<complex>(size + start),
|
||||
real(false), aligned(false), symmetry(NONE)
|
||||
{
|
||||
mStart = mData + start;
|
||||
}
|
||||
|
||||
signalVector::signalVector(complex *data, size_t start, size_t span)
|
||||
: Vector<complex>(NULL, data + start, data + start + span),
|
||||
real(false), aligned(false), symmetry(NONE)
|
||||
{
|
||||
}
|
||||
|
||||
signalVector::signalVector(const signalVector &vector)
|
||||
: Vector<complex>(vector.size() + vector.getStart()), aligned(false)
|
||||
{
|
||||
mStart = mData + vector.getStart();
|
||||
vector.copyTo(*this);
|
||||
symmetry = vector.getSymmetry();
|
||||
real = vector.isReal();
|
||||
};
|
||||
|
||||
signalVector::signalVector(const signalVector &vector,
|
||||
size_t start, size_t tail)
|
||||
: Vector<complex>(start + vector.size() + tail), aligned(false)
|
||||
{
|
||||
mStart = mData + start;
|
||||
vector.copyTo(*this);
|
||||
symmetry = vector.getSymmetry();
|
||||
real = vector.isReal();
|
||||
};
|
||||
|
||||
void signalVector::operator=(const signalVector& vector)
|
||||
{
|
||||
resize(vector.size() + vector.getStart());
|
||||
memcpy(mData, vector.mData, bytes());
|
||||
mStart = mData + vector.getStart();
|
||||
}
|
||||
|
||||
size_t signalVector::getStart() const
|
||||
{
|
||||
return mStart - mData;
|
||||
}
|
||||
|
||||
Symmetry signalVector::getSymmetry() const
|
||||
{
|
||||
return symmetry;
|
||||
}
|
||||
|
||||
void signalVector::setSymmetry(Symmetry symmetry)
|
||||
{
|
||||
this->symmetry = symmetry;
|
||||
}
|
||||
|
||||
bool signalVector::isReal() const
|
||||
{
|
||||
return real;
|
||||
}
|
||||
|
||||
void signalVector::isReal(bool wOnly)
|
||||
{
|
||||
real = wOnly;
|
||||
}
|
||||
|
||||
bool signalVector::isAligned() const
|
||||
{
|
||||
return aligned;
|
||||
}
|
||||
|
||||
void signalVector::setAligned(bool aligned)
|
||||
{
|
||||
this->aligned = aligned;
|
||||
}
|
51
Transceiver52M/signalVector.h
Normal file
51
Transceiver52M/signalVector.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef _SIGNALVECTOR_H_
|
||||
#define _SIGNALVECTOR_H_
|
||||
|
||||
#include <Vector.h>
|
||||
#include <Complex.h>
|
||||
|
||||
/** Vector symmetry */
|
||||
enum Symmetry {
|
||||
NONE = 0,
|
||||
ABSSYM = 1
|
||||
};
|
||||
|
||||
class signalVector: public Vector<complex> {
|
||||
public:
|
||||
/** Default constructor */
|
||||
signalVector(size_t size = 0);
|
||||
|
||||
/** Construct with head room */
|
||||
signalVector(size_t size, size_t start);
|
||||
|
||||
/** Construct from existing buffer data (buffer not managed) */
|
||||
signalVector(complex *data, size_t start, size_t span);
|
||||
|
||||
/** Construct by from existing vector */
|
||||
signalVector(const signalVector &vector);
|
||||
|
||||
/** Construct by from existing vector and append head-tail room */
|
||||
signalVector(const signalVector &vector, size_t start, size_t tail = 0);
|
||||
|
||||
/** Override base assignment operator to include start offsets */
|
||||
void operator=(const signalVector& vector);
|
||||
|
||||
/** Return head room */
|
||||
size_t getStart() const;
|
||||
|
||||
Symmetry getSymmetry() const;
|
||||
void setSymmetry(Symmetry symmetry);
|
||||
|
||||
bool isReal() const;
|
||||
void isReal(bool real);
|
||||
|
||||
bool isAligned() const;
|
||||
void setAligned(bool aligned);
|
||||
|
||||
private:
|
||||
bool real;
|
||||
bool aligned;
|
||||
Symmetry symmetry;
|
||||
};
|
||||
|
||||
#endif /* _SIGNALVECTOR_H_ */
|
10
Transceiver52M/x86/Makefile.am
Normal file
10
Transceiver52M/x86/Makefile.am
Normal file
@@ -0,0 +1,10 @@
|
||||
if !ARCH_ARM
|
||||
AM_CFLAGS = -Wall -std=gnu99 -march=native -I../common
|
||||
|
||||
noinst_LTLIBRARIES = libarch.la
|
||||
|
||||
libarch_la_SOURCES = \
|
||||
../common/convolve_base.c \
|
||||
convert.c \
|
||||
convolve.c
|
||||
endif
|
200
Transceiver52M/x86/convert.c
Normal file
200
Transceiver52M/x86/convert.c
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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>
|
||||
#include "convert.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
|
||||
}
|
601
Transceiver52M/x86/convolve.c
Normal file
601
Transceiver52M/x86/convolve.c
Normal file
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
* 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>
|
||||
#include "convolve.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
/* Forward declarations from base implementation */
|
||||
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);
|
||||
|
||||
int bounds_check(int x_len, int h_len, int y_len,
|
||||
int start, int len, int step);
|
||||
|
||||
#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
|
||||
|
||||
/* 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;
|
||||
}
|
72
config/ax_check_compile_flag.m4
Normal file
72
config/ax_check_compile_flag.m4
Normal file
@@ -0,0 +1,72 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# Check whether the given FLAG works with the current language's compiler
|
||||
# or gives an error. (Warnings, however, are ignored)
|
||||
#
|
||||
# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
|
||||
# success/failure.
|
||||
#
|
||||
# If EXTRA-FLAGS is defined, it is added to the current language's default
|
||||
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
|
||||
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
|
||||
# force the compiler to issue an error when a bad flag is given.
|
||||
#
|
||||
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
|
||||
# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
|
||||
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||
# gives unlimited permission to copy, distribute and modify the configure
|
||||
# scripts that are the output of Autoconf when processing the Macro. You
|
||||
# need not follow the terms of the GNU General Public License when using
|
||||
# or distributing such scripts, even though portions of the text of the
|
||||
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||
# all other use of the material that constitutes the Autoconf Macro.
|
||||
#
|
||||
# This special exception to the GPL applies to versions of the Autoconf
|
||||
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 2
|
||||
|
||||
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
|
||||
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
|
||||
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
|
||||
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
|
||||
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
|
||||
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
|
||||
[AS_VAR_SET(CACHEVAR,[yes])],
|
||||
[AS_VAR_SET(CACHEVAR,[no])])
|
||||
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
|
||||
AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
|
||||
[m4_default([$2], :)],
|
||||
[m4_default([$3], :)])
|
||||
AS_VAR_POPDEF([CACHEVAR])dnl
|
||||
])dnl AX_CHECK_COMPILE_FLAGS
|
221
config/ax_ext.m4
Normal file
221
config/ax_ext.m4
Normal file
@@ -0,0 +1,221 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_ext.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_EXT
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# Find supported SIMD extensions by requesting cpuid. When an SIMD
|
||||
# extension is found, the -m"simdextensionname" is added to SIMD_FLAGS if
|
||||
# compiler supports it. For example, if "sse2" is available, then "-msse2"
|
||||
# is added to SIMD_FLAGS.
|
||||
#
|
||||
# This macro calls:
|
||||
#
|
||||
# AC_SUBST(SIMD_FLAGS)
|
||||
#
|
||||
# And defines:
|
||||
#
|
||||
# HAVE_MMX / HAVE_SSE / HAVE_SSE2 / HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4.1 / HAVE_SSE4.2 / HAVE_AVX
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2007 Christophe Tournayre <turn3r@users.sourceforge.net>
|
||||
# Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty provided the copyright notice
|
||||
# and this notice are preserved. This file is offered as-is, without any
|
||||
# warranty.
|
||||
|
||||
#serial 12
|
||||
|
||||
AC_DEFUN([AX_EXT],
|
||||
[
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||
|
||||
case $host_cpu in
|
||||
i[[3456]]86*|x86_64*|amd64*)
|
||||
|
||||
AC_REQUIRE([AX_GCC_X86_CPUID])
|
||||
AC_REQUIRE([AX_GCC_X86_AVX_XGETBV])
|
||||
|
||||
AX_GCC_X86_CPUID(0x00000001)
|
||||
ecx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 3`
|
||||
edx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 4`
|
||||
|
||||
AC_CACHE_CHECK([whether mmx is supported], [ax_cv_have_mmx_ext],
|
||||
[
|
||||
ax_cv_have_mmx_ext=no
|
||||
if test "$((0x$edx>>23&0x01))" = 1; then
|
||||
ax_cv_have_mmx_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether sse is supported], [ax_cv_have_sse_ext],
|
||||
[
|
||||
ax_cv_have_sse_ext=no
|
||||
if test "$((0x$edx>>25&0x01))" = 1; then
|
||||
ax_cv_have_sse_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether sse2 is supported], [ax_cv_have_sse2_ext],
|
||||
[
|
||||
ax_cv_have_sse2_ext=no
|
||||
if test "$((0x$edx>>26&0x01))" = 1; then
|
||||
ax_cv_have_sse2_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether sse3 is supported], [ax_cv_have_sse3_ext],
|
||||
[
|
||||
ax_cv_have_sse3_ext=no
|
||||
if test "$((0x$ecx&0x01))" = 1; then
|
||||
ax_cv_have_sse3_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether ssse3 is supported], [ax_cv_have_ssse3_ext],
|
||||
[
|
||||
ax_cv_have_ssse3_ext=no
|
||||
if test "$((0x$ecx>>9&0x01))" = 1; then
|
||||
ax_cv_have_ssse3_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether sse4.1 is supported], [ax_cv_have_sse41_ext],
|
||||
[
|
||||
ax_cv_have_sse41_ext=no
|
||||
if test "$((0x$ecx>>19&0x01))" = 1; then
|
||||
ax_cv_have_sse41_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether sse4.2 is supported], [ax_cv_have_sse42_ext],
|
||||
[
|
||||
ax_cv_have_sse42_ext=no
|
||||
if test "$((0x$ecx>>20&0x01))" = 1; then
|
||||
ax_cv_have_sse42_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
AC_CACHE_CHECK([whether avx is supported by processor], [ax_cv_have_avx_cpu_ext],
|
||||
[
|
||||
ax_cv_have_avx_cpu_ext=no
|
||||
if test "$((0x$ecx>>28&0x01))" = 1; then
|
||||
ax_cv_have_avx_cpu_ext=yes
|
||||
fi
|
||||
])
|
||||
|
||||
if test x"$ax_cv_have_avx_cpu_ext" = x"yes"; then
|
||||
AX_GCC_X86_AVX_XGETBV(0x00000000)
|
||||
|
||||
xgetbv_eax="0"
|
||||
if test x"$ax_cv_gcc_x86_avx_xgetbv_0x00000000" != x"unknown"; then
|
||||
xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d ":" -f 1`
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([whether avx is supported by operating system], [ax_cv_have_avx_ext],
|
||||
[
|
||||
ax_cv_have_avx_ext=no
|
||||
|
||||
if test "$((0x$ecx>>27&0x01))" = 1; then
|
||||
if test "$((0x$xgetbv_eax&0x6))" = 6; then
|
||||
ax_cv_have_avx_ext=yes
|
||||
fi
|
||||
fi
|
||||
])
|
||||
if test x"$ax_cv_have_avx_ext" = x"no"; then
|
||||
AC_MSG_WARN([Your processor supports AVX, but your operating system doesn't])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_mmx_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-mmmx, ax_cv_support_mmx_ext=yes, [])
|
||||
if test x"$ax_cv_support_mmx_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -mmmx"
|
||||
AC_DEFINE(HAVE_MMX,,[Support mmx instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports mmx instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_sse_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-msse, ax_cv_support_sse_ext=yes, [])
|
||||
if test x"$ax_cv_support_sse_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -msse"
|
||||
AC_DEFINE(HAVE_SSE,,[Support SSE (Streaming SIMD Extensions) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports sse instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_sse2_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-msse2, ax_cv_support_sse2_ext=yes, [])
|
||||
if test x"$ax_cv_support_sse2_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -msse2"
|
||||
AC_DEFINE(HAVE_SSE2,,[Support SSE2 (Streaming SIMD Extensions 2) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports sse2 instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_sse3_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-msse3, ax_cv_support_sse3_ext=yes, [])
|
||||
if test x"$ax_cv_support_sse3_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -msse3"
|
||||
AC_DEFINE(HAVE_SSE3,,[Support SSE3 (Streaming SIMD Extensions 3) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports sse3 instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_ssse3_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-mssse3, ax_cv_support_ssse3_ext=yes, [])
|
||||
if test x"$ax_cv_support_ssse3_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -mssse3"
|
||||
AC_DEFINE(HAVE_SSSE3,,[Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports ssse3 instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_sse41_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-msse4.1, ax_cv_support_sse41_ext=yes, [])
|
||||
if test x"$ax_cv_support_sse41_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -msse4.1"
|
||||
AC_DEFINE(HAVE_SSE4_1,,[Support SSSE4.1 (Streaming SIMD Extensions 4.1) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports sse4.1 instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_sse42_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-msse4.2, ax_cv_support_sse42_ext=yes, [])
|
||||
if test x"$ax_cv_support_sse42_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -msse4.2"
|
||||
AC_DEFINE(HAVE_SSE4_2,,[Support SSSE4.2 (Streaming SIMD Extensions 4.2) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports sse4.2 instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$ax_cv_have_avx_ext" = yes; then
|
||||
AX_CHECK_COMPILE_FLAG(-mavx, ax_cv_support_avx_ext=yes, [])
|
||||
if test x"$ax_cv_support_avx_ext" = x"yes"; then
|
||||
SIMD_FLAGS="$SIMD_FLAGS -mavx"
|
||||
AC_DEFINE(HAVE_AVX,,[Support AVX (Advanced Vector Extensions) instructions])
|
||||
else
|
||||
AC_MSG_WARN([Your processor supports avx instructions but not your compiler, can you try another compiler?])
|
||||
fi
|
||||
fi
|
||||
|
||||
;;
|
||||
esac
|
||||
|
||||
AC_SUBST(SIMD_FLAGS)
|
||||
])
|
79
config/ax_gcc_x86_avx_xgetbv.m4
Normal file
79
config/ax_gcc_x86_avx_xgetbv.m4
Normal file
@@ -0,0 +1,79 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_GCC_X86_AVX_XGETBV
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# On later x86 processors with AVX SIMD support, with gcc or a compiler
|
||||
# that has a compatible syntax for inline assembly instructions, run a
|
||||
# small program that executes the xgetbv instruction with input OP. This
|
||||
# can be used to detect if the OS supports AVX instruction usage.
|
||||
#
|
||||
# On output, the values of the eax and edx registers are stored as
|
||||
# hexadecimal strings as "eax:edx" in the cache variable
|
||||
# ax_cv_gcc_x86_avx_xgetbv.
|
||||
#
|
||||
# If the xgetbv instruction fails (because you are running a
|
||||
# cross-compiler, or because you are not using gcc, or because you are on
|
||||
# a processor that doesn't have this instruction),
|
||||
# ax_cv_gcc_x86_avx_xgetbv_OP is set to the string "unknown".
|
||||
#
|
||||
# This macro mainly exists to be used in AX_EXT.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||
# gives unlimited permission to copy, distribute and modify the configure
|
||||
# scripts that are the output of Autoconf when processing the Macro. You
|
||||
# need not follow the terms of the GNU General Public License when using
|
||||
# or distributing such scripts, even though portions of the text of the
|
||||
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||
# all other use of the material that constitutes the Autoconf Macro.
|
||||
#
|
||||
# This special exception to the GPL applies to versions of the Autoconf
|
||||
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 1
|
||||
|
||||
AC_DEFUN([AX_GCC_X86_AVX_XGETBV],
|
||||
[AC_REQUIRE([AC_PROG_CC])
|
||||
AC_LANG_PUSH([C])
|
||||
AC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1,
|
||||
[AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [
|
||||
int op = $1, eax, edx;
|
||||
FILE *f;
|
||||
/* Opcodes for xgetbv */
|
||||
__asm__(".byte 0x0f, 0x01, 0xd0"
|
||||
: "=a" (eax), "=d" (edx)
|
||||
: "c" (op));
|
||||
f = fopen("conftest_xgetbv", "w"); if (!f) return 1;
|
||||
fprintf(f, "%x:%x\n", eax, edx);
|
||||
fclose(f);
|
||||
return 0;
|
||||
])],
|
||||
[ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv],
|
||||
[ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv],
|
||||
[ax_cv_gcc_x86_avx_xgetbv_$1=unknown])])
|
||||
AC_LANG_POP([C])
|
||||
])
|
79
config/ax_gcc_x86_cpuid.m4
Normal file
79
config/ax_gcc_x86_cpuid.m4
Normal file
@@ -0,0 +1,79 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_GCC_X86_CPUID(OP)
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# On Pentium and later x86 processors, with gcc or a compiler that has a
|
||||
# compatible syntax for inline assembly instructions, run a small program
|
||||
# that executes the cpuid instruction with input OP. This can be used to
|
||||
# detect the CPU type.
|
||||
#
|
||||
# On output, the values of the eax, ebx, ecx, and edx registers are stored
|
||||
# as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable
|
||||
# ax_cv_gcc_x86_cpuid_OP.
|
||||
#
|
||||
# If the cpuid instruction fails (because you are running a
|
||||
# cross-compiler, or because you are not using gcc, or because you are on
|
||||
# a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP
|
||||
# is set to the string "unknown".
|
||||
#
|
||||
# This macro mainly exists to be used in AX_GCC_ARCHFLAG.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
|
||||
# Copyright (c) 2008 Matteo Frigo
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||
# gives unlimited permission to copy, distribute and modify the configure
|
||||
# scripts that are the output of Autoconf when processing the Macro. You
|
||||
# need not follow the terms of the GNU General Public License when using
|
||||
# or distributing such scripts, even though portions of the text of the
|
||||
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||
# all other use of the material that constitutes the Autoconf Macro.
|
||||
#
|
||||
# This special exception to the GPL applies to versions of the Autoconf
|
||||
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 7
|
||||
|
||||
AC_DEFUN([AX_GCC_X86_CPUID],
|
||||
[AC_REQUIRE([AC_PROG_CC])
|
||||
AC_LANG_PUSH([C])
|
||||
AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1,
|
||||
[AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [
|
||||
int op = $1, eax, ebx, ecx, edx;
|
||||
FILE *f;
|
||||
__asm__("cpuid"
|
||||
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
|
||||
: "a" (op));
|
||||
f = fopen("conftest_cpuid", "w"); if (!f) return 1;
|
||||
fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx);
|
||||
fclose(f);
|
||||
return 0;
|
||||
])],
|
||||
[ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid],
|
||||
[ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid],
|
||||
[ax_cv_gcc_x86_cpuid_$1=unknown])])
|
||||
AC_LANG_POP([C])
|
||||
])
|
69
configure.ac
69
configure.ac
@@ -22,6 +22,7 @@ AC_INIT(openbts,P2.8TRUNK)
|
||||
AC_PREREQ(2.57)
|
||||
AC_CONFIG_SRCDIR([Transceiver52M/Makefile.am])
|
||||
AC_CONFIG_AUX_DIR([.])
|
||||
AC_CONFIG_MACRO_DIR([config])
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
|
||||
AC_CANONICAL_BUILD
|
||||
@@ -57,68 +58,58 @@ AC_TYPE_SIZE_T
|
||||
AC_HEADER_TIME
|
||||
AC_C_BIGENDIAN
|
||||
|
||||
dnl checks for libraries
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.9)
|
||||
|
||||
AC_ARG_WITH(usrp1, [
|
||||
AS_HELP_STRING([--with-usrp1],
|
||||
[enable USRP1 gnuradio based transceiver])
|
||||
])
|
||||
|
||||
AC_ARG_WITH(uhd, [
|
||||
AS_HELP_STRING([--with-uhd],
|
||||
[enable UHD based transceiver])
|
||||
])
|
||||
|
||||
AC_ARG_WITH(singledb, [
|
||||
AS_HELP_STRING([--with-singledb],
|
||||
[enable single daughterboard use on USRP1])
|
||||
])
|
||||
|
||||
AC_ARG_WITH(resamp, [
|
||||
AS_HELP_STRING([--with-resamp],
|
||||
[enable resampling for non-52MHz devices])
|
||||
AC_ARG_WITH(neon, [
|
||||
AS_HELP_STRING([--with-neon],
|
||||
[enable ARM NEON support])
|
||||
])
|
||||
|
||||
AC_ARG_WITH(extref, [
|
||||
AS_HELP_STRING([--with-extref],
|
||||
[enable external reference on UHD devices])
|
||||
AC_ARG_WITH(neon-vfpv4, [
|
||||
AS_HELP_STRING([--with-neon-vfpv4],
|
||||
[enable ARM NEON FMA support])
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_neon" = "xyes"], [
|
||||
AC_DEFINE(HAVE_NEON, 1, Support ARM NEON)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_neon_vfpv4" = "xyes"], [
|
||||
AC_DEFINE(HAVE_NEON, 1, Support ARM NEON)
|
||||
AC_DEFINE(HAVE_NEON_FMA, 1, Support ARM NEON with FMA)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_usrp1" = "xyes"], [
|
||||
# Defines USRP_CFLAGS, USRP_INCLUDEDIR, and USRP_LIBS
|
||||
PKG_CHECK_MODULES(USRP, usrp > 3.1)
|
||||
# Check whether we have libusrp >= 3.2
|
||||
PKG_CHECK_EXISTS(usrp >= 3.2, libusrp_3_2=yes, libusrp_3_2=no)
|
||||
if test "x$libusrp_3_2" = "xyes";then
|
||||
AC_DEFINE(HAVE_LIBUSRP_3_2, 1, Define to 1 if you have libusrp >= 3.2)
|
||||
fi
|
||||
# Check whether we have libusrp >= 3.3
|
||||
PKG_CHECK_EXISTS(usrp >= 3.3, libusrp_3_3=yes, libusrp_3_3=no)
|
||||
if test "x$libusrp_3_3" = "xyes";then
|
||||
AC_DEFINE(HAVE_LIBUSRP_3_3, 1, Define to 1 if you have libusrp >= 3.3)
|
||||
fi
|
||||
PKG_CHECK_MODULES(USRP, usrp >= 3.3)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_uhd" = "xyes"],[
|
||||
PKG_CHECK_MODULES(UHD, uhd >= 003.002.000)
|
||||
AS_IF([test "x$with_usrp1" != "xyes"],[
|
||||
PKG_CHECK_MODULES(UHD, uhd >= 003.004.000)
|
||||
AC_DEFINE(USE_UHD, 1, Define to 1 if using UHD)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_resamp" = "xyes"], [
|
||||
AC_DEFINE(RESAMPLE, 1, Define to 1 for resampling)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_extref" = "xyes"], [
|
||||
AC_DEFINE(EXTREF, 1, Define to 1 for external reference)
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_singledb" = "xyes"], [
|
||||
AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard)
|
||||
])
|
||||
|
||||
AM_CONDITIONAL(RESAMPLE, [test "x$with_resamp" = "xyes"])
|
||||
AM_CONDITIONAL(UHD, [test "x$with_uhd" = "xyes"])
|
||||
AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "xyes"])
|
||||
# Find and define supported SIMD extensions
|
||||
AX_EXT
|
||||
|
||||
AM_CONDITIONAL(USRP1, [test "x$with_usrp1" = "xyes"])
|
||||
AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"])
|
||||
AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
|
||||
|
||||
# Defines LIBUSB_TRANSFER_CANCELLED, LIBUSB_TRANSFER_COMPLETED, LIBUSB_SUCCESS, LIBUSB_ERROR_*
|
||||
PKG_CHECK_MODULES(LIBUSB, libusb-1.0)
|
||||
|
||||
dnl Output files
|
||||
@@ -127,7 +118,9 @@ AC_CONFIG_FILES([\
|
||||
CommonLibs/Makefile \
|
||||
GSM/Makefile \
|
||||
Transceiver52M/Makefile \
|
||||
Transceiver52M/arm/Makefile \
|
||||
Transceiver52M/x86/Makefile \
|
||||
sqlite3/Makefile \
|
||||
])
|
||||
])
|
||||
|
||||
AC_OUTPUT
|
||||
|
Reference in New Issue
Block a user