From 2b775462f72602701af800ffe57376d183de53ff Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Mon, 25 Sep 2017 13:07:01 +0200 Subject: [PATCH 01/49] Added LV_HAVE_AVX512 to CMakeLists --- CMakeLists.txt | 5 ++++ cmake/modules/FindSSE.cmake | 46 +++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d354a5497..40d3ef4da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,6 +282,11 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif(HAVE_AVX) endif (HAVE_AVX2) + if (HAVE_AVX512) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx512f -DLV_HAVE_AVX512") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx512f -DLV_HAVE_AVX512") + endif(HAVE_AVX512) + if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug") if(HAVE_SSE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -funroll-loops") diff --git a/cmake/modules/FindSSE.cmake b/cmake/modules/FindSSE.cmake index de8b38d1d..4c9673a9d 100644 --- a/cmake/modules/FindSSE.cmake +++ b/cmake/modules/FindSSE.cmake @@ -4,10 +4,11 @@ include(CheckCSourceRuns) -option(ENABLE_SSE "Enable compile-time SSE4.1 support." ON) -option(ENABLE_AVX "Enable compile-time AVX support." ON) -option(ENABLE_AVX2 "Enable compile-time AVX2 support." ON) -option(ENABLE_FMA "Enable compile-time FMA support." ON) +option(ENABLE_SSE "Enable compile-time SSE4.1 support." ON) +option(ENABLE_AVX "Enable compile-time AVX support." ON) +option(ENABLE_AVX2 "Enable compile-time AVX2 support." ON) +option(ENABLE_FMA "Enable compile-time FMA support." ON) +option(ENABLE_AVX512 "Enable compile-time AVX512 support." ON) if (ENABLE_SSE) # @@ -135,6 +136,41 @@ if (ENABLE_SSE) endif() endif() + if (ENABLE_AVX512) + + # + # Check compiler for AVX intrinsics + # + if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG ) + set(CMAKE_REQUIRED_FLAGS "-mavx512f") + check_c_source_runs(" + #include + int main() + { + __m512i a, b, c; + const int src[16] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 , 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + int dst[16]; + a = _mm512_loadu_si512( (__m512i*)src ); + b = _mm512_loadu_si512( (__m512i*)src ); + c = _mm512_add_epi32( a, b ); + _mm512_storeu_si512( (__m512i*)dst, c ); + int i = 0; + for( i = 0; i < 16; i++ ){ + if( ( src[i] + src[i] ) != dst[i] ){ + return -1; + } + } + return 0; + }" + HAVE_AVX512) + endif() + + if (HAVE_AVX512) + message(STATUS "AVX512 is enabled - target CPU must support it") + endif() + endif() + + endif() -mark_as_advanced(HAVE_SSE, HAVE_AVX, HAVE_AVX2, HAVE_FMA) +mark_as_advanced(HAVE_SSE, HAVE_AVX, HAVE_AVX2, HAVE_FMA, HAVE_AVX512) From 8078238cb59624d6c7ef5eec87195f361092c9ea Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Mon, 25 Sep 2017 13:08:38 +0200 Subject: [PATCH 02/49] Removed test macros from mat.h --- lib/include/srslte/phy/utils/mat.h | 8 +------- lib/src/phy/utils/mat.c | 1 + lib/src/phy/utils/test/mat_test.c | 32 +++++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/include/srslte/phy/utils/mat.h b/lib/include/srslte/phy/utils/mat.h index d960590c4..942559955 100644 --- a/lib/include/srslte/phy/utils/mat.h +++ b/lib/include/srslte/phy/utils/mat.h @@ -27,14 +27,8 @@ #ifndef SRSLTE_MAT_H #define SRSLTE_MAT_H -#include "srslte/phy/utils/simd.h" #include "srslte/config.h" - - -/* - * Generic Macros - */ -#define RANDOM_CF() (((float)rand())/((float)RAND_MAX) + _Complex_I*((float)rand())/((float)RAND_MAX)) +#include "srslte/phy/utils/simd.h" /* Generic implementation for complex reciprocal */ SRSLTE_API cf_t srslte_mat_cf_recip_gen(cf_t a); diff --git a/lib/src/phy/utils/mat.c b/lib/src/phy/utils/mat.c index 439daa2ce..bbfc38135 100644 --- a/lib/src/phy/utils/mat.c +++ b/lib/src/phy/utils/mat.c @@ -27,6 +27,7 @@ #include #include +#include #include "srslte/phy/utils/mat.h" diff --git a/lib/src/phy/utils/test/mat_test.c b/lib/src/phy/utils/test/mat_test.c index 49be5c9ae..46081da98 100644 --- a/lib/src/phy/utils/test/mat_test.c +++ b/lib/src/phy/utils/test/mat_test.c @@ -33,12 +33,18 @@ #include #include "srslte/phy/utils/mat.h" +#include "srslte/phy/utils/simd.h" +#include "srslte/phy/utils/vector.h" bool zf_solver = false; bool mmse_solver = false; bool verbose = false; +#define RANDOM_F() ((float)rand())/((float)RAND_MAX) +#define RANDOM_S() ((int16_t)(rand() && 0x800F)) +#define RANDOM_CF() (RANDOM_F() + _Complex_I*RANDOM_F()) + double elapsed_us(struct timeval *ts_start, struct timeval *ts_end) { if (ts_end->tv_usec > ts_start->tv_usec) { return ((double) ts_end->tv_sec - (double) ts_start->tv_sec) * 1000000 + @@ -49,16 +55,16 @@ double elapsed_us(struct timeval *ts_start, struct timeval *ts_end) { } } -#define NOF_REPETITIONS 1000 +#define BLOCK_SIZE 1000 #define RUN_TEST(FUNCTION) /*TYPE NAME (void)*/ { \ int i;\ struct timeval start, end;\ gettimeofday(&start, NULL); \ bool ret = true; \ - for (i = 0; i < NOF_REPETITIONS; i++) {ret &= FUNCTION ();}\ + for (i = 0; i < BLOCK_SIZE; i++) {ret &= FUNCTION ();}\ gettimeofday(&end, NULL);\ if (verbose) printf("%32s: %s ... %6.2f us/call\n", #FUNCTION, (ret)?"Pass":"Fail", \ - elapsed_us(&start, &end)/NOF_REPETITIONS);\ + elapsed_us(&start, &end)/BLOCK_SIZE);\ passed &= ret;\ } @@ -373,6 +379,24 @@ bool test_mmse_solver_avx(void) { #endif /* LV_HAVE_AVX */ +bool test_vec_dot_prod_ccc(void) { + __attribute__((aligned(256))) cf_t a[14]; + __attribute__((aligned(256))) cf_t b[14]; + cf_t res = 0, gold = 0; + + for (int i = 0; i < 14; i++) { + a[i] = RANDOM_CF(); + b[i] = RANDOM_CF(); + } + + res = srslte_vec_dot_prod_ccc(a, b, 14); + + for (int i=0;i<14;i++) { + gold += a[i]*b[i]; + } + + return (cabsf(res - gold) < 1e-3); +} int main(int argc, char **argv) { bool passed = true; @@ -405,6 +429,8 @@ int main(int argc, char **argv) { #endif /* LV_HAVE_AVX */ } + RUN_TEST(test_vec_dot_prod_ccc); + printf("%s!\n", (passed) ? "Ok" : "Failed"); if (!passed) { From 1c3b5552be004064e16527fba55e2d419a1143b2 Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Mon, 25 Sep 2017 13:15:59 +0200 Subject: [PATCH 03/49] added c16 type and architecture independent inline SIMD calls --- lib/include/srslte/config.h | 1 + lib/include/srslte/phy/utils/simd.h | 833 +++++++++++++++++++++++++++- 2 files changed, 832 insertions(+), 2 deletions(-) diff --git a/lib/include/srslte/config.h b/lib/include/srslte/config.h index 68076c0c8..8a988a971 100644 --- a/lib/include/srslte/config.h +++ b/lib/include/srslte/config.h @@ -59,5 +59,6 @@ // cf_t definition typedef _Complex float cf_t; +typedef _Complex short int c16_t; #endif // CONFIG_H diff --git a/lib/include/srslte/phy/utils/simd.h b/lib/include/srslte/phy/utils/simd.h index 420d07213..774dd54bd 100644 --- a/lib/include/srslte/phy/utils/simd.h +++ b/lib/include/srslte/phy/utils/simd.h @@ -27,6 +27,8 @@ #ifndef SRSLTE_SIMD_H_H #define SRSLTE_SIMD_H_H +#include + /* * SSE Macros */ @@ -44,7 +46,7 @@ /* * AVX Macros */ -#ifdef LV_HAVE_AVX +#ifdef LV_HAVE_AVX2 #define _MM256_MULJ_PS(X) _mm256_permute_ps(_MM256_CONJ_PS(X), 0b10110001) #define _MM256_CONJ_PS(X) (_mm256_xor_ps(X, _mm256_set_ps(-0.0f, 0.0f, -0.0f, 0.0f, -0.0f, 0.0f, -0.0f, 0.0f))) @@ -60,7 +62,7 @@ #define _MM256_PROD_PS(a, b) _mm256_addsub_ps(_mm256_mul_ps(a,_mm256_moveldup_ps(b)),\ _mm256_mul_ps(_mm256_shuffle_ps(a,a,0xB1),_mm256_movehdup_ps(b))) #endif /* LV_HAVE_FMA */ -#endif /* LV_HAVE_AVX */ +#endif /* LV_HAVE_AVX2 */ /* @@ -78,4 +80,831 @@ _mm256_fmsubadd_ps(_mm256_shuffle_ps(A,A,0xB1),_mm256_movehdup_ps(B), C)) #endif /* LV_HAVE_FMA */ + + +/* Memory Sizes for Single Floating Point and fixed point */ +#ifdef LV_HAVE_AVX512 + +#define SRSLTE_SIMD_F_SIZE 16 +#define SRSLTE_SIMD_CF_SIZE 16 + +#define SRSLTE_SIMD_S_SIZE 32 +#define SRSLTE_SIMD_C16_SIZE 0 + +#else +#ifdef LV_HAVE_AVX2 + +#define SRSLTE_SIMD_F_SIZE 8 +#define SRSLTE_SIMD_CF_SIZE 8 + +#define SRSLTE_SIMD_S_SIZE 16 +#define SRSLTE_SIMD_C16_SIZE 16 + +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + +#define SRSLTE_SIMD_F_SIZE 4 +#define SRSLTE_SIMD_CF_SIZE 4 + +#define SRSLTE_SIMD_S_SIZE 8 +#define SRSLTE_SIMD_C16_SIZE 8 + +#else /* LV_HAVE_SSE */ + +#define SRSLTE_SIMD_F_SIZE 0 +#define SRSLTE_SIMD_CF_SIZE 0 + +#define SRSLTE_SIMD_S_SIZE 0 +#define SRSLTE_SIMD_C16_SIZE 0 + +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + + + +#if SRSLTE_SIMD_F_SIZE + +/* Data types */ +#ifdef LV_HAVE_AVX512 +typedef __m512 simd_f_t; +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 +typedef __m256 simd_f_t; +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE +typedef __m128 simd_f_t; +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + +/* Single precision Floating point functions */ +static inline simd_f_t srslte_simd_f_load(float *ptr) { +#ifdef LV_HAVE_AVX512 + return _mm512_load_ps(ptr); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_load_ps(ptr); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_load_ps(ptr); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_loadu(float *ptr) { +#ifdef LV_HAVE_AVX512 + return _mm512_loadu_ps(ptr); +#else /* LV_HAVE_AVX512 */ + #ifdef LV_HAVE_AVX2 + return _mm256_loadu_ps(ptr); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_loadu_ps(ptr); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_f_store(float *ptr, simd_f_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_store_ps(ptr, simdreg); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_store_ps(ptr, simdreg); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_store_ps(ptr, simdreg); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_f_storeu(float *ptr, simd_f_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_storeu_ps(ptr, simdreg); +#else /* LV_HAVE_AVX512 */ + #ifdef LV_HAVE_AVX2 + _mm256_storeu_ps(ptr, simdreg); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_storeu_ps(ptr, simdreg); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_set1(float x) { +#ifdef LV_HAVE_AVX512 + return _mm512_set1_ps(x); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_set1_ps(x); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_set1_ps(x); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_mul(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_mul_ps(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_mul_ps(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_mul_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_addsub(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + __m512 r = _mm512_add_ps(a, b); + return _mm512_mask_sub_ps(r, 0b1010101010101010, a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_addsub_ps(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_addsub_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_sub(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_sub_ps(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_sub_ps(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_sub_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_add(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_add_ps(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_add_ps(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_add_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_zero (void) { +#ifdef LV_HAVE_AVX512 + return _mm512_setzero_ps(); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_setzero_ps(); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_setzero_ps(); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_swap(simd_f_t a) { +#ifdef LV_HAVE_AVX512 + return _mm512_permute_ps(a, 0b10110001); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_permute_ps(a, 0b10110001); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + return _mm_shuffle_ps(a, a, 0b10110001); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_hadd(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + const __m512i idx1 = _mm512_setr_epi32((0b00000), (0b00010), + (0b00100), (0b00110), + (0b01000), (0b01010), + (0b01100), (0b01110), + (0b10000), (0b10010), + (0b10100), (0b10110), + (0b11000), (0b11010), + (0b11100), (0b11110)); + const __m512i idx2 = _mm512_or_epi32(idx1, _mm512_set1_epi32(1)); + + simd_f_t a1 = _mm512_permutex2var_ps(a, idx1, b); + simd_f_t b1 = _mm512_permutex2var_ps(a, idx2, b); + return _mm512_add_ps(a1, b1); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + simd_f_t a1 = _mm256_permute2f128_ps(a, b, 0b00100000); + simd_f_t b1 = _mm256_permute2f128_ps(a, b, 0b00110001); + return _mm256_hadd_ps(a1, b1); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + return _mm_hadd_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_f_t srslte_simd_f_sqrt(simd_f_t a) { +#ifdef LV_HAVE_AVX512 + return _mm512_sqrt_ps(a); +#else /* LV_HAVE_AVX512 */ + #ifdef LV_HAVE_AVX2 + return _mm256_sqrt_ps(a); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + return _mm_sqrt_ps(a); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +#endif /* SRSLTE_SIMD_F_SIZE */ + + +#if SRSLTE_SIMD_CF_SIZE + +typedef struct { + simd_f_t re; + simd_f_t im; +} simd_cf_t; + +/* Complex Single precission Floating point functions */ +static inline simd_cf_t srslte_simd_cfi_load(cf_t *ptr) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + __m512 in1 = _mm512_permute_ps(_mm512_load_ps((float*)(ptr)), 0b11011000); + __m512 in2 = _mm512_permute_ps(_mm512_load_ps((float*)(ptr + 8)), 0b11011000); + ret.re = _mm512_unpacklo_ps(in1, in2); + ret.im = _mm512_unpackhi_ps(in1, in2); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + __m256 in1 = _mm256_permute_ps(_mm256_load_ps((float*)(ptr)), 0b11011000); + __m256 in2 = _mm256_permute_ps(_mm256_load_ps((float*)(ptr + 4)), 0b11011000); + ret.re = _mm256_unpacklo_ps(in1, in2); + ret.im = _mm256_unpackhi_ps(in1, in2); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + __m128 i1 = _mm_load_ps((float*)(ptr)); + __m128 i2 = _mm_load_ps((float*)(ptr + 2)); + ret.re = _mm_shuffle_ps(i1, i2, _MM_SHUFFLE(2,0,2,0)); + ret.im = _mm_shuffle_ps(i1, i2, _MM_SHUFFLE(3,1,3,1)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +/* Complex Single precission Floating point functions */ +static inline simd_cf_t srslte_simd_cfi_loadu(cf_t *ptr) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + __m512 in1 = _mm512_permute_ps(_mm512_loadu_ps((float*)(ptr)), 0b11011000); + __m512 in2 = _mm512_permute_ps(_mm512_loadu_ps((float*)(ptr + 8)), 0b11011000); + ret.re = _mm512_unpacklo_ps(in1, in2); + ret.im = _mm512_unpackhi_ps(in1, in2); +#else /* LV_HAVE_AVX512 */ + #ifdef LV_HAVE_AVX2 + __m256 in1 = _mm256_permute_ps(_mm256_loadu_ps((float*)(ptr)), 0b11011000); + __m256 in2 = _mm256_permute_ps(_mm256_loadu_ps((float*)(ptr + 4)), 0b11011000); + ret.re = _mm256_unpacklo_ps(in1, in2); + ret.im = _mm256_unpackhi_ps(in1, in2); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + __m128 i1 = _mm_loadu_ps((float*)(ptr)); + __m128 i2 = _mm_loadu_ps((float*)(ptr + 2)); + ret.re = _mm_shuffle_ps(i1, i2, _MM_SHUFFLE(2,0,2,0)); + ret.im = _mm_shuffle_ps(i1, i2, _MM_SHUFFLE(3,1,3,1)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_load(float *re, float *im) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_load_ps(re); + ret.im = _mm512_load_ps(im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_load_ps(re); + ret.im = _mm256_load_ps(im); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_load_ps(re); + ret.im = _mm_load_ps(im); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_loadu(float *re, float *im) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_loadu_ps(re); + ret.im = _mm512_loadu_ps(im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_loadu_ps(re); + ret.im = _mm256_loadu_ps(im); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_loadu_ps(re); + ret.im = _mm_loadu_ps(im); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline void srslte_simd_cfi_store(cf_t *ptr, simd_cf_t simdreg) { +#ifdef LV_HAVE_AVX512 + __m512 out1 = _mm512_permute_ps(simdreg.re, 0b11011000); + __m512 out2 = _mm512_permute_ps(simdreg.im, 0b11011000); + _mm512_store_ps((float*)(ptr), _mm512_unpacklo_ps(out1, out2)); + _mm512_store_ps((float*)(ptr + 8), _mm512_unpackhi_ps(out1, out2)); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + __m256 out1 = _mm256_permute_ps(simdreg.re, 0b11011000); + __m256 out2 = _mm256_permute_ps(simdreg.im, 0b11011000); + _mm256_store_ps((float*)(ptr), _mm256_unpacklo_ps(out1, out2)); + _mm256_store_ps((float*)(ptr + 4), _mm256_unpackhi_ps(out1, out2)); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_store_ps((float*)(ptr), _mm_unpacklo_ps(simdreg.re, simdreg.im)); + _mm_store_ps((float*)(ptr + 2), _mm_unpackhi_ps(simdreg.re, simdreg.im)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_cfi_storeu(cf_t *ptr, simd_cf_t simdreg) { +#ifdef LV_HAVE_AVX512 + __m512 out1 = _mm512_permute_ps(simdreg.re, 0b11011000); + __m512 out2 = _mm512_permute_ps(simdreg.im, 0b11011000); + _mm512_storeu_ps((float*)(ptr), _mm512_unpacklo_ps(out1, out2)); + _mm512_storeu_ps((float*)(ptr + 8), _mm512_unpackhi_ps(out1, out2)); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + __m256 out1 = _mm256_permute_ps(simdreg.re, 0b11011000); + __m256 out2 = _mm256_permute_ps(simdreg.im, 0b11011000); + _mm256_storeu_ps((float*)(ptr), _mm256_unpacklo_ps(out1, out2)); + _mm256_storeu_ps((float*)(ptr + 4), _mm256_unpackhi_ps(out1, out2)); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_storeu_ps((float*)(ptr), _mm_unpacklo_ps(simdreg.re, simdreg.im)); + _mm_storeu_ps((float*)(ptr + 2), _mm_unpackhi_ps(simdreg.re, simdreg.im)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_cf_store(float *re, float *im, simd_cf_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_store_ps(re, simdreg.re); + _mm512_store_ps(im, simdreg.im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_store_ps((float *) re, simdreg.re); + _mm256_store_ps((float *) im, simdreg.im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_SSE + _mm_store_ps((float *) re, simdreg.re); + _mm_store_ps((float *) im, simdreg.im); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_cf_storeu(float *re, float *im, simd_cf_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_storeu_ps(re, simdreg.re); + _mm512_storeu_ps(im, simdreg.im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_storeu_ps((float *) re, simdreg.re); + _mm256_storeu_ps((float *) im, simdreg.im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_SSE + _mm_storeu_ps((float *) re, simdreg.re); + _mm_storeu_ps((float *) im, simdreg.im); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_cf_t srslte_simd_cf_set1 (cf_t x) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_set1_ps(__real__ x); + ret.im = _mm512_set1_ps(__imag__ x); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_set1_ps(__real__ x); + ret.im = _mm256_set1_ps(__imag__ x); +#else +#ifdef LV_HAVE_SSE + ret.re = _mm_set1_ps(__real__ x); + ret.im = _mm_set1_ps(__imag__ x); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_prod (simd_cf_t a, simd_cf_t b) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_sub_ps(_mm512_mul_ps(a.re, b.re), + _mm512_mul_ps(a.im, b.im)); + ret.im = _mm512_add_ps(_mm512_mul_ps(a.re, b.im), + _mm512_mul_ps(a.im, b.re)); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_sub_ps(_mm256_mul_ps(a.re, b.re), + _mm256_mul_ps(a.im, b.im)); + ret.im = _mm256_add_ps(_mm256_mul_ps(a.re, b.im), + _mm256_mul_ps(a.im, b.re)); +#else +#ifdef LV_HAVE_SSE + ret.re = _mm_sub_ps(_mm_mul_ps(a.re, b.re), + _mm_mul_ps(a.im, b.im)); + ret.im = _mm_add_ps(_mm_mul_ps(a.re, b.im), + _mm_mul_ps(a.im, b.re)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_conjprod (simd_cf_t a, simd_cf_t b) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_add_ps(_mm512_mul_ps(a.re, b.re), + _mm512_mul_ps(a.im, b.im)); + ret.im = _mm512_sub_ps(_mm512_mul_ps(a.im, b.re), + _mm512_mul_ps(a.re, b.im)); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_add_ps(_mm256_mul_ps(a.re, b.re), + _mm256_mul_ps(a.im, b.im)); + ret.im = _mm256_sub_ps(_mm256_mul_ps(a.im, b.re), + _mm256_mul_ps(a.re, b.im)); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_add_ps(_mm_mul_ps(a.re, b.re), + _mm_mul_ps(a.im, b.im)); + ret.im = _mm_sub_ps(_mm_mul_ps(a.im, b.re), + _mm_mul_ps(a.re, b.im)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_add (simd_cf_t a, simd_cf_t b) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_add_ps(a.re, b.re); + ret.im = _mm512_add_ps(a.im, b.im); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_add_ps(a.re, b.re); + ret.im = _mm256_add_ps(a.im, b.im); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_add_ps(a.re, b.re); + ret.im = _mm_add_ps(a.im, b.im); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_zero (void) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + ret.re = _mm512_setzero_ps(); + ret.im = _mm512_setzero_ps(); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + ret.re = _mm256_setzero_ps(); + ret.im = _mm256_setzero_ps(); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_setzero_ps(); + ret.im = _mm_setzero_ps(); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +#endif /* SRSLTE_SIMD_CF_SIZE */ + + +#if SRSLTE_SIMD_S_SIZE + + +#ifdef LV_HAVE_AVX512 +typedef __m512i simd_s_t; +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 +typedef __m256i simd_s_t; +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE +typedef __m128i simd_s_t; +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + +static inline simd_s_t srslte_simd_s_load(int16_t *ptr) { +#ifdef LV_HAVE_AVX512 + return _mm512_load_si512(ptr); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_load_si256(ptr); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_load_si128(ptr); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_s_t srslte_simd_s_loadu(int16_t *ptr) { +#ifdef LV_HAVE_AVX512 + return _mm512_load_si512(ptr); +#else /* LV_HAVE_AVX512 */ + #ifdef LV_HAVE_AVX2 + return _mm256_load_si256(ptr); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_load_si128(ptr); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_s_store(int16_t *ptr, simd_s_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_store_si512(ptr, simdreg); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_store_si256(ptr, simdreg); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_store_si128(ptr, simdreg); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_s_storeu(int16_t *ptr, simd_s_t simdreg) { +#ifdef LV_HAVE_AVX512 + _mm512_storeu_si512(ptr, simdreg); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_storeu_si256(ptr, simdreg); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + _mm_storeu_si128(ptr, simdreg); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_s_t srslte_simd_s_zero(void) { +#ifdef LV_HAVE_AVX512 + return _mm512_setzero_si512(); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_setzero_si256(); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_setzero_si128(); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_s_t srslte_simd_s_mul(simd_s_t a, simd_s_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_mullo_epi16(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_mullo_epi16(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_mullo_epi16(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_s_t srslte_simd_s_add(simd_s_t a, simd_s_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_add_epi16(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_add_epi16(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_add_epi16(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_s_t srslte_simd_s_sub(simd_s_t a, simd_s_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_sub_epi16(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_sub_epi16(a, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + return _mm_sub_epi16(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +#endif /* SRSLTE_SIMD_S_SIZE */ + + +#if SRSLTE_SIMD_C16_SIZE + +typedef struct { +#ifdef LV_HAVE_AVX512 + union { + __m512i m512; + int16_t i16[32]; + } re; + union { + __m512i m512; + int16_t i16[32]; + } im; +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + union { + __m256i m256; + int16_t i16[16]; + } re; + union { + __m256i m256; + int16_t i16[16]; + } im; +#else +#ifdef LV_HAVE_SSE + union { + __m128i m128; + int16_t i16[8]; + } re; + union { + __m128i m128; + int16_t i16[8]; + } im; +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} simd_c16_t; + +/* Fixed point precision (16-bit) functions */ +static inline simd_c16_t srslte_simd_c16i_load(c16_t *ptr) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX512 + __m512i in1 = _mm512_load_si512((__m512i*)(ptr)); + __m512i in2 = _mm512_load_si512((__m512i*)(ptr + 8)); + ret.re.m512 = _mm512_mask_blend_epi16(0xAAAAAAAA, in1,_mm512_shufflelo_epi16(_mm512_shufflehi_epi16(in2, 0b10100000), 0b10100000)); + ret.im.m512 = _mm512_mask_blend_epi16(0xAAAAAAAA, _mm512_shufflelo_epi16(_mm512_shufflehi_epi16(in1, 0b11110101), 0b11110101),in2); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_AVX2 + __m256i in1 = _mm256_load_si256((__m256i*)(ptr)); + __m256i in2 = _mm256_load_si256((__m256i*)(ptr + 8)); + ret.re.m256 = _mm256_blend_epi16(in1,_mm256_shufflelo_epi16(_mm256_shufflehi_epi16(in2, 0b10100000), 0b10100000), 0b10101010); + ret.im.m256 = _mm256_blend_epi16(_mm256_shufflelo_epi16(_mm256_shufflehi_epi16(in1, 0b11110101), 0b11110101),in2, 0b10101010); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + __m128i in1 = _mm_load_si128((__m128i*)(ptr)); + __m128i in2 = _mm_load_si128((__m128i*)(ptr + 8)); + ret.re.m128 = _mm_blend_epi16(in1,_mm_shufflelo_epi16(_mm_shufflehi_epi16(in2, 0b10100000), 0b10100000), 0b10101010); + ret.im.m128 = _mm_blend_epi16(_mm_shufflelo_epi16(_mm_shufflehi_epi16(in1, 0b11110101), 0b11110101),in2, 0b10101010); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_c16_t srslte_simd_c16_load(int16_t *re, int16_t *im) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX2 + ret.re.m256 = _mm256_load_si256((__m256i*)(re)); + ret.im.m256 = _mm256_load_si256((__m256i*)(im)); +#else +#ifdef LV_HAVE_SSE + ret.re.m128 = _mm_load_si128((__m128i*)(re)); + ret.im.m128 = _mm_load_si128((__m128i*)(im)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ + return ret; +} + +static inline void srslte_simd_c16i_store(c16_t *ptr, simd_c16_t simdreg) { +#ifdef LV_HAVE_AVX2 + __m256i re_sw = _mm256_shufflelo_epi16(_mm256_shufflehi_epi16(simdreg.re.m256, 0b10110001), 0b10110001); + __m256i im_sw = _mm256_shufflelo_epi16(_mm256_shufflehi_epi16(simdreg.im.m256, 0b10110001), 0b10110001); + _mm256_store_si256((__m256i *) (ptr), _mm256_blend_epi16(simdreg.re.m256, im_sw, 0b10101010)); + _mm256_store_si256((__m256i *) (ptr + 8), _mm256_blend_epi16(re_sw, simdreg.im.m256, 0b10101010)); +#else +#ifdef LV_HAVE_SSE + __m128i re_sw = _mm_shufflelo_epi16(_mm_shufflehi_epi16(simdreg.re.m128, 0b10110001), 0b10110001); + __m128i im_sw = _mm_shufflelo_epi16(_mm_shufflehi_epi16(simdreg.im.m128, 0b10110001), 0b10110001); + _mm_store_si128((__m128i *) (ptr), _mm_blend_epi16(simdreg.re.m128, im_sw, 0b10101010)); + _mm_store_si128((__m128i *) (ptr + 8), _mm_blend_epi16(re_sw, simdreg.im.m128, 0b10101010)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +} + +static inline void srslte_simd_c16_store(int16_t *re, int16_t *im, simd_c16_t simdreg) { +#ifdef LV_HAVE_AVX2 + _mm256_store_si256((__m256i *) re, simdreg.re.m256); + _mm256_store_si256((__m256i *) im, simdreg.im.m256); +#else +#ifdef LV_HAVE_SSE + _mm_store_si128((__m128i *) re, simdreg.re.m128); + _mm_store_si128((__m128i *) im, simdreg.im.m128); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +} + +static inline simd_c16_t srslte_simd_c16_prod (simd_c16_t a, simd_c16_t b) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX2 + ret.re.m256 = _mm256_sub_epi16(_mm256_mulhrs_epi16(a.re.m256, _mm256_slli_epi16(b.re.m256, 1)), + _mm256_mulhrs_epi16(a.im.m256, _mm256_slli_epi16(b.im.m256, 1))); + ret.im.m256 = _mm256_add_epi16(_mm256_mulhrs_epi16(a.re.m256, _mm256_slli_epi16(b.im.m256, 1)), + _mm256_mulhrs_epi16(a.im.m256, _mm256_slli_epi16(b.re.m256, 1))); +#else +#ifdef LV_HAVE_SSE + ret.re.m128 = _mm_sub_epi16(_mm_mulhrs_epi16(a.re.m128, _mm_slli_epi16(b.re.m128, 1)), + _mm_mulhrs_epi16(a.im.m128, _mm_slli_epi16(b.im.m128, 1))); + ret.im.m128 = _mm_add_epi16(_mm_mulhrs_epi16(a.re.m128, _mm_slli_epi16(b.im.m128, 1)), + _mm_mulhrs_epi16(a.im.m128, _mm_slli_epi16(b.re.m128, 1))); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ + return ret; +} + +static inline simd_c16_t srslte_simd_c16_add (simd_c16_t a, simd_c16_t b) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX2 + ret.re.m256 = _mm256_add_epi16(a.re.m256, b.re.m256); + ret.im.m256 = _mm256_add_epi16(a.im.m256, b.im.m256); +#else +#ifdef LV_HAVE_SSE + ret.re.m128 = _mm_add_epi16(a.re.m128, b.re.m128); + ret.im.m128 = _mm_add_epi16(a.im.m128, b.im.m128); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ + return ret; +} + +static inline simd_c16_t srslte_simd_c16_zero (void) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX2 + ret.re.m256 = _mm256_setzero_si256(); + ret.im.m256 = _mm256_setzero_si256(); +#else +#ifdef LV_HAVE_SSE + ret.re.m128 = _mm_setzero_si128(); + ret.im.m128 = _mm_setzero_si128(); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ + return ret; +} + +#endif /* SRSLTE_SIMD_C16_SIZE */ + + + #endif //SRSLTE_SIMD_H_H From c9f6bfccd47d6dfcda2930e7cb81465e38fb3b9d Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Mon, 25 Sep 2017 13:19:34 +0200 Subject: [PATCH 04/49] Refactored vector library with SIMD independent architecture inline functions test-benchmark --- lib/include/srslte/phy/utils/vector.h | 8 +- lib/include/srslte/phy/utils/vector_simd.h | 65 +- lib/src/phy/utils/test/CMakeLists.txt | 3 + lib/src/phy/utils/test/vector_test.c | 555 +++++++++++ lib/src/phy/utils/vector.c | 228 +---- lib/src/phy/utils/vector_simd.c | 1018 ++++++++++---------- 6 files changed, 1138 insertions(+), 739 deletions(-) create mode 100644 lib/src/phy/utils/test/vector_test.c diff --git a/lib/include/srslte/phy/utils/vector.h b/lib/include/srslte/phy/utils/vector.h index 4a55d18b6..0fadfb334 100644 --- a/lib/include/srslte/phy/utils/vector.h +++ b/lib/include/srslte/phy/utils/vector.h @@ -80,8 +80,8 @@ SRSLTE_API void srslte_vec_load_file(char *filename, void *buffer, uint32_t len) SRSLTE_API void srslte_vec_sum_ch(uint8_t *x, uint8_t *y, char *z, uint32_t len); SRSLTE_API void srslte_vec_sum_fff(float *x, float *y, float *z, uint32_t len); SRSLTE_API void srslte_vec_sum_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); -SRSLTE_API void srslte_vec_sub_sss(short *x, short *y, short *z, uint32_t len); -SRSLTE_API void srslte_vec_sum_sss(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_sub_sss(int16_t *x, int16_t *y, int16_t *z, uint32_t len); +SRSLTE_API void srslte_vec_sum_sss(int16_t *x, int16_t *y, int16_t *z, uint32_t len); /* substract two vectors z=x-y */ SRSLTE_API void srslte_vec_sub_fff(float *x, float *y, float *z, uint32_t len); @@ -91,7 +91,7 @@ SRSLTE_API void srslte_vec_sub_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); SRSLTE_API void srslte_vec_ema_filter(cf_t *new_data, cf_t *average, cf_t *output, float coeff, uint32_t len); /* Square distance */ -SRSLTE_API void srslte_vec_square_dist(cf_t symbol, cf_t *points, float *distance, uint32_t npoints); +//SRSLTE_API void srslte_vec_square_dist(cf_t symbol, cf_t *points, float *distance, uint32_t npoints); /* scalar addition */ SRSLTE_API void srslte_vec_sc_add_fff(float *x, float h, float *z, uint32_t len); @@ -132,7 +132,7 @@ SRSLTE_API void srslte_vec_prod_conj_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len /* real vector product (element-wise) */ SRSLTE_API void srslte_vec_prod_fff(float *x, float *y, float *z, uint32_t len); -SRSLTE_API void srslte_vec_prod_sss(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_sss(int16_t *x, int16_t *y, int16_t *z, uint32_t len); /* Dot-product */ SRSLTE_API cf_t srslte_vec_dot_prod_cfc(cf_t *x, float *y, uint32_t len); diff --git a/lib/include/srslte/phy/utils/vector_simd.h b/lib/include/srslte/phy/utils/vector_simd.h index 1010cbed6..8ea2ce9bc 100644 --- a/lib/include/srslte/phy/utils/vector_simd.h +++ b/lib/include/srslte/phy/utils/vector_simd.h @@ -35,47 +35,66 @@ extern "C" { #include #include "srslte/config.h" -SRSLTE_API int srslte_vec_dot_prod_sss_sse(short *x, short *y, uint32_t len); +#ifdef LV_HAVE_AVX512 +#define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x3F) == 0) +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX +#define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x1F) == 0) +#else /* LV_HAVE_AVX */ +#ifdef LV_HAVE_SSE +#define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x0F) == 0) +#else /* LV_HAVE_SSE */ +#define SRSLTE_IS_ALIGNED(PTR) (true) +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX */ +#endif /* LV_HAVE_AVX512 */ + +SRSLTE_API int srslte_vec_dot_prod_sss_simd(int16_t *x, int16_t *y, int len); + +SRSLTE_API void srslte_vec_sum_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); + +SRSLTE_API void srslte_vec_sub_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); -SRSLTE_API int srslte_vec_dot_prod_sss_avx2(short *x, short *y, uint32_t len); - -SRSLTE_API void srslte_vec_sum_sss_sse(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_sub_sss_avx2(short *x, short *y, short *z, uint32_t len); -SRSLTE_API void srslte_vec_sum_sss_avx2(short *x, short *y, short *z, uint32_t len); +SRSLTE_API cf_t srslte_vec_acc_cc_simd(cf_t *x, int len); -SRSLTE_API void srslte_vec_sub_sss_sse(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_add_fff_simd(float *x, float *y, float *z, int len); -SRSLTE_API void srslte_vec_sub_sss_avx2(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_sub_fff_simd(float *x, float *y, float *z, int len); -SRSLTE_API void srslte_vec_sum_fff_sse(float *x, float *y, float *z, uint32_t len); +SRSLTE_API void srslte_vec_sc_prod_fff_simd(float *x, float h, float *z, int len); -SRSLTE_API void srslte_vec_sum_fff_avx(float *x, float *y, float *z, uint32_t len); +SRSLTE_API void srslte_vec_sc_prod_ccc_simd(cf_t *x, cf_t h, cf_t *z, int len); -SRSLTE_API void srslte_vec_sub_fff_sse(float *x, float *y, float *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_fff_simd(float *x, float *y, float *z, int len); -SRSLTE_API void srslte_vec_sub_fff_avx(float *x, float *y, float *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_ccc_simd(cf_t *x,cf_t *y, cf_t *z, int len); -SRSLTE_API void srslte_vec_sc_prod_fff_sse(float *x, float h, float *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_conj_ccc_simd(cf_t *x,cf_t *y, cf_t *z, int len); -SRSLTE_API void srslte_vec_sc_prod_ccc_sse(cf_t *x, cf_t h, cf_t *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_ccc_cf_simd(float *a_re, float *a_im, float *b_re, float *b_im, float *r_re, float *r_im, int len); -SRSLTE_API void srslte_vec_prod_ccc_sse(cf_t *x,cf_t *y, cf_t *z, uint32_t len); - -SRSLTE_API void srslte_vec_prod_sss_sse(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_ccc_c16_simd(int16_t *a_re, int16_t *a_im, int16_t *b_re, int16_t *b_im, int16_t *r_re, + int16_t *r_im, int len); -SRSLTE_API void srslte_vec_prod_sss_avx2(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); -SRSLTE_API cf_t srslte_vec_dot_prod_conj_ccc_sse(cf_t *x, cf_t *y, uint32_t len); +SRSLTE_API cf_t srslte_vec_dot_prod_conj_ccc_simd(cf_t *x, cf_t *y, int len); -SRSLTE_API void srslte_vec_prod_conj_ccc_sse(cf_t *x,cf_t *y, cf_t *z, uint32_t len); +SRSLTE_API cf_t srslte_vec_dot_prod_ccc_simd(cf_t *x, cf_t *y, int len); SRSLTE_API cf_t srslte_vec_dot_prod_ccc_sse(cf_t *x, cf_t *y, uint32_t len); +SRSLTE_API c16_t srslte_vec_dot_prod_ccc_c16i_simd(c16_t *x, c16_t *y, int len); + SRSLTE_API void srslte_vec_sc_div2_sss_avx2(short *x, int k, short *z, uint32_t len); -SRSLTE_API void srslte_vec_abs_square_cf_sse(cf_t *x, float *z, uint32_t len); +SRSLTE_API void srslte_vec_abs_cf_simd(cf_t *x, float *z, int len); + +SRSLTE_API void srslte_vec_abs_square_cf_simd(cf_t *x, float *z, int len); -SRSLTE_API void srslte_vec_prod_sss_sse(short *x, short *y, short *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_sss_sse(short *x, short *y, short *z, uint32_t len); SRSLTE_API void srslte_vec_prod_sss_avx(short *x, short *y, short *z, uint32_t len); @@ -93,7 +112,9 @@ SRSLTE_API void srslte_vec_lut_sss_sse(short *x, unsigned short *lut, short *y, SRSLTE_API void srslte_vec_convert_fi_sse(float *x, int16_t *z, float scale, uint32_t len); -SRSLTE_API void srslte_vec_sc_prod_cfc_avx(const cf_t *x,const float h,cf_t *y,const uint32_t len); +SRSLTE_API void srslte_vec_sc_prod_cfc_simd(const cf_t *x,const float h,cf_t *y,const int len); + +SRSLTE_API void srslte_vec_cp_simd(cf_t *src, cf_t *dst, int len); #ifdef __cplusplus } diff --git a/lib/src/phy/utils/test/CMakeLists.txt b/lib/src/phy/utils/test/CMakeLists.txt index 4dccbf2a0..76df7ac59 100644 --- a/lib/src/phy/utils/test/CMakeLists.txt +++ b/lib/src/phy/utils/test/CMakeLists.txt @@ -42,3 +42,6 @@ target_link_libraries(algebra_test srslte_phy) add_test(algebra_2x2_zf_solver_test algebra_test -z) add_test(algebra_2x2_mmse_solver_test algebra_test -m) + +add_executable(vector_test vector_test.c) +target_link_libraries(vector_test srslte_phy) \ No newline at end of file diff --git a/lib/src/phy/utils/test/vector_test.c b/lib/src/phy/utils/test/vector_test.c new file mode 100644 index 000000000..e781d05b9 --- /dev/null +++ b/lib/src/phy/utils/test/vector_test.c @@ -0,0 +1,555 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2015 Software Radio Systems Limited + * + * \section LICENSE + * + * This file is part of the srsLTE library. + * + * srsLTE 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. + * + * srsLTE 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. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "srslte/phy/utils/mat.h" +#include "srslte/phy/utils/simd.h" +#include "srslte/phy/utils/vector.h" + + +bool zf_solver = false; +bool mmse_solver = false; +bool verbose = false; + +#define MAX_MSE (1e-3) +#define NOF_REPETITIONS (1024*128) +#define MAX_FUNCTIONS (64) +#define MAX_BLOCKS (16) + +#define RANDOM_F() ((float)rand())/((float)RAND_MAX) +#define RANDOM_S() ((int16_t)(rand() && 0x800F)) +#define RANDOM_CF() (RANDOM_F() + _Complex_I*RANDOM_F()) + +#define TEST_CALL(TEST_CODE) gettimeofday(&start, NULL);\ + for (int i = 0; i < NOF_REPETITIONS; i++){TEST_CODE;}\ + gettimeofday(&end, NULL); \ + *timing = elapsed_us(&start, &end); + +#define TEST(X, CODE) static bool test_##X (char *func_name, double *timing, uint32_t block_size) {\ + struct timeval start, end;\ + float mse = 0.0f;\ + bool passed;\ + strncpy(func_name, #X, 32);\ + CODE;\ + passed = (mse < MAX_MSE);\ + printf("%32s (%5d) ... %7.1f MSamp/s ... %3s Passed\n", func_name, block_size, \ + (double) block_size*NOF_REPETITIONS/ *timing, passed?"":"Not");\ + return passed;\ +} + +#define MALLOC(TYPE, NAME) TYPE *NAME = srslte_vec_malloc(sizeof(TYPE)*block_size) + + +static double elapsed_us(struct timeval *ts_start, struct timeval *ts_end) { + if (ts_end->tv_usec > ts_start->tv_usec) { + return ((double) ts_end->tv_sec - (double) ts_start->tv_sec) * 1000000 + + (double) ts_end->tv_usec - (double) ts_start->tv_usec; + } else { + return ((double) ts_end->tv_sec - (double) ts_start->tv_sec - 1) * 1000000 + + ((double) ts_end->tv_usec + 1000000) - (double) ts_start->tv_usec; + } +} + +float squared_error (cf_t a, cf_t b) { + float diff_re = __real__ a - __real__ b; + float diff_im = __imag__ a - __imag__ b; + return diff_re*diff_re + diff_im*diff_im; +} + +TEST(srslte_vec_dot_prod_sss, + MALLOC(int16_t, x); + MALLOC(int16_t, y); + int16_t z; + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_S(); + y[i] = RANDOM_S(); + } + + TEST_CALL(z = srslte_vec_dot_prod_sss(x, y, block_size)) + + for (int i = 0; i < block_size; i++) { + gold += x[i] * y[i]; + } + + mse += cabsf(gold - z) / cabsf(gold); + + free(x); + free(y); +) + +TEST(srslte_vec_sum_sss, + MALLOC(int16_t, x); + MALLOC(int16_t, y); + MALLOC(int16_t, z); + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_S(); + y[i] = RANDOM_S(); + } + + TEST_CALL(srslte_vec_sum_sss(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] + y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); + free(z); +) + +TEST(srslte_vec_sub_sss, + MALLOC(int16_t, x); + MALLOC(int16_t, y); + MALLOC(int16_t, z); + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_S(); + y[i] = RANDOM_S(); + } + + TEST_CALL(srslte_vec_sub_sss(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] - y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); + free(z); +) + +TEST(srslte_vec_prod_sss, + MALLOC(int16_t, x); + MALLOC(int16_t, y); + MALLOC(int16_t, z); + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_S(); + y[i] = RANDOM_S(); + } + + TEST_CALL(srslte_vec_prod_sss(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); + free(z); +) + +TEST(srslte_vec_acc_cc, + MALLOC(cf_t, x); + cf_t z; + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + } + + TEST_CALL(z = srslte_vec_acc_cc(x, block_size)) + + for (int i = 0; i < block_size; i++) { + gold += x[i]; + } + + mse += cabsf(gold - z)/cabsf(gold); + + free(x); +) + + +TEST(srslte_vec_sum_fff, + MALLOC(float, x); + MALLOC(float, y); + MALLOC(float, z); + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + y[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_sum_fff(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] + y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); +) + +TEST(srslte_vec_sub_fff, + MALLOC(float, x); + MALLOC(float, y); + MALLOC(float, z); + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + y[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_sub_fff(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] - y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); +) + +TEST(srslte_vec_dot_prod_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, y); + cf_t z; + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(z = srslte_vec_dot_prod_ccc(x, y, block_size)) + + for (int i = 0; i < block_size; i++) { + gold += x[i] * y[i]; + } + + mse = cabsf(gold - z) / cabsf(gold); + + free(x); + free(y); +) + +TEST(srslte_vec_dot_prod_conj_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, y); + cf_t z; + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(z = srslte_vec_dot_prod_conj_ccc(x, y, block_size)) + + for (int i = 0; i < block_size; i++) { + gold += x[i] * conjf(y[i]); + } + + mse = cabsf(gold - z) / cabsf(gold); + + free(x); + free(y); +) + +TEST(srslte_vec_prod_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, y); + MALLOC(cf_t, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_prod_ccc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_prod_conj_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, y); + MALLOC(cf_t, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_prod_conj_ccc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * conjf(y[i]); + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_sc_prod_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, z); + cf_t y = RANDOM_F(); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_sc_prod_ccc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y; + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_prod_fff, + MALLOC(float, x); + MALLOC(float, y); + MALLOC(float, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_prod_fff(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_sc_prod_fff, + MALLOC(float, x); + MALLOC(float, z); + float y = RANDOM_F(); + + float gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_sc_prod_fff(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y; + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_abs_cf, + MALLOC(cf_t, x); + MALLOC(float, z); + float gold; + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_abs_cf(x, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = sqrtf(crealf(x[i]) * crealf(x[i]) + cimagf(x[i])*cimagf(x[i])); + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_abs_square_cf, + MALLOC(cf_t, x); + MALLOC(float, z); + float gold; + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_abs_square_cf(x, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = crealf(x[i]) * crealf(x[i]) + cimagf(x[i])*cimagf(x[i]); + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +TEST(srslte_vec_sc_prod_cfc, + MALLOC(cf_t, x); + MALLOC(cf_t, z); + cf_t gold; + float h = RANDOM_F(); + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_sc_prod_cfc(x, h, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * h; + mse += cabsf(gold - z[i]); + } + + free(x); + free(z); +) + +int main(int argc, char **argv) { + char func_names[MAX_FUNCTIONS][32]; + double timmings[MAX_FUNCTIONS][MAX_BLOCKS]; + uint32_t sizes[32]; + uint32_t size_count = 0; + uint32_t func_count = 0; + bool passed = true; + + for (uint32_t block_size = 1; block_size <= 1024*16; block_size *= 2) { + func_count = 0; + + passed &= test_srslte_vec_dot_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sum_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sub_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_acc_cc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sum_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sub_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_dot_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_dot_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sc_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sc_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_abs_cf(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_abs_square_cf(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed &= test_srslte_vec_sc_prod_cfc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + sizes[size_count] = block_size; + size_count++; + } + + printf("\n"); + printf("%32s |", "Subroutine/MSps"); + for (int i = 0; i < size_count; i++) { + printf(" %7d", sizes[i]); + } + printf(" |\n"); + + for (int j = 0; j < 32; j++) { + printf("-"); + } + printf("-+-"); + for (int j = 0; j < size_count; j++) { + printf("--------"); + } + printf("-|\n"); + + for (int i = 0; i < func_count; i++) { + printf("%32s | ", func_names[i]); + for (int j = 0; j < size_count; j++) { + printf(" %7.1f", (double) NOF_REPETITIONS*(double)sizes[j]/timmings[i][j]); + } + printf(" |\n"); + } + + return (passed)?SRSLTE_SUCCESS:SRSLTE_ERROR; +} diff --git a/lib/src/phy/utils/vector.c b/lib/src/phy/utils/vector.c index 917810e92..cb21f24f1 100644 --- a/lib/src/phy/utils/vector.c +++ b/lib/src/phy/utils/vector.c @@ -36,25 +36,6 @@ #include "srslte/phy/utils/bit.h" -#ifdef LV_HAVE_SSE -#include -#endif - -#ifdef LV_HAVE_AVX -#include -#endif - - -#ifdef HAVE_VOLK -#include "volk/volk.h" -#endif - -#ifdef DEBUG_MODE -#warning FIXME: Disabling SSE/AVX vector code -#undef LV_HAVE_SSE -#undef LV_HAVE_AVX -#endif - int srslte_vec_acc_ii(int *x, uint32_t len) { int i; @@ -88,51 +69,25 @@ void srslte_vec_ema_filter(cf_t *new_data, cf_t *average, cf_t *output, float co } cf_t srslte_vec_acc_cc(cf_t *x, uint32_t len) { - int i; - cf_t z=0; - for (i=0;i #include #include #include #include - -#include "srslte/phy/utils/vector_simd.h" - #include #include -#ifdef LV_HAVE_SSE -#include -#endif - -#ifdef LV_HAVE_AVX -#include -#endif - - -int srslte_vec_dot_prod_sss_sse(short *x, short *y, uint32_t len) -{ - int result = 0; -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 8; +#include +#include "srslte/phy/utils/vector_simd.h" +#include "srslte/phy/utils/simd.h" - const __m128i* xPtr = (const __m128i*) x; - const __m128i* yPtr = (const __m128i*) y; - - __m128i dotProdVal = _mm_setzero_si128(); - __m128i xVal, yVal, zVal; - for(;number < points; number++){ +int srslte_vec_dot_prod_sss_simd(int16_t *x, int16_t *y, int len) { + int i = 0; + int result = 0; +#if SRSLTE_SIMD_S_SIZE + simd_s_t simd_dotProdVal = srslte_simd_s_zero(); + if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y)) { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_load(&x[i]); + simd_s_t b = srslte_simd_s_load(&y[i]); - xVal = _mm_load_si128(xPtr); - yVal = _mm_loadu_si128(yPtr); + simd_s_t z = srslte_simd_s_mul(a, b); - zVal = _mm_mullo_epi16(xVal, yVal); + simd_dotProdVal = srslte_simd_s_add(simd_dotProdVal, z); + } + } else { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_loadu(&x[i]); + simd_s_t b = srslte_simd_s_loadu(&y[i]); - dotProdVal = _mm_add_epi16(dotProdVal, zVal); + simd_s_t z = srslte_simd_s_mul(a, b); - xPtr ++; - yPtr ++; + simd_dotProdVal = srslte_simd_s_add(simd_dotProdVal, z); + } } - - short dotProdVector[8]; - _mm_store_si128((__m128i*) dotProdVector, dotProdVal); - for (int i=0;i<8;i++) { - result += dotProdVector[i]; + __attribute__ ((aligned (SRSLTE_SIMD_S_SIZE*2))) short dotProdVector[SRSLTE_SIMD_S_SIZE]; + srslte_simd_s_store(dotProdVector, simd_dotProdVal); + for (int k = 0; k < SRSLTE_SIMD_S_SIZE; k++) { + result += dotProdVector[k]; } +#endif /* SRSLTE_SIMD_S_SIZE */ - number = points * 8; - for(;number < len; number++){ - result += (x[number] * y[number]); + for(; i < len; i++){ + result += (x[i] * y[i]); } - -#endif - return result; -} - -int srslte_vec_dot_prod_sss_avx2(short *x, short *y, uint32_t len) -{ - int result = 0; -#ifdef LV_HAVE_AVX2 - unsigned int number = 0; - const unsigned int points = len / 16; - - const __m256i* xPtr = (const __m256i*) x; - const __m256i* yPtr = (const __m256i*) y; - - __m256i dotProdVal = _mm256_setzero_si256(); - - __m256i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm256_load_si256(xPtr); - yVal = _mm256_loadu_si256(yPtr); - zVal = _mm256_mullo_epi16(xVal, yVal); - dotProdVal = _mm256_add_epi16(dotProdVal, zVal); - xPtr ++; - yPtr ++; - } - - __attribute__ ((aligned (256))) short dotProdVector[16]; - _mm256_store_si256((__m256i*) dotProdVector, dotProdVal); - for (int i=0;i<16;i++) { - result += dotProdVector[i]; - } - - number = points * 16; - for(;number < len; number++){ - result += (x[number] * y[number]); - } - -#endif return result; } +void srslte_vec_sum_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { + int i = 0; +#ifdef SRSLTE_SIMD_S_SIZE + if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_load(&x[i]); + simd_s_t b = srslte_simd_s_load(&y[i]); + simd_s_t r = srslte_simd_s_add(a, b); -void srslte_vec_sum_sss_sse(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 8; - - const __m128i* xPtr = (const __m128i*) x; - const __m128i* yPtr = (const __m128i*) y; - __m128i* zPtr = (__m128i*) z; - - __m128i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm_load_si128(xPtr); - yVal = _mm_load_si128(yPtr); - - zVal = _mm_add_epi16(xVal, yVal); - - _mm_store_si128(zPtr, zVal); - - xPtr ++; - yPtr ++; - zPtr ++; - } - - number = points * 8; - for(;number < len; number++){ - z[number] = x[number] + y[number]; - } -#endif - -} - -void srslte_vec_sum_sss_avx2(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_AVX2 - unsigned int number = 0; - const unsigned int points = len / 16; - - const __m256i* xPtr = (const __m256i*) x; - const __m256i* yPtr = (const __m256i*) y; - __m256i* zPtr = (__m256i*) z; - - __m256i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm256_load_si256(xPtr); - yVal = _mm256_loadu_si256(yPtr); - - zVal = _mm256_add_epi16(xVal, yVal); - _mm256_store_si256(zPtr, zVal); - - xPtr ++; - yPtr ++; - zPtr ++; - } - - number = points * 16; - for(;number < len; number++){ - z[number] = x[number] + y[number]; - } -#endif - -} - - -void srslte_vec_sub_sss_sse(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 8; - - const __m128i* xPtr = (const __m128i*) x; - const __m128i* yPtr = (const __m128i*) y; - __m128i* zPtr = (__m128i*) z; - - __m128i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm_load_si128(xPtr); - yVal = _mm_load_si128(yPtr); - - zVal = _mm_sub_epi16(xVal, yVal); - - _mm_store_si128(zPtr, zVal); - - xPtr ++; - yPtr ++; - zPtr ++; - } - - number = points * 8; - for(;number < len; number++){ - z[number] = x[number] - y[number]; - } -#endif -} - -void srslte_vec_sub_sss_avx2(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_AVX2 - unsigned int number = 0; - const unsigned int points = len / 16; - - const __m256i* xPtr = (const __m256i*) x; - const __m256i* yPtr = (const __m256i*) y; - __m256i* zPtr = (__m256i*) z; - - __m256i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm256_load_si256(xPtr); - yVal = _mm256_loadu_si256(yPtr); - - zVal = _mm256_sub_epi16(xVal, yVal); + srslte_simd_s_store(&z[i], r); + } + } else { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_loadu(&x[i]); + simd_s_t b = srslte_simd_s_loadu(&y[i]); - _mm256_store_si256(zPtr, zVal); + simd_s_t r = srslte_simd_s_add(a, b); - xPtr ++; - yPtr ++; - zPtr ++; + srslte_simd_s_storeu(&z[i], r); + } } +#endif /* SRSLTE_SIMD_S_SIZE */ - number = points * 16; - for(;number < len; number++){ - z[number] = x[number] - y[number]; + for(; i < len; i++){ + z[i] = x[i] + y[i]; } - #endif } +void srslte_vec_sub_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { + int i = 0; +#ifdef SRSLTE_SIMD_S_SIZE + if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_load(&x[i]); + simd_s_t b = srslte_simd_s_load(&y[i]); + simd_s_t r = srslte_simd_s_sub(a, b); + srslte_simd_s_store(&z[i], r); + } + } else { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_loadu(&x[i]); + simd_s_t b = srslte_simd_s_loadu(&y[i]); -void srslte_vec_prod_sss_sse(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 8; - - const __m128i* xPtr = (const __m128i*) x; - const __m128i* yPtr = (const __m128i*) y; - __m128i* zPtr = (__m128i*) z; - - __m128i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm_load_si128(xPtr); - yVal = _mm_load_si128(yPtr); - - zVal = _mm_mullo_epi16(xVal, yVal); - - _mm_store_si128(zPtr, zVal); + simd_s_t r = srslte_simd_s_sub(a, b); - xPtr ++; - yPtr ++; - zPtr ++; + srslte_simd_s_storeu(&z[i], r); + } } +#endif /* SRSLTE_SIMD_S_SIZE */ - number = points * 8; - for(;number < len; number++){ - z[number] = x[number] * y[number]; + for(; i < len; i++){ + z[i] = x[i] - y[i]; } -#endif } -void srslte_vec_prod_sss_avx2(short *x, short *y, short *z, uint32_t len) -{ -#ifdef LV_HAVE_AVX2 - unsigned int number = 0; - const unsigned int points = len / 16; - - const __m256i* xPtr = (const __m256i*) x; - const __m256i* yPtr = (const __m256i*) y; - __m256i* zPtr = (__m256i*) z; +void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { + int i = 0; +#ifdef SRSLTE_SIMD_S_SIZE + if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_load(&x[i]); + simd_s_t b = srslte_simd_s_load(&y[i]); - __m256i xVal, yVal, zVal; - for(;number < points; number++){ - - xVal = _mm256_loadu_si256(xPtr); - yVal = _mm256_loadu_si256(yPtr); + simd_s_t r = srslte_simd_s_mul(a, b); - zVal = _mm256_mullo_epi16(xVal, yVal); + srslte_simd_s_store(&z[i], r); + } + } else { + for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { + simd_s_t a = srslte_simd_s_loadu(&x[i]); + simd_s_t b = srslte_simd_s_loadu(&y[i]); - _mm256_storeu_si256(zPtr, zVal); + simd_s_t r = srslte_simd_s_mul(a, b); - xPtr ++; - yPtr ++; - zPtr ++; + srslte_simd_s_storeu(&z[i], r); + } } +#endif /* SRSLTE_SIMD_S_SIZE */ - number = points * 16; - for(;number < len; number++){ - z[number] = x[number] * y[number]; + for(; i < len; i++){ + z[i] = x[i] * y[i]; } -#endif } - - +#warning remove function if it is not used +/* void srslte_vec_sc_div2_sss_sse(short *x, int k, short *z, uint32_t len) { #ifdef LV_HAVE_SSE @@ -357,8 +195,10 @@ void srslte_vec_sc_div2_sss_sse(short *x, int k, short *z, uint32_t len) z[number] = x[number] / divn; } #endif -} +}*/ +#warning remove function if it is not used +/* void srslte_vec_sc_div2_sss_avx2(short *x, int k, short *z, uint32_t len) { #ifdef LV_HAVE_AVX2 @@ -387,7 +227,7 @@ void srslte_vec_sc_div2_sss_avx2(short *x, int k, short *z, uint32_t len) z[number] = x[number] / divn; } #endif -} +}*/ @@ -531,379 +371,527 @@ void srslte_vec_sum_fff_avx(float *x, float *y, float *z, uint32_t len) { #endif } -void srslte_vec_sub_fff_sse(float *x, float *y, float *z, uint32_t len) { -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 4; +cf_t srslte_vec_acc_cc_simd(cf_t *x, int len) { + int i = 0; + cf_t acc_sum = 0.0f; - const float* xPtr = (const float*) x; - const float* yPtr = (const float*) y; - float* zPtr = (float*) z; +#if SRSLTE_SIMD_F_SIZE + simd_f_t simd_sum = srslte_simd_f_zero(); - __m128 xVal, yVal, zVal; - for(;number < points; number++){ + if (SRSLTE_IS_ALIGNED(x)) { + for (; i < len - SRSLTE_SIMD_F_SIZE / 2 + 1; i += SRSLTE_SIMD_F_SIZE / 2) { + simd_f_t a = srslte_simd_f_load((float *) &x[i]); - xVal = _mm_loadu_ps(xPtr); - yVal = _mm_loadu_ps(yPtr); + simd_sum = srslte_simd_f_add(simd_sum, a); + } + } else { + for (; i < len - SRSLTE_SIMD_F_SIZE / 2 + 1; i += SRSLTE_SIMD_F_SIZE / 2) { + simd_f_t a = srslte_simd_f_loadu((float *) &x[i]); - zVal = _mm_sub_ps(xVal, yVal); + simd_sum = srslte_simd_f_add(simd_sum, a); + } + } - _mm_storeu_ps(zPtr, zVal); + __attribute__((aligned(64))) cf_t sum[SRSLTE_SIMD_F_SIZE/2]; + srslte_simd_f_store((float*)&sum, simd_sum); + for (int k = 0; k < SRSLTE_SIMD_F_SIZE/2; k++) { + acc_sum += sum[k]; + } +#endif - xPtr += 4; - yPtr += 4; - zPtr += 4; + for (; i Date: Mon, 25 Sep 2017 17:08:11 +0200 Subject: [PATCH 05/49] Solved bugs and compilation error in simd and vector_simd --- lib/include/srslte/phy/utils/simd.h | 20 ++++++++++---------- lib/include/srslte/phy/utils/vector_simd.h | 2 +- lib/src/phy/utils/test/vector_test.c | 8 ++++---- lib/src/phy/utils/vector_simd.c | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/include/srslte/phy/utils/simd.h b/lib/include/srslte/phy/utils/simd.h index 774dd54bd..22d8db79d 100644 --- a/lib/include/srslte/phy/utils/simd.h +++ b/lib/include/srslte/phy/utils/simd.h @@ -226,7 +226,7 @@ static inline simd_f_t srslte_simd_f_mul(simd_f_t a, simd_f_t b) { static inline simd_f_t srslte_simd_f_addsub(simd_f_t a, simd_f_t b) { #ifdef LV_HAVE_AVX512 __m512 r = _mm512_add_ps(a, b); - return _mm512_mask_sub_ps(r, 0b1010101010101010, a, b); + return _mm512_mask_sub_ps(r, 0b0101010101010101, a, b); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 return _mm256_addsub_ps(a, b); @@ -642,10 +642,10 @@ static inline simd_s_t srslte_simd_s_load(int16_t *ptr) { return _mm512_load_si512(ptr); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - return _mm256_load_si256(ptr); + return _mm256_load_si256((__m256i*) ptr); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE - return _mm_load_si128(ptr); + return _mm_load_si128((__m128i*) ptr); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -653,13 +653,13 @@ static inline simd_s_t srslte_simd_s_load(int16_t *ptr) { static inline simd_s_t srslte_simd_s_loadu(int16_t *ptr) { #ifdef LV_HAVE_AVX512 - return _mm512_load_si512(ptr); + return _mm512_loadu_si512(ptr); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - return _mm256_load_si256(ptr); + return _mm256_loadu_si256((__m256i*) ptr); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE - return _mm_load_si128(ptr); + return _mm_loadu_si128((__m128i*) ptr); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -670,10 +670,10 @@ static inline void srslte_simd_s_store(int16_t *ptr, simd_s_t simdreg) { _mm512_store_si512(ptr, simdreg); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - _mm256_store_si256(ptr, simdreg); + _mm256_store_si256((__m256i*) ptr, simdreg); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE - _mm_store_si128(ptr, simdreg); + _mm_store_si128((__m128i*) ptr, simdreg); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -684,10 +684,10 @@ static inline void srslte_simd_s_storeu(int16_t *ptr, simd_s_t simdreg) { _mm512_storeu_si512(ptr, simdreg); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - _mm256_storeu_si256(ptr, simdreg); + _mm256_storeu_si256((__m256i*) ptr, simdreg); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE - _mm_storeu_si128(ptr, simdreg); + _mm_storeu_si128((__m128i*) ptr, simdreg); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ diff --git a/lib/include/srslte/phy/utils/vector_simd.h b/lib/include/srslte/phy/utils/vector_simd.h index 8ea2ce9bc..4ee839fab 100644 --- a/lib/include/srslte/phy/utils/vector_simd.h +++ b/lib/include/srslte/phy/utils/vector_simd.h @@ -44,7 +44,7 @@ extern "C" { #ifdef LV_HAVE_SSE #define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x0F) == 0) #else /* LV_HAVE_SSE */ -#define SRSLTE_IS_ALIGNED(PTR) (true) +#define SRSLTE_IS_ALIGNED(PTR) (1) #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX */ #endif /* LV_HAVE_AVX512 */ diff --git a/lib/src/phy/utils/test/vector_test.c b/lib/src/phy/utils/test/vector_test.c index e781d05b9..05dce1d35 100644 --- a/lib/src/phy/utils/test/vector_test.c +++ b/lib/src/phy/utils/test/vector_test.c @@ -45,7 +45,7 @@ bool mmse_solver = false; bool verbose = false; #define MAX_MSE (1e-3) -#define NOF_REPETITIONS (1024*128) +#define NOF_REPETITIONS (1024) #define MAX_FUNCTIONS (64) #define MAX_BLOCKS (16) @@ -70,7 +70,7 @@ bool verbose = false; return passed;\ } -#define MALLOC(TYPE, NAME) TYPE *NAME = srslte_vec_malloc(sizeof(TYPE)*block_size) +#define MALLOC(TYPE, NAME) TYPE *NAME = malloc(sizeof(TYPE)*block_size) static double elapsed_us(struct timeval *ts_start, struct timeval *ts_end) { @@ -339,7 +339,7 @@ TEST(srslte_vec_prod_conj_ccc, TEST(srslte_vec_sc_prod_ccc, MALLOC(cf_t, x); MALLOC(cf_t, z); - cf_t y = RANDOM_F(); + cf_t y = RANDOM_CF(); cf_t gold; for (int i = 0; i < block_size; i++) { @@ -469,7 +469,7 @@ int main(int argc, char **argv) { uint32_t func_count = 0; bool passed = true; - for (uint32_t block_size = 1; block_size <= 1024*16; block_size *= 2) { + for (uint32_t block_size = 1; block_size <= 1024*8; block_size *= 2) { func_count = 0; passed &= test_srslte_vec_dot_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 21132390f..2eb0428b7 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -77,7 +77,7 @@ int srslte_vec_dot_prod_sss_simd(int16_t *x, int16_t *y, int len) { void srslte_vec_sum_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { int i = 0; -#ifdef SRSLTE_SIMD_S_SIZE +#if SRSLTE_SIMD_S_SIZE if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { simd_s_t a = srslte_simd_s_load(&x[i]); @@ -106,7 +106,7 @@ void srslte_vec_sum_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { void srslte_vec_sub_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { int i = 0; -#ifdef SRSLTE_SIMD_S_SIZE +#if SRSLTE_SIMD_S_SIZE if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { simd_s_t a = srslte_simd_s_load(&x[i]); @@ -135,7 +135,7 @@ void srslte_vec_sub_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { int i = 0; -#ifdef SRSLTE_SIMD_S_SIZE +#if SRSLTE_SIMD_S_SIZE if (SRSLTE_IS_ALIGNED(x) && SRSLTE_IS_ALIGNED(y) && SRSLTE_IS_ALIGNED(z)) { for (; i < len - SRSLTE_SIMD_S_SIZE + 1; i += SRSLTE_SIMD_S_SIZE) { simd_s_t a = srslte_simd_s_load(&x[i]); @@ -721,14 +721,14 @@ void srslte_vec_sc_prod_ccc_simd(cf_t *x, cf_t h, cf_t *z, int len) { } } else { for (; i < len - SRSLTE_SIMD_F_SIZE / 2 + 1; i += SRSLTE_SIMD_F_SIZE / 2) { - simd_f_t temp = srslte_simd_f_load((float *) &x[i]); + simd_f_t temp = srslte_simd_f_loadu((float *) &x[i]); simd_f_t m1 = srslte_simd_f_mul(hre, temp); simd_f_t sw = srslte_simd_f_swap(temp); simd_f_t m2 = srslte_simd_f_mul(him, sw); simd_f_t r = srslte_simd_f_addsub(m1, m2); - srslte_simd_f_store((float *) &z[i], r); + srslte_simd_f_storeu((float *) &z[i], r); } } #endif From 9e5f999666a97b82fa0558b28294a7b9f62becbf Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Thu, 28 Sep 2017 11:04:26 +0200 Subject: [PATCH 06/49] Added more functions --- lib/include/srslte/phy/utils/simd.h | 237 ++++++++++ lib/include/srslte/phy/utils/vector.h | 5 +- lib/include/srslte/phy/utils/vector_simd.h | 59 +-- lib/src/phy/mimo/precoding.c | 3 +- lib/src/phy/sync/find_sss.c | 8 +- lib/src/phy/utils/test/CMakeLists.txt | 3 +- lib/src/phy/utils/test/vector_test.c | 287 +++++++++++- lib/src/phy/utils/vector.c | 134 +----- lib/src/phy/utils/vector_simd.c | 487 ++++++++++++++++----- 9 files changed, 934 insertions(+), 289 deletions(-) diff --git a/lib/include/srslte/phy/utils/simd.h b/lib/include/srslte/phy/utils/simd.h index 22d8db79d..9a5f15dbb 100644 --- a/lib/include/srslte/phy/utils/simd.h +++ b/lib/include/srslte/phy/utils/simd.h @@ -88,6 +88,8 @@ #define SRSLTE_SIMD_F_SIZE 16 #define SRSLTE_SIMD_CF_SIZE 16 +#define SRSLTE_SIMD_I_SIZE 16 + #define SRSLTE_SIMD_S_SIZE 32 #define SRSLTE_SIMD_C16_SIZE 0 @@ -97,6 +99,8 @@ #define SRSLTE_SIMD_F_SIZE 8 #define SRSLTE_SIMD_CF_SIZE 8 +#define SRSLTE_SIMD_I_SIZE 8 + #define SRSLTE_SIMD_S_SIZE 16 #define SRSLTE_SIMD_C16_SIZE 16 @@ -106,6 +110,8 @@ #define SRSLTE_SIMD_F_SIZE 4 #define SRSLTE_SIMD_CF_SIZE 4 +#define SRSLTE_SIMD_I_SIZE 4 + #define SRSLTE_SIMD_S_SIZE 8 #define SRSLTE_SIMD_C16_SIZE 8 @@ -114,6 +120,8 @@ #define SRSLTE_SIMD_F_SIZE 0 #define SRSLTE_SIMD_CF_SIZE 0 +#define SRSLTE_SIMD_I_SIZE 0 + #define SRSLTE_SIMD_S_SIZE 0 #define SRSLTE_SIMD_C16_SIZE 0 @@ -223,6 +231,20 @@ static inline simd_f_t srslte_simd_f_mul(simd_f_t a, simd_f_t b) { #endif /* LV_HAVE_AVX512 */ } +static inline simd_f_t srslte_simd_f_rcp(simd_f_t a) { +#ifdef LV_HAVE_AVX512 + return _mm512_rcp_ps(a); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_rcp_ps(a); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + return _mm_rcp_ps(a); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + static inline simd_f_t srslte_simd_f_addsub(simd_f_t a, simd_f_t b) { #ifdef LV_HAVE_AVX512 __m512 r = _mm512_add_ps(a, b); @@ -600,6 +622,61 @@ static inline simd_cf_t srslte_simd_cf_add (simd_cf_t a, simd_cf_t b) { return ret; } +static inline simd_cf_t srslte_simd_cf_mul (simd_cf_t a, simd_f_t b) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + b = _mm512_permutexvar_ps(b, _mm512_setr_epi32(0,4,1,5,2,6,3,7,8,12,9,13,10,14,11,15)); + ret.re = _mm512_mul_ps(a.re, b); + ret.im = _mm512_mul_ps(a.im, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + b = _mm256_permutevar8x32_ps(b, _mm256_setr_epi32(0,4,1,5,2,6,3,7)); + ret.re = _mm256_mul_ps(a.re, b); + ret.im = _mm256_mul_ps(a.im, b); +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE + ret.re = _mm_mul_ps(a.re, b); + ret.im = _mm_mul_ps(a.im, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + +static inline simd_cf_t srslte_simd_cf_rcp (simd_cf_t a) { + simd_cf_t ret; +#ifdef LV_HAVE_AVX512 + simd_f_t a2re = _mm512_mul_ps(a.re, a.re); + simd_f_t a2im = _mm512_mul_ps(a.im, a.im); + simd_f_t mod2 = _mm512_add_ps(a2re, a2im); + simd_f_t rcp = _mm512_rcp_ps(mod2); + simd_f_t neg_a_im = _mm512_xor_ps(_mm512_set1_ps(-0.0f), a.im); + ret.re = _mm512_mul_ps(a.re, rcp); + ret.im = _mm512_mul_ps(neg_a_im, rcp); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + simd_f_t a2re = _mm256_mul_ps(a.re, a.re); + simd_f_t a2im = _mm256_mul_ps(a.im, a.im); + simd_f_t mod2 = _mm256_add_ps(a2re, a2im); + simd_f_t rcp = _mm256_rcp_ps(mod2); + simd_f_t neg_a_im = _mm256_xor_ps(_mm256_set1_ps(-0.0f), a.im); + ret.re = _mm256_mul_ps(a.re, rcp); + ret.im = _mm256_mul_ps(neg_a_im, rcp); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + simd_f_t a2re = _mm_mul_ps(a.re, a.re); + simd_f_t a2im = _mm_mul_ps(a.im, a.im); + simd_f_t mod2 = _mm_add_ps(a2re, a2im); + simd_f_t rcp = _mm_rcp_ps(mod2); + simd_f_t neg_a_im = _mm_xor_ps(_mm_set1_ps(-0.0f), a.im); + ret.re = _mm_mul_ps(a.re, rcp); + ret.im = _mm_mul_ps(neg_a_im, rcp); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + return ret; +} + static inline simd_cf_t srslte_simd_cf_zero (void) { simd_cf_t ret; #ifdef LV_HAVE_AVX512 @@ -621,6 +698,106 @@ static inline simd_cf_t srslte_simd_cf_zero (void) { #endif /* SRSLTE_SIMD_CF_SIZE */ +#if SRSLTE_SIMD_I_SIZE + +#ifdef LV_HAVE_AVX512 +typedef __m512i simd_i_t; +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 +typedef __m256i simd_i_t; +#else /* LV_HAVE_AVX2 */ +#ifdef LV_HAVE_SSE +typedef __m128i simd_i_t; +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ + +static inline simd_i_t srslte_simd_i_load(int *x) { +#ifdef LV_HAVE_AVX512 + return _mm512_load_epi32((__m512i*)x); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_load_si256((__m256i*)x); +#else + #ifdef LV_HAVE_SSE + return _mm_load_si128((__m128i*)x); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline void srslte_simd_i_store(int *x, simd_i_t reg) { +#ifdef LV_HAVE_AVX512 + _mm512_store_epi32((__m512i*)x, reg); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + _mm256_store_si256((__m256i*)x, reg); +#else +#ifdef LV_HAVE_SSE + _mm_store_si128((__m128i*)x, reg); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_i_t srslte_simd_i_set1(int x) { +#ifdef LV_HAVE_AVX512 + return _mm512_set1_epi32(x); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_set1_epi32(x); +#else + #ifdef LV_HAVE_SSE + return _mm_set1_epi32(x); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_i_t srslte_simd_i_add(simd_i_t a, simd_i_t b) { +#ifdef LV_HAVE_AVX512 + return _mm512_add_epi32(a, b); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return _mm256_add_epi32(a, b); +#else +#ifdef LV_HAVE_SSE + return _mm_add_epi32(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_i_t srslte_simd_f_max(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + return (simd_i_t) _mm512_cmp_ps_mask(a, b, _CMP_GT_OS); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return (simd_i_t) _mm256_cmp_ps(a, b, _CMP_GT_OS); +#else /* LV_HAVE_AVX2 */ + #ifdef LV_HAVE_SSE + return (simd_i_t) _mm_cmpgt_ps(a, b); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +static inline simd_i_t srslte_simd_i_select(simd_i_t a, simd_i_t b, simd_i_t selector) { +#ifdef LV_HAVE_AVX512 + return (__m512i) _mm512_blendv_ps((__m512)a, (__m512) b, (__m512) selector); +#else /* LV_HAVE_AVX512 */ +#ifdef LV_HAVE_AVX2 + return (__m256i) _mm256_blendv_ps((__m256) a,(__m256) b,(__m256) selector); +#else + #ifdef LV_HAVE_SSE + return (__m128i) _mm_blendv_ps((__m128)a, (__m128)b, (__m128)selector); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ +} + +#endif /* SRSLTE_SIMD_I_SIZE*/ + #if SRSLTE_SIMD_S_SIZE @@ -829,6 +1006,20 @@ static inline simd_c16_t srslte_simd_c16_load(int16_t *re, int16_t *im) { return ret; } +static inline simd_c16_t srslte_simd_c16_loadu(int16_t *re, int16_t *im) { + simd_c16_t ret; +#ifdef LV_HAVE_AVX2 + ret.re.m256 = _mm256_loadu_si256((__m256i*)(re)); + ret.im.m256 = _mm256_loadu_si256((__m256i*)(im)); +#else +#ifdef LV_HAVE_SSE + ret.re.m128 = _mm_loadu_si128((__m128i*)(re)); + ret.im.m128 = _mm_loadu_si128((__m128i*)(im)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ + return ret; +} + static inline void srslte_simd_c16i_store(c16_t *ptr, simd_c16_t simdreg) { #ifdef LV_HAVE_AVX2 __m256i re_sw = _mm256_shufflelo_epi16(_mm256_shufflehi_epi16(simdreg.re.m256, 0b10110001), 0b10110001); @@ -845,6 +1036,22 @@ static inline void srslte_simd_c16i_store(c16_t *ptr, simd_c16_t simdreg) { #endif /* LV_HAVE_AVX2 */ } +static inline void srslte_simd_c16i_storeu(c16_t *ptr, simd_c16_t simdreg) { +#ifdef LV_HAVE_AVX2 + __m256i re_sw = _mm256_shufflelo_epi16(_mm256_shufflehi_epi16(simdreg.re.m256, 0b10110001), 0b10110001); + __m256i im_sw = _mm256_shufflelo_epi16(_mm256_shufflehi_epi16(simdreg.im.m256, 0b10110001), 0b10110001); + _mm256_storeu_si256((__m256i *) (ptr), _mm256_blend_epi16(simdreg.re.m256, im_sw, 0b10101010)); + _mm256_storeu_si256((__m256i *) (ptr + 8), _mm256_blend_epi16(re_sw, simdreg.im.m256, 0b10101010)); +#else +#ifdef LV_HAVE_SSE + __m128i re_sw = _mm_shufflelo_epi16(_mm_shufflehi_epi16(simdreg.re.m128, 0b10110001), 0b10110001); + __m128i im_sw = _mm_shufflelo_epi16(_mm_shufflehi_epi16(simdreg.im.m128, 0b10110001), 0b10110001); + _mm_storeu_si128((__m128i *) (ptr), _mm_blend_epi16(simdreg.re.m128, im_sw, 0b10101010)); + _mm_storeu_si128((__m128i *) (ptr + 8), _mm_blend_epi16(re_sw, simdreg.im.m128, 0b10101010)); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +} + static inline void srslte_simd_c16_store(int16_t *re, int16_t *im, simd_c16_t simdreg) { #ifdef LV_HAVE_AVX2 _mm256_store_si256((__m256i *) re, simdreg.re.m256); @@ -857,6 +1064,18 @@ static inline void srslte_simd_c16_store(int16_t *re, int16_t *im, simd_c16_t si #endif /* LV_HAVE_AVX2 */ } +static inline void srslte_simd_c16_storeu(int16_t *re, int16_t *im, simd_c16_t simdreg) { +#ifdef LV_HAVE_AVX2 + _mm256_storeu_si256((__m256i *) re, simdreg.re.m256); + _mm256_storeu_si256((__m256i *) im, simdreg.im.m256); +#else +#ifdef LV_HAVE_SSE + _mm_storeu_si128((__m128i *) re, simdreg.re.m128); + _mm_storeu_si128((__m128i *) im, simdreg.im.m128); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +} + static inline simd_c16_t srslte_simd_c16_prod (simd_c16_t a, simd_c16_t b) { simd_c16_t ret; #ifdef LV_HAVE_AVX2 @@ -905,6 +1124,24 @@ static inline simd_c16_t srslte_simd_c16_zero (void) { #endif /* SRSLTE_SIMD_C16_SIZE */ +#if SRSLTE_SIMD_F_SIZE && SRSLTE_SIMD_S_SIZE + +static inline simd_s_t srslte_simd_convert_2f_s(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX2 + __m256 aa = _mm256_permute2f128_ps(a, b, 0x20); + __m256 bb = _mm256_permute2f128_ps(a, b, 0x31); + __m256i ai = _mm256_cvttps_epi32(aa); + __m256i bi = _mm256_cvttps_epi32(bb); + return _mm256_packs_epi32(ai, bi); +#else +#ifdef LV_HAVE_SSE + __m128i ai = _mm_cvttps_epi32(a); + __m128i bi = _mm_cvttps_epi32(b); + return _mm_packs_epi32(ai, bi); +#endif /* LV_HAVE_SSE */ +#endif /* LV_HAVE_AVX2 */ +} +#endif /* SRSLTE_SIMD_F_SIZE && SRSLTE_SIMD_C16_SIZE */ #endif //SRSLTE_SIMD_H_H diff --git a/lib/include/srslte/phy/utils/vector.h b/lib/include/srslte/phy/utils/vector.h index 0fadfb334..9b99c6fff 100644 --- a/lib/include/srslte/phy/utils/vector.h +++ b/lib/include/srslte/phy/utils/vector.h @@ -123,6 +123,7 @@ SRSLTE_API void srslte_vec_interleave_cf(float *real, float *imag, cf_t *x, uint /* vector product (element-wise) */ SRSLTE_API void srslte_vec_prod_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); +SRSLTE_API void srslte_vec_prod_ccc_split(float *x_re, float *x_im, float *y_re, float *y_im, float *z_re, float *z_im, uint32_t len); /* vector product (element-wise) */ SRSLTE_API void srslte_vec_prod_cfc(cf_t *x, float *y, cf_t *z, uint32_t len); @@ -142,8 +143,8 @@ SRSLTE_API float srslte_vec_dot_prod_fff(float *x, float *y, uint32_t len); SRSLTE_API int32_t srslte_vec_dot_prod_sss(int16_t *x, int16_t *y, uint32_t len); /* z=x/y vector division (element-wise) */ -SRSLTE_API void srslte_vec_div_ccc(cf_t *x, cf_t *y, float *y_mod, cf_t *z, float *z_real, float *z_imag, uint32_t len); -void srslte_vec_div_cfc(cf_t *x, float *y, cf_t *z, float *z_real, float *z_imag, uint32_t len); +SRSLTE_API void srslte_vec_div_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); +SRSLTE_API void srslte_vec_div_cfc(cf_t *x, float *y, cf_t *z, uint32_t len); SRSLTE_API void srslte_vec_div_fff(float *x, float *y, float *z, uint32_t len); /* conjugate */ diff --git a/lib/include/srslte/phy/utils/vector_simd.h b/lib/include/srslte/phy/utils/vector_simd.h index 4ee839fab..294cff50f 100644 --- a/lib/include/srslte/phy/utils/vector_simd.h +++ b/lib/include/srslte/phy/utils/vector_simd.h @@ -36,26 +36,29 @@ extern "C" { #include "srslte/config.h" #ifdef LV_HAVE_AVX512 +#define SRSLTE_SIMD_BIT_ALIGN 512 #define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x3F) == 0) #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX +#define SRSLTE_SIMD_BIT_ALIGN 256 #define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x1F) == 0) #else /* LV_HAVE_AVX */ #ifdef LV_HAVE_SSE +#define SRSLTE_SIMD_BIT_ALIGN 128 #define SRSLTE_IS_ALIGNED(PTR) (((size_t)(PTR) & 0x0F) == 0) #else /* LV_HAVE_SSE */ +#define SRSLTE_SIMD_BIT_ALIGN 64 #define SRSLTE_IS_ALIGNED(PTR) (1) #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX */ #endif /* LV_HAVE_AVX512 */ -SRSLTE_API int srslte_vec_dot_prod_sss_simd(int16_t *x, int16_t *y, int len); - +/* SIMD Basic vector math */ SRSLTE_API void srslte_vec_sum_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); SRSLTE_API void srslte_vec_sub_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); -SRSLTE_API void srslte_vec_sub_sss_avx2(short *x, short *y, short *z, uint32_t len); +SRSLTE_API float srslte_vec_acc_ff_simd(float *x, int len); SRSLTE_API cf_t srslte_vec_acc_cc_simd(cf_t *x, int len); @@ -63,58 +66,62 @@ SRSLTE_API void srslte_vec_add_fff_simd(float *x, float *y, float *z, int len); SRSLTE_API void srslte_vec_sub_fff_simd(float *x, float *y, float *z, int len); +/* SIMD Vector Scalar Product */ +SRSLTE_API void srslte_vec_sc_prod_cfc_simd(const cf_t *x,const float h,cf_t *y,const int len); + SRSLTE_API void srslte_vec_sc_prod_fff_simd(float *x, float h, float *z, int len); SRSLTE_API void srslte_vec_sc_prod_ccc_simd(cf_t *x, cf_t h, cf_t *z, int len); +/* SIMD Vector Product */ +SRSLTE_API void srslte_vec_prod_ccc_split_simd(float *a_re, float *a_im, float *b_re, float *b_im, float *r_re, float *r_im, int len); + +SRSLTE_API void srslte_vec_prod_ccc_c16_simd(int16_t *a_re, int16_t *a_im, int16_t *b_re, int16_t *b_im, int16_t *r_re, + int16_t *r_im, int len); + +SRSLTE_API void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); + +SRSLTE_API void srslte_vec_prod_cfc_simd(cf_t *x, float *y, cf_t *z, int len); + SRSLTE_API void srslte_vec_prod_fff_simd(float *x, float *y, float *z, int len); SRSLTE_API void srslte_vec_prod_ccc_simd(cf_t *x,cf_t *y, cf_t *z, int len); SRSLTE_API void srslte_vec_prod_conj_ccc_simd(cf_t *x,cf_t *y, cf_t *z, int len); -SRSLTE_API void srslte_vec_prod_ccc_cf_simd(float *a_re, float *a_im, float *b_re, float *b_im, float *r_re, float *r_im, int len); +/* SIMD Division */ +SRSLTE_API void srslte_vec_div_ccc_simd(cf_t *x,cf_t *y, cf_t *z, int len); -SRSLTE_API void srslte_vec_prod_ccc_c16_simd(int16_t *a_re, int16_t *a_im, int16_t *b_re, int16_t *b_im, int16_t *r_re, - int16_t *r_im, int len); +SRSLTE_API void srslte_vec_div_cfc_simd(cf_t *x, float *y, cf_t *z, int len); -SRSLTE_API void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len); +SRSLTE_API void srslte_vec_div_fff_simd(float *x, float *y, float *z, int len); +/* SIMD Dot product */ SRSLTE_API cf_t srslte_vec_dot_prod_conj_ccc_simd(cf_t *x, cf_t *y, int len); SRSLTE_API cf_t srslte_vec_dot_prod_ccc_simd(cf_t *x, cf_t *y, int len); -SRSLTE_API cf_t srslte_vec_dot_prod_ccc_sse(cf_t *x, cf_t *y, uint32_t len); - SRSLTE_API c16_t srslte_vec_dot_prod_ccc_c16i_simd(c16_t *x, c16_t *y, int len); -SRSLTE_API void srslte_vec_sc_div2_sss_avx2(short *x, int k, short *z, uint32_t len); +SRSLTE_API int srslte_vec_dot_prod_sss_simd(int16_t *x, int16_t *y, int len); +/* SIMD Modulus functions */ SRSLTE_API void srslte_vec_abs_cf_simd(cf_t *x, float *z, int len); SRSLTE_API void srslte_vec_abs_square_cf_simd(cf_t *x, float *z, int len); -SRSLTE_API void srslte_vec_prod_sss_sse(short *x, short *y, short *z, uint32_t len); - -SRSLTE_API void srslte_vec_prod_sss_avx(short *x, short *y, short *z, uint32_t len); - -SRSLTE_API void srslte_vec_sc_div2_sss_sse(short *x, int n_rightshift, short *z, uint32_t len); +/* Other Functions */ +SRSLTE_API void srslte_vec_lut_sss_simd(short *x, unsigned short *lut, short *y, int len); -SRSLTE_API void srslte_vec_sc_div2_sss_avx(short *x, int k, short *z, uint32_t len); +SRSLTE_API void srslte_vec_convert_fi_simd(float *x, int16_t *z, float scale, int len); -SRSLTE_API void srslte_vec_lut_sss_sse(short *x, unsigned short *lut, short *y, uint32_t len); - -SRSLTE_API void srslte_vec_convert_fi_sse(float *x, int16_t *z, float scale, uint32_t len); - -SRSLTE_API void srslte_vec_mult_scalar_cf_f_avx( cf_t *z,const cf_t *x,const float h,const uint32_t len); - -SRSLTE_API void srslte_vec_lut_sss_sse(short *x, unsigned short *lut, short *y, uint32_t len); +SRSLTE_API void srslte_vec_cp_simd(cf_t *src, cf_t *dst, int len); -SRSLTE_API void srslte_vec_convert_fi_sse(float *x, int16_t *z, float scale, uint32_t len); -SRSLTE_API void srslte_vec_sc_prod_cfc_simd(const cf_t *x,const float h,cf_t *y,const int len); +/* SIMD Find Max functions */ +SRSLTE_API uint32_t srslte_vec_max_fi_simd(float *x, int len); -SRSLTE_API void srslte_vec_cp_simd(cf_t *src, cf_t *dst, int len); +SRSLTE_API uint32_t srslte_vec_max_ci_simd(cf_t *x, int len); #ifdef __cplusplus } diff --git a/lib/src/phy/mimo/precoding.c b/lib/src/phy/mimo/precoding.c index f1aab3b5d..8f8bd7737 100644 --- a/lib/src/phy/mimo/precoding.c +++ b/lib/src/phy/mimo/precoding.c @@ -36,17 +36,16 @@ #ifdef LV_HAVE_SSE #include -#include "srslte/phy/utils/mat.h" int srslte_predecoding_single_sse(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS], cf_t *x, int nof_rxant, int nof_symbols, float noise_estimate); int srslte_predecoding_diversity2_sse(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS][SRSLTE_MAX_PORTS], cf_t *x[SRSLTE_MAX_LAYERS], int nof_rxant, int nof_symbols); #endif #ifdef LV_HAVE_AVX #include -#include "srslte/phy/utils/mat.h" int srslte_predecoding_single_avx(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS], cf_t *x, int nof_rxant, int nof_symbols, float noise_estimate); #endif +#include "srslte/phy/utils/mat.h" static srslte_mimo_decoder_t mimo_decoder = SRSLTE_MIMO_DECODER_MMSE; diff --git a/lib/src/phy/sync/find_sss.c b/lib/src/phy/sync/find_sss.c index 2afeced42..082aee52a 100644 --- a/lib/src/phy/sync/find_sss.c +++ b/lib/src/phy/sync/find_sss.c @@ -70,14 +70,12 @@ static void corr_all_sz_partial(cf_t z[SRSLTE_SSS_N], float s[SRSLTE_SSS_N][SRSL static void extract_pair_sss(srslte_sss_synch_t *q, cf_t *input, cf_t *ce, cf_t y[2][SRSLTE_SSS_N]) { cf_t input_fft[SRSLTE_SYMBOL_SZ_MAX]; - float ce_mod[2*SRSLTE_SSS_N], z_real[2*SRSLTE_SSS_N], z_imag[2*SRSLTE_SSS_N]; - + srslte_dft_run_c(&q->dftp_input, input, input_fft); if (ce) { - srslte_vec_div_ccc(&input_fft[q->fft_size/2-SRSLTE_SSS_N], ce, ce_mod, - &input_fft[q->fft_size/2-SRSLTE_SSS_N], z_real, z_imag, - 2*SRSLTE_SSS_N); + srslte_vec_div_ccc(&input_fft[q->fft_size/2-SRSLTE_SSS_N], ce, + &input_fft[q->fft_size/2-SRSLTE_SSS_N], 2*SRSLTE_SSS_N); } for (int i = 0; i < SRSLTE_SSS_N; i++) { diff --git a/lib/src/phy/utils/test/CMakeLists.txt b/lib/src/phy/utils/test/CMakeLists.txt index 76df7ac59..1f5c66827 100644 --- a/lib/src/phy/utils/test/CMakeLists.txt +++ b/lib/src/phy/utils/test/CMakeLists.txt @@ -44,4 +44,5 @@ add_test(algebra_2x2_zf_solver_test algebra_test -z) add_test(algebra_2x2_mmse_solver_test algebra_test -m) add_executable(vector_test vector_test.c) -target_link_libraries(vector_test srslte_phy) \ No newline at end of file +target_link_libraries(vector_test srslte_phy) +add_test(vector_test vector_test) diff --git a/lib/src/phy/utils/test/vector_test.c b/lib/src/phy/utils/test/vector_test.c index 05dce1d35..cf0f38926 100644 --- a/lib/src/phy/utils/test/vector_test.c +++ b/lib/src/phy/utils/test/vector_test.c @@ -89,6 +89,26 @@ float squared_error (cf_t a, cf_t b) { return diff_re*diff_re + diff_im*diff_im; } +TEST(srslte_vec_acc_ff, + MALLOC(float, x); + float z; + + cf_t gold = 0.0f; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + } + + TEST_CALL(z = srslte_vec_acc_ff(x, block_size)) + + for (int i = 0; i < block_size; i++) { + gold += x[i]; + } + + mse += fabs(gold - z) / gold; + + free(x); +) + TEST(srslte_vec_dot_prod_sss, MALLOC(int16_t, x); MALLOC(int16_t, y); @@ -314,6 +334,37 @@ TEST(srslte_vec_prod_ccc, free(z); ) +TEST(srslte_vec_prod_ccc_split, + MALLOC(float, x_re); + MALLOC(float, x_im); + MALLOC(float, y_re); + MALLOC(float, y_im); + MALLOC(float, z_re); + MALLOC(float, z_im); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x_re[i] = RANDOM_F(); + x_im[i] = RANDOM_F(); + y_re[i] = RANDOM_F(); + y_im[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_prod_ccc_split(x_re, x_im, y_re, y_im, z_re, z_im, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = (x_re[i] + I * x_im[i]) * (y_re[i] + I * y_im[i]); + mse += cabsf(gold - (z_re[i] + I*z_im[i])); + } + + free(x_re); + free(x_im); + free(y_re); + free(y_im); + free(z_re); + free(z_im); +) + TEST(srslte_vec_prod_conj_ccc, MALLOC(cf_t, x); MALLOC(cf_t, y); @@ -357,6 +408,27 @@ TEST(srslte_vec_sc_prod_ccc, free(z); ) +TEST(srslte_vec_convert_fi, + MALLOC(float, x); + MALLOC(short, z); + float scale = 1000.0f; + + short gold; + for (int i = 0; i < block_size; i++) { + x[i] = (float) RANDOM_F(); + } + + TEST_CALL(srslte_vec_convert_fi(x, z, scale, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = (short) ((x[i] * scale)); + mse += cabsf((float)gold - (float) z[i]); + } + + free(x); + free(z); +) + TEST(srslte_vec_prod_fff, MALLOC(float, x); MALLOC(float, y); @@ -376,6 +448,30 @@ TEST(srslte_vec_prod_fff, } free(x); + free(y); + free(z); +) + +TEST(srslte_vec_prod_cfc, + MALLOC(cf_t, x); + MALLOC(float, y); + MALLOC(cf_t, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_prod_cfc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] * y[i]; + mse += cabsf(gold - z[i]); + } + + free(x); + free(y); free(z); ) @@ -461,66 +557,216 @@ TEST(srslte_vec_sc_prod_cfc, free(z); ) +TEST(srslte_vec_div_ccc, + MALLOC(cf_t, x); + MALLOC(cf_t, y); + MALLOC(cf_t, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_CF(); + } + + TEST_CALL(srslte_vec_div_ccc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] / y[i]; + mse += cabsf(gold - z[i]); + } + mse /= block_size; + + free(x); + free(y); + free(z); +) + + +TEST(srslte_vec_div_cfc, + MALLOC(cf_t, x); + MALLOC(float, y); + MALLOC(cf_t, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + y[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_div_cfc(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] / y[i]; + mse += cabsf(gold - z[i])/cabsf(gold); + } + mse /= block_size; + + free(x); + free(y); + free(z); +) + + +TEST(srslte_vec_div_fff, + MALLOC(float, x); + MALLOC(float, y); + MALLOC(float, z); + + cf_t gold; + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + y[i] = RANDOM_F(); + } + + TEST_CALL(srslte_vec_div_fff(x, y, z, block_size)) + + for (int i = 0; i < block_size; i++) { + gold = x[i] / y[i]; + mse += cabsf(gold - z[i]); + } + mse /= block_size; + + free(x); + free(y); + free(z); +) + +TEST(srslte_vec_max_fi, + MALLOC(float, x); + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + } + + uint32_t max_index = 0; + TEST_CALL(max_index = srslte_vec_max_fi(x, block_size);) + + float gold_value = -INFINITY; + uint32_t gold_index = 0; + for (int i = 0; i < block_size; i++) { + if (gold_value < x[i]) { + gold_value = x[i]; + gold_index = i; + } + } + mse = (gold_index != max_index) ? 1:0; + + free(x); +) + +TEST(srslte_vec_max_abs_ci, + MALLOC(cf_t, x); + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_CF(); + } + + uint32_t max_index = 0; + TEST_CALL(max_index = srslte_vec_max_abs_ci(x, block_size);) + + float gold_value = -INFINITY; + uint32_t gold_index = 0; + for (int i = 0; i < block_size; i++) { + cf_t a = x[i]; + float abs2 = __real__ a * __real__ a + __imag__ a * __imag__ a; + if (abs2 > gold_value) { + gold_value = abs2; + gold_index = (uint32_t)i; + } + } + mse = (gold_index != max_index) ? 1:0; + + free(x); +) + int main(int argc, char **argv) { char func_names[MAX_FUNCTIONS][32]; double timmings[MAX_FUNCTIONS][MAX_BLOCKS]; uint32_t sizes[32]; uint32_t size_count = 0; uint32_t func_count = 0; - bool passed = true; + bool passed[MAX_FUNCTIONS][MAX_BLOCKS]; + bool all_passed = true; for (uint32_t block_size = 1; block_size <= 1024*8; block_size *= 2) { func_count = 0; - passed &= test_srslte_vec_dot_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_acc_ff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_dot_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_sum_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_sub_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_acc_cc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_sum_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_sub_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_dot_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + + passed[func_count][size_count] = test_srslte_vec_dot_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sum_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_convert_fi(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sub_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_prod_sss(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_prod_cfc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_acc_cc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sum_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_prod_ccc_split(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sub_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_dot_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_sc_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_dot_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_sc_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_abs_cf(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_abs_square_cf(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_prod_conj_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_sc_prod_cfc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sc_prod_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_div_ccc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sc_prod_fff(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_div_cfc(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_abs_cf(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_div_fff(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_abs_square_cf(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_max_fi(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; - passed &= test_srslte_vec_sc_prod_cfc(func_names[func_count], &timmings[func_count][size_count], block_size); + passed[func_count][size_count] = test_srslte_vec_max_abs_ci(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; sizes[size_count] = block_size; @@ -546,10 +792,11 @@ int main(int argc, char **argv) { for (int i = 0; i < func_count; i++) { printf("%32s | ", func_names[i]); for (int j = 0; j < size_count; j++) { - printf(" %7.1f", (double) NOF_REPETITIONS*(double)sizes[j]/timmings[i][j]); + printf(" %s%7.1f\x1b[0m", (passed[i][j])?"":"\x1B[31m", (double) NOF_REPETITIONS*(double)sizes[j]/timmings[i][j]); + all_passed &= passed[i][j]; } printf(" |\n"); } - return (passed)?SRSLTE_SUCCESS:SRSLTE_ERROR; + return (all_passed)?SRSLTE_SUCCESS:SRSLTE_ERROR; } diff --git a/lib/src/phy/utils/vector.c b/lib/src/phy/utils/vector.c index cb21f24f1..f85dbca0a 100644 --- a/lib/src/phy/utils/vector.c +++ b/lib/src/phy/utils/vector.c @@ -48,18 +48,7 @@ int srslte_vec_acc_ii(int *x, uint32_t len) { // Used in PRACH detector, AGC and chest_dl for noise averaging float srslte_vec_acc_ff(float *x, uint32_t len) { -#ifdef HAVE_VOLK_ACC_FUNCTION - float result; - volk_32f_accumulator_s32f(&result,x,len); - return result; -#else - int i; - float z=0; - for (i=0;im) { - m=x[i]; - p=i; - } - } - return p; -#endif -#endif + return srslte_vec_max_fi_simd(x, len); } int16_t srslte_vec_max_star_si(int16_t *x, uint32_t len) { @@ -616,30 +541,7 @@ void srslte_vec_max_fff(float *x, float *y, float *z, uint32_t len) { // CP autocorr uint32_t srslte_vec_max_abs_ci(cf_t *x, uint32_t len) { -#ifdef HAVE_VOLK_MAX_ABS_FUNCTION_32 - uint32_t target=0; - volk_32fc_index_max_32u(&target,x,len); - return target; -#else -#ifdef HAVE_VOLK_MAX_ABS_FUNCTION_16 - uint32_t target=0; - volk_32fc_index_max_16u(&target,x,len); - return target; -#else - uint32_t i; - float m=-FLT_MAX; - uint32_t p=0; - float tmp; - for (i=0;im) { - m=tmp; - p=i; - } - } - return p; -#endif -#endif + return srslte_vec_max_ci_simd(x, len); } void srslte_vec_quant_fuc(float *in, uint8_t *out, float gain, float offset, float clip, uint32_t len) { diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 2eb0428b7..2dd354548 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -232,143 +232,113 @@ void srslte_vec_sc_div2_sss_avx2(short *x, int k, short *z, uint32_t len) /* No improvement with AVX */ -void srslte_vec_lut_sss_sse(short *x, unsigned short *lut, short *y, uint32_t len) -{ -#ifdef DEBUG_MODE - for (int i=0;i max_value) { + max_value = values_buffer[k]; + max_index = (uint32_t) indexes_buffer[k]; + } + } +#endif /* SRSLTE_SIMD_I_SIZE */ + + for (; i < len; i++) { + if (x[i] > max_value) { + max_value = x[i]; + max_index = (uint32_t)i; + } + } + + return max_index; +} + +uint32_t srslte_vec_max_ci_simd(cf_t *x, int len) { + int i = 0; + + float max_value = -INFINITY; + uint32_t max_index = 0; + +#if SRSLTE_SIMD_I_SIZE + __attribute__ ((aligned (SRSLTE_SIMD_I_SIZE*sizeof(int)))) int indexes_buffer[SRSLTE_SIMD_I_SIZE] = {0}; + __attribute__ ((aligned (SRSLTE_SIMD_I_SIZE*sizeof(float)))) float values_buffer[SRSLTE_SIMD_I_SIZE] = {0}; + + for (int k = 0; k < SRSLTE_SIMD_I_SIZE; k++) indexes_buffer[k] = k; + simd_i_t simd_inc = srslte_simd_i_set1(SRSLTE_SIMD_I_SIZE); + simd_i_t simd_indexes = srslte_simd_i_load(indexes_buffer); + simd_i_t simd_max_indexes = srslte_simd_i_set1(0); + + simd_f_t simd_max_values = srslte_simd_f_set1(-INFINITY); + + if (SRSLTE_IS_ALIGNED(x)) { + for (; i < len - SRSLTE_SIMD_I_SIZE + 1; i += SRSLTE_SIMD_I_SIZE) { + simd_f_t x1 = srslte_simd_f_load((float *) &x[i]); + simd_f_t x2 = srslte_simd_f_load((float *) &x[i + SRSLTE_SIMD_F_SIZE / 2]); + + simd_f_t mul1 = srslte_simd_f_mul(x1, x1); + simd_f_t mul2 = srslte_simd_f_mul(x2, x2); + + simd_f_t z1 = srslte_simd_f_hadd(mul1, mul2); + + simd_i_t res = srslte_simd_f_max(z1, simd_max_values); + + simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); + simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) z1, res); + simd_indexes = srslte_simd_i_add(simd_indexes, simd_inc); + } + } else { + for (; i < len - SRSLTE_SIMD_I_SIZE + 1; i += SRSLTE_SIMD_I_SIZE) { + simd_f_t x1 = srslte_simd_f_loadu((float *) &x[i]); + simd_f_t x2 = srslte_simd_f_loadu((float *) &x[i + SRSLTE_SIMD_F_SIZE / 2]); + + simd_f_t mul1 = srslte_simd_f_mul(x1, x1); + simd_f_t mul2 = srslte_simd_f_mul(x2, x2); + + simd_f_t z1 = srslte_simd_f_hadd(mul1, mul2); + + simd_i_t res = srslte_simd_f_max(z1, simd_max_values); + + simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); + simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) z1, res); + simd_indexes = srslte_simd_i_add(simd_indexes, simd_inc); + } + } + + srslte_simd_i_store(indexes_buffer, simd_max_indexes); + srslte_simd_f_store(values_buffer, simd_max_values); + + for (int k = 0; k < SRSLTE_SIMD_I_SIZE; k++) { + if (values_buffer[k] > max_value) { + max_value = values_buffer[k]; + max_index = (uint32_t) indexes_buffer[k]; + } + } +#endif /* SRSLTE_SIMD_I_SIZE */ + + for (; i < len; i++) { + cf_t a = x[i]; + float abs2 = __real__ a * __real__ a + __imag__ a * __imag__ a; + if (abs2 > max_value) { + max_value = abs2; + max_index = (uint32_t)i; + } + } + + return max_index; +} From 94a06867a3c3efbb8c6eb36e1ac2fa5f1aa2dc07 Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Fri, 29 Sep 2017 16:42:46 +0200 Subject: [PATCH 07/49] Optimized SIMD includes and solved AVX512 bugs --- CMakeLists.txt | 4 +- lib/include/srslte/phy/utils/mat.h | 3 - lib/include/srslte/phy/utils/simd.h | 97 ++++++++++++++++++++-------- lib/src/phy/mimo/precoding.c | 5 +- lib/src/phy/utils/test/mat_test.c | 1 - lib/src/phy/utils/test/vector_test.c | 2 - lib/src/phy/utils/vector_simd.c | 10 +-- 7 files changed, 79 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28afbc0d8..efaa1973a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,8 +283,8 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif (HAVE_AVX2) if (HAVE_AVX512) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx512f -DLV_HAVE_AVX512") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx512f -DLV_HAVE_AVX512") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx512f -mavx512cd -DLV_HAVE_AVX512") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx512f -mavx512cd -DLV_HAVE_AVX512") endif(HAVE_AVX512) if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug") diff --git a/lib/include/srslte/phy/utils/mat.h b/lib/include/srslte/phy/utils/mat.h index 942559955..339cfea23 100644 --- a/lib/include/srslte/phy/utils/mat.h +++ b/lib/include/srslte/phy/utils/mat.h @@ -60,7 +60,6 @@ SRSLTE_API float srslte_mat_2x2_cn(cf_t h00, #ifdef LV_HAVE_SSE -#include /* SSE implementation for complex reciprocal */ SRSLTE_API __m128 srslte_mat_cf_recip_sse(__m128 a); @@ -84,8 +83,6 @@ SRSLTE_API void srslte_mat_2x2_mmse_sse(__m128 y0, __m128 y1, #ifdef LV_HAVE_AVX -#include - /* AVX implementation for complex reciprocal */ SRSLTE_API __m256 srslte_mat_cf_recip_avx(__m256 a); diff --git a/lib/include/srslte/phy/utils/simd.h b/lib/include/srslte/phy/utils/simd.h index 9a5f15dbb..2590794f2 100644 --- a/lib/include/srslte/phy/utils/simd.h +++ b/lib/include/srslte/phy/utils/simd.h @@ -27,7 +27,12 @@ #ifndef SRSLTE_SIMD_H_H #define SRSLTE_SIMD_H_H +#ifdef LV_HAVE_SSE /* AVX, AVX2, FMA, AVX512 are in this group */ +#ifndef __OPTIMIZE__ +#define __OPTIMIZE__ +#endif #include +#endif /* LV_HAVE_SSE */ /* * SSE Macros @@ -233,7 +238,7 @@ static inline simd_f_t srslte_simd_f_mul(simd_f_t a, simd_f_t b) { static inline simd_f_t srslte_simd_f_rcp(simd_f_t a) { #ifdef LV_HAVE_AVX512 - return _mm512_rcp_ps(a); + return _mm512_rcp14_ps(a); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 return _mm256_rcp_ps(a); @@ -372,10 +377,16 @@ typedef struct { static inline simd_cf_t srslte_simd_cfi_load(cf_t *ptr) { simd_cf_t ret; #ifdef LV_HAVE_AVX512 - __m512 in1 = _mm512_permute_ps(_mm512_load_ps((float*)(ptr)), 0b11011000); - __m512 in2 = _mm512_permute_ps(_mm512_load_ps((float*)(ptr + 8)), 0b11011000); - ret.re = _mm512_unpacklo_ps(in1, in2); - ret.im = _mm512_unpackhi_ps(in1, in2); + __m512 in1 = _mm512_load_ps((float*)(ptr)); + __m512 in2 = _mm512_load_ps((float*)(ptr + SRSLTE_SIMD_CF_SIZE/2)); + ret.re = _mm512_permutex2var_ps(in1, _mm512_setr_epi32(0x00, 0x02, 0x04, 0x06, + 0x08, 0x0A, 0x0C, 0x0E, + 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1A, 0x1C, 0x1E), in2); + ret.im = _mm512_permutex2var_ps(in1, _mm512_setr_epi32(0x01, 0x03, 0x05, 0x07, + 0x09, 0x0B, 0x0D, 0x0F, + 0x11, 0x13, 0x15, 0x17, + 0x19, 0x1B, 0x1D, 0x1F), in2); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 __m256 in1 = _mm256_permute_ps(_mm256_load_ps((float*)(ptr)), 0b11011000); @@ -398,10 +409,16 @@ static inline simd_cf_t srslte_simd_cfi_load(cf_t *ptr) { static inline simd_cf_t srslte_simd_cfi_loadu(cf_t *ptr) { simd_cf_t ret; #ifdef LV_HAVE_AVX512 - __m512 in1 = _mm512_permute_ps(_mm512_loadu_ps((float*)(ptr)), 0b11011000); - __m512 in2 = _mm512_permute_ps(_mm512_loadu_ps((float*)(ptr + 8)), 0b11011000); - ret.re = _mm512_unpacklo_ps(in1, in2); - ret.im = _mm512_unpackhi_ps(in1, in2); + __m512 in1 = _mm512_loadu_ps((float*)(ptr)); + __m512 in2 = _mm512_loadu_ps((float*)(ptr + SRSLTE_SIMD_CF_SIZE/2)); + ret.re = _mm512_permutex2var_ps(in1, _mm512_setr_epi32(0x00, 0x02, 0x04, 0x06, + 0x08, 0x0A, 0x0C, 0x0E, + 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1A, 0x1C, 0x1E), in2); + ret.im = _mm512_permutex2var_ps(in1, _mm512_setr_epi32(0x01, 0x03, 0x05, 0x07, + 0x09, 0x0B, 0x0D, 0x0F, + 0x11, 0x13, 0x15, 0x17, + 0x19, 0x1B, 0x1D, 0x1F), in2); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 __m256 in1 = _mm256_permute_ps(_mm256_loadu_ps((float*)(ptr)), 0b11011000); @@ -460,10 +477,16 @@ static inline simd_cf_t srslte_simd_cf_loadu(float *re, float *im) { static inline void srslte_simd_cfi_store(cf_t *ptr, simd_cf_t simdreg) { #ifdef LV_HAVE_AVX512 - __m512 out1 = _mm512_permute_ps(simdreg.re, 0b11011000); - __m512 out2 = _mm512_permute_ps(simdreg.im, 0b11011000); - _mm512_store_ps((float*)(ptr), _mm512_unpacklo_ps(out1, out2)); - _mm512_store_ps((float*)(ptr + 8), _mm512_unpackhi_ps(out1, out2)); + __m512 s1 = _mm512_permutex2var_ps(simdreg.re, _mm512_setr_epi32(0x00, 0x10, 0x01, 0x11, + 0x02, 0x12, 0x03, 0x13, + 0x04, 0x14, 0x05, 0x15, + 0x06, 0x16, 0x07, 0x17), simdreg.im); + __m512 s2 = _mm512_permutex2var_ps(simdreg.re, _mm512_setr_epi32(0x08, 0x18, 0x09, 0x19, + 0x0A, 0x1A, 0x0B, 0x1B, + 0x0C, 0x1C, 0x0D, 0x1D, + 0x0E, 0x1E, 0x0F, 0x1F), simdreg.im); + _mm512_store_ps((float*)(ptr), s1); + _mm512_store_ps((float*)(ptr + 8), s2); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 __m256 out1 = _mm256_permute_ps(simdreg.re, 0b11011000); @@ -481,10 +504,16 @@ static inline void srslte_simd_cfi_store(cf_t *ptr, simd_cf_t simdreg) { static inline void srslte_simd_cfi_storeu(cf_t *ptr, simd_cf_t simdreg) { #ifdef LV_HAVE_AVX512 - __m512 out1 = _mm512_permute_ps(simdreg.re, 0b11011000); - __m512 out2 = _mm512_permute_ps(simdreg.im, 0b11011000); - _mm512_storeu_ps((float*)(ptr), _mm512_unpacklo_ps(out1, out2)); - _mm512_storeu_ps((float*)(ptr + 8), _mm512_unpackhi_ps(out1, out2)); + __m512 s1 = _mm512_permutex2var_ps(simdreg.re, _mm512_setr_epi32(0x00, 0x10, 0x01, 0x11, + 0x02, 0x12, 0x03, 0x13, + 0x04, 0x14, 0x05, 0x15, + 0x06, 0x16, 0x07, 0x17), simdreg.im); + __m512 s2 = _mm512_permutex2var_ps(simdreg.re, _mm512_setr_epi32(0x08, 0x18, 0x09, 0x19, + 0x0A, 0x1A, 0x0B, 0x1B, + 0x0C, 0x1C, 0x0D, 0x1D, + 0x0E, 0x1E, 0x0F, 0x1F), simdreg.im); + _mm512_storeu_ps((float*)(ptr), s1); + _mm512_storeu_ps((float*)(ptr + 8), s2); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 __m256 out1 = _mm256_permute_ps(simdreg.re, 0b11011000); @@ -625,7 +654,6 @@ static inline simd_cf_t srslte_simd_cf_add (simd_cf_t a, simd_cf_t b) { static inline simd_cf_t srslte_simd_cf_mul (simd_cf_t a, simd_f_t b) { simd_cf_t ret; #ifdef LV_HAVE_AVX512 - b = _mm512_permutexvar_ps(b, _mm512_setr_epi32(0,4,1,5,2,6,3,7,8,12,9,13,10,14,11,15)); ret.re = _mm512_mul_ps(a.re, b); ret.im = _mm512_mul_ps(a.im, b); #else /* LV_HAVE_AVX512 */ @@ -649,7 +677,7 @@ static inline simd_cf_t srslte_simd_cf_rcp (simd_cf_t a) { simd_f_t a2re = _mm512_mul_ps(a.re, a.re); simd_f_t a2im = _mm512_mul_ps(a.im, a.im); simd_f_t mod2 = _mm512_add_ps(a2re, a2im); - simd_f_t rcp = _mm512_rcp_ps(mod2); + simd_f_t rcp = _mm512_rcp14_ps(mod2); simd_f_t neg_a_im = _mm512_xor_ps(_mm512_set1_ps(-0.0f), a.im); ret.re = _mm512_mul_ps(a.re, rcp); ret.im = _mm512_mul_ps(neg_a_im, rcp); @@ -702,12 +730,15 @@ static inline simd_cf_t srslte_simd_cf_zero (void) { #ifdef LV_HAVE_AVX512 typedef __m512i simd_i_t; +typedef __mmask16 simd_sel_t; #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 typedef __m256i simd_i_t; +typedef __m256 simd_sel_t; #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE typedef __m128i simd_i_t; +typedef __m128i simd_sel_t; #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -768,12 +799,12 @@ static inline simd_i_t srslte_simd_i_add(simd_i_t a, simd_i_t b) { #endif /* LV_HAVE_AVX512 */ } -static inline simd_i_t srslte_simd_f_max(simd_f_t a, simd_f_t b) { +static inline simd_sel_t srslte_simd_f_max(simd_f_t a, simd_f_t b) { #ifdef LV_HAVE_AVX512 - return (simd_i_t) _mm512_cmp_ps_mask(a, b, _CMP_GT_OS); + return _mm512_cmp_ps_mask(a, b, _CMP_GT_OS); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - return (simd_i_t) _mm256_cmp_ps(a, b, _CMP_GT_OS); + return _mm256_cmp_ps(a, b, _CMP_GT_OS); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE return (simd_i_t) _mm_cmpgt_ps(a, b); @@ -782,15 +813,15 @@ static inline simd_i_t srslte_simd_f_max(simd_f_t a, simd_f_t b) { #endif /* LV_HAVE_AVX512 */ } -static inline simd_i_t srslte_simd_i_select(simd_i_t a, simd_i_t b, simd_i_t selector) { +static inline simd_i_t srslte_simd_i_select(simd_i_t a, simd_i_t b, simd_sel_t selector) { #ifdef LV_HAVE_AVX512 - return (__m512i) _mm512_blendv_ps((__m512)a, (__m512) b, (__m512) selector); + return (__m512i) _mm512_mask_blend_ps( selector, (__m512)a, (__m512) b); #else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 - return (__m256i) _mm256_blendv_ps((__m256) a,(__m256) b,(__m256) selector); + return (__m256i) _mm256_blendv_ps((__m256) a,(__m256) b, selector); #else #ifdef LV_HAVE_SSE - return (__m128i) _mm_blendv_ps((__m128)a, (__m128)b, (__m128)selector); + return (__m128i) _mm_blendv_ps((__m128)a, (__m128)b, selector); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -1127,6 +1158,19 @@ static inline simd_c16_t srslte_simd_c16_zero (void) { #if SRSLTE_SIMD_F_SIZE && SRSLTE_SIMD_S_SIZE static inline simd_s_t srslte_simd_convert_2f_s(simd_f_t a, simd_f_t b) { +#ifdef LV_HAVE_AVX512 + __m512 aa = _mm512_permutex2var_ps(a, _mm512_setr_epi32(0x00, 0x01, 0x02, 0x03, + 0x08, 0x09, 0x0A, 0x0B, + 0x10, 0x11, 0x12, 0x13, + 0x18, 0x19, 0x1A, 0x1B), b); + __m512 bb = _mm512_permutex2var_ps(a, _mm512_setr_epi32(0x04, 0x05, 0x06, 0x07, + 0x0C, 0x0D, 0x0E, 0x0F, + 0x14, 0x15, 0x16, 0x17, + 0x1C, 0x1D, 0x1E, 0x1F), b); + __m512i ai = _mm512_cvttps_epi32(aa); + __m512i bi = _mm512_cvttps_epi32(bb); + return _mm512_packs_epi32(ai, bi); +#else /* LV_HAVE_AVX512 */ #ifdef LV_HAVE_AVX2 __m256 aa = _mm256_permute2f128_ps(a, b, 0x20); __m256 bb = _mm256_permute2f128_ps(a, b, 0x31); @@ -1140,6 +1184,7 @@ static inline simd_s_t srslte_simd_convert_2f_s(simd_f_t a, simd_f_t b) { return _mm_packs_epi32(ai, bi); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ +#endif /* LV_HAVE_AVX512 */ } #endif /* SRSLTE_SIMD_F_SIZE && SRSLTE_SIMD_C16_SIZE */ diff --git a/lib/src/phy/mimo/precoding.c b/lib/src/phy/mimo/precoding.c index 8f8bd7737..21350524b 100644 --- a/lib/src/phy/mimo/precoding.c +++ b/lib/src/phy/mimo/precoding.c @@ -33,20 +33,17 @@ #include "srslte/phy/mimo/precoding.h" #include "srslte/phy/utils/vector.h" #include "srslte/phy/utils/debug.h" +#include "srslte/phy/utils/mat.h" #ifdef LV_HAVE_SSE -#include int srslte_predecoding_single_sse(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS], cf_t *x, int nof_rxant, int nof_symbols, float noise_estimate); int srslte_predecoding_diversity2_sse(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS][SRSLTE_MAX_PORTS], cf_t *x[SRSLTE_MAX_LAYERS], int nof_rxant, int nof_symbols); #endif #ifdef LV_HAVE_AVX -#include int srslte_predecoding_single_avx(cf_t *y[SRSLTE_MAX_PORTS], cf_t *h[SRSLTE_MAX_PORTS], cf_t *x, int nof_rxant, int nof_symbols, float noise_estimate); #endif -#include "srslte/phy/utils/mat.h" - static srslte_mimo_decoder_t mimo_decoder = SRSLTE_MIMO_DECODER_MMSE; /************************************************ diff --git a/lib/src/phy/utils/test/mat_test.c b/lib/src/phy/utils/test/mat_test.c index 46081da98..0bfb482a9 100644 --- a/lib/src/phy/utils/test/mat_test.c +++ b/lib/src/phy/utils/test/mat_test.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include "srslte/phy/utils/mat.h" diff --git a/lib/src/phy/utils/test/vector_test.c b/lib/src/phy/utils/test/vector_test.c index cf0f38926..8d5b9f2d6 100644 --- a/lib/src/phy/utils/test/vector_test.c +++ b/lib/src/phy/utils/test/vector_test.c @@ -29,9 +29,7 @@ #include #include #include -#include #include -#include #include #include diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 2dd354548..109c99717 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -556,7 +556,7 @@ void srslte_vec_prod_cfc_simd(cf_t *x, float *y, cf_t *z, int len) { for (; i < len - SRSLTE_SIMD_F_SIZE + 1; i += SRSLTE_SIMD_F_SIZE) { simd_f_t s = srslte_simd_f_loadu(&y[i]); - simd_cf_t a = srslte_simd_cfi_load(&x[i]); + simd_cf_t a = srslte_simd_cfi_loadu(&x[i]); simd_cf_t r = srslte_simd_cf_mul(a, s); srslte_simd_cfi_storeu(&z[i], r); } @@ -1036,7 +1036,7 @@ uint32_t srslte_vec_max_fi_simd(float *x, int len) { for (; i < len - SRSLTE_SIMD_I_SIZE + 1; i += SRSLTE_SIMD_I_SIZE) { simd_f_t a = srslte_simd_f_load(&x[i]); - simd_i_t res = srslte_simd_f_max(a, simd_max_values); + simd_sel_t res = srslte_simd_f_max(a, simd_max_values); simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) a, res); @@ -1046,7 +1046,7 @@ uint32_t srslte_vec_max_fi_simd(float *x, int len) { for (; i < len - SRSLTE_SIMD_I_SIZE + 1; i += SRSLTE_SIMD_I_SIZE) { simd_f_t a = srslte_simd_f_loadu(&x[i]); - simd_i_t res = srslte_simd_f_max(a, simd_max_values); + simd_sel_t res = srslte_simd_f_max(a, simd_max_values); simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) a, res); @@ -1102,7 +1102,7 @@ uint32_t srslte_vec_max_ci_simd(cf_t *x, int len) { simd_f_t z1 = srslte_simd_f_hadd(mul1, mul2); - simd_i_t res = srslte_simd_f_max(z1, simd_max_values); + simd_sel_t res = srslte_simd_f_max(z1, simd_max_values); simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) z1, res); @@ -1118,7 +1118,7 @@ uint32_t srslte_vec_max_ci_simd(cf_t *x, int len) { simd_f_t z1 = srslte_simd_f_hadd(mul1, mul2); - simd_i_t res = srslte_simd_f_max(z1, simd_max_values); + simd_sel_t res = srslte_simd_f_max(z1, simd_max_values); simd_max_indexes = srslte_simd_i_select(simd_max_indexes, simd_indexes, res); simd_max_values = (simd_f_t) srslte_simd_i_select((simd_i_t) simd_max_values, (simd_i_t) z1, res); From d6bdabfdc09ddd315e5285ae46e335f59557d9fd Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 29 Sep 2017 20:38:12 +0200 Subject: [PATCH 08/49] Changed all harq delays to variables --- lib/include/srslte/common/common.h | 6 +++++ srsenb/hdr/mac/scheduler_ue.h | 2 +- srsenb/hdr/mac/ue.h | 2 +- srsenb/hdr/phy/phch_common.h | 8 +++---- srsenb/src/mac/mac.cc | 6 ++--- srsenb/src/mac/scheduler.cc | 8 +++---- srsenb/src/mac/scheduler_harq.cc | 2 +- srsenb/src/mac/scheduler_ue.cc | 4 ++-- srsenb/src/phy/phch_common.cc | 4 ++-- srsenb/src/phy/phch_worker.cc | 35 +++++++++++++++--------------- srsue/hdr/mac/mac.h | 2 +- srsue/hdr/phy/phch_common.h | 2 +- srsue/src/mac/proc_bsr.cc | 2 +- srsue/src/phy/phch_common.cc | 14 ++++++------ srsue/src/phy/phch_recv.cc | 4 ++-- srsue/src/phy/phch_worker.cc | 26 +++++++++++----------- 16 files changed, 66 insertions(+), 61 deletions(-) diff --git a/lib/include/srslte/common/common.h b/lib/include/srslte/common/common.h index 7156fbfc9..ea89cc8b7 100644 --- a/lib/include/srslte/common/common.h +++ b/lib/include/srslte/common/common.h @@ -44,6 +44,12 @@ #define SRSLTE_N_DRB 8 #define SRSLTE_N_RADIO_BEARERS 11 +#define HARQ_SFMOD 10 +#define HARQ_DELAY_MS 4 +#define MSG3_DELAY_MS 6 +#define HARQ_TX(tti) ((tti+HARQ_DELAY_MS)%10240) +#define HARQ_RX(tti) ((tti+(2*HARQ_DELAY_MS))%10240) + // Cat 3 UE - Max number of DL-SCH transport block bits received within a TTI // 3GPP 36.306 Table 4.1.1 #define SRSLTE_MAX_BUFFER_SIZE_BITS 102048 diff --git a/srsenb/hdr/mac/scheduler_ue.h b/srsenb/hdr/mac/scheduler_ue.h index b59461140..c454f1edf 100644 --- a/srsenb/hdr/mac/scheduler_ue.h +++ b/srsenb/hdr/mac/scheduler_ue.h @@ -173,7 +173,7 @@ private: // Allowed DCI locations per CFI and per subframe sched_dci_cce_t dci_locations[3][10]; - const static int SCHED_MAX_HARQ_PROC = 8; + const static int SCHED_MAX_HARQ_PROC = 2*HARQ_DELAY_MS; dl_harq_proc dl_harq[SCHED_MAX_HARQ_PROC]; ul_harq_proc ul_harq[SCHED_MAX_HARQ_PROC]; diff --git a/srsenb/hdr/mac/ue.h b/srsenb/hdr/mac/ue.h index b879d040b..0f95a1144 100644 --- a/srsenb/hdr/mac/ue.h +++ b/srsenb/hdr/mac/ue.h @@ -120,7 +120,7 @@ private: uint32_t nof_failures; - const static int NOF_HARQ_PROCESSES = 8; + const static int NOF_HARQ_PROCESSES = 2*HARQ_DELAY_MS; srslte_softbuffer_tx_t softbuffer_tx[NOF_HARQ_PROCESSES]; srslte_softbuffer_rx_t softbuffer_rx[NOF_HARQ_PROCESSES]; diff --git a/srsenb/hdr/phy/phch_common.h b/srsenb/hdr/phy/phch_common.h index 00a59d969..b6c4ceb08 100644 --- a/srsenb/hdr/phy/phch_common.h +++ b/srsenb/hdr/phy/phch_common.h @@ -78,13 +78,13 @@ public: mac_interface_phy *mac; // Common objects for schedulign grants - mac_interface_phy::ul_sched_t ul_grants[10]; - mac_interface_phy::dl_sched_t dl_grants[10]; + mac_interface_phy::ul_sched_t ul_grants[HARQ_SFMOD]; + mac_interface_phy::dl_sched_t dl_grants[HARQ_SFMOD]; // Map of pending ACKs for each user typedef struct { - bool is_pending[10]; - uint16_t n_pdcch[10]; + bool is_pending[HARQ_SFMOD]; + uint16_t n_pdcch[HARQ_SFMOD]; } pending_ack_t; std::map pending_ack; diff --git a/srsenb/src/mac/mac.cc b/srsenb/src/mac/mac.cc index 7cb30cc65..0528852be 100644 --- a/srsenb/src/mac/mac.cc +++ b/srsenb/src/mac/mac.cc @@ -406,7 +406,7 @@ int mac::get_dl_sched(uint32_t tti, dl_sched_t *dl_sched_res) log_step_dl(tti); if (!started) { - return 0; + return 0; } if (!dl_sched_res) { @@ -604,7 +604,7 @@ int mac::get_ul_sched(uint32_t tti, ul_sched_t *ul_sched_res) void mac::log_step_ul(uint32_t tti) { - int tti_ul = tti-8; + int tti_ul = tti-(2*HARQ_DELAY_MS); if (tti_ul < 0) { tti_ul += 10240; } @@ -613,7 +613,7 @@ void mac::log_step_ul(uint32_t tti) void mac::log_step_dl(uint32_t tti) { - int tti_dl = tti-4; + int tti_dl = tti-HARQ_DELAY_MS; if (tti_dl < 0) { tti_dl += 10240; } diff --git a/srsenb/src/mac/scheduler.cc b/srsenb/src/mac/scheduler.cc index 79cf3f476..5de780470 100644 --- a/srsenb/src/mac/scheduler.cc +++ b/srsenb/src/mac/scheduler.cc @@ -541,7 +541,7 @@ int sched::dl_sched_rar(dl_sched_rar_t rar[MAX_RAR_LIST]) pending_rar[j].rar_tti = 0; // Save UL resources - uint32_t pending_tti=(current_tti+6)%10; + uint32_t pending_tti=(current_tti+MSG3_DELAY_MS)%10; pending_msg3[pending_tti].enabled = true; pending_msg3[pending_tti].rnti = pending_rar[j].rnti; pending_msg3[pending_tti].L = L_prb; @@ -677,7 +677,7 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched pthread_mutex_lock(&mutex); /* If dl_sched() not yet called this tti (this tti is +4ms advanced), reset CCE state */ - if ((current_tti+4)%10240 != tti) { + if (HARQ_TX(current_tti) != tti) { bzero(used_cce, MAX_CCE*sizeof(bool)); } @@ -685,9 +685,9 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched current_tti = tti; sfn = tti/10; if (tti > 4) { - sf_idx = (tti-4)%10; + sf_idx = (tti-HARQ_DELAY_MS)%10; } else { - sf_idx = (tti+10240-4)%10; + sf_idx = (tti+10240-HARQ_DELAY_MS)%10; } int nof_dci_elems = 0; int nof_phich_elems = 0; diff --git a/srsenb/src/mac/scheduler_harq.cc b/srsenb/src/mac/scheduler_harq.cc index a6ae70d19..f5209b374 100644 --- a/srsenb/src/mac/scheduler_harq.cc +++ b/srsenb/src/mac/scheduler_harq.cc @@ -177,7 +177,7 @@ void dl_harq_proc::set_rbgmask(uint32_t new_mask) bool dl_harq_proc::has_pending_retx(uint32_t current_tti) { - return srslte_tti_interval(current_tti, tti) >= 8 && has_pending_retx_common(); + return srslte_tti_interval(current_tti, tti) >= (2*HARQ_DELAY_MS) && has_pending_retx_common(); } int dl_harq_proc::get_tbs() diff --git a/srsenb/src/mac/scheduler_ue.cc b/srsenb/src/mac/scheduler_ue.cc index 1534d12ef..567711cbe 100644 --- a/srsenb/src/mac/scheduler_ue.cc +++ b/srsenb/src/mac/scheduler_ue.cc @@ -247,7 +247,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32 // First check if it has pending ACKs for (int i=0;iul_grants[i].nof_grants;j++) { if (phy->ul_grants[i].sched_grants[j].rnti == rnti) { phy->ul_grants[i].sched_grants[j].rnti = 0; @@ -266,7 +266,7 @@ void phch_worker::rem_rnti(uint16_t rnti) void phch_worker::work_imp() { uint32_t sf_ack; - + if (!running) { return; } @@ -326,12 +326,12 @@ void phch_worker::work_imp() encode_phich(ul_grants[sf_sched_ul].phich, ul_grants[sf_sched_ul].nof_phich, sf_tx); // Prepare for receive ACK for DL grants in sf_tx+4 - sf_ack = (sf_tx+4)%10; + sf_ack = HARQ_RX(sf_tx)%HARQ_SFMOD; phy->ack_clear(sf_ack); for (uint32_t i=0;i= SRSLTE_CRNTI_START && dl_grants[sf_tx].sched_grants[i].rnti <= SRSLTE_CRNTI_END) { - phy->ack_set_pending(sf_ack, dl_grants[sf_tx].sched_grants[i].rnti, dl_grants[sf_tx].sched_grants[i].location.ncce); + phy->ack_set_pending(sf_ack, dl_grants[sf_tx].sched_grants[i].rnti, dl_grants[sf_tx].sched_grants[i].location.ncce); } } @@ -504,8 +504,7 @@ int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch, int phch_worker::decode_pucch(uint32_t tti_rx) { - uint32_t sf_rx = tti_rx%10; - srslte_uci_data_t uci_data; + srslte_uci_data_t uci_data; for(std::map::iterator iter=ue_db.begin(); iter!=ue_db.end(); ++iter) { uint16_t rnti = (uint16_t) iter->first; @@ -523,7 +522,7 @@ int phch_worker::decode_pucch(uint32_t tti_rx) uci_data.scheduling_request = true; } } - if (phy->ack_is_pending(sf_rx, rnti, &last_n_pdcch)) { + if (phy->ack_is_pending(tti_rx%HARQ_SFMOD, rnti, &last_n_pdcch)) { needs_pucch = true; needs_ack = true; uci_data.uci_ack_len = 1; @@ -589,7 +588,7 @@ int phch_worker::encode_phich(srslte_enb_dl_phich_t *acks, uint32_t nof_acks, ui srslte_enb_dl_put_phich(&enb_dl, acks[i].ack, ue_db[rnti].phich_info.n_prb_lowest, ue_db[rnti].phich_info.n_dmrs, - sf_idx); + sf_idx%10); Info("PHICH: rnti=0x%x, hi=%d, I_lowest=%d, n_dmrs=%d, tti_tx=%d\n", rnti, acks[i].ack, @@ -606,13 +605,13 @@ int phch_worker::encode_pdcch_ul(srslte_enb_ul_pusch_t *grants, uint32_t nof_gra for (uint32_t i=0;iinfo_hex(ptr, len, - "PDSCH: rnti=0x%x, l_crb=%2d, %s, harq=%d, tbs=%d, mcs=%d, rv=%d, tti_tx=%d\n", + "PDSCH: rnti=0x%x, l_crb=%2d, %s, harq=%d, tbs=%d, mcs=%d, rv=%d, tti_tx=%d\n", rnti, phy_grant.nof_prb, grant_str, grants[i].grant.harq_process, phy_grant.mcs[0].tbs/8, phy_grant.mcs[0].idx, grants[i].grant.rv_idx, tti_tx); } diff --git a/srsue/hdr/mac/mac.h b/srsue/hdr/mac/mac.h index a306af187..d19f668bf 100644 --- a/srsue/hdr/mac/mac.h +++ b/srsue/hdr/mac/mac.h @@ -109,7 +109,7 @@ private: static const int MAC_MAIN_THREAD_PRIO = 5; static const int MAC_PDU_THREAD_PRIO = 6; - static const int MAC_NOF_HARQ_PROC = 8; + static const int MAC_NOF_HARQ_PROC = 2*HARQ_DELAY_MS; // Interaction with PHY srslte::tti_sync_cv ttisync; diff --git a/srsue/hdr/phy/phch_common.h b/srsue/hdr/phy/phch_common.h index aa64fe9ea..fb077e733 100644 --- a/srsue/hdr/phy/phch_common.h +++ b/srsue/hdr/phy/phch_common.h @@ -138,7 +138,7 @@ namespace srsue { uint32_t I_lowest; uint32_t n_dmrs; } pending_ack_t; - pending_ack_t pending_ack[10]; + pending_ack_t pending_ack[HARQ_SFMOD]; bool is_first_tx; diff --git a/srsue/src/mac/proc_bsr.cc b/srsue/src/mac/proc_bsr.cc index 898943ab9..43694c1bc 100644 --- a/srsue/src/mac/proc_bsr.cc +++ b/srsue/src/mac/proc_bsr.cc @@ -368,7 +368,7 @@ bool bsr_proc::need_to_reset_sr() { bool bsr_proc::need_to_send_sr(uint32_t tti) { if (!sr_is_sent && triggered_bsr_type == REGULAR) { - if (srslte_tti_interval(tti,next_tx_tti)>0 && srslte_tti_interval(tti,next_tx_tti) < 10240-4) { + if (srslte_tti_interval(tti,next_tx_tti)>0 && srslte_tti_interval(tti,next_tx_tti) < 10240-HARQ_DELAY_MS) { reset_sr = false; sr_is_sent = true; Debug("BSR: Need to send sr: sr_is_sent=true, reset_sr=false, tti=%d, next_tx_tti=%d\n", tti, next_tx_tti); diff --git a/srsue/src/phy/phch_common.cc b/srsue/src/phy/phch_common.cc index d49b1ced2..cce1857f1 100644 --- a/srsue/src/phy/phch_common.cc +++ b/srsue/src/phy/phch_common.cc @@ -195,13 +195,13 @@ void phch_common::set_dl_rnti(srslte_rnti_type_t type, uint16_t rnti_value, int } void phch_common::reset_pending_ack(uint32_t tti) { - pending_ack[tti%10].enabled = false; + pending_ack[tti%HARQ_SFMOD].enabled = false; } void phch_common::set_pending_ack(uint32_t tti, uint32_t I_lowest, uint32_t n_dmrs) { - pending_ack[tti%10].enabled = true; - pending_ack[tti%10].I_lowest = I_lowest; - pending_ack[tti%10].n_dmrs = n_dmrs; + pending_ack[tti%HARQ_SFMOD].enabled = true; + pending_ack[tti%HARQ_SFMOD].I_lowest = I_lowest; + pending_ack[tti%HARQ_SFMOD].n_dmrs = n_dmrs; Debug("Set pending ACK for tti=%d I_lowest=%d, n_dmrs=%d\n", tti, I_lowest, n_dmrs); } @@ -211,12 +211,12 @@ bool phch_common::get_pending_ack(uint32_t tti) { bool phch_common::get_pending_ack(uint32_t tti, uint32_t *I_lowest, uint32_t *n_dmrs) { if (I_lowest) { - *I_lowest = pending_ack[tti%10].I_lowest; + *I_lowest = pending_ack[tti%HARQ_SFMOD].I_lowest; } if (n_dmrs) { - *n_dmrs = pending_ack[tti%10].n_dmrs; + *n_dmrs = pending_ack[tti%HARQ_SFMOD].n_dmrs; } - return pending_ack[tti%10].enabled; + return pending_ack[tti%HARQ_SFMOD].enabled; } /* The transmisison of UL subframes must be in sequence. Each worker uses this function to indicate diff --git a/srsue/src/phy/phch_recv.cc b/srsue/src/phy/phch_recv.cc index dad2c82b8..647226384 100644 --- a/srsue/src/phy/phch_recv.cc +++ b/srsue/src/phy/phch_recv.cc @@ -715,11 +715,11 @@ void phch_recv::run_thread() { worker->set_sample_offset(srslte_ue_sync_get_sfo(&ue_sync)/1000); - /* Compute TX time: Any transmission happens in TTI4 thus advance 4 ms the reception time */ + /* Compute TX time: Any transmission happens in TTI+4 thus advance 4 ms the reception time */ srslte_timestamp_t rx_time, tx_time, tx_time_prach; srslte_ue_sync_get_last_timestamp(&ue_sync, &rx_time); srslte_timestamp_copy(&tx_time, &rx_time); - srslte_timestamp_add(&tx_time, 0, 4e-3 - time_adv_sec); + srslte_timestamp_add(&tx_time, 0, HARQ_DELAY_MS*1e-3 - time_adv_sec); worker->set_tx_time(tx_time, next_offset); next_offset = 0; diff --git a/srsue/src/phy/phch_worker.cc b/srsue/src/phy/phch_worker.cc index 1c9abc1ca..1c52568af 100644 --- a/srsue/src/phy/phch_worker.cc +++ b/srsue/src/phy/phch_worker.cc @@ -335,7 +335,7 @@ void phch_worker::work_imp() &ul_action.softbuffers[0], ul_action.rv[0], ul_action.rnti, ul_mac_grant.is_from_rar); signal_ready = true; if (ul_action.expect_ack) { - phy->set_pending_ack(tti + 8, ue_ul.pusch_cfg.grant.n_prb_tilde[0], ul_action.phy_grant.ul.ncs_dmrs); + phy->set_pending_ack(tti + 2*HARQ_DELAY_MS, ue_ul.pusch_cfg.grant.n_prb_tilde[0], ul_action.phy_grant.ul.ncs_dmrs); } } else if (dl_action.generate_ack || uci_data.scheduling_request || uci_data.uci_cqi_len > 0) { @@ -663,7 +663,7 @@ bool phch_worker::decode_pdcch_ul(mac_interface_phy::mac_grant_t* grant) char timestr[64]; timestr[0]='\0'; - phy->reset_pending_ack(tti + 8); + phy->reset_pending_ack(tti + 2*HARQ_DELAY_MS); srslte_dci_msg_t dci_msg; srslte_ra_ul_dci_t dci_unpacked; @@ -776,7 +776,7 @@ void phch_worker::set_uci_sr() { uci_data.scheduling_request = false; if (phy->sr_enabled) { - uint32_t sr_tx_tti = (tti+4)%10240; + uint32_t sr_tx_tti = HARQ_TX(tti); // Get I_sr parameter if (srslte_ue_ul_sr_send_tti(I_sr, sr_tx_tti)) { Info("PUCCH: SR transmission at TTI=%d, I_sr=%d\n", sr_tx_tti, I_sr); @@ -793,7 +793,7 @@ void phch_worker::set_uci_periodic_cqi() int cqi_max = phy->args->cqi_max; if (period_cqi.configured && rnti_is_set) { - if (period_cqi.ri_idx_present && srslte_ri_send(period_cqi.pmi_idx, period_cqi.ri_idx, (tti+4)%10240)) { + if (period_cqi.ri_idx_present && srslte_ri_send(period_cqi.pmi_idx, period_cqi.ri_idx, HARQ_TX(tti))) { if (uci_data.uci_ri_len) { uci_data.uci_cqi[0] = uci_data.uci_ri; uci_data.uci_cqi_len = uci_data.uci_ri_len; @@ -802,7 +802,7 @@ void phch_worker::set_uci_periodic_cqi() uci_data.uci_pmi_len = 0; Info("PUCCH: Periodic RI=%d\n", uci_data.uci_cqi[0]); } - } else if (srslte_cqi_send(period_cqi.pmi_idx, (tti+4)%10240)) { + } else if (srslte_cqi_send(period_cqi.pmi_idx, HARQ_TX(tti))) { srslte_cqi_value_t cqi_report; if (period_cqi.format_is_subband) { // TODO: Implement subband periodic reports @@ -868,8 +868,8 @@ void phch_worker::set_uci_aperiodic_cqi() bool phch_worker::srs_is_ready_to_send() { if (srs_cfg.configured) { - if (srslte_refsignal_srs_send_cs(srs_cfg.subframe_config, (tti+4)%10) == 1 && - srslte_refsignal_srs_send_ue(srs_cfg.I_srs, (tti+4)%10240) == 1) + if (srslte_refsignal_srs_send_cs(srs_cfg.subframe_config, HARQ_RX(tti)%10) == 1 && + srslte_refsignal_srs_send_ue(srs_cfg.I_srs, HARQ_TX(tti)) == 1) { return true; } @@ -889,7 +889,7 @@ void phch_worker::encode_pusch(srslte_ra_ul_grant_t *grant, uint8_t *payload, ui char timestr[64]; timestr[0]='\0'; - if (srslte_ue_ul_cfg_grant(&ue_ul, grant, (tti+4)%10240, rv, current_tx_nb)) { + if (srslte_ue_ul_cfg_grant(&ue_ul, grant, HARQ_TX(tti), rv, current_tx_nb)) { Error("Configuring UL grant\n"); } @@ -919,7 +919,7 @@ void phch_worker::encode_pusch(srslte_ra_ul_grant_t *grant, uint8_t *payload, ui #endif Info("PUSCH: tti_tx=%d, n_prb=%d, rb_start=%d, tbs=%d, mod=%d, mcs=%d, rv_idx=%d, ack=%s, ri=%s, cfo=%.1f Hz%s\n", - (tti+4)%10240, + HARQ_TX(tti), grant->L_prb, grant->n_prb[0], grant->mcs.tbs/8, grant->mcs.mod, grant->mcs.idx, rv, uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", @@ -950,7 +950,7 @@ void phch_worker::encode_pucch() gettimeofday(&t[1], NULL); #endif - if (srslte_ue_ul_pucch_encode(&ue_ul, uci_data, last_dl_pdcch_ncce, (tti+4)%10240, signal_buffer[0])) { + if (srslte_ue_ul_pucch_encode(&ue_ul, uci_data, last_dl_pdcch_ncce, HARQ_TX(tti), signal_buffer[0])) { Error("Encoding PUCCH\n"); } @@ -966,7 +966,7 @@ void phch_worker::encode_pucch() float gain = set_power(tx_power); Info("PUCCH: tti_tx=%d, n_cce=%3d, n_pucch=%d, n_prb=%d, ack=%s%s, ri=%s, pmi=%s%s, sr=%s, cfo=%.1f Hz%s\n", - (tti+4)%10240, + HARQ_TX(tti), last_dl_pdcch_ncce, ue_ul.pucch.last_n_pucch, ue_ul.pucch.last_n_prb, uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", uci_data.uci_ack_len>1?(uci_data.uci_ack_2?"1":"0"):"", @@ -987,7 +987,7 @@ void phch_worker::encode_srs() char timestr[64]; timestr[0]='\0'; - if (srslte_ue_ul_srs_encode(&ue_ul, (tti+4)%10240, signal_buffer[0])) + if (srslte_ue_ul_srs_encode(&ue_ul, HARQ_TX(tti), signal_buffer[0])) { Error("Encoding SRS\n"); } @@ -1002,7 +1002,7 @@ void phch_worker::encode_srs() float gain = set_power(tx_power); uint32_t fi = srslte_vec_max_fi((float*) signal_buffer, SRSLTE_SF_LEN_PRB(cell.nof_prb)); float *f = (float*) signal_buffer; - Info("SRS: power=%.2f dBm, tti_tx=%d%s\n", tx_power, (tti+4)%10240, timestr); + Info("SRS: power=%.2f dBm, tti_tx=%d%s\n", tx_power, HARQ_TX(tti), timestr); } From d3916cec9187e768d79799cd3da1449a780c6e62 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 2 Oct 2017 10:50:06 +0200 Subject: [PATCH 09/49] fix segfault when printing help message --- lib/examples/pdsch_ue.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/examples/pdsch_ue.c b/lib/examples/pdsch_ue.c index 38113dbcf..9d7282cdd 100644 --- a/lib/examples/pdsch_ue.c +++ b/lib/examples/pdsch_ue.c @@ -99,7 +99,7 @@ typedef struct { int net_port_signal; char *net_address_signal; int decimate; - int mbsfn_area_id; + int32_t mbsfn_area_id; uint8_t non_mbsfn_region; int verbose; }prog_args_t; @@ -171,8 +171,8 @@ void usage(prog_args_t *args, char *prog) { printf("\t-S remote UDP address to send input signal [Default %s]\n", args->net_address_signal); printf("\t-u remote TCP port to send data (-1 does nothing with it) [Default %d]\n", args->net_port); printf("\t-U remote TCP address to send data [Default %s]\n", args->net_address); - printf("\t-M MBSFN area id [Default %s]\n", args->mbsfn_area_id); - printf("\t-N Non-MBSFN region [Default %s]\n", args->non_mbsfn_region); + printf("\t-M MBSFN area id [Default %d]\n", args->mbsfn_area_id); + printf("\t-N Non-MBSFN region [Default %d]\n", args->non_mbsfn_region); printf("\t-v [set srslte_verbose to debug, default none]\n"); } From 428e5955df68d4967c1ec56a5d44e476f0d4ad7d Mon Sep 17 00:00:00 2001 From: Paul Sutton Date: Thu, 21 Sep 2017 16:49:33 +0100 Subject: [PATCH 10/49] Fix for SPGW address issue for GTP bearers - now using addresses from bearer setup requests htonl fix --- .../srslte/interfaces/enb_interfaces.h | 2 +- srsenb/hdr/upper/gtpu.h | 10 ++- srsenb/hdr/upper/rrc.h | 3 + srsenb/src/upper/gtpu.cc | 81 ++++++++++++++++--- srsenb/src/upper/rrc.cc | 62 ++++++++------ 5 files changed, 116 insertions(+), 42 deletions(-) diff --git a/lib/include/srslte/interfaces/enb_interfaces.h b/lib/include/srslte/interfaces/enb_interfaces.h index ed2478896..9c85814f8 100644 --- a/lib/include/srslte/interfaces/enb_interfaces.h +++ b/lib/include/srslte/interfaces/enb_interfaces.h @@ -244,7 +244,7 @@ public: class gtpu_interface_rrc { public: - virtual void add_bearer(uint16_t rnti, uint32_t lcid, uint32_t teid_out, uint32_t *teid_in) = 0; + virtual void add_bearer(uint16_t rnti, uint32_t lcid, uint32_t addr, uint32_t teid_out, uint32_t *teid_in) = 0; virtual void rem_bearer(uint16_t rnti, uint32_t lcid) = 0; virtual void rem_user(uint16_t rnti) = 0; }; diff --git a/srsenb/hdr/upper/gtpu.h b/srsenb/hdr/upper/gtpu.h index 9ad9441fa..6ec371655 100644 --- a/srsenb/hdr/upper/gtpu.h +++ b/srsenb/hdr/upper/gtpu.h @@ -75,7 +75,7 @@ public: void stop(); // gtpu_interface_rrc - void add_bearer(uint16_t rnti, uint32_t lcid, uint32_t teid_out, uint32_t *teid_in); + void add_bearer(uint16_t rnti, uint32_t lcid, uint32_t addr, uint32_t teid_out, uint32_t *teid_in); void rem_bearer(uint16_t rnti, uint32_t lcid); void rem_user(uint16_t rnti); @@ -86,7 +86,7 @@ public: private: static const int THREAD_PRIO = 7; static const int GTPU_PORT = 2152; - srslte::byte_buffer_pool *pool; + srslte::byte_buffer_pool *pool; bool running; bool run_enable; @@ -98,11 +98,13 @@ private: typedef struct{ uint32_t teids_in[SRSENB_N_RADIO_BEARERS]; uint32_t teids_out[SRSENB_N_RADIO_BEARERS]; + uint32_t spgw_addrs[SRSENB_N_RADIO_BEARERS]; }bearer_map; std::map rnti_bearers; - srslte_netsink_t snk; - srslte_netsource_t src; + // Socket file descriptors + int snk_fd; + int src_fd; void run_thread(); diff --git a/srsenb/hdr/upper/rrc.h b/srsenb/hdr/upper/rrc.h index 49a02d0b9..e0b9dd158 100644 --- a/srsenb/hdr/upper/rrc.h +++ b/srsenb/hdr/upper/rrc.h @@ -195,6 +195,9 @@ public: bool setup_erabs(LIBLTE_S1AP_E_RABTOBESETUPLISTCTXTSUREQ_STRUCT *e); bool setup_erabs(LIBLTE_S1AP_E_RABTOBESETUPLISTBEARERSUREQ_STRUCT *e); + void setup_erab(uint8_t id, LIBLTE_S1AP_E_RABLEVELQOSPARAMETERS_STRUCT *qos, + LIBLTE_S1AP_TRANSPORTLAYERADDRESS_STRUCT *addr, uint32_t teid_out, + LIBLTE_S1AP_NAS_PDU_STRUCT *nas_pdu); bool release_erabs(); void notify_s1ap_ue_ctxt_setup_complete(); diff --git a/srsenb/src/upper/gtpu.cc b/srsenb/src/upper/gtpu.cc index 83f7a1b75..ddd695ffe 100644 --- a/srsenb/src/upper/gtpu.cc +++ b/srsenb/src/upper/gtpu.cc @@ -26,6 +26,9 @@ #include "upper/gtpu.h" #include +#include +#include +#include using namespace srslte; @@ -42,16 +45,51 @@ bool gtpu::init(std::string gtp_bind_addr_, std::string mme_addr_, srsenb::pdcp_ pool = byte_buffer_pool::get_instance(); - if(0 != srslte_netsource_init(&src, gtp_bind_addr.c_str(), GTPU_PORT, SRSLTE_NETSOURCE_UDP)) { - gtpu_log->error("Failed to create source socket on %s:%d", gtp_bind_addr.c_str(), GTPU_PORT); + // Set up sink socket + snk_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (snk_fd < 0) { + gtpu_log->error("Failed to create sink socket\n"); return false; } - if(0 != srslte_netsink_init(&snk, mme_addr.c_str(), GTPU_PORT, SRSLTE_NETSINK_UDP)) { - gtpu_log->error("Failed to create sink socket on %s:%d", mme_addr.c_str(), GTPU_PORT); + if (fcntl(snk_fd, F_SETFL, O_NONBLOCK)) { + gtpu_log->error("Failed to set non-blocking sink socket\n"); + return false; + } + int enable = 1; +#if defined (SO_REUSEADDR) + if (setsockopt(snk_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + gtpu_log->error("setsockopt(SO_REUSEADDR) failed\n"); +#endif +#if defined (SO_REUSEPORT) + if (setsockopt(snk_fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) + gtpu_log->error("setsockopt(SO_REUSEPORT) failed\n"); +#endif + + + // Set up source socket + src_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (src_fd < 0) { + gtpu_log->error("Failed to create source socket\n"); + return false; + } +#if defined (SO_REUSEADDR) + if (setsockopt(src_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + gtpu_log->error("setsockopt(SO_REUSEADDR) failed\n"); +#endif +#if defined (SO_REUSEPORT) + if (setsockopt(src_fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) + gtpu_log->error("setsockopt(SO_REUSEPORT) failed\n"); +#endif + + struct sockaddr_in bindaddr; + bindaddr.sin_family = AF_INET; + bindaddr.sin_addr.s_addr = inet_addr(gtp_bind_addr.c_str()); + bindaddr.sin_port = htons(GTPU_PORT); + + if (bind(src_fd, (struct sockaddr *)&bindaddr, sizeof(struct sockaddr_in))) { + gtpu_log->error("Failed to bind on address %s, port %d\n", gtp_bind_addr, GTPU_PORT); return false; } - - srslte_netsink_set_nonblocking(&snk); // Setup a thread to receive packets from the src socket start(THREAD_PRIO); @@ -61,7 +99,7 @@ bool gtpu::init(std::string gtp_bind_addr_, std::string mme_addr_, srsenb::pdcp_ void gtpu::stop() { - if(run_enable) { + if (run_enable) { run_enable = false; // Wait thread to exit gracefully otherwise might leave a mutex locked int cnt=0; @@ -75,8 +113,12 @@ void gtpu::stop() wait_thread_finish(); } - srslte_netsink_free(&snk); - srslte_netsource_free(&src); + if (snk_fd) { + close(snk_fd); + } + if (src_fd) { + close(src_fd); + } } // gtpu_interface_pdcp @@ -89,28 +131,35 @@ void gtpu::write_pdu(uint16_t rnti, uint32_t lcid, srslte::byte_buffer_t* pdu) header.length = pdu->N_bytes; header.teid = rnti_bearers[rnti].teids_out[lcid]; + struct sockaddr_in servaddr; + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(rnti_bearers[rnti].spgw_addrs[lcid]); + servaddr.sin_port = htons(GTPU_PORT); + gtpu_write_header(&header, pdu); - srslte_netsink_write(&snk, pdu->msg, pdu->N_bytes); + sendto(snk_fd, pdu->msg, pdu->N_bytes, MSG_EOR, (struct sockaddr*)&servaddr, sizeof(struct sockaddr_in)); pool->deallocate(pdu); } // gtpu_interface_rrc -void gtpu::add_bearer(uint16_t rnti, uint32_t lcid, uint32_t teid_out, uint32_t *teid_in) +void gtpu::add_bearer(uint16_t rnti, uint32_t lcid, uint32_t addr, uint32_t teid_out, uint32_t *teid_in) { // Allocate a TEID for the incoming tunnel rntilcid_to_teidin(rnti, lcid, teid_in); - gtpu_log->info("Adding bearer for rnti: 0x%x, lcid: %d, teid_out: 0x%x, teid_in: 0x%x\n", rnti, lcid, teid_out, *teid_in); + gtpu_log->info("Adding bearer for rnti: 0x%x, lcid: %d, addr: 0x%x, teid_out: 0x%x, teid_in: 0x%x\n", rnti, lcid, addr, teid_out, *teid_in); // Initialize maps if it's a new RNTI if(rnti_bearers.count(rnti) == 0) { for(int i=0;ireset(); gtpu_log->debug("Waiting for read...\n"); - pdu->N_bytes = srslte_netsource_read(&src, pdu->msg, SRSENB_MAX_BUFFER_SIZE_BYTES - SRSENB_BUFFER_HEADER_OFFSET); + do{ + pdu->N_bytes = recv(src_fd, pdu->msg, SRSENB_MAX_BUFFER_SIZE_BYTES - SRSENB_BUFFER_HEADER_OFFSET, 0); + }while (pdu->N_bytes == -1 && errno == EAGAIN); + if (pdu->N_bytes == -1) { + gtpu_log->error("Failed to read from socket\n"); + } gtpu_header_t header; gtpu_read_header(pdu, &header); diff --git a/srsenb/src/upper/rrc.cc b/srsenb/src/upper/rrc.cc index 89b38dd36..b1280cf55 100644 --- a/srsenb/src/upper/rrc.cc +++ b/srsenb/src/upper/rrc.cc @@ -946,20 +946,16 @@ bool rrc::ue::setup_erabs(LIBLTE_S1AP_E_RABTOBESETUPLISTCTXTSUREQ_STRUCT *e) if(erab->iE_Extensions_present) { parent->rrc_log->warning("Not handling LIBLTE_S1AP_E_RABTOBESETUPITEMCTXTSUREQ_STRUCT extensions\n"); } - - uint8_t id = erab->e_RAB_ID.E_RAB_ID; - erabs[id].id = id; - memcpy(&erabs[id].qos_params, &erab->e_RABlevelQoSParameters, sizeof(LIBLTE_S1AP_E_RABLEVELQOSPARAMETERS_STRUCT)); - memcpy(&erabs[id].address, &erab->transportLayerAddress, sizeof(LIBLTE_S1AP_TRANSPORTLAYERADDRESS_STRUCT)); - uint8_to_uint32(erab->gTP_TEID.buffer, &erabs[id].teid_out); - - uint8_t lcid = id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) - parent->gtpu->add_bearer(rnti, lcid, erabs[id].teid_out, &(erabs[id].teid_in)); - - if(erab->nAS_PDU_present) { - memcpy(parent->erab_info.msg, erab->nAS_PDU.buffer, erab->nAS_PDU.n_octets); - parent->erab_info.N_bytes = erab->nAS_PDU.n_octets; + if(erab->transportLayerAddress.n_bits > 32) { + parent->rrc_log->error("IPv6 addresses not currently supported\n"); + return false; } + + uint32_t teid_out; + uint8_to_uint32(erab->gTP_TEID.buffer, &teid_out); + LIBLTE_S1AP_NAS_PDU_STRUCT *nas_pdu = erab->nAS_PDU_present ? &erab->nAS_PDU : NULL; + setup_erab(erab->e_RAB_ID.E_RAB_ID, &erab->e_RABlevelQoSParameters, + &erab->transportLayerAddress, teid_out, nas_pdu); } return true; } @@ -974,25 +970,43 @@ bool rrc::ue::setup_erabs(LIBLTE_S1AP_E_RABTOBESETUPLISTBEARERSUREQ_STRUCT *e) if(erab->iE_Extensions_present) { parent->rrc_log->warning("Not handling LIBLTE_S1AP_E_RABTOBESETUPITEMCTXTSUREQ_STRUCT extensions\n"); } + if(erab->transportLayerAddress.n_bits > 32) { + parent->rrc_log->error("IPv6 addresses not currently supported\n"); + return false; + } - uint8_t id = erab->e_RAB_ID.E_RAB_ID; - erabs[id].id = id; - memcpy(&erabs[id].qos_params, &erab->e_RABlevelQoSParameters, sizeof(LIBLTE_S1AP_E_RABLEVELQOSPARAMETERS_STRUCT)); - memcpy(&erabs[id].address, &erab->transportLayerAddress, sizeof(LIBLTE_S1AP_TRANSPORTLAYERADDRESS_STRUCT)); - uint8_to_uint32(erab->gTP_TEID.buffer, &erabs[id].teid_out); - - uint8_t lcid = id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) - parent->gtpu->add_bearer(rnti, lcid, erabs[id].teid_out, &(erabs[id].teid_in)); - - memcpy(parent->erab_info.msg, erab->nAS_PDU.buffer, erab->nAS_PDU.n_octets); - parent->erab_info.N_bytes = erab->nAS_PDU.n_octets; + uint32_t teid_out; + uint8_to_uint32(erab->gTP_TEID.buffer, &teid_out); + setup_erab(erab->e_RAB_ID.E_RAB_ID, &erab->e_RABlevelQoSParameters, + &erab->transportLayerAddress, teid_out, &erab->nAS_PDU); } + // Work in progress notify_s1ap_ue_erab_setup_response(e); send_connection_reconf_new_bearer(e); return true; } +void rrc::ue::setup_erab(uint8_t id, LIBLTE_S1AP_E_RABLEVELQOSPARAMETERS_STRUCT *qos, + LIBLTE_S1AP_TRANSPORTLAYERADDRESS_STRUCT *addr, uint32_t teid_out, + LIBLTE_S1AP_NAS_PDU_STRUCT *nas_pdu) +{ + erabs[id].id = id; + memcpy(&erabs[id].qos_params, qos, sizeof(LIBLTE_S1AP_E_RABLEVELQOSPARAMETERS_STRUCT)); + memcpy(&erabs[id].address, addr, sizeof(LIBLTE_S1AP_TRANSPORTLAYERADDRESS_STRUCT)); + erabs[id].teid_out = teid_out; + + uint8_t* bit_ptr = addr->buffer; + uint32_t addr_ = liblte_bits_2_value(&bit_ptr, addr->n_bits); + uint8_t lcid = id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) + parent->gtpu->add_bearer(rnti, lcid, addr_, erabs[id].teid_out, &(erabs[id].teid_in)); + + if(nas_pdu) { + memcpy(parent->erab_info.msg, nas_pdu->buffer, nas_pdu->n_octets); + parent->erab_info.N_bytes = nas_pdu->n_octets; + } +} + bool rrc::ue::release_erabs() { typedef std::map::iterator it_t; From 48186cd4fc8fbd0f71cc5345f44f8cc440d3adb4 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Mon, 2 Oct 2017 18:06:39 +0100 Subject: [PATCH 11/49] fixed warning --- srsenb/src/upper/gtpu.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/srsenb/src/upper/gtpu.cc b/srsenb/src/upper/gtpu.cc index ddd695ffe..e01be1ad0 100644 --- a/srsenb/src/upper/gtpu.cc +++ b/srsenb/src/upper/gtpu.cc @@ -198,13 +198,16 @@ void gtpu::run_thread() pdu->reset(); gtpu_log->debug("Waiting for read...\n"); + int n = 0; do{ - pdu->N_bytes = recv(src_fd, pdu->msg, SRSENB_MAX_BUFFER_SIZE_BYTES - SRSENB_BUFFER_HEADER_OFFSET, 0); - }while (pdu->N_bytes == -1 && errno == EAGAIN); + n = recv(src_fd, pdu->msg, SRSENB_MAX_BUFFER_SIZE_BYTES - SRSENB_BUFFER_HEADER_OFFSET, 0); + } while (n == -1 && errno == EAGAIN); - if (pdu->N_bytes == -1) { + if (n < 0) { gtpu_log->error("Failed to read from socket\n"); } + + pdu->N_bytes = (uint32_t) n; gtpu_header_t header; gtpu_read_header(pdu, &header); From dbae016b003c69f651e2c0fe885281ca61b9cb7c Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Mon, 2 Oct 2017 18:16:03 +0100 Subject: [PATCH 12/49] Removed unused vector functions --- lib/include/srslte/phy/utils/vector.h | 35 +---- lib/src/phy/utils/vector.c | 182 +------------------------- lib/src/phy/utils/vector_simd.c | 69 ---------- 3 files changed, 2 insertions(+), 284 deletions(-) diff --git a/lib/include/srslte/phy/utils/vector.h b/lib/include/srslte/phy/utils/vector.h index 9b99c6fff..4a5daefb3 100644 --- a/lib/include/srslte/phy/utils/vector.h +++ b/lib/include/srslte/phy/utils/vector.h @@ -54,7 +54,6 @@ extern "C" { #define SRSLTE_VEC_EMA(data, average, alpha) ((alpha)*(data)+(1-alpha)*(average)) /** Return the sum of all the elements */ -SRSLTE_API int srslte_vec_acc_ii(int *x, uint32_t len); SRSLTE_API float srslte_vec_acc_ff(float *x, uint32_t len); SRSLTE_API cf_t srslte_vec_acc_cc(cf_t *x, uint32_t len); @@ -77,7 +76,6 @@ SRSLTE_API void srslte_vec_save_file(char *filename, void *buffer, uint32_t len) SRSLTE_API void srslte_vec_load_file(char *filename, void *buffer, uint32_t len); /* sum two vectors */ -SRSLTE_API void srslte_vec_sum_ch(uint8_t *x, uint8_t *y, char *z, uint32_t len); SRSLTE_API void srslte_vec_sum_fff(float *x, float *y, float *z, uint32_t len); SRSLTE_API void srslte_vec_sum_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); SRSLTE_API void srslte_vec_sub_sss(int16_t *x, int16_t *y, int16_t *z, uint32_t len); @@ -87,39 +85,16 @@ SRSLTE_API void srslte_vec_sum_sss(int16_t *x, int16_t *y, int16_t *z, uint32_t SRSLTE_API void srslte_vec_sub_fff(float *x, float *y, float *z, uint32_t len); SRSLTE_API void srslte_vec_sub_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); -/* EMA filter: output=coeff*new_data + (1-coeff)*average */ -SRSLTE_API void srslte_vec_ema_filter(cf_t *new_data, cf_t *average, cf_t *output, float coeff, uint32_t len); - -/* Square distance */ -//SRSLTE_API void srslte_vec_square_dist(cf_t symbol, cf_t *points, float *distance, uint32_t npoints); - -/* scalar addition */ -SRSLTE_API void srslte_vec_sc_add_fff(float *x, float h, float *z, uint32_t len); -SRSLTE_API void srslte_vec_sc_add_cfc(cf_t *x, float h, cf_t *z, uint32_t len); -SRSLTE_API void srslte_vec_sc_add_ccc(cf_t *x, cf_t h, cf_t *z, uint32_t len); -SRSLTE_API void srslte_vec_sc_add_sss(int16_t *x, int16_t h, int16_t *z, uint32_t len); - /* scalar product */ SRSLTE_API void srslte_vec_sc_prod_cfc(cf_t *x, float h, cf_t *z, uint32_t len); SRSLTE_API void srslte_vec_sc_prod_ccc(cf_t *x, cf_t h, cf_t *z, uint32_t len); SRSLTE_API void srslte_vec_sc_prod_fff(float *x, float h, float *z, uint32_t len); -SRSLTE_API void srslte_vec_sc_prod_sfs(short *x, float h, short *z, uint32_t len); -SRSLTE_API void srslte_vec_sc_div2_sss(short *x, int pow2_div, short *z, uint32_t len); -/* Normalization */ -SRSLTE_API void srslte_vec_norm_cfc(cf_t *x, float amplitude, cf_t *y, uint32_t len); SRSLTE_API void srslte_vec_convert_fi(float *x, int16_t *z, float scale, uint32_t len); SRSLTE_API void srslte_vec_convert_if(int16_t *x, float *z, float scale, uint32_t len); -SRSLTE_API void srslte_vec_convert_ci(int8_t *x, int16_t *z, uint32_t len); - -SRSLTE_API void srslte_vec_lut_fuf(float *x, uint32_t *lut, float *y, uint32_t len); -SRSLTE_API void srslte_vec_lut_sss(short *x, unsigned short *lut, short *y, uint32_t len); -SRSLTE_API void srslte_vec_deinterleave_cf(cf_t *x, float *real, float *imag, uint32_t len); -SRSLTE_API void srslte_vec_deinterleave_real_cf(cf_t *x, float *real, uint32_t len); - -SRSLTE_API void srslte_vec_interleave_cf(float *real, float *imag, cf_t *x, uint32_t len); +SRSLTE_API void srslte_vec_lut_sss(short *x, unsigned short *lut, short *y, uint32_t len); /* vector product (element-wise) */ SRSLTE_API void srslte_vec_prod_ccc(cf_t *x, cf_t *y, cf_t *z, uint32_t len); @@ -159,11 +134,6 @@ SRSLTE_API float srslte_vec_corr_ccc(cf_t *x, cf_t *y, uint32_t len); /* return the index of the maximum value in the vector */ SRSLTE_API uint32_t srslte_vec_max_fi(float *x, uint32_t len); SRSLTE_API uint32_t srslte_vec_max_abs_ci(cf_t *x, uint32_t len); -SRSLTE_API int16_t srslte_vec_max_star_si(int16_t *x, uint32_t len); -SRSLTE_API int16_t srslte_vec_max_abs_star_si(int16_t *x, uint32_t len); - -/* maximum between two vectors */ -SRSLTE_API void srslte_vec_max_fff(float *x, float *y, float *z, uint32_t len); /* quantify vector of floats or int16 and convert to uint8_t */ SRSLTE_API void srslte_vec_quant_fuc(float *in, uint8_t *out, float gain, float offset, float clip, uint32_t len); @@ -173,9 +143,6 @@ SRSLTE_API void srslte_vec_quant_suc(int16_t *in, uint8_t *out, float gain, int1 SRSLTE_API void srslte_vec_abs_cf(cf_t *x, float *abs, uint32_t len); SRSLTE_API void srslte_vec_abs_square_cf(cf_t *x, float *abs_square, uint32_t len); -/* argument of each vector element */ -SRSLTE_API void srslte_vec_arg_cf(cf_t *x, float *arg, uint32_t len); - /* Copy 256 bit aligned vector */ SRSLTE_API void srs_vec_cf_cpy(cf_t *src, cf_t *dst, int len); diff --git a/lib/src/phy/utils/vector.c b/lib/src/phy/utils/vector.c index f85dbca0a..3bb7fb08f 100644 --- a/lib/src/phy/utils/vector.c +++ b/lib/src/phy/utils/vector.c @@ -37,15 +37,6 @@ -int srslte_vec_acc_ii(int *x, uint32_t len) { - int i; - int z=0; - for (i=0;im) { - m=x[i]; - } - } - return m; -} - -int16_t srslte_vec_max_abs_star_si(int16_t *x, uint32_t len) { - uint32_t i; - int16_t m=-INT16_MIN; - for (i=0;im) { - m=abs(x[i]); - } - } - return m; -} - -void srslte_vec_max_fff(float *x, float *y, float *z, uint32_t len) { - uint32_t i; - for (i=0;i y[i]) { - z[i] = x[i]; - } else { - z[i] = y[i]; - } - } -} - - // CP autocorr uint32_t srslte_vec_max_abs_ci(cf_t *x, uint32_t len) { return srslte_vec_max_ci_simd(x, len); diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 109c99717..0294bd1af 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -162,75 +162,6 @@ void srslte_vec_prod_sss_simd(int16_t *x, int16_t *y, int16_t *z, int len) { } } - - - -#warning remove function if it is not used -/* -void srslte_vec_sc_div2_sss_sse(short *x, int k, short *z, uint32_t len) -{ -#ifdef LV_HAVE_SSE - unsigned int number = 0; - const unsigned int points = len / 8; - - const __m128i* xPtr = (const __m128i*) x; - __m128i* zPtr = (__m128i*) z; - - __m128i xVal, zVal; - for(;number < points; number++){ - - xVal = _mm_load_si128(xPtr); - - zVal = _mm_srai_epi16(xVal, k); - - _mm_store_si128(zPtr, zVal); - - xPtr ++; - zPtr ++; - } - - number = points * 8; - short divn = (1< Date: Tue, 3 Oct 2017 14:49:19 +0100 Subject: [PATCH 13/49] adding pmch tests and fixing pdsch_enb --- CMakeLists.txt | 2 +- lib/examples/pdsch_enodeb.c | 4 ++-- lib/src/phy/ch_estimation/chest_dl.c | 2 ++ lib/src/phy/ch_estimation/refsignal_dl.c | 3 ++- lib/src/phy/phch/pmch.c | 4 +++- lib/src/phy/phch/ra.c | 1 + lib/src/phy/phch/test/CMakeLists.txt | 19 ++++++++++++++++++- lib/src/phy/ue/ue_dl.c | 3 +-- 8 files changed, 30 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d72bb5fef..a63a77016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ configure_file( IMMEDIATE @ONLY) if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) + set(CMAKE_BUILD_TYPE Debug) message(STATUS "Build type not specified: defaulting to Release.") endif(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") diff --git a/lib/examples/pdsch_enodeb.c b/lib/examples/pdsch_enodeb.c index 844e769a7..98ad78507 100644 --- a/lib/examples/pdsch_enodeb.c +++ b/lib/examples/pdsch_enodeb.c @@ -320,9 +320,9 @@ void base_init() { exit(-1); } srslte_ofdm_set_non_mbsfn_region(&ifft_mbsfn, 2); - - srslte_ofdm_set_normalize(&ifft, true); srslte_ofdm_set_normalize(&ifft_mbsfn, true); + srslte_ofdm_set_normalize(&ifft, true); + if (srslte_pbch_init(&pbch)) { fprintf(stderr, "Error creating PBCH object\n"); diff --git a/lib/src/phy/ch_estimation/chest_dl.c b/lib/src/phy/ch_estimation/chest_dl.c index b5107f9ab..0f8ae8074 100644 --- a/lib/src/phy/ch_estimation/chest_dl.c +++ b/lib/src/phy/ch_estimation/chest_dl.c @@ -467,6 +467,8 @@ int srslte_chest_dl_estimate_port_mbsfn(srslte_chest_dl_t *q, cf_t *input, cf_t srslte_vec_prod_conj_ccc(q->pilot_recv_signal+(2*q->cell.nof_prb), q->mbsfn_refs[mbsfn_area_id]->pilots[port_id/2][sf_idx], q->pilot_estimates+(2*q->cell.nof_prb), SRSLTE_REFSIGNAL_NUM_SF_MBSFN(q->cell.nof_prb, port_id)-(2*q->cell.nof_prb)); + + chest_interpolate_noise_est(q, input, ce, sf_idx, port_id, rxant_id, SRSLTE_SF_MBSFN); return 0; diff --git a/lib/src/phy/ch_estimation/refsignal_dl.c b/lib/src/phy/ch_estimation/refsignal_dl.c index 4de00039e..9adbc6c18 100644 --- a/lib/src/phy/ch_estimation/refsignal_dl.c +++ b/lib/src/phy/ch_estimation/refsignal_dl.c @@ -228,7 +228,8 @@ int srslte_refsignal_cs_init(srslte_refsignal_t * q, uint32_t max_prb) if (q != NULL) { - ret = SRSLTE_ERROR; + ret = SRSLTE_ERROR; + bzero(q, sizeof(srslte_refsignal_t)); for (int p=0;p<2;p++) { for (int i=0;ipilots[p][i] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_REFSIGNAL_NUM_SF(max_prb, 2*p)); diff --git a/lib/src/phy/phch/pmch.c b/lib/src/phy/phch/pmch.c index 4ce869b1c..99e40d50f 100644 --- a/lib/src/phy/phch/pmch.c +++ b/lib/src/phy/phch/pmch.c @@ -394,8 +394,10 @@ int srslte_pmch_decode_multi(srslte_pmch_t *q, * thus we don't need tot set it in thde LLRs normalization */ + + srslte_demod_soft_demodulate_s(cfg->grant.mcs[0].mod, q->d, q->e, cfg->nbits[0].nof_re); - + /* descramble */ srslte_scrambling_s_offset(&q->seqs[area_id]->seq[cfg->sf_idx], q->e, 0, cfg->nbits[0].nof_bits); diff --git a/lib/src/phy/phch/ra.c b/lib/src/phy/phch/ra.c index 418aa1260..be10c304c 100644 --- a/lib/src/phy/phch/ra.c +++ b/lib/src/phy/phch/ra.c @@ -466,6 +466,7 @@ int srslte_dl_fill_ra_mcs(srslte_ra_mcs_t *mcs, uint32_t nprb) { tbs = 0; i_tbs = 0; } + if (tbs == -1) { tbs = srslte_ra_tbs_from_idx(i_tbs, nprb); if (tbs >= 0) { diff --git a/lib/src/phy/phch/test/CMakeLists.txt b/lib/src/phy/phch/test/CMakeLists.txt index 832f18d1f..6e6b8c024 100644 --- a/lib/src/phy/phch/test/CMakeLists.txt +++ b/lib/src/phy/phch/test/CMakeLists.txt @@ -159,6 +159,19 @@ add_test(pdsch_test_multiplex2cw_p1_50 pdsch_test -x multiplex -a 2 -t 0 -p 1 - add_test(pdsch_test_multiplex2cw_p1_75 pdsch_test -x multiplex -a 2 -t 0 -p 1 -n 75) add_test(pdsch_test_multiplex2cw_p1_100 pdsch_test -x multiplex -a 2 -t 0 -p 1 -n 100) +######################################################################## +# PMCH TEST +######################################################################## + + +add_executable(pmch_test pmch_test.c) +target_link_libraries(pmch_test srslte_phy) + +add_test(pmch_test_qpsk pmch_test -m 6 -n 50) +add_test(pmch_test_qam16 pmch_test -m 15 -n 100) +add_test(pmch_test_qam64 pmch_test -m 25 -n 100) + + ######################################################################## # FILE TEST ######################################################################## @@ -178,11 +191,15 @@ target_link_libraries(pdcch_file_test srslte_phy) add_executable(pdsch_pdcch_file_test pdsch_pdcch_file_test.c) target_link_libraries(pdsch_pdcch_file_test srslte_phy) +add_executable(pmch_file_test pmch_file_test.c) +target_link_libraries(pmch_file_test srslte_phy) + add_test(pbch_file_test pbch_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.1.92M.dat) add_test(pcfich_file_test pcfich_file_test -c 150 -n 50 -p 2 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.10M.dat) add_test(phich_file_test phich_file_test -c 150 -n 50 -p 2 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.10M.dat) add_test(pdcch_file_test pdcch_file_test -c 1 -f 3 -n 6 -p 1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.1.92M.amar.dat) -add_test(pdsch_pdcch_file_test pdsch_pdcch_file_test -c 1 -f 3 -n 6 -p 1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.1.92M.amar.dat) +add_test(pdsch_pdcch_file_test pdsch_pdcch_file_test -c 1 -f 3 -n 6 -p 1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.1.92M.amar.dat) +add_test(pmch_file_test pmch_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/pmch_100prbs_MCS2_SR0.bin) ######################################################################## # PUSCH TEST diff --git a/lib/src/phy/ue/ue_dl.c b/lib/src/phy/ue/ue_dl.c index 583368b53..c4e2d3f6c 100644 --- a/lib/src/phy/ue/ue_dl.c +++ b/lib/src/phy/ue/ue_dl.c @@ -631,8 +631,7 @@ int srslte_ue_dl_decode_mbsfn(srslte_ue_dl_t * q, fprintf(stderr, "Error calling srslte_pmch_decode()\n"); } } -printf("q->pmch_pkts_total %d \n", q->pmch_pkts_total); -printf("qq->pmch_pkt_errors %d \n", q->pmch_pkt_errors); + q->pmch_pkts_total++; if (ret == SRSLTE_SUCCESS) { From c1ef157afd2fa9fe34cb4968d2b0ea6ad8dcc94c Mon Sep 17 00:00:00 2001 From: yagoda Date: Tue, 3 Oct 2017 14:50:58 +0100 Subject: [PATCH 14/49] adding test files for pmch --- .../phy/phch/test/pmch_100prbs_MCS2_SR0.bin | Bin 0 -> 184320 bytes lib/src/phy/phch/test/pmch_file_test.c | 204 ++++++++ lib/src/phy/phch/test/pmch_test.c | 475 ++++++++++++++++++ 3 files changed, 679 insertions(+) create mode 100644 lib/src/phy/phch/test/pmch_100prbs_MCS2_SR0.bin create mode 100644 lib/src/phy/phch/test/pmch_file_test.c create mode 100644 lib/src/phy/phch/test/pmch_test.c diff --git a/lib/src/phy/phch/test/pmch_100prbs_MCS2_SR0.bin b/lib/src/phy/phch/test/pmch_100prbs_MCS2_SR0.bin new file mode 100644 index 0000000000000000000000000000000000000000..276c6ae01592534b437a2f2dc59a83485aca6a20 GIT binary patch literal 184320 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�iVQ%Hm9f_#jb_wP@`$DtNt*6bF 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 V|MLI;<^TW7|Noc&|G$&}|9>SCF8Kfe literal 0 HcmV?d00001 diff --git a/lib/src/phy/phch/test/pmch_file_test.c b/lib/src/phy/phch/test/pmch_file_test.c new file mode 100644 index 000000000..4ab99b560 --- /dev/null +++ b/lib/src/phy/phch/test/pmch_file_test.c @@ -0,0 +1,204 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2015 Software Radio Systems Limited + * + * \section LICENSE + * + * This file is part of the srsLTE library. + * + * srsLTE 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. + * + * srsLTE 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. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include +#include +#include +#include +#include + +#include "srslte/srslte.h" + +char *input_file_name = NULL; + +srslte_cell_t cell = { + 100, // nof_prb + 1, // nof_ports + 1, // cell_id + SRSLTE_CP_EXT, // cyclic prefix + SRSLTE_PHICH_R_1, // PHICH resources + SRSLTE_PHICH_NORM // PHICH length +}; + +int flen; + +uint32_t cfi = 2; +uint16_t rnti = SRSLTE_SIRNTI; + +int max_frames = 150; +uint32_t sf_idx = 1; + +uint8_t non_mbsfn_region = 2; +int mbsfn_area_id = 1; + +srslte_dci_format_t dci_format = SRSLTE_DCI_FORMAT1A; +srslte_filesource_t fsrc; +srslte_ue_dl_t ue_dl; +cf_t *input_buffer[SRSLTE_MAX_PORTS]; + +void usage(char *prog) { + printf("Usage: %s [rovfcenmps] -i input_file\n", prog); + printf("\t-o DCI format [Default %s]\n", srslte_dci_format_string(dci_format)); + printf("\t-c cell.id [Default %d]\n", cell.id); + printf("\t-s Start subframe_idx [Default %d]\n", sf_idx); + printf("\t-f cfi [Default %d]\n", cfi); + printf("\t-r rnti [Default 0x%x]\n",rnti); + printf("\t-p cell.nof_ports [Default %d]\n", cell.nof_ports); + printf("\t-n cell.nof_prb [Default %d]\n", cell.nof_prb); + printf("\t-M mbsfn_area_id [Default %d]\n", mbsfn_area_id); + printf("\t-e Set extended prefix [Default Normal]\n"); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "irovfcenmps")) != -1) { + switch(opt) { + case 'i': + input_file_name = argv[optind]; + break; + case 'c': + cell.id = atoi(argv[optind]); + break; + case 's': + sf_idx = atoi(argv[optind]); + break; + case 'r': + rnti = strtoul(argv[optind], NULL, 0); + break; + case 'f': + cfi = atoi(argv[optind]); + break; + case 'n': + cell.nof_prb = atoi(argv[optind]); + break; + case 'p': + cell.nof_ports = atoi(argv[optind]); + break; + case 'M': + mbsfn_area_id = atoi(argv[optind]); + break; + case 'o': + dci_format = srslte_dci_format_from_string(argv[optind]); + if (dci_format == SRSLTE_DCI_NOF_FORMATS) { + fprintf(stderr, "Error unsupported format %s\n", argv[optind]); + exit(-1); + } + break; + case 'v': + srslte_verbose++; + break; + case 'e': + cell.cp = SRSLTE_CP_EXT; + break; + default: + usage(argv[0]); + exit(-1); + } + } + if (!input_file_name) { + usage(argv[0]); + exit(-1); + } +} + +int base_init() { + + if (srslte_filesource_init(&fsrc, input_file_name, SRSLTE_COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", input_file_name); + exit(-1); + } + + flen = 2 * (SRSLTE_SLOT_LEN(srslte_symbol_sz(cell.nof_prb))); + + input_buffer[0] = malloc(flen * sizeof(cf_t)); + if (!input_buffer[0]) { + perror("malloc"); + exit(-1); + } + + if (srslte_ue_dl_init(&ue_dl, cell.nof_prb, 1)) { + fprintf(stderr, "Error initializing UE DL\n"); + return -1; + } + + + if (srslte_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Error initializing UE DL\n"); + return -1; + } + + srslte_ue_dl_set_rnti(&ue_dl, rnti); + + srslte_ue_dl_set_mbsfn_area_id(&ue_dl, mbsfn_area_id); + srslte_ue_dl_set_non_mbsfn_region(&ue_dl, non_mbsfn_region); + + + DEBUG("Memory init OK\n",0); + return 0; +} + +void base_free() { + srslte_filesource_free(&fsrc); + srslte_ue_dl_free(&ue_dl); + free(input_buffer[0]); +} + +int main(int argc, char **argv) { + int ret; + + if (argc < 3) { + usage(argv[0]); + exit(-1); + } + parse_args(argc,argv); + + if (base_init()) { + fprintf(stderr, "Error initializing memory\n"); + exit(-1); + } + + uint8_t *data[] = {malloc(100000)}; + + ret = -1; + + srslte_filesource_read(&fsrc, input_buffer[0], flen); + INFO("Reading %d samples sub-frame %d\n", flen, sf_idx); + ret = srslte_ue_dl_decode_mbsfn(&ue_dl, input_buffer, data[0], sf_idx); + if(ret > 0) { + printf("PMCH Decoded OK!\n"); + } else if (ret < 0) { + printf("Error decoding PMCH\n"); + } + + base_free(); + free(data[0]); + if (ret > 0) { + exit(0); + } else { + exit(-1); + } +} diff --git a/lib/src/phy/phch/test/pmch_test.c b/lib/src/phy/phch/test/pmch_test.c new file mode 100644 index 000000000..de7948852 --- /dev/null +++ b/lib/src/phy/phch/test/pmch_test.c @@ -0,0 +1,475 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2015 Software Radio Systems Limited + * + * \section LICENSE + * + * This file is part of the srsLTE library. + * + * srsLTE 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. + * + * srsLTE 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. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "srslte/srslte.h" + +// Enable to measure execution time +#define DO_OFDM + +#ifdef DO_OFDM +#define NOF_CE_SYMBOLS SRSLTE_SF_LEN_PRB(cell.nof_prb) +#else +#define NOF_CE_SYMBOLS SRSLTE_SF_LEN_RE(cell.nof_prb, cell.cp) +#endif + +srslte_cell_t cell = { + 100, // nof_prb + 1, // nof_ports + 1, // cell_id + SRSLTE_CP_EXT, // cyclic prefix + SRSLTE_PHICH_NORM, // PHICH length + SRSLTE_PHICH_R_1_6 // PHICH resources +}; + +char mimo_type_str [32] = "single"; +srslte_mimo_type_t mimo_type = SRSLTE_MIMO_TYPE_SINGLE_ANTENNA; +uint32_t cfi = 2; +uint32_t mcs_idx = 2; +uint32_t subframe = 1; +int rv_idx[SRSLTE_MAX_CODEWORDS] = {0, 1}; +uint16_t rnti = 1234; +uint32_t nof_rx_antennas = 1; +uint32_t pmi = 0; +char *input_file = NULL; +uint32_t mbsfn_area_id = 1; +uint32_t non_mbsfn_region = 2; +void usage(char *prog) { + printf("Usage: %s [fmMcsrtRFpnwav] \n", prog); + printf("\t-f read signal from file [Default generate it with pdsch_encode()]\n"); + printf("\t-m MCS [Default %d]\n", mcs_idx); + printf("\t-M mbsfn area id [Default %d]\n", mbsfn_area_id); + printf("\t-N non mbsfn region [Default %d]\n", non_mbsfn_region); + printf("\t-c cell id [Default %d]\n", cell.id); + printf("\t-s subframe [Default %d]\n", subframe); + printf("\t-r rv_idx [Default %d]\n", rv_idx[0]); + printf("\t-R rnti [Default %d]\n", rnti); + printf("\t-F cfi [Default %d]\n", cfi); + printf("\t-n cell.nof_prb [Default %d]\n", cell.nof_prb); + printf("\t-a nof_rx_antennas [Default %d]\n", nof_rx_antennas); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "fmMcsrtRFpnavx")) != -1) { + switch(opt) { + case 'f': + input_file = argv[optind]; + break; + case 'm': + mcs_idx = (uint32_t) atoi(argv[optind]); + break; + case 's': + subframe = atoi(argv[optind]); + break; + case 'r': + rv_idx[0] = (uint32_t) atoi(argv[optind]); + break; + case 'R': + rnti = atoi(argv[optind]); + break; + case 'F': + cfi = atoi(argv[optind]); + break; + case 'x': + strncpy(mimo_type_str, argv[optind], 32); + break; + case 'p': + pmi = (uint32_t) atoi(argv[optind]); + break; + case 'n': + cell.nof_prb = atoi(argv[optind]); + break; + case 'c': + cell.id = atoi(argv[optind]); + break; + case 'a': + nof_rx_antennas = (uint32_t) atoi(argv[optind]); + break; + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +static uint8_t *data_tx[SRSLTE_MAX_CODEWORDS] = {NULL}; +static uint8_t *data_rx[SRSLTE_MAX_CODEWORDS] = {NULL}; +cf_t *ce[SRSLTE_MAX_PORTS][SRSLTE_MAX_PORTS]; +srslte_softbuffer_rx_t *softbuffers_rx[SRSLTE_MAX_CODEWORDS]; +srslte_ra_dl_grant_t grant; +#ifdef DO_OFDM +cf_t *tx_sf_symbols[SRSLTE_MAX_PORTS]; +cf_t *rx_sf_symbols[SRSLTE_MAX_PORTS]; +#endif /* DO_OFDM */ +cf_t *tx_slot_symbols[SRSLTE_MAX_PORTS]; +cf_t *rx_slot_symbols[SRSLTE_MAX_PORTS]; +srslte_pmch_t pmch_tx, pmch_rx; +srslte_pdsch_cfg_t pmch_cfg; +srslte_ofdm_t ifft_mbsfn, fft_mbsfn; + +int main(int argc, char **argv) { + uint32_t i, j, k; + int ret = -1; + struct timeval t[3]; + srslte_softbuffer_tx_t *softbuffers_tx[SRSLTE_MAX_CODEWORDS]; + int M=1; + + parse_args(argc,argv); + + /* Initialise to zeros */ + bzero(&pmch_tx, sizeof(srslte_pdsch_t)); + bzero(&pmch_rx, sizeof(srslte_pdsch_t)); + bzero(&pmch_cfg, sizeof(srslte_pdsch_cfg_t)); + bzero(ce, sizeof(cf_t*)*SRSLTE_MAX_PORTS); + bzero(tx_slot_symbols, sizeof(cf_t*)*SRSLTE_MAX_PORTS); + bzero(rx_slot_symbols, sizeof(cf_t*)*SRSLTE_MAX_PORTS); + + + cell.nof_ports = 1; + + srslte_ra_dl_dci_t dci; + bzero(&dci, sizeof(srslte_ra_dl_dci_t)); + dci.type0_alloc.rbg_bitmask = 0xffffffff; + + + /* If transport block 0 is enabled */ + grant.tb_en[0] = true; + grant.tb_en[1] = false; + grant.nof_tb = 1; + grant.mcs[0].idx = mcs_idx; + + grant.nof_prb = cell.nof_prb; + grant.sf_type = SRSLTE_SF_MBSFN; + + srslte_dl_fill_ra_mcs(&grant.mcs[0], cell.nof_prb); + grant.Qm[0] = srslte_mod_bits_x_symbol(grant.mcs[0].mod); + + for(int i = 0; i < 2; i++){ + for(int j = 0; j < grant.nof_prb; j++){ + grant.prb_idx[i][j] = true; + } + } + + + +#ifdef DO_OFDM + + if (srslte_ofdm_tx_init_mbsfn(&ifft_mbsfn, SRSLTE_CP_EXT, cell.nof_prb)) { + fprintf(stderr, "Error creating iFFT object\n"); + exit(-1); + } + if (srslte_ofdm_rx_init_mbsfn(&fft_mbsfn, SRSLTE_CP_EXT, cell.nof_prb)) { + fprintf(stderr, "Error creating iFFT object\n"); + exit(-1); + } + + + srslte_ofdm_set_non_mbsfn_region(&ifft_mbsfn, non_mbsfn_region); + srslte_ofdm_set_non_mbsfn_region(&fft_mbsfn, non_mbsfn_region); + srslte_ofdm_set_normalize(&ifft_mbsfn, true); + srslte_ofdm_set_normalize(&fft_mbsfn, true); + + + for (i = 0; i < cell.nof_ports; i++) { + tx_sf_symbols[i] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_PRB(cell.nof_prb)); + } + + for (i = 0; i < nof_rx_antennas; i++) { + rx_sf_symbols[i] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_PRB(cell.nof_prb)); + } +#endif /* DO_OFDM */ + + /* Configure PDSCH */ + + if (srslte_pmch_cfg(&pmch_cfg, cell, &grant, cfi, subframe)) { + fprintf(stderr, "Error configuring PMCH\n"); + exit(-1); + } + + + /* init memory */ + for (i=0;i Date: Wed, 4 Oct 2017 10:08:01 +0200 Subject: [PATCH 15/49] Missing call to c_str in printf --- srsenb/src/upper/gtpu.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srsenb/src/upper/gtpu.cc b/srsenb/src/upper/gtpu.cc index e01be1ad0..d8f53d662 100644 --- a/srsenb/src/upper/gtpu.cc +++ b/srsenb/src/upper/gtpu.cc @@ -87,7 +87,7 @@ bool gtpu::init(std::string gtp_bind_addr_, std::string mme_addr_, srsenb::pdcp_ bindaddr.sin_port = htons(GTPU_PORT); if (bind(src_fd, (struct sockaddr *)&bindaddr, sizeof(struct sockaddr_in))) { - gtpu_log->error("Failed to bind on address %s, port %d\n", gtp_bind_addr, GTPU_PORT); + gtpu_log->error("Failed to bind on address %s, port %d\n", gtp_bind_addr.c_str(), GTPU_PORT); return false; } From a043b8a1678944b1306b8a60dcb9945069eac67d Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Wed, 4 Oct 2017 10:15:10 +0200 Subject: [PATCH 16/49] buffer overflow in pmch_test bzero --- lib/src/phy/phch/test/pmch_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/phy/phch/test/pmch_test.c b/lib/src/phy/phch/test/pmch_test.c index de7948852..88e9fd296 100644 --- a/lib/src/phy/phch/test/pmch_test.c +++ b/lib/src/phy/phch/test/pmch_test.c @@ -151,8 +151,8 @@ int main(int argc, char **argv) { parse_args(argc,argv); /* Initialise to zeros */ - bzero(&pmch_tx, sizeof(srslte_pdsch_t)); - bzero(&pmch_rx, sizeof(srslte_pdsch_t)); + bzero(&pmch_tx, sizeof(srslte_pmch_t)); + bzero(&pmch_rx, sizeof(srslte_pmch_t)); bzero(&pmch_cfg, sizeof(srslte_pdsch_cfg_t)); bzero(ce, sizeof(cf_t*)*SRSLTE_MAX_PORTS); bzero(tx_slot_symbols, sizeof(cf_t*)*SRSLTE_MAX_PORTS); From 983bd0060c863972f68ce1486de09d446d8e108c Mon Sep 17 00:00:00 2001 From: yagoda Date: Wed, 4 Oct 2017 09:53:35 +0100 Subject: [PATCH 17/49] fixing incorrect type in bzero in pmch test --- CMakeLists.txt | 2 +- lib/src/phy/phch/test/pmch_test.c | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a63a77016..d72bb5fef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ configure_file( IMMEDIATE @ONLY) if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) + set(CMAKE_BUILD_TYPE Release) message(STATUS "Build type not specified: defaulting to Release.") endif(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") diff --git a/lib/src/phy/phch/test/pmch_test.c b/lib/src/phy/phch/test/pmch_test.c index de7948852..aea1b50c0 100644 --- a/lib/src/phy/phch/test/pmch_test.c +++ b/lib/src/phy/phch/test/pmch_test.c @@ -149,16 +149,14 @@ int main(int argc, char **argv) { int M=1; parse_args(argc,argv); - /* Initialise to zeros */ - bzero(&pmch_tx, sizeof(srslte_pdsch_t)); - bzero(&pmch_rx, sizeof(srslte_pdsch_t)); + bzero(&pmch_tx, sizeof(srslte_pmch_t)); + bzero(&pmch_rx, sizeof(srslte_pmch_t)); bzero(&pmch_cfg, sizeof(srslte_pdsch_cfg_t)); bzero(ce, sizeof(cf_t*)*SRSLTE_MAX_PORTS); bzero(tx_slot_symbols, sizeof(cf_t*)*SRSLTE_MAX_PORTS); bzero(rx_slot_symbols, sizeof(cf_t*)*SRSLTE_MAX_PORTS); - cell.nof_ports = 1; srslte_ra_dl_dci_t dci; @@ -177,7 +175,6 @@ int main(int argc, char **argv) { srslte_dl_fill_ra_mcs(&grant.mcs[0], cell.nof_prb); grant.Qm[0] = srslte_mod_bits_x_symbol(grant.mcs[0].mod); - for(int i = 0; i < 2; i++){ for(int j = 0; j < grant.nof_prb; j++){ grant.prb_idx[i][j] = true; @@ -197,7 +194,6 @@ int main(int argc, char **argv) { exit(-1); } - srslte_ofdm_set_non_mbsfn_region(&ifft_mbsfn, non_mbsfn_region); srslte_ofdm_set_non_mbsfn_region(&fft_mbsfn, non_mbsfn_region); srslte_ofdm_set_normalize(&ifft_mbsfn, true); @@ -220,7 +216,6 @@ int main(int argc, char **argv) { exit(-1); } - /* init memory */ for (i=0;i Date: Wed, 4 Oct 2017 11:50:15 +0200 Subject: [PATCH 18/49] Fixed incorrect scheduling with PUCCH --- srsenb/hdr/mac/mac.h | 5 +---- srsenb/hdr/mac/scheduler.h | 2 +- srsenb/hdr/mac/scheduler_metric.h | 2 +- srsenb/hdr/mac/scheduler_ue.h | 3 +-- srsenb/src/mac/mac.cc | 24 +++--------------------- srsenb/src/mac/scheduler.cc | 22 +++++++++------------- srsenb/src/mac/scheduler_metric.cc | 8 +++----- srsenb/src/mac/scheduler_ue.cc | 25 +++++++++---------------- srsenb/src/upper/rrc.cc | 10 ++++++---- 9 files changed, 34 insertions(+), 67 deletions(-) diff --git a/srsenb/hdr/mac/mac.h b/srsenb/hdr/mac/mac.h index 4620c9629..d71245e89 100644 --- a/srsenb/hdr/mac/mac.h +++ b/srsenb/hdr/mac/mac.h @@ -113,10 +113,7 @@ public: private: - void log_step_ul(uint32_t tti); - void log_step_dl(uint32_t tti); - - static const int MAX_LOCATIONS = 20; + static const int MAX_LOCATIONS = 20; static const uint32_t cfi = 3; srslte_dci_location_t locations[MAX_LOCATIONS]; diff --git a/srsenb/hdr/mac/scheduler.h b/srsenb/hdr/mac/scheduler.h index c70ee4247..231239285 100644 --- a/srsenb/hdr/mac/scheduler.h +++ b/srsenb/hdr/mac/scheduler.h @@ -68,7 +68,7 @@ public: /* Virtual methods for user metric calculation */ virtual void new_tti(std::map &ue_db, uint32_t nof_rb, uint32_t tti) = 0; virtual ul_harq_proc* get_user_allocation(sched_ue *user) = 0; - virtual void update_allocation(ul_harq_proc::ul_alloc_t alloc) = 0; + virtual void update_allocation(ul_harq_proc::ul_alloc_t alloc) = 0; }; diff --git a/srsenb/hdr/mac/scheduler_metric.h b/srsenb/hdr/mac/scheduler_metric.h index b9d515ade..eda0b31ed 100644 --- a/srsenb/hdr/mac/scheduler_metric.h +++ b/srsenb/hdr/mac/scheduler_metric.h @@ -65,7 +65,7 @@ class ul_metric_rr : public sched::metric_ul public: void new_tti(std::map &ue_db, uint32_t nof_rb, uint32_t tti); ul_harq_proc* get_user_allocation(sched_ue *user); - void update_allocation(ul_harq_proc::ul_alloc_t alloc); + void update_allocation(ul_harq_proc::ul_alloc_t alloc); private: const static int MAX_PRB = 100; diff --git a/srsenb/hdr/mac/scheduler_ue.h b/srsenb/hdr/mac/scheduler_ue.h index b59461140..3653ff65c 100644 --- a/srsenb/hdr/mac/scheduler_ue.h +++ b/srsenb/hdr/mac/scheduler_ue.h @@ -42,7 +42,6 @@ public: // used by sched_metric uint32_t ue_idx; - bool has_pusch; bool has_pucch; typedef struct { @@ -116,7 +115,7 @@ public: bool needs_cqi(uint32_t tti, bool will_send = false); uint32_t get_max_retx(); - bool get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32_t *L); + bool get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]); bool pucch_sr_collision(uint32_t current_tti, uint32_t n_cce); uint32_t get_pending_ul_old_data(); diff --git a/srsenb/src/mac/mac.cc b/srsenb/src/mac/mac.cc index 7cb30cc65..6c8458540 100644 --- a/srsenb/src/mac/mac.cc +++ b/srsenb/src/mac/mac.cc @@ -403,7 +403,7 @@ int mac::rach_detected(uint32_t tti, uint32_t preamble_idx, uint32_t time_adv) int mac::get_dl_sched(uint32_t tti, dl_sched_t *dl_sched_res) { - log_step_dl(tti); + log_h->step(tti); if (!started) { return 0; @@ -544,8 +544,8 @@ uint8_t* mac::assemble_si(uint32_t index) int mac::get_ul_sched(uint32_t tti, ul_sched_t *ul_sched_res) { - - log_step_ul(tti); + + log_h->step(tti); if (!started) { return 0; @@ -602,24 +602,6 @@ int mac::get_ul_sched(uint32_t tti, ul_sched_t *ul_sched_res) return SRSLTE_SUCCESS; } -void mac::log_step_ul(uint32_t tti) -{ - int tti_ul = tti-8; - if (tti_ul < 0) { - tti_ul += 10240; - } - log_h->step(tti_ul); -} - -void mac::log_step_dl(uint32_t tti) -{ - int tti_dl = tti-4; - if (tti_dl < 0) { - tti_dl += 10240; - } - log_h->step(tti_dl); -} - void mac::tti_clock() { timers_thread.tti_clock(); diff --git a/srsenb/src/mac/scheduler.cc b/srsenb/src/mac/scheduler.cc index 79cf3f476..539b7725f 100644 --- a/srsenb/src/mac/scheduler.cc +++ b/srsenb/src/mac/scheduler.cc @@ -700,7 +700,6 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched sched_ue *user = (sched_ue*) &iter->second; uint16_t rnti = (uint16_t) iter->first; - user->has_pusch = false; user->has_pucch = false; ul_harq_proc *h = user->get_ul_harq(current_tti); @@ -726,15 +725,12 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched sched_ue *user = (sched_ue*) &iter->second; uint16_t rnti = (uint16_t) iter->first; uint32_t prb_idx[2] = {0, 0}; - uint32_t L = 0; - if (user->get_pucch_sched(current_tti, prb_idx, &L)) { + if (user->get_pucch_sched(current_tti, prb_idx)) { user->has_pucch = true; - // allocate PUCCH if no PUSCH for user - if (!user->has_pusch) { - for (int i=0;i<2;i++) { - ul_harq_proc::ul_alloc_t pucch = {prb_idx[i], L}; - ul_metric->update_allocation(pucch); - } + // allocate PUCCH + for (int i=0;i<2;i++) { + ul_harq_proc::ul_alloc_t pucch = {prb_idx[i], 1}; + ul_metric->update_allocation(pucch); } } } @@ -807,22 +803,22 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched user->unset_sr(); } - log_h->info("SCHED: %s %s rnti=0x%x, pid=%d, dci=%d,%d, grant=%d,%d, n_rtx=%d, tbs=%d, bsr=%d (%d-%d)\n", + log_h->info("SCHED: %s %s rnti=0x%x, pid=%d, dci=%d,%d, grant=(%d,%d), n_rtx=%d, tbs=%d, bsr=%d (%d-%d)\n", is_rar?"RAR":"UL", is_newtx?"tx":"retx", rnti, h->get_id(), sched_result->pusch[nof_dci_elems].dci_location.L, sched_result->pusch[nof_dci_elems].dci_location.ncce, - alloc.RB_start, alloc.L, h->nof_retx(), sched_result->pusch[nof_dci_elems].tbs, + alloc.RB_start, alloc.RB_start+alloc.L, h->nof_retx(), sched_result->pusch[nof_dci_elems].tbs, user->get_pending_ul_new_data(current_tti),pending_data_before, user->get_pending_ul_old_data()); nof_dci_elems++; } else { - log_h->warning("SCHED: Error %s %s rnti=0x%x, pid=%d, dci=%d,%d, grant=%d,%d, tbs=%d, bsr=%d\n", + log_h->warning("SCHED: Error %s %s rnti=0x%x, pid=%d, dci=%d,%d, grant=(%d,%d), tbs=%d, bsr=%d\n", is_rar?"RAR":"UL", is_newtx?"tx":"retx", rnti, h->get_id(), sched_result->pusch[nof_dci_elems].dci_location.L, sched_result->pusch[nof_dci_elems].dci_location.ncce, - alloc.RB_start, alloc.L, sched_result->pusch[nof_dci_elems].tbs, + alloc.RB_start, alloc.RB_start+alloc.L, sched_result->pusch[nof_dci_elems].tbs, user->get_pending_ul_new_data(current_tti)); } } diff --git a/srsenb/src/mac/scheduler_metric.cc b/srsenb/src/mac/scheduler_metric.cc index 309eed45a..708ab2dd8 100644 --- a/srsenb/src/mac/scheduler_metric.cc +++ b/srsenb/src/mac/scheduler_metric.cc @@ -25,8 +25,7 @@ */ #include - -#include "srslte/srslte.h" +#include "mac/scheduler_harq.h" #include "mac/scheduler_metric.h" #define Error(fmt, ...) log_h->error_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) @@ -215,8 +214,7 @@ void ul_metric_rr::new_tti(std::map &ue_db, uint32_t nof_rb_, sched_ue *user = (sched_ue*) &iter->second; if (user->get_pending_ul_new_data(current_tti) || !user->get_ul_harq(current_tti)->is_empty()) { user->ue_idx = nof_users_with_data; - user->has_pusch = true; - nof_users_with_data++; + nof_users_with_data++; } } @@ -275,7 +273,7 @@ void ul_metric_rr::update_allocation(ul_harq_proc::ul_alloc_t alloc) return; } for (uint32_t n=alloc.RB_start;n #include #include +#include #include "srslte/srslte.h" #include "srslte/common/pdu.h" @@ -232,7 +233,7 @@ bool sched_ue::pucch_sr_collision(uint32_t current_tti, uint32_t n_cce) } } -bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32_t *L) +bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]) { if (!phy_config_dedicated_enabled) { return false; @@ -241,7 +242,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32 pucch_sched.sps_enabled = false; pucch_sched.n_pucch_sr = cfg.sr_N_pucch; pucch_sched.n_pucch_2 = cfg.n_pucch_cqi; - pucch_sched.N_pucch_1 = cfg.pucch_cfg.n1_pucch_an; + pucch_sched.N_pucch_1 = cfg.pucch_cfg.n1_pucch_an; bool has_sr = cfg.sr_enabled && srslte_ue_ul_sr_send_tti(cfg.sr_I, current_tti); @@ -251,13 +252,11 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32 uint32_t n_pucch = srslte_pucch_get_npucch(dl_harq[i].get_n_cce(), SRSLTE_PUCCH_FORMAT_1A, has_sr, &pucch_sched); if (prb_idx) { for (int i=0;i<2;i++) { - prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_1A, n_pucch, cell.nof_prb, cell.cp, i); - } - } - if (L) { - *L = 1; + prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_1A, n_pucch, cell.nof_prb, cell.cp, i); + } } - Debug("SCHED: Reserved Format1A PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d\n", rnti, prb_idx[0], prb_idx[1], n_pucch); + Info("SCHED: Reserved Format1A PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, ncce=%d, has_sr=%d, n_pucch_1=%d\n", + rnti, prb_idx[0], prb_idx[1], n_pucch, dl_harq[i].get_n_cce(), has_sr, pucch_sched.N_pucch_1); return true; } } @@ -268,10 +267,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32 prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_1, cfg.sr_N_pucch, cell.nof_prb, cell.cp, i); } } - if (L) { - *L = 1; - } - Debug("SCHED: Reserved Format1 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.sr_N_pucch); + Info("SCHED: Reserved Format1 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.sr_N_pucch); return true; } // Finally check Format2 (periodic CQI) @@ -281,10 +277,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2], uint32 prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_2, cfg.cqi_pucch, cell.nof_prb, cell.cp, i); } } - if(L) { - *L = 2; - } - Debug("SCHED: Reserved Format2 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, pmi_idx=%d\n", + Info("SCHED: Reserved Format2 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, pmi_idx=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.cqi_pucch, cfg.cqi_idx); return true; } diff --git a/srsenb/src/upper/rrc.cc b/srsenb/src/upper/rrc.cc index b1280cf55..4a432b394 100644 --- a/srsenb/src/upper/rrc.cc +++ b/srsenb/src/upper/rrc.cc @@ -24,9 +24,10 @@ * */ -#include -#include -#include +#include "srslte/interfaces/sched_interface.h" +#include "srslte/asn1/liblte_rrc.h" +#include "upper/rrc.h" +#include "srslte/srslte.h" #include "srslte/asn1/liblte_mme.h" #include "upper/rrc.h" @@ -1177,7 +1178,8 @@ void rrc::ue::send_connection_setup(bool is_setup) sched_cfg.pucch_cfg.delta_pucch_shift = liblte_rrc_delta_pucch_shift_num[parent->sib2.rr_config_common_sib.pucch_cnfg.delta_pucch_shift%LIBLTE_RRC_DELTA_PUCCH_SHIFT_N_ITEMS]; sched_cfg.pucch_cfg.N_cs = parent->sib2.rr_config_common_sib.pucch_cnfg.n_cs_an; sched_cfg.pucch_cfg.n_rb_2 = parent->sib2.rr_config_common_sib.pucch_cnfg.n_rb_cqi; - + sched_cfg.pucch_cfg.n1_pucch_an = parent->sib2.rr_config_common_sib.pucch_cnfg.n1_pucch_an; + // Configure MAC parent->mac->ue_cfg(rnti, &sched_cfg); From b155ba4c2865e1f8d0aa4dceef41e352e7c0e2fc Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Wed, 4 Oct 2017 12:00:31 +0200 Subject: [PATCH 19/49] Fixed segfault in pdsch_ue --help command --- lib/examples/pdsch_ue.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/examples/pdsch_ue.c b/lib/examples/pdsch_ue.c index 38113dbcf..97fd71f50 100644 --- a/lib/examples/pdsch_ue.c +++ b/lib/examples/pdsch_ue.c @@ -171,8 +171,8 @@ void usage(prog_args_t *args, char *prog) { printf("\t-S remote UDP address to send input signal [Default %s]\n", args->net_address_signal); printf("\t-u remote TCP port to send data (-1 does nothing with it) [Default %d]\n", args->net_port); printf("\t-U remote TCP address to send data [Default %s]\n", args->net_address); - printf("\t-M MBSFN area id [Default %s]\n", args->mbsfn_area_id); - printf("\t-N Non-MBSFN region [Default %s]\n", args->non_mbsfn_region); + printf("\t-M MBSFN area id [Default %d]\n", args->mbsfn_area_id); + printf("\t-N Non-MBSFN region [Default %d]\n", args->non_mbsfn_region); printf("\t-v [set srslte_verbose to debug, default none]\n"); } From 189dcfa1d23f0bac69b3ff301ecc6be63d7e2516 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Wed, 4 Oct 2017 13:35:18 +0200 Subject: [PATCH 20/49] Updated TX/RX calibrated values for x300 --- lib/src/radio/radio.cc | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/src/radio/radio.cc b/lib/src/radio/radio.cc index c0e828f71..b393a3ff4 100644 --- a/lib/src/radio/radio.cc +++ b/lib/src/radio/radio.cc @@ -357,25 +357,9 @@ void radio::set_tx_srate(double srate) nsamples = cur_tx_srate*(uhd_default_tx_adv_samples * (1/cur_tx_srate) + uhd_default_tx_adv_offset_sec); } } else if (!strcmp(srslte_rf_name(&rf_device), "uhd_x300")) { - - double srate_khz = round(cur_tx_srate/1e3); - if (srate_khz == 1.92e3) { - nsamples = 50; - } else if (srate_khz == 3.84e3) { - nsamples = 65; - } else if (srate_khz == 5.76e3) { - nsamples = 75; - } else if (srate_khz == 11.52e3) { - nsamples = 89; - } else if (srate_khz == 15.36e3) { - nsamples = 86; - } else if (srate_khz == 23.04e3) { - nsamples = 110; - } else { - /* Interpolate from known values */ - printf("\nWarning TX/RX time offset for sampling rate %.0f KHz not calibrated. Using interpolated value\n\n", cur_tx_srate); - nsamples = cur_tx_srate*(uhd_default_tx_adv_samples * (1/cur_tx_srate) + uhd_default_tx_adv_offset_sec); - } + + // In X300 TX/RX offset is independent of sampling rate + nsamples = 45; } else if (!strcmp(srslte_rf_name(&rf_device), "bladerf")) { double srate_khz = round(cur_tx_srate/1e3); From 582c87e86d1c487a4967b07d7484dcfabcf83425 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Wed, 4 Oct 2017 17:45:17 +0200 Subject: [PATCH 21/49] Removed PUCCH reserved logs --- srsenb/src/mac/scheduler_ue.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srsenb/src/mac/scheduler_ue.cc b/srsenb/src/mac/scheduler_ue.cc index f0627bb4b..3eab6b33d 100644 --- a/srsenb/src/mac/scheduler_ue.cc +++ b/srsenb/src/mac/scheduler_ue.cc @@ -255,7 +255,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]) prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_1A, n_pucch, cell.nof_prb, cell.cp, i); } } - Info("SCHED: Reserved Format1A PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, ncce=%d, has_sr=%d, n_pucch_1=%d\n", + Debug("SCHED: Reserved Format1A PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, ncce=%d, has_sr=%d, n_pucch_1=%d\n", rnti, prb_idx[0], prb_idx[1], n_pucch, dl_harq[i].get_n_cce(), has_sr, pucch_sched.N_pucch_1); return true; } @@ -267,7 +267,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]) prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_1, cfg.sr_N_pucch, cell.nof_prb, cell.cp, i); } } - Info("SCHED: Reserved Format1 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.sr_N_pucch); + Debug("SCHED: Reserved Format1 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.sr_N_pucch); return true; } // Finally check Format2 (periodic CQI) @@ -277,7 +277,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]) prb_idx[i] = srslte_pucch_n_prb(&cfg.pucch_cfg, SRSLTE_PUCCH_FORMAT_2, cfg.cqi_pucch, cell.nof_prb, cell.cp, i); } } - Info("SCHED: Reserved Format2 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, pmi_idx=%d\n", + Debug("SCHED: Reserved Format2 PUCCH for rnti=0x%x, n_prb=%d,%d, n_pucch=%d, pmi_idx=%d\n", rnti, prb_idx[0], prb_idx[1], cfg.cqi_pucch, cfg.cqi_idx); return true; } From b353ed03ddc9ae2df9fa217f8192a24da6866562 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Thu, 5 Oct 2017 12:26:48 +0200 Subject: [PATCH 22/49] cleaned stdout metric --- srsenb/src/metrics_stdout.cc | 8 ++++---- srsue/src/metrics_stdout.cc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/srsenb/src/metrics_stdout.cc b/srsenb/src/metrics_stdout.cc index 6c294b96d..ec55b0dcf 100644 --- a/srsenb/src/metrics_stdout.cc +++ b/srsenb/src/metrics_stdout.cc @@ -130,9 +130,9 @@ void metrics_stdout::print_metrics() cout << float_to_string(0, 2); } if (metrics.mac[i].tx_pkts > 0 && metrics.mac[i].tx_errors) { - cout << float_to_string((float) 100*metrics.mac[i].tx_errors/metrics.mac[i].tx_pkts, 2) << "%"; + cout << float_to_string((float) 100*metrics.mac[i].tx_errors/metrics.mac[i].tx_pkts, 1) << "%"; } else { - cout << float_to_string(0, 2) << "%"; + cout << float_to_string(0, 1) << "%"; } cout << float_to_string(metrics.phy[i].ul.sinr, 2); cout << float_to_string(metrics.mac[i].phr, 2); @@ -143,9 +143,9 @@ void metrics_stdout::print_metrics() cout << float_to_string(0, 2); } if (metrics.mac[i].rx_pkts > 0 && metrics.mac[i].rx_errors > 0) { - cout << float_to_string((float) 100*metrics.mac[i].rx_errors/metrics.mac[i].rx_pkts, 2) << "%"; + cout << float_to_string((float) 100*metrics.mac[i].rx_errors/metrics.mac[i].rx_pkts, 1) << "%"; } else { - cout << float_to_string(0, 2) << "%"; + cout << float_to_string(0, 1) << "%"; } cout << float_to_eng_string(metrics.mac[i].ul_buffer, 2); cout << endl; diff --git a/srsue/src/metrics_stdout.cc b/srsue/src/metrics_stdout.cc index 6828c912f..048532c8f 100644 --- a/srsue/src/metrics_stdout.cc +++ b/srsue/src/metrics_stdout.cc @@ -92,7 +92,7 @@ void metrics_stdout::set_metrics(ue_metrics_t &metrics, float metrics_report_per if (metrics.mac.rx_pkts > 0) { cout << float_to_string((float) 100*metrics.mac.rx_errors/metrics.mac.rx_pkts, 1) << "%"; } else { - cout << float_to_string(0, 2) << "%"; + cout << float_to_string(0, 1) << "%"; } cout << float_to_string(metrics.phy.ul.mcs, 2); cout << float_to_eng_string((float) metrics.mac.ul_buffer, 2); @@ -100,7 +100,7 @@ void metrics_stdout::set_metrics(ue_metrics_t &metrics, float metrics_report_per if (metrics.mac.tx_pkts > 0) { cout << float_to_string((float) 100*metrics.mac.tx_errors/metrics.mac.tx_pkts, 1) << "%"; } else { - cout << float_to_string(0, 2) << "%"; + cout << float_to_string(0, 1) << "%"; } cout << endl; From 81b143715b0b0bb5e7f3f22b31f0f0c5bd43e411 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 4 Oct 2017 16:17:44 +0200 Subject: [PATCH 23/49] fix formating --- lib/src/phy/rf/rf_soapy_imp.c | 95 +++++++++++++++++------------------ 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index 31649af69..084308116 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -88,12 +88,12 @@ void rf_soapy_register_error_handler(void *notused, srslte_rf_error_handler_t ne } - char* rf_soapy_devname(void* h) { return "soapy"; } + bool rf_soapy_rx_wait_lo_locked(void *h) { printf("TODO: implement rf_soapy_rx_wait_lo_locked()\n"); @@ -155,7 +155,6 @@ int rf_soapy_stop_tx_stream(void *h) if(SoapySDRDevice_deactivateStream(handler->device, handler->txStream, 0, 0) != 0) return SRSLTE_ERROR; - handler->tx_stream_active = false; return SRSLTE_SUCCESS; } @@ -199,9 +198,8 @@ int rf_soapy_open_multi(char *args, void **h, uint32_t nof_rx_antennas) } for (size_t i = 0; i < length; i++) { - printf("Soapy Has Found device #%d: ", (int)i); - for (size_t j = 0; j < soapy_args[i].size; j++) - { + printf("Soapy has Found device #%d: ", (int)i); + for (size_t j = 0; j < soapy_args[i].size; j++) { printf("%s=%s, ", soapy_args[i].keys[j], soapy_args[i].vals[j]); } printf("\n"); @@ -221,7 +219,6 @@ int rf_soapy_open_multi(char *args, void **h, uint32_t nof_rx_antennas) handler->tx_stream_active = false; handler->rx_stream_active = false; - if(SoapySDRDevice_getNumChannels(handler->device,SOAPY_SDR_RX) > 0){ printf("setting up RX stream\n"); if(SoapySDRDevice_setupStream(handler->device, &(handler->rxStream), SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL) != 0) { @@ -364,7 +361,8 @@ double rf_soapy_set_tx_freq(void *h, double freq) } -void rf_soapy_get_time(void *h, time_t *secs, double *frac_secs) { +void rf_soapy_get_time(void *h, time_t *secs, double *frac_secs) +{ } @@ -430,49 +428,46 @@ int rf_soapy_recv_with_time(void *h, int rf_soapy_send_timed(void *h, - void *data, - int nsamples, - time_t secs, - double frac_secs, - bool has_time_spec, - bool blocking, - bool is_start_of_burst, - bool is_end_of_burst) -{ - - int flags; - long long timeNs; - int trials = 0; - int ret = 0; - rf_soapy_handler_t *handler = (rf_soapy_handler_t*) h; - timeNs = secs * 1000000000; - timeNs = timeNs + (frac_secs * 1000000000); - int n = 0; - - if(!handler->tx_stream_active){ - rf_soapy_start_tx_stream(h); + void *data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst) +{ + int flags; + long long timeNs; + int trials = 0; + int ret = 0; + rf_soapy_handler_t *handler = (rf_soapy_handler_t *) h; + timeNs = secs * 1000000000; + timeNs = timeNs + (frac_secs * 1000000000); + int n = 0; + + if (!handler->tx_stream_active) { + rf_soapy_start_tx_stream(h); + } + + cf_t *data_c = (cf_t *) data; + do { + size_t tx_samples = nsamples; + if (tx_samples > nsamples - n) { + tx_samples = nsamples - n; } - - - cf_t *data_c = (cf_t*) data; - do{ - size_t tx_samples = nsamples; - if (tx_samples > nsamples - n) { - tx_samples = nsamples - n; - } - void *buff = (void*) &data_c[n]; - const void *buffs_ptr[1] = {buff}; - ret = SoapySDRDevice_writeStream(handler->device, handler->txStream, buffs_ptr, tx_samples, &flags, timeNs, 10000); - if(ret < 0) - return SRSLTE_ERROR; - - n += ret; - trials++; - }while (n < nsamples && trials < 100); - - if(ret != nsamples) - return SRSLTE_ERROR; - - return ret; + void *buff = (void *) &data_c[n]; + const void *buffs_ptr[1] = {buff}; + ret = SoapySDRDevice_writeStream(handler->device, handler->txStream, buffs_ptr, tx_samples, &flags, timeNs, 10000); + if (ret < 0) + return SRSLTE_ERROR; + + n += ret; + trials++; + } while (n < nsamples && trials < 100); + + if (ret != nsamples) + return SRSLTE_ERROR; + return ret; } From df2bbd40871be545f151cf3e1a382f691d56c3bf Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 5 Oct 2017 16:26:23 +0200 Subject: [PATCH 24/49] fix multi channel tx support in soapy --- lib/src/phy/rf/rf_dev.h | 2 +- lib/src/phy/rf/rf_soapy_imp.c | 129 ++++++++++++++++++++++++++++------ lib/src/phy/rf/rf_soapy_imp.h | 11 ++- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/lib/src/phy/rf/rf_dev.h b/lib/src/phy/rf/rf_dev.h index 00f157b6b..d41adbeed 100644 --- a/lib/src/phy/rf/rf_dev.h +++ b/lib/src/phy/rf/rf_dev.h @@ -177,7 +177,7 @@ static rf_dev_t dev_soapy = { rf_soapy_recv_with_time, rf_soapy_recv_with_time_multi, rf_soapy_send_timed, - .srslte_rf_send_timed_multi = /* FIXME: Implement srslte_rf_send_timed_multi for Soapy SDR */ NULL, + .srslte_rf_send_timed_multi = rf_soapy_send_timed_multi, rf_soapy_set_tx_cal, rf_soapy_set_rx_cal }; diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index 084308116..0eaf34c32 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -248,12 +248,12 @@ int rf_soapy_open(char *args, void **h) int rf_soapy_close(void *h) { rf_soapy_handler_t *handler = (rf_soapy_handler_t*) h; - if (handler->txStream) { + if (handler->tx_stream_active) { rf_soapy_stop_tx_stream(handler); SoapySDRDevice_closeStream(handler->device, handler->txStream); } - if (handler->rxStream) { + if (handler->rx_stream_active) { rf_soapy_stop_rx_stream(handler); SoapySDRDevice_closeStream(handler->device, handler->rxStream); } @@ -285,6 +285,12 @@ double rf_soapy_set_rx_srate(void *h, double rate) printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } + + if (SoapySDRDevice_setBandwidth(handler->device, SOAPY_SDR_RX, 0, rate) != 0) { + printf("setBandwidth failed: %s\n", SoapySDRDevice_lastError()); + return SRSLTE_ERROR; + } + return SoapySDRDevice_getSampleRate(handler->device, SOAPY_SDR_RX,0); } @@ -295,6 +301,12 @@ double rf_soapy_set_tx_srate(void *h, double rate) printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } + + if (SoapySDRDevice_setBandwidth(handler->device, SOAPY_SDR_TX, 0, rate) != 0) { + printf("setBandwidth failed: %s\n", SoapySDRDevice_lastError()); + return SRSLTE_ERROR; + } + return SoapySDRDevice_getSampleRate(handler->device, SOAPY_SDR_TX,0); } @@ -345,7 +357,15 @@ double rf_soapy_set_rx_freq(void *h, double freq) printf("setFrequency fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } - + + // Todo: expose antenna setting + if (SoapySDRDevice_setAntenna(handler->device, SOAPY_SDR_RX, 0, "LNAH") != 0) { + fprintf(stderr, "Failed to set Rx antenna.\n"); + } + + char *ant = SoapySDRDevice_getAntenna(handler->device, SOAPY_SDR_RX, 0); + printf("Rx antenna set to %s\n", ant); + return SoapySDRDevice_getFrequency(handler->device, SOAPY_SDR_RX, 0); } @@ -357,6 +377,16 @@ double rf_soapy_set_tx_freq(void *h, double freq) printf("setFrequency fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } + + // Todo: expose antenna name in arguments + if (SoapySDRDevice_setAntenna(handler->device, SOAPY_SDR_TX, 0, "BAND1") != 0) { + fprintf(stderr, "Failed to set Tx antenna.\n"); + } + + + char *ant = SoapySDRDevice_getAntenna(handler->device, SOAPY_SDR_TX, 0); + printf("Tx antenna set to %s\n", ant); + return SoapySDRDevice_getFrequency(handler->device, SOAPY_SDR_TX, 0); } @@ -366,6 +396,7 @@ void rf_soapy_get_time(void *h, time_t *secs, double *frac_secs) } + //TODO: add multi-channel support int rf_soapy_recv_with_time_multi(void *h, void **data, @@ -405,17 +436,22 @@ int rf_soapy_recv_with_time_multi(void *h, } } +#if 0 + *secs = timeNs / 1000000000; + *frac_secs = (timeNs % 1000000000)/1000000000; + printf("ret=%d, flags=%d, timeNs=%lld\n", ret, flags, timeNs); +#endif + n += ret; trials++; } while (n < nsamples && trials < 100); - //*secs = timeNs / 1000000000; - //*frac_secs = (timeNs % 1000000000)/1000000000; - // printf("ret=%d, flags=%d, timeNs=%lld\n", ret, flags, timeNs); + return n; } + int rf_soapy_recv_with_time(void *h, void *data, uint32_t nsamples, @@ -428,19 +464,37 @@ int rf_soapy_recv_with_time(void *h, int rf_soapy_send_timed(void *h, - void *data, - int nsamples, - time_t secs, - double frac_secs, - bool has_time_spec, - bool blocking, - bool is_start_of_burst, - bool is_end_of_burst) -{ - int flags; + void *data, + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst) +{ + void *_data[SRSLTE_MAX_PORTS]= {data, zero_mem, zero_mem, zero_mem}; + return rf_soapy_send_timed_multi(h, _data, nsamples, secs, frac_secs, has_time_spec, blocking, is_start_of_burst, is_end_of_burst); +} + + +// Todo: Check correct handling of flags, use RF metrics API, fix timed transmissions +int rf_soapy_send_timed_multi(void *h, + void *data[SRSLTE_MAX_PORTS], + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst) +{ + int flags = 0; + const long timeoutUs = 2000; // arbitrarily chosen long long timeNs; int trials = 0; int ret = 0; + rf_soapy_handler_t *handler = (rf_soapy_handler_t *) h; timeNs = secs * 1000000000; timeNs = timeNs + (frac_secs * 1000000000); @@ -449,25 +503,54 @@ int rf_soapy_send_timed(void *h, if (!handler->tx_stream_active) { rf_soapy_start_tx_stream(h); } - - cf_t *data_c = (cf_t *) data; + + //printf("send_timed_multi(): time_spec=%d blocking=%d, is_start_of_burst=%d is_end_of_burst=%d\n", has_time_spec, blocking, is_start_of_burst, is_end_of_burst); + + if (is_start_of_burst && is_end_of_burst) { + flags |= SOAPY_SDR_ONE_PACKET; + } + + if (is_end_of_burst) { + flags |= SOAPY_SDR_END_BURST; + } + + if (has_time_spec) { + flags |= SOAPY_SDR_HAS_TIME; + } + do { size_t tx_samples = nsamples; if (tx_samples > nsamples - n) { tx_samples = nsamples - n; } - void *buff = (void *) &data_c[n]; - const void *buffs_ptr[1] = {buff}; - ret = SoapySDRDevice_writeStream(handler->device, handler->txStream, buffs_ptr, tx_samples, &flags, timeNs, 10000); - if (ret < 0) + + ret = SoapySDRDevice_writeStream(handler->device, handler->txStream, (const void *)data, tx_samples, &flags, timeNs, timeoutUs); + if (ret == SOAPY_SDR_TIMEOUT) { + printf("L"); + continue; + } + if (ret == SOAPY_SDR_OVERFLOW) { + printf("O"); + continue; + } + if (ret == SOAPY_SDR_UNDERFLOW) { + printf("U"); + continue; + } + if (ret < 0) { + fprintf(stderr, "Error during writeStream\n"); + exit(-1); return SRSLTE_ERROR; + } n += ret; trials++; } while (n < nsamples && trials < 100); - if (ret != nsamples) + if (n != nsamples) { + fprintf(stderr, "Couldn't write all samples.\n"); return SRSLTE_ERROR; + } return ret; } diff --git a/lib/src/phy/rf/rf_soapy_imp.h b/lib/src/phy/rf/rf_soapy_imp.h index 23b59a8b3..19de4536c 100644 --- a/lib/src/phy/rf/rf_soapy_imp.h +++ b/lib/src/phy/rf/rf_soapy_imp.h @@ -106,7 +106,7 @@ SRSLTE_API void rf_soapy_get_time(void *h, time_t *secs, double *frac_secs); -SRSLTE_API int rf_soapy_send_timed(void *h, +SRSLTE_API int rf_soapy_send_timed(void *h, void *data, int nsamples, time_t secs, @@ -116,3 +116,12 @@ SRSLTE_API int rf_soapy_send_timed(void *h, bool is_start_of_burst, bool is_end_of_burst); +int rf_soapy_send_timed_multi(void *h, + void *data[4], + int nsamples, + time_t secs, + double frac_secs, + bool has_time_spec, + bool blocking, + bool is_start_of_burst, + bool is_end_of_burst); From ca0cf017d6a71775c9a842eefef410229a494202 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Thu, 5 Oct 2017 16:52:02 +0200 Subject: [PATCH 25/49] Now working with variable HARQ scheduling --- lib/include/srslte/common/common.h | 14 +- srsenb/hdr/phy/phch_worker.h | 4 +- srsenb/src/mac/scheduler.cc | 4 +- srsenb/src/mac/scheduler_metric.cc | 18 +- srsenb/src/mac/scheduler_ue.cc | 12 +- srsenb/src/phy/phch_common.cc | 10 +- srsenb/src/phy/phch_worker.cc | 367 +++++++++++++++-------------- srsenb/src/phy/txrx.cc | 2 +- srsue/hdr/mac/mux.h | 3 +- srsue/src/phy/phch_common.cc | 12 +- srsue/src/phy/phch_worker.cc | 41 ++-- 11 files changed, 260 insertions(+), 227 deletions(-) diff --git a/lib/include/srslte/common/common.h b/lib/include/srslte/common/common.h index c6ad06402..9d3d56569 100644 --- a/lib/include/srslte/common/common.h +++ b/lib/include/srslte/common/common.h @@ -44,13 +44,15 @@ #define SRSLTE_N_DRB 8 #define SRSLTE_N_RADIO_BEARERS 11 -#define HARQ_DELAY_MS 4 -#define MSG3_DELAY_MS 6 -#define HARQ_TX(tti) ((tti+HARQ_DELAY_MS)%10240) -#define HARQ_RX(tti) ((tti+(2*HARQ_DELAY_MS))%10240) +#define HARQ_DELAY_MS 6 +#define MSG3_DELAY_MS 6 +#define TTI_TX(tti) ((tti+HARQ_DELAY_MS)%10240) +#define TTI_RX_ACK(tti) ((tti+(2*HARQ_DELAY_MS))%10240) -#define TTIMOD_SZ 10 -#define TTIMOD(tti) (tti%TTIMOD_SZ) +#define TTIMOD_SZ 20 +#define TTIMOD(tti) (tti%TTIMOD_SZ) + +#define ASYNC_DL_SCHED (HARQ_DELAY_MS <= 4) // Cat 3 UE - Max number of DL-SCH transport block bits received within a TTI // 3GPP 36.306 Table 4.1.1 diff --git a/srsenb/hdr/phy/phch_worker.h b/srsenb/hdr/phy/phch_worker.h index a84045920..3194751e8 100644 --- a/srsenb/hdr/phy/phch_worker.h +++ b/srsenb/hdr/phy/phch_worker.h @@ -89,9 +89,9 @@ private: cf_t *signal_buffer_rx; cf_t *signal_buffer_tx; - uint32_t tti_rx, tti_tx, tti_sched_ul; + uint32_t tti_rx, tti_tx_dl, tti_tx_ul; uint32_t sf_rx, sf_tx, tx_mutex_cnt; - uint32_t t_rx, t_tx, t_sched_ul; + uint32_t t_rx, t_tx_dl, t_tx_ul; srslte_enb_dl_t enb_dl; srslte_enb_ul_t enb_ul; diff --git a/srsenb/src/mac/scheduler.cc b/srsenb/src/mac/scheduler.cc index 73f121424..ab499efb0 100644 --- a/srsenb/src/mac/scheduler.cc +++ b/srsenb/src/mac/scheduler.cc @@ -677,14 +677,14 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched pthread_mutex_lock(&mutex); /* If dl_sched() not yet called this tti (this tti is +4ms advanced), reset CCE state */ - if (HARQ_TX(current_tti) != tti) { + if (TTI_TX(current_tti) != tti) { bzero(used_cce, MAX_CCE*sizeof(bool)); } /* Initialize variables */ current_tti = tti; sfn = tti/10; - if (tti > 4) { + if (tti > HARQ_DELAY_MS) { sf_idx = (tti-HARQ_DELAY_MS)%10; } else { sf_idx = (tti+10240-HARQ_DELAY_MS)%10; diff --git a/srsenb/src/mac/scheduler_metric.cc b/srsenb/src/mac/scheduler_metric.cc index 708ab2dd8..6c50009f7 100644 --- a/srsenb/src/mac/scheduler_metric.cc +++ b/srsenb/src/mac/scheduler_metric.cc @@ -142,8 +142,12 @@ dl_harq_proc* dl_metric_rr::get_user_allocation(sched_ue *user) dl_harq_proc *h = user->get_pending_dl_harq(current_tti); // Time-domain RR scheduling +#if ASYNC_DL_SCHED if (pending_data || h) { - if (nof_users_with_data) { +#else + if (pending_data || (h && !h->is_empty())) { +#endif + if (nof_users_with_data) { if (nof_users_with_data == 2) { } if ((current_tti%nof_users_with_data) != user->ue_idx) { @@ -153,7 +157,11 @@ dl_harq_proc* dl_metric_rr::get_user_allocation(sched_ue *user) } // Schedule retx if we have space +#if ASYNC_DL_SCHED if (h) { +#else + if (h && !h->is_empty()) { +#endif uint32_t retx_mask = h->get_rbgmask(); // If can schedule the same mask, do it if (!allocation_is_valid(retx_mask)) { @@ -170,10 +178,14 @@ dl_harq_proc* dl_metric_rr::get_user_allocation(sched_ue *user) } } } - // If could not schedule the reTx, or there wasn't any pending retx, find an empty PID + // If could not schedule the reTx, or there wasn't any pending retx, find an empty PID +#if ASYNC_DL_SCHED h = user->get_empty_dl_harq(); if (h) { - // Allocate resources based on pending data +#else + if (h && h->is_empty()) { +#endif + // Allocate resources based on pending data if (pending_data) { uint32_t pending_rb = user->get_required_prb_dl(pending_data, nof_ctrl_symbols); uint32_t newtx_mask = 0; diff --git a/srsenb/src/mac/scheduler_ue.cc b/srsenb/src/mac/scheduler_ue.cc index 525494ad2..7f4042849 100644 --- a/srsenb/src/mac/scheduler_ue.cc +++ b/srsenb/src/mac/scheduler_ue.cc @@ -248,7 +248,7 @@ bool sched_ue::get_pucch_sched(uint32_t current_tti, uint32_t prb_idx[2]) // First check if it has pending ACKs for (int i=0;iN_pucch_1 = phy->pucch_cfg.n1_pucch_an; srslte_enb_ul_cfg_ue(&enb_ul, rnti, uci_cfg, pucch_sched, srs_cfg); - - ue_db[rnti].I_sr = I_sr; + + ue_db[rnti].I_sr = I_sr; ue_db[rnti].I_sr_en = true; if (pucch_cqi) { - ue_db[rnti].pmi_idx = pmi_idx; - ue_db[rnti].cqi_en = true; - ue_db[rnti].pucch_cqi_ack = pucch_cqi_ack; + ue_db[rnti].pmi_idx = pmi_idx; + ue_db[rnti].cqi_en = true; + ue_db[rnti].pucch_cqi_ack = pucch_cqi_ack; } else { - ue_db[rnti].pmi_idx = 0; - ue_db[rnti].cqi_en = false; + ue_db[rnti].pmi_idx = 0; + ue_db[rnti].cqi_en = false; } - + } else { Error("Setting config dedicated: rnti=0x%x does not exist\n"); } - pthread_mutex_unlock(&mutex); + pthread_mutex_unlock(&mutex); } void phch_worker::rem_rnti(uint16_t rnti) { - pthread_mutex_lock(&mutex); + pthread_mutex_lock(&mutex); if (ue_db.count(rnti)) { ue_db.erase(rnti); - - srslte_enb_dl_rem_rnti(&enb_dl, rnti); + + srslte_enb_dl_rem_rnti(&enb_dl, rnti); srslte_enb_ul_rem_rnti(&enb_ul, rnti); - - // remove any pending grant for each subframe + + // remove any pending grant for each subframe for (uint32_t i=0;iul_grants[i].nof_grants;j++) { if (phy->ul_grants[i].sched_grants[j].rnti == rnti) { - phy->ul_grants[i].sched_grants[j].rnti = 0; + phy->ul_grants[i].sched_grants[j].rnti = 0; } } for (uint32_t j=0;jdl_grants[i].nof_grants;j++) { if (phy->dl_grants[i].sched_grants[j].rnti == rnti) { - phy->dl_grants[i].sched_grants[j].rnti = 0; + phy->dl_grants[i].sched_grants[j].rnti = 0; } } } } else { Error("Removing user: rnti=0x%x does not exist\n", rnti); } - pthread_mutex_unlock(&mutex); + pthread_mutex_unlock(&mutex); } void phch_worker::work_imp() @@ -275,18 +275,18 @@ void phch_worker::work_imp() } pthread_mutex_lock(&mutex); - + mac_interface_phy::ul_sched_t *ul_grants = phy->ul_grants; - mac_interface_phy::dl_sched_t *dl_grants = phy->dl_grants; - mac_interface_phy *mac = phy->mac; - + mac_interface_phy::dl_sched_t *dl_grants = phy->dl_grants; + mac_interface_phy *mac = phy->mac; + log_h->step(tti_rx); - + Debug("Worker %d running\n", get_id()); - + for(std::map::iterator iter=ue_db.begin(); iter!=ue_db.end(); ++iter) { uint16_t rnti = (uint16_t) iter->first; - ue_db[rnti].has_grant_tti = -1; + ue_db[rnti].has_grant_tti = -1; } // Process UL signal @@ -294,51 +294,51 @@ void phch_worker::work_imp() // Decode pending UL grants for the tti they were scheduled decode_pusch(ul_grants[t_rx].sched_grants, ul_grants[t_rx].nof_grants); - + // Decode remaining PUCCH ACKs not associated with PUSCH transmission and SR signals decode_pucch(); - + // Get DL scheduling for the TX TTI from MAC - if (mac->get_dl_sched(tti_tx, &dl_grants[t_tx]) < 0) { + if (mac->get_dl_sched(tti_tx_dl, &dl_grants[t_tx_dl]) < 0) { Error("Getting DL scheduling from MAC\n"); goto unlock; - } - - if (dl_grants[t_tx].cfi < 1 || dl_grants[t_tx].cfi > 3) { - Error("Invalid CFI=%d\n", dl_grants[t_tx].cfi); + } + + if (dl_grants[t_tx_dl].cfi < 1 || dl_grants[t_tx_dl].cfi > 3) { + Error("Invalid CFI=%d\n", dl_grants[t_tx_dl].cfi); goto unlock; } - + // Get UL scheduling for the TX TTI from MAC - if (mac->get_ul_sched(tti_sched_ul, &ul_grants[t_sched_ul]) < 0) { + if (mac->get_ul_sched(tti_tx_ul, &ul_grants[t_tx_ul]) < 0) { Error("Getting UL scheduling from MAC\n"); goto unlock; - } - + } + // Put base signals (references, PBCH, PCFICH and PSS/SSS) into the resource grid srslte_enb_dl_clear_sf(&enb_dl); - srslte_enb_dl_set_cfi(&enb_dl, dl_grants[t_tx].cfi); - srslte_enb_dl_put_base(&enb_dl, tti_tx); + srslte_enb_dl_set_cfi(&enb_dl, dl_grants[t_tx_dl].cfi); + srslte_enb_dl_put_base(&enb_dl, tti_tx_dl); + + // Put UL/DL grants to resource grid. PDSCH data will be encoded as well. + encode_pdcch_dl(dl_grants[t_tx_dl].sched_grants, dl_grants[t_tx_dl].nof_grants); + encode_pdcch_ul(ul_grants[t_tx_ul].sched_grants, ul_grants[t_tx_ul].nof_grants); + encode_pdsch(dl_grants[t_tx_dl].sched_grants, dl_grants[t_tx_dl].nof_grants); - // Put UL/DL grants to resource grid. PDSCH data will be encoded as well. - encode_pdcch_dl(dl_grants[t_tx].sched_grants, dl_grants[t_tx].nof_grants); - encode_pdcch_ul(ul_grants[t_sched_ul].sched_grants, ul_grants[t_sched_ul].nof_grants); - encode_pdsch(dl_grants[t_tx].sched_grants, dl_grants[t_tx].nof_grants); - // Put pending PHICH HARQ ACK/NACK indications into subframe - encode_phich(ul_grants[t_sched_ul].phich, ul_grants[t_sched_ul].nof_phich); - - // Prepare for receive ACK for DL grants in t_tx+4 - phy->ack_clear(TTIMOD(HARQ_TX(sf_tx))); - for (uint32_t i=0;iack_clear(TTIMOD(TTI_TX(t_tx_dl))); + for (uint32_t i=0;i= SRSLTE_CRNTI_START && dl_grants[t_tx].sched_grants[i].rnti <= SRSLTE_CRNTI_END) { - phy->ack_set_pending(TTIMOD(HARQ_TX(sf_tx)), dl_grants[t_tx].sched_grants[i].rnti, dl_grants[t_tx].sched_grants[i].location.ncce); + if (dl_grants[t_tx_dl].sched_grants[i].rnti >= SRSLTE_CRNTI_START && dl_grants[t_tx_dl].sched_grants[i].rnti <= SRSLTE_CRNTI_END) { + phy->ack_set_pending(TTIMOD(TTI_TX(t_tx_dl)), dl_grants[t_tx_dl].sched_grants[i].rnti, dl_grants[t_tx_dl].sched_grants[i].location.ncce); } } - + // Generate signal and transmit - srslte_enb_dl_gen_signal(&enb_dl, signal_buffer_tx); + srslte_enb_dl_gen_signal(&enb_dl, signal_buffer_tx); Debug("Sending to radio\n"); phy->worker_end(tx_mutex_cnt, signal_buffer_tx, SRSLTE_SF_LEN_PRB(phy->cell.nof_prb), tx_time); @@ -347,35 +347,35 @@ void phch_worker::work_imp() #endif #ifdef DEBUG_WRITE_FILE - if (tti_tx == 10) { + if (tti_tx_dl == 10) { fclose(f); exit(-1); } -#endif - +#endif + /* Tell the plotting thread to draw the plots */ #ifdef ENABLE_GUI if ((int) get_id() == plot_worker_id) { - sem_post(&plot_sem); + sem_post(&plot_sem); } #endif unlock: - pthread_mutex_unlock(&mutex); + pthread_mutex_unlock(&mutex); } int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch) { - srslte_uci_data_t uci_data; + srslte_uci_data_t uci_data; bzero(&uci_data, sizeof(srslte_uci_data_t)); - - uint32_t wideband_cqi_value = 0; - - uint32_t n_rb_ho = 0; + + uint32_t wideband_cqi_value = 0; + + uint32_t n_rb_ho = 0; for (uint32_t i=0;iack_is_pending(t_rx, rnti)) { - uci_data.uci_ack_len = 1; + uci_data.uci_ack_len = 1; } - // Configure PUSCH CQI channel + // Configure PUSCH CQI channel srslte_cqi_value_t cqi_value; - bool cqi_enabled = false; + bool cqi_enabled = false; if (ue_db[rnti].cqi_en && srslte_cqi_send(ue_db[rnti].pmi_idx, tti_rx)) { cqi_value.type = SRSLTE_CQI_TYPE_WIDEBAND; - cqi_enabled = true; + cqi_enabled = true; } else if (grants[i].grant.cqi_request) { cqi_value.type = SRSLTE_CQI_TYPE_SUBBAND_HL; cqi_value.subband_hl.N = (phy->cell.nof_prb > 7) ? srslte_cqi_hl_get_no_subbands(phy->cell.nof_prb) : 0; - cqi_enabled = true; + cqi_enabled = true; } if (cqi_enabled) { uci_data.uci_cqi_len = srslte_cqi_size(&cqi_value); } - - // mark this tti as having an ul grant to avoid pucch - ue_db[rnti].has_grant_tti = tti_rx; - - srslte_ra_ul_grant_t phy_grant; + + // mark this tti as having an ul grant to avoid pucch + ue_db[rnti].has_grant_tti = tti_rx; + + srslte_ra_ul_grant_t phy_grant; int res = -1; if (!srslte_ra_ul_dci_to_grant(&grants[i].grant, enb_ul.cell.nof_prb, n_rb_ho, &phy_grant, tti_rx%8)) { if (phy_grant.mcs.mod == SRSLTE_MOD_64QAM) { @@ -414,27 +414,27 @@ int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch) } phy_grant.Qm = SRSLTE_MIN(phy_grant.Qm, 4); res = srslte_enb_ul_get_pusch(&enb_ul, &phy_grant, grants[i].softbuffer, - rnti, grants[i].rv_idx, - grants[i].current_tx_nb, - grants[i].data, - &uci_data, + rnti, grants[i].rv_idx, + grants[i].current_tx_nb, + grants[i].data, + &uci_data, sf_rx); } else { Error("Computing PUSCH grant\n"); - return SRSLTE_ERROR; + return SRSLTE_ERROR; } - + #ifdef LOG_EXECTIME gettimeofday(&t[2], NULL); get_time_interval(t); snprintf(timestr, 64, ", dec_time=%4d us", (int) t[0].tv_usec); #endif - - bool crc_res = (res == 0); - + + bool crc_res = (res == 0); + // Save PHICH scheduling for this user. Each user can have just 1 PUSCH grant per TTI - ue_db[rnti].phich_info.n_prb_lowest = enb_ul.pusch_cfg.grant.n_prb_tilde[0]; - ue_db[rnti].phich_info.n_dmrs = phy_grant.ncs_dmrs; + ue_db[rnti].phich_info.n_prb_lowest = enb_ul.pusch_cfg.grant.n_prb_tilde[0]; + ue_db[rnti].phich_info.n_dmrs = phy_grant.ncs_dmrs; char cqi_str[64]; if (cqi_enabled) { @@ -446,8 +446,8 @@ int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch) } snprintf(cqi_str, 64, ", cqi=%d", wideband_cqi_value); } - - float snr_db = 10*log10(srslte_chest_ul_get_snr(&enb_ul.chest)); + + float snr_db = 10*log10(srslte_chest_ul_get_snr(&enb_ul.chest)); /* if (!crc_res && enb_ul.pusch_cfg.grant.L_prb == 1 && enb_ul.pusch_cfg.grant.n_prb[0] == 0 && snr_db > 5) { @@ -456,8 +456,8 @@ int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch) srslte_vec_save_file("d", enb_ul.pusch.d, sizeof(cf_t)*enb_ul.pusch_cfg.nbits.nof_re); srslte_vec_save_file("ce2", enb_ul.pusch.ce, sizeof(cf_t)*enb_ul.pusch_cfg.nbits.nof_re); srslte_vec_save_file("z", enb_ul.pusch.z, sizeof(cf_t)*enb_ul.pusch_cfg.nbits.nof_re); - printf("saved sf_idx=%d, mcs=%d, tbs=%d, rnti=%d, rv=%d, snr=%.1f\n", tti%10, - grants[i].grant.mcs_idx, enb_ul.pusch_cfg.cb_segm.tbs, rnti, grants[i].rv_idx, snr_db); + printf("saved sf_idx=%d, mcs=%d, tbs=%d, rnti=%d, rv=%d, snr=%.1f\n", tti%10, + grants[i].grant.mcs_idx, enb_ul.pusch_cfg.cb_segm.tbs, rnti, grants[i].rv_idx, snr_db); exit(-1); } */ @@ -465,120 +465,121 @@ int phch_worker::decode_pusch(srslte_enb_ul_pusch_t *grants, uint32_t nof_pusch) "PUSCH: rnti=0x%x, prb=(%d,%d), tbs=%d, mcs=%d, rv=%d, snr=%.1f dB, n_iter=%d, crc=%s%s%s%s\n", rnti, phy_grant.n_prb[0], phy_grant.n_prb[0]+phy_grant.L_prb, phy_grant.mcs.tbs/8, phy_grant.mcs.idx, grants[i].grant.rv_idx, - snr_db, + snr_db, srslte_pusch_last_noi(&enb_ul.pusch), crc_res?"OK":"KO", uci_data.uci_ack_len>0?(uci_data.uci_ack?", ack=1":", ack=0"):"", - uci_data.uci_cqi_len>0?cqi_str:"", - timestr); - - // Notify MAC of RL status + uci_data.uci_cqi_len>0?cqi_str:"", + timestr); + + // Notify MAC of RL status if (grants[i].grant.rv_idx == 0) { if (res && snr_db < PUSCH_RL_SNR_DB_TH) { Debug("PUSCH: Radio-Link failure snr=%.1f dB\n", snr_db); phy->mac->rl_failure(rnti); } else { phy->mac->rl_ok(rnti); - } + } } - + // Notify MAC new received data and HARQ Indication value - phy->mac->crc_info(tti_rx, rnti, phy_grant.mcs.tbs/8, crc_res); + phy->mac->crc_info(tti_rx, rnti, phy_grant.mcs.tbs/8, crc_res); if (uci_data.uci_ack_len) { phy->mac->ack_info(tti_rx, rnti, uci_data.uci_ack && (crc_res || snr_db > PUSCH_RL_SNR_DB_TH)); } - - // Notify MAC of UL SNR and DL CQI + + // Notify MAC of UL SNR and DL CQI if (snr_db >= PUSCH_RL_SNR_DB_TH) { phy->mac->snr_info(tti_rx, rnti, snr_db); } if (uci_data.uci_cqi_len>0 && crc_res) { phy->mac->cqi_info(tti_rx, rnti, wideband_cqi_value); } - - // Save metrics stats + + // Save metrics stats ue_db[rnti].metrics_ul(phy_grant.mcs.idx, 0, snr_db, srslte_pusch_last_noi(&enb_ul.pusch)); - } + } } - return SRSLTE_SUCCESS; + return SRSLTE_SUCCESS; } int phch_worker::decode_pucch() { srslte_uci_data_t uci_data; - + for(std::map::iterator iter=ue_db.begin(); iter!=ue_db.end(); ++iter) { uint16_t rnti = (uint16_t) iter->first; if (rnti >= SRSLTE_CRNTI_START && rnti <= SRSLTE_CRNTI_END && ue_db[rnti].has_grant_tti != (int) tti_rx) { - // Check if user needs to receive PUCCH - bool needs_pucch = false, needs_ack=false, needs_sr=false, needs_cqi=false; + // Check if user needs to receive PUCCH + bool needs_pucch = false, needs_ack=false, needs_sr=false, needs_cqi=false; uint32_t last_n_pdcch = 0; bzero(&uci_data, sizeof(srslte_uci_data_t)); - + if (ue_db[rnti].I_sr_en) { if (srslte_ue_ul_sr_send_tti(ue_db[rnti].I_sr, tti_rx)) { - needs_pucch = true; - needs_sr = true; - uci_data.scheduling_request = true; + needs_pucch = true; + needs_sr = true; + uci_data.scheduling_request = true; } - } + } + if (phy->ack_is_pending(t_rx, rnti, &last_n_pdcch)) { - needs_pucch = true; - needs_ack = true; - uci_data.uci_ack_len = 1; + needs_pucch = true; + needs_ack = true; + uci_data.uci_ack_len = 1; } srslte_cqi_value_t cqi_value; if (ue_db[rnti].cqi_en && (ue_db[rnti].pucch_cqi_ack || !needs_ack)) { if (srslte_cqi_send(ue_db[rnti].pmi_idx, tti_rx)) { - needs_pucch = true; - needs_cqi = true; - cqi_value.type = SRSLTE_CQI_TYPE_WIDEBAND; + needs_pucch = true; + needs_cqi = true; + cqi_value.type = SRSLTE_CQI_TYPE_WIDEBAND; uci_data.uci_cqi_len = srslte_cqi_size(&cqi_value); } } - + if (needs_pucch) { - if (srslte_enb_ul_get_pucch(&enb_ul, rnti, last_n_pdcch, t_rx, &uci_data)) { + if (srslte_enb_ul_get_pucch(&enb_ul, rnti, last_n_pdcch, sf_rx, &uci_data)) { fprintf(stderr, "Error getting PUCCH\n"); - return SRSLTE_ERROR; + return SRSLTE_ERROR; } if (uci_data.uci_ack_len > 0) { - phy->mac->ack_info(tti_rx, rnti, uci_data.uci_ack && (srslte_pucch_get_last_corr(&enb_ul.pucch) >= PUCCH_RL_CORR_TH)); + phy->mac->ack_info(tti_rx, rnti, uci_data.uci_ack && (srslte_pucch_get_last_corr(&enb_ul.pucch) >= PUCCH_RL_CORR_TH)); } if (uci_data.scheduling_request) { - phy->mac->sr_detected(tti_rx, rnti); + phy->mac->sr_detected(tti_rx, rnti); } - + char cqi_str[64]; if (uci_data.uci_cqi_len) { srslte_cqi_value_unpack(uci_data.uci_cqi, &cqi_value); phy->mac->cqi_info(tti_rx, rnti, cqi_value.wideband.wideband_cqi); sprintf(cqi_str, ", cqi=%d", cqi_value.wideband.wideband_cqi); } - log_h->info("PUCCH: rnti=0x%x, corr=%.2f, n_pucch=%d, n_prb=%d%s%s%s\n", - rnti, + log_h->info("PUCCH: rnti=0x%x, corr=%.2f, n_pucch=%d, n_prb=%d%s%s%s\n", + rnti, srslte_pucch_get_last_corr(&enb_ul.pucch), enb_ul.pucch.last_n_pucch, enb_ul.pucch.last_n_prb, - needs_ack?(uci_data.uci_ack?", ack=1":", ack=0"):"", - needs_sr?(uci_data.scheduling_request?", sr=yes":", sr=no"):"", - needs_cqi?cqi_str:""); + needs_ack?(uci_data.uci_ack?", ack=1":", ack=0"):"", + needs_sr?(uci_data.scheduling_request?", sr=yes":", sr=no"):"", + needs_cqi?cqi_str:""); - // Notify MAC of RL status + // Notify MAC of RL status if (!needs_sr) { if (srslte_pucch_get_last_corr(&enb_ul.pucch) < PUCCH_RL_CORR_TH) { Debug("PUCCH: Radio-Link failure corr=%.1f\n", srslte_pucch_get_last_corr(&enb_ul.pucch)); phy->mac->rl_failure(rnti); } else { phy->mac->rl_ok(rnti); - } - } + } + } } } - } - return 0; + } + return 0; } @@ -587,15 +588,15 @@ int phch_worker::encode_phich(srslte_enb_dl_phich_t *acks, uint32_t nof_acks) for (uint32_t i=0;iinfo_hex(ptr, len, - "PDSCH: rnti=0x%x, l_crb=%2d, %s, harq=%d, tbs=%d, mcs=%d, rv=%d, tti_tx=%d\n", - rnti, phy_grant.nof_prb, grant_str, grants[i].grant.harq_process, - phy_grant.mcs[0].tbs/8, phy_grant.mcs[0].idx, grants[i].grant.rv_idx, tti_tx); + "PDSCH: rnti=0x%x, l_crb=%2d, %s, harq=%d, tbs=%d, mcs=%d, rv=%d, tti_tx_dl=%d\n", + rnti, phy_grant.nof_prb, grant_str, grants[i].grant.harq_process, + phy_grant.mcs[0].tbs/8, phy_grant.mcs[0].idx, grants[i].grant.rv_idx, tti_tx_dl); } srslte_softbuffer_tx_t *sb[SRSLTE_MAX_CODEWORDS] = {grants[i].softbuffer, NULL}; diff --git a/srsenb/src/phy/txrx.cc b/srsenb/src/phy/txrx.cc index 9427e3459..fa14b0b82 100644 --- a/srsenb/src/phy/txrx.cc +++ b/srsenb/src/phy/txrx.cc @@ -115,7 +115,7 @@ void txrx::run_thread() /* Compute TX time: Any transmission happens in TTI+4 thus advance 4 ms the reception time */ srslte_timestamp_copy(&tx_time, &rx_time); - srslte_timestamp_add(&tx_time, 0, 4e-3); + srslte_timestamp_add(&tx_time, 0, HARQ_DELAY_MS*1e-3); Debug("Settting TTI=%d, tx_mutex=%d, tx_time=%d:%f to worker %d\n", tti, tx_mutex_cnt, diff --git a/srsue/hdr/mac/mux.h b/srsue/hdr/mac/mux.h index 1167af752..ab081070c 100644 --- a/srsue/hdr/mac/mux.h +++ b/srsue/hdr/mac/mux.h @@ -82,8 +82,7 @@ private: const static int MIN_RLC_SDU_LEN = 0; const static int MAX_NOF_SUBHEADERS = 20; - const static int MAX_HARQ_PROC = 8; - + std::vector lch; // Keep track of the PIDs that transmitted BSR reports diff --git a/srsue/src/phy/phch_common.cc b/srsue/src/phy/phch_common.cc index 489d27197..549783fd9 100644 --- a/srsue/src/phy/phch_common.cc +++ b/srsue/src/phy/phch_common.cc @@ -136,12 +136,16 @@ srslte::radio* phch_common::get_radio() void phch_common::set_rar_grant(uint32_t tti, uint8_t grant_payload[SRSLTE_RAR_GRANT_LEN]) { srslte_dci_rar_grant_unpack(&rar_grant, grant_payload); - rar_grant_pending = true; - // PUSCH is at n+6 or n+7 and phch_worker assumes default delay of 4 ttis + rar_grant_pending = true; + int delay = MSG3_DELAY_MS-HARQ_DELAY_MS; + if (delay < 0) { + fprintf(stderr, "Error MSG3_DELAY_MS can't be lower than HARQ_DELAY_MS\n"); + delay = 0; + } if (rar_grant.ul_delay) { - rar_grant_tti = (tti + 3) % 10240; + rar_grant_tti = (tti + delay + 1) % 10240; } else { - rar_grant_tti = (tti + 2) % 10240; + rar_grant_tti = (tti + delay) % 10240; } } diff --git a/srsue/src/phy/phch_worker.cc b/srsue/src/phy/phch_worker.cc index e6c0050e2..adeb41603 100644 --- a/srsue/src/phy/phch_worker.cc +++ b/srsue/src/phy/phch_worker.cc @@ -292,6 +292,13 @@ void phch_worker::work_imp() } } } + + // Process RAR before UL to enable zero-delay Msg3 + bool rar_delivered = false; + if (HARQ_DELAY_MS == MSG3_DELAY_MS && dl_mac_grant.rnti_type == SRSLTE_RNTI_RAR) { + rar_delivered = true; + phy->mac->tb_decoded(dl_ack[0], 0, dl_mac_grant.rnti_type, dl_mac_grant.pid); + } // Decode PHICH bool ul_ack = false; @@ -313,8 +320,8 @@ void phch_worker::work_imp() set_uci_periodic_cqi(); } - /* TTI offset for UL is always 4 for LTE */ - ul_action.tti_offset = 4; + /* TTI offset for UL */ + ul_action.tti_offset = HARQ_DELAY_MS; /* Send UL grant or HARQ information (from PHICH) to MAC */ if (ul_grant_available && ul_ack_available) { @@ -335,7 +342,7 @@ void phch_worker::work_imp() &ul_action.softbuffers[0], ul_action.rv[0], ul_action.rnti, ul_mac_grant.is_from_rar); signal_ready = true; if (ul_action.expect_ack) { - phy->set_pending_ack(HARQ_RX(tti), ue_ul.pusch_cfg.grant.n_prb_tilde[0], ul_action.phy_grant.ul.ncs_dmrs); + phy->set_pending_ack(TTI_RX_ACK(tti), ue_ul.pusch_cfg.grant.n_prb_tilde[0], ul_action.phy_grant.ul.ncs_dmrs); } } else if (dl_action.generate_ack || uci_data.scheduling_request || uci_data.uci_cqi_len > 0) { @@ -357,7 +364,7 @@ void phch_worker::work_imp() if (!dl_action.generate_ack_callback) { if (dl_mac_grant.rnti_type == SRSLTE_RNTI_PCH && dl_action.decode_enabled[0]) { phy->mac->pch_decoded_ok(dl_mac_grant.n_bytes[0]); - } else { + } else if (!rar_delivered) { for (uint32_t tb = 0; tb < SRSLTE_MAX_TB; tb++) { if (dl_action.decode_enabled[tb]) { phy->mac->tb_decoded(dl_ack[tb], tb, dl_mac_grant.rnti_type, dl_mac_grant.pid); @@ -475,7 +482,7 @@ bool phch_worker::decode_pdcch_dl(srsue::mac_interface_phy::mac_grant_t* grant) /* Fill MAC grant structure */ grant->ndi[0] = dci_unpacked.ndi; grant->ndi[1] = dci_unpacked.ndi_1; - grant->pid = dci_unpacked.harq_process; + grant->pid = ASYNC_DL_SCHED?dci_unpacked.harq_process:(tti%(2*HARQ_DELAY_MS)); grant->n_bytes[0] = grant->phy_grant.dl.mcs[0].tbs / (uint32_t) 8; grant->n_bytes[1] = grant->phy_grant.dl.mcs[1].tbs / (uint32_t) 8; grant->tti = tti; @@ -663,7 +670,7 @@ bool phch_worker::decode_pdcch_ul(mac_interface_phy::mac_grant_t* grant) char timestr[64]; timestr[0]='\0'; - phy->reset_pending_ack(HARQ_RX(tti)); + phy->reset_pending_ack(TTI_RX_ACK(tti)); srslte_dci_msg_t dci_msg; srslte_ra_ul_dci_t dci_unpacked; @@ -776,7 +783,7 @@ void phch_worker::set_uci_sr() { uci_data.scheduling_request = false; if (phy->sr_enabled) { - uint32_t sr_tx_tti = HARQ_TX(tti); + uint32_t sr_tx_tti = TTI_TX(tti); // Get I_sr parameter if (srslte_ue_ul_sr_send_tti(I_sr, sr_tx_tti)) { Info("PUCCH: SR transmission at TTI=%d, I_sr=%d\n", sr_tx_tti, I_sr); @@ -793,7 +800,7 @@ void phch_worker::set_uci_periodic_cqi() int cqi_max = phy->args->cqi_max; if (period_cqi.configured && rnti_is_set) { - if (period_cqi.ri_idx_present && srslte_ri_send(period_cqi.pmi_idx, period_cqi.ri_idx, HARQ_TX(tti))) { + if (period_cqi.ri_idx_present && srslte_ri_send(period_cqi.pmi_idx, period_cqi.ri_idx, TTI_TX(tti))) { if (uci_data.uci_ri_len) { uci_data.uci_cqi[0] = uci_data.uci_ri; uci_data.uci_cqi_len = uci_data.uci_ri_len; @@ -802,7 +809,7 @@ void phch_worker::set_uci_periodic_cqi() uci_data.uci_pmi_len = 0; Info("PUCCH: Periodic RI=%d\n", uci_data.uci_cqi[0]); } - } else if (srslte_cqi_send(period_cqi.pmi_idx, HARQ_TX(tti))) { + } else if (srslte_cqi_send(period_cqi.pmi_idx, TTI_TX(tti))) { srslte_cqi_value_t cqi_report; if (period_cqi.format_is_subband) { // TODO: Implement subband periodic reports @@ -868,8 +875,8 @@ void phch_worker::set_uci_aperiodic_cqi() bool phch_worker::srs_is_ready_to_send() { if (srs_cfg.configured) { - if (srslte_refsignal_srs_send_cs(srs_cfg.subframe_config, HARQ_RX(tti)%10) == 1 && - srslte_refsignal_srs_send_ue(srs_cfg.I_srs, HARQ_TX(tti)) == 1) + if (srslte_refsignal_srs_send_cs(srs_cfg.subframe_config, TTI_TX(tti)%10) == 1 && + srslte_refsignal_srs_send_ue(srs_cfg.I_srs, TTI_TX(tti)) == 1) { return true; } @@ -889,7 +896,7 @@ void phch_worker::encode_pusch(srslte_ra_ul_grant_t *grant, uint8_t *payload, ui char timestr[64]; timestr[0]='\0'; - if (srslte_ue_ul_cfg_grant(&ue_ul, grant, HARQ_TX(tti), rv, current_tx_nb)) { + if (srslte_ue_ul_cfg_grant(&ue_ul, grant, TTI_TX(tti), rv, current_tx_nb)) { Error("Configuring UL grant\n"); } @@ -919,7 +926,7 @@ void phch_worker::encode_pusch(srslte_ra_ul_grant_t *grant, uint8_t *payload, ui #endif Info("PUSCH: tti_tx=%d, n_prb=%d, rb_start=%d, tbs=%d, mod=%d, mcs=%d, rv_idx=%d, ack=%s, ri=%s, cfo=%.1f Hz%s\n", - HARQ_TX(tti), + TTI_TX(tti), grant->L_prb, grant->n_prb[0], grant->mcs.tbs/8, grant->mcs.mod, grant->mcs.idx, rv, uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", @@ -950,7 +957,7 @@ void phch_worker::encode_pucch() gettimeofday(&t[1], NULL); #endif - if (srslte_ue_ul_pucch_encode(&ue_ul, uci_data, last_dl_pdcch_ncce, HARQ_TX(tti), signal_buffer[0])) { + if (srslte_ue_ul_pucch_encode(&ue_ul, uci_data, last_dl_pdcch_ncce, TTI_TX(tti), signal_buffer[0])) { Error("Encoding PUCCH\n"); } @@ -966,7 +973,7 @@ void phch_worker::encode_pucch() float gain = set_power(tx_power); Info("PUCCH: tti_tx=%d, n_cce=%3d, n_pucch=%d, n_prb=%d, ack=%s%s, ri=%s, pmi=%s%s, sr=%s, cfo=%.1f Hz%s\n", - HARQ_TX(tti), + TTI_TX(tti), last_dl_pdcch_ncce, ue_ul.pucch.last_n_pucch, ue_ul.pucch.last_n_prb, uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", uci_data.uci_ack_len>1?(uci_data.uci_ack_2?"1":"0"):"", @@ -987,7 +994,7 @@ void phch_worker::encode_srs() char timestr[64]; timestr[0]='\0'; - if (srslte_ue_ul_srs_encode(&ue_ul, HARQ_TX(tti), signal_buffer[0])) + if (srslte_ue_ul_srs_encode(&ue_ul, TTI_TX(tti), signal_buffer[0])) { Error("Encoding SRS\n"); } @@ -1002,7 +1009,7 @@ void phch_worker::encode_srs() float gain = set_power(tx_power); uint32_t fi = srslte_vec_max_fi((float*) signal_buffer, SRSLTE_SF_LEN_PRB(cell.nof_prb)); float *f = (float*) signal_buffer; - Info("SRS: power=%.2f dBm, tti_tx=%d%s\n", tx_power, HARQ_TX(tti), timestr); + Info("SRS: power=%.2f dBm, tti_tx=%d%s\n", tx_power, TTI_TX(tti), timestr); } From 5e9750f2f6e565ceb784eeb593f6697f6c69784b Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 5 Oct 2017 21:17:17 +0200 Subject: [PATCH 26/49] remove clutter from enb.conf.example --- srsenb/enb.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 391eb8af6..d88b63bb7 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -100,7 +100,7 @@ filename = /tmp/enb.pcap # # filename: File path to use for log output. Can be set to stdout # to print logs to standard output -git c##################################################################### +##################################################################### [log] all_level = info all_hex_limit = 32 From ba91a38da49422e025d5d2518b56515f51f0a21d Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 10:11:43 +0200 Subject: [PATCH 27/49] Allow PDCCH scheduling for 6 PRB --- srsenb/src/mac/scheduler_ue.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/srsenb/src/mac/scheduler_ue.cc b/srsenb/src/mac/scheduler_ue.cc index 3eab6b33d..38058415f 100644 --- a/srsenb/src/mac/scheduler_ue.cc +++ b/srsenb/src/mac/scheduler_ue.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include "srslte/srslte.h" #include "srslte/common/pdu.h" @@ -695,10 +696,16 @@ uint32_t sched_ue::get_aggr_level(uint32_t nof_bits) uint32_t l=0; float max_coderate = srslte_cqi_to_coderate(dl_cqi); float coderate = 99; + float factor=1.5; + uint32_t l_max = 3; + if (cell.nof_prb == 6) { + factor = 1.0; + l_max = 2; + } do { coderate = srslte_pdcch_coderate(nof_bits, l); l++; - } while(l<3 && 1.5*coderate > max_coderate); + } while(l max_coderate); Debug("SCHED: CQI=%d, l=%d, nof_bits=%d, coderate=%.2f, max_coderate=%.2f\n", dl_cqi, l, nof_bits, coderate, max_coderate); return l; } From c2c637e06a2c958557b9481f645a5d742a1ac5ff Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 10:12:02 +0200 Subject: [PATCH 28/49] Fixed out-of-bounds checking in rlc_um resegmentation --- lib/src/upper/rlc_um.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/upper/rlc_um.cc b/lib/src/upper/rlc_um.cc index d44df6348..b2697178c 100644 --- a/lib/src/upper/rlc_um.cc +++ b/lib/src/upper/rlc_um.cc @@ -513,10 +513,10 @@ void rlc_um::reassemble_rx_sdus() } // Handle last segment - // Handle last segment - if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES || - rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES || - rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) { + if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && + rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && + rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) + { memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; From 5bd92fb6581d5294a65c87803adcacff6e533b27 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 10:30:58 +0200 Subject: [PATCH 29/49] fix prach example and close rf at exit --- lib/src/phy/phch/test/prach_test_usrp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/phy/phch/test/prach_test_usrp.c b/lib/src/phy/phch/test/prach_test_usrp.c index 2defdec4f..c0fae365c 100644 --- a/lib/src/phy/phch/test/prach_test_usrp.c +++ b/lib/src/phy/phch/test/prach_test_usrp.c @@ -231,7 +231,8 @@ int main(int argc, char **argv) { } srslte_vec_save_file(output_filename,buffer,11*flen*sizeof(cf_t)); - + + srslte_rf_close(&rf); srslte_prach_free(p); free(p); From f619f53cc460df6230b69e2598550b190720f1d6 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 10:40:59 +0200 Subject: [PATCH 30/49] fix soapy support --- lib/src/phy/rf/rf_soapy_imp.c | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index 0eaf34c32..e1b451412 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -174,7 +174,7 @@ void rf_soapy_flush_buffer(void *h) bool rf_soapy_has_rssi(void *h) { - printf("TODO: implement rf_soapy_has_rssi()\n"); + // TODO: implement rf_soapy_has_rssi() return false; } @@ -282,12 +282,12 @@ double rf_soapy_set_rx_srate(void *h, double rate) { rf_soapy_handler_t *handler = (rf_soapy_handler_t*) h; if (SoapySDRDevice_setSampleRate(handler->device, SOAPY_SDR_RX, 0, rate) != 0) { - printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError()); + printf("setSampleRate Rx fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } if (SoapySDRDevice_setBandwidth(handler->device, SOAPY_SDR_RX, 0, rate) != 0) { - printf("setBandwidth failed: %s\n", SoapySDRDevice_lastError()); + printf("setBandwidth Rx failed: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } @@ -298,12 +298,12 @@ double rf_soapy_set_tx_srate(void *h, double rate) { rf_soapy_handler_t *handler = (rf_soapy_handler_t*) h; if (SoapySDRDevice_setSampleRate(handler->device, SOAPY_SDR_TX, 0, rate) != 0) { - printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError()); + printf("setSampleRate Tx fail: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } if (SoapySDRDevice_setBandwidth(handler->device, SOAPY_SDR_TX, 0, rate) != 0) { - printf("setBandwidth failed: %s\n", SoapySDRDevice_lastError()); + printf("setBandwidth Tx failed: %s\n", SoapySDRDevice_lastError()); return SRSLTE_ERROR; } @@ -393,7 +393,7 @@ double rf_soapy_set_tx_freq(void *h, double freq) void rf_soapy_get_time(void *h, time_t *secs, double *frac_secs) { - + printf("Todo: implement rf_soapy_get_time()\n"); } @@ -424,7 +424,7 @@ int rf_soapy_recv_with_time_multi(void *h, cf_t *data_c = (cf_t*) data[i]; buffs_ptr[i] = &data_c[n]; } - ret = SoapySDRDevice_readStream(handler->device, handler->rxStream, buffs_ptr , rx_samples, &flags, &timeNs, 1000000); + ret = SoapySDRDevice_readStream(handler->device, handler->rxStream, buffs_ptr, rx_samples, &flags, &timeNs, 10000); if(ret < 0) { // continue when getting overflows if (ret == SOAPY_SDR_OVERFLOW) { @@ -436,11 +436,12 @@ int rf_soapy_recv_with_time_multi(void *h, } } -#if 0 - *secs = timeNs / 1000000000; - *frac_secs = (timeNs % 1000000000)/1000000000; - printf("ret=%d, flags=%d, timeNs=%lld\n", ret, flags, timeNs); -#endif + // update rx time + if (secs != NULL && frac_secs != NULL) { + *secs = timeNs / 1e9; + *frac_secs = (timeNs % 1000000000)/1e9; + //printf("rx_time: secs=%d, frac_secs=%lf timeNs=%lld\n", *secs, *frac_secs, timeNs); + } n += ret; trials++; @@ -489,23 +490,19 @@ int rf_soapy_send_timed_multi(void *h, bool is_start_of_burst, bool is_end_of_burst) { + rf_soapy_handler_t *handler = (rf_soapy_handler_t *) h; int flags = 0; const long timeoutUs = 2000; // arbitrarily chosen long long timeNs; int trials = 0; int ret = 0; - - rf_soapy_handler_t *handler = (rf_soapy_handler_t *) h; - timeNs = secs * 1000000000; - timeNs = timeNs + (frac_secs * 1000000000); int n = 0; + if (!handler->tx_stream_active) { rf_soapy_start_tx_stream(h); } - //printf("send_timed_multi(): time_spec=%d blocking=%d, is_start_of_burst=%d is_end_of_burst=%d\n", has_time_spec, blocking, is_start_of_burst, is_end_of_burst); - if (is_start_of_burst && is_end_of_burst) { flags |= SOAPY_SDR_ONE_PACKET; } @@ -516,6 +513,9 @@ int rf_soapy_send_timed_multi(void *h, if (has_time_spec) { flags |= SOAPY_SDR_HAS_TIME; + timeNs = secs * 1000000000; + timeNs = timeNs + (frac_secs * 1000000000); + //printf("time_spec: secs=%d, frac_secs=%lf timeNs=%lld\n", secs, frac_secs, timeNs); } do { From 0c3d4e9ad313110552bff2cf4d08555f48e2472d Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 14:04:14 +0200 Subject: [PATCH 31/49] Removed log --- srsue/src/mac/mac.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/srsue/src/mac/mac.cc b/srsue/src/mac/mac.cc index 60327fcd4..b54ea5be6 100644 --- a/srsue/src/mac/mac.cc +++ b/srsue/src/mac/mac.cc @@ -307,7 +307,6 @@ void mac::new_grant_ul(mac_interface_phy::mac_grant_t grant, mac_interface_phy:: void mac::new_grant_ul_ack(mac_interface_phy::mac_grant_t grant, bool ack, mac_interface_phy::tb_action_ul_t* action) { - log_h->info("new_grant_ul_ack\n"); int tbs = ul_harq.get_current_tbs(tti); ul_harq.new_grant_ul_ack(grant, ack, action); if (!ack) { From 404971703c1ea92e4bf31b8f6759dc04025f93b1 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 14:04:33 +0200 Subject: [PATCH 32/49] PLMN update only on PLMN search state --- srsue/src/upper/rrc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srsue/src/upper/rrc.cc b/srsue/src/upper/rrc.cc index 3bb3e0017..1d614aed4 100644 --- a/srsue/src/upper/rrc.cc +++ b/srsue/src/upper/rrc.cc @@ -427,7 +427,7 @@ void rrc::cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp) { if (!known_cells[i].has_valid_sib1) { si_acquire_state = SI_ACQUIRE_SIB1; - } else { + } else if (state == RRC_STATE_PLMN_SELECTION) { for (uint32_t i = 0; i < current_cell->sib1.N_plmn_ids; i++) { nas->plmn_found(current_cell->sib1.plmn_id[i].id, current_cell->sib1.tracking_area_code); } From eb07dacfe94cf6844fceab65f5b656c53aab9b95 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 14:33:22 +0200 Subject: [PATCH 33/49] Cleaning logging format --- lib/src/common/pdu_queue.cc | 4 ++-- srsue/src/phy/phch_worker.cc | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/src/common/pdu_queue.cc b/lib/src/common/pdu_queue.cc index a1bf7cd59..6b4c8bfd9 100644 --- a/lib/src/common/pdu_queue.cc +++ b/lib/src/common/pdu_queue.cc @@ -66,7 +66,7 @@ uint8_t* pdu_queue::request(uint32_t len) void pdu_queue::deallocate(uint8_t* pdu) { if (!pool.deallocate((pdu_t*) pdu)) { - log_h->warning("Error deallocating from buffer pool: buffer not created in this pool.\n"); + log_h->warning("Error deallocating from buffer pool in deallocate(): buffer not created in this pool.\n"); } } @@ -92,7 +92,7 @@ bool pdu_queue::process_pdus() callback->process_pdu(pdu->ptr, pdu->len, pdu->tstamp); } if (!pool.deallocate(pdu)) { - log_h->warning("Error deallocating from buffer pool: buffer not created in this pool.\n"); + log_h->warning("Error deallocating from buffer pool in process_pdus(): buffer not created in this pool.\n"); } cnt++; have_data = true; diff --git a/srsue/src/phy/phch_worker.cc b/srsue/src/phy/phch_worker.cc index 1c9abc1ca..c0fec2ba2 100644 --- a/srsue/src/phy/phch_worker.cc +++ b/srsue/src/phy/phch_worker.cc @@ -915,16 +915,16 @@ void phch_worker::encode_pusch(srslte_ra_ul_grant_t *grant, uint8_t *payload, ui #ifdef LOG_EXECTIME gettimeofday(&logtime_start[2], NULL); get_time_interval(logtime_start); - snprintf(timestr, 64, ", total_time=%4d us", (int) logtime_start[0].tv_usec); + snprintf(timestr, 64, ", tot_time=%4d us", (int) logtime_start[0].tv_usec); #endif - Info("PUSCH: tti_tx=%d, n_prb=%d, rb_start=%d, tbs=%d, mod=%d, mcs=%d, rv_idx=%d, ack=%s, ri=%s, cfo=%.1f Hz%s\n", + Info("PUSCH: tti_tx=%d, alloc=(%d,%d), tbs=%d, mcs=%d, rv=%d, ack=%s, ri=%s, cfo=%.1f KHz%s\n", (tti+4)%10240, - grant->L_prb, grant->n_prb[0], - grant->mcs.tbs/8, grant->mcs.mod, grant->mcs.idx, rv, + grant->n_prb[0], grant->n_prb[0]+grant->L_prb, + grant->mcs.tbs/8, grant->mcs.idx, rv, uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", uci_data.uci_ri_len>0?(uci_data.uci_ri?"1":"0"):"no", - cfo*15000, timestr); + cfo*15, timestr); // Store metrics ul_metrics.mcs = grant->mcs.idx; @@ -959,22 +959,22 @@ void phch_worker::encode_pucch() memcpy(&t[2], &logtime_start[2], sizeof(struct timeval)); get_time_interval(logtime_start); get_time_interval(t); - snprintf(timestr, 64, ", enc_time=%d, total_time=%d us", (int) t[0].tv_usec, (int) logtime_start[0].tv_usec); + snprintf(timestr, 64, ", tot_time=%d us", (int) logtime_start[0].tv_usec); #endif float tx_power = srslte_ue_ul_pucch_power(&ue_ul, phy->pathloss, ue_ul.last_pucch_format, uci_data.uci_cqi_len, uci_data.uci_ack_len); float gain = set_power(tx_power); - Info("PUCCH: tti_tx=%d, n_cce=%3d, n_pucch=%d, n_prb=%d, ack=%s%s, ri=%s, pmi=%s%s, sr=%s, cfo=%.1f Hz%s\n", + Info("PUCCH: tti_tx=%d, n_pucch=%d, n_prb=%d, ack=%s%s, ri=%s, pmi=%s%s, sr=%s, cfo=%.1f KHz%s\n", (tti+4)%10240, - last_dl_pdcch_ncce, ue_ul.pucch.last_n_pucch, ue_ul.pucch.last_n_prb, - uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", - uci_data.uci_ack_len>1?(uci_data.uci_ack_2?"1":"0"):"", - uci_data.uci_ri_len>0?(uci_data.uci_ri?"1":"0"):"no", - uci_data.uci_pmi_len>0?(uci_data.uci_pmi[1]?"1":"0"):"no", - uci_data.uci_pmi_len>0?(uci_data.uci_pmi[0]?"1":"0"):"", - uci_data.scheduling_request?"yes":"no", - cfo*15000, timestr); + ue_ul.pucch.last_n_pucch, ue_ul.pucch.last_n_prb, + uci_data.uci_ack_len>0?(uci_data.uci_ack?"1":"0"):"no", + uci_data.uci_ack_len>1?(uci_data.uci_ack_2?"1":"0"):"", + uci_data.uci_ri_len>0?(uci_data.uci_ri?"1":"0"):"no", + uci_data.uci_pmi_len>0?(uci_data.uci_pmi[1]?"1":"0"):"no", + uci_data.uci_pmi_len>0?(uci_data.uci_pmi[0]?"1":"0"):"", + uci_data.scheduling_request?"yes":"no", + cfo*15, timestr); } if (uci_data.scheduling_request) { @@ -995,7 +995,7 @@ void phch_worker::encode_srs() #ifdef LOG_EXECTIME gettimeofday(&logtime_start[2], NULL); get_time_interval(logtime_start); - snprintf(timestr, 64, ", total_time=%4d us", (int) logtime_start[0].tv_usec); + snprintf(timestr, 64, ", tot_time=%4d us", (int) logtime_start[0].tv_usec); #endif float tx_power = srslte_ue_ul_srs_power(&ue_ul, phy->pathloss); From 0c263b123fbc8c9d5db88c297d7349e80e290e93 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 14:56:25 +0200 Subject: [PATCH 34/49] Reset HARQ process if TB can't be scheduled --- srsenb/src/mac/scheduler.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srsenb/src/mac/scheduler.cc b/srsenb/src/mac/scheduler.cc index 539b7725f..801b68d38 100644 --- a/srsenb/src/mac/scheduler.cc +++ b/srsenb/src/mac/scheduler.cc @@ -619,6 +619,7 @@ int sched::dl_sched_data(dl_sched_data_t data[MAX_DATA_LIST]) tbs, user->get_pending_dl_new_data(current_tti)); } } else { + h->reset(); Warning("SCHED: Could not schedule DL DCI for rnti=0x%x, pid=%d\n", rnti, h->get_id()); } } @@ -782,6 +783,7 @@ int sched::ul_sched(uint32_t tti, srsenb::sched_interface::ul_sched_res_t* sched user->get_locations(current_cfi, sf_idx), aggr_level)) { + h->reset(); log_h->warning("SCHED: Could not schedule UL DCI rnti=0x%x, pid=%d, L=%d\n", rnti, h->get_id(), aggr_level); sched_result->pusch[nof_dci_elems].needs_pdcch = false; From 5d5e8167b7e4b5dd7ea08a952d37fd77f314a8f8 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 15:04:17 +0200 Subject: [PATCH 35/49] stop radio after radio error --- lib/src/phy/rf/rf_soapy_imp.c | 1 - srsue/src/phy/phch_recv.cc | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index e1b451412..ee646c2c2 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -383,7 +383,6 @@ double rf_soapy_set_tx_freq(void *h, double freq) fprintf(stderr, "Failed to set Tx antenna.\n"); } - char *ant = SoapySDRDevice_getAntenna(handler->device, SOAPY_SDR_TX, 0); printf("Tx antenna set to %s\n", ant); diff --git a/srsue/src/phy/phch_recv.cc b/srsue/src/phy/phch_recv.cc index dad2c82b8..718b15b81 100644 --- a/srsue/src/phy/phch_recv.cc +++ b/srsue/src/phy/phch_recv.cc @@ -171,6 +171,7 @@ void phch_recv::radio_error() { // Need to find a method to effectively reset radio, reloading the driver does not work //radio_h->reset(); + radio_h->stop(); fprintf(stdout, "Error while receiving samples. Restart srsUE\n"); exit(-1); From f4e13c4a562727ebba2943eebf2b66fc97ebe0d5 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 15:05:47 +0200 Subject: [PATCH 36/49] update readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6eada615..e588ea1fd 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ srsLTE is released under the AGPLv3 license and uses software from the OpenLTE p Common Features --------------- - * LTE Release 8 compliant + * LTE Release 8 compliant (with selected features of Release 9) * FDD configuration * Tested bandwidths: 1.4, 3, 5, 10, 15 and 20 MHz - * Transmission mode 1 (single antenna) and 2 (transmit diversity) + * Transmission mode 1 (single antenna), 2 (transmit diversity), 3 (transmit diversity/CCD) and 4 (closed-loop spatial multiplexing) * Frequency-based ZF and MMSE equalizer + * Evolved multimedia broadcast and multicast service (eMBMS) * Highly optimized Turbo Decoder available in Intel SSE4.1/AVX (+100 Mbps) and standard C (+25 Mbps) * MAC, RLC, PDCP, RRC, NAS, S1AP and GW layers * Detailed log system with per-layer log levels and hex dumps @@ -33,6 +34,7 @@ srsUE Features * Cell search and synchronization procedure for the UE * Soft USIM supporting Milenage and XOR authentication * Virtual network interface *tun_srsue* created upon network attach + * >100 Mbps DL in 20 MHz MIMO TM4 configuration in i7 Quad-Core CPU. * 75 Mbps DL in 20 MHz SISO configuration in i7 Quad-Core CPU. * 36 Mbps DL in 10 MHz SISO configuration in i5 Dual-Core CPU. @@ -65,7 +67,7 @@ We have tested the following hardware: * USRP B210 * USRP X300 * bladeRF - * limeSDR + * limeSDR (currently, only the PHY-layer examples, i.e., pdsch_enodeb/ue are supported) Build Instructions ------------------ From b2a4d93fb89f765a82c9fcf8aad1369e8199155a Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 15:07:19 +0200 Subject: [PATCH 37/49] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e588ea1fd..c891ab219 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ srsUE Features * Cell search and synchronization procedure for the UE * Soft USIM supporting Milenage and XOR authentication * Virtual network interface *tun_srsue* created upon network attach - * >100 Mbps DL in 20 MHz MIMO TM4 configuration in i7 Quad-Core CPU. + * +100 Mbps DL in 20 MHz MIMO TM4 configuration in i7 Quad-Core CPU. * 75 Mbps DL in 20 MHz SISO configuration in i7 Quad-Core CPU. * 36 Mbps DL in 10 MHz SISO configuration in i5 Dual-Core CPU. From 8dc8d8d52100d439f0990fefdfe95b0e547a6268 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 16:14:16 +0200 Subject: [PATCH 38/49] fix warning --- lib/src/phy/rf/rf_soapy_imp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/phy/rf/rf_soapy_imp.c b/lib/src/phy/rf/rf_soapy_imp.c index ee646c2c2..fef533ffc 100644 --- a/lib/src/phy/rf/rf_soapy_imp.c +++ b/lib/src/phy/rf/rf_soapy_imp.c @@ -492,7 +492,7 @@ int rf_soapy_send_timed_multi(void *h, rf_soapy_handler_t *handler = (rf_soapy_handler_t *) h; int flags = 0; const long timeoutUs = 2000; // arbitrarily chosen - long long timeNs; + long long timeNs = 0; int trials = 0; int ret = 0; int n = 0; From c50b093ca3aa51afaf53ff247205349ed66f15f5 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 16:14:58 +0200 Subject: [PATCH 39/49] update version --- cmake/modules/SRSLTEVersion.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/modules/SRSLTEVersion.cmake b/cmake/modules/SRSLTEVersion.cmake index d2ab204f5..b52e47fe5 100644 --- a/cmake/modules/SRSLTEVersion.cmake +++ b/cmake/modules/SRSLTEVersion.cmake @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -SET(SRSLTE_VERSION_MAJOR 002) -SET(SRSLTE_VERSION_MINOR 000) -SET(SRSLTE_VERSION_PATCH 000) +SET(SRSLTE_VERSION_MAJOR 17) +SET(SRSLTE_VERSION_MINOR 9) +SET(SRSLTE_VERSION_PATCH 0) SET(SRSLTE_VERSION_STRING "${SRSLTE_VERSION_MAJOR}.${SRSLTE_VERSION_MINOR}.${SRSLTE_VERSION_PATCH}") From e7407ba45b7a121a0b8a906d40d9d334d25cb9c7 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 6 Oct 2017 16:20:35 +0200 Subject: [PATCH 40/49] update changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 7dfff7d89..2acd0c4a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ Change Log for Releases ============================== +## 17.09 + * Added MIMO 2x2 in the PHY layer and srsUE (i.e. TM3/TM4) + * eMBMS support in the PHY layer + * Many bug-fixes and improved stability and performance in srsUE/srsENB + ## 002.000.000 * Added fully functional srsENB to srsLTE code * Merged srsUE code into srsLTE and reestructured PHY code From af376e6f04003a06f45045e1b2807f2c9ff8af4d Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 6 Oct 2017 16:26:22 +0200 Subject: [PATCH 41/49] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c891ab219..ad87ad149 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Common Features * LTE Release 8 compliant (with selected features of Release 9) * FDD configuration * Tested bandwidths: 1.4, 3, 5, 10, 15 and 20 MHz - * Transmission mode 1 (single antenna), 2 (transmit diversity), 3 (transmit diversity/CCD) and 4 (closed-loop spatial multiplexing) + * Transmission mode 1 (single antenna), 2 (transmit diversity), 3 (CCD) and 4 (closed-loop spatial multiplexing) * Frequency-based ZF and MMSE equalizer * Evolved multimedia broadcast and multicast service (eMBMS) - * Highly optimized Turbo Decoder available in Intel SSE4.1/AVX (+100 Mbps) and standard C (+25 Mbps) + * Highly optimized Turbo Decoder available in Intel SSE4.1/AVX2 (+100 Mbps) and standard C (+25 Mbps) * MAC, RLC, PDCP, RRC, NAS, S1AP and GW layers * Detailed log system with per-layer log levels and hex dumps * MAC layer wireshark packet capture @@ -57,6 +57,8 @@ srsENB has been tested and validated with the following handsets: * LG Nexus 5 * LG Nexus 4 * Motorola Moto G4 plus + * Huawei P9/P9lite + * Huawei dongles: E3276 and E398 Hardware -------- From 2159ad32809c19a8b99a5c1058986fe1587f57d6 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Sat, 7 Oct 2017 21:31:13 +0200 Subject: [PATCH 42/49] Added support for roaming PLMN --- lib/include/srslte/common/bcd_helpers.h | 2 +- lib/include/srslte/interfaces/ue_interfaces.h | 2 ++ srsue/hdr/upper/nas.h | 1 + srsue/hdr/upper/rrc.h | 2 +- srsue/src/phy/phch_recv.cc | 1 + srsue/src/upper/nas.cc | 34 ++++++++++++++++--- srsue/src/upper/rrc.cc | 15 ++++++-- 7 files changed, 48 insertions(+), 9 deletions(-) diff --git a/lib/include/srslte/common/bcd_helpers.h b/lib/include/srslte/common/bcd_helpers.h index 55411ae33..b696954c2 100644 --- a/lib/include/srslte/common/bcd_helpers.h +++ b/lib/include/srslte/common/bcd_helpers.h @@ -113,7 +113,7 @@ inline bool mnc_to_string(uint16_t mnc, std::string *str) *str += (mnc & 0x000F) + '0'; return true; } -inline std::string plmn_id_to_c_str(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id) { +inline std::string plmn_id_to_string(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id) { std::string mcc_str, mnc_str; mnc_to_string(plmn_id.mnc, &mnc_str); mcc_to_string(plmn_id.mcc, &mcc_str); diff --git a/lib/include/srslte/interfaces/ue_interfaces.h b/lib/include/srslte/interfaces/ue_interfaces.h index 2c29bd4d4..15ef48568 100644 --- a/lib/include/srslte/interfaces/ue_interfaces.h +++ b/lib/include/srslte/interfaces/ue_interfaces.h @@ -104,6 +104,7 @@ public: virtual uint32_t get_ul_count() = 0; virtual bool get_s_tmsi(LIBLTE_RRC_S_TMSI_STRUCT *s_tmsi) = 0; virtual void plmn_found(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id, uint16_t tracking_area_code) = 0; + virtual void plmn_search_end() = 0; }; // NAS interface for UE @@ -140,6 +141,7 @@ class rrc_interface_phy public: virtual void in_sync() = 0; virtual void out_of_sync() = 0; + virtual void earfcn_end() = 0; virtual void cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp) = 0; }; diff --git a/srsue/hdr/upper/nas.h b/srsue/hdr/upper/nas.h index 68d00ba06..7c1420128 100644 --- a/srsue/hdr/upper/nas.h +++ b/srsue/hdr/upper/nas.h @@ -91,6 +91,7 @@ public: bool get_s_tmsi(LIBLTE_RRC_S_TMSI_STRUCT *s_tmsi); void plmn_found(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id, uint16_t tracking_area_code); + void plmn_search_end(); // UE interface void attach_request(); diff --git a/srsue/hdr/upper/rrc.h b/srsue/hdr/upper/rrc.h index 8dfa7f70d..3643f76c3 100644 --- a/srsue/hdr/upper/rrc.h +++ b/srsue/hdr/upper/rrc.h @@ -177,8 +177,8 @@ private: // PHY interface void in_sync(); - void out_of_sync(); + void earfcn_end(); void cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp); // MAC interface diff --git a/srsue/src/phy/phch_recv.cc b/srsue/src/phy/phch_recv.cc index 718b15b81..c14141257 100644 --- a/srsue/src/phy/phch_recv.cc +++ b/srsue/src/phy/phch_recv.cc @@ -479,6 +479,7 @@ void phch_recv::cell_search_inc() if (cur_earfcn_index >= 0) { if (cur_earfcn_index >= (int) earfcn.size() - 1) { cur_earfcn_index = 0; + rrc->earfcn_end(); } } Info("SYNC: Cell Search idx %d/%d\n", cur_earfcn_index, earfcn.size()); diff --git a/srsue/src/upper/nas.cc b/srsue/src/upper/nas.cc index 91b35ce01..2d9da4c2e 100644 --- a/srsue/src/upper/nas.cc +++ b/srsue/src/upper/nas.cc @@ -72,7 +72,7 @@ void nas::attach_request() { nas_log->info("Starting PLMN Search...\n"); rrc->plmn_search(); } else if (plmn_selection == PLMN_SELECTED) { - nas_log->info("Selecting PLMN %s\n", plmn_id_to_c_str(current_plmn).c_str()); + nas_log->info("Selecting PLMN %s\n", plmn_id_to_string(current_plmn).c_str()); rrc->plmn_select(current_plmn); selecting_plmn = current_plmn; } @@ -96,25 +96,49 @@ RRC interface void nas::plmn_found(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id, uint16_t tracking_area_code) { - // Store PLMN if not registered + // Check if already registered for (uint32_t i=0;iinfo("Detected known PLMN %s\n", plmn_id_to_c_str(plmn_id).c_str()); + nas_log->info("Found known PLMN Id=%s\n", plmn_id_to_string(plmn_id).c_str()); if (plmn_id.mcc == home_plmn.mcc && plmn_id.mnc == home_plmn.mnc) { + nas_log->info("Connecting Home PLMN Id=%s\n", plmn_id_to_string(plmn_id).c_str()); rrc->plmn_select(plmn_id); selecting_plmn = plmn_id; } return; } } - nas_log->info("Found PLMN: Id=%s, TAC=%d\n", plmn_id_to_c_str(plmn_id).c_str(), + + // Save if new PLMN + known_plmns.push_back(plmn_id); + + nas_log->info("Found PLMN: Id=%s, TAC=%d\n", plmn_id_to_string(plmn_id).c_str(), tracking_area_code); - nas_log->console("Found PLMN: Id=%s, TAC=%d\n", plmn_id_to_c_str(plmn_id).c_str(), + nas_log->console("Found PLMN: Id=%s, TAC=%d\n", plmn_id_to_string(plmn_id).c_str(), tracking_area_code); + if (plmn_id.mcc == home_plmn.mcc && plmn_id.mnc == home_plmn.mnc) { rrc->plmn_select(plmn_id); selecting_plmn = plmn_id; } + +} + +// RRC indicates that the UE has gone through all EARFCN and finished PLMN selection +void nas::plmn_search_end() { + if (known_plmns.size() > 0) { + nas_log->info("Could not find Home PLMN Id=%s, trying to connect to PLMN Id=%s\n", + plmn_id_to_string(home_plmn).c_str(), + plmn_id_to_string(known_plmns[0]).c_str()); + + nas_log->console("Could not find Home PLMN Id=%s, trying to connect to PLMN Id=%s\n", + plmn_id_to_string(home_plmn).c_str(), + plmn_id_to_string(known_plmns[0]).c_str()); + + rrc->plmn_select(known_plmns[0]); + } else { + nas_log->info("Finished searching PLMN in current EARFCN set but no networks were found.\n"); + } } bool nas::is_attached() { diff --git a/srsue/src/upper/rrc.cc b/srsue/src/upper/rrc.cc index 1d614aed4..3ebeb97dd 100644 --- a/srsue/src/upper/rrc.cc +++ b/srsue/src/upper/rrc.cc @@ -177,7 +177,7 @@ void rrc::run_thread() { case RRC_STATE_PLMN_SELECTION: plmn_select_timeout++; if (plmn_select_timeout >= RRC_PLMN_SELECT_TIMEOUT) { - rrc_log->info("RRC PLMN Search: timeout expired. Searching again\n"); + rrc_log->info("RRC PLMN Search: timeout expired\n"); phy->cell_search_stop(); sleep(1); rrc_log->console("\nRRC PLMN Search: timeout expired. Searching again\n"); @@ -369,7 +369,7 @@ void rrc::plmn_select(LIBLTE_RRC_PLMN_IDENTITY_STRUCT plmn_id) { state = RRC_STATE_CELL_SELECTING; select_cell_timeout = 0; } else { - rrc_log->info("PLMN %s selected\n", plmn_id_to_c_str(plmn_id).c_str()); + rrc_log->info("PLMN Id=%s selected\n", plmn_id_to_string(plmn_id).c_str()); // Sort cells according to RSRP selected_plmn_id = plmn_id; @@ -431,6 +431,8 @@ void rrc::cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp) { for (uint32_t i = 0; i < current_cell->sib1.N_plmn_ids; i++) { nas->plmn_found(current_cell->sib1.plmn_id[i].id, current_cell->sib1.tracking_area_code); } + usleep(5000); + phy->cell_search_next(); } return; } @@ -454,6 +456,15 @@ void rrc::cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp) { cell.earfcn, cell.rsrp); } +// PHY indicates that has gone through all known EARFCN +void rrc::earfcn_end() { + rrc_log->info("Finished searching cells in EARFCN set while in state %s\n", rrc_state_text[state]); + + // If searching for PLMN, indicate NAS we scanned all frequencies + if (state == RRC_STATE_PLMN_SELECTION) { + nas->plmn_search_end(); + } +} From 1c677f71835e65581ce1f9223d033c36e3091841 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Sat, 7 Oct 2017 21:58:08 +0200 Subject: [PATCH 43/49] Read Home PLMN from IMSI --- lib/include/srslte/interfaces/ue_interfaces.h | 1 + srsue/hdr/upper/nas.h | 1 - srsue/hdr/upper/usim.h | 3 + srsue/src/ue.cc | 3 +- srsue/src/upper/nas.cc | 11 +++- srsue/src/upper/rrc.cc | 2 +- srsue/src/upper/usim.cc | 60 +++++++++++++++++-- 7 files changed, 69 insertions(+), 12 deletions(-) diff --git a/lib/include/srslte/interfaces/ue_interfaces.h b/lib/include/srslte/interfaces/ue_interfaces.h index 15ef48568..8561ba55c 100644 --- a/lib/include/srslte/interfaces/ue_interfaces.h +++ b/lib/include/srslte/interfaces/ue_interfaces.h @@ -54,6 +54,7 @@ class usim_interface_nas public: virtual void get_imsi_vec(uint8_t* imsi_, uint32_t n) = 0; virtual void get_imei_vec(uint8_t* imei_, uint32_t n) = 0; + virtual int get_home_plmn_id(LIBLTE_RRC_PLMN_IDENTITY_STRUCT *home_plmn_id) = 0; virtual void generate_authentication_response(uint8_t *rand, uint8_t *autn_enb, uint16_t mcc, diff --git a/srsue/hdr/upper/nas.h b/srsue/hdr/upper/nas.h index 7c1420128..e743165fb 100644 --- a/srsue/hdr/upper/nas.h +++ b/srsue/hdr/upper/nas.h @@ -95,7 +95,6 @@ public: // UE interface void attach_request(); - void deattach_request(); private: diff --git a/srsue/hdr/upper/usim.h b/srsue/hdr/upper/usim.h index bb4e394bd..fea15ba68 100644 --- a/srsue/hdr/upper/usim.h +++ b/srsue/hdr/upper/usim.h @@ -61,6 +61,7 @@ public: // NAS interface void get_imsi_vec(uint8_t* imsi_, uint32_t n); void get_imei_vec(uint8_t* imei_, uint32_t n); + int get_home_plmn_id(LIBLTE_RRC_PLMN_IDENTITY_STRUCT *home_plmn_id); void generate_authentication_response(uint8_t *rand, uint8_t *autn_enb, @@ -119,6 +120,8 @@ private: uint8_t k_asme[32]; uint8_t k_enb[32]; + bool initiated; + }; } // namespace srsue diff --git a/srsue/src/ue.cc b/srsue/src/ue.cc index 03e4b6546..92adaf8dd 100644 --- a/srsue/src/ue.cc +++ b/srsue/src/ue.cc @@ -181,11 +181,10 @@ bool ue::init(all_args_t *args_) rlc.init(&pdcp, &rrc, this, &rlc_log, &mac, 0 /* RB_ID_SRB0 */); pdcp.init(&rlc, &rrc, &gw, &pdcp_log, 0 /* RB_ID_SRB0 */, SECURITY_DIRECTION_UPLINK); + usim.init(&args->usim, &usim_log); nas.init(&usim, &rrc, &gw, &nas_log, 1 /* RB_ID_SRB1 */); gw.init(&pdcp, &nas, &gw_log, 3 /* RB_ID_DRB1 */); - usim.init(&args->usim, &usim_log); - rrc.init(&phy, &mac, &rlc, &pdcp, &nas, &usim, &mac, &rrc_log); rrc.set_ue_category(atoi(args->expert.ue_cateogry.c_str())); diff --git a/srsue/src/upper/nas.cc b/srsue/src/upper/nas.cc index 2d9da4c2e..f0fd8cf54 100644 --- a/srsue/src/upper/nas.cc +++ b/srsue/src/upper/nas.cc @@ -50,8 +50,12 @@ void nas::init(usim_interface_nas *usim_, nas_log = nas_log_; state = EMM_STATE_DEREGISTERED; plmn_selection = PLMN_NOT_SELECTED; - home_plmn.mcc = 61441; // This is 001 - home_plmn.mnc = 65281; // This is 01 + + if (usim->get_home_plmn_id(&home_plmn)) { + nas_log->error("Getting Home PLMN Id from USIM. Defaulting to 001-01\n"); + home_plmn.mcc = 61441; // This is 001 + home_plmn.mnc = 65281; // This is 01 + } cfg = cfg_; } @@ -64,6 +68,7 @@ emm_state_t nas::get_state() { /******************************************************************************* UE interface *******************************************************************************/ + void nas::attach_request() { nas_log->info("Attach Request\n"); if (state == EMM_STATE_DEREGISTERED) { @@ -137,7 +142,7 @@ void nas::plmn_search_end() { rrc->plmn_select(known_plmns[0]); } else { - nas_log->info("Finished searching PLMN in current EARFCN set but no networks were found.\n"); + nas_log->debug("Finished searching PLMN in current EARFCN set but no networks were found.\n"); } } diff --git a/srsue/src/upper/rrc.cc b/srsue/src/upper/rrc.cc index 3ebeb97dd..7b78bd774 100644 --- a/srsue/src/upper/rrc.cc +++ b/srsue/src/upper/rrc.cc @@ -458,7 +458,7 @@ void rrc::cell_found(uint32_t earfcn, srslte_cell_t phy_cell, float rsrp) { // PHY indicates that has gone through all known EARFCN void rrc::earfcn_end() { - rrc_log->info("Finished searching cells in EARFCN set while in state %s\n", rrc_state_text[state]); + rrc_log->debug("Finished searching cells in EARFCN set while in state %s\n", rrc_state_text[state]); // If searching for PLMN, indicate NAS we scanned all frequencies if (state == RRC_STATE_PLMN_SELECTION) { diff --git a/srsue/src/upper/usim.cc b/srsue/src/upper/usim.cc index 7b1f92896..418bb6f78 100644 --- a/srsue/src/upper/usim.cc +++ b/srsue/src/upper/usim.cc @@ -25,13 +25,15 @@ */ +#include #include "upper/usim.h" +#include "srslte/common/bcd_helpers.h" using namespace srslte; namespace srsue{ -usim::usim() +usim::usim() : initiated(false) {} void usim::init(usim_args_t *args, srslte::log *usim_log_) @@ -91,6 +93,7 @@ void usim::init(usim_args_t *args, srslte::log *usim_log_) if("xor" == args->algo) { auth_algo = auth_algo_xor; } + initiated = true; } void usim::stop() @@ -102,6 +105,11 @@ void usim::stop() void usim::get_imsi_vec(uint8_t* imsi_, uint32_t n) { + if (!initiated) + { + usim_log->error("Getting IMSI: USIM not initiated\n"); + return; + } if(NULL == imsi_ || n < 15) { usim_log->error("Invalid parameters to get_imsi_vec"); @@ -111,13 +119,18 @@ void usim::get_imsi_vec(uint8_t* imsi_, uint32_t n) uint64_t temp = imsi; for(int i=14;i>=0;i--) { - imsi_[i] = temp % 10; - temp /= 10; + imsi_[i] = temp % 10; + temp /= 10; } } void usim::get_imei_vec(uint8_t* imei_, uint32_t n) { + if (!initiated) + { + usim_log->error("Getting IMEI: USIM not initiated\n"); + return; + } if(NULL == imei_ || n < 15) { usim_log->error("Invalid parameters to get_imei_vec"); @@ -127,9 +140,46 @@ void usim::get_imei_vec(uint8_t* imei_, uint32_t n) uint64 temp = imei; for(int i=14;i>=0;i--) { - imei_[i] = temp % 10; - temp /= 10; + imei_[i] = temp % 10; + temp /= 10; + } +} + +int usim::get_home_plmn_id(LIBLTE_RRC_PLMN_IDENTITY_STRUCT *home_plmn_id) +{ + if (!initiated) + { + usim_log->error("Getting Home PLMN Id: USIM not initiated\n"); + return -1; } + + uint32_t mcc_len = 3; + uint32_t mnc_len = 2; + + uint8_t imsi_vec[15]; + get_imsi_vec(imsi_vec, 15); + + std::ostringstream mcc_str, mnc_str; + + for (int i=0;imcc); + string_to_mnc(mnc_str.str(), &home_plmn_id->mnc); + + usim_log->info("Read Home PLMN Id=%s\n", + plmn_id_to_string(*home_plmn_id).c_str()); + + return 0; } void usim::generate_authentication_response(uint8_t *rand, From e4529b943b228ea87609b8adb06681176b047ac2 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Sat, 7 Oct 2017 22:17:39 +0200 Subject: [PATCH 44/49] Extened to all US MCC codes --- srsue/src/upper/usim.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/srsue/src/upper/usim.cc b/srsue/src/upper/usim.cc index 418bb6f78..9be69383e 100644 --- a/srsue/src/upper/usim.cc +++ b/srsue/src/upper/usim.cc @@ -153,8 +153,8 @@ int usim::get_home_plmn_id(LIBLTE_RRC_PLMN_IDENTITY_STRUCT *home_plmn_id) return -1; } - uint32_t mcc_len = 3; - uint32_t mnc_len = 2; + int mcc_len = 3; + int mnc_len = 2; uint8_t imsi_vec[15]; get_imsi_vec(imsi_vec, 15); @@ -166,7 +166,12 @@ int usim::get_home_plmn_id(LIBLTE_RRC_PLMN_IDENTITY_STRUCT *home_plmn_id) } // US MCC uses 3 MNC digits - if (!mcc_str.str().compare("310")) { + if (!mcc_str.str().compare("310") || + !mcc_str.str().compare("311") || + !mcc_str.str().compare("312") || + !mcc_str.str().compare("313") || + !mcc_str.str().compare("316")) + { mnc_len = 3; } for (int i=mcc_len;i Date: Sun, 8 Oct 2017 00:41:39 +0200 Subject: [PATCH 45/49] Removed unused code --- srsenb/src/phy/phch_common.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/srsenb/src/phy/phch_common.cc b/srsenb/src/phy/phch_common.cc index c4b94153c..061743274 100644 --- a/srsenb/src/phy/phch_common.cc +++ b/srsenb/src/phy/phch_common.cc @@ -128,10 +128,6 @@ bool phch_common::ack_is_pending(uint32_t sf_idx, uint16_t rnti, uint32_t *last_ bool ret = pending_ack[rnti].is_pending[sf_idx]; pending_ack[rnti].is_pending[sf_idx] = false; - if (ret) { - - } - if (ret && last_n_pdcch) { *last_n_pdcch = pending_ack[rnti].n_pdcch[sf_idx]; } From 51c2c041d76038d7e099fe63c60c440dfe1b4ca9 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Sat, 7 Oct 2017 23:40:59 +0200 Subject: [PATCH 46/49] Update enb.conf.example --- srsenb/enb.conf.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index d88b63bb7..44e546116 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -123,8 +123,8 @@ enable = false #pdsch_mcs = -1 #pdsch_max_mcs = -1 #pusch_mcs = -1 -#pusch_max_mcs = -1 -nof_ctrl_symbols = 2 +pusch_max_mcs = 16 +nof_ctrl_symbols = 3 ##################################################################### # Expert configuration options From 1a5cf45ddacb16271bf70df002ff7ad881a84690 Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Mon, 9 Oct 2017 16:30:32 +0200 Subject: [PATCH 47/49] Solved compilation error for SSE (Tested in Atom) --- lib/include/srslte/phy/utils/simd.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/include/srslte/phy/utils/simd.h b/lib/include/srslte/phy/utils/simd.h index 2590794f2..08eed115f 100644 --- a/lib/include/srslte/phy/utils/simd.h +++ b/lib/include/srslte/phy/utils/simd.h @@ -738,7 +738,7 @@ typedef __m256 simd_sel_t; #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE typedef __m128i simd_i_t; -typedef __m128i simd_sel_t; +typedef __m128 simd_sel_t; #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ @@ -807,7 +807,7 @@ static inline simd_sel_t srslte_simd_f_max(simd_f_t a, simd_f_t b) { return _mm256_cmp_ps(a, b, _CMP_GT_OS); #else /* LV_HAVE_AVX2 */ #ifdef LV_HAVE_SSE - return (simd_i_t) _mm_cmpgt_ps(a, b); + return (simd_sel_t) _mm_cmpgt_ps(a, b); #endif /* LV_HAVE_SSE */ #endif /* LV_HAVE_AVX2 */ #endif /* LV_HAVE_AVX512 */ From a180b5ebacfdfa8f90add5f764608ee57be3a8fd Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Tue, 10 Oct 2017 12:06:24 +0200 Subject: [PATCH 48/49] Msg3 delay is added to harq delay --- lib/include/srslte/common/common.h | 2 +- srsenb/src/mac/scheduler.cc | 2 +- srsue/src/phy/phch_common.cc | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/include/srslte/common/common.h b/lib/include/srslte/common/common.h index fd785fbca..6372af73e 100644 --- a/lib/include/srslte/common/common.h +++ b/lib/include/srslte/common/common.h @@ -45,7 +45,7 @@ #define SRSLTE_N_RADIO_BEARERS 11 #define HARQ_DELAY_MS 4 -#define MSG3_DELAY_MS 6 +#define MSG3_DELAY_MS 2 // Delay added to HARQ_DELAY_MS #define TTI_TX(tti) ((tti+HARQ_DELAY_MS)%10240) #define TTI_RX_ACK(tti) ((tti+(2*HARQ_DELAY_MS))%10240) diff --git a/srsenb/src/mac/scheduler.cc b/srsenb/src/mac/scheduler.cc index 21d60652d..a7e3d12d6 100644 --- a/srsenb/src/mac/scheduler.cc +++ b/srsenb/src/mac/scheduler.cc @@ -541,7 +541,7 @@ int sched::dl_sched_rar(dl_sched_rar_t rar[MAX_RAR_LIST]) pending_rar[j].rar_tti = 0; // Save UL resources - uint32_t pending_tti=(current_tti+MSG3_DELAY_MS)%10; + uint32_t pending_tti=(current_tti+MSG3_DELAY_MS+HARQ_DELAY_MS)%10; pending_msg3[pending_tti].enabled = true; pending_msg3[pending_tti].rnti = pending_rar[j].rnti; pending_msg3[pending_tti].L = L_prb; diff --git a/srsue/src/phy/phch_common.cc b/srsue/src/phy/phch_common.cc index 549783fd9..7d2948c1a 100644 --- a/srsue/src/phy/phch_common.cc +++ b/srsue/src/phy/phch_common.cc @@ -137,15 +137,13 @@ void phch_common::set_rar_grant(uint32_t tti, uint8_t grant_payload[SRSLTE_RAR_G { srslte_dci_rar_grant_unpack(&rar_grant, grant_payload); rar_grant_pending = true; - int delay = MSG3_DELAY_MS-HARQ_DELAY_MS; - if (delay < 0) { - fprintf(stderr, "Error MSG3_DELAY_MS can't be lower than HARQ_DELAY_MS\n"); - delay = 0; + if (MSG3_DELAY_MS < 0) { + fprintf(stderr, "Error MSG3_DELAY_MS can't be negative\n"); } if (rar_grant.ul_delay) { - rar_grant_tti = (tti + delay + 1) % 10240; + rar_grant_tti = (tti + MSG3_DELAY_MS + 1) % 10240; } else { - rar_grant_tti = (tti + delay) % 10240; + rar_grant_tti = (tti + MSG3_DELAY_MS) % 10240; } } From fda886407b7bebe2537ee608687f74f5dae9cf81 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Tue, 10 Oct 2017 12:33:10 +0200 Subject: [PATCH 49/49] Added option to force the DL/UL frequency at the UE --- srsenb/enb.conf.example | 4 +++- srsue/hdr/phy/phch_recv.h | 5 +++++ srsue/hdr/phy/phy.h | 3 ++- srsue/src/main.cc | 2 ++ srsue/src/phy/phch_recv.cc | 29 ++++++++++++++++++++++------- srsue/src/phy/phy.cc | 5 +++++ srsue/src/ue.cc | 4 ++++ srsue/ue.conf.example | 2 ++ 8 files changed, 45 insertions(+), 9 deletions(-) diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 44e546116..7f023d0c6 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -45,7 +45,9 @@ drb_config = drb.conf # tx_gain: Transmit gain (dB). # rx_gain: Optional receive gain (dB). If disabled, AGC if enabled # -# Optional parameters: +# Optional parameters: +# dl_freq: Override DL frequency corresponding to dl_earfcn +# ul_freq: Override UL frequency corresponding to dl_earfcn (must be set if dl_freq is set) # device_name: Device driver family. Supported options: "auto" (uses first found), "UHD" or "bladeRF" # device_args: Arguments for the device driver. Options are "auto" or any string. # Default for UHD: "recv_frame_size=9232,send_frame_size=9232" diff --git a/srsue/hdr/phy/phch_recv.h b/srsue/hdr/phy/phch_recv.h index 044960760..c51622e53 100644 --- a/srsue/hdr/phy/phch_recv.h +++ b/srsue/hdr/phy/phch_recv.h @@ -53,6 +53,7 @@ public: void set_agc_enable(bool enable); void set_earfcn(std::vector earfcn); + void force_freq(float dl_freq, float ul_freq); void reset_sync(); void cell_search_start(); @@ -171,6 +172,10 @@ private: int cell_meas_rsrp(); int cell_search(int force_N_id_2 = -1); bool set_cell(); + + float dl_freq; + float ul_freq; + }; } // namespace srsue diff --git a/srsue/hdr/phy/phy.h b/srsue/hdr/phy/phy.h index 0c77360b2..07a2713fa 100644 --- a/srsue/hdr/phy/phy.h +++ b/srsue/hdr/phy/phy.h @@ -76,6 +76,7 @@ public: void write_trace(std::string filename); void set_earfcn(std::vector earfcns); + void force_freq(float dl_freq, float ul_freq); /********** RRC INTERFACE ********************/ void reset(); @@ -167,7 +168,7 @@ private: /* Current time advance */ uint32_t n_ta; - + bool init_(srslte::radio *radio_handler, mac_interface_phy *mac, srslte::log *log_h, bool do_agc, uint32_t nof_workers); void set_default_args(phy_args_t *args); bool check_args(phy_args_t *args); diff --git a/srsue/src/main.cc b/srsue/src/main.cc index 569083998..47d5f807e 100644 --- a/srsue/src/main.cc +++ b/srsue/src/main.cc @@ -65,6 +65,8 @@ void parse_args(all_args_t *args, int argc, char *argv[]) { common.add_options() ("rf.dl_earfcn", bpo::value(&args->rf.dl_earfcn)->default_value(3400), "Downlink EARFCN") ("rf.freq_offset", bpo::value(&args->rf.freq_offset)->default_value(0), "(optional) Frequency offset") + ("rf.dl_freq", bpo::value(&args->rf.dl_freq)->default_value(-1), "Downlink Frequency (if positive overrides EARFCN)") + ("rf.ul_freq", bpo::value(&args->rf.ul_freq)->default_value(-1), "Uplink Frequency (if positive overrides EARFCN)") ("rf.rx_gain", bpo::value(&args->rf.rx_gain)->default_value(-1), "Front-end receiver gain") ("rf.tx_gain", bpo::value(&args->rf.tx_gain)->default_value(-1), "Front-end transmitter gain") ("rf.nof_rx_ant", bpo::value(&args->rf.nof_rx_ant)->default_value(1), "Number of RX antennas") diff --git a/srsue/src/phy/phch_recv.cc b/srsue/src/phy/phch_recv.cc index e10c013fe..8642460aa 100644 --- a/srsue/src/phy/phch_recv.cc +++ b/srsue/src/phy/phch_recv.cc @@ -63,6 +63,8 @@ double callback_set_rx_gain(void *h, double gain) { phch_recv::phch_recv() { + dl_freq = -1; + ul_freq = -1; bzero(&cell, sizeof(srslte_cell_t)); running = false; } @@ -445,6 +447,11 @@ void phch_recv::set_earfcn(std::vector earfcn) { this->earfcn = earfcn; } +void phch_recv::force_freq(float dl_freq, float ul_freq) { + this->dl_freq = dl_freq; + this->ul_freq = ul_freq; +} + bool phch_recv::stop_sync() { wait_radio_reset(); @@ -568,17 +575,25 @@ bool phch_recv::cell_select(uint32_t earfcn, srslte_cell_t cell) { bool phch_recv::set_frequency() { - double dl_freq = 1e6*srslte_band_fd(current_earfcn); - double ul_freq = 1e6*srslte_band_fu(srslte_band_ul_earfcn(current_earfcn)); - if (dl_freq > 0 && ul_freq > 0) { + double set_dl_freq = 0; + double set_ul_freq = 0; + + if (this->dl_freq > 0 && this->ul_freq > 0) { + set_dl_freq = this->dl_freq; + set_ul_freq = this->ul_freq; + } else { + set_dl_freq = 1e6*srslte_band_fd(current_earfcn); + set_ul_freq = 1e6*srslte_band_fu(srslte_band_ul_earfcn(current_earfcn)); + } + if (set_dl_freq > 0 && set_ul_freq > 0) { log_h->info("SYNC: Set DL EARFCN=%d, f_dl=%.1f MHz, f_ul=%.1f MHz\n", - current_earfcn, dl_freq / 1e6, ul_freq / 1e6); + current_earfcn, set_dl_freq / 1e6, set_ul_freq / 1e6); log_h->console("Searching cell in DL EARFCN=%d, f_dl=%.1f MHz, f_ul=%.1f MHz\n", - current_earfcn, dl_freq / 1e6, ul_freq / 1e6); + current_earfcn, set_dl_freq / 1e6, set_ul_freq / 1e6); - radio_h->set_rx_freq(dl_freq); - radio_h->set_tx_freq(ul_freq); + radio_h->set_rx_freq(set_dl_freq); + radio_h->set_tx_freq(set_ul_freq); ul_dl_factor = radio_h->get_tx_freq()/radio_h->get_rx_freq(); srslte_ue_sync_reset(&ue_sync); diff --git a/srsue/src/phy/phy.cc b/srsue/src/phy/phy.cc index e74107661..df29726d5 100644 --- a/srsue/src/phy/phy.cc +++ b/srsue/src/phy/phy.cc @@ -332,6 +332,11 @@ void phy::set_earfcn(vector< uint32_t > earfcns) sf_recv.set_earfcn(earfcns); } +void phy::force_freq(float dl_freq, float ul_freq) +{ + sf_recv.force_freq(dl_freq, ul_freq); +} + bool phy::sync_status() { return sf_recv.status_is_sync(); diff --git a/srsue/src/ue.cc b/srsue/src/ue.cc index 92adaf8dd..3560a78fe 100644 --- a/srsue/src/ue.cc +++ b/srsue/src/ue.cc @@ -193,6 +193,10 @@ bool ue::init(all_args_t *args_) earfcn_list.push_back(args->rf.dl_earfcn); phy.set_earfcn(earfcn_list); + if (args->rf.dl_freq > 0 && args->rf.ul_freq > 0) { + phy.force_freq(args->rf.dl_freq, args->rf.ul_freq); + } + printf("Waiting PHY to initialize...\n"); phy.wait_initialize(); phy.configure_ul_params(); diff --git a/srsue/ue.conf.example b/srsue/ue.conf.example index 30c05069a..5d14d3c3c 100644 --- a/srsue/ue.conf.example +++ b/srsue/ue.conf.example @@ -9,6 +9,8 @@ # rx_gain: Optional receive gain (dB). If disabled, AGC if enabled # # Optional parameters: +# dl_freq: Override DL frequency corresponding to dl_earfcn +# ul_freq: Override UL frequency corresponding to dl_earfcn # nof_rx_ant: Number of RX antennas (Default 1, supported 1 or 2) # device_name: Device driver family. Supported options: "auto" (uses first found), "UHD" or "bladeRF" # device_args: Arguments for the device driver. Options are "auto" or any string.