From 45708029745d6fecfde093c624278d06feabc5b7 Mon Sep 17 00:00:00 2001 From: ismagom Date: Wed, 23 Apr 2014 12:36:01 +0100 Subject: [PATCH 01/15] Added license for CheckFunctionExistsMath.cmake --- cmake/modules/CheckFunctionExistsMath.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmake/modules/CheckFunctionExistsMath.cmake b/cmake/modules/CheckFunctionExistsMath.cmake index 0fbccb836..a05b431de 100644 --- a/cmake/modules/CheckFunctionExistsMath.cmake +++ b/cmake/modules/CheckFunctionExistsMath.cmake @@ -14,6 +14,16 @@ # CMAKE_REQUIRED_INCLUDES = list of include directories # CMAKE_REQUIRED_LIBRARIES = list of libraries to link +#============================================================================= +# Copyright 2002-2011 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= MACRO(CHECK_FUNCTION_EXISTS_MATH FUNCTION VARIABLE) IF("${VARIABLE}" MATCHES "^${VARIABLE}$") From 6c597355540196f1639ec781663d56bd09c1fe85 Mon Sep 17 00:00:00 2001 From: ismagom Date: Thu, 24 Apr 2014 14:22:00 +0100 Subject: [PATCH 02/15] Fixed FEC parity.h compilation error in 32-bit arch --- lte/lib/fec/src/parity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lte/lib/fec/src/parity.h b/lte/lib/fec/src/parity.h index 56689124b..dfda24f57 100644 --- a/lte/lib/fec/src/parity.h +++ b/lte/lib/fec/src/parity.h @@ -11,7 +11,7 @@ /* Determine parity of argument: 1 = odd, 0 = even */ #ifdef __i386__ static inline int parityb(unsigned char x){ - __asm__ __volatile__ ("test %1,%1;setpo %0" : "=g"(x) : "r" (x)); + __asm__ __volatile__ ("test %1,%1;setpo %0" : "=qhm" (x) : "qh" (x)); return x; } #else From 587ff2c7f80d370913a0435f72ae179acd1354e6 Mon Sep 17 00:00:00 2001 From: ismagom Date: Thu, 24 Apr 2014 14:33:58 +0100 Subject: [PATCH 03/15] Increased FFT test threshold --- lte/lib/common/test/fft_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lte/lib/common/test/fft_test.c b/lte/lib/common/test/fft_test.c index e82206eb8..2e4feff2a 100644 --- a/lte/lib/common/test/fft_test.c +++ b/lte/lib/common/test/fft_test.c @@ -122,7 +122,7 @@ int main(int argc, char **argv) { } printf("MSE=%f\n", mse); - if (mse >= 0.05) { + if (mse >= 0.07) { printf("MSE too large\n"); exit(-1); } From aad0c6c56a176005d1de913f7c41b2efc7bcb593 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Fri, 2 May 2014 10:42:42 -0400 Subject: [PATCH 04/15] cmake: adding LINK_DIRECTORIES to cmake to tell it where to find uhd if not in a normal (i.e., /usr or /usr/local) prefix. --- cuhd/lib/CMakeLists.txt | 12 +++++------- examples/CMakeLists.txt | 14 +++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cuhd/lib/CMakeLists.txt b/cuhd/lib/CMakeLists.txt index 82ad6382c..fb74eae89 100644 --- a/cuhd/lib/CMakeLists.txt +++ b/cuhd/lib/CMakeLists.txt @@ -25,19 +25,17 @@ IF(UHD_FOUND) ADD_LIBRARY(cuhd cuhd_imp.cpp cuhd_utils.c) INCLUDE_DIRECTORIES(${UHD_INCLUDE_DIRS}) + LINK_DIRECTORIES(${UHD_LIBRARY_DIRS}) TARGET_LINK_LIBRARIES(cuhd ${UHD_LIBRARIES}) - + LIBLTE_SET_PIC(cuhd) APPEND_INTERNAL_LIST(OPTIONAL_LIBS cuhd) INSTALL(TARGETS cuhd DESTINATION ${LIBRARY_DIR}) - + MESSAGE(STATUS " cuhd UHD C wrapper will be installed.") - + ELSE(UHD_FOUND) - MESSAGE(STATUS " UHD driver not found. CUHD library is not generated") + MESSAGE(STATUS " UHD driver not found. CUHD library is not generated") ENDIF(UHD_FOUND) - - - \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 21d3cdba8..117c6d47c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -38,6 +38,7 @@ target_link_libraries(ll_example lte) add_executable(synch_file synch_file.c) target_link_libraries(synch_file lte) +LINK_DIRECTORIES(${UHD_LIBRARY_DIRS}) ################################################################# # Check if UHD C-API and Graphics library are available @@ -59,8 +60,8 @@ target_link_libraries(pbch_enodeb lte) IF(${CUHD_FIND} EQUAL -1) SET_TARGET_PROPERTIES(pbch_ue PROPERTIES COMPILE_DEFINITIONS "DISABLE_UHD") SET_TARGET_PROPERTIES(pbch_enodeb PROPERTIES COMPILE_DEFINITIONS "DISABLE_UHD") -ELSE(${CUHD_FIND} EQUAL -1) - target_link_libraries(pbch_ue cuhd) +ELSE(${CUHD_FIND} EQUAL -1) + target_link_libraries(pbch_ue cuhd) target_link_libraries(pbch_enodeb cuhd) ENDIF(${CUHD_FIND} EQUAL -1) @@ -79,13 +80,13 @@ ENDIF(${GRAPHICS_FIND} EQUAL -1) ################################################################# IF(${CUHD_FIND} GREATER -1) - + add_executable(scan_rssi scan_rssi.c) target_link_libraries(scan_rssi lte cuhd ) - + add_executable(scan_pss scan_pss.c) target_link_libraries(scan_pss lte cuhd ) - + add_executable(scan_mib scan_mib.c) target_link_libraries(scan_mib lte cuhd ) @@ -93,5 +94,4 @@ IF(${CUHD_FIND} GREATER -1) ELSE(${CUHD_FIND} GREATER -1) MESSAGE(STATUS " UHD examples NOT INSTALLED: CUHD library not compiled.") -ENDIF(${CUHD_FIND} GREATER -1) - +ENDIF(${CUHD_FIND} GREATER -1) From 8bb3d759ab535e124c17323adf352c100b752795 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Fri, 2 May 2014 10:43:40 -0400 Subject: [PATCH 05/15] uhd: check for lo_locked or ref_locked sensor before trying to use them. If neither found, wait a bit to settle and return true. --- cuhd/lib/cuhd_imp.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cuhd/lib/cuhd_imp.cpp b/cuhd/lib/cuhd_imp.cpp index 88e1ce27f..2f63ddbcc 100644 --- a/cuhd/lib/cuhd_imp.cpp +++ b/cuhd/lib/cuhd_imp.cpp @@ -46,7 +46,18 @@ typedef _Complex float complex_t; bool isLocked(void *h) { cuhd_handler* handler = static_cast(h); - return handler->usrp->get_rx_sensor("lo_locked", 0).to_bool(); + std::vector mb_sensors = handler->usrp->get_mboard_sensor_names(); + std::vector rx_sensors = handler->usrp->get_rx_sensor_names(0); + if(std::find(rx_sensors.begin(), rx_sensors.end(), "lo_locked") != rx_sensors.end()) { + return handler->usrp->get_rx_sensor("lo_locked", 0).to_bool(); + } + else if(std::find(mb_sensors.begin(), mb_sensors.end(), "ref_locked") != mb_sensors.end()) { + return handler->usrp->get_mboard_sensor("ref_locked", 0).to_bool(); + } + else { + usleep(500); + return true; + } } bool cuhd_rx_wait_lo_locked(void *h) @@ -190,4 +201,3 @@ int cuhd_send(void *h, void *data, int nsamples, int blocking) { return handler->tx_stream->send(data, nsamples, md, 0.0); } } - From 07528df864b704a5e9a088965423f9ce866fdf7c Mon Sep 17 00:00:00 2001 From: agelonch Date: Mon, 12 May 2014 13:20:21 +0200 Subject: [PATCH 06/15] Fixed crc issues --- lte/include/lte/fec/crc.h | 1 + lte/lib/fec/src/crc.c | 189 +++++++++++++++++++++++--------- lte/lib/fec/test/CMakeLists.txt | 8 +- lte/lib/fec/test/crc_test.c | 12 +- lte/lib/fec/test/crc_test.h | 11 +- 5 files changed, 159 insertions(+), 62 deletions(-) diff --git a/lte/include/lte/fec/crc.h b/lte/include/lte/fec/crc.h index 5cc24ad23..010122973 100644 --- a/lte/include/lte/fec/crc.h +++ b/lte/include/lte/fec/crc.h @@ -36,6 +36,7 @@ #define LTE_CRC8 0x19B +int init_crc(int lorder, unsigned long polynom); unsigned int crc(unsigned int crc, char *bufptr, int len, int long_crc,unsigned int poly, int paste_word); diff --git a/lte/lib/fec/src/crc.c b/lte/lib/fec/src/crc.c index 6360d3537..3cbd1ad11 100644 --- a/lte/lib/fec/src/crc.c +++ b/lte/lib/fec/src/crc.c @@ -28,63 +28,152 @@ #include #include +#include +#include "lte/utils/pack.h" + +#define _WITHMALLOC unsigned int cword; +char *cdata; +const unsigned long crcinit = 0x00000000; //initial CRC value +const unsigned long crcxor = 0x00000000; //final XOR value +unsigned long crcmask; +unsigned long crchighbit; +unsigned long crctab[256]; + + + -unsigned int icrc1(unsigned int crc, unsigned short onech,int long_crc, - int left_shift,unsigned int poly) -{ - int i; - unsigned int tmp=(unsigned int) (crc ^ (onech << (long_crc >> 1) )); +void gen_crc_table(int lorder, unsigned long poly) { - for (i=0;i>long_crc), - data,long_crc,i,poly)<>long_crc; - } - - ret=cword; - if (paste_word) { - cword<<=32-long_crc; - for (i=0;i>31); - cword<<=1; - } - } - return (ret); + +unsigned long crctable (unsigned char* data, unsigned long length, int lorder) { + + // Polynom lorders of 8, 16, 24 or 32. + unsigned long crc = crcinit; + + while (length--) crc = (crc << 8) ^ crctab[ ((crc >> (lorder-8)) & 0xff) ^ *data++]; + + return((crc^crcxor)&crcmask); } +unsigned long reversecrcbit(unsigned int crc, unsigned int polynom, int lorder, int nbits) { + + unsigned long m, rmask=0x1; + + for(m=0; m>1; + else crc = crc >> 1; + } + return((crc^crcxor)&crcmask); +} + + +int init_crc(int lorder, unsigned long polynom){ + + unsigned long polyhighbit; + + // Compute bit masks for whole CRC and CRC high bit + crcmask = ((((unsigned long)1<<(lorder-1))-1)<<1)|1; + polyhighbit=0xFFFFFFFF ^ (crcmask+1); + crchighbit = (unsigned long)1<<(lorder-1); + + // Eliminate highest bit in polynom word + polynom=polynom & polyhighbit; + + // check parameters + if (lorder < 1 || lorder > 32) { + printf("ERROR, invalid order, it must be between 1..32.\n"); + return(0); + } + if (lorder%8 != 0) { + printf("ERROR, invalid order=%d, it must be 8, 16, 24 or 32.\n", lorder); + return(0); + } + if (polynom != (polynom & crcmask)) { + printf("ERROR, invalid polynom.\n"); + return(0); + } + if (crcinit != (crcinit & crcmask)) { + printf("ERROR, invalid crcinit.\n"); + return(0); + } + if (crcxor != (crcxor & crcmask)) { + printf("ERROR, invalid crcxor.\n"); + return(0); + } + // generate lookup table + gen_crc_table(lorder, polynom); + + return(1); +} + +#define MAX_LENGTH 8192 + +unsigned int crc(unsigned int crc, char *bufptr, int len, + int long_crc, unsigned int poly, int paste_word) { + + int i, len8, res8, a; +#ifdef _WITHMALLOC + char *data0, *pter; + + data0 = (char *)malloc(sizeof(char) * (len+long_crc)*2); + if (!data0) { + perror("malloc ERROR: Allocating memory for data pointer in crc() function"); + return(-1); + } +#endif +#ifndef _WITHMALLOC + char data0[MAX_LENGTH], *pter; + + if((((len+long_crc)>>3) + 1) > MAX_LENGTH){ + printf("ERROR: Not enough memory allocated\n"); + return(-1); + } +#endif + //# Pack bits into bytes + len8=(len>>3); + res8=8-(len - (len8<<3)); + if(res8>0)a=1; + else a=0; + + // Zeroed additional bits + memset((char *)(bufptr+len),0,(32)*sizeof(char)); + + for(i=0; i #include "lte.h" - #include "crc_test.h" -int num_bits = 1000, crc_length = 16; -unsigned int crc_poly = 0x11021; -unsigned int seed = 0; +int num_bits = 5000, crc_length = 24; +unsigned int crc_poly = 0x1864CFB; +unsigned int seed = 1; void usage(char *prog) { @@ -81,7 +80,7 @@ int main(int argc, char **argv) { parse_args(argc, argv); - data = malloc(sizeof(char) * (num_bits+crc_length)); + data = malloc(sizeof(char) * (num_bits+crc_length)*2); if (!data) { perror("malloc"); exit(-1); @@ -97,6 +96,9 @@ int main(int argc, char **argv) { data[i] = rand()%2; } + //Initialize crc params and tables + if(!init_crc(crc_length, crc_poly))exit(0); + // generate CRC word crc_word = crc(0, data, num_bits, crc_length, crc_poly, 1); diff --git a/lte/lib/fec/test/crc_test.h b/lte/lib/fec/test/crc_test.h index 926e79426..164032b7b 100644 --- a/lte/lib/fec/test/crc_test.h +++ b/lte/lib/fec/test/crc_test.h @@ -39,10 +39,15 @@ typedef struct { static expected_word_t expected_words[] = { - {5000, 24, LTE_CRC24A, 1, 0x4D0836}, // LTE CRC24A (36.212 Sec 5.1.1) + /* {5000, 24, LTE_CRC24A, 1, 0x4D0836}, // LTE CRC24A (36.212 Sec 5.1.1) {5000, 24, LTE_CRC24B, 1, 0x9B68F8}, // LTE CRC24B - {5000, 16, LTE_CRC16, 1, 0xBFFA}, // LTE CRC16 - {5000, 8, LTE_CRC8, 1, 0xF8}, // LTE CRC8 + {5000, 16, LTE_CRC16, 1, 0xBFFA}, // LTE CRC16: 0xBFFA + {5000, 8, LTE_CRC8, 1, 0xF8}, // LTE CRC8 0xF8 + */ + {5001, 24, LTE_CRC24A, 1, 0x1C5C97}, // LTE CRC24A (36.212 Sec 5.1.1) + {5001, 24, LTE_CRC24B, 1, 0x36D1F0}, // LTE CRC24B + {5001, 16, LTE_CRC16, 1, 0x7FF4}, // LTE CRC16: 0x7FF4 + {5001, 8, LTE_CRC8, 1, 0xF0}, // LTE CRC8 0xF8 {-1, -1, 0, 0, 0} }; From 869b842415c05725099979f01f0f5c83eb082630 Mon Sep 17 00:00:00 2001 From: ismagom Date: Mon, 12 May 2014 18:43:14 +0100 Subject: [PATCH 07/15] Added PDCCH encoder/decoder. Tested with Amarisoft eNodeB --- lte/include/lte/common/base.h | 7 +- lte/include/lte/mimo/layermap.h | 4 +- lte/include/lte/mimo/precoding.h | 4 +- lte/include/lte/modem/modem_table.h | 4 +- lte/include/lte/phch/dci.h | 75 +- lte/include/lte/phch/regs.h | 16 +- lte/include/lte/utils/bit.h | 1 + lte/include/lte/utils/debug.h | 4 +- lte/lib/common/src/lte.c | 4 +- lte/lib/mimo/src/layermap.c | 4 +- lte/lib/mimo/src/precoding.c | 4 +- lte/lib/mimo/test/layermap_test.c | 2 +- lte/lib/mimo/test/precoding_test.c | 2 +- lte/lib/modem/src/demod_soft.c | 2 +- lte/lib/phch/src/dci.c | 775 ++++++++++++++++++- lte/lib/phch/src/pdcch.c | 141 ++-- lte/lib/phch/src/regs.c | 268 +++++-- lte/lib/phch/test/CMakeLists.txt | 9 + lte/lib/phch/test/pbch_file_test.c | 3 +- lte/lib/phch/test/pdcch_test.c | 76 +- lte/lib/ratematching/src/rm_conv.c | 9 +- lte/lib/resampling/test/resample_arb_bench.c | 2 +- lte/lib/utils/src/bit.c | 8 + 23 files changed, 1177 insertions(+), 247 deletions(-) diff --git a/lte/include/lte/common/base.h b/lte/include/lte/common/base.h index ef8c33519..a1f011d2b 100644 --- a/lte/include/lte/common/base.h +++ b/lte/include/lte/common/base.h @@ -43,6 +43,7 @@ typedef enum {CPNORM, CPEXT} lte_cp_t; #define SIRNTI 0xFFFF #define PRNTI 0xFFFE +#define MRNTI 0xFFFD #define MAX_NSYMB 7 @@ -93,7 +94,7 @@ int lte_voffset(int symbol_id, int cell_id, int nof_ports); typedef enum { SINGLE_ANTENNA,TX_DIVERSITY, SPATIAL_MULTIPLEX -} mimo_type_t; +} lte_mimo_type_t; typedef enum { PHICH_NORM, PHICH_EXT} phich_length_t; typedef enum { R_1_6, R_1_2, R_1, R_2} phich_resources_t; @@ -113,8 +114,8 @@ int lte_band_get_fd_band(int band, lte_earfcn_t *earfcn, int earfcn_start, int e int lte_band_get_fd_band_all(int band, lte_earfcn_t *earfcn, int max_nelems); int lte_band_get_fd_region(enum band_geographical_area region, lte_earfcn_t *earfcn, int max_elems); -int lte_str2mimotype(char *mimo_type_str, mimo_type_t *type); -char *lte_mimotype2str(mimo_type_t type); +int lte_str2mimotype(char *mimo_type_str, lte_mimo_type_t *type); +char *lte_mimotype2str(lte_mimo_type_t type); #endif diff --git a/lte/include/lte/mimo/layermap.h b/lte/include/lte/mimo/layermap.h index 9a2f6f9bb..3a472c34a 100644 --- a/lte/include/lte/mimo/layermap.h +++ b/lte/include/lte/mimo/layermap.h @@ -38,7 +38,7 @@ int layermap_diversity(cf_t *d, cf_t *x[MAX_LAYERS], int nof_layers, int nof_sym int layermap_multiplex(cf_t *d[MAX_CODEWORDS], cf_t *x[MAX_LAYERS], int nof_cw, int nof_layers, int nof_symbols[MAX_CODEWORDS]); int layermap_type(cf_t *d[MAX_CODEWORDS], cf_t *x[MAX_LAYERS], int nof_cw, int nof_layers, - int nof_symbols[MAX_CODEWORDS], mimo_type_t type); + int nof_symbols[MAX_CODEWORDS], lte_mimo_type_t type); /* Generates the vector of data symbols "d" based on the vector of layer-mapped symbols "x" @@ -48,6 +48,6 @@ int layerdemap_diversity(cf_t *x[MAX_LAYERS], cf_t *d, int nof_layers, int nof_l int layerdemap_multiplex(cf_t *x[MAX_LAYERS], cf_t *d[MAX_CODEWORDS], int nof_layers, int nof_cw, int nof_layer_symbols, int nof_symbols[MAX_CODEWORDS]); int layerdemap_type(cf_t *x[MAX_LAYERS], cf_t *d[MAX_CODEWORDS], int nof_layers, int nof_cw, - int nof_layer_symbols, int nof_symbols[MAX_CODEWORDS], mimo_type_t type); + int nof_layer_symbols, int nof_symbols[MAX_CODEWORDS], lte_mimo_type_t type); #endif diff --git a/lte/include/lte/mimo/precoding.h b/lte/include/lte/mimo/precoding.h index ea68c5496..0a4cc18f7 100644 --- a/lte/include/lte/mimo/precoding.h +++ b/lte/include/lte/mimo/precoding.h @@ -41,7 +41,7 @@ typedef _Complex float cf_t; int precoding_single(cf_t *x, cf_t *y, int nof_symbols); int precoding_diversity(cf_t *x[MAX_LAYERS], cf_t *y[MAX_PORTS], int nof_ports, int nof_symbols); int precoding_type(cf_t *x[MAX_LAYERS], cf_t *y[MAX_PORTS], int nof_layers, int nof_ports, - int nof_symbols, mimo_type_t type); + int nof_symbols, lte_mimo_type_t type); /* Estimates the vector "x" based on the received signal "y" and the channel estimates "ce" @@ -51,6 +51,6 @@ int predecoding_diversity_zf(cf_t *y[MAX_PORTS], cf_t *ce[MAX_PORTS], cf_t *x[MAX_LAYERS], int nof_ports, int nof_symbols); int predecoding_type(cf_t *y[MAX_PORTS], cf_t *ce[MAX_PORTS], cf_t *x[MAX_LAYERS], int nof_ports, int nof_layers, int nof_symbols, - mimo_type_t type); + lte_mimo_type_t type); #endif /* PRECODING_H_ */ diff --git a/lte/include/lte/modem/modem_table.h b/lte/include/lte/modem/modem_table.h index 93a6aa230..f52bb1482 100644 --- a/lte/include/lte/modem/modem_table.h +++ b/lte/include/lte/modem/modem_table.h @@ -40,7 +40,7 @@ typedef struct { }soft_table_t; typedef struct { - cf_t* symbol_table; // bit-to-symbol mapping + cf_t* symbol_table; // bit-to-symbol mapping soft_table_t soft_table; // symbol-to-bit mapping (used in soft demodulating) int nsymbols; // number of modulation symbols int nbits_x_symbol; // number of bits per symbol @@ -49,7 +49,7 @@ typedef struct { // Modulation standards enum modem_std { - LTE_BPSK, LTE_QPSK, LTE_QAM16, LTE_QAM64 + LTE_BPSK = 1, LTE_QPSK = 2, LTE_QAM16 = 4, LTE_QAM64 = 6 }; void modem_table_init(modem_table_t* q); diff --git a/lte/include/lte/phch/dci.h b/lte/include/lte/phch/dci.h index ee9224687..06c6a6f3d 100644 --- a/lte/include/lte/phch/dci.h +++ b/lte/include/lte/phch/dci.h @@ -25,11 +25,12 @@ * */ - #ifndef DCI_ #define DCI_ #include "lte/common/base.h" +#include +#include "lte/phch/ra.h" typedef _Complex float cf_t; @@ -37,77 +38,63 @@ typedef _Complex float cf_t; * DCI message generation according to the formats, as specified in * 36.212 Section 5.3.3.1 * - * Call the function dci_init(&q) to generate a collection of DCI messages - * to be transmitted in a subframe. Each subsequent call to - * dci_add_formatXX(&q, ...) generates the DCI message and appends the data - * to the collection "q". - * */ -#define DCI_MAX_BITS 45 -typedef enum { - FORMAT0, - FORMAT1, - FORMAT1A, - /* ... */ -}dci_format_t; +#define DCI_MAX_BITS 57 typedef enum { - DCI_COMMON=0, DCI_UE=1 -}dci_spec_t; - -/** TODO: this is Release 8 */ -typedef struct { - /* 36.213 Table 8.4-2: hop_half is 0 for < 10 Mhz and 10 for > 10 Mh. - * hop_quart is 00 for > 10 Mhz and hop_quart_neg is 01 for > 10 Mhz. - */ - enum {hop_disabled, hop_half, hop_quart, hop_quart_neg, hop_type_2} freq_hop_fl; - int n_rb_ul; // number of resource blocks - int riv; // Resource Indication Value (36.213 8.1) - int mcs_and_rv; // MCS and RV value - enum {ndi_true=1, ndi_false=0} ndi; // New Data Indicator - int tpc; // Transmit Power Control - int dm_rs; // DM RS - enum {cqi_true=0, cqi_false=1} cqi_request; -}dci_format0_t; + Format0, Format1, Format1A, Format1C +} dci_format_t; +// Each type is for a different interface to packing/unpacking functions typedef struct { + enum { + PUSCH_SCHED, PDSCH_SCHED, MCCH_CHANGE, TPC_COMMAND, RA_PROC_PDCCH + } type; + dci_format_t format; +}dci_msg_type_t; -}dci_format1_t; +typedef enum { + DCI_COMMON = 0, DCI_UE = 1 +} dci_spec_t; typedef struct { unsigned char nof_bits; unsigned char L; // Aggregation level unsigned char ncce; // Position of first CCE of the dci unsigned short rnti; -}dci_candidate_t; +} dci_candidate_t; typedef struct { char data[DCI_MAX_BITS]; dci_candidate_t location; -}dci_msg_t; +} dci_msg_t; typedef struct { dci_msg_t *msg; int nof_dcis; -}dci_t; + int max_dcis; +} dci_t; - -int dci_init(dci_t *q, int nof_dci); +int dci_init(dci_t *q, int max_dci); void dci_free(dci_t *q); +char* dci_format_string(dci_format_t format); + +int dci_msg_candidate_set(dci_msg_t *msg, int L, int nCCE, unsigned short rnti); void dci_candidate_fprint(FILE *f, dci_candidate_t *q); -int dci_format0_add(dci_t *q, dci_format0_t *msg, int L, int nCCE, unsigned short rnti); -int dci_format0_sizeof(int nof_prb); +int dci_msg_get_type(dci_msg_t *msg, dci_msg_type_t *type, int nof_prb, unsigned short crnti); +void dci_msg_type_fprint(FILE *f, dci_msg_type_t type); -int dci_format1_add(dci_t *q, dci_format1_t *msg, int L, int nCCE, unsigned short rnti); -int dci_format1_sizeof(int nof_prb, int P); +// For dci_msg_type_t = PUSCH_SCHED +int dci_msg_pack_pusch(ra_pusch_t *data, dci_msg_t *msg, int nof_prb); +int dci_msg_unpack_pusch(dci_msg_t *msg, ra_pusch_t *data, int nof_prb); -int dci_format1A_add(dci_t *q, dci_format1_t *msg); -int dci_format1A_sizeof(int nof_prb, bool random_access_initiated); +// For dci_msg_type_t = PDSCH_SCHED +int dci_msg_pack_pdsch(ra_pdsch_t *data, dci_msg_t *msg, dci_format_t format, int nof_prb, bool crc_is_crnti); +int dci_msg_unpack_pdsch(dci_msg_t *msg, ra_pdsch_t *data, int nof_prb, bool crc_is_crnti); -int dci_format1C_add(dci_t *q, dci_format1_t *msg); -int dci_format1C_sizeof(); +int dci_format_sizeof(dci_format_t format, int nof_prb); #endif diff --git a/lte/include/lte/phch/regs.h b/lte/include/lte/phch/regs.h index a439e2e61..657776650 100644 --- a/lte/include/lte/phch/regs.h +++ b/lte/include/lte/phch/regs.h @@ -59,36 +59,42 @@ typedef struct { int cell_id; int nof_prb; int max_ctrl_symbols; + int cfi; int ngroups_phich; - int refs_in_symbol1; + int nof_ports; lte_cp_t cp; phich_resources_t phich_res; phich_length_t phich_len; - int nof_cce; regs_ch_t pcfich; regs_ch_t *phich; // there are several phich - regs_ch_t *pdcch; // there are several pdcch + regs_ch_t pdcch[3]; /* PDCCH indexing, permutation and interleaving is computed for + the three possible CFI value */ int nof_regs; regs_reg_t *regs; }regs_t; -int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, +int regs_init(regs_t *h, int cell_id, int nof_prb, int nof_ports, phich_resources_t phich_res, phich_length_t phich_len, lte_cp_t cp); void regs_free(regs_t *h); +int regs_set_cfi(regs_t *h, int nof_ctrl_symbols); int regs_put_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_prb); int regs_add_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_prb); int regs_get_reg(regs_reg_t *reg, cf_t *slot_symbols, cf_t *reg_data, int nof_prb); int regs_reset_reg(regs_reg_t *reg, cf_t *slot_symbols, int nof_prb); +int regs_pcfich_nregs(regs_t *h); int regs_pcfich_put(regs_t *h, cf_t pcfich_symbols[REGS_PCFICH_NSYM], cf_t *slot_symbols); -int regs_pcfich_get(regs_t *h, cf_t *slot_symbols, cf_t ch_data[REGS_PCFICH_NSYM]); +int regs_pcfich_get(regs_t *h, cf_t *slot_symbols, cf_t pcfich_symbols[REGS_PCFICH_NSYM]); +int regs_phich_nregs(regs_t *h); int regs_phich_add(regs_t *h, cf_t phich_symbols[REGS_PHICH_NSYM], int ngroup, cf_t *slot_symbols); int regs_phich_get(regs_t *h, cf_t *slot_symbols, cf_t phich_symbols[REGS_PHICH_NSYM], int ngroup); int regs_phich_ngroups(regs_t *h); int regs_phich_reset(regs_t *h, cf_t *slot_symbols); int regs_pdcch_nregs(regs_t *h); +int regs_pdcch_put(regs_t *h, cf_t *pdcch_symbols, cf_t *slot_symbols); +int regs_pdcch_get(regs_t *h, cf_t *slot_symbols, cf_t *pdcch_symbols); #endif diff --git a/lte/include/lte/utils/bit.h b/lte/include/lte/utils/bit.h index 00e2523f1..18ef23e9e 100644 --- a/lte/include/lte/utils/bit.h +++ b/lte/include/lte/utils/bit.h @@ -36,6 +36,7 @@ uint32_t bit_unpack(char **bits, int nof_bits); void bit_pack(uint32_t value, char **bits, int nof_bits); void bit_fprint(FILE *stream, char *bits, int nof_bits); unsigned int bit_diff(char *x, char *y, int nbits); +int bit_count(unsigned int n); #endif diff --git a/lte/include/lte/utils/debug.h b/lte/include/lte/utils/debug.h index f6f08d777..993bab615 100644 --- a/lte/include/lte/utils/debug.h +++ b/lte/include/lte/utils/debug.h @@ -41,8 +41,8 @@ void get_time_interval(struct timeval * tdata); extern int verbose; -#define VERBOSE_ISINFO() (verbose==VERBOSE_INFO) -#define VERBOSE_ISDEBUG() (verbose==VERBOSE_DEBUG) +#define VERBOSE_ISINFO() (verbose>=VERBOSE_INFO) +#define VERBOSE_ISDEBUG() (verbose>=VERBOSE_DEBUG) #define PRINT_DEBUG verbose=VERBOSE_DEBUG #define PRINT_INFO verbose=VERBOSE_INFO diff --git a/lte/lib/common/src/lte.c b/lte/lib/common/src/lte.c index e475cf161..7163c41da 100644 --- a/lte/lib/common/src/lte.c +++ b/lte/lib/common/src/lte.c @@ -135,7 +135,7 @@ struct lte_band lte_bands[NOF_LTE_BANDS] = { }; #define EOF_BAND 9919 -int lte_str2mimotype(char *mimo_type_str, mimo_type_t *type) { +int lte_str2mimotype(char *mimo_type_str, lte_mimo_type_t *type) { if (!strcmp(mimo_type_str, "single")) { *type = SINGLE_ANTENNA; } else if (!strcmp(mimo_type_str, "diversity")) { @@ -148,7 +148,7 @@ int lte_str2mimotype(char *mimo_type_str, mimo_type_t *type) { return 0; } -char *lte_mimotype2str(mimo_type_t type) { +char *lte_mimotype2str(lte_mimo_type_t type) { switch(type) { case SINGLE_ANTENNA: return "single"; diff --git a/lte/lib/mimo/src/layermap.c b/lte/lib/mimo/src/layermap.c index cd685dcd0..b178b953d 100644 --- a/lte/lib/mimo/src/layermap.c +++ b/lte/lib/mimo/src/layermap.c @@ -78,7 +78,7 @@ int layermap_multiplex(cf_t *d[MAX_CODEWORDS], cf_t *x[MAX_LAYERS], int nof_cw, * Returns the number of symbols per layer (M_symb^layer in the specs) */ int layermap_type(cf_t *d[MAX_CODEWORDS], cf_t *x[MAX_LAYERS], int nof_cw, int nof_layers, - int nof_symbols[MAX_CODEWORDS], mimo_type_t type) { + int nof_symbols[MAX_CODEWORDS], lte_mimo_type_t type) { if (nof_cw > MAX_CODEWORDS) { fprintf(stderr, "Maximum number of codewords is %d (nof_cw=%d)\n", MAX_CODEWORDS, nof_cw); @@ -167,7 +167,7 @@ int layerdemap_multiplex(cf_t *x[MAX_LAYERS], cf_t *d[MAX_CODEWORDS], int nof_la * nof_symbols. Returns -1 on error */ int layerdemap_type(cf_t *x[MAX_LAYERS], cf_t *d[MAX_CODEWORDS], int nof_layers, int nof_cw, - int nof_layer_symbols, int nof_symbols[MAX_CODEWORDS], mimo_type_t type) { + int nof_layer_symbols, int nof_symbols[MAX_CODEWORDS], lte_mimo_type_t type) { if (nof_cw > MAX_CODEWORDS) { fprintf(stderr, "Maximum number of codewords is %d (nof_cw=%d)\n", MAX_CODEWORDS, nof_cw); diff --git a/lte/lib/mimo/src/precoding.c b/lte/lib/mimo/src/precoding.c index dd136a01a..8cc033233 100644 --- a/lte/lib/mimo/src/precoding.c +++ b/lte/lib/mimo/src/precoding.c @@ -85,7 +85,7 @@ int precoding_diversity(cf_t *x[MAX_LAYERS], cf_t *y[MAX_PORTS], int nof_ports, /* 36.211 v10.3.0 Section 6.3.4 */ int precoding_type(cf_t *x[MAX_LAYERS], cf_t *y[MAX_PORTS], int nof_layers, int nof_ports, int nof_symbols, - mimo_type_t type) { + lte_mimo_type_t type) { if (nof_ports > MAX_PORTS) { fprintf(stderr, "Maximum number of ports is %d (nof_ports=%d)\n", MAX_PORTS, nof_ports); @@ -177,7 +177,7 @@ int predecoding_diversity_zf(cf_t *y[MAX_PORTS], cf_t *ce[MAX_PORTS], /* 36.211 v10.3.0 Section 6.3.4 */ int predecoding_type(cf_t *y[MAX_PORTS], cf_t *ce[MAX_PORTS], - cf_t *x[MAX_LAYERS], int nof_ports, int nof_layers, int nof_symbols, mimo_type_t type) { + cf_t *x[MAX_LAYERS], int nof_ports, int nof_layers, int nof_symbols, lte_mimo_type_t type) { if (nof_ports > MAX_PORTS) { fprintf(stderr, "Maximum number of ports is %d (nof_ports=%d)\n", MAX_PORTS, nof_ports); diff --git a/lte/lib/mimo/test/layermap_test.c b/lte/lib/mimo/test/layermap_test.c index 3e60737f0..0c701cc4b 100644 --- a/lte/lib/mimo/test/layermap_test.c +++ b/lte/lib/mimo/test/layermap_test.c @@ -75,7 +75,7 @@ void parse_args(int argc, char **argv) { int main(int argc, char **argv) { int i, j, num_errors, symbols_layer; cf_t *d[MAX_CODEWORDS], *x[MAX_LAYERS], *dp[MAX_CODEWORDS]; - mimo_type_t type; + lte_mimo_type_t type; int nof_symb_cw[MAX_CODEWORDS]; int n[2]; diff --git a/lte/lib/mimo/test/precoding_test.c b/lte/lib/mimo/test/precoding_test.c index 741bbd216..e913cc512 100644 --- a/lte/lib/mimo/test/precoding_test.c +++ b/lte/lib/mimo/test/precoding_test.c @@ -78,7 +78,7 @@ int main(int argc, char **argv) { int i, j; float mse; cf_t *x[MAX_LAYERS], *r[MAX_PORTS], *y[MAX_PORTS], *h[MAX_PORTS], *xr[MAX_LAYERS]; - mimo_type_t type; + lte_mimo_type_t type; parse_args(argc, argv); diff --git a/lte/lib/modem/src/demod_soft.c b/lte/lib/modem/src/demod_soft.c index 3180ff114..3b42467e3 100644 --- a/lte/lib/modem/src/demod_soft.c +++ b/lte/lib/modem/src/demod_soft.c @@ -47,7 +47,7 @@ void demod_soft_alg_set(demod_soft_t *q, enum alg alg_type) { } void demod_soft_sigma_set(demod_soft_t *q, float sigma) { - q->sigma = sigma; + q->sigma = 2*sigma; } int demod_soft_demodulate(demod_soft_t *q, const cf_t* symbols, float* llr, int nsymbols) { diff --git a/lte/lib/phch/src/dci.c b/lte/lib/phch/src/dci.c index 30d9feb93..c66abf6e1 100644 --- a/lte/lib/phch/src/dci.c +++ b/lte/lib/phch/src/dci.c @@ -41,14 +41,14 @@ #include "lte/utils/vector.h" #include "lte/utils/debug.h" - -int dci_init(dci_t *q, int nof_dcis) { - q->msg = calloc(sizeof(dci_msg_t), nof_dcis); +int dci_init(dci_t *q, int max_dcis) { + q->msg = calloc(sizeof(dci_msg_t), max_dcis); if (!q->msg) { perror("malloc"); return -1; } - q->nof_dcis = nof_dcis; + q->nof_dcis = 0; + q->max_dcis = max_dcis; return 0; } @@ -63,60 +63,755 @@ void dci_candidate_fprint(FILE *f, dci_candidate_t *q) { q->L, q->ncce, q->rnti, q->nof_bits); } -int dci_format1_add(dci_t *q, dci_format1_t *msg, int L, int nCCE, unsigned short rnti) { - int i, j; - i=0; - while(inof_dcis && q->msg[i].location.nof_bits) - i++; - if (i == q->nof_dcis) { - fprintf(stderr, "No more space in DCI container\n"); +int dci_msg_candidate_set(dci_msg_t *msg, int L, int nCCE, unsigned short rnti) { + if (L >= 0 && L <=3) { + msg->location.L = (unsigned char) L; + } else { + fprintf(stderr, "Invalid L %d\n", L); return -1; } - q->msg[i].location.L = L; - q->msg[i].location.ncce = nCCE; - q->msg[i].location.nof_bits = dci_format1_sizeof(6, 1); - q->msg[i].location.rnti = rnti; - for (j=0;jmsg[i].location.nof_bits;j++) { - q->msg[i].data[j] = rand()%2; + if (nCCE >= 0 && nCCE <= 87) { + msg->location.ncce = (unsigned char) nCCE; + } else { + fprintf(stderr, "Invalid nCCE %d\n", nCCE); + return -1; } + msg->location.rnti = rnti; return 0; } -int dci_format0_add(dci_t *q, dci_format0_t *msg, int L, int nCCE, unsigned short rnti) { - int i, j; - i=0; - while(inof_dcis && q->msg[i].location.nof_bits) - i++; - if (i == q->nof_dcis) { - fprintf(stderr, "No more space in DCI container\n"); +int riv_nbits(int nof_prb) { + return (int) ceilf(log2f((float) nof_prb*((float) nof_prb+1)/2)); +} + +const int ambiguous_sizes[10] = {12, 14, 16, 20, 24, 26, 32, 40, 44, 56}; + +bool is_ambiguous_size(int size) { + int i; + for (i=0;i<10;i++) { + if (size == ambiguous_sizes[i]) { + return true; + } + } + return false; +} + + + +/********************************** + * PAYLOAD sizeof functions + * ********************************/ +int dci_format0_sizeof_(int nof_prb) { + return 1+1+riv_nbits(nof_prb)+5+1+2+3+1; +} + + +int dci_format1A_sizeof(int nof_prb) { + int n; + n = 1+1+riv_nbits(nof_prb)+5+3+1+2+2; + while(n < dci_format0_sizeof_(nof_prb)) { + n++; + } + if (is_ambiguous_size(n)) { + n++; + } + return n; +} + + +int dci_format0_sizeof(int nof_prb) { + int n = dci_format0_sizeof_(nof_prb); + while (n < dci_format1A_sizeof(nof_prb)) { + n++; + } + return n; +} + +int dci_format1_sizeof(int nof_prb) { + + int n = (int) ceilf((float) nof_prb/ra_type0_P(nof_prb))+5+3+1+2+2; + if (nof_prb > 10) { + n++; + } + while(n == dci_format0_sizeof(nof_prb) + || n == dci_format1A_sizeof(nof_prb) + || is_ambiguous_size(n)) { + n++; + } + return n; +} + +int dci_format1C_sizeof(int nof_prb) { + int n_vrb_dl_gap1 = ra_type2_n_vrb_dl(nof_prb, true); + int n_step = ra_type2_n_rb_step(nof_prb); + int n = + riv_nbits((int) n_vrb_dl_gap1/n_step) + 5; + if (nof_prb >= 50) { + n++; + } + return n; +} + +int dci_format_sizeof(dci_format_t format, int nof_prb) { + switch(format) { + case Format0: + return dci_format0_sizeof(nof_prb); + case Format1: + return dci_format1_sizeof(nof_prb); + case Format1A: + return dci_format1A_sizeof(nof_prb); + case Format1C: + return dci_format1C_sizeof(nof_prb); + default: return -1; } - q->msg[i].location.L = L; - q->msg[i].location.ncce = nCCE; - q->msg[i].location.nof_bits = dci_format0_sizeof(msg->n_rb_ul); - q->msg[i].location.rnti = rnti; - for (j=0;jmsg[i].location.nof_bits;j++) { - q->msg[i].data[j] = rand()%2; +} + + + +/********************************** + * DCI Resource Allocation functions + * ********************************/ + + +/* Packs DCI format 0 data to a sequence of bits and store them in msg according + * to 36.212 5.3.3.1.1 + * + * TODO: TPC and cyclic shift for DM RS not implemented + */ +int dci_format0_pack(ra_pusch_t *data, dci_msg_t *msg, int nof_prb) { + + /* pack bits */ + char *y = msg->data; + int n_ul_hop; + + *y++ = 0; // format differentiation + if (data->freq_hop_fl == hop_disabled) { // frequency hopping + *y++ = 0; + n_ul_hop = 0; + } else { + *y++ = 1; + if (nof_prb < 50) { + n_ul_hop = 1; // Table 8.4-1 of 36.213 + *y++ = data->freq_hop_fl & 1; + } else { + n_ul_hop = 2; // Table 8.4-1 of 36.213 + *y++ = (data->freq_hop_fl & 2) >> 1; + *y++ = data->freq_hop_fl & 1; + } } + + /* pack RIV according to 8.1 of 36.213 */ + uint32_t riv; + if (data->type2_alloc.L_crb) { + riv = ra_type2_to_riv(data->type2_alloc.L_crb, data->type2_alloc.RB_start, nof_prb); + } else { + riv = data->type2_alloc.riv; + } + bit_pack(riv, &y, riv_nbits(nof_prb) - n_ul_hop); + + /* pack MCS according to 8.6.1 of 36.213 */ + uint32_t mcs; + if (data->cqi_request) { + mcs = 29; + } else { + if (data->rv_idx) { + mcs = 28 + data->rv_idx; + } else { + if (data->mcs.mod == MOD_NULL) { + mcs = data->mcs.mcs_idx; + } else { + if (data->mcs.tbs) { + if (data->mcs.tbs) { + data->mcs.tbs_idx = ra_tbs_to_table_idx(data->mcs.tbs, ra_nprb_ul(data, nof_prb)); + } + } + mcs = ra_mcs_to_table_idx(&data->mcs); + } + } + } + + bit_pack(mcs, &y, 5); + + *y++ = data->ndi; + + // TCP commands not implemented + *y++ = 0; + *y++ = 0; + + // DM RS not implemented + *y++ = 0; + *y++ = 0; + *y++ = 0; + + // CQI request + *y++ = data->cqi_request; + + // Padding with zeros + int n = dci_format0_sizeof(nof_prb); + while (y-msg->data < n) { + *y++ = 0; + } + msg->location.nof_bits = (y - msg->data); return 0; } +/* Unpacks DCI format 0 data and store result in msg according + * to 36.212 5.3.3.1.1 + * + * TODO: TPC and cyclic shift for DM RS not implemented + */ +int dci_format0_unpack(dci_msg_t *msg, ra_pusch_t *data, int nof_prb) { -int dci_format0_sizeof(int nof_prb) { - return 1+1+(int) ceilf(log2f(nof_prb*(nof_prb+1)/2))+2+3+1; + /* pack bits */ + char *y = msg->data; + int n_ul_hop; + + /* Make sure it's a Format0 message */ + if (msg->location.nof_bits != dci_format_sizeof(Format0, nof_prb)) { + fprintf(stderr, "Invalid message length for format 0\n"); + return -1; + } + if (*y++ != 0) { + fprintf(stderr, "Invalid format differentiation field value. This is Format1A\n"); + return -1; + } + if (*y++ == 0) { + data->freq_hop_fl = hop_disabled; + n_ul_hop = 0; + } else { + if (nof_prb < 50) { + n_ul_hop = 1; // Table 8.4-1 of 36.213 + data->freq_hop_fl = *y++; + } else { + n_ul_hop = 2; // Table 8.4-1 of 36.213 + data->freq_hop_fl = y[0]<<1 | y[1]; + y += 2; + } + } + /* unpack RIV according to 8.1 of 36.213 */ + uint32_t riv = bit_unpack(&y, riv_nbits(nof_prb) - n_ul_hop); + ra_type2_from_riv(riv, &data->type2_alloc.L_crb, &data->type2_alloc.RB_start, nof_prb, nof_prb); + bit_pack(riv, &y, riv_nbits(nof_prb) - n_ul_hop); + data->type2_alloc.riv = riv; + + /* unpack MCS according to 8.6 of 36.213 */ + uint32_t mcs = bit_unpack(&y, 5); + + data->ndi = *y++?true:false; + + // TCP and DM RS commands not implemented + y+= 5; + + // CQI request + data->cqi_request = *y++?true:false; + + // 8.6.2 First paragraph + if (mcs <= 28) { + ra_mcs_from_idx_ul(mcs, &data->mcs); + data->mcs.tbs = ra_tbs_from_idx(data->mcs.tbs_idx, ra_nprb_ul(data, nof_prb)); + } + + // 8.6.1 and 8.6.2 36.213 second paragraph + if (mcs == 29 && data->cqi_request && ra_nprb_ul(data, nof_prb) <= 4) { + data->mcs.mod = QPSK; + } + if (mcs > 29) { + // Else leave MOD_NULL and use the previously used PUSCH modulation + data->mcs.mod = MOD_NULL; + data->rv_idx = mcs - 28; + } + + return 0; +} + +/* Packs DCI format 1 data to a sequence of bits and store them in msg according + * to 36.212 5.3.3.1.2 + * + * TODO: TPC commands + */ + +int dci_format1_pack(ra_pdsch_t *data, dci_msg_t *msg, int nof_prb) { + + /* pack bits */ + char *y = msg->data; + + if (nof_prb > 10) { + *y++ = data->alloc_type; + } + + /* Resource allocation: type0 or type 1 */ + int P = ra_type0_P(nof_prb); + int alloc_size = (int) ceilf((float) nof_prb/P); + switch(data->alloc_type) { + case alloc_type0: + bit_pack(data->type0_alloc.rbg_bitmask, &y, alloc_size); + break; + case alloc_type1: + bit_pack(data->type1_alloc.rbg_subset, &y, (int) ceilf(log2f(P))); + *y++ = data->type1_alloc.shift?1:0; + bit_pack(data->type1_alloc.vrb_bitmask, &y, alloc_size - (int) ceilf(log2f(P)) - 1); + break; + default: + fprintf(stderr, "Format 1 accepts type0 or type1 resource allocation only\n"); + return -1; + + } + /* pack MCS according to 7.1.7 of 36.213 */ + uint32_t mcs; + if (data->mcs.mod == MOD_NULL) { + mcs = data->mcs.mcs_idx; + } else { + if (data->mcs.tbs) { + data->mcs.tbs_idx = ra_tbs_to_table_idx(data->mcs.tbs, ra_nprb_dl(data, nof_prb)); + } + mcs = ra_mcs_to_table_idx(&data->mcs); + } + bit_pack(mcs, &y, 5); + + /* harq process number */ + bit_pack(data->harq_process, &y, 3); + + *y++ = data->ndi; + + // rv version + bit_pack(data->rv_idx, &y, 2); + + // TPC not implemented + *y++ = 0; + *y++ = 0; + + // Padding with zeros + int n = dci_format1_sizeof(nof_prb); + while (y-msg->data < n) { + *y++ = 0; + } + msg->location.nof_bits = (y - msg->data); + + return 0; +} + +int dci_format1_unpack(dci_msg_t *msg, ra_pdsch_t *data, int nof_prb) { + + /* pack bits */ + char *y = msg->data; + + /* Make sure it's a Format1 message */ + if (msg->location.nof_bits != dci_format_sizeof(Format1, nof_prb)) { + fprintf(stderr, "Invalid message length for format 1\n"); + return -1; + } + + if (nof_prb > 10) { + data->alloc_type = *y++; + } else { + data->alloc_type = alloc_type0; + } + + /* Resource allocation: type0 or type 1 */ + int P = ra_type0_P(nof_prb); + int alloc_size = (int) ceilf((float) nof_prb/P); + switch(data->alloc_type) { + case alloc_type0: + data->type0_alloc.rbg_bitmask = bit_unpack(&y, alloc_size); + break; + case alloc_type1: + data->type1_alloc.rbg_subset = bit_unpack(&y, (int) ceilf(log2f(P))); + data->type1_alloc.shift = *y++?true:false; + data->type1_alloc.vrb_bitmask = bit_unpack(&y, alloc_size - (int) ceilf(log2f(P)) - 1); + break; + default: + fprintf(stderr, "Format 1 accepts type0 or type1 resource allocation only\n"); + return -1; + + } + /* pack MCS according to 7.1.7 of 36.213 */ + uint32_t mcs = bit_unpack(&y, 5); + data->mcs.mcs_idx = mcs; + ra_mcs_from_idx_dl(mcs, &data->mcs); + data->mcs.tbs = ra_tbs_from_idx(data->mcs.tbs_idx, ra_nprb_dl(data, nof_prb)); + + /* harq process number */ + data->harq_process = bit_unpack(&y, 3); + + data->ndi = *y++?true:false; + + // rv version + data->rv_idx = bit_unpack(&y, 2); + + // TPC not implemented + + + return 0; } -int dci_format1_sizeof(int nof_prb, int P) { - return (nof_prb>10)?1:0+(int) ceilf(log2f(nof_prb/P))+5+3+1+2+2; + +/* Packs DCI format 1A for compact scheduling of PDSCH words according to 36.212 5.3.3.1.3 + * + * TODO: RA procedure initiated by PDCCH, TPC commands + */ +int dci_format1As_pack(ra_pdsch_t *data, dci_msg_t *msg, int nof_prb, bool crc_is_crnti) { + + /* pack bits */ + char *y = msg->data; + + *y++ = 1; // format differentiation + + if (data->alloc_type != alloc_type2) { + fprintf(stderr, "Format 1A accepts type2 resource allocation only\n"); + return -1; + } + + *y++ = data->type2_alloc.mode; // localized or distributed VRB assignment + + if (data->type2_alloc.mode == t2_loc) { + if (data->type2_alloc.L_crb > nof_prb) { + fprintf(stderr, "L_CRB=%d can not exceed system BW for localized type2\n", data->type2_alloc.L_crb); + return -1; + } + } else { + int n_vrb_dl; + if (crc_is_crnti && nof_prb > 50) { + n_vrb_dl = 16; + } else { + n_vrb_dl = ra_type2_n_vrb_dl(nof_prb, data->type2_alloc.n_gap==t2_ng1); + } + if (data->type2_alloc.L_crb > n_vrb_dl) { + fprintf(stderr, "L_CRB=%d can not exceed N_vrb_dl=%d for distributed type2\n", data->type2_alloc.L_crb, n_vrb_dl); + return -1; + } + } + /* pack RIV according to 7.1.6.3 of 36.213 */ + uint32_t riv; + if (data->type2_alloc.L_crb) { + riv = ra_type2_to_riv(data->type2_alloc.L_crb, data->type2_alloc.RB_start, nof_prb); + } else { + riv = data->type2_alloc.riv; + } + int nb_gap = 0; + if (crc_is_crnti && data->type2_alloc.mode == t2_dist && nof_prb >= 50) { + nb_gap = 1; + *y++ = data->type2_alloc.n_gap; + } + bit_pack(riv, &y, riv_nbits(nof_prb)-nb_gap); + + // in format1A, MCS = TBS according to 7.1.7.2 of 36.213 + uint32_t mcs; + if (data->mcs.mod == MOD_NULL) { + mcs = data->mcs.mcs_idx; + } else { + if (data->mcs.tbs) { + // In format 1A, n_prb_1a is 2 or 3 if crc is not scrambled with C-RNTI + int n_prb; + if (!crc_is_crnti) { + n_prb = ra_nprb_dl(data, nof_prb); + } else { + n_prb = data->type2_alloc.n_prb1a==nprb1a_2?2:3; + } + data->mcs.tbs_idx = ra_tbs_to_table_idx(data->mcs.tbs, n_prb); + } + mcs = data->mcs.tbs_idx; + } + bit_pack(mcs, &y, 5); + + bit_pack(data->harq_process, &y, 3); + + if (!crc_is_crnti && nof_prb >= 50 && data->type2_alloc.mode == t2_dist) { + *y++ = data->type2_alloc.n_gap; + } else { + y++; // bit reserved + } + + // rv version + bit_pack(data->rv_idx, &y, 2); + + if (crc_is_crnti) { + // TPC not implemented + *y++ = 0; + *y++ = 0; + } else { + y++; // MSB of TPC is reserved + *y++ = data->type2_alloc.n_prb1a; // LSB indicates N_prb_1a for TBS + } + + // Padding with zeros + int n = dci_format1A_sizeof(nof_prb); + while (y-msg->data < n) { + *y++ = 0; + } + msg->location.nof_bits = (y - msg->data); + + return 0; } -int dci_format1A_sizeof(int nof_prb, bool random_access_initiated) { - if (random_access_initiated) { - return 1+(int) ceilf(log2f(nof_prb*(nof_prb+1)/2))+6+4; +/* Unpacks DCI format 1A for compact scheduling of PDSCH words according to 36.212 5.3.3.1.3 + * + */ +int dci_format1As_unpack(dci_msg_t *msg, ra_pdsch_t *data, int nof_prb, bool crc_is_crnti) { + + /* pack bits */ + char *y = msg->data; + + /* Make sure it's a Format0 message */ + if (msg->location.nof_bits != dci_format_sizeof(Format1A, nof_prb)) { + fprintf(stderr, "Invalid message length for format 1A\n"); + return -1; + } + if (*y++ != 1) { + fprintf(stderr, "Invalid format differentiation field value. This is Format0\n"); + return -1; + } + + data->alloc_type = alloc_type2; + data->type2_alloc.mode = *y++; + + // by default, set N_gap to 1 + data->type2_alloc.n_gap = t2_ng1; + + /* unpack RIV according to 7.1.6.3 of 36.213 */ + int nb_gap = 0; + if (crc_is_crnti && data->type2_alloc.mode == t2_dist && nof_prb >= 50) { + nb_gap = 1; + data->type2_alloc.n_gap = *y++; + } + int nof_vrb; + if (data->type2_alloc.mode == t2_loc) { + nof_vrb = nof_prb; } else { - return 1+(int) ceilf(log2f(nof_prb*(nof_prb+1)/2))+5+3+1+2+2; + nof_vrb = ra_type2_n_vrb_dl(nof_prb, data->type2_alloc.n_gap == t2_ng1); } + uint32_t riv = bit_unpack(&y, riv_nbits(nof_prb) - nb_gap); + ra_type2_from_riv(riv, &data->type2_alloc.L_crb, &data->type2_alloc.RB_start, nof_prb, nof_vrb); + data->type2_alloc.riv = riv; + + // unpack MCS + data->mcs.mcs_idx = bit_unpack(&y, 5); + + data->harq_process = bit_unpack(&y, 3); + + if (!crc_is_crnti && nof_prb >= 50 && data->type2_alloc.mode == t2_dist) { + data->type2_alloc.n_gap = *y++; + } else { + y++; // bit reserved + } + + // rv version + bit_pack(data->rv_idx, &y, 2); + + if (crc_is_crnti) { + // TPC not implemented + y++; + y++; + } else { + y++; // MSB of TPC is reserved + *y++ = data->type2_alloc.n_prb1a; // LSB indicates N_prb_1a for TBS + } + data->mcs.tbs_idx = data->mcs.mcs_idx; + int n_prb; + if (crc_is_crnti) { + n_prb = ra_nprb_dl(data, nof_prb); + } else { + n_prb = data->type2_alloc.n_prb1a==nprb1a_2?2:3; + } + data->mcs.tbs = ra_tbs_from_idx(data->mcs.tbs_idx, n_prb); + data->mcs.mod = QPSK; + + return 0; } -int dci_format1C_sizeof() { - return 10; +/* Format 1C for compact scheduling of PDSCH words + * + */ +int dci_format1Cs_pack(ra_pdsch_t *data, dci_msg_t *msg, int nof_prb) { + + /* pack bits */ + char *y = msg->data; + + if (data->alloc_type != alloc_type2 || data->type2_alloc.mode != t2_dist) { + fprintf(stderr, "Format 1C accepts distributed type2 resource allocation only\n"); + return -1; + } + + if (nof_prb >= 50) { + *y++ = data->type2_alloc.n_gap; + } + int n_step = ra_type2_n_rb_step(nof_prb); + int n_vrb_dl = ra_type2_n_vrb_dl(nof_prb, data->type2_alloc.n_gap==t2_ng1); + + if (data->type2_alloc.L_crb > ((int) n_vrb_dl/n_step)*n_step) { + fprintf(stderr, "L_CRB=%d can not exceed N_vrb_dl=%d for distributed type2\n", data->type2_alloc.L_crb, + ((int) n_vrb_dl/n_step)*n_step); + return -1; + } + if (data->type2_alloc.L_crb % n_step) { + fprintf(stderr, "L_crb must be multiple of n_step\n"); + return -1; + } + if (data->type2_alloc.RB_start % n_step) { + fprintf(stderr, "RB_start must be multiple of n_step\n"); + return -1; + } + int L_p = data->type2_alloc.L_crb/n_step; + int RB_p = data->type2_alloc.RB_start/n_step; + int n_vrb_p = (int) n_vrb_dl / n_step; + + uint32_t riv; + if (data->type2_alloc.L_crb) { + riv = ra_type2_to_riv(L_p, RB_p, n_vrb_p); + } else { + riv = data->type2_alloc.riv; + } + bit_pack(riv, &y, riv_nbits((int) n_vrb_dl/n_step)); + + // in format1C, MCS = TBS according to 7.1.7.2 of 36.213 + uint32_t mcs; + if (data->mcs.mod == MOD_NULL) { + mcs = data->mcs.mcs_idx; + } else { + if (data->mcs.tbs) { + data->mcs.tbs_idx = ra_tbs_to_table_idx_format1c(data->mcs.tbs); + } + mcs = data->mcs.tbs_idx; + } + bit_pack(mcs, &y, 5); + + msg->location.nof_bits = (y - msg->data); + + return 0; +} + +int dci_format1Cs_unpack(dci_msg_t *msg, ra_pdsch_t *data, int nof_prb) { + uint16_t L_p, RB_p; + + /* pack bits */ + char *y = msg->data; + + if (msg->location.nof_bits != dci_format_sizeof(Format1C, nof_prb)) { + fprintf(stderr, "Invalid message length for format 1C\n"); + return -1; + } + data->alloc_type = alloc_type2; + data->type2_alloc.mode = t2_dist; + if (nof_prb >= 50) { + data->type2_alloc.n_gap = *y++; + } + int n_step = ra_type2_n_rb_step(nof_prb); + int n_vrb_dl = ra_type2_n_vrb_dl(nof_prb, data->type2_alloc.n_gap==t2_ng1); + + uint32_t riv = bit_unpack(&y, riv_nbits((int) n_vrb_dl/n_step)); + int n_vrb_p = (int) n_vrb_dl / n_step; + + ra_type2_from_riv(riv, &L_p, &RB_p, n_vrb_p, n_vrb_p); + data->type2_alloc.L_crb = L_p * n_step; + data->type2_alloc.RB_start = RB_p * n_step; + data->type2_alloc.riv = riv; + + data->mcs.mcs_idx = bit_unpack(&y, 5); + data->mcs.tbs_idx = data->mcs.mcs_idx; + data->mcs.tbs = ra_tbs_from_idx_format1c(data->mcs.tbs_idx); + data->mcs.mod = QPSK; + + msg->location.nof_bits = (y - msg->data); + + return 0; +} + +int dci_msg_pack_pdsch(ra_pdsch_t *data, dci_msg_t *msg, dci_format_t format, int nof_prb, bool crc_is_crnti) { + switch(format) { + case Format1: + return dci_format1_pack(data, msg, nof_prb); + case Format1A: + return dci_format1As_pack(data, msg, nof_prb, crc_is_crnti); + case Format1C: + return dci_format1Cs_pack(data, msg, nof_prb); + default: + fprintf(stderr, "Invalid DCI format %s for PDSCH resource allocation\n", dci_format_string(format)); + return -1; + } +} + +int dci_msg_unpack_pdsch(dci_msg_t *msg, ra_pdsch_t *data, int nof_prb, bool crc_is_crnti) { + if (msg->location.nof_bits == dci_format_sizeof(Format1, nof_prb)) { + return dci_format1_unpack(msg, data, nof_prb); + } else if (msg->location.nof_bits == dci_format_sizeof(Format1A, nof_prb)) { + return dci_format1As_unpack(msg, data, nof_prb, crc_is_crnti); + } else if (msg->location.nof_bits == dci_format_sizeof(Format1C, nof_prb)) { + return dci_format1Cs_unpack(msg, data, nof_prb); + } else { + return -1; + } } + +int dci_msg_pack_pusch(ra_pusch_t *data, dci_msg_t *msg, int nof_prb) { + return dci_format0_pack(data, msg, nof_prb); +} + +int dci_msg_unpack_pusch(dci_msg_t *msg, ra_pusch_t *data, int nof_prb) { + return dci_format0_unpack(msg, data, nof_prb); +} + +char* dci_format_string(dci_format_t format) { + switch(format) { + case Format0: + return "Format0"; + case Format1: + return "Format1"; + case Format1A: + return "Format1A"; + case Format1C: + return "Format1C"; + default: + return "N/A"; // fatal error + } +} + +void dci_msg_type_fprint(FILE *f, dci_msg_type_t type) { + switch(type.type) { + case PUSCH_SCHED: + fprintf(f,"%s PUSCH Scheduling\n", dci_format_string(type.format)); + break; + case PDSCH_SCHED: + fprintf(f,"%s PDSCH Scheduling\n", dci_format_string(type.format)); + break; + case RA_PROC_PDCCH: + fprintf(f,"%s Random access initiated by PDCCH\n", dci_format_string(type.format)); + break; + case MCCH_CHANGE: + fprintf(f,"%s MCCH change notification\n", dci_format_string(type.format)); + break; + case TPC_COMMAND: + fprintf(f,"%s TPC command\n", dci_format_string(type.format)); + break; + } +} + +int dci_msg_get_type(dci_msg_t *msg, dci_msg_type_t *type, int nof_prb, unsigned short crnti) { + if (msg->location.nof_bits == dci_format_sizeof(Format0, nof_prb) + && !msg->data[0]) { + type->type = PUSCH_SCHED; + type->format = Format0; + return 0; + } else if (msg->location.nof_bits == dci_format_sizeof(Format1, nof_prb)) { + type->type = PDSCH_SCHED; // only these 2 types supported + type->format = Format1; + return 0; + } else if (msg->location.nof_bits == dci_format_sizeof(Format1A, nof_prb)) { + if (msg->location.rnti == crnti) { + type->type = RA_PROC_PDCCH; + type->format = Format1A; + } else { + type->type = PDSCH_SCHED; // only these 2 types supported + type->format = Format1A; + } + return 0; + } else if (msg->location.nof_bits == dci_format_sizeof(Format1C, nof_prb)) { + if (msg->location.rnti == MRNTI) { + type->type = MCCH_CHANGE; + type->format = Format1C; + } else { + type->type = PDSCH_SCHED; // only these 2 types supported + type->format = Format1C; + } + return 0; + } + return -1; +} + diff --git a/lte/lib/phch/src/pdcch.c b/lte/lib/phch/src/pdcch.c index 1dc8463ca..a98b6a47c 100644 --- a/lte/lib/phch/src/pdcch.c +++ b/lte/lib/phch/src/pdcch.c @@ -47,15 +47,11 @@ #define PDCCH_FORMAT_NOF_REGS(i) ((1<b)?b:a) @@ -63,18 +59,18 @@ int pdcch_get(cf_t *slot1_data, cf_t *pdcch, int nsymbols) { * 36.213 9.1 */ int gen_common_search(dci_candidate_t *c, int nof_cce, int nof_bits, unsigned short rnti) { - int i, L, k; + int i, l, L, k; k = 0; - for (L = 2; L > 0; L--) { - for (i = 0; i < MIN(nof_cce,16) / (4 * L); i++) { - c[k].L = 4 * L; + for (l = 3; l > 1; l--) { + L = (1 << l); + for (i = 0; i < MIN(nof_cce,16) / (L); i++) { + c[k].L = l; c[k].nof_bits = nof_bits; c[k].rnti = rnti; - c[k].ncce = (4 * L) * (i % (nof_cce / (4 * L))); - k++; - INFO( - "Common SS Candidate %d: RNTI: 0x%x, nCCE: %d, Nbits: %d, L: %d\n", + c[k].ncce = (L) * (i % (nof_cce / (L))); + INFO("Common SS Candidate %d: RNTI: 0x%x, nCCE: %d, Nbits: %d, L: %d\n", k, c[k].rnti, c[k].ncce, c[k].nof_bits, c[k].L); + k++; } } return k; @@ -88,6 +84,11 @@ int gen_ue_search(dci_candidate_t *c, int nof_cce, int nof_bits, unsigned short unsigned int Yk; const int S[4] = { 6, 12, 8, 16 }; k = 0; + if (!subframe) { + INFO("UE-specific candidates for RNTI: 0x%x, NofBits: %d, NofCCE: %d\n", + rnti, nof_bits, nof_cce); + if (VERBOSE_ISINFO()) printf("[INFO]: "); + } for (l = 3; l >= 0; l--) { L = (1 << l); for (i = 0; i < MIN(nof_cce/L,16/S[l]); i++) { @@ -99,27 +100,35 @@ int gen_ue_search(dci_candidate_t *c, int nof_cce, int nof_bits, unsigned short Yk = (39827 * Yk) % 65537; } c[k].ncce = L * ((Yk + i) % (nof_cce / L)); - INFO("UE-specific SS Candidate %d: SF: %d, RNTI: 0x%x, nCCE: %d, Nbits: %d, L: %d\n", - k, subframe, c[k].rnti, c[k].ncce, c[k].nof_bits, c[k].L); + if (!subframe) { + if (VERBOSE_ISINFO()) { + printf("(%d, %d), ", c[k].ncce, c[k].L); + } + } k++; } } + if (!subframe) { + if (VERBOSE_ISINFO()) printf("\n"); + } return k; } + void pdcch_init_common(pdcch_t *q, pdcch_search_t *s, unsigned short rnti) { - int k; - s->nof_candidates = 2*(MIN(q->nof_cce,16) / 4 + MIN(q->nof_cce,16) / 8); + int k, i; + s->nof_candidates = NOF_COMMON_FORMATS*(MIN(q->nof_cce,16) / 4 + MIN(q->nof_cce,16) / 8); if (s->nof_candidates) { s->candidates[0] = malloc(sizeof(dci_candidate_t) * s->nof_candidates); dci_candidate_t *c = s->candidates[0]; - + s->nof_candidates = 0; if (c) { // Format 1A and 1C L=4 and L=8, 4 and 2 candidates, only if nof_cce > 16 k = 0; - k += gen_common_search(&c[k], q->nof_cce, - dci_format1A_sizeof(q->nof_prb, true), SIRNTI); - k += gen_common_search(&c[k], q->nof_cce, - dci_format1C_sizeof(q->nof_prb), SIRNTI); + for(i=0;inof_cce, + dci_format_sizeof(common_formats[i], q->nof_prb), SIRNTI); + s->nof_candidates++; + } } } } @@ -137,11 +146,11 @@ void pdcch_init_search_si(pdcch_t *q) { * DCI Format 1A and 1 + PUSCH scheduling format 0 */ void pdcch_init_search_ue(pdcch_t *q, unsigned short c_rnti) { - int l, n, k; + int l, n, k, i; pdcch_search_t *s = &q->search_mode[SEARCH_UE]; s->nof_candidates = 0; for (l=0;l<3;l++) { - s->nof_candidates += 3*(MIN(q->nof_cce,16) / (1<nof_candidates += NOF_UE_FORMATS*(MIN(q->nof_cce,16) / (1<nof_candidates, c_rnti); if (s->nof_candidates) { @@ -152,12 +161,10 @@ void pdcch_init_search_ue(pdcch_t *q, unsigned short c_rnti) { if (c) { // Expect Formats 1, 1A, 0 k = 0; - k += gen_ue_search(&c[k], q->nof_cce, - dci_format0_sizeof(q->nof_prb), c_rnti, n); - k += gen_ue_search(&c[k], q->nof_cce, - dci_format1_sizeof(q->nof_prb, 1), c_rnti, n); - k += gen_ue_search(&c[k], q->nof_cce, - dci_format1A_sizeof(q->nof_prb, true), c_rnti, n); + for(i=0;inof_cce, + dci_format_sizeof(ue_formats[i], q->nof_prb), c_rnti, n); + } } } } @@ -200,15 +207,16 @@ int pdcch_init(pdcch_t *q, regs_t *regs, int nof_prb, int nof_ports, q->cp = cp; q->regs = regs; q->nof_ports = nof_ports; + q->nof_prb = nof_prb; q->current_search_mode = SEARCH_NONE; - q->nof_regs = regs_pdcch_nregs(q->regs); + q->nof_regs = (regs_pdcch_nregs(q->regs)/9)*9; q->nof_cce = q->nof_regs / 9; q->nof_symbols = 4 * q->nof_regs; q->nof_bits = 2 * q->nof_symbols; - INFO("Init PDCCH: %d REGs, %d bits, %d symbols, %d ports\n", q->nof_regs, - q->nof_bits, q->nof_symbols, q->nof_ports); + INFO("Init PDCCH: %d CCEs (%d REGs), %d bits, %d symbols, %d ports\n", q->nof_cce, + q->nof_regs, q->nof_bits, q->nof_symbols, q->nof_ports); if (modem_table_std(&q->mod, LTE_QPSK, true)) { goto clean; @@ -314,11 +322,18 @@ unsigned short dci_decode(viterbi_t *decoder, float *e, char *data, int E, int nof_bits) { float tmp[3 * (DCI_MAX_BITS + 16)]; - unsigned short p_bits; + unsigned short p_bits, crc_res; char *x; assert(nof_bits < DCI_MAX_BITS); +/* char a[] = {1,1,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0,0}; + + float *b = malloc(sizeof(E)); + for (int i=0;inof_bits, PDCCH_FORMAT_NOF_BITS(c->L), c->ncce, c->L, + c->rnti); crc_res = dci_decode(&q->decoder, &llr[72 * c->ncce], msg->data, PDCCH_FORMAT_NOF_BITS(c->L), c->nof_bits); if (c->rnti == crc_res) { memcpy(&msg->location, c, sizeof(dci_candidate_t)); INFO( - "FOUND CAND: Nbits: %d, E: %d, nCCE: %d, L: %d, RNTI: 0x%x\n", + "FOUND Candidate: Nbits: %d, E: %d, nCCE: %d, L: %d, RNTI: 0x%x\n", c->nof_bits, PDCCH_FORMAT_NOF_BITS(c->L), c->ncce, c->L, c->rnti); return 1; @@ -379,25 +401,21 @@ int pdcch_extract_llr(pdcch_t *q, cf_t *slot1_symbols, cf_t *ce[MAX_PORTS_CTRL], memset(&x[q->nof_ports], 0, sizeof(cf_t*) * (MAX_LAYERS - q->nof_ports)); /* extract symbols */ - if (q->nof_symbols - != pdcch_get(slot1_symbols, q->pdcch_symbols[0], q->nof_symbols)) { - fprintf(stderr, "There was an error getting the PDCCH symbols\n"); + int n = regs_pdcch_get(q->regs, slot1_symbols, q->pdcch_symbols[0]); + if (q->nof_symbols != n) { + fprintf(stderr, "Expected %d PDCCH symbols but got %d symbols\n", q->nof_symbols, n); return -1; } /* extract channel estimates */ for (i = 0; i < q->nof_ports; i++) { - if (q->nof_symbols != pdcch_get(ce[i], q->ce[i], q->nof_symbols)) { - fprintf(stderr, "There was an error getting the PDCCH symbols\n"); + n = regs_pdcch_get(q->regs, ce[i], q->ce[i]); + if (q->nof_symbols != n) { + fprintf(stderr, "Expected %d PDCCH symbols but got %d symbols\n", q->nof_symbols, n); return -1; } } - DEBUG("pdcch_symbols: ", 0); - if (VERBOSE_ISDEBUG()) { - vec_fprint_c(stdout, q->pdcch_symbols[0], q->nof_symbols); - } - /* in control channels, only diversity is supported */ if (q->nof_ports == 1) { /* no need for layer demapping */ @@ -410,13 +428,18 @@ int pdcch_extract_llr(pdcch_t *q, cf_t *slot1_symbols, cf_t *ce[MAX_PORTS_CTRL], q->nof_symbols / q->nof_ports); } + DEBUG("pdcch d symbols: ", 0); + if (VERBOSE_ISDEBUG()) { + vec_fprint_c(stdout, q->pdcch_d, q->nof_symbols); + } + /* demodulate symbols */ demod_soft_sigma_set(&q->demod, ebno); demod_soft_demodulate(&q->demod, q->pdcch_d, q->pdcch_llr, q->nof_symbols); DEBUG("llr: ", 0); if (VERBOSE_ISDEBUG()) { - vec_fprint_f(stdout, q->pdcch_llr, q->nof_symbols); + vec_fprint_f(stdout, q->pdcch_llr, q->nof_bits); } /* descramble */ @@ -426,7 +449,6 @@ int pdcch_extract_llr(pdcch_t *q, cf_t *slot1_symbols, cf_t *ce[MAX_PORTS_CTRL], } int pdcch_decode_current_mode(pdcch_t *q, float *llr, dci_t *dci, int subframe) { - int dci_cnt; int k, i; if (q->current_search_mode == SEARCH_UE) { @@ -435,16 +457,15 @@ int pdcch_decode_current_mode(pdcch_t *q, float *llr, dci_t *dci, int subframe) k = 0; } - dci_cnt = 0; for (i = 0; i < q->search_mode[q->current_search_mode].nof_candidates - && dci_cnt < dci->nof_dcis; i++) { + && dci->nof_dcis < dci->max_dcis; i++) { if (pdcch_decode_candidate(q, q->pdcch_llr, &q->search_mode[q->current_search_mode].candidates[k][i], - &dci->msg[dci_cnt])) { - dci_cnt++; + &dci->msg[dci->nof_dcis])) { + dci->nof_dcis++; } } - return dci_cnt; + return dci->nof_dcis; } int pdcch_decode_si(pdcch_t *q, float *llr, dci_t *dci) { @@ -578,7 +599,7 @@ int pdcch_encode(pdcch_t *q, dci_t *dci, cf_t *slot1_symbols[MAX_PORTS_CTRL], /* mapping to resource elements */ for (i = 0; i < q->nof_ports; i++) { - pdcch_put(q->pdcch_symbols[i], slot1_symbols[i], q->nof_symbols); + regs_pdcch_put(q->regs, q->pdcch_symbols[i], slot1_symbols[i]); } return 0; } diff --git a/lte/lib/phch/src/regs.c b/lte/lib/phch/src/regs.c index 06b9a0c79..6583dbdc3 100644 --- a/lte/lib/phch/src/regs.c +++ b/lte/lib/phch/src/regs.c @@ -37,29 +37,144 @@ regs_reg_t *regs_find_reg(regs_t *h, int k, int l); - - /*************************************************************** * * PDCCH REG ALLOCATION * ***************************************************************/ +void regs_pdcch_free(regs_t *h) { + int i; + for (i=0;i<3;i++) { + if (h->pdcch[i].regs) { + free(h->pdcch[i].regs); + } + } +} + +#define PDCCH_NCOLS 32 +const unsigned char PDCCH_PERM[PDCCH_NCOLS] = + { 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31, 0, 16, 8, + 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30 }; + /** Initialize REGs for PDCCH * 36.211 10.3 section 6.8.5 */ int regs_pdcch_init(regs_t *h) { - return 0; -} + int i, m, cfi, nof_ctrl_symbols; + int ret = -1; + int nrows, ndummy, j, k, kp; + regs_reg_t **tmp = NULL; -void regs_pdcch_free(regs_t *h) { - if (h->pdcch) { - free(h->pdcch); + bzero(&h->pdcch, sizeof(regs_ch_t)); + + for (cfi=0;cfi<3;cfi++) { + if (h->nof_prb < 10) { + nof_ctrl_symbols = cfi+2; + } else { + nof_ctrl_symbols = cfi+1; + } + + tmp = malloc(sizeof(regs_reg_t*) * h->nof_regs); + if (!tmp) { + perror("malloc"); + goto clean_and_exit; + } + + /* Number and count REGs for this CFI */ + m=0; + for (i=0;inof_regs;i++) { + if (h->regs[i].l < nof_ctrl_symbols && !h->regs[i].assigned) { + tmp[m] = &h->regs[i]; + m++; + } + } + + h->pdcch[cfi].nof_regs = m; + + h->pdcch[cfi].regs = malloc(sizeof(regs_reg_t*) * h->pdcch[cfi].nof_regs); + if (!h->pdcch[cfi].regs) { + perror("malloc"); + goto clean_and_exit; + } + + /* Interleave REGs */ + nrows = (h->pdcch[cfi].nof_regs-1)/PDCCH_NCOLS+1; + ndummy = PDCCH_NCOLS*nrows - h->pdcch[cfi].nof_regs; + if (ndummy < 0) { + ndummy = 0; + } + + k=0; + for (j = 0; j < PDCCH_NCOLS; j++) { + for (i = 0; i < nrows; i++) { + if (i*PDCCH_NCOLS + PDCCH_PERM[j] >= ndummy) { + m = i*PDCCH_NCOLS + PDCCH_PERM[j]-ndummy; + kp = (k-h->cell_id)%h->pdcch[cfi].nof_regs; + if (kp < 0) { + kp += h->pdcch[cfi].nof_regs; + } + h->pdcch[cfi].regs[m] = tmp[kp]; + k++; + } + } + } + h->pdcch[cfi].nof_regs = (h->pdcch[cfi].nof_regs/9)*9; + free(tmp); + tmp = NULL; + if (VERBOSE_ISINFO() && cfi == 1) { + for (i=0;ipdcch[cfi].nof_regs;i++) { + INFO("Logical PDCCH REG#%d:%d (%d,%d)\n", i%9,i/9, + h->pdcch[cfi].regs[i]->k0, h->pdcch[cfi].regs[i]->l); + } + } } + + ret = 0; +clean_and_exit: + if (tmp) { + free(tmp); + } + if (ret == -1) { + regs_pdcch_free(h); + } + return ret; } int regs_pdcch_nregs(regs_t *h) { - return 9; + if (h->cfi == -1) { + fprintf(stderr, "Must call regs_set_cfi() first\n"); + return -1; + } else { + return h->pdcch[h->cfi].nof_regs; + } +} + +/** Copy quadruplets to REGs and cyclic shift them, according to the + * second part of 6.8.5 in 36.211 + */ +int regs_pdcch_put(regs_t *h, cf_t *pdcch_symbols, cf_t *slot_symbols) { + if (h->cfi == -1) { + fprintf(stderr, "Must call regs_set_cfi() first\n"); + return -1; + } + int i; + for (i=0;ipdcch[h->cfi].nof_regs;i++) { + regs_put_reg(h->pdcch[h->cfi].regs[i], &pdcch_symbols[i*4], slot_symbols, h->nof_prb); + } + return h->pdcch[h->cfi].nof_regs*4; +} + +int regs_pdcch_get(regs_t *h, cf_t *slot_symbols, cf_t *pdcch_symbols) { + if (h->cfi == -1) { + fprintf(stderr, "Must call regs_set_cfi() first\n"); + return -1; + } + int i; + for (i=0;ipdcch[h->cfi].nof_regs;i++) { + regs_get_reg(h->pdcch[h->cfi].regs[i], slot_symbols, &pdcch_symbols[i*4], h->nof_prb); + } + return h->pdcch[h->cfi].nof_regs*4; } @@ -195,6 +310,16 @@ void regs_phich_free(regs_t *h) { } } +int regs_phich_nregs(regs_t *h) { + int i, n; + n=0; + for (i=0;ingroups_phich;i++) { + n += h->phich[i].nof_regs; + } + return n; +} + + int regs_phich_ngroups(regs_t *h) { return h->ngroups_phich; } @@ -274,10 +399,6 @@ int regs_phich_get(regs_t *h, cf_t *slot_symbols, cf_t phich_symbols[REGS_PHICH_ - - - - /*************************************************************** * * PCFICH REG ALLOCATION @@ -330,6 +451,10 @@ void regs_pcfich_free(regs_t *h) { } } +int regs_pcfich_nregs(regs_t *h) { + return h->pcfich.nof_regs; +} + /** * Maps the PCFICH symbols to the resource grid pointed by slot_symbols * @@ -392,13 +517,13 @@ regs_reg_t *regs_find_reg(regs_t *h, int k, int l) { * Returns the number of REGs in a PRB * 36.211 Section 6.2.4 */ -int regs_num_x_symbol(int symbol, int refs_in_symbol1, lte_cp_t cp) { +int regs_num_x_symbol(int symbol, int nof_port, lte_cp_t cp) { switch (symbol) { case 0: return 2; case 1: - switch (refs_in_symbol1) { + switch (nof_port) { case 1: case 2: return 3; @@ -430,10 +555,10 @@ int regs_reg_init(regs_reg_t *reg, int symbol, int nreg, int k0, int maxreg, int reg->l = symbol; reg->assigned = false; - reg->k0 = k0 + nreg * 6; switch (maxreg) { case 2: + reg->k0 = k0 + nreg * 6; /* there are two references in the middle */ j = z = 0; for (i = 0; i < vo; i++) { @@ -456,6 +581,7 @@ int regs_reg_init(regs_reg_t *reg, int symbol, int nreg, int k0, int maxreg, int break; case 3: + reg->k0 = k0 + nreg * 4; /* there is no reference */ for (i = 0; i < 4; i++) { reg->k[i] = k0 + nreg * 4 + i; @@ -479,15 +605,33 @@ void regs_free(regs_t *h) { bzero(h, sizeof(regs_t)); } +/** Sets the CFI value for this subframe (CFI must be in the range 1..3). + */ +int regs_set_cfi(regs_t *h, int cfi) { + if (cfi > 0 && cfi <= 3) { + if (h->phich_len == PHICH_EXT && + ((h->nof_prb < 10 && cfi < 2) || (h->nof_prb >= 10 && cfi < 3))) { + fprintf(stderr, "PHICH length is extended. The number of control symbols should be at least 3.\n"); + return -1; + } else { + h->cfi = cfi - 1; + return 0; + } + } else { + fprintf(stderr, "Invalid CFI %d\n", cfi); + return -1; + } +} + /** * Initializes REGs structure. * Sets all REG indices and initializes PCFICH, PHICH and PDCCH REGs * Returns 0 if OK, -1 on error */ -int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, +int regs_init(regs_t *h, int cell_id, int nof_prb, int nof_ports, phich_resources_t phich_res, phich_length_t phich_len, lte_cp_t cp) { int ret = -1; - int i, j, n, p, k; + int i, j[4], jmax, n[4], prb, k; int vo = cell_id % 3; int max_ctrl_symbols = nof_prb<10?4:3; @@ -496,18 +640,19 @@ int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, h->cell_id = cell_id; h->nof_prb = nof_prb; h->max_ctrl_symbols = max_ctrl_symbols; + h->cfi = -1; // not yet initialized h->phich_res = phich_res; h->phich_len = phich_len; h->cp = cp; - h->refs_in_symbol1 = refs_in_symbol1; + h->nof_ports = nof_ports; h->nof_regs = 0; for (i = 0; i < max_ctrl_symbols; i++) { - n = regs_num_x_symbol(i, refs_in_symbol1, cp); - if (n == -1) { + n[i] = regs_num_x_symbol(i, nof_ports, cp); + if (n[i] == -1) { return -1; } - h->nof_regs += nof_prb * n; + h->nof_regs += nof_prb * n[i]; } INFO("Indexing %d REGs. CellId: %d, %d PRB, CP: %s\n", h->nof_regs, h->cell_id, h->nof_prb, CP_ISNORM(cp)?"Normal":"Extended"); @@ -517,26 +662,36 @@ int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, goto clean_and_exit; } - k = 0; - for (i = 0; i < max_ctrl_symbols; i++) { - n = regs_num_x_symbol(i, refs_in_symbol1, cp); - for (p = 0; p < nof_prb; p++) { - for (j = 0; j < n; j++) { - if (regs_reg_init(&h->regs[k], i, j, p * RE_X_RB, n, vo)) { - fprintf(stderr, "Error initializing REGs\n"); - goto clean_and_exit; - } - DEBUG("Available REG #%3d: %d:%d:%d (k0=%d)\n", k, i, p, j, - h->regs[k].k0); - k++; + /* Sort REGs according to PDCCH mapping, beggining from the lowest l index then k */ + bzero(j, sizeof(int) * 4); + k = i = prb = jmax = 0; + while (k < h->nof_regs) { + if (n[i] == 3 || (n[i] == 2 && jmax != 1)) { + if (regs_reg_init(&h->regs[k], i, j[i], prb * RE_X_RB, n[i], vo)) { + fprintf(stderr, "Error initializing REGs\n"); + goto clean_and_exit; } + DEBUG("Available REG #%3d: l=%d, prb=%d, nreg=%d (k0=%d)\n", k, i, prb, j[i], + h->regs[k].k0); + j[i]++; + k++; + } + i++; + if (i == max_ctrl_symbols) { + i = 0; + jmax++; + } + if (jmax == 3) { + prb++; + bzero(j, sizeof(int) * 4); + jmax = 0; } } - if (regs_pcfich_init(h)) { fprintf(stderr, "Error initializing PCFICH REGs\n"); goto clean_and_exit; } + if (regs_phich_init(h)) { fprintf(stderr, "Error initializing PHICH REGs\n"); goto clean_and_exit; @@ -548,7 +703,8 @@ int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, ret = 0; - clean_and_exit: if (ret == -1) { +clean_and_exit: + if (ret == -1) { regs_free(h); } return ret; @@ -562,13 +718,8 @@ int regs_init(regs_t *h, int cell_id, int nof_prb, int refs_in_symbol1, int regs_put_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_prb) { int i; for (i = 0; i < REGS_RE_X_REG; i++) { - if (reg->assigned) { - DEBUG("PUT REG: i=%d, (k=%d,l=%d)\n", i, REG_IDX(reg, i, nof_prb),reg->l); - slot_symbols[REG_IDX(reg, i, nof_prb)] = reg_data[i]; - } else { - fprintf(stderr, "Error REG not assigned\n"); - return -1; - } + DEBUG("PUT REG: i=%d, (k=%d,l=%d)\n", i, REG_IDX(reg, i, nof_prb),reg->l); + slot_symbols[REG_IDX(reg, i, nof_prb)] = reg_data[i]; } return REGS_RE_X_REG; } @@ -580,15 +731,10 @@ int regs_put_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_pr int regs_add_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_prb) { int i; for (i = 0; i < REGS_RE_X_REG; i++) { - if (reg->assigned) { - slot_symbols[REG_IDX(reg, i, nof_prb)] += reg_data[i]; - DEBUG("ADD REG: i=%d, (k=%d,l=%d): %.1f+%.1fi\n", i, REG_IDX(reg, i, nof_prb),reg->l, - __real__ slot_symbols[REG_IDX(reg, i, nof_prb)], - __imag__ slot_symbols[REG_IDX(reg, i, nof_prb)]); - } else { - fprintf(stderr, "Error REG not assigned\n"); - return -1; - } + slot_symbols[REG_IDX(reg, i, nof_prb)] += reg_data[i]; + DEBUG("ADD REG: i=%d, (k=%d,l=%d): %.1f+%.1fi\n", i, REG_IDX(reg, i, nof_prb),reg->l, + __real__ slot_symbols[REG_IDX(reg, i, nof_prb)], + __imag__ slot_symbols[REG_IDX(reg, i, nof_prb)]); } return REGS_RE_X_REG; } @@ -600,13 +746,8 @@ int regs_add_reg(regs_reg_t *reg, cf_t *reg_data, cf_t *slot_symbols, int nof_pr int regs_reset_reg(regs_reg_t *reg, cf_t *slot_symbols, int nof_prb) { int i; for (i = 0; i < REGS_RE_X_REG; i++) { - if (reg->assigned) { - DEBUG("RESET REG: i=%d, (k=%d,l=%d)\n", i, REG_IDX(reg, i, nof_prb),reg->l); - slot_symbols[REG_IDX(reg, i, nof_prb)] = 0; - } else { - fprintf(stderr, "Error REG not assigned\n"); - return -1; - } + DEBUG("RESET REG: i=%d, (k=%d,l=%d)\n", i, REG_IDX(reg, i, nof_prb),reg->l); + slot_symbols[REG_IDX(reg, i, nof_prb)] = 0; } return REGS_RE_X_REG; } @@ -617,14 +758,9 @@ int regs_reset_reg(regs_reg_t *reg, cf_t *slot_symbols, int nof_prb) { int regs_get_reg(regs_reg_t *reg, cf_t *slot_symbols, cf_t *reg_data, int nof_prb) { int i; for (i = 0; i < REGS_RE_X_REG; i++) { - if (reg->assigned) { - reg_data[i] = slot_symbols[REG_IDX(reg, i, nof_prb)]; - DEBUG("GET REG: i=%d, (k=%d,l=%d): %.1f+%.1fi\n", i, REG_IDX(reg, i, nof_prb),reg->l, - __real__ reg_data[i], __imag__ reg_data[i]); - } else { - fprintf(stderr, "Error REG not assigned\n"); - return -1; - } + reg_data[i] = slot_symbols[REG_IDX(reg, i, nof_prb)]; + //DEBUG("GET REG: i=%d, (k=%d,l=%d): %.1f+%.1fi\n", i, REG_IDX(reg, i, nof_prb),reg->l, + // __real__ reg_data[i], __imag__ reg_data[i]); } return REGS_RE_X_REG; } diff --git a/lte/lib/phch/test/CMakeLists.txt b/lte/lib/phch/test/CMakeLists.txt index 204441ba9..f7a6c00d1 100644 --- a/lte/lib/phch/test/CMakeLists.txt +++ b/lte/lib/phch/test/CMakeLists.txt @@ -75,6 +75,11 @@ ADD_TEST(phich_test_104 phich_test -p 4 -n 10 -e -l -g 1/2) ADD_EXECUTABLE(pdcch_test pdcch_test.c) TARGET_LINK_LIBRARIES(pdcch_test lte) +ADD_TEST(pdcch_test pdcch_test) + +ADD_EXECUTABLE(dci_unpacking dci_unpacking.c) +TARGET_LINK_LIBRARIES(dci_unpacking lte) + ######################################################################## # FILE TEST ######################################################################## @@ -88,7 +93,11 @@ TARGET_LINK_LIBRARIES(pcfich_file_test lte) ADD_EXECUTABLE(phich_file_test phich_file_test.c) TARGET_LINK_LIBRARIES(phich_file_test lte) +ADD_EXECUTABLE(pdcch_file_test pdcch_file_test.c) +TARGET_LINK_LIBRARIES(pdcch_file_test lte) + 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) \ No newline at end of file diff --git a/lte/lib/phch/test/pbch_file_test.c b/lte/lib/phch/test/pbch_file_test.c index 15cf7ace0..02ad61073 100644 --- a/lte/lib/phch/test/pbch_file_test.c +++ b/lte/lib/phch/test/pbch_file_test.c @@ -223,7 +223,8 @@ int main(int argc, char **argv) { } else { if (mib.nof_ports == 2 && mib.nof_prb == 50 && mib.phich_length == PHICH_NORM && mib.phich_resources == R_1 && mib.sfn == 28) { - printf("This is the pbch_test.dat file\n"); + pbch_mib_fprint(stdout, &mib); + printf("This is the signal.1.92M.dat file\n"); exit(0); } else { pbch_mib_fprint(stdout, &mib); diff --git a/lte/lib/phch/test/pdcch_test.c b/lte/lib/phch/test/pdcch_test.c index 2263e9bfb..a6d0d9b42 100644 --- a/lte/lib/phch/test/pdcch_test.c +++ b/lte/lib/phch/test/pdcch_test.c @@ -36,10 +36,12 @@ int cell_id = 1; int nof_prb = 6; int nof_ports = 1; +int cfi = 1; void usage(char *prog) { printf("Usage: %s [cpv]\n", prog); printf("\t-c cell id [Default %d]\n", cell_id); + printf("\t-f cfi [Default %d]\n", cfi); printf("\t-p nof_ports [Default %d]\n", nof_ports); printf("\t-n nof_prb [Default %d]\n", nof_prb); printf("\t-v [set verbose to debug, default none]\n"); @@ -47,11 +49,14 @@ void usage(char *prog) { void parse_args(int argc, char **argv) { int opt; - while ((opt = getopt(argc, argv, "cpnv")) != -1) { + while ((opt = getopt(argc, argv, "cpnfv")) != -1) { switch(opt) { case 'p': nof_ports = atoi(argv[optind]); break; + case 'f': + cfi = atoi(argv[optind]); + break; case 'n': nof_prb = atoi(argv[optind]); break; @@ -68,10 +73,42 @@ void parse_args(int argc, char **argv) { } } + +int test_dci_payload_size() { + int i, j; + int x[4]; + const dci_format_t formats[4] = {Format0, Format1, Format1A, Format1C}; + const int prb[6]={6, 15, 25, 50, 75, 100}; + const int dci_sz[6][5] = { + {21, 19, 21, 8}, + {22, 23, 22, 10}, + {25, 27, 25, 12}, + {27, 31, 27, 13}, + {27, 33, 27, 14}, + {28, 39, 28, 15} + }; + + printf("Testing DCI payload sizes...\n"); + printf(" PRB\t0\t1\t1A\t1C\n"); + for (i=0;i<6;i++) { + int n=prb[i]; + for (j=0;j<4;j++) { + x[j] = dci_format_sizeof(formats[j], n); + if (x[j] != dci_sz[i][j]) { + fprintf(stderr, "Invalid DCI payload size for %s\n", dci_format_string(formats[j])); + return -1; + } + } + printf(" %2d:\t%2d\t%2d\t%2d\t%2d\n",n,x[0],x[1],x[2],x[3]); + } + printf("Ok\n"); + return 0; +} + int main(int argc, char **argv) { pdcch_t pdcch; dci_t dci_tx, dci_rx; - dci_format1_t dci_msg; + ra_pdsch_t ra_dl; regs_t regs; int i, j; cf_t *ce[MAX_PORTS_CTRL]; @@ -84,6 +121,10 @@ int main(int argc, char **argv) { nof_re = CPNORM_NSYMB * nof_prb * RE_X_RB; + if (test_dci_payload_size()) { + exit(-1); + } + /* init memory */ for (i=0;i Date: Mon, 12 May 2014 18:44:00 +0100 Subject: [PATCH 08/15] Added PDCCH encoder/decoder. Tested with Amarisoft eNodeB --- lte/include/lte/phch/ra.h | 156 +++++++ lte/lib/phch/src/ra.c | 518 ++++++++++++++++++++++++ lte/lib/phch/src/tbs_tables.h | 278 +++++++++++++ lte/lib/phch/test/dci_unpacking.c | 105 +++++ lte/lib/phch/test/pdcch_file_test.c | 309 ++++++++++++++ lte/lib/phch/test/signal.1.92M.amar.dat | Bin 0 -> 153600 bytes 6 files changed, 1366 insertions(+) create mode 100644 lte/include/lte/phch/ra.h create mode 100644 lte/lib/phch/src/ra.c create mode 100644 lte/lib/phch/src/tbs_tables.h create mode 100644 lte/lib/phch/test/dci_unpacking.c create mode 100644 lte/lib/phch/test/pdcch_file_test.c create mode 100644 lte/lib/phch/test/signal.1.92M.amar.dat diff --git a/lte/include/lte/phch/ra.h b/lte/include/lte/phch/ra.h new file mode 100644 index 000000000..0519dc51d --- /dev/null +++ b/lte/include/lte/phch/ra.h @@ -0,0 +1,156 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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/. + * + */ + +#ifndef RB_ALLOC_H_ +#define RB_ALLOC_H_ + +#include +#include + +/** Structures and utility functions for DL/UL resource + * allocation. + */ + +typedef enum { + MOD_NULL = 0, BPSK = 1, QPSK = 2, QAM16 = 4, QAM64 = 16 +} ra_mod_t; + +typedef struct { + ra_mod_t mod; // By default, mod = MOD_NULL and the mcs_idx value is taken by the packing functions + // otherwise mod + tbs values are used to generate the mcs_idx automatically. + uint8_t tbs_idx; + uint8_t mcs_idx; + int tbs; // If tbs<=0, the tbs_idx value is taken by the packing functions to generate the DCI + // message. Otherwise the tbs_idx corresponding to the lower nearest TBS is taken. +}ra_mcs_t; + + +typedef enum { + alloc_type0 = 0, alloc_type1 = 1, alloc_type2 = 2 +}ra_type_t; + +typedef struct { + uint32_t rbg_bitmask; +}ra_type0_t; + +typedef struct { + uint32_t vrb_bitmask; + uint8_t rbg_subset; + bool shift; +}ra_type1_t; + +typedef struct { + uint32_t riv; // if L_crb==0, DCI message packer will take this value directly + uint16_t L_crb; + uint16_t RB_start; + enum {nprb1a_2 = 0, nprb1a_3 = 1} n_prb1a; + enum {t2_ng1 = 0, t2_ng2 = 1} n_gap; + enum {t2_loc = 0, t2_dist = 1} mode; +}ra_type2_t; + +typedef struct { + unsigned short rnti; + ra_type_t alloc_type; + union { + ra_type0_t type0_alloc; + ra_type1_t type1_alloc; + ra_type2_t type2_alloc; + }; + ra_mcs_t mcs; + uint8_t harq_process; + uint8_t rv_idx; + bool ndi; +} ra_pdsch_t; + +typedef struct { + /* 36.213 Table 8.4-2: hop_half is 0 for < 10 Mhz and 10 for > 10 Mh. + * hop_quart is 00 for > 10 Mhz and hop_quart_neg is 01 for > 10 Mhz. + */ + enum { + hop_disabled = -1, + hop_quart = 0, + hop_quart_neg = 1, + hop_half = 2, + hop_type_2 = 3 + } freq_hop_fl; + + ra_type2_t type2_alloc; + ra_mcs_t mcs; + uint8_t rv_idx; // If set to non-zero, a retransmission is requested with the same modulation + // than before (Format0 message, see also 8.6.1 in 36.2313). + bool ndi; + bool cqi_request; + +} ra_pusch_t; + +typedef struct { + uint8_t prb_idx[110]; + int nof_prb; +}ra_prb_slot_t; + +typedef struct { + ra_prb_slot_t slot1; + ra_prb_slot_t slot2; + bool is_dist; +}ra_prb_t; + + +void ra_prb_fprint(FILE *f, ra_prb_slot_t *prb); +int ra_prb_get_dl(ra_prb_t *prb, ra_pdsch_t *ra, int nof_prb); +int ra_prb_get_ul(ra_prb_slot_t *prb, ra_pusch_t *ra, int nof_prb); +int ra_nprb_dl(ra_pdsch_t *ra, int nof_prb); +int ra_nprb_ul(ra_pusch_t *ra, int nof_prb); + +uint8_t ra_mcs_to_table_idx(ra_mcs_t *mcs); +int ra_mcs_from_idx_dl(uint8_t idx, ra_mcs_t *mcs); +int ra_mcs_from_idx_ul(uint8_t idx, ra_mcs_t *mcs); +int ra_tbs_from_idx_format1c(uint8_t tbs_idx); +int ra_tbs_to_table_idx_format1c(int tbs); +int ra_tbs_from_idx(uint8_t tbs_idx, int n_prb); +int ra_tbs_to_table_idx(int tbs, int n_prb); + +uint8_t ra_mcs_to_table_idx(ra_mcs_t *mcs); +int ra_mcs_from_idx_dl(uint8_t idx, ra_mcs_t *mcs); +int ra_mcs_from_idx_ul(uint8_t idx, ra_mcs_t *mcs); + +char *ra_mod_string(ra_mod_t mod); + +int ra_type0_P(int nof_prb); + +uint32_t ra_type2_to_riv(uint16_t L_crb, uint16_t RB_start, int nof_prb); +void ra_type2_from_riv(uint32_t riv, uint16_t *L_crb, uint16_t *RB_start, int nof_prb, int nof_vrb); +int ra_type2_n_vrb_dl(int nof_prb, bool ngap_is_1); +int ra_type2_n_rb_step(int nof_prb); +int ra_type2_ngap(int nof_prb, bool ngap_is_1); +int ra_type1_N_rb(int nof_prb); + +void ra_pdsch_set_mcs_index(ra_pdsch_t *ra, uint8_t mcs_idx); +void ra_pdsch_set_mcs(ra_pdsch_t *ra, ra_mod_t mod, uint8_t tbs_idx); +void ra_pdsch_fprint(FILE *f, ra_pdsch_t *ra, int nof_prb); +void ra_pusch_fprint(FILE *f, ra_pusch_t *ra, int nof_prb); + +#endif /* RB_ALLOC_H_ */ diff --git a/lte/lib/phch/src/ra.c b/lte/lib/phch/src/ra.c new file mode 100644 index 000000000..936d2169a --- /dev/null +++ b/lte/lib/phch/src/ra.c @@ -0,0 +1,518 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte/common/base.h" +#include "lte/utils/bit.h" +#include "lte/utils/vector.h" +#include "lte/utils/debug.h" +#include "lte/phch/ra.h" +#include "lte/utils/bit.h" + +#include "tbs_tables.h" + +#define min(a,b) (anof_prb - 1)/ 25 + 1; + for (j=0;jnof_prb-j*25);i++) { + fprintf(f, "%3d, ", prb->prb_idx[j*25+i]); + } + fprintf(f, "\n"); + } +} + +/** Compute PRB allocation for Downlink as defined in 8.1 of 36.213 */ +int ra_prb_get_ul(ra_prb_slot_t *prb, ra_pusch_t *ra, int nof_prb) { + int i; + if (ra->type2_alloc.mode != t2_loc) { + fprintf(stderr, "Uplink only accepts type2 localized scheduling\n"); + return -1; + } + for (i=0;itype2_alloc.L_crb;i++) { + prb->prb_idx[i] = i+ra->type2_alloc.RB_start; + prb->nof_prb++; + } + return 0; +} + +/** Compute PRB allocation for Downlink as defined in 7.1.6 of 36.213 */ +int ra_prb_get_dl(ra_prb_t *prb_dist, ra_pdsch_t *ra, int nof_prb) { + int i, j; + uint32_t bitmask; + int P = ra_type0_P(nof_prb); + ra_prb_slot_t *prb; + + bzero(prb_dist, sizeof(ra_prb_t)); + switch(ra->alloc_type) { + case alloc_type0: + prb = &prb_dist->slot1; + prb_dist->is_dist = false; + bitmask = ra->type0_alloc.rbg_bitmask; + int nb = (int) ceilf((float)nof_prb/P); + for (i=0;iprb_idx[prb->nof_prb] = i*P+j; + prb->nof_prb++; + } + } + } + break; + case alloc_type1: + prb = &prb_dist->slot1; + prb_dist->is_dist = false; + int n_rb_type1 = ra_type1_N_rb(nof_prb); + int n_rb_rbg_subset; + if (ra->type1_alloc.rbg_subset < (nof_prb/P) % P) { + n_rb_rbg_subset = ((nof_prb-1)/(P*P)) * P + P; + } else if (ra->type1_alloc.rbg_subset == ((nof_prb/P) % P)) { + n_rb_rbg_subset = ((nof_prb-1)/(P*P)) * P + ((nof_prb-1)%P)+1; + } else { + n_rb_rbg_subset = ((nof_prb-1)/(P*P)) * P; + } + int shift = ra->type1_alloc.shift?(n_rb_rbg_subset-n_rb_type1):0; + bitmask = ra->type1_alloc.vrb_bitmask; + for (i=0;iprb_idx[prb->nof_prb] = ((i+shift)/P)*P*P+ + ra->type1_alloc.rbg_subset*P+(i+shift)%P; + prb->nof_prb++; + } + } + break; + case alloc_type2: + if (ra->type2_alloc.mode == t2_loc) { + prb = &prb_dist->slot1; + prb_dist->is_dist = false; + for (i=0;itype2_alloc.L_crb;i++) { + prb->prb_idx[i] = i+ra->type2_alloc.RB_start; + prb->nof_prb++; + } + } else { + /* Mapping of Virtual to Physical RB for distributed type is defined in + * 6.2.3.2 of 36.211 + */ + prb_dist->is_dist = true; + int N_gap, N_tilde_vrb, n_tilde_vrb, n_tilde_prb, n_tilde2_prb, N_null, N_row, n_vrb; + int n_tilde_prb_odd, n_tilde_prb_even; + if (ra->type2_alloc.n_gap == t2_ng1) { + N_tilde_vrb = nof_prb; + N_gap = ra_type2_ngap(nof_prb, true); + } else { + N_tilde_vrb = 2*nof_prb; + N_gap = ra_type2_ngap(nof_prb, false); + } + N_row = (int) ceilf((float) N_tilde_vrb/(4*P))*P; + N_null = 4*N_row-N_tilde_vrb; + for (i=0;itype2_alloc.L_crb;i++) { + n_vrb = i+ra->type2_alloc.RB_start; + n_tilde_vrb = n_vrb%N_tilde_vrb; + n_tilde_prb = 2*N_row*(n_tilde_vrb % 2)+n_tilde_vrb/2+N_tilde_vrb*(n_vrb/N_tilde_vrb); + n_tilde2_prb = N_row*(n_tilde_vrb % 4)+n_tilde_vrb/4+N_tilde_vrb*(n_vrb/N_tilde_vrb); + + if (N_null != 0 && n_tilde_vrb >= (N_tilde_vrb - N_null) && (n_tilde_vrb%2) == 1) { + n_tilde_prb_odd = n_tilde_prb-N_row; + } else if (N_null != 0 && n_tilde_vrb >= (N_tilde_vrb - N_null) && (n_tilde_vrb%2) == 0) { + n_tilde_prb_odd = n_tilde_prb-N_row+N_null/2; + } else if (N_null != 0 && n_tilde_vrb < (N_tilde_vrb - N_null) && (n_tilde_vrb%4) >= 2) { + n_tilde_prb_odd = n_tilde2_prb-N_null/2; + } else { + n_tilde_prb_odd = n_tilde2_prb; + } + n_tilde_prb_even = (n_tilde_prb_odd+N_tilde_vrb/2)%N_tilde_vrb+N_tilde_vrb*(n_vrb/N_tilde_vrb); + + if (n_tilde_prb_odd < N_tilde_vrb/2) { + prb_dist->slot1.prb_idx[i] = n_tilde_prb_odd; + } else { + prb_dist->slot1.prb_idx[i] = n_tilde_prb_odd+N_gap-N_tilde_vrb/2; + } + prb_dist->slot1.nof_prb++; + if (n_tilde_prb_even < N_tilde_vrb/2) { + prb_dist->slot2.prb_idx[i] = n_tilde_prb_even; + } else { + prb_dist->slot2.prb_idx[i] = n_tilde_prb_even+N_gap-N_tilde_vrb/2; + } + prb_dist->slot2.nof_prb++; + } + } + break; + default: + return -1; + } + return 0; +} + +/* Returns the number of allocated PRB for Uplink */ +int ra_nprb_ul(ra_pusch_t *ra, int nof_prb) { + return ra->type2_alloc.L_crb; +} + +/* Returns the number of allocated PRB for Downlink */ +int ra_nprb_dl(ra_pdsch_t *ra, int nof_prb) { + int nprb; + int nof_rbg, P; + switch(ra->alloc_type) { + case alloc_type0: + // Get the number of allocated RBG except the last RBG + nof_rbg = bit_count(ra->type0_alloc.rbg_bitmask & 0xFFFFFFFE); + P = ra_type0_P(nof_prb); + if (nof_rbg > (int) ceilf((float)nof_prb/P)) { + fprintf(stderr, "Number of RGB (%d) can not exceed %d\n", nof_prb, + (int) ceilf((float)nof_prb/P)); + return -1; + } + nprb = nof_rbg * P; + + // last RBG may have smaller size. Add if set + int P_last = (nof_prb%P); + if (!P_last) P_last = P; + nprb += P_last*(ra->type0_alloc.rbg_bitmask&1); + break; + case alloc_type1: + nprb = bit_count(ra->type1_alloc.vrb_bitmask); + if (nprb > ra_type1_N_rb(nof_prb)) { + fprintf(stderr, "Number of RB (%d) can not exceed %d\n", nprb, + ra_type1_N_rb(nof_prb)); + return -1; + } + break; + case alloc_type2: + nprb = ra->type2_alloc.L_crb; + break; + default: + return -1; + } + return nprb; +} + +/* RBG size for type0 scheduling as in table 7.1.6.1-1 of 36.213 */ +int ra_type0_P(int nof_prb) { + if (nof_prb <= 10) { + return 1; + } else if (nof_prb <= 26) { + return 2; + } else if (nof_prb <= 63) { + return 3; + } else { + return 4; + } +} + +/* Returns N_rb_type1 according to section 7.1.6.2 */ +int ra_type1_N_rb(int nof_prb) { + int P = ra_type0_P(nof_prb); + return (int) ceilf((float) nof_prb/P) - (int) ceilf(log2f((float) P)) - 1; +} + +/* Convert Type2 scheduling L_crb and RB_start to RIV value */ +uint32_t ra_type2_to_riv(uint16_t L_crb, uint16_t RB_start, int nof_prb) { + uint32_t riv; + if (L_crb <= (int) nof_prb/2) { + riv = nof_prb*(L_crb-1) + RB_start; + } else { + riv = nof_prb*(nof_prb-L_crb+1) + nof_prb - 1 - RB_start; + } + return riv; +} + +/* Convert Type2 scheduling RIV value to L_crb and RB_start values */ +void ra_type2_from_riv(uint32_t riv, uint16_t *L_crb, uint16_t *RB_start, int nof_prb, int nof_vrb) { + *L_crb = (int) (riv/nof_prb) + 1; + *RB_start = riv%nof_prb; + if (*L_crb > nof_vrb - *RB_start) { + *L_crb = nof_prb - (int) (riv/nof_prb) + 1; + *RB_start = nof_prb - riv%nof_prb - 1; + } +} + + +/* Table 6.2.3.2-1 in 36.211 */ +int ra_type2_ngap(int nof_prb, bool ngap_is_1) { + if (nof_prb <= 10) { + return nof_prb/2; + } else if (nof_prb == 11) { + return 4; + } else if (nof_prb <= 19) { + return 8; + } else if (nof_prb <= 26) { + return 12; + } else if (nof_prb <= 44) { + return 18; + } else if (nof_prb <= 49) { + return 27; + } else if (nof_prb <= 63) { + return ngap_is_1?27:9; + } else if (nof_prb <= 79) { + return ngap_is_1?32:16; + } else { + return ngap_is_1?48:16; + } +} + + +/* Table 7.1.6.3-1 in 36.213 */ +int ra_type2_n_rb_step(int nof_prb) { + if (nof_prb < 50) { + return 2; + } else { + return 4; + } +} + + +/* as defined in 6.2.3.2 of 36.211 */ +int ra_type2_n_vrb_dl(int nof_prb, bool ngap_is_1) { + int ngap = ra_type2_ngap(nof_prb, ngap_is_1); + if (ngap_is_1) { + return 2*(ngap<(nof_prb-ngap)?ngap:nof_prb-ngap); + } else { + return ((int) nof_prb/ngap)*2*ngap; + } +} + +/* Converts ra_mcs_t structure to MCS index for both Uplink and Downlink */ +uint8_t ra_mcs_to_table_idx(ra_mcs_t *mcs) { + switch (mcs->mod) { + case QPSK: + return mcs->tbs_idx; + case QAM16: + return mcs->tbs_idx + 1; + case QAM64: + return mcs->tbs_idx + 2; + default: + return 0; + } +} + +/* Converts MCS index to ra_mcs_t structure for Downlink as defined inTable 7.1.7.1-1 on 36.213 */ +int ra_mcs_from_idx_dl(uint8_t idx, ra_mcs_t *mcs) { + if (idx < 10) { + mcs->mod = QPSK; + mcs->tbs_idx = idx; + } else if (idx < 17) { + mcs->mod = QAM16; + mcs->tbs_idx = idx-1; + } else if (idx < 29) { + mcs->mod = QAM64; + mcs->tbs_idx = idx-2; + } else if (idx == 29) { + mcs->mod = QPSK; + mcs->tbs_idx = 0; + } else if (idx == 30) { + mcs->mod = QAM16; + mcs->tbs_idx = 0; + } else if (idx == 31) { + mcs->mod = QAM64; + mcs->tbs_idx = 0; + } else { + mcs->mod = MOD_NULL; + mcs->tbs_idx = 0; + return -1; + } + return 0; +} + + +/* Converts MCS index to ra_mcs_t structure for Uplink as defined in Table 8.6.1-1 on 36.213 */ +int ra_mcs_from_idx_ul(uint8_t idx, ra_mcs_t *mcs) { + if (idx < 11) { + mcs->mod = QPSK; + mcs->tbs_idx = idx; + } else if (idx < 21) { + mcs->mod = QAM16; + mcs->tbs_idx = idx-1; + } else if (idx < 29) { + mcs->mod = QAM64; + mcs->tbs_idx = idx-2; + } else { + mcs->mod = MOD_NULL; + mcs->tbs_idx = 0; + return -1; + } + return 0; +} + +/* Downlink Transport Block size for Format 1C as defined in 7.1.7.2.2-1 on 36.213 */ +int ra_tbs_from_idx_format1c(uint8_t tbs_idx) { + if (tbs_idx < 32) { + return tbs_format1c_table[tbs_idx]; + } else { + return -1; + } +} + +/* Returns lowest nearest index of TBS value in table 7.1.7.2.2-1 on 36.213 + * or -1 if the TBS value is not within the valid TBS values + */ +int ra_tbs_to_table_idx_format1c(int tbs) { + int idx; + if (tbs < tbs_format1c_table[0]) { + return -1; + } + for (idx=1;idx<32;idx++) { + if (tbs_format1c_table[idx-1] <= tbs && + tbs_format1c_table[idx] >= tbs) { + return idx; + } + } + return -1; +} + +/* Downlink Transport Block size determination as defined in 7.1.7.2 on 36.213 */ +int ra_tbs_from_idx(uint8_t tbs_idx, int n_prb ) { + if (tbs_idx < 27 && n_prb > 0 && n_prb <= 110) { + return tbs_table[tbs_idx][n_prb-1]; + } else { + return -1; + } +} + +/* Returns lowest nearest index of TBS value in table 7.1.7.2 on 36.213 + * or -1 if the TBS value is not within the valid TBS values + */ +int ra_tbs_to_table_idx(int tbs, int n_prb) { + int idx; + if (n_prb > 0 && n_prb <= 110) { + return -1; + } + if (tbs < tbs_table[0][n_prb]) { + return -1; + } + for (idx=1;idx<28;idx++) { + if (tbs_table[idx-1][n_prb] <= tbs && + tbs_table[idx][n_prb] >= tbs) { + return idx; + } + } + return -1; +} + +char *ra_mod_string(ra_mod_t mod) { + switch (mod) { + case QPSK: + return "QPSK"; + case QAM16: + return "QAM16"; + case QAM64: + return "QAM64"; + default: + return "N/A"; + } +} + +void ra_pusch_fprint(FILE *f, ra_pusch_t *ra, int nof_prb) { + fprintf(f, "Frequency Hopping:\t"); + if (ra->freq_hop_fl == hop_disabled) { + fprintf(f, "No"); + } else { + fprintf(f, "Yes"); + + } +} + +char *ra_type_string(ra_type_t alloc_type) { + switch(alloc_type) { + case alloc_type0: + return "Type 0"; + case alloc_type1: + return "Type 1"; + case alloc_type2: + return "Type 2"; + default: + return "N/A"; + } +} + +void ra_pdsch_set_mcs_index(ra_pdsch_t *ra, uint8_t mcs_idx) { + ra->mcs.mod = MOD_NULL; + ra->mcs.mcs_idx = mcs_idx; +} +void ra_pdsch_set_mcs(ra_pdsch_t *ra, ra_mod_t mod, uint8_t tbs_idx) { + ra->mcs.mod = mod; + ra->mcs.tbs_idx = tbs_idx; +} + + +void ra_pdsch_fprint(FILE *f, ra_pdsch_t *ra, int nof_prb) { + fprintf(f, " - Resource Allocation Type:\t\t%s\n",ra_type_string(ra->alloc_type)); + switch(ra->alloc_type) { + case alloc_type0: + fprintf(f, " + Resource Block Group Size:\t\t%d\n",ra_type0_P(nof_prb)); + fprintf(f, " + RBG Bitmap:\t\t\t0x%x\n",ra->type0_alloc.rbg_bitmask); + break; + case alloc_type1: + fprintf(f, " + Resource Block Group Size:\t\t%d\n",ra_type0_P(nof_prb)); + fprintf(f, " + RBG Bitmap:\t\t\t0x%x\n",ra->type1_alloc.vrb_bitmask); + fprintf(f, " + RBG Subset:\t\t\t%d\n",ra->type1_alloc.rbg_subset); + fprintf(f, " + RBG Shift:\t\t\t\t%s\n",ra->type1_alloc.shift?"Yes":"No"); + break; + case alloc_type2: + fprintf(f, " + Type:\t\t\t\t%s\n", + ra->type2_alloc.mode==t2_loc?"Localized":"Distributed"); + fprintf(f, " + Resource Indicator Value:\t\t%d\n",ra->type2_alloc.riv); + if (ra->type2_alloc.mode == t2_loc) { + fprintf(f, " + VRB Assignment:\t\t\t%d VRB starting with VRB %d\n", + ra->type2_alloc.L_crb, ra->type2_alloc.RB_start); + } else { + fprintf(f, " + VRB Assignment:\t\t\t%d VRB starting with VRB %d\n", + ra->type2_alloc.L_crb, ra->type2_alloc.RB_start); + fprintf(f, " + VRB gap selection:\t\t\tGap %d\n", + ra->type2_alloc.n_gap == t2_ng1?1:2); + fprintf(f, " + VRB gap:\t\t\t\t%d\n", + ra_type2_ngap(nof_prb, ra->type2_alloc.n_gap == t2_ng1)); + } + break; + } + + ra_prb_t alloc; + ra_prb_get_dl(&alloc, ra, nof_prb); + if (alloc.is_dist) { + fprintf(f, " - PRB Bitmap Assignment 1st slot:\n"); + ra_prb_fprint(f, &alloc.slot1); + fprintf(f, " - PRB Bitmap Assignment 2nd slot:\n"); + ra_prb_fprint(f, &alloc.slot2); + } else { + fprintf(f, " - PRB Bitmap Assignment:\n"); + ra_prb_fprint(f, &alloc.slot1); + } + + fprintf(f, " - Number of PRBs:\t\t\t%d\n", ra_nprb_dl(ra, nof_prb)); + fprintf(f, " - Modulation and coding scheme index:\t%d\n", ra->mcs.mcs_idx); + fprintf(f, " - Modulation type:\t\t\t%s\n", ra_mod_string(ra->mcs.mod)); + fprintf(f, " - Transport block size:\t\t%d\n", ra->mcs.tbs); + fprintf(f, " - HARQ process:\t\t\t%d\n", ra->harq_process); + fprintf(f, " - New data indicator:\t\t\t%s\n", ra->ndi?"Yes":"No"); + fprintf(f, " - Redundancy version:\t\t\t%d\n", ra->rv_idx); + fprintf(f, " - TPC command for PUCCH:\t\t--\n"); +} + diff --git a/lte/lib/phch/src/tbs_tables.h b/lte/lib/phch/src/tbs_tables.h new file mode 100644 index 000000000..3a6cad731 --- /dev/null +++ b/lte/lib/phch/src/tbs_tables.h @@ -0,0 +1,278 @@ + +const int tbs_format1c_table[32] = { + 40, 56, 72, 120, 136, 144, 176, 208, 224, 256, 280, 296, 328, 336, 392, 488, + 552, 600, 632, 696, 776, 840, 904, 1000, 1064, 1128, 1224, 1288, 1384, 1480, 1608, 1736 +}; + + +/* Transport Block Size from 3GPP TS 36.213 v10.3.0 table 7.1.7.2.1-1 */ +const int tbs_table[27][110] = {{ 16, 32, 56, 88, 120, 152, 176, 208, 224, 256, 288, + 328, 344, 376, 392, 424, 456, 488, 504, 536, 568, 600, + 616, 648, 680, 712, 744, 776, 776, 808, 840, 872, 904, + 936, 968, 1000, 1032, 1032, 1064, 1096, 1128, 1160, 1192, 1224, + 1256, 1256, 1288, 1320, 1352, 1384, 1416, 1416, 1480, 1480, 1544, + 1544, 1608, 1608, 1608, 1672, 1672, 1736, 1736, 1800, 1800, 1800, + 1864, 1864, 1928, 1928, 1992, 1992, 2024, 2088, 2088, 2088, 2152, + 2152, 2216, 2216, 2280, 2280, 2280, 2344, 2344, 2408, 2408, 2472, + 2472, 2536, 2536, 2536, 2600, 2600, 2664, 2664, 2728, 2728, 2728, + 2792, 2792, 2856, 2856, 2856, 2984, 2984, 2984, 2984, 2984, 3112}, + { 24, 56, 88, 144, 176, 208, 224, 256, 328, 344, 376, + 424, 456, 488, 520, 568, 600, 632, 680, 712, 744, 776, + 808, 872, 904, 936, 968, 1000, 1032, 1064, 1128, 1160, 1192, + 1224, 1256, 1288, 1352, 1384, 1416, 1416, 1480, 1544, 1544, 1608, + 1608, 1672, 1736, 1736, 1800, 1800, 1864, 1864, 1928, 1992, 1992, + 2024, 2088, 2088, 2152, 2152, 2216, 2280, 2280, 2344, 2344, 2408, + 2472, 2472, 2536, 2536, 2600, 2600, 2664, 2728, 2728, 2792, 2792, + 2856, 2856, 2856, 2984, 2984, 2984, 3112, 3112, 3112, 3240, 3240, + 3240, 3240, 3368, 3368, 3368, 3496, 3496, 3496, 3496, 3624, 3624, + 3624, 3752, 3752, 3752, 3752, 3880, 3880, 3880, 4008, 4008, 4008}, + { 32, 72, 144, 176, 208, 256, 296, 328, 376, 424, 472, + 520, 568, 616, 648, 696, 744, 776, 840, 872, 936, 968, + 1000, 1064, 1096, 1160, 1192, 1256, 1288, 1320, 1384, 1416, 1480, + 1544, 1544, 1608, 1672, 1672, 1736, 1800, 1800, 1864, 1928, 1992, + 2024, 2088, 2088, 2152, 2216, 2216, 2280, 2344, 2344, 2408, 2472, + 2536, 2536, 2600, 2664, 2664, 2728, 2792, 2856, 2856, 2856, 2984, + 2984, 3112, 3112, 3112, 3240, 3240, 3240, 3368, 3368, 3368, 3496, + 3496, 3496, 3624, 3624, 3624, 3752, 3752, 3880, 3880, 3880, 4008, + 4008, 4008, 4136, 4136, 4136, 4264, 4264, 4264, 4392, 4392, 4392, + 4584, 4584, 4584, 4584, 4584, 4776, 4776, 4776, 4776, 4968, 4968}, + { 40, 104, 176, 208, 256, 328, 392, 440, 504, 568, 616, + 680, 744, 808, 872, 904, 968, 1032, 1096, 1160, 1224, 1256, + 1320, 1384, 1416, 1480, 1544, 1608, 1672, 1736, 1800, 1864, 1928, + 1992, 2024, 2088, 2152, 2216, 2280, 2344, 2408, 2472, 2536, 2536, + 2600, 2664, 2728, 2792, 2856, 2856, 2984, 2984, 3112, 3112, 3240, + 3240, 3368, 3368, 3496, 3496, 3624, 3624, 3624, 3752, 3752, 3880, + 3880, 4008, 4008, 4136, 4136, 4264, 4264, 4392, 4392, 4392, 4584, + 4584, 4584, 4776, 4776, 4776, 4776, 4968, 4968, 4968, 5160, 5160, + 5160, 5352, 5352, 5352, 5352, 5544, 5544, 5544, 5736, 5736, 5736, + 5736, 5992, 5992, 5992, 5992, 6200, 6200, 6200, 6200, 6456, 6456}, + { 56, 120, 208, 256, 328, 408, 488, 552, 632, 696, 776, + 840, 904, 1000, 1064, 1128, 1192, 1288, 1352, 1416, 1480, 1544, + 1608, 1736, 1800, 1864, 1928, 1992, 2088, 2152, 2216, 2280, 2344, + 2408, 2472, 2600, 2664, 2728, 2792, 2856, 2984, 2984, 3112, 3112, + 3240, 3240, 3368, 3496, 3496, 3624, 3624, 3752, 3752, 3880, 4008, + 4008, 4136, 4136, 4264, 4264, 4392, 4392, 4584, 4584, 4584, 4776, + 4776, 4968, 4968, 4968, 5160, 5160, 5160, 5352, 5352, 5544, 5544, + 5544, 5736, 5736, 5736, 5992, 5992, 5992, 5992, 6200, 6200, 6200, + 6456, 6456, 6456, 6456, 6712, 6712, 6712, 6968, 6968, 6968, 6968, + 7224, 7224, 7224, 7480, 7480, 7480, 7480, 7736, 7736, 7736, 7992}, + { 72, 144, 224, 328, 424, 504, 600, 680, 776, 872, 968, + 1032, 1128, 1224, 1320, 1384, 1480, 1544, 1672, 1736, 1864, 1928, + 2024, 2088, 2216, 2280, 2344, 2472, 2536, 2664, 2728, 2792, 2856, + 2984, 3112, 3112, 3240, 3368, 3496, 3496, 3624, 3752, 3752, 3880, + 4008, 4008, 4136, 4264, 4392, 4392, 4584, 4584, 4776, 4776, 4776, + 4968, 4968, 5160, 5160, 5352, 5352, 5544, 5544, 5736, 5736, 5736, + 5992, 5992, 5992, 6200, 6200, 6200, 6456, 6456, 6712, 6712, 6712, + 6968, 6968, 6968, 7224, 7224, 7224, 7480, 7480, 7480, 7736, 7736, + 7736, 7992, 7992, 7992, 8248, 8248, 8248, 8504, 8504, 8760, 8760, + 8760, 8760, 9144, 9144, 9144, 9144, 9528, 9528, 9528, 9528, 9528}, + { 328, 176, 256, 392, 504, 600, 712, 808, 936, 1032, 1128, + 1224, 1352, 1480, 1544, 1672, 1736, 1864, 1992, 2088, 2216, 2280, + 2408, 2472, 2600, 2728, 2792, 2984, 2984, 3112, 3240, 3368, 3496, + 3496, 3624, 3752, 3880, 4008, 4136, 4136, 4264, 4392, 4584, 4584, + 4776, 4776, 4968, 4968, 5160, 5160, 5352, 5352, 5544, 5736, 5736, + 5992, 5992, 5992, 6200, 6200, 6456, 6456, 6456, 6712, 6712, 6968, + 6968, 6968, 7224, 7224, 7480, 7480, 7736, 7736, 7736, 7992, 7992, + 8248, 8248, 8248, 8504, 8504, 8760, 8760, 8760, 9144, 9144, 9144, + 9144, 9528, 9528, 9528, 9528, 9912, 9912, 9912,10296,10296,10296, + 10296,10680,10680,10680,10680,11064,11064,11064,11448,11448,11448}, + { 104, 224, 328, 472, 584, 712, 840, 968, 1096, 1224, 1320, + 1480, 1608, 1672, 1800, 1928, 2088, 2216, 2344, 2472, 2536, 2664, + 2792, 2984, 3112, 3240, 3368, 3368, 3496, 3624, 3752, 3880, 4008, + 4136, 4264, 4392, 4584, 4584, 4776, 4968, 4968, 5160, 5352, 5352, + 5544, 5736, 5736, 5992, 5992, 6200, 6200, 6456, 6456, 6712, 6712, + 6712, 6968, 6968, 7224, 7224, 7480, 7480, 7736, 7736, 7992, 7992, + 8248, 8248, 8504, 8504, 8760, 8760, 8760, 9144, 9144, 9144, 9528, + 9528, 9528, 9912, 9912, 9912,10296,10296,10296,10680,10680,10680, + 11064,11064,11064,11448,11448,11448,11448,11832,11832,11832,12216, + 12216,12216,12576,12576,12576,12960,12960,12960,12960,13536,13536}, + { 120, 256, 392, 536, 680, 808, 968, 1096, 1256, 1384, 1544, + 1672, 1800, 1928, 2088, 2216, 2344, 2536, 2664, 2792, 2984, 3112, + 3240, 3368, 3496, 3624, 3752, 3880, 4008, 4264, 4392, 4584, 4584, + 4776, 4968, 4968, 5160, 5352, 5544, 5544, 5736, 5992, 5992, 6200, + 6200, 6456, 6456, 6712, 6968, 6968, 7224, 7224, 7480, 7480, 7736, + 7736, 7992, 7992, 8248, 8504, 8504, 8760, 8760, 9144, 9144, 9144, + 9528, 9528, 9528, 9912, 9912, 9912,10296,10296,10680,10680,10680, + 11064,11064,11064,11448,11448,11448,11832,11832,12216,12216,12216, + 12576,12576,12576,12960,12960,12960,13536,13536,13536,13536,14112, + 14112,14112,14112,14688,14688,14688,14688,15264,15264,15264,15264}, + { 136, 296, 456, 616, 776, 936, 1096, 1256, 1416, 1544, 1736, + 1864, 2024, 2216, 2344, 2536, 2664, 2856, 2984, 3112, 3368, 3496, + 3624, 3752, 4008, 4136, 4264, 4392, 4584, 4776, 4968, 5160, 5160, + 5352, 5544, 5736, 5736, 5992, 6200, 6200, 6456, 6712, 6712, 6968, + 6968, 7224, 7480, 7480, 7736, 7992, 7992, 8248, 8248, 8504, 8760, + 8760, 9144, 9144, 9144, 9528, 9528, 9912, 9912,10296,10296,10296, + 10680,10680,11064,11064,11064,11448,11448,11832,11832,11832,12216, + 12216,12576,12576,12960,12960,12960,13536,13536,13536,13536,14112, + 14112,14112,14112,14688,14688,14688,15264,15264,15264,15264,15840, + 15840,15840,16416,16416,16416,16416,16992,16992,16992,16992,17568}, + { 144, 328, 504, 680, 872, 1032, 1224, 1384, 1544, 1736, 1928, + 2088, 2280, 2472, 2664, 2792, 2984, 3112, 3368, 3496, 3752, 3880, + 4008, 4264, 4392, 4584, 4776, 4968, 5160, 5352, 5544, 5736, 5736, + 5992, 6200, 6200, 6456, 6712, 6712, 6968, 7224, 7480, 7480, 7736, + 7992, 7992, 8248, 8504, 8504, 8760, 9144, 9144, 9144, 9528, 9528, + 9912, 9912,10296,10296,10680,10680,11064,11064,11448,11448,11448, + 11832,11832,12216,12216,12576,12576,12960,12960,12960,13536,13536, + 13536,14112,14112,14112,14688,14688,14688,14688,15264,15264,15264, + 15840,15840,15840,16416,16416,16416,16992,16992,16992,16992,17568, + 17568,17568,18336,18336,18336,18336,18336,19080,19080,19080,19080}, + { 176, 376, 584, 776, 1000, 1192, 1384, 1608, 1800, 2024, 2216, + 2408, 2600, 2792, 2984, 3240, 3496, 3624, 3880, 4008, 4264, 4392, + 4584, 4776, 4968, 5352, 5544, 5736, 5992, 5992, 6200, 6456, 6712, + 6968, 6968, 7224, 7480, 7736, 7736, 7992, 8248, 8504, 8760, 8760, + 9144, 9144, 9528, 9528, 9912, 9912,10296,10680,10680,11064,11064, + 11448,11448,11832,11832,12216,12216,12576,12576,12960,12960,13536, + 13536,13536,14112,14112,14112,14688,14688,14688,15264,15264,15840, + 15840,15840,16416,16416,16416,16992,16992,16992,17568,17568,17568, + 18336,18336,18336,18336,19080,19080,19080,19080,19848,19848,19848, + 19848,20616,20616,20616,21384,21384,21384,21384,22152,22152,22152}, + { 208, 440, 680, 904, 1128, 1352, 1608, 1800, 2024, 2280, 2472, + 2728, 2984, 3240, 3368, 3624, 3880, 4136, 4392, 4584, 4776, 4968, + 5352, 5544, 5736, 5992, 6200, 6456, 6712, 6712, 6968, 7224, 7480, + 7736, 7992, 8248, 8504, 8760, 8760, 9144, 9528, 9528, 9912, 9912, + 10296,10680,10680,11064,11064,11448,11832,11832,12216,12216,12576, + 12576,12960,12960,13536,13536,14112,14112,14112,14688,14688,15264, + 15264,15264,15840,15840,16416,16416,16416,16992,16992,17568,17568, + 17568,18336,18336,18336,19080,19080,19080,19080,19848,19848,19848, + 20616,20616,20616,21384,21384,21384,21384,22152,22152,22152,22920, + 22920,22920,23688,23688,23688,23688,24496,24496,24496,24496,25456}, + { 224, 488, 744, 1000, 1256, 1544, 1800, 2024, 2280, 2536, 2856, + 3112, 3368, 3624, 3880, 4136, 4392, 4584, 4968, 5160, 5352, 5736, + 5992, 6200, 6456, 6712, 6968, 7224, 7480, 7736, 7992, 8248, 8504, + 8760, 9144, 9144, 9528, 9912, 9912,10296,10680,10680,11064,11448, + 11448,11832,12216,12216,12576,12960,12960,13536,13536,14112,14112, + 14688,14688,14688,15264,15264,15840,15840,16416,16416,16992,16992, + 16992,17568,17568,18336,18336,18336,19080,19080,19080,19848,19848, + 19848,20616,20616,20616,21384,21384,21384,22152,22152,22152,22920, + 22920,22920,23688,23688,23688,24496,24496,24496,25456,25456,25456, + 25456,26416,26416,26416,26416,27376,27376,27376,27376,28336,28336}, + { 256, 552, 840, 1128, 1416, 1736, 1992, 2280, 2600, 2856, 3112, + 3496, 3752, 4008, 4264, 4584, 4968, 5160, 5544, 5736, 5992, 6200, + 6456, 6968, 7224, 7480, 7736, 7992, 8248, 8504, 8760, 9144, 9528, + 9912, 9912,10296,10680,11064,11064,11448,11832,12216,12216,12576, + 12960,12960,13536,13536,14112,14112,14688,14688,15264,15264,15840, + 15840,16416,16416,16992,16992,17568,17568,18336,18336,18336,19080, + 19080,19848,19848,19848,20616,20616,20616,21384,21384,22152,22152, + 22152,22920,22920,22920,23688,23688,24496,24496,24496,25456,25456, + 25456,25456,26416,26416,26416,27376,27376,27376,28336,28336,28336, + 28336,29296,29296,29296,29296,30576,30576,30576,30576,31704,31704}, + { 280, 600, 904, 1224, 1544, 1800, 2152, 2472, 2728, 3112, 3368, + 3624, 4008, 4264, 4584, 4968, 5160, 5544, 5736, 6200, 6456, 6712, + 6968, 7224, 7736, 7992, 8248, 8504, 8760, 9144, 9528, 9912,10296, + 10296,10680,11064,11448,11832,11832,12216,12576,12960,12960,13536, + 13536,14112,14688,14688,15264,15264,15840,15840,16416,16416,16992, + 16992,17568,17568,18336,18336,18336,19080,19080,19848,19848,20616, + 20616,20616,21384,21384,22152,22152,22152,22920,22920,23688,23688, + 23688,24496,24496,24496,25456,25456,25456,26416,26416,26416,27376, + 27376,27376,28336,28336,28336,29296,29296,29296,29296,30576,30576, + 30576,30576,31704,31704,31704,31704,32856,32856,32856,34008,34008}, + { 328, 632, 968, 1288, 1608, 1928, 2280, 2600, 2984, 3240, 3624, + 3880, 4264, 4584, 4968, 5160, 5544, 5992, 6200, 6456, 6712, 7224, + 7480, 7736, 7992, 8504, 8760, 9144, 9528, 9912, 9912,10296,10680, + 11064,11448,11832,12216,12216,12576,12960,13536,13536,14112,14112, + 14688,14688,15264,15840,15840,16416,16416,16992,16992,17568,17568, + 18336,18336,19080,19080,19848,19848,19848,20616,20616,21384,21384, + 22152,22152,22152,22920,22920,23688,23688,24496,24496,24496,25456, + 25456,25456,26416,26416,26416,27376,27376,27376,28336,28336,28336, + 29296,29296,29296,30576,30576,30576,30576,31704,31704,31704,31704, + 32856,32856,32856,34008,34008,34008,34008,35160,35160,35160,35160}, + { 336, 696, 1064, 1416, 1800, 2152, 2536, 2856, 3240, 3624, 4008, + 4392, 4776, 5160, 5352, 5736, 6200, 6456, 6712, 7224, 7480, 7992, + 8248, 8760, 9144, 9528, 9912,10296,10296,10680,11064,11448,11832, + 12216,12576,12960,13536,13536,14112,14688,14688,15264,15264,15840, + 16416,16416,16992,17568,17568,18336,18336,19080,19080,19848,19848, + 20616,20616,20616,21384,21384,22152,22152,22920,22920,23688,23688, + 24496,24496,24496,25456,25456,26416,26416,26416,27376,27376,27376, + 28336,28336,29296,29296,29296,30576,30576,30576,30576,31704,31704, + 31704,32856,32856,32856,34008,34008,34008,35160,35160,35160,35160, + 36696,36696,36696,36696,37888,37888,37888,39232,39232,39232,39232}, + { 376, 776, 1160, 1544, 1992, 2344, 2792, 3112, 3624, 4008, 4392, + 4776, 5160, 5544, 5992, 6200, 6712, 7224, 7480, 7992, 8248, 8760, + 9144, 9528, 9912,10296,10680,11064,11448,11832,12216,12576,12960, + 13536,14112,14112,14688,15264,15264,15840,16416,16416,16992,17568, + 17568,18336,18336,19080,19080,19848,19848,20616,21384,21384,22152, + 22152,22920,22920,23688,23688,24496,24496,24496,25456,25456,26416, + 26416,27376,27376,27376,28336,28336,29296,29296,29296,30576,30576, + 30576,31704,31704,31704,32856,32856,32856,34008,34008,34008,35160, + 35160,35160,36696,36696,36696,37888,37888,37888,37888,39232,39232, + 39232,40576,40576,40576,40576,42368,42368,42368,42368,43816,43816}, + { 408, 840, 1288, 1736, 2152, 2600, 2984, 3496, 3880, 4264, 4776, + 5160, 5544, 5992, 6456, 6968, 7224, 7736, 8248, 8504, 9144, 9528, + 9912,10296,10680,11064,11448,12216,12576,12960,13536,13536,14112, + 14688,15264,15264,15840,16416,16992,16992,17568,18336,18336,19080, + 19080,19848,20616,20616,21384,21384,22152,22152,22920,22920,23688, + 24496,24496,25456,25456,25456,26416,26416,27376,27376,28336,28336, + 29296,29296,29296,30576,30576,30576,31704,31704,32856,32856,32856, + 34008,34008,34008,35160,35160,35160,36696,36696,36696,37888,37888, + 37888,39232,39232,39232,40576,40576,40576,40576,42368,42368,42368, + 43816,43816,43816,43816,45352,45352,45352,46888,46888,46888,46888}, + { 440, 904, 1384, 1864, 2344, 2792, 3240, 3752, 4136, 4584, 5160, + 5544, 5992, 6456, 6968, 7480, 7992, 8248, 8760, 9144, 9912,10296, + 10680,11064,11448,12216,12576,12960,13536,14112,14688,14688,15264, + 15840,16416,16992,16992,17568,18336,18336,19080,19848,19848,20616, + 20616,21384,22152,22152,22920,22920,23688,24496,24496,25456,25456, + 26416,26416,27376,27376,28336,28336,29296,29296,29296,30576,30576, + 31704,31704,31704,32856,32856,34008,34008,34008,35160,35160,35160, + 36696,36696,36696,37888,37888,39232,39232,39232,40576,40576,40576, + 42368,42368,42368,42368,43816,43816,43816,45352,45352,45352,46888, + 46888,46888,46888,48936,48936,48936,48936,48936,51024,51024,51024}, + { 488, 1000, 1480, 1992, 2472, 2984, 3496, 4008, 4584, 4968, 5544, + 5992, 6456, 6968, 7480, 7992, 8504, 9144, 9528, 9912,10680,11064, + 11448,12216,12576,12960,13536,14112,14688,15264,15840,15840,16416, + 16992,17568,18336,18336,19080,19848,19848,20616,21384,21384,22152, + 22920,22920,23688,24496,24496,25456,25456,26416,26416,27376,27376, + 28336,28336,29296,29296,30576,30576,31704,31704,31704,32856,32856, + 34008,34008,35160,35160,35160,36696,36696,36696,37888,37888,39232, + 39232,39232,40576,40576,40576,42368,42368,42368,43816,43816,43816, + 45352,45352,45352,46888,46888,46888,46888,48936,48936,48936,48936, + 51024,51024,51024,51024,52752,52752,52752,52752,55056,55056,55056}, + { 520, 1064, 1608, 2152, 2664, 3240, 3752, 4264, 4776, 5352, 5992, + 6456, 6968, 7480, 7992, 8504, 9144, 9528,10296,10680,11448,11832, + 12576,12960,13536,14112,14688,15264,15840,16416,16992,16992,17568, + 18336,19080,19080,19848,20616,21384,21384,22152,22920,22920,23688, + 24496,24496,25456,25456,26416,27376,27376,28336,28336,29296,29296, + 30576,30576,31704,31704,32856,32856,34008,34008,34008,35160,35160, + 36696,36696,36696,37888,37888,39232,39232,40576,40576,40576,42368, + 42368,42368,43816,43816,43816,45352,45352,45352,46888,46888,46888, + 48936,48936,48936,48936,51024,51024,51024,51024,52752,52752,52752, + 55056,55056,55056,55056,57336,57336,57336,57336,59256,59256,59256}, + { 552, 1128, 1736, 2280, 2856, 3496, 4008, 4584, 5160, 5736, 6200, + 6968, 7480, 7992, 8504, 9144, 9912,10296,11064,11448,12216,12576, + 12960,13536,14112,14688,15264,15840,16416,16992,17568,18336,19080, + 19848,19848,20616,21384,22152,22152,22920,23688,24496,24496,25456, + 25456,26416,27376,27376,28336,28336,29296,29296,30576,30576,31704, + 31704,32856,32856,34008,34008,35160,35160,36696,36696,37888,37888, + 37888,39232,39232,40576,40576,40576,42368,42368,43816,43816,43816, + 45352,45352,45352,46888,46888,46888,48936,48936,48936,51024,51024, + 51024,51024,52752,52752,52752,55056,55056,55056,55056,57336,57336, + 57336,57336,59256,59256,59256,59256,61664,61664,61664,61664,63776}, + { 584, 1192, 1800, 2408, 2984, 3624, 4264, 4968, 5544, 5992, 6712, + 7224, 7992, 8504, 9144, 9912,10296,11064,11448,12216,12960,13536, + 14112,14688,15264,15840,16416,16992,17568,18336,19080,19848,19848, + 20616,21384,22152,22920,22920,23688,24496,25456,25456,26416,26416, + 27376,28336,28336,29296,29296,30576,31704,31704,32856,32856,34008, + 34008,35160,35160,36696,36696,36696,37888,37888,39232,39232,40576, + 40576,42368,42368,42368,43816,43816,45352,45352,45352,46888,46888, + 46888,48936,48936,48936,51024,51024,51024,52752,52752,52752,52752, + 55056,55056,55056,57336,57336,57336,57336,59256,59256,59256,61664, + 61664,61664,61664,63776,63776,63776,63776,66592,66592,66592,66592}, + { 616, 1256, 1864, 2536, 3112, 3752, 4392, 5160, 5736, 6200, 6968, + 7480, 8248, 8760, 9528,10296,10680,11448,12216,12576,13536,14112, + 14688,15264,15840,16416,16992,17568,18336,19080,19848,20616,20616, + 21384,22152,22920,23688,24496,24496,25456,26416,26416,27376,28336, + 28336,29296,29296,30576,31704,31704,32856,32856,34008,34008,35160, + 35160,36696,36696,37888,37888,39232,39232,40576,40576,40576,42368, + 42368,43816,43816,43816,45352,45352,46888,46888,46888,48936,48936, + 48936,51024,51024,51024,52752,52752,52752,55056,55056,55056,55056, + 57336,57336,57336,59256,59256,59256,61664,61664,61664,61664,63776, + 63776,63776,63776,66592,66592,66592,66592,68808,68808,68808,71112}, + { 712, 1480, 2216, 2984, 3752, 4392, 5160, 5992, 6712, 7480, 8248, + 8760, 9528,10296,11064,11832,12576,13536,14112,14688,15264,16416, + 16992,17568,18336,19080,19848,20616,21384,22152,22920,23688,24496, + 25456,25456,26416,27376,28336,29296,29296,30576,30576,31704,32856, + 32856,34008,35160,35160,36696,36696,37888,37888,39232,40576,40576, + 40576,42368,42368,43816,43816,45352,45352,46888,46888,48936,48936, + 48936,51024,51024,52752,52752,52752,55056,55056,55056,55056,57336, + 57336,57336,59256,59256,59256,61664,61664,61664,63776,63776,63776, + 66592,66592,66592,68808,68808,68808,71112,71112,71112,73712,73712, + 75376,75376,75376,75376,75376,75376,75376,75376,75376,75376,75376}}; diff --git a/lte/lib/phch/test/dci_unpacking.c b/lte/lib/phch/test/dci_unpacking.c new file mode 100644 index 000000000..97b3266ee --- /dev/null +++ b/lte/lib/phch/test/dci_unpacking.c @@ -0,0 +1,105 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte.h" + +void usage(char *prog) { + printf("Usage: %s nof_prb length_bits Word0 Word1 ...\n", prog); +} + +int main(int argc, char **argv) { + dci_msg_t msg; + ra_pdsch_t ra_dl; + ra_pdsch_t ra_ul; + int len, rlen; + int nof_prb; + int nwords; + int i; + char *y; + + if (argc < 3) { + usage(argv[0]); + exit(-1); + } + + nof_prb = atoi(argv[1]); + len = atoi(argv[2]); + + nwords = (len-1)/32+1; + + if (argc < 3 + nwords) { + usage(argv[0]); + exit(-1); + } + + y = msg.data; + rlen = 0; + unsigned int x; + for (i=0;i +#include +#include +#include +#include + +#include "lte.h" + +char *input_file_name = NULL; +char *matlab_file_name = NULL; +int cell_id = 0; +int cfi = 2; +lte_cp_t cp = CPNORM; +int nof_prb = 6; +int nof_ports = 1; +int flen; +unsigned short rnti = SIRNTI; +int max_frames = 10; +FILE *fmatlab = NULL; + +filesource_t fsrc; +pdcch_t pdcch; +cf_t *input_buffer, *fft_buffer, *ce[MAX_PORTS_CTRL]; +regs_t regs; +lte_fft_t fft; +chest_t chest; +dci_t dci_rx; + +void usage(char *prog) { + printf("Usage: %s [vcfoe] -i input_file\n", prog); + printf("\t-o output matlab file name [Default Disabled]\n"); + printf("\t-c cell_id [Default %d]\n", cell_id); + printf("\t-f cfi [Default %d]\n", cfi); + printf("\t-r rnti [Default SI-RNTI]\n"); + printf("\t-p nof_ports [Default %d]\n", nof_ports); + printf("\t-n nof_prb [Default %d]\n", nof_prb); + printf("\t-m max_frames [Default %d]\n", max_frames); + printf("\t-e Set extended prefix [Default Normal]\n"); + printf("\t-v [set verbose to debug, default none]\n"); +} + +void parse_args(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "irovfcenmp")) != -1) { + switch(opt) { + case 'i': + input_file_name = argv[optind]; + break; + case 'c': + cell_id = atoi(argv[optind]); + break; + case 'r': + rnti = strtoul(argv[optind], NULL, 0); + break; + case 'm': + max_frames = strtoul(argv[optind], NULL, 0); + break; + case 'f': + cfi = atoi(argv[optind]); + break; + case 'n': + nof_prb = atoi(argv[optind]); + break; + case 'p': + nof_ports = atoi(argv[optind]); + break; + case 'o': + matlab_file_name = argv[optind]; + break; + case 'v': + verbose++; + break; + case 'e': + cp = CPEXT; + break; + default: + usage(argv[0]); + exit(-1); + } + } + if (!input_file_name) { + usage(argv[0]); + exit(-1); + } +} + +int base_init() { + int i; + + if (filesource_init(&fsrc, input_file_name, COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", input_file_name); + exit(-1); + } + + if (matlab_file_name) { + fmatlab = fopen(matlab_file_name, "w"); + if (!fmatlab) { + perror("fopen"); + return -1; + } + } else { + fmatlab = NULL; + } + + flen = 2 * (SLOT_LEN(lte_symbol_sz(nof_prb), cp)); + + input_buffer = malloc(flen * sizeof(cf_t)); + if (!input_buffer) { + perror("malloc"); + exit(-1); + } + + fft_buffer = malloc(CP_NSYMB(cp) * nof_prb * RE_X_RB * sizeof(cf_t)); + if (!fft_buffer) { + perror("malloc"); + return -1; + } + + for (i=0;ie84awa|t$iw!?D~v%&J-RG7PG8E-MK9qg)xG>1gl&82?VFBhP|xv;O0ChP-;-xd znz=6;qiMscIC@}2HNSDZKb@VNfXeqAp(yc)LdO6NkL#r5S0y?2;b4=E6)`toaEeeBJ07g^{-vBk?A5imI zH~+q@6FGAc{cqtweq`YAioWz)G$ExD{0Hr%H-FuRMFT`!>ZtwM2fEp7Yzfm`4bPwmD%rfRu)d~?cE8d($r4KKHGTlymK zU`HyhnO}?cIdRbE@Dw#>IZ?ysFVJvVGJiDtJicw{fxz)HoUL8}nGkh~M%${;kW-7O zu&18+B!7jA>yAcE&%IRr(|QaKoFshR%yY@F+yr)Q6jl5$fvU^8^PgTAp{D&ov`k3A z?48BPvhlcL^<5HNvIjA24Iel4CMpRj_;%uN4EkPy*EG&xu|^diHK(8VKlqU9Px(YU zBJ}A{qdF=II}4Rx9B9wg2s%=F82zCY=C5;Pc|K$VZ0kw-m4pltq3YWYr$-!`zE z&w;D-tX4Mky}Cx5S|-xA_>E#mFc1zUEk-MsN*ZL92nP;xIOAvpof|rUwK$bVceP#Q z?^);ZcYMdt%ctYWjz8wCT9_r}9%ZpBGdIzdPFn1r!!_{S%7C8O*MZ&}jcDl6gV^XF z1*Z=h(-RxV&~KxS=(WZys`%mJgN^g|*yEx;aDcdg`X z|2o1F`4e1FoFljid%1iu1}sL~aG6~Q55F66Q{ByAk|uJCM;d|pTYIkXggb1tnZQXk zxqx@EH#gwza+_P79u>BCriMQFp)Qg7v--u{6TpD|CbDCoTCD z^9Zond5UvfmjWdNXE8~(-K4rIlz)?-2!E|D8O^2;&^nz;_WFDvl^w<6qnrFeZf$FM zV9|V7^8FEa`i22aZ>->IV!~ltmm6nuIEQp^n8f>g_>e;TJ>0vJO`w~rz%kB~;BWIa zPJK@>7^^)I_x<&P^7xh9i8c1HQge^^!bn%R%z24z$wY9jbP#{jjR5JgWt`cURZwEG zg|n;lgDIt_#3uthAoI#BuFTC1{B7QIJ165 ze$}Z(6-yQ;z@!`B#cQl=A*y>hS25=zu?^eB_&!R66{obt#BVXQx$1KVKTL%$){UYW zRr8_vR-MTBmL|kqy(hMhmXrVmGymla3g{u*=n)-t$HhZ(znf-Lw*(p48=RcX@zun+cbFb}gii9K+qwT?ko* zN!;?UlR+=)gBXqkK*8lX;_=)4VU7GpvA<0u^laDVX7+Ag?=zC zS(h{3=LK6skC*Rm+Xx#B@|5Jrm6PAj92Udag<|*8Zdwbx4PZpzCH4?7RR~A=x z&V^4wC(Em~BOorgN;D_H6WZUOF1KK&!Qq%3=7q8+a9-&Z^+)Yt_u_JLjc$NnLsZDB zq%b(}Yo=H?Boq!0xh>XO7z9_XW0}~V4bbSM&y9ZDP7cf-Om>9!5%WDPd7kS4=5CQ( znT2FMg40D=uNQ#cxv}Ei8^R&jF;SeT5)QAOLzp3+o8i4_PlZa`L0D>jm#OZZ2^V`S z%k{;x!Fk6qX3O{x2z40dw7+Hq&>SF{mR5}i{ z&yC^o%-x~NUXi(RdOM_T+9VFj4}quOt3;pQOU~uIImAoL5o$)h6z_WHPJZZl5{tx( zOjSoE`Bcb))N?iQXSYyzaaoq+t@nlqozbLyv3grjU z5ad3AwA-e@sbB@-yxJcWS_d&ZW(2^X9YsvNbpZHGi)H5C4S>9z$C-0`r$hUnET_`A z>7c3gklCpk0v!d#j7!Wk7*+6ziIfV0616z-$G%{Qy>&<=(H{Tb-_<|7VTsNIX6w%X zAjanh!<|V1+Jd(`&Fmt$fB|E$k{{GH)AF|o9!o#rLM5()>LkD+%izM_{&sQ zID@=>M)_6$>7XWag0cHK6?EOAnK<>Skm#|2iF0>`$Elg+m5t`0C-3tcsGjd!IGfzLz1pN=86wXt4NRb|_?O+!AF(xByczm|*8*_|L4EaeLO%gZZ=&ks-k@&QDel#9hJ>j@i05fX(9X~&1*RhC&9ETf^a!jnu_w_Q z79buqjYNI41T`?PxF0~{MrTGMX=<%Q#zi9<(%)eB4Vc#${A1bdLVTP)%8jH%@4qp9#cC7W2yoete+LdnJT zqv4RMGxNxGDja#%!rYlY2b81zNcyYM@ZT_F61U6{dIcv^xp@q9+nbU}kL+NCZX#K= zNelicn2@&PUXU?(Gr9ZK62ALPBUoVpz25@K*EP0KUNemtSsTF04kNNiZ5-t64<`d< zeBi-#114mu4iv`yW$axIK%$L_xeUIr>rKe=bSk!@K)kw(DVGur(CAw?ci2pYQV!K5Vq=$Afo$m6`=XaXfp!bLj zGg4^R96lJR}~*3m!J-IWs}IpRx{{N*g}TJYCyF1 zB6621z{hQtqFZ;Y%}D>vP9n3ihLL{NOKcP0F>P(H$h7qaWWqHQFl|&I%aZg# zDq4-Cy30Uau85gGRR$t6UNF!9w2)h7YD6sig(M&U#Pr6!C89(*vcTsH*%YTmMo7N9 zt5l9e*>;e=7cIYe(SO17DtcsPKFFMlC?-{<)=^R1qf)2ClC?jv(aK*V6N zTT2JA-#JFqnR%OBd^Uz$b!a7a9T8+oe?2*LYB?jN_>laVxu2=yu9F8^`@9a%{&*=8x1>^8VQUvWF|0NZ*LrjDlSWY2W^sap}H8HV-(&EH8RQ znxcxB&=4_k&WvF0{OlpKmrW!qCmd%ogKl%t-zq_|Hy>8j9>ki%^Kqrz9-NZ31BTt{ z10Pj2=o?_hw?AKszF|7l=*ehYq6u_)cpl$A<}yFa=N-Q-KSaDiZx)f$Rs+c$gs*~u zK_z*zWw6Nc&O*#emB-rKR=DB(3N&dx#!Iv%T6L(>8|gjJYNWyYe+^=$w}$d@#n-s_ z3szK4B*QEEY17F51pfV`4*vT55`O+4BZ?|#`D+UmX!X~h#3Sqmzl{yVSuYZ3)~fM% zcknTKVvIkj{4a?Z?=Jw2q1#Jiof!c+G9aB09|j9I@A|8t+iFL>y{ zubZAiZ!XuMdJczar}#RNKeUY&sV)#FWqjpl??|Wr)*@iw4qB`_7?&)W&3o}OSn_r+ z@4p}goNP^LPUjF%R;}eXhvgDw!xh~6Wf|P>#-8Omvho4q5wynqE`>27?p70jVt^WWQ)W)Deku%=7A&Vv_ z55mKP??C8Pr7LGu;Dz#j{>uUtw)CeJl>2YODL41=o~;2?Dd7U$bYucuTz`NHKgZ+J z^iVjs`6Bw9WpT){L>yH%jx3sJh2?QcB!5hS__)$!T$5u@k7(QA(~Xm9pwkVWUth^r zDQoZ&-Ot6`QsC>iDPhCueE?%hQJk27yLacotjIHTn}Qc*oks~bSMdD#-@B;a@H~FY zu-jm|WGj8Sb{!5g@1$-!kD{_wKmG3U6mw)`h1#G#9J|y=koZh!a@=r=`dWeI4xC5t2aC{mViF&@dmH%u(nB~R z$Cp)}{1!#oChX;!W_%xR1w)3+fr!&rsQ-Z*RPqf9zba`)&tAHrcsZ;pTut-SGO>Au z5uNl*o`&45V{-ng2tf;`qxFx+^q!SFu0A=KsIa&B;?giYmfu7tXbnfVn`%Oi@l8}+ zafF6$8^<2CV9;deO|&>ujll=Hs8pdNdi6vIGk3m}oJo#`sq50k&JEa{oB)FTQ1)%Z ze!Om3h#^BSp`mvREUOuThYg!JJ--!HG%1N?UP|%_<=<&>pv*)EE3?6X^!i)%=IyjL0o z`TloT`A2I{(p$Taac?}c=zuj&0(W15Zi`M462_U*i6<}6*8lF(Q$MWOxuM2FOVdoY zR9#l+EP8>O-pVxm)(h;mJPjRFmar)bdmyATj+M>43S(9^VoRPSR?W)ebuQeQZHe^K-wOp)) zJ=JDxuu3TXIy0HAt4|@dbG6vNm(JtI6R{BTat;QzrlIVt1$1g0!;arkMjMtJvA-vD zgN>XSE?4zr%Nr(hO1aVOX7}~5qvk38%o7( ze%!~$NvZtdN9EkX1rJGNMm2TL;BkCv5xw=W7B{AhCRrsOqMDqw7?~bGJ**bv0gaas zb8a|3x+%*Vb??Kx(Dyi}SitN`6<+XIz#O07L(4wP3zvN?1q(+bLCQ;3sH$_IFK2V~ zr%WCgarJbSV+b|z(G>jMd+FmOCE;3J6m{VhdFK^-QB={4?aBA?wR8q<8WRidD+*}V zNgcW_%1k)1=P_S)Kc8NEuFlI`-Vgt&5!&Q48PDx`OAT66@kIMT;d06Yto&L}Yd81e z`UNiZh>R4B8z#q3zh)|IEwV%pv(dsu1wFQ}Wiwrv;K}OzP?LDk+U&dARn)*_7cNP5 z7GfTXnF_yOv|*+i^(&c-AIvAyskZ(2$LB8}e<>Ce>s-KBNl!T0QiAs^#|V20t=X7? zsnnHqXR|D{iDIe|8*NucpKcnBKW~i@tow%2akkHCTy74pq%|3f2gSix&}Em%TXN5C z#G!|BHod;*9Mv6ol$viGD*U==gs(;n6}~qPVAmQM3juRq0Ar{nXsHbpv`P<4at#$B zxa$w8J*SHePTG<^uwv_~GvJfwD~wZh$6ZBzyu;*=SY|<~dSEXKho4d7`GFXvI8@NG z@`4dc->C7x417O#H#~IHW^ukE`|Y?iJMq6`xcmGwh|3M8y+?M_EZ^z$+xS=XP-Hb8 z=&qtUIGv3PGou00T)_nWyByhFvCKa)1HcVoOV*H zdu?>T)+K(_L0cjHO*eh;VlCA<>?(xDkHAe&c2M7gUUv4k+55NyrJagM zHNAk&zOur|MfA4+OaACaWl8<6h0XIdg%;k4O6p#4*e@@Pf1N^$pET2m zmI1iqr4`Pdd>)T0S+L@!wWxOJBJ$3L|6lh6VGTavoCW7KQh5KiKo=|U)cHg{Um!nL zFbJrjrSUoRcfGBUx~C3TYwe}=N90(=SNE}E`*LC>XTbUe9j1#aig4&!8#s_JkEZHs z>~ZTr*!tR%{hXanC3zq23^5U6Ozz^RszUngojjYI`US5yD6pl8z&5=oz*<)uH0v1* zbCtzZ@3${q!hhpG1YV^>+d6S|LX|{6jc5JNkEXek8MgU`G$j1;XR{4nOYX@YZ0JZw z8nrnI!!zq>L-jWL=x!5zC-;Q^o;#QR>%+7*5MzJ;!a31etj?k|KKr#2yQNiG2oDIt z^_Mh+g4wfS_KOcxuW1MReirBhUWR>}Ysl&DeTb{4DYHMIiaPXDE)+77A;ZQGRzg zmAPaC8Fp3J6g3Lk9EPxq=JxV;n*u@FqlRx>rY{`4xCOQAZGsNe%d+QPZJJpTBsCZufogsE#P z;ao@%9-}U}YI`}q)9nv0Jt>WTzLf}OVK@03S#R*w10Dx?&Bk8uQS9K8a&*CnHq2Z0 zfqP^UhSjMLz;E9xoI6pUp4gzkeoM-sp_-Q1{L3C1V;s2`2iM^6;T{+mSzZ4CzR ztcB_VRpBZ&Qjh&sf||x|iGO{MUvps~L?D6mexG(q_zodE}o^U3D&VwTSX>@=eE>*zS zd~e5p^Itu11HH7;OlbYjNAR`!0Z%3-(fXdB_)}I9htJN(-))if^T0N|y;_z&wsT^) zKXzb0Z?422A@S@KsW6Jxr*Md{h3@b!rNdS|A@4H#>ENR~xns5m=&9)jbaRXin54Yq zzaRPrmd~x}>c$gPLqCmP+^;Ky96!#VKI%u7AJWFUREC{9sg7TrE6@6z(-w@LUE|LM zi3G*JqB9!l1t7~y~}5q-3yR9X0H)IdF* z*1&dkNtXOBh`m3eoLlUA5%+G9#%%|N2%o%)u;cq6p*W`jTt}_rUVT;(Iz!FKfjoP` z_PRg4V*H-Bp0SQE)`&+|eg`VuC@1fVui}z13#jM#6l^{fN7q{(hKmz6(cLQAAhlrv z`8#SK-mh=R;aMu|*uE2(bh8rmf8@}`L9h9gja9Uz@dYic>HvAohqTO00bd71(OB;z zwBwr$9kBW=HCeuarr8DYI-MtIOi3EQ_}MrzYp@=DZ=u14oHHWh$IG*sBgXN5x1;Ew zXaCVrcQ3ZPq8%dmgJ10^0N-p-tbu|K_W&$I>n za$a(u?9ijN(bB?PvvH_1vL7VvVi;Ljm-iZD& zpXr5ZGudU{=I9b*%@&=MlCT^3XuJ9#>Z@Ks^;f(=#c_zI(Q)~rf)E2jy@Z3 zuA0ugx(EB$`v~8keFBFNA8OQef$uK4hwkH!(9IoBG5*4Pid)oJyI)E|p4Cj&^}+zb z#{3L!`J+zv*lP(s>rY|KJ_TWpOcQE8ZsatIPQ#b77}Oo;N)7d0Sn>N7ZmUR--Q(4Z z&t`w)6$ZY;pKT^|!uCYE-MS4v2T2PF1!gpE!MDjPdWL4C*@1T*x*!O67i;3D-xY z(;YRlY21`O^x2yd`YyAQZdMu~m^7QyjiM-6cl;i{S*Xjhb2QmvwKLe0)yBAH+y9$Y z^wy+4es5U<)$^W>mZ#*IoWMA2R~o>#R77*5En@hn3-Z)4*}vkwyDf%|^5ggXHefej zD(9P?>$3YobKv}QUkLhYiUrPVsM51HjQ^J;^mCD;KTIRoj*({kx~On=*PQWqY1aoV zI5&VjY_J0D!w0iDqC#{HYK6+K)A)7vOImA^gclOm(zuv!7&x;Q*H^f+m!mqcN+iQ> z?YDvVDn)eJiKSGoV2tn|mm<+;YV@jT32k^?gszuec=M79SZ1${pYnZa^7LcG_kl*m zoYt}Q&g)5_7uwHFlYU4q47vuYEviD@XiYj`yfQt!I-3sqy`SWq+s*eIB~!y?3b=Z~ zM*4i~G1#))54xQ4xoYtc-gez4K5t_pEx*@AMtoHx{h2rLh07=8axB@0^PXWs>JrqB z1~?e~oO+ne;BQ%;p|WQ_z?u3;e73d|UasATDcP1Z+i@5_@=qc4&KXVrSZ(C)>K2L) zzwMRGjwYz-x{o14vcRw|oJv25^jWuGhhNNREidK661%7*)5NIZ?)3VmS-kbwW4Mc$ z3&~f`;~1I2g7)nM2v}Do9`t<^^$vdrhC`HuGp5OO{}mhI>q;--gIpy|nsrFn{&X7b zZr>{m>gi+h8l(i(sKHcpX&_4VoPd_@Cs4Jz6Zvx@cN3cYo<~#b2URrgI!c@NY4S^Vo}{wv(ft0TSJdd}4F2``N@^iB66Y1HDm@DaJA@aD#YXg0HTZP{JnoN5u6X;Dn2b#Up zo1ZXaHs2e56?m6RWH{;MlirE>i!s}&zGn?Dw(aA~7g=)M6V>tgBWpT(UJ3@A@2Bo| zYA|)HJ+}7kq)W7>u+oPQ^9~C%*!o4ixOH_MuRE_9mAB~Aza6`&t^Y%CN>mbL%!25! zE#omR~MbY%noC$a$6yV{|> z6)V}lx2P4i3*UAO#93Ejux!CddO`Cmh)Sxs(uMzF^O9UJQ!Yn&>s?fmzwy_m=?IS_ z%>>>=SLmsJLwDC330rsWz|nJALBg70W{!)Xx=g~eogu=rU8l&ffr^4nW(m=7+6_6U zfAh<(w4uGnJ^oXcCLgPnieX2F2=HVphF$TcY4*n?TIZUims$$#J~G&v-JHnUC5^#lRlBh(b%HQz)Os3xKwX%(TH;ZjPsC-<((rY(gv|)tgTv03 z(z>zRanY8+>~*tN{QCAX8(36@m;Zc3mCoB-*`%Su_D4q4aDNA5@J<k1(Z>b$?5+9ZY4MaJ>>A5J zdN=+m<}QBB`_=mk&$~0>WSzATlwHYnTP{KMwapS=rW1c>x(Yu7d@%m5nt*LiM8Z+< ze*5BSQbrcq%##-EpWH^Da}Q~(eHRk_RrH_6EHaRz*SeQs?Z=H6eJ(-d_;w_lHtiAb zu&D*FO;M(Lg~n8J7vhJ-sL-dO({RkJE@T2OVQis0s}^5?EfqSf?5Z0W8eWZSvz*wN zb@_PZQz-jWItx#`*|Nje5?pa!njLXafx1lEi;FW=h1_of?KE~2Dy_E)**BXcJeq-E zW?cz8j(H~Q*Y_@7;981u@j2pmm)qoLgN5f9c zra;=3)=o#7ts#*3g|GK`fc*n&_~s*CWJFvT)#@HD1o441`_*Ckdh!6?bc_lsnK6)Q zX~mX4S<2fr4Z_yb7kJBRd1^Js2e*B&pqU-R*aHtsXlu0#o10Tgr%BzA^xSK_ns_eP zyG@5C+nd52$Db0Ne+?&Ic}0)eR-nDw0p2-dGgTU$%^6P~A>{uZOS45U=&wyybn@*0 zs=DkWuNJE+%$<`;cb!9F`id_!qDBwozpDzyms8lIvC=~B-nDlx>4qQ6kYOtFedwm;a#RPb=I#yi9V;+%~@zx(MR*jGEuudi%NN!vEMm+ zs=Z+qyWmkAmE0d#T?HNP{!I!JmJ>haHgeY}cpa6G#E_VbSL!_+W`U8@iwj zgZFG;BmXR=V_)g8>VZQAw|X&_-A$$Gr{!72na+4wuK~{%?gj4!gtB+%(zvb4!slQc z_)nZiLl>6g{IL@t^6g9hMaM>rG;k1HzOW|zD}OCv2c`lEmtv2)0SF%%48(`8_d=^LpjU4 zxar6h`lnEj?T?nFgA!#~)fva|shkXsaq?gdsvdJCMQhk)S1cuKlV>W5KfGugM< zF8uJ@f$Y28OHfPcK5q4ILG24;X+^6lEgNx>&r=vLXk7b+$tRD}Z$9=c*DwZ?WTe;$ zLLyzf;U%uBG!?!{=8slDGxr&v^F=oe+1GL!RN`&oc!%j|)-f5y-UUz{RgDe4y>!aE z`*>`sl~9zi3MA)H(5So1*B|m0%I2No7OBk?KGrZ)?S4CWfBq@)wUgP#9a6&e+xDzO zLj#@ky-~78oixT|xp1;hSuo2s5+<8}r&IG?{?8OAiccc5P967ZUnCx(FC_CtT9~~< z4U73g%F8v-mBw#S$LbQ5N>*p}BXaSE%na6jGjKz3r=-3({E7u| zW$!00@)Yndi{fdZzaIUtZXI0H9mID$&c&ZW>ZtE*2pxw{z$(82y3%kEEjX7?*L23y zFt4|Km;Fu}+BgxO-pQtm@`mBk>#?+^LLSFf*;5Zi7v65UH%+gdMs@c&(?t=4*6v>e zMg3p-;U;Tw=!$*((fQ|r>MKK6R62i5V=OMuv69S$FqHcv$)tVU(eU?dT5v;7k`<>= z_iq8*S{XT9r~i=Orf?R2%`GMprkxbLoq)wp=F*w|58y{xBFy}wL^r;h zy|DK%k9*as;K6ebEFDrszU?X|(*kq&x7kuycEE#Hd+KAb!W4S#(RGPmK7f81B}Fy= zTgfjxx*d-t9|6f6!*AO)@Jh%Ja8!9j3}1fbpOpXQXIyba30uT3_5Xt{8nf`E{&k$u zlZMh?#8CR(53lR|<%%aMW8=`dd_=1YjE_kcdt2G@tAC2{$HV~sTH=08uanG>y4mQQ znM|LA?}T}Ku29fA$S8jrO(mL*x82@Bc!d$Pr!Iq6*`rMZEc*C)Z5I6JbrfbTSqDEh zy_T?$wz%Y18V$;wgFf$DB>eX&nAdikzneFWKUS%OYg`w@g+_I{?elItqxhQ2KRJO* zThsq#zW6N74@x6HN%ChqtgJjlrZ6(N-|iegSJ4cv-#gC-muS=VpDF)-t}m@vwT^0S zImH*fDWJORmqF~}h5T6^8C=ye7z2x5fnLoa(znzHcgxP_^DfzA*OdY2r`=2Drwzp1 z+%$ew&2su4runXTJKY(s zZ-~XOtT&QfhjC@y3S4xbgX?#baLhVsTG^F~ch64dyq>Tqv;PA8lpYIxQtAAEHlBR% zk79n#s^>f}n~Zm9GN0#s76+&AfTc%UxX~jXz`CJZXsnk%Otoe~@oFP4I#__a2kzu8 zhpY$J3vpn1C4(*%1>n7oW7Hhea8&0=sxtfpTJ2Zi4g0sDzsFsOvssP~1HVG78p1G;zRP&T4}L9sBnWL|fPM38U<}0V|@Q-TeVk zTcC;4)=Q&v`hD2`=rD|*SkLsmw1DErYq;BOb6_d$5|`s9qJwGEp8;*F63B;k zCs|oMN7fV+L>5uX2JYlGJIL69~3)3U^Yfg1M$j940AUS#;l1VpEPGde-aVr zdYORJX+`ei-_f8ud7XHLg$2wjxKx2t8MrJjTj5^KLhy%JQQc5Cs9G=0e6l0p=D3;SCX73LquEPNeRk!U*~ zRJ7y7m)2~9znue#(z9@=^c}>^8)F2QY(yfa%@T$)R?Me-O&D#yn^9Tk2K)D!kQ2j4 z!mip(QMidYqzrn_T=E|WS|=|tS1yf!*yM2L{v1niGtOp8uG@izcL}3WG!n#xSDCA4 zM#1!k3ru!^Eu3zwW;zaAf|uGk=6s|%aCi4H+39AG*_6usdmnIdZ#MHu+Zq<%|K1=31hv*0LF}rV~Td0f@{Tf#v)=EM49F>)O0AUO-X0!QuQDreJOLZ z&lDDI2p9e1L0jtWnES26;J$1sq$s429`X%|wmTfiHh*n7fffz-sq7<^!hz;sHw-D=%%h@ZE!X9jF5uKVz7X zYg&+{6U^9ZsX=;t5)&hNzi>Wu6BE{<4Ud1mV#J%(;pmT2#(J+53}Y0?Xias9imhh; zo>3yE zLsY;qyrf+3(ID75aRlSE`Y&mUdBV*2JrL4F0;Bjt7NkGEV8Wf1Vc4P+X8d7UxT0Lj znBMwJZV&We)?bu|DW<=e;^ETJJ*9|opWa8-{+P@B;~#@Z9AJKrl7l%;mzjkf1ODHS ztS3vuzs&ku%V%O#65jIv!M1HIlc0TXmUm;8G5zCX<1qi30zSls_^V4wN6^&%a^p1GbOnd)aeV^ zwYJMbzxjPBi5rneGu?q&IOS zGuG`JS$q99Bk?u~wv{tIvTsO<_I**~kvF6xZ8Br2@rpcMSucA2@GY6&r^7Uxydy?m z1~H@3TFE(uag6=7SLEm1{pG3Uoy4(8v0~)8E+W^RHeS5)9XXU4Ewb9%MKWINGN1Fh z$q;NGuPN>#uV!kC#%eT?*4i}{DqkNGsvE6(;U~@{HVh^Qc1H_nKI!h{Wf<-X#0S8;jMoYDnFf zIbt`_eX{4Fs+bjDBRf5_D}q1YBEL?q6ptElo}}A6sJN|jm0bO0EjGw`K#GoMh~2X5 ziHpV#@zk5Q$Y!-{@!q0`#N$D}xTUa`{OMrC3)rjVgWLr1egB(e#p{{k+BJ1#;=&6R zs@J}f+7C0t=GFH|&3Y|y%TA80Jyk0H_O70sb}$p$Wmb{BvNOb&USA<&0w#*rK^19i z*Aid6euezlyiB}$U=?wEUn+hreUB(D-6Iavy+#V&oe>}Ryh`*J9}t^ftR-%zs5qpY zlC*Ql;k8-{G*mk@o5q-QoBSB_epc5de@1=O(X98^8(`8HmAa?Pz@Gb?&T~3 zMsttWM*^o3%R9`^!9|w_V|MX8+_>WeKCY6Yx3A|z+#?Oj&RIpT?(PPUOW|~)cQbUG zWbvA#BWP39GV$W!Rdk`QFOmB*iPqBz&`>sn=T!P(!-S{&3AuFiP)eif@hUH~ zH&b%YXr^b6I^*nL>cUIeX8!z8V=C`CPm+hoqU*qu*fU0hUdobUCBI9b=gmXoi>v9p zbv{(Vq6`9plIYo{c{u$~0(ky8gy$ygkz^+=c;C4Srf&O!?_*4<&8PSH;?ObfACpo) zXeI8Aufc#1PvOwGo&1q!#x%@BR(Ny8n!m1KAWYXw#cg*!(n$YO)cm=hZZ(kPvln$~ z(b*?>-L#yOj&;H}O&r7?-Ay0&Mx%1*9=ag*DgM*NlcscI-u-=;oOBFT`y=UsNvgO< zOI46~SaisXjnu2MmR>ww!^C7Ar$;Zu(@wWN+|XfiLW-#x+6=Fxa!uPIzv?(2f2R=s z@h!?*f^ooK3ICaxjINvW@qo${a5TJyH%C9^*Izz_@j+?4%7-Snx7nN5vRE$ae%i&U zq{qN7OD8@(We-e!oJN~|Yyaaagu}N_OFZapwCrO9{nC92*BXE1^L^f6kj7qmWZ`xG z<|#@gStHHZ*3SIiphaUs&*Q2kDllL`92hH-f4nYLx&D&&)jj69^4s*ulr~&7lOXASmF2(&us9fpZ6u=$sK$62i{(MBaHAacAAsn2ef>R%l5+r;M-(1b`Ycq6c)a_D? z{uu{Gc4qjXz=|uMVaql8#?gKwOYHfjN6pur#jlxra9Yq8ICMhdb2NKV?T<4tAp0U6 zqCJ^@E&j#3M@7?1#pls9E0^0PHI%Kr_d%R0mxouc`_h2hW2xh+Tsqg>h@aqfR+9U@ zz?tde&~xc2^qW0X!sq$We++r`%UU|GqzRvfXJ&pUC!~vM->d;>N!5gVxh;IQK{b6>Y)&P5lBWi*z;E3~-po7~*O;bJHTP-A?|1Nh@e;YFeGp%-8-Wul?a+2tDtX@nBasUnqs*|wezmzyRY+jot&tcfR5y}tC{`!z>E{`GW4Xx7i; zrfxe=?0?_mBwQI;^`U}Wx#W+H;kmbB1G-;6hpr_bNpV>OPimJ?`3&x-Xj>czzHxOs?ccPcQ+M z^rKv)trYZssoL0`Fgl=nA+#Gm(YajPjDTKEvJoUnw=_a5WTk1B9#dK;>~8w_tp z+{M172Z%n=<%hjIOw^B>^H(1@f>Bu-rj9ZIhueiXajO+9eNl$-^5$@^8&PSi4HW#$ zM}@JbFwu?2%B*TqJXV1}BII9>FaL!zdmLd{OeXF)VFkkO8IyNAfWUe9_h;)%2|j=5 z2v;ZN;@zWma6~^7%W53KyyGESk8yy~R323Vtf66dCU#G>gIrsN&t)tj>tGq$Jhz3F z( zw~L$xSCZ5DM^823(MU`5uRlWGpJ@;!ik*q3UWa>_>MenN@rM7pQA~uaH81#XV5N(Q ztn0kW2k18N)7NXDy45H?^yLK5R#2l4ONyCk<_w8&j!2V7D} zyfxel@*Lmd zwm3W36`zgLT%yU3^Z0jRIB53V$C$Rc!u#iNMA%pu*ylgo*%JaScRX-J^<*%7vll-< z^99w265O!N5N<8rjMj%lpt@d;H+(k+Iz$beVRJZ~$=%CK^O|OHm+$b4HISS`)TnJmY?Ejam|FE_UhYwr;t+Af?iEV*NF~aYQiG(p1 zJF>6-4g$CEJ)E1H703_2iM#(BEoFD$vK&`f{^K0BtsV!SE8_m;xl89IakCCh2K~(^ zQ8A;9I8=<}y&ILGf2|R3yLB!!yj_aNbSJ<%t$Yk$JPtO8EArADe{kF_jLvX}j64q2 z5(2<`_aThh5D4l$Tk*nH3();G8|M~{0Il_|81M&RyGb72R9yh`mhQ)6YexYIJ%;jW zQ=tF7RidNQHo@VjNG>fj40e1mL23RtM@1WVPV$D8BSX-&H4p~IT*AwzLclNkA_~uH z2-KL1pPY^h8kjBTTyPMUc6;%=2JZ)Fk93l~IgjWEc=L}{!g;|X$E4TM@seZQyj7dQ zC9Ta(n$IuBWxQM6dQcdm!v6{ifD{K^T&6n_8VC5{p!f-pvVAQ|^`eoUbFk{25A57C z8DDJ@a=v4XaQv?+P%y&~wGI8C*YXf-oE!{;2l`^#Vj-t`WD=@1O@pONY;dP6g%cJM z{IfU^uH~FY*#Tq0N3)hI`W*-kvW7UxXcpwVdEhwx$zXEJ5|yIeVdRex>`nDStAOCA z@$TT4w~w2BAqaloRl&mk!Qi`81|?T~!11doMwN|#)Z`HuFdz_OTr_Zxn+5!-bVkdW zqv6D&3hwV$KNxsN2CsTJ!{jkwP$$lYXv?#+ab)t01_4Tpv%%O@OTz zMHuEa20E8JeV8{zV-(f_Xd3inB{>{ux$)vH!nwR7&B zUNBBe4HN$Jg}&xxoRFah>YU1bJ2W0F4pefB{*H%tO&>WSn+`8mS93q3eBg&q61VK~ zSTHh7`0y%dY{^jz695T2$NI33qfxfRN*ra8gdf zU+oBP!_)ClqKZ)Q`lhA&qMk-4=}CIo;!RbO#gI_8*zUWv|KCWYE4JM zs|~g&Ru}=}#?XKKjIKQ;-1~{X@Kg5)Cop=z+SHq~QT2z5g?s<~p8Kh}+@1zs2$>tk zRksr;nKXfu?h;m8&gCZf`oi-gm$>!Wo^WWuQqE}X7})b;9rwf57apD6!3lc`aCS}R zyl)7(XSwU#z(@)r+40=a%R?dYZ5cO-c*BdTE>3fvH@F?W!wH@k=uRumPS)^*nA2w5 zAwPGx;kchW=k5V=UK2R|eI77sk}e)F^nt|FQ@BBS?od?oo-=j`g1!H}$}&jzg6hbA zMD`GYtkIF2{WnkWJu;QE-z5gg#r@pdr$KNwZ3g#kf)mX7sLI(r91B`sV>!K(#xS(3 zoy&dY08Ym)ad&o%hP40gi^x75s9%wpwQCtb!;Mee7E3>P$WpQe<_9dEqMAK%%Q#q) z)04ek(-mZWjJaO!6h>~|g&hqep)&lUXjZ2uY|OkQ+HiU#?C{_B?|v$ow*vF*heG|D zR*%^@6ebV)?S4jcC>USS#l{932nNZq?C0erC6HQz|qdt^ViE{wgj|JJ8HZK2k(A~{LA#LMk_of8~5TOz) zy4>3qQjB8%>2-O16hvVk9pL<%#B46m9ZuPt^msni5L&fr+_}Rh@bYK}*YA`QI6sW@ zXu9ACnRa0!pF7TQQJaaL?zV^XD)+K&Y=^+;i5}S z1KYdJ;OCXE|MKFM7P+GPYaAhM{A$r5M;llb=Pqhj`br*GjpqDbnL$|7&CG=#9U<_~ zP|=6Wrr_9n7H4>S5Lhjll`Xw5cpE?RxS?$YD{9}mOW#MIbW^vO;Z`8`d#Xr5#ti08 z3>W=bY7W4^cYn#-L3VYM$GT1n=qlXjzS+SPa$?;@zDu;>O2S$XT=t9T)b8;pyr&K6 zhRLF5PKKah)aD_5zf(+AaK5807%n{Qame2s+Da~Y9PTy-{ppS(nqmk`Q~QaYKN<+j z{=4H56l4y)*6tPs4A+Iu_Gw)27yThQ{t{Q%F%VScbvX5SU8t)bC|aX#2wtD3iuP|X z1eKruB5OfM+~&Phv|C#TmMnec5z)~fI`0H{c;x89`CeWg&GMS?*Jhb$m!lpiC}Shg!A|${QH?-?L1N$&1x69Wxn@Dfn-oe#%ar=Vx(GFq zN1Qe^Y_bz|i48y{MO~D!SODovw5Zd=b#6a!3|-|uQdI%s zuD5$Q^;L#3U#vx6FRFuK!yb?ATFTJ2UhGjbvN!CxE$22lSQ#9Z2Z^p5{v~k}{Ilm? z=p}F`OtZHvR1)$4g&swUz2T~kgU1PxEIb`?M&$Ohn`kK>$ZlD!3>j*RM5AT@kdTpg zM9No$d1KwwZ28MNu)&GO4JSE~!Y^{9_J&9C-fu*- zLf8G`ZzcE^;p>sVwvKFV-pzHknt35zjuRujQ}mv~G?*Tn3Zs`@qWph1 z`Bz?#seQ{8qQ8jJ@zraoo=X@!+Y&}$oHae7R|3K=jT*l=0KtA1tS3c8hXzh%2iB-k zfhPtVg3O4)r$9PDr$0t*Er*G}K9I;ovh?v2J9wusl?KM0hxb1(ldY>Rfk*o?=617^ z-^V@VJwpz%D@ALl;Ra=PMaTlpw#$G#nMw>BX-b2?#Ipsq6KHV7O{Q`4Db+5@<%{Y* z&{YXZaAuq*ZS9wi&PJBtcyqttmo}rK6Ps8--E+uZdxW2V#hyve8-_AYEFnpQO}->c zWyjyRT)dxcVAcSf_CvY^8~oNu`?nGKUmW&IDXU_X;iu_vZ$*ose4yvONW=CkvU zkQunoCQh7*)!!%Z3U4;B&!g_bp)o#)5jc)a@XLWriLDp0aHZ6V*(+>BiZ_n>#6*9F{@G3J6*#!F6?jn=_SW4EI z?`KUpGOVU`5sW@?pWNVQ(a!6;Xtmm9_~YVDLzX7MiZPWK({hq3yYJ)sjM_?o(;RzNXACD@!t-<0|Q=#m+ps}WNwSTIM`FW1reN?klSLPA}}+3 z6pgS<6=-C2*X686@~=?*`Hf=3mk9?Oa-t zWy@}?7(f#z6L_3CikkN+O>l-s8^mT4zd6YI;yEc|E;3JeieFs7P}a!6uz zKlbs(Xvgy3HS)Wd9JNczk#e)3ZSpNzdg2Is)to>l7C&e1P8PIi>@v2|NSg}!5S;Q% zhp_Ea=-auQ*qg-ZBA?Ps7S%hDmG`$`PIGE`GxubEahod5KC*^gur{J93x}~f-zw78 zs~>$Zi^5}-0BXL>jJnR-#uqNC28+2v+0)t0DD^?j@SMn>Gqh!wjEB?Q@O-p7DQmN@u7Sg89VDgUOl{&b9v2)PQ+axM$?C*t2#l?;fAQ9 zl`48K=sJ-e`$bZtDMBXOmDv^*LD?a25sD2PvHvhaa?>$iNHNQEOj4 zd42r`hRpg*O8oYsn&cU&G&_#%_U}mXmlUjh`G?pIyoSpf8_A};3=E|A$pX2Dc$~f` zjS7Y668wXRQc{u0bdeo5Q?QTUeX^}z0`AN0A`aPaai73p`E&msDp-FapCZ!nY{6U7 z_i-T_S&aQW0byx9Djluv5Jy*t~< zx~z-nIza|j=Lovoj22R+(1b+wCkfFi#u*1%iPD(Ic)R>3Njh7AzlYr+SNF!?QLWb` zy;nJU?dm337hYh)&}L%PoGUyRI>@9Oukh2G@8l|1gfK(~J|w@!W6JM|sb3HJ`u!#S zJznDZhjQSen}s_j{vpB+3zcK)h(fc&i$>+)xpfNAYX1y--u93%!x#8sq9XWB zeuSUXdcm_pH&N|bEs^r>`e=3&Xa9N>d{eOVa3jt*qaf%D6{vb%4Thv8qeZ(CbeR;R zb8Q!CJKuog?G(W!p%`0^^n&V@rFc|L6YAc+#E5(u2oVe`zVExG%o+4tsR9qG%JAH+ zcO>vl0j`?gANIbe#Dz7=Fn&NA3VB%=`KAV=7iW_s`R6!Mr=0w_uFQY>t_hc*0c{J6 z;CjGwoV8jX))`dd_B16}Kl}@BU(g$d7d2wn5LGaI^cJTl>O#AB8#?!>0WNAlc@s@= z|6GQf@9Mz1A{kx zFR;yeAXtBRi-VeVA>dId2HI=GwzH)uctat0^Ap_o%LK|J>+se=BWU+4!LF49;imI@ zTs>D8VvVcN__Q3X@SH24zxR^0ixhCl(GTbTWj%Ru?qZt_!B*Y zWg+T1k7_n5pckKkZ9kt8x1bc{HpoF__6zh(HiAq3rFfe&hWA~z{LDkfFl6a_RJ`3E zT5Hdt`zlpX%+un3?Nok+NI|&^Lt(i?3AU_vg)GZbl=8M#thk4=n(iR%9`X8NTOjZT zPgx1Mibs!;J{<975roGIUV_ zO8qnu3qPV+!eC*}NWd>I$AYH&L)6go1gGaw=+j~Y?*~NUtO>5rG3DVu{^XGpsc70H zjD-{9aesL*_)lm=sn04_SYrl#aR-Y9>G^0UNF+@8phU+0{dHW7-8oP|LwW+Pp>?#Byco) zjRux>AGclggpx}aQF3S)tmB@bR3DY@1&$WG!?UgzDD^dcA0z1KyQT>I%43)oYz{fm z$5F>u1>QGjqU^X)u)OgsN_lz`?{g^CIiA*@!XKYJV2sQaG&}4Eg0C6p)p>xe_63Zr z8VP-Fd7(JX1L7(Scrn>e4t>_-uWfov?ygWAD|7B44 zLys!d7_r>f@^tm0Fy<_q0}CI;@*6%~qz;=8&i*Ow+X zcJilYj)LsX-Z(Pw8?Qfj7D)3-MYR?Ds=Vv)--m7NyXXvYnJ}11`NqyMV?{!Kojv|C zp3Yw6K*#^yLy!H{rM9PiU>Lm)3SmM{c-|iAUhE%pIY0O@-1#h=(Sy#@qyL@fSAW%~ zYS(`7N1Tm0I>!Zn4ETY+v|IS2Tb%j1xAob8*^l^ywG|#_e@)rW5nW{BDN{z%MNIQZ z8L3!vn3WxxK$XsM{G$SSDyP^g{l4&KE>qZ$&SPZzq6BiP)J?_tk@E23G~ z2-`T>gkII3$0GX^x?+1gfAU%|AM}he9qm-MBq~c_>>OpguJ&faIRJ!Njg~ywOLuNK zEHF~8!QX|d^!tzZe9Bp%fj`vQjlJ9H_z-#a{VPKJY&X`oHjOxM@?v40bLseNR&3R; z>vZT8p196Zr^3#NN_DcMCed(h$Z8=^RLAc~xkNt%Oy}c@wdfj~Zg_XjoFC)NlN0OB znD(s$?A&o#w&2VO*0Avp1hqBrt=4(e?~*-BcL}Fo70w9rw53F-_8-$%Z4s{>a2RifXCestRM>pZVfLdy4R5UnlN>|$6X z?Xg_STw`MCN!@|WIar`5|4jo?R52`>cACyP7fAQi8&H>z{dk#o(^UCVuN zQeo2lF28>``M{i5rEV@v_|n5W-WtNkzPSy~O)vR-3kSk!atBsllwotW9iShgyHPYE zl6GuZMHNHssIikEHz}Rqy^BSB>ex^=W99X)CmDQCn`abYG7>T-kx&nMh&(?O8ycAeWl(-!W{ zn9VshT0zcSq&`jV+Soxb_Ov`Fi% zJ7&AUm(1@iIyTS-{)-%*t^dsurfz)b{^zbfct5(F?Ov=8bCg3ksSnbP&-XaA+YwH; zD2in7Il;r8PaabLLj(Q-4mR%q!V|H&MR zzFzQn@W>1@5(3@kwi`o#e^XJ~H&c*|yyDT#83Ol7>|ti$0{dE?W%qaKA_@zCadI!T z;pS~I>e_0-Pnge5iZq5R+iL~?(|Qn|rM&dK1bY=piSl%~P#(%xpPK`@id#`3)eItg zzaRrYOb4TTh3I!u2_gpS68|z~$iFn29C&|?fZ(tt&M%4U*-j_wXi>2 zK5|+#Ke(1`$xIVT=ZymQ0U~wnfiUoJ9d~Tc7jkTf6K0P4Kz`~_ZiJ2*j1J!Fae28m zyb9fyz0+G4z7Los5}4NTr(>%~%5@Hy>dy5ZqX_qA*Lb9i7xv%%dOW1hce7&PE)4rd zp5C;^<#v6*_{j~?mP}`Gs8k_I4^ET#b0a}3B$5bRQS=K$;xNmTJZSk%{(GQCoX@M` ztE~t4MZGgfWQRHOQyidpUMx3r?lQ93@>q7{M`1qn@j@w2^m|`7o_o_x>bmR6cpWjk zkG_N{BTdL9r&fG_HI~@0A{6t6Anb;Tf>$?j`8{5E=KdvLn$Kj%=lvr0ZcG;CsRzLU z_vfgSHyNxBenUr@2>8%)Ji|XLlCSZLLxCsG3(tOjOp_dCH(l^aNFV&~^(nG!;$vdk zdpSNg?JxK&tf7C45v+7-B2$JjQrH!M>Z1*z!bV**E8r!W->im(O9pb@eTI__w#nr9 zrwp!Y=Le#6Il2# zQxqZ+*w-SB!%3pxC`ItE??V<2_7}9p{aJ7CT0_g_*)}mkbz=U59L9 z?dgZQPxN8O@o^}beT%657=xarRV0>8#m6Q$iKV+9?)JV;r28H3MQV`Je?A)5T_xD+ z02j2CATB5mudVn*tb6($gJIJ zm^-3?gyyQ_zN#V;J5>j#Sm%=XIfa~3UJ=3QA}-|5eR6BJ0qW-F3+&ru?!@5&k~+JK zbEtSgYD*pQJxBz8rW|55lJdvOc=NV|j92K78ya{rM#daNT^T7}t%L#N^2h{dE&Td` z5y9Jni@Q?=Kd(97ev?J+D!HQHx)gGz(;VwW=|p$bP~6v-Bf?CD{VdbT{Z&)ZZcQ5b zQ5%Hg{gcR51#et%{|=eZ9Dwx#14Q6C3Ufv}iJmnXw~ErpHrsjl=T`=4ah;F(YRTmO z<7qgz?Jl89#$(2(bkf=3CirX;No&zW#EeW580m|GW=AUW#-YCgN9Nv`h%E~_!XEjf z-~}b2hrBT;lqV}HMk5Z$CdC66;j`rr$bG+sDAMLg#_4JQe3?$Y?J-N7NQ7NHaxEFe zC2KRLal(ANHV}V~Pa`JYzNp!eNTzfy#U17xv0gm`uXsHs@!@{h^SFe3Yn>*{d3T6R z_ZA#7=`NA_D{^`+l5}-%%qhG{PHtR<9kq<8uMWqki^(MO;X%|&Nh42sAIAq>NbZ>& zMU_7ZWOwWlbS$_>N(V)t)|(h|B>EtF-zy->N%L{uks5N`YA<%%#*rUsQK<7ZiA2}z zL6iNLh{m`Bcq}T7EUb@2r{0OAYvox?tiDYGhM&eAzmmz6EvJP!DxJ7ZJ%Oj^@Z_9z z1b$tcNvv&-;lgt%!y;rfQ;c}6zS2>LVW~oHTxS{^0fFQ zZY$-<+N$%Yu_&8J{Uz*UCeamif%$&fEqAi^T#}ywCXZCoyj4m9^ORA zeL!q1Z{T)62~k=ck5zgZWbO7V*m3zDIcuAQgZC#Aczp%si!(?{Kr$|pxlVF08rNE$ zBHzuA;mnEoWUXo@wtbZl*?XxNz3DD78gduc>*tXrg=zRXA)QS0$;9Se*~DQQhdze6 zn1pV6_sD>8 z*|+^eo)6H;?#63qN;$4!M6S4g0H?lJo7E z=tv%r1j|%3<5EecTnfUxO5zvGVaoLia{Sd@Otmg1k+-w)+vyU4_sHR=ipS*Y4u&Pl zRb)^*hs~eMiNM6gy&s=R{mm%7=ekdSfb&!C6XDv!x8`MJ)QEDNRh&a~1|x3y@`{{S zFGqK8B;$u?Bk6aAsP7YWRP7QXe*PX$W^q=@(^X(Dv9uSy!)|?EV%v@ zJNmsKXCyChZT4;Q?!RJOyfBiOUeCcfdATI#X*o`~@RT@ZKF9hG<)qEO8hxKXCU-O% z@%PVilBDwzFGsy0rs++1>|~?pL(gqIIqWv^Zz{ocvU#MYv=h&C-z04ZUSaUNpG4`{ zYxFlMA}_EBmo9H6t9)xPHnf-w`SJ-_|0rV7REqW+tB6z&+&a6Ccolrc<#Ekq=CC)o zu&avL-D*a2tFPom{zH6qT%AM)DDgk8oF-D8C#&~+vSEB9I!^jaG}v<#xcFol>&3sX z*+I0lzGJ>-A8>3h#>1E1k!b%)>>0S4)Rug}u)ftq>GyA}%us|b|L1sa(ktSe*MgJW z<-nrwJ#L-;id2=pM_ZpSM0jnuKK3<9Slx(a8W)JbjOM$(d?jDTSK*x^;Tf3Oj5(8q zbC%~9^nED{X^9_k`;BIDlQm*tup;!{_g?US<&ula^8CPUhshC@K72@J1{oF6iK6AQ z&|d!;bszj7<7|GTxBnkfHn{_jZ~aE9JGxOxP8rOT-s9z|S>%3{A}@I5MM7T%j}9w{ z$+;#3vvwl;=`%LZ=mV~Yf8e=-A7tI#Z}{Jmz7VkC3tEzLw{db>D*B#Hd8C?Xtrw;-rnbz5<(<2SH z@#wk7I85}O&$t}O2mTHw@ds9r?o}dzB{YN`eRqheMjm2<|ALZ-%fVS;5c}j(NKLX1 zv(00o=+7`4Qod|AT*9l=qVf{7)E}W`6AfXZwhY;LbT`}Ge3$Qxf5c9vwva3G6WFU% z4eEOK3*Ys=8I;;ju>#AN#IY%rX?SfCja%cyY<8>BD2v|g(d2O2f8TPUqe_piy=B6t zOzO+`lx>0~S9)PmKqpA`Pk~>`|It!nH>w=joi=}#UOkN^7WAjD2O$4#Y&(51UYl~- zm+6xKX4B^n5{Q-IBK{pYk9}ejS$cXoN;>zj?i%#aS-qHTxRV142ke=U+2Y5R z6W*$E01H_*fo374`wZf@U|Sl#w1B|FPJ$;!rSlt$lFGEa{lqZ$Oe^ld~oGP zDsR4;ZS9#u$0W~Z;|@QE$dXs+I$K1ePN&0!Il)xf?JSHj=J-B#H~AByORS>cxWJoO z&d-e6%R;}NrZZ*t!|~m*)aqy~HCwZfh73`pQqJqI7tT!J{L=Kl!R+B`k%UGU8DWg!}?eY&6=%FGRiZNRbLe_mfzBT5 z4);321im#ZnYWqs#nrHHaW0=!5opdEslhpYv0Wa`;A;FLUPq^8YA)H?+Y`m6&Gb#aUiZZeHdIB@&-A4Zm5%isR>0iH-&4%mf zxLyQz{o_zN?^-W8fznI~H!&J}-i-n~>&Sn)_t)HTw95B|P_;vNLvtLgI(PsB9etou zb{DoB9R*g4193}C0BC6*MsCSy=s$J!zy6ykrhMJ|2H`5 z0Jd(T&;t8$^HOgZp|%e<+_eL9hiDXUvcaKUffdK1sRb%rb7<5Ai(BX|d~DBKNFFHS}&FRZV-6^_eugBSaT zq9C<_b_Ycr7cZFdcql%f>IJHQ-2e4O$tDd(E7zegX;%z(EBEe6&dn-d)x^uZ~5Zn zYIk^;G7`HK2*fFvqEg!+C|@!N7yH^l{JbqF?T2X_Gz!l}*~8lGAt?1B+#0kMPwpQK z7y1gXOW3EC{xrn9#ct5?d@!!sKLmO$vBZf}ZKb_2C}j3Q;ma7@)MXD3hl#K%*ah50 zhG@LU4z}xB{_7_jSfq?nEx~h}-oM}P+HU?&@A@K9z`!x~pz=)_cgz?97V;|p_Eceo z>KN`c1QaI!bCG+jVOD|$N?D{8!~Swo-~N`D}#!^jX`;lB1ZLAgNj679JSm8F3(axFKPyjo0YM0ngJ*} z>tO3#V<_or}^vEs*F!n?Vt0 zcS93C1XOWci7~`2_`zv^(t|!luQ)e%4bW_m!E00tlx>x9lXYLPQ)}UpZTrErr9Zh9 zhc&=>*=sJUKn+G%c5?MMG~j`LBd2WA5Bjg}RRgpH$Ct(z#@EbRkDWx=3T`I8>dW4d?lX+=p;^IPOu$Ia+DJBCkDM zLyiiV=tpsN2@0@mO$KK$Km{%(mT}3Ba?tRti4)cJf=9OPT=hdm&{xmkrZ1NVYlCN; zQb8ZEPEF?`+kTSz`6ejrc8Sop!g=13gEiYma?<%me{c;~<<&x3Hvi2k*Oq}$7gc1< zUr1U|JJ&O{H*jMbxJ{xKvLe<7!%w~r<yd% zl8BaGSk=@{cE9Y6IoM7*-77g4nJf;TRfY| zW|zO5pKmP@P0>Ot#cFb|?@w-K%RAC*QVo|?(oCe^S69hf@@<(6&Wfugf;Sb1^t>Pj z2Z!K+&L<@HiqIqY;yJmtM+G;YtR>M(ebI9L6Y}CwIrrh_Q}WvME>~tPt!&_rH&N%KI5*%J|&hLbcI~f6C(S!7q&~v zN%bU6oKoY+n3lHsoa>%d_p^8;m!x6*DGL89htZnKu^xi#tmKt8X%^AWf9x z_p`SZTiA<3b>MAup9xuc7<@=zbM4b$TfQss6U*gLXQ%_sJQGJdj_nlq`2FbWefx!M z<1x1DelqPZabs`i)l=PH5z5bd(g9UMQ9 zUGS6_AG?8UoNTCgscRE+cDECI#%M_7%g?je1U*UG5|PB}59MXTttGAto=~AvRg&E` zllp1AV$o+Sc+$O*aW5Y6b{lFLUowO@-0Ux@$yg0nqVyy)4M)(k3x4s72cL^x0GFt zSit%ZF6VCq6*3!NSvs&n7ZhT)h55+SnPdKMY&{vo+>Q>A>~8NSK_lbX)w(cR z7kHXJ7%_!D5eL&XbCe)c9r!Mg47apPMSFiBh6MCq{>*9|L}6g9VkcGHaBFF|AXbtjr~Y zxwy??<25N2ZP-dTY*Q9z3=XC8!|lLjgq}$6x~fFrShEhf5;mz`olP}Xm$LqNfnh>7 z7WZW%26XZvmke25zmJT(`G)h3USK&s69lGI0#pmUUv(dQI`H^)_-VI<2>d1LHC&Ub z3`wLTS1F2LwJ?DZ|C25>ZUVvo&%9=o@z2*gOFSb>@nM^TMCg8F?vhOclRA~v+gi}3 zeSXaDoe7;Cc!3CA0_<9;9Dn-lAeNy~&Z|v6PTy`*qNz-E@DuzkB1v*x*fm}1C&RwXCLMl8L~ z3)c#G%dDY3WtZ8?CXWC8xsT*lvxphFL^4;OL3GEbWO$kxKr>GdrK2aFA*#i3P@gF* zN;{S5Be{39`=}|MecoFfXROH7{_P6bU? zU~#s>!jDJk{lB;9TQS&L=uXbeQ&X-@!WMr_h2x zHD+b{iClVllNS0X(zNfYOyKv5kFD7%X>yoCU)0Q$G`j8Q>tnXDO}|djm!s2|MpGRX zd?b?0KBfFRonVPU`&lV3kqKTaT6Uv}f0A~TN@sO}fyCVOWN1WeK8x2l%I!Zrm(R~L z0k<^=LEfvFwoZPGAyt2=R{AlKPns{iySaeZeD)er!%A4O{bee=UUqd2r7!1iW;Nb> zm^AyZvZR3xX`dzUOG~O~6$8U84ebK*;;qu|uJGysdaAGfMct zMuj+1y1RIf|%9`4J3-Ya#(4~JA7AG!cUF#5zF~arLvlx^lFbK zo#{SAviBOt4o_Pxxfx?3*&I*<15WJZcRZdbo<2XHx{du#=gQ3E&2FxNfsudMlTk*H z)Fkmpd{oH~5SxkfryXb2*|!BfVl*t=63$LV3)h{;HMZ-!JM9dUXO+vYGqvk(>_Xc? zmK*kg*D6keb%%p!bx;QPF*OO&UZt|q=KhlIx>&YYWtL=z{0UgIIfEs|FQ>1Rrcp_v zESEi0OuzUXV178BMa&KqJRI*>;!y*kckC4t&C(IaZ8{@x6DNw}h$s8F#a=vQ%6CFM zR_B}sM6KoI(JBsHIZ^R?@u+3)s5dLT-f3QTA5@;)h4;HQhVWg6-G z;OkW2_tQTI!f5UEczXX`ER0)xLbM_M8T+&(mL*0yONtT-S@{0@Y|8Lc>`UJ@U^8EX zy-TPe(*mae^DtnwllIYhs~ezV(lL7Q*H^l_xgks7h6s#$p3OaJ!Y)Y^Bncy|SXZw| zRyA!Xy`C5kDV7WA)m4J-HpT|JHbp`Gu3Lf+)tG7xR}^>tS`F#(1H}Ue7{R@h0@rls z1G=mEfrsDT8IY^}n7_Z~C=(?YfN4`Kb1W*M>k_^(F+D{`=3QnD>#b?(@4eK(E1hoh zYosw{W%SL9&!Rh-TJ-TYb5?NZ0kj1N({UmVYLJmcCFX$=d9=iKfGGC)KLVQ!z zoHh5IFYe>@3trsQ5l?$_kL+~~;-6kfV~&w^Y}TqZ(!O=}@q~KZJ;Wv% z_%b@@1U0{Ul-z^-X&r-hegUxS|*>^k&GHJ~ywBiPFhS9*WlNER~YxzG(Zm+AJu%@@B;WulFXn8oQhc6kmmeV-6CUu-Iw zfAI)yuO^ZL?bGzY^h)L!z5yanX+h+RD{Od^1HEG*Be`e#6k1#<6J}3J)%%KnMy{cX z-yOs!d5c>6uEW2$O4%cr!hL(5p;X?Xr>X;z-7RhK~aGQ27^MKccs|YN|4G zn9)akJ82FxtBs*6i?k&_hmYX5w9b+UzE*Zy{yh_Tk-!*Ouo1&~E z=@(@wx#U~I;^*WFT*hRoHhw>!aHo=*^d85Gs}3-MXUII`MoW&lsIl6If~E7J2VKxw z#e|+~(k1tsZ|${>-F6GcS{WDS_tk*8Eok7zvvhuQ;|bBOefMb=m1QGqti_hiF-&dc zMfxzko@u(IVu{s7Ho9REotJNmLg#|`>Pa7RPb{JrMy+Ny2fbmdmKM*@Wiwo{lLjU1o zx+D1qOWt#auFJj3RJ5F^(byw2qw^_U>0Uys5@TpzY=gZM?y~YB=|c8fS@LPwIxriX z#zxo2&;g&<@{!BU#h$j)>DqNYbWO`D%GSIGslMj%QH>X77QQU`q=(U!dW<&-6D=*c z%paCX;G=yvv&qh(eDU6)OlGw#6&SO0Pfr9@3)@Bo<~tl$-ogugYOHs!t?b;RpG@#R zKu*kFrXO;M&e&!QnmcXz5(EpUcFO06>vo1^_D!XU0b$nL8R*c#op5=KP?GcZJgN z7t3j{bz!_ra3TL>xU6JN#xEv))-Ma2%yONI`L}Hs=-sI*0v|Pl3ferco-u$e8XCn` zM4SBUEW7CTjG6uUA#jt+@WAwmyi-ObFX&@px$Iu7V^c5j?SgE2zN36iRwu?;TVLmI0xD5Uk--SJqhFBsAX63)a=xe9%@IOiC9oO^s#qoA3Eoo_M zDx>VW_gpiwNhEu(>^+lBgEU2{R5FS}RPTGFw-3m7 z{Ak*D)Bub(I0q3!=E{pEb!Sa|b>z@dTLpK%0p{h|$x2=wS*V|x-~@M7>|{cb38CLR ztz@0z!r^-BdvG#;GSuDoL5uk^-5WCoFP}dShhi4fE~zyTXAuTT>Ma;T!Vltm#1j{K;<^QQ??kwxj7_m2e)C-iBYEx)p9tIVOF%bCW)vdIz?S z{88|6oDMtLVF+}r83Y*t!LTi?FVwo333pRhCOF-K{W^px{cDQ}g*Nb_DxV&@vjLw? z=?wc+214fpq4a2i4?E(OEafih#0DRcm$2+ig!=g zbK0D&DHlD=1IM>JIW^q{-|vTtb8bJR4|m0fnZH2zDdbLPAH&>-L3GVhZ{X@(nbzek zpeHYb$(gU=&hlJzdvE}Zk9)(tuAPXF$ujn?M?HBTug;o3Z^(aeHUVA3^UzTx1_dVz zJ@qqapUjKAc4RHjpc_Qpa}j<1#6aWj3G~WO6?FFw>p9o+t@;Fb5j0n@|27B zH_SBLj*S{N1#`P;N>l1qa6EPmPArH6k@*M9cl9v6{1kSEiIDcZ3fYDMbjPPXI482P zKsYh5!m~GaD0i1j?TQ3P_Z8;7xrOKVEkuVwXVAZ|9rns-gP?5+Aq~M0(x(i*{VoNU z3o0P8-+Zv`6aHm-IsV%q_~c_pLx#p7+Pmw1W|?Cm7e?%4-7?%{uZiP`et#u+_^gY( z;dmAZ&nRwrdI=9-@g_$)beHQAH(~YfpZvX~BH!v14Of-45C*0aQJ+Hag&5r5vopJ> z6^9CDcNMI_19_TE!SUbFat>xSs=?>Ex-jd-3{rHoJ*5P>>UA1F*9*pcyNnitJF+pU5xA_sGFx@;0Muu#nEygUq!Rn`E4XF`ombsqt&wWo{4PPw0ZKn{5{a>Kx_DWH=K@~>CS?D z-@~$2Jy^~YUVqc@oG`&9dOoSqCSOuPh+V^r8BHdnvCNf9tG|EJG}CB1^CpB zLcwDbS?e`eQTLI4n6jP5yB>r?8tNeK-`G7_#`ROC$v%$;$koT4W#9LA6~0+oWa^}F z715lY8Zcn>IdtDzi{@(%^2;$DWjVD395xO_kGrwrGp~hr4TZ2Mwj8R~4PjoZ({ZJt z4f{1PNbb8!_yamlmoMGV#?M0|vChGiww(D&cREDDilu*P#lC2G8BogS4O{?Izb?e~ zp5~a-GE(@IEM?K3!7(Ghq4Ba?e1LjAIngx}o{axRALq_y8FnXm^g!|s!-u*>{{B2skk{a>nlr` zBHx`8V#+k%`eAL)FwpfYf#W@HLf=A1_U~*9_&VFtgDE-i_1bwfx?ltKXWr(cSs5=% zp9Rh%_u<>5SgH1&wsd~YBwn_s9DIEu!RCk~Xl$*50V>C^xyA}ieGKF?Z#&XcPA>9U z{j*fC?{IwnXnc0D5GTD0RdDa{u~rjx^_+)c+r&G)T@X%9SjPj#43K?YEPi#`(?iDEkpiaqr*! z<~<)X>>TLUEtVBMxJ%oFlQ8`YE(r{U<4Yf4huB&;wedL>{2sm{WFH^$P_XetO*LlG zPTFnn2J}gv0EJ7p!&TLx53MNeQ^e5xGqYc%`ZdAFSIz8^GxY{#3{F0#t~ z7~X%nx~y=heQVbu-q-ErIZu1A165hb@?xdw6aE(bic7<+-Djv^Ve|H?xee-fYI$-NHc?!^GT!7W{B$VqO8k zo4ez49LmK04Z4PnP@G*{;R7mbIf%FNm1xi^ZF%EzGii1dMR%V=aB=f~lDg^*wGEPS z*qTN>{PHmx|CxbHQu_%HZ!h%@U5L^5ec^S&_+M+@!k|H?MVrp4nBXY<32Q z#v0+S<-Ks-ri~asSjNM>oAFxBUf7=c9B&O7#ni4x2#1R^8x$Lcf(8%e+lFA>&{)`< z(+lR*n?OqSdEjqixy78#yi@K}`1#vF&Maf1CU}c{m634EF2pXOLr`#7Gh z{KkT>V7<;R!p z;3Ky*;axdiWL>WOmu-=ohEvbVC2&%$N)kR_X8rg&iXB^S(j^wpY#J|%Sv3hNEm3$V zLE$G>-CQqrthCvLR}&b0FoFs{FpC(PBYDKW;l{0EiB0f5T&4LL>*f#UVbvb8xPwx< z=P@q68iZOS5j)r&M30K6xbk}gja?szHTO3NS5yS9KA?g*A-&`vr8}tT@%EW82*2mI zlh*|aE~@T0c~5>C{2FL2|5zD9CH-JL{Av%Fzlg_VKMU5lrx<5vZH0`@6;kD-O&B~~ zk11U{LSM$3u&l=>EMZ9i&lGfE9S0Y=M+bG;d#kl9{4QAXs0XicRc1wx%~`8b6*f;B zn5*wIaJsr1($d?okqM7@YJ>x@DXU-@}vr zh3nvMy~Eh&Wb=-|jYtHndH_Y&c9ZzcM?d*SC(ZTSJLXNp?K zZp}*=QvHnT+_`}XA>HK%@hWnc%ctc7-UYa~wVS*uHXgfgoe8Pshw;lhO_KE}lz%fV zKqt#XQ05y5LkgUj)|Fvw$b21EkTe$V73kv2$*yup`!qbUu)S<|vj)_|_F#A4T}|8YOw zWhCL*f^pP;ovHkHx)O^pI>uv?y0f;?Mc_VXI#dNGkf8^)VK{9bJ< zx2(?K-}4_*F;{>)EgEcZhf45>z6)_L0%7(_W7xiX0Y)C^Mtwi|q1XY!s`4&yVZjZo z`yNl9nunv&fUbD*Za>Hk34(){-$U{-6ZSZF2fXZ)MnhhhfTEAEqT3_zP1ug+o;oaZ zSt#C3+6=Z1Zu0OyoEuD@gIZ|=xn9@3Os#h!F!gNK^13Y>uzvy!h_Gi0XWi~cHx%bO zo;IlFqs{+8n5-h7bLh*mUnJ3wcqqDA|Mw@6F!MmZj zC;vAOPOfB)kAeStm?pSUbltjBZd&H(-p7Z(!ib3Px15DBoO;&JhQ$Ni@+zarkxf$ylN8k z`ttxDj*r4w3I9OhGQM;tAN)%DV&_+FK;-+d_tpha6}J;RN5%@jVlF@Wu80SfYJky@ zZ@6{ha3D`d;gTo4Aui&UaFu9-@Y6EmG8LF`)q>gT&il^`J>|r1*awHfvFQ>?@3Mp6 zx|E2ojYpyItl{gzDfEr&6ug%>4<5vf$EiQ6x%RN%_{Ydnb~x`SuS}dP3qOc(t!|fd z7atV;_WAORWm%AM&Pa~RET)&=?!mhc_QIHyLs-_!g0=LyhI{nG;CxJ%s`h7S_itU~$;#;v>}D=6i`&eb-8bT~%~7bn&;y4~?*^I^`Kd3U~Af~kbB`pp*>(%7lo^9}* z3xASS+29HH#7wvf71>ey9lK>A-x2rG&az6UL>Mt|H>jO4!H(RG`BqNAF@X=EC%HpS zKU+~V^?o$<%_7urorM?l^YLQtPCDZ9QsMt=#iUM+)cNA^jbrdgsTZ4MWs&Z}7IXr&t0(TDT;dS7P5sP}^pm0vIp7yEutU?{e zZ0{hi{yM#&{6}kS7}Z>GQ@K66yE9LmvmIH;DOY)MyIQKT;{tyE9)ls7xpb669_of? zP_sVanAMF@k7y$lb334)RXvj$wqrw$4XxYJU!LB6DotB-KzIZl;r4ACmKQM?eCn@4 zplc}oH>rdCMf(a@KmG=%F1Dqyjb^OD^a)j&vy#o5>4n0>rs&M=uL;uG`2fgO%iN$JgNfwW}Z+*XnAP3wZaM#ae84Vy&YXq0^S ziLJc7+dBFn{wUn|zt8PGUZ6#Ri5p!hm8;-X%nEhst))UCVC3Bg1|0*d-EF z4(;J)j^h169-#0n;f}8-#D2Grs3l|3Xy9XrTH1tG4MXv8W+i^Q(or^Sr;I1{n$iEv zQT}$)OPpWfg}(+eS-}=K^-4|dm|q6plX9tW_`r;I8f<9oX~|Q^f(h4v*Ztx+So1-X z>wUXT^>vI{;I>w*=6fLXj{)X#2cWL)cpQ2D6t|rkCs#amU~`@im-mnD&c28GkW=hF z&f8qZ!{#lMyLWzsW6w;&Ls}Yq^~5+htG65M4qTuv7q;<(<~^Z*)DMY}2l;EFV z6ZKY%;DK&H6+LCyU3!Imf28BCs8#rAkSZ7rKElQBFZb1L;ho1Vz-uW%sCK#>`lxKd zIm`Um=fg#G&$n7p<9vh7SvoLq=ShWw5kk9Kh#qVd6qd$fUz4L;(Q~c3dQmW;^jUYg z3%or$fxY@MpkP<#2YA(^i7KACVW;ev@cGI&Hgb4cHw=A3T;zRkSHZZZDRRd=8%!#{ z$NkKAVdeDB1*^0Ff>X~va^}CWEV8|?Jg>kMN?*m$%oU<{a=$D4eeEcozU#syZ7n%N zy$e$tzeG0wH zbvSx#+JTDf^Oa4_u(@K2I4?Ue(WxM$*aEki$|E@LH#rG{7Vi6HIqQ!oqN9dr%Ko?(D^eTgGGk^#QQi(E#TTv6W*3 z2I7ppn({2Y{+RG97>u7e!GbUowxMUB!j}%}#}}X?|E_f6CYt+Mz}t7t)IHA$hN*OC z!+qoVrHdWdie7urcXlVbDYsa>r*-6CxF+=1chVxF1TI_yX zAMDWgJS)(!n(BcQsuxuBum8Y@kr_|&MOJ8WW;0?|l*-M_x z#5_{XT(;_c4F~Uk3P1eyu~@e^D=kffi@K?>y-REMH-0OOeQhbuP7aE>=e+oyVMs6`EMa;wu;7-kPNwu*7l9%;_M+Fj8ugHL)j8Z=1ac@+6)lq(H*G|4&RsCPD zEb_G@dpOSLKfmm{4USalv>J1=&K7)&0rHu(Hf&VnJ~_8t8`eto7B={fgWqN=!QqP) zcIoa9-K+odK1wI}r2cQy&}IR@kHPW3%5ZG(IaoQr68bwm0JB?t znf&Aryl}39bx!B0qVHX~stLQ-M8l`i_mH)BVwbjbgAkhr*jeGmE)BUW(LD$XPk32j zCJGLS9HZA$7T!$0ZM?1gxkF#J?!2MgIcqK(Ti#PH5iHxPU#Qxvln332_AUcxho3C7`KVrCe2>A2$pFi!Ij*J z^wjAAQ2OE$9g`y|s|8@4x(W7Wz2Zeb-*BfM=jp=T z|F9rA2)fUoFGv4aj+K)K%fqtGsTVZ@R*`M9`en_T{3ETR{nD|aS2|2}OQAo`gaJR-1fnO(^M7kH_qm}k==~9pspngcU|H8lmiyDk=4fQ$wtmU#5UOis%$zN)r&?d@-y^7w2plL*m4-Y#}mF4 zB}y&jompVj5**g&8Mpav3JxyTveM~+tgT=%4h$X6wmQvZO`qm~mE|I;+G-m1jOrq{ zikgYfHr&IJm4>+KgBm`I+kn$wk3r$h#r_o=c~k2dIP~yIKIddII9a>P(>6MZT+<-= z;^z#Q{&FKU-7>osm-%)KEv$NVUcdtfTBUuBPn z`p?6(rEM_L^(poCnL?k2E=9w!^)P!1l|MbQVD4?aLo)!TUM>|5Y8eh^&!hl(>C9>g{BRfoKB7j%+) zw6>8aysE+C3+l3>uYX=}dd^HsW_w&W(My}H{;R78G@Q?~3s7Ra){F$FlRsdnrW^aS z@HG5csK*S)2{&iTdK@vQNoq*9koPIaqu4#+!g-AgdZfX+sjk>Jy^N3QsZF=8dyLJ7 zm(a{P2)kwI$WisP#QnE{*4)*Tn`XX-Ylb><#YuhietxoGdSnRRi$5%!fdDlvG#Hd!_d{>vZ-)AiE?q(w^Svkob zo0p>C;P8^i$-H0wd#by1JZh&DBkL1J`}ppVXDrWyofpDn!GOWw`2AS3b*22hlM!nv zA1QZYJHgPr8mrViskNwx%;M2~@WXMr}Sgzg{lJ;q`B{_y<{(*4#urV6u z8U;iCo~5jLyB%v&w4JS9^96pre+MQQIH zdveaX5QZQyfUqu>=kelO^S0-_zCdWqf3(DyFl~x1X6Wo6&UVMgNom- z|MeXMcKt+=4~D2-!Qwq`DqqqQdslvKWJeedquvg~p`VxY{JKo6EZ>7U-LB$mqxCrO zX(GIwl*Dh}p2oJMucR&OyD{gK?y#popS_uL56(CaWNHy!tV65?i@WN;#;?y1JhYu~ zq9zZ&7W$Lhx0CQ$$6#q~as|IRQ5)ymlq=lbxU9m5`JdB6C8cC|Rq_pjysYKeb$uCM zHCE2`djiAOh#u($U+MIBb$F8-kA?oL;QY06Ot?}9$LmEFP~U}ZH~j-i%k9{=Iz9Hv zTmt72s;K7jTZ%hwE$an_;GCk5_&)la;LhK~*_mr#;S)nSIm|=c=bX?pzmMF1fDJqM z)=2jAw`S*+C-4W8hVoziRCvUZ_I#>Ckd)AD%$D1#F*>pmgfoOW=+xkt2NAfiP)B}u zJVe%seM|4A`N^h9N5L;53A@_u!XvG=Vb$61CXmD_|U^E%(qarDBJdsAn)dmiHD?V>V*v`W9HW!4zLFdix zvT*4z2b>dj2EJsw%ZuyP*n?IB<*3E#+-=EZ+}@VsznB;plGqn-zkdNnUh5_H{uA8g zv!LF-9zp`TFwaequ*21urFS-mht`{MWK1;a)5BUG*Ct-{$WypTqTr)BNzgcNIi5um zT=~a@9^S7bdo)Hsa(XCkzU2d|v5#o~n0A=iP7RJ|=h0J@ZupuHVy{Zt%5FU-vF!bw z^ZY6_JyZ@u zHwS=?{{u2!KMymnyTHjq5h8x!Z9dnk!=?CfOJ&ta@ zwI3CEv0Qr{Zm{|(Y`$$IvU#gS|NIHW&zJ=Us$%W+iBQX8RzeOCm&0JrAAjG zlnU3eDNJ6S2*00{zz*+p9A3PPsSc>d$F0{gNifoet>`P}DQ?Wj+Xs(o|D!qyVenGr zBGNrpY+kMs9-4L&aIq8U1)c=0B4Z%E!eJrr4xO|K9=Wp>dfJ%Ex+lYFV)bJ2Ua98~ z$FBsz-Jl_Tv|(qAHhfD90~7fO>eY^CPp<#Q=gAH%aMmK+`)NPsTsB3g7pF0E)mqV) zFlW8m_2t&j{MZ;?MyHQ=fy`CCFks3-qMs0jpGLQ3b=&=^xMQ;bc?k%A68HwSfx*j1 zir-@ch&x(jG^lpUlcE>(KwpQq6c5F$? zDS9++3`=_HfCu!>z_k9}*rYj>W`=3NoSu8YW=AkMr-s1r_z=mzA9E5#oI{$x7~&J(%|_pM!;#NPn!D z21=`H;N0yB`pl&nsP{QZeD=`I;yDyo6~estyI9G>mHhs%NH#LRz5Mg!I8p2S$i3&O zv0i?e7_lH6-uX3CYtvkq6nBW$)%r2TUFv&^8@nC60hfM%g#B-6$QwOY$?sjCWABHb z`R1w1spyZf@!Qndla;n$?@K)^%c4Wdk8} znGqB%uwx;sPx384<6+5?j?CtgshG25vz()IWrGO;tj)tV@|Xh?3N$a2Vc^>to&y(r z#Gj_h5PVTd?y7Vi)yg#F?Z2wA%)AWqtR{l+;z=G(i4f5x1w>y6y=x5El3!nOm`Yn_ zG}nujiF-qgUM$;t=Lz;Xpvm%-_sb_cXn{0?$h&`8fv?*QxRh%oyx*Ic?zJc!>FvtI z?gl;h@DBauHXAM+*^2%)=gH-1nP{O~2P<9Uc_%i9ZQs)t79JH@rS%)}`Hwa7((hDe z<=y1LYj0y=yLr&Gr2>Sr2sWYxGcl4dt>G=5Hocl3zj_(>GF*m>7hGD~up@x^C`@;tAW*m*qb45?8MTLzmOfS-@)gKpUh)# z3r1mA`G(7QfM9Q2=6rx2zP1(b8B}roY8!dZsZh~>Z6`Ne-G=K9wZ?B_Ed{gl3@G@c zH8n55t#&sx&+HAGV_(sC9j_FeWGP;K=L7WANaSON%P?}p-|!jf+vsusq5RX)QS3}C zLH~9B?36`YURrjB-@JYqmTq;B1AQKW+@=+N*{dO){CXYY4Xv?oe~9Q~MZm;w`$W#9 z976qivjG;tIH*+@mZ#BE4$Kx@<;O=cz0DE){oDmya{aOJY6w+c=!%oepL2z8DJY~B z%`&xT567RQrjpDFb%6Fu^kC1m(AGHf5Bvp-*?FKpUXWaPeGA?qQmRMaP3z=_Q!A#OjsGrK1*+C zeuf*KZF3GkT}qc>%ONZ=*(Uehy`IikWQWVOENGuAyXcdB?wD_(hVp`6(x{w;=#$n7 zt~hGSQK!s6_^xE((1vpz?&Ehk2Yjwxf>V9AL5qJ+<~;l$z1J}UR(&G$4#)(z>-^SE89U-fD44+V6h`B}Is7=9T^xWi2lQU9KCoV>@ zUm)Arv}HRr!|~pyESRRL$MW75h~GbhDRKs;i8r9+S!-FG`I2t0VA=bMDp&Lc6MU-3 zd3p=Hth>R~`=glHje?KfWcNZf zE}m_9Y|I+$v0fWT=BvnNc9!h*gWBd@sHkU@Ctq-il8bb;G0uT?;;F z`6@DN{6&x^3&<$rjx+^^hyL;!svC|Xr-}XtkKiJvCqK!`q?az9z@Ytypy6r}J^lSE z9b&YUZdmq%OOER?@LnZIXU%*Y*kzW8JscCZrn#Cn7VXE~8Kx}f z$^iNEe0$cn{Dc&1w-4%SzrolX57_JIhyzFl^tWp-U;OYAri7Z{>HK0isuc~_+k{Dc z@Bt~sKv}+!{~1U3e1Ye)MXz|&XCizj=ycr&ihg#05$*|mV9Nw(To{R&mJ?C*?Eag% zI*qrJoVsgaFVij1dO{=!*F3a-ktXH`R&YHo8&>93Q;~m%k-6i+*sBW+^LMAArl)Yh zvq*eAFpH;rUxt&SMSkP8?DcxvFA%%<+`)S`E;KcQ5B*nSr(5=nX*E*CxqINlEBaDn z0EoK)?3wK&{C94k)f@~SIqSLD2ZFF|bzCn^2U6Du^FLkg^XOp>eE8@AG`xcuPIA4p2S}UT**tGYJI~`{I_%WbCU--tsaC|9)(Rfpa^FOx|N& zw9A=^dl5$YYqE*m*Ad~z1(At|lLdo8vA2;BdP(%bI^tU6HXwTp#Odvi;MmDkbmq|K zh?!b2SK5Z^3kIQP{Bu6t<}udpZG*+1DzQMS;FWLUamM1|uzS&TGIX4Ne(bW78+`m%40(R73mzZpjtbuU(S9NP#Joc4 zy!R?BzY&RRhj7q8-(HbzN`$!blmcDL(WraJii=%i6unyh z(*G;m@BdKhd0mZ}jCe(FM!n-~T_^Y#QzdrBf6|4aB~p(#3n~=Hi z?Yu>o#bn^8F@3=6R*f|DUMRPHb(G|U7GdO>9ilgbC>)vm(N~J;+d{a*(xK9`Tkd$& z^%(v0umDBgpNhVe-0p}f^)dN`(QUR-8;>d}-e)hE)Ls#+_E@+uCmEUsb)uTMn21cQ z6kt4qmUP<#g<&TkuxcMHj@S#j=A-aP<4Zocv>ZbNxq^2tn9X5$N$(LTS3V}q$@Adm z+bPhsK7%M5>%psiK>(ZLxp)pBDntWETPlGfZ(H`ancF`|`rLR0e?s~& zat}FM`9!)sX$HA}@v$T?*g$+upGp;7mXe_%52VPai^z|h$5I>JwPgDJ7gDeF!Nlr9 zgJj%w6DhE|EeU21aXeQgt?v;+u5_xBlHczp=a$@*%Cz?ry?6JeK)+a0U3FL5wP_c* zf3`$g+98a%Ex0N7+4DmT*%Fd970-`DcfdPrA3HN9Vsn*beQDb{v=hU>?Pv)lQKQR$)enPscq>dQY*X_!ST`L_osSk?5%C2FyXD_ zyf2Pu&v_=@4%<(>@4bDI<)QO^~)ncI=1ToVet>8 zvJ;79$Za+LqAHr?jZooRx1^Jyr!{$+iw0TdTQ8l6o=@`iwfXL__M{@-p6?$b{9wQI z_(#h!BE07Oj%392r_#;-v0h#(d6IDMlBP5jUf4f@ESzY@_m4{_?QUE0`{n7R?N~j&zvvK| zzgvrkOt?b6FVW2dizoAM>T~zEY2-(uF^^Q6 zK~{RU<#qO%M7Xs1`3FbIjb=0M{W^yT_8~WapFwKPop^fj5wa=EnBSUzitN3o&s+67 zN_qyFa{HZUiNjSxPMkA|$hGpWg$2aU*np=STqiLL^!X*VJTimn^I3ng$@Xa+Un>>cD@wHhby0n(*H}uMmrbR(#F= zYvji|J3hNt4OwSk!m9#&iCLa0?=~}=m^6R?k0aYFNsX^K{*?H+nsV>6jpW4)J6`8r zNd}jh@ZO_tljK4tK6>3HV)Vk2cl>>el&iMqg{NK+r~S?xPP`ym3$6IZ;Hdvx8Q#b4 zleF#@JUILzX%XM=y>C1tB^zz{)PB#2_XKO6Uh$qZEU@ONQ9~ZxwBow)&&Yt;?YYsh zCnPY{n0NOnB-w73yvXYf8Ea+APuwjhJ1aZ#e=09Y;~zVo<1RQlV8MS_6%*}n6F#)1 zioCzvmhX(eKt7G?z*iq4L^#Fy;nW&(c6d8JKQxzk2D$L~J4wVKz=7*Lt0%2Bt$1A4 zcd{|glJC1$O~$6R=10HPk&Znr`I&WZ$@?ERyw~stWX54@{{HPR^87@5Zf0#ul2-QO zVh4j9X=};tZhs?PT&(zx=r`oUXDjYC;UDqWwc_z3YDwTW7v3uD5HY!L#lN0tMGv1d z=d0Q%QBOZBe(msY(xKj*^VNTe+@~#n15b(SF)MziM>?_4@5y(q)TLQFjJRNx($FR= z?$E46_4F)w^XNaMveB4pEd5IY`dRU)iK^6Nof&V_ONHjFGUwMSl<4;p=DcZ~D!t)j z#%*oY=+BGdc|tWBT5QgHToZo#rxyIiD`ml8wB&8OsMD7v=3MwA=z9$der~{TGI^9a zKUbqny@)ygv0t4&>u=4&CM(mmjz$8c^p>=&cILUZAIa;!wmkHT8hx_JjGJv%r%PvB z^W3fKG-9eXH-G(*Sl5{Fm4e$^8*a`gl>a32tZcZ8x!|MUapX>d4_04b#uLKS>BNwB zJjlE?bsuKJCp2r*Yj;ign5Vk5_>&=DQmRgO*tg+_TE3IUdP6R|=+KB4MjU$U(G3qQ zxRLmC!d0rsd(d@}=6rO82Ho<(m}ieRpgY41d48HcU2Ja5r$n@(A2m&R$m3RYORW*_ zN_6PCLt1>~i@)UU7fasCOoeXUXw8!i4QWfHF1PQkMXSHGNhV zugBm(JHB**0d*K}z@r1T>A1bRy!f684QnykNwr5?}Nk+{=s&%c(WGQ z@iU++R;u$Ax7*S&>(%)*=kH|c13TVu&y2cl*XO?9Ea=+ns+_smP|qG3e9|N<>YS{_ zHLu#ziCQXL;dy`Kpv1d0C{z3I)_g3!Bd<@j
A>A#@XJbjf7^&ZnA1q`;K8WX-t zru!}FiF{?Q9BD&)cq{Sh0&D8>Q<-a?wWc3x|4Ais>D3+h?ZBv~8i(!#s# zxOit%lfd5GTE~K3tW@HvUzF(2<@!86z?eq(DDmkd+tK_|Rlf6w;FLX4=1#5K($wq! zq)Fvkbo;e7eDA5YRMd^Uc8B28?f52n-7uwhhA8p+?t1j@v}S3+8FTu4Q;WFIw4-UA zeoEcnYtj+c>O3LFo*Mr6BiZ^|(Us=!|MPYY&TWwXZMLJCTYpHMXX??6b04H>yKSk@ z<=0Y|o}Fl?hEG!EDhoP-ypzPUPJ^`@rSP-%bg1fcX>A)rx}y5C`k{M+6!o+{z5JzGa-??j zy{f2ZVr*%UL#_0{&YnJ*TPInUIM5evkEEo{7F6W^CBb2!1N%Ldo*j0gHcRTI$Tjw~ zbG!Re-+S%p>JODteU1%%aqgK!YwhXW1COM)6CCM+)TffX#)b~NSSu~9Z$|}-LvjqV zrO_uIOSZbsbn5j7(%)u7T4w!Q%BrxXKm5K*`%CPo$aYE*+O25ehvEmhSYZkv{oZQ2o2Iw8P7S&N@&ooz$_S=RGT=*VOz!4_MoOmNa-?wbZ4o zJsmc^LdyGQO|vJLNu8%y(g|%ZOWl6j(kGoBO7^+!=q&AOX+)$I?Q3vVQu4K+y>4BT zLX@28tmG0Y@|_d)@VY96lv>dCzjCGPbKBFHF2&M>1Y7Evcu5*=(2jE!+ilw?;W^`cnCF$0XwzNU{veadu8TE|Gk-Q?> z(Uq_6N)Cbt_<4t?bYgx-x*==@pO+R*9Mw)ykp<)9M%;!+)h2k>wLgeGb$-bp5)<EJ;>+|K5EreO2GN=?L%H@t!ET!WlB!gn zr2k@!;lx4})c?1EsGE()cD~-I_nq)FPWy4>>vkmDQtWEYFXJgk-||lD#_-(}_mY?7 zC+)FiIH-J$pzkbGVRn3gH1Eh{IGw*8W-gdc%etzub2*W8vDIXtDW+gHU_HM0ATsqa zy-^DG2Q|XraO5JWt6BlVZ%1tx8uBkQnz$cP#RXSdV|unFjd70Pj&HqToLMcm{^t#i z&4+k5PXj8rRuEOYjQfTrKZnAeSytHhHx)Y-F=EGA{Q8SG zwDRu@PR|Em&9!FosPif`?rn~r7JAYT>x}SPVjEa-)m!YY4B@=}IzHEMDDH=9$*R;5 z`|h*{T|E^q)_(-l%;^oyTNitgp8a9L)r*`I-yg+h$<1X@~fltZF zL1$=WUse3<8YnV4Cb(qxAQ1bLv}S5{!QEsRco5qUHzw%OoOYq;pX)#~uOwhjk4d6O z7KPD|>|pDLb3Ai}9sRqxgdWHn4DXazQvbK{ko7xUs?Ti2D{j6gRkvy}!y%BKHr$Fy zs>7)5Ss!S425_P_04nyJ6?2Mg-m4*6c!E3Pgy;9^3VRD`VHieLb2>uc%&l~CRuom= zvyC5ftKpk(?BqTuQs z9s~vMzR+gjF5!DClqBsA;AJ}mot6ee+GrD;ViLux4_Wdjr>Zfv)SJIf9)`a@RC7l$ zGuW}M4GuI8g3IlH^iUI4ot!WB8!-~-Pjg*UlumMQPEs*sD^ z9bNIL1I7#*PCwG+JZO0bWE(q!Xa8N;_&5n_f4$*1PalR`-y(URr3;~?Xf*yE_(Gi1 z4rpFq!@Z357IerCM$y~n&qsE^Vm*6&czQG+ZPpfW$pPN|{u|hVT3DGg07kmD!<6m& zaDT=eJUb)^M+}d{Pwwxj-9$GrOXvs+FVnoBTwbjlL4}e6>o49Xa9OXGLlAU?VA(xQapG;xkvai<> zX@+Ma(KvWSn(#A;yp7M70y7fG#5u>LT{a0M#qEOBC+7sY_4}%nc`}ZioN-C2jEE(7 zKcq;)T|pe}4oLfVts^-vmPytV-jL9i{Ziw?FC?RTrZm6JDe}`NT6(zSD)H&PM+!9i zMlQ4(FP$H>l8m_0Q*s+vNz}U?lN4V^w~81k`d9)am};9`mXDj;+FB#>Xi;~|W##AaOysXlW{vgvh}7+b8A>;}msbWV~~W|T$rS`JFh zS~;ZjQHeC_R5%&Xm?3QoiXjuNmP&;mV@RKh{nFDb5#+7@c4;s>LwakRmTFZJNv6$V z$^CLX$(1st>!adH{QEPKLBc-r=}?L^^G!JE{3=WGzHm^~X=&0%od^=)h?3ggNV2>1 zjO6+*l9aYTF7en1aD*t7uZPCQF($eLs1c znkp4^2q7n9PD{N~Ly5mdmXu}{Ni3x#sc=IiQC*uXu>n!!2gXT0`r%}a-Wh4rn@FPv;sA!WSQ5q_v zIL}*2lG)IpWR{RpQjzdpyq|oK9`1Qv$9WvT1Gip30x=_E@r%|W@btQjMx%~&^^9y@ zbpjUdjm6H4>A7RP&-hLd=*)=6xz_`rk6#pCU%~aR*KxSc<0vc*jl@-JgW=J4-W!~7 z2r4e##Igzg@Tlf0Mid4B&+p=x)jm))_73*U+7BByJ-|IH_JQKSd#Jj42OLd$fQEg& zVNXL2COP`UqL3UMlfE4WD&=FZEH9YR^8vU&qu;uPv=+~5vFTVPK zHh(WSEBHWVP$t&c9DwfpE1SCwQtF@J4lf^Y4S0g%_Uwi8EswE&xetu?&B5adJ7K7p z*4cFpoFUJNJ{y76KSSRG|3Ti4Czx_=KlHDEj0>}OLbvdI+z_)JOiByziOmL3Ka-B? zCw+j)6yxT7Tfrsp4GP^hgZr5`_~QL)@QZnmr@c19zMw)hP2K|M%%9_5EpOOS`WB1# zZHI=|5{xb01iaw_ccpBB6NPW_YQtJ+NqmK$^r? zXSTaOzv|C>EQ#LJrPIAM*$SFcmH7FV54@bh`$qKkz??sISbBH{oK?Sx@fV#SuOb?w z0(XIXdIbh$EQQs*s!)CWW_XxiiM;a&4ja`VX8}v<6w7f|@EmA)Sc{jsEr&VYzwph7 zHPG2_vAcR3csaM?S9mTlZ@Gd8Z>@$eZ$D#x&=Ro9YQ`(kOCV|OXS}H44W6Zcv24I% zC|LOkcdb|traS*&Z@Y!?{&6<)?jsm_sfhPY?}2YN9ayKa9$e2hBJZ+-yYK#?Y2!wq zb#gRp;8J++`4Q`FonZ9&Li|v+2Kamu^L0moo%0{8DGLIF-SX7Xc2_bd=oJrRf-~Q%GdyVs}(4F#U8NgsY;#a zJK0;EBI5VLpmsHK$y^Fge``?6gaAlcphOo&?}KFpYIJYnVi@*Iop_%I@a|~}?R}K% z#yu!;7{9JRYSM&j-f&c_C#})k2KjzHNz2?D@}zr^obyWfvPX{|%-ILJrUoQevKbD~ z*CexJ2Vv0W9<+F^GpKGeCZDBK;Qru#R5NosyphwT^;bMVZh}5NIBW-&-8wKb#1Ay> z^~lX?8!R8DOLLZ6cIi^}`hlQ!Lyy{L?FNephSUdqz%NsmUVS|Q8?y}P?aIlp;gvF} zzgPtmoO;lh%0tl2(}>bFeBt(aBjOq|v@g`Bza0nQU$PEee0&66mFke`jw2vbV@N%t zj=}=|-p;f<3dc`yZ|bX1Fn!mX{KAfa|6Dy8q4lA^#PzBk<-a|R6Gn~`hUZ^@?SK2(%&1R6(J(6ZQ(@Zz&M z>0I%XkgQc_2L?pR_9oR4=Rq~OH@)k55jr_xGxEaW$ZAVcN{<8MXe+vT@iL4Pt*ACW z63)G`q^AF_fP#W4#hpunv-eFXf7?OeYjkpR&}PAn(&RHWA6;5~X_dk}(Xjh3I?{Yg zd=NgHMqE!2z2d$>uvs^;FtU)H{54lN85zg+mLC$vPPP>H`Tl0hy%WWERilK^Yr{qR z)p^1VgAMHYvLQnDjRou#XZe1Z_m7k>r-)N_IEi}4MbNTy6Q7>?%hso9iotUy(i#0o zlHC|V_l6fyS)MTzg^%FKj|o548_>+M9W3ves!$YBESCIO!L##k#K`6WLW2DrZEIhVbyeFzQICW{1;9h<7Shv(8LTbg7o`Gfqt?@trCt z8ke!A!_$OCmY;Ci-k0p&xkD7Kry{nL1d*3?9%qJyB46u?iCGhbl2zTr*_R`P2Uc(B z&Ige#pXw!^uIVR!-ENS|{qrQ-@m}cUPkAUj6qcQ^6jt^-PpK_Wn3Vr=@y}3s79AW6 zHH&(qU7oZc=eb7`(sV%5$rCx~R|*FG!rAqmEiAEeDDD?>Qw!7o;^B>h#de#aEP9Z< zxNTz?QwW#@!s#lIUTno?INu;k7iFyZ8zI(?E~1Z@WYf~y4~w&G3&qREQDW?^f7En1 zgn9%$qKl>j1Vy>!;@ZL@<}>F`YIJJ~HBZkFKD@LM{asHBKVApZkqI-!3;9i{1Eb?a z-HH<6-Ud1rej5G-jb}p)+{k@=Al)`_Ce9(EgrvbNK58p`d=t$QZ&p)v?=+ZY8!GB2 z`lJTLdWd^BOlQ0+nI;dn7J9bm)1^TVgcU_ssgvXLZmYfcqhGqfdm}}uwKG|I#TBu$ zYog*+Kqph11^2cUyu70*(rzX5J8)Nw4Xr1?i`zxcLKKG>?-Xjo5^3896t4B{3)922 zSczmk1bY)SG8 zTVX=aH>gyqfvWeWND{9kfX7#T&QAzqxbKFr_n(Du(xFhW9=1;?x<6PrqP0-?cSJ=j zkNAwXqO|@)B`!&SeTw5Gpcq#Q{Hx0bl-;k~8l}0P?>CoA~EwpIP3>MbDjsky; z7Wn-}DvOiY%~l0*wqq6@?dv6Wo=*yi78_OULI28iF*WTNTcMrX)k*C%(Mzb5JHZZ5 zJt7pna_h`zu%fX0;yl->f~V^^(M-BPP+tIS!SA)g{)CfI;qwC=v&T~R%Byhvj}1)- zlwk{Yn$l3;#;)g3r_Qc;*_{5J{|&2ZOz7&W-|#iin4Ty$!jpOfTC@2Nc*) z=t+ycy7Rtw4a%D*$L?kHB-5uVY^j9`&EH_ang^>;xUDuDGeDVg_vo@&Q~7)|MTa$q z%TdY-U6!39L+k!iXDLVJ>B?4Rmg*=&B`?*PVu>u-D(SP0ciJ#^l>rk^b)$J&hRkSu zJMtL`TRTve?r$<+TF#BQ`Mn++;MRfJO+A@SeH&V=)MdwWWa!x!JyxF4jci?YnabmT z$l23uVQ(2S`J}^geA>|Yp$-e#-HjSVB^I_&hOYlMVP_ToV%+C%a8p5=#$*(LdXF}o z=%~wfEd7I5@(4P6sD3T%&aAIC;+loZta_XxwPj|3(EJfQOnWfyZNS+heuD9RZF)Yk z2)Oo6e`-|O-DPTIl3oMGkD8IQVOUVXTl~3FiCr`NhVu>NSiqZC`0A@Bd*RuNz5ePj z%Nk`;JEg%w*3{z8J1Xq=rB-xYqsVMCYw<{EcgC4#7%Q*O7SH{K`_}bf%DJ^D>&(wX zg)EgH(_}_mPyO;ot?T!^|Dh-A?^liU9vd>He-&8!S(lY)*I|!QJ$MglC8k(vG2{NF z_}bQht<(L8sISg=hXa0Dqsy$9zD57;`t0+hI;>GsU>mUl&puIR!`DB@vX4Djg7aGp zQqX2^r*vSy6|yYjX$jhgYcLI)Jam%PWPcU^;jVOPc0&C%Di^6SsextKH(QnsZ?D3; zpv5+yeT7eBHQDr$6{u5Z!jdymaro9=?6m3=O!y?lZdbP8T2x};N5A9vkFJ@fJJZ5gI>BM(c;)Y+;tIk-4ni4_^X!RKdW z*sPj-eCeaY=JtPq1@ooYny*=yy0{w~^!q(FEqntpE?;qypDJ^Borlx6sx!yrC%CPl z5k?U2V;tNLlN|F=K~t8MEq{(Cy($CGXcUU^k4rv75i-xjk^)Os6 z0RPlg!IOLGI5|L;St#DcX*Lz`&jj&^$_+SS{2a%$e1oZqg&1Mp2%Y-yD&=Mf*_V!| zn?vCE`Z7$pkOlLbQZdUxnt3=TWB#V%|NBSQy?_~g({X@91#B907uRn4*43A}zwsr= ze@MX|ue-6j*-7}=su_64E8cqe1^yJ@!+RS#z_w2uj;*c-o!^Q0>2xc|r$yo4q+bxQ z_a3$jkKmI<2Y%U=0hLwv(JrYJY_49##=jrHWW&|2e%jj+(Wpl)z**9G#;6kBcs|0# zla$$)S^) zScY9oh{hZ9WZ3LCQC&YDA9@k@yL^MgS(mVMZao~*x`v(>^^g;B4L4SNg0o{TW73{t zn5PzpoVyOO|B^5v=mS`(T*GcNOW^y>%edh2OUP2Xk2{7I!@h{?IAqy-;GPFOoBt9N zldoX(qaygJlZZxcFX7DC1We0+0iMRuxJ~gTbT^K{UZY<_d0ZrZz3>7;!ecQ_=@~q? zzk+J%d64CH2}A8lfO{Nx|5q+#*<8U%N-trM-bK7*_7WWDMByTbY?${k0=YK_M%iD+ zK~tYW^S4Vl;Z-^`R9{0AolGbhACJ11o`O@4NNjoZ05++_;(++45V+QdR4GIU%@z-$q;b{2&=^Q>ka2*`IqS0ylHQ48U4%f%tgpnoT2xrcA>1DosqF_^Q z6xz(b2B{w5xXSYgoa=TM@4X6#kG>J;D!BmP-krpCXComgCl+1O!obV$4*p$!7~cBa z#88_s&YHM^-FC-7TzL$#6`T=~dles;ML^SqYbe$43>?Xg#phC?;B@#pZjw0%sg74M z!RP|4P>4aU>cJD|Yxs2gN%-`D>xYjoLD{^!xJ2t5yv)9dHL;-}opBY%JPQPDyNbu% zcfb^gMDD$W!Bg+!vVF(k=;1_cY!8CRd*d*-<^%*J-^AFRM`88%o7m0eFdVv=geTSn zgV{$yy;Xq_JUJGB^L=k~_6_V+><2En4{?Y2W_TL^7}b^h;DO=;99--Ry=yWsBIY1e z1twwc$3u`*n}(d}1;sy7u}{4ZWZy-+o30gp`bhtZ2}V50Ii z=#wqtk3TCwzd99nd29xo0K|dUcEil*M`)dP9GcX#ur_o*%>0>-A})lDU$Xg}awo(H zIcWIdKbW{J3-eyD;l+Z4EpA(2fMyPE-LM9v_P*`%Ta{+ypnB>;aE{5tv61eur7i~t zHf@0|nQt({c?-`XKxYR-#uGTqj!p0e^*Wfwbj6u~>dREV2BJ(=6w}sp&tE=Ro1@%dc4F>J5^+ zjVQly2DCo=g%(fk;I+p;46v914ZDBg%^vQ&znstGt1O|nMhg~wk%rEBc|gWXNxvNx zc-VXtbeOf_hjjvs?j}Xq2c`pGd(wm?8-UN!(pRgHlKBmZL9bW1#{8&+`*QJF zxIWAa%|`yjfV&DE7_x2%Sk%dpb%;H@8YV+|g8&jFG8E;=b;&d-(w{UP{&1Zy_}2&+ za!;OmzVU#AS_-5QH5yo^9JwWqg^IatC~g}GZ_jd_r|mEtA36zt%1=w3vka+hS^?9` z-NN~oDMG=KcFJ2ADXg?SOLYDWa1W?>AnP72{GKiDiz%g#{SSzoV=1~EK0^+6apHt2 zQP^|FD>}a71{oCD2%9GD7sr^56JkA+MCsIc_Pns0SRkjwwEJumQj>lFXAuf=G1HmH z{4wGPk0@rRuv5%&9LfIVx3S0VyHTtR5^|P*#rCta*r?Qt^dsmw^0~3d=S$3ViLV&T zB=pn3lJDc<#W|Wu}1YXZVDH9_DWE^sUT)7vk?aB1+!rnHVMOg^I5gb z0bz}i0~)qohF$&+Vx!d>(c`~$VoT4poZayPW(H=_mc%%LcMMTT-XUS~4_A8Ly^`|2 zjibol2I7+eAp{>+2%%Yp=%SM-G$v1DdpkW;D~#1`oghq@H-_5$ zHVge$@?59GV^$rnAr`;819L9S6Xtc(7q&D+3e{U?3!DunY|06Nn8dw;*5VPob7#A- zev(97tUL;*)NG|89ec#mZIP^7=2%hHHJ44i%I^{CC3g0c-k4P_CTt(Tj?fM9ReTx@ zu0O~+@88b7e9^L_Y(CEy<}K_vPtsAMvE_Y|i~yBd!Q= z6{}^2i!usM+{?9_BTGb{Bd0%R$HZpG7xZ*T z2aDn_35pyA;-?e!4J1+`Uv$fPaHT<0l=dttN}a zjMRAyYX}C}{LKuGfAT8*TCG6_qNxJ=J z3W~#3$yZkpJH4hIL*2wPE&;TtmI<%Ms)*qm{DcCh3hK;e#y<-u{YM+c@k_F)aBTw3 zd=yGNgGQ0>bGcv6Q~dkFLGWsqrC4(x=6&XZ;AYzf*|W|F^J7*Bon5zyvMIvw1;?n; ze2H-GULDO`b&2f_J|-UDar)lF?N2&$zO41uSkA`YAzY5sV7V!;1l_PVOh6_K*kjKb z%f{lWggWq_qa|ATF9VrB>RT{hkqo17 zr88h(;znbpMnYPkGl9lsFijXp6H@K~&spLi%@gp93~AihXqdTj5E+C-!@FE(s&9*g z5y5se{AxJl6}r*motI&ij007F=ii?-kS4|Zf{Kj|sXo37eHUBOz^C`Y^Og&(KAj2^ zy{xHLIT?5-X{Rp(c*iOI=5yL4Ju9-FSOAV>P75LmpvudFE)~UheLpbd9c(K%q_~ul zE*)BoegoSZ`jSrKJK$a>stL{I>nm%j_AG_3(R#%9%Ft^h*SGr~hsszT+CSqJc)c{I z(vB?HQf5jc?`yF~lLye}pwDn?oC~R%z6JF|hBS9=DJTqgB-|JdGpbjRUtd{vNi-q) zk_CI4jmfbn1Gp!Os*1U}3o&9BR#&I2_yB*!qdj{tf z9O&tyOxWh_K---j1N%LYcCO0>%?=0J5Ofb@KRJ_YMGB0TccVKk_o2C0KUyF8911#Y ziO&YRJ|}efOWp_MK>ZU7U~rv1Nr#uhkm>fcRW~2F$BZ)c?!q&NK{V%Q6_gyXAoEWj zpw85uxR<|cUJ!0^4=h)k)AyA7@X&;RZQ?%6G2AEX{}Z_PjI4CiV3evQ-TU+uUXC!Q z@p0Xli;E?l8}|x~8%(He4)^iv459N%{-9yvNn;i%Fy1vvg_;k+Dawfo>vO@?xHruk z{1v<^jA{9&MsU4k!Si6>A?S=1owVeiXVH&#{rUmi!$yC!%HW2?mO_0vTfHa0mw(oQ z*)~g(Rjr4B%)TVw`!!5&?oHoZ>p-!}ioSZ)0`Fs|hK?%WIe+3aeMtUeO@=2I-^afdT%AHx@}Ed8-mRfZ0SzHXXs0REpXC~VkU2;5;x%_}~^lom63 zRPqht&sdUSa5KQBKD6THceqkzN}0AD5Nl*k{8y z0gas01R1mRX-%CxyUgcQhV36=^ml!Vh>>ICt$NUeQ!?yQk|v#W<@@j}+H|u>mN^(} zlbPf1E?@mdvpR5EqetNlKfuIWi_gJoz}>q$ohbPQDid^xXEGp8N}Dd5r$EA=USu@8 z1-2z?()7$0xZ$BmJx6iBvV$J|yeq|QleLI@8vz!n(x`hftkzM3oS*yz&V{83>MgJ& zpgXmPb6>5oI_;H~VXAeS6uC*7{qgTke|yNW(fze(rn3UOTB}C-AC*|k&F=JKgDi`^ zsY%EC$uZuUMbrAovL%-l$a;$m8}>+rR!>uABVxH9cBmX1q_0eJQ@b<6^QvT^Aj2wc zQ0Bp{Dky_@-#R_ibeQH(bvEh@Mx8z zG%qQ3KBEIY`YW?x+uQMbwJdY>??zS~sSTW?M)M!x~MYF zT*YIptq@&bg>zp1hPj?4XgjkF{8B#Pvmq+%!IO`;;kz8Gnc0lGHXZOjya9uKrP#-Q z^;oT-#D2~Dj%5Kd?2m6QZk+NDcs2)*?^9uBF}3KM-vEvC$}nlJ6#I1a17?41gxVGF zFzC(?7~1d_i@fAnj^x&JZQFL})ML{% zWSGDDd(7dw=r!H9xage}8@c8kK3%EGngUa|;#Py#TDvjDT3I@DPnJo%-lC;i z9b~*O#j8R$cJ%stj0peNrBhy0lwzYiif}fyL3MHw{%HLQrC(oTw{G3ow-2u|y0Qbj zbY7v%sUP6{{4Ks|{sBE}i&6Pk6U3AhVeN)yIPCrwr}#I4U;he>xA+LAT4i|c?{{!x z1(H3_YdU{Zkl|7!$3KgGQ=-(a}O3lyE|AhGQ^9t`^o$95Fr z_ShfLe@HHJA1MqCOUH9L^&s~n7xx9%fv}A0wR3;Ldg(`)s{0eh4StB$0~?_C_)K(F zYJgK!LsHeyL`@ z4_toJ{iqy5s9|`>Vizn32^De#K!grlO@tJ0dkeS^kK3iKa ztg1ak%UGQ7RV`b1<9&jCRZkSUhprZ7b2WwI7mxB8sI@S0Y6i1y_7c3>oP-jeXRPUJ zGnEZmMVX&DKY7XtVr%jk??gr;qkF=aD+Ms{YO|*O>Z)f3Y{IShi)Af{(Zbk`<%k5#`q}=mWdO2R|q>|uP^rg;*+Yk{}EVh zvlA8iP7-IA3=xNHkxDCEZ6yBiX%wsF&Wb^=oWzS#Nh04%2`+JkJhxFSv<|Tvyl(C* zLB{gCFg?*xINPEoermkPj&(?j(QYAv>9-uAlcAA4`>fDw_+;wr9SZgMEb62$Oq-cr zBbqA5h+hj&aK`Ql;d@|?7`OPM5TW3bW|N)DR^A_$_9ridsYRU<-I5mwb$#Y??$8;5 zyP$=Ovd%(CZU-GZGLtxKU0mH}Csy~FA^f?0hIc-N2rH|m;ru?AL920?XeSrVrfeT4 zepPK3rm)ZKl=Tzg{o`xGJ?jPH$zNeY&bsH~okw=UxEN!S`PRbfQez;cG?`iFYNSn# zYh%CXyQh_F91;Sig^5zXUkIJ{!Y<0>l738b zYS{)Sas59f8257w>tqLIc3coLtIY?0T;0M_uB8hne~%U~H)P{voAZ=?!&uy!%UOca z#-gMqgdGi=Bl7+>@x+T^qDF|ED1Uk^e%wEn`DeBBPT3&3-TsW()rfQ~*p@{fIV*(w z_hoKrzr?KLErgW%GoOA3BzBe8uSuPuoCaITL%JsW=S$d_o z_i(USaX(M|TBOK%d#>WAVdup@d*%qA*Xs&V$u|XE%}mBMRmK^W;^Yz9qDchjP(ARX zbp^xN;>u&J<>Ei~xVDcd7bq=`ZBa^Fpy4hKzNMPxGUz$o?bk~*o0)=(=3W%mT8$TY z|FEDFpfy)Y=Kn>WWU%{WcJ{tm0Lc;5_{asvZd!Jhn**QEG?90R1b%k@3oD-xd}NgJoaSI4DSHN6$ z%sq|YtI^`=FLpwF{Q>b@_c6kO^BQd0qG7_glL~CjQA4+@E(cOKgjv(|)ICs=8itlx zMj-uP9V*|ohkc*((b3of7QcCoe>D5Ubh{#ablVXQY^%gvMK{>{`za>q4h63pk8$ji zQJ~(Ijho9xLB3)Re()U)r(Is4(XE-_SpEWEkM{tj=68H9q3m^Bu$kT{!C3~RqU>ZJLya(L2=VHy(y|C*~7HkbR!s_^g6v0d*$ ztsW!cfO!d0N`F|X^cIt92SZKv7aSS>Q^MJSSoKH>)?Tbd&Y*(Sjn()d%LbaI%!y}& zpmUBJeZ&>c8!A%ti|L?!s~#6Ea|IWxPgt_q24pukVT;QMnBP`}_63un)Sw3E8-ABO z^lrva8WVwk2G$sl1)u0De6ybGW|PWMai9y_SyhHFRx#K#zEq#M%?H=4OB*a z#B{BZU{wAMBc{KToGAZ?2X)3mOW_wZwwwyl|CQr&XD?WGs1)D+SqxM5R^xw17Q?Q* z-?(r8D3BfY9>49@hf=>I`0Vi%i2dG*YbFeb3Wd+OWYz+h<<^cxZzXW*vLw}ay);^oyae+3ut~m%-MynFf9K!95Z76kdBxpWR zAntX7vZa6V&0#axtk8I=!;4;b1e0hrRH3XK-P~_`DOMrRuWU*%u z#5_`?O9=qy8sx|~b}jhwJn6WlPH^d0PpViu7_R)3r_S@=-&CE3oHvCzFI0FQ+5#Fp zr0D$vN7&8h^R6%X^>&=+p;Mh;wY56crCLH~E-QQOc;I~zG;w~7#9mdEI8y?oZt2tK zQS%_;ID7JzUB?DUUW+d#~%=Z_w$AeFe7OD3h z48G3Vly!dzOpZ{c^(*usL$ePZ5T`@^`W76pbvJ}j4^nwF0ZLZ(q=SLWVWqqd@%I}v zm2!W_-J#%oQ=3i=9t?B+deQS2HV~YoPv2KKg4qW>+FdmW%-mIJ+#N6I`^K7_Yn@@i zr9Lzzc^;^z=+T^o<6uy+K50&NfIbN(QiNvD|F^E z2kN@Q7BHn7gB{^-j7cZgx67w@e@%_#=371PJNzkm5^P4F&HX^GTW{KP%mkpjAzfYQ z38S-2=)^7wxXv*lRnJMCTm<^&J`OhgGNhf?JV9Q+Hsf=F#U@>J;!pAxNT!3s|} zYh^~~D*;Mp^r8Jpqd-nhn|6Mh2~YRfl4t6~u6_E5axYk;Zc1)jmEm53Jp~VCaJbZ( zCWY^Zmwr8H(1BH;e9?~Pdd>sBrXq)lmJnZQOkcQJr_*1Ul;;U6R$FmC!vY9BZcejk z9TXdwk;WKD=)T*OHulhlIW^kU*s+DrWi9D)v^&fVGoyj}JK(IB71%P8*E0>Pg;jEz!692O5+{yX~GTPe&4WNO&fa;&x8~Nro}qe)b>ii8rI6X`I_2 zYfD!q?1LY6{phRl8aOw>hF&lqkn*x6Az>U`^t7UnFO5L`dmq{^I}cnVH7ITTQg96I zOS@++1+{m!WPI>A7|A$N`A9to^f#q>Z5v$oM46G|KY1v);!2et=7O`O38|bu)^(ge zj8udh=Dn%)9DiSMoh9*hEa*>npz)JS+@z(lUYh*eys%Vg`kPnJAj5cQDWnMuwG+A&Ob&% zbEXBI`*;F!9@~+PXJpD)uS$G;dY&797SNk5XZakC>vVddV7JqXjunMM>0n#Z3Ofdg zGW}_w(J4sx?oY}a!=PuTE%jsvf#2IydP^6E&+kKXEcSp?y&>NRZ{h3@8-5(lLG>pG z>gRp~{C--~&YR0z6{M9YdeAwS108mhH6aA{T{fbd6OFp`nYg`Iz_`MR+%nI>_4xiY ztj{XonJ7}S4uS{P{pe;Y_pyaq(OBIu&^SGSe6C!B99<`hl8x%pwbFm`+{s02(kMUI z_3!!Y5Zcf8qj?%WU||9zT8H6Yw}EtCV=~mqE0fK65#GtzQMAe>7*R2h&arsl-8^)D zcLJY#a^39pR!Png7m^t{4Bkw)CEMaVUAo$=+EAF)$aTV`r;<*6Q&CSXb>-V$w07NU zx7SJOM_? z3?Sz>iLm9FghI7KVE(j0^zdvT7~Qrddv_7STW!f}!C{z(=Cp3^Nr+Okre3nUASA(o z2A$gohYeh4xx-r6HhnxrPX8k59cxcEW(mN3h2)~d=gYeXkZ#6dsG00Q>-R)SG%LrG zv3aB0YU82w8&Y5w&wC$NO9by>&LsTIhklxU$#S9yRfFtF>+7?w>+D5H7L>)>P-Fjm z(0*Z0Q}1L#ilIG~49$g_;(ipX^#ClUnA14<98i?AAZwuzCdk^*=d!{sU2gTmOo;p0 zm#)qTf|esDv}(pdn10HccxN8;jTlH;Z=S$>7yff46s`?_OYx>=T1I4A`0Rfg$G@B|JuhZz9yEQhBZHNNUB^d*e}2&zE0SA$ zAEMnHDfvMH_^z^}^X2gJo=NA(yK1Ln6qA>z-u@y zNuw@ZaY=kS^!4W4ghQp=KjTEZ&gFOc3nMe$cHI{`kwq|On+3gF_@YbKet)u}>%MUg zNP^z;dv|dzE{9r>($;#|a@(91yS@e9)k2lC{;yZwxmwkwgJ-Ij!QEFTG^Ed47!qJi z?!~v^){>2s<9!B7JH~YBo|6|_&A+7G8Vfbm`KkSCBf{oWlP51C7B(G~3|^WcD|pL9r#^k!D0fKn?hQ z)+UR4Qf#Wd5$&cgu>6|_akdWpcICd-(m!xc%s&w$~cere#N>4Zb=*m~{^&IQfq)p?wKDAk1gFMd3 zGDlI3N*aE_i5@z%`(YzktTm?H0~+8(iYC1qpwBwrH%ayT;O>XBxN}+z%L%wd59j&_ zo^gk1X7L$e;MyQTx%re}nv^Z9`4A%n#q5DQ|IP^;k1ZG13@l*ZqMu>SK@hV$KJxA- z4^d;tKJwT$jD0caCALbuh5kEq(MGSE(9^juo;vW2_9qSzg}oa@tD=44y3;D+phTYi z)bV4P%vRi0q#~$|yGdS~nqk^#Lt#|Q2b9Q876$A+&&*~&B)5H`%)vHMRXWD=KHh6@paanJ}H{n>Ll_WO5tDJJn?(kZlTceCGnXy%R763 zj6yR>OHG8lg=S(y#WZ2KVrofLU#m|iv zyk>?l+fN#d@5Ke4RiNF$#o%w256auSNrXLq0=qa&c*VJqs=by8#d0!2Thm4MtLQYD z`JG7BSzSqQ2l~++M}4;X@kDW}X9eT$Ik6=6oTPP*uJCF{3(C$(6*ArGcxKmMsGpk8 z`i^+bxYi@`z5-@AbcpEgH-jDXnk_8Oyhh&V3WX~XqeR{X!frL47lr+|si((l@yDtv zc1Y%`_$AIq&~lfiF^Hv_F^vYG&wC^58W)p@*WnRFJ)>|;>pea z8aBARV|VlxQHkwO_W7T-_@tQgKXPMfgh4k^EaMC`@9CKP{)Lb_y(eq=dNJqCOd&&XU5)eflf4j2+s%8#&r zn^uV8$Bxb%35!^?k2Ceh2)(_dDc*Gj{<(eytIb!72kV)*f4Cst_8cL0=0NRx?o9Q4 zIG%sT0~Wf;MYv&<$-3w3NY-9d6Q+er6HpHSSMkHW2;+vaRiIANo1P>UB!Z;3Y04i68m4jO%EXgAJuBH z&+Z!q&eCGL()@)pN7cmj1NI4JUZoVW?Xn;xdJD7HUlSsmu5iv?l)$wN44CdOyo{YG z_S3w|9xGPh@~qKfgJ(U@c#jZ$zMD~>7rCjOyvok`*l#0MGIYJexiOFMz~q|%3!|{% zk~!OZ5ztoVNZ1tVARHcQOq)vj~0D&cVSx3HZ!A1tOJFaA8d{6b;M4VAr2nCeS)<=Z$QB4bW}KT2b3DPAMHpoG-PL>$NnVn zz4;XHm*0VpHi%-_efT-_0WMql5cbI?VnFLtsC0gSGp2{Yj?MRxdsv}3Nkq+e1hGHQ zVASzjz*(4ht@!~MaX)9N;Vn3E;T~>Kehx?9aDQIQJs4tf0q3fx0?!I#yUj!3tTD73 zeIJr9@SO7Wc*w51gGX+q!|SEVcyQn?kV?+SYjWnPzl%2FGBRYB)||snlZwIY!Bb32 z+ypg!Z{V~!*${H?UYC!tP@x22|4zs~b_>4rZ$R!Im(t z>qqbFIzQK*dcb9ThaEw4VY&2dytbnVLhtTMl^GWdV^rj6PTQESyfWYWum{RLY2dI7 zn5|lvYOHr1oKC5u)0QZ((S3$}4G)8*RW^P-as}?cdxdKDf#81V4*CRNf@5JDwhw0Vm2%V0%L#d|lX%TNX#d&LKR1`foNIHz-Eq=Yg>QdIf68j)kX> zo?st4ZalIED?~5; zjJrOL0#oBco}XO?G5+;jnQcbAR@SD_SrX#}jV{({Y}g_6zpG)Ql@YM1_at6&zm{{4wiE&-#Wa$F|m z3a1vAU~0}d$m>&w3zLmNS?yIPhXi&E`h*6SL*UEZpBOjV9)i?LP*F<*uAQvGv=DtT z{QCiS$GXCVifVkEZw)>(KjN#&GB9iWGqjyN2u4rtNjH>7!1?k|IO1=QE?rGO#u;)a zv|~$>K4?m6aIuwb7hiDta0du%uEcK*V_;Qi2HGDEf{TY9qLO=>gwLocePTBd_rAo| zUIH}hyul^$!$5ubCoK5x0B5%};PefSFut?_uTNKoGWvj*Z!Cn(tN&r~ltHP3+@7Fs z@ii3KeTcRPDysAUH6Ar*W2cgIQKc?{Z^@D70TYl7 zmZb?6x-itb16_)3!KO`?Sn}c>YMT!c~ zki6~QlPIO4pwW!;4TNtmxIi#!PA)-l-cIFwt zfD8?~I=oRbA-N~HebWY>!y|)O4++;CXz=iSN%#v>;$96X4AdiUx4say!H{0`bcU@x z4M}sJ0XQ`1(A@DqB#w?fsQ=|XkZY<)k+(V|<1MYoL5YEbpCSE-yqJ3Cf;yR<5>waK z+{R~k2Xs@csO4L$q+p9Ofx8RjR`GqEy-gR_@9~srkkY$59Vve&iAw5Cwd^a z!h-vmL;91`%<9yl^LEs4S$_ytYe&wegLC~&D0;&**s5nvS6q7oR-4eE&vNkUkpUh2 zFGbSXORCdo2R^~3G$EiD%<0pE-Ue)isQXK)EbM!#c2mB@b<{evo#G?>4t)bf5h=pH z2|gs1ZYXqiVm<5ek)AHtAP&yYrp{~Y`SMI?JMe)SAMs@Kp78vvn!GUk{S2W;e?Q^D zIZwg9Y$w}se>y&y_E_wnWh?RyZ&5iU5c(+JB;I+;ZtbmSa}^s&F?a$!S2-g-d~Pmw z_KqFyR|U0WMlhc-GX&rCByzspQJ>TfIo8yJS{GNI)E7od&R7g4fy1JL+AUMoPK`f46jF2@%%koepDjzZbRX; z({Zlb++!D3y%j7C|B`KbrO=cSD2|rC3+`8{xgIBo&%ZQDk|ZZnw|$r^K50`B)cjM$ z%KxU*GU>shzwt&fwQ(=!UsO=$7BkUk?lX3E(|IxZ-3UQTJy6WK@`F{@91{8aiY@j& zDn4A;T^KU+AidQN6fEWs6so2V70#BnGs}#PG~IGSuDG@k4!7(a|+XSQ~* z`H{cf-oa%moe|9XRV-iO)vXg7a^W(25K9W;sw@;szOdc~-{3CB7xVCtS1xHu2FWyabaIX|) zUcZ5>4cCzU`pdL(;SMowoP*dL_gYj@9ZCH7iM*ph`1tb>ZT8(S?3EJee;l26TuyHo z#-qKLv}s#ORMPuA_er**Us>6ENA`@+)X+c~p%T%M5vk5|pA;EIkv+1?R+%Zp?|J=Q zpHF?>_nc?k*L8ibKF6Es$c0sG`HDltrv4V&Vlcs<}S}csChaCL2Ifm6qYWu}=6)y}X2T)=F07xIz2;mvqt5bA+>cS;oWf zEMZv!UKZTNgQ+R_a%HnDeV} z`Kj|vxoi#|dwZ4TuldR|R9-~hYn;*4o$S`C&z%A*$~pa9ZOBZ?0C-33Jq` zXRJNjpm<#tvCf0pKPx1QOnWdD^|fTU1)l?293nvx&TOe>1nCmx#p3MOlIOi-OwQkt z`&}(Uc9K8$J=hPg75{vj6Lj~I($8+pvFk2kVc&<<{mCNB=D4sLqobtnP!ATgIQ`!o zmiPbu<9y2f{CDp3V+J!1lN;7O*gn^cfBXeIJ|}CqXU9HVcuKCMb!Rj!i|fLp*hQxp z@@rGGXk1W9espwUlUG&|l>^)_dFn2iI^Tx1oroiw*A8TdI_xL&U0#aiI%-sLhCiF} z{W1A|$eUUJsvu2G9hfzKAyKWWY@@hmxvmTNYCQi)2_%WeQJz3_Y zhyUiJ7t(J2^HbKJxj;ArpPjt&j`Z|5XPgN|#;x*TF1k5nS!WH_II%y8dFCNPW5_wnW9~ZKtj}to?euVtC)@OY^iA*o( zzHG}S4Vo1)UHr8@o96}xF|THA`XiI)ls!Ka-V4b*mnV}^`pTSRex4i-^ESu4CF9I3m^XPu9O_M& z@7G1-+B^d`eNq7#JE{|ln(>R&=k#N)T9?TBuKLW;_&FK;+MC(s1&{}yRvE3&cNbclXvGwcue{*gX-P`~4kn#>Zr|jP%hSX$|#E6BAw*MkN_dBwU z+xZmZk`9YhcuuO_MzQa~s#JT45!>J{5~n}?nNj0&^8NEo(I&E->%Erj$^B-Pj?y@pvxv$m{9rLhBL_6mD#Cm?3DMiwU^hi zjfL&xf+wE~Pq{+Y?Cj0fT9{F@#*T~_-1+CHm3;u z$aQzUM#4S7>~+aAQs~f;9m)7Wx;Uz_lkZ-W{N){3$)6&^J;~yS_wUG?qpF+>UQRY? zD6nn63rR?pB5Qj0o~%Bm%yecxCRVc*S*hh0@~z>!czW&wQaa9to$;z7+}p}Fo8BWE z*OiJL{a+KumM@~5V|?NHT(M!UDlP2TCN{9Y739IOTJh7HO7c66&&gM8BUv>C;+0W{Nk^v4>`kBkd%n7so+t6s z+C|O?CH~`oi$QZQk~_N&iT%xQk}oqg+0XecWRPQvsPkYvsR>eJi__ak@R$zl-prfC z;m32)#SI89fD-}oAEhlq^*NYyon>2c) ziM%J7w0!u=^LNPv2mTc&NA4o)qpet~w2-WhG-2D~D#_Ss7sR?(SIEMZ_rz9HV|u;t zwivf6hLpZ65qm!5{^6tyar=gJa(j)jn7L>_`I^UhN5M&?;HEFDCOyc;{<)$l&jt6K zenI>&n&(CqabNMS>m)WdQ_T5`gf4m^dZovcYYlhA<)>|^!}!PI>+hU<{H|48yQ!jN zW%y|^s5p%@OuZvo%d*McgLR_3S74}biFnTSJQ)=IT)cWUldPX!BR<>Ac}e~F=h$@* z`5kdlT&9{pmMkn5=YLElbV#F^yocvlGB1nzul!TsjClX- z0g^huN{n&HB_3IIV#$39}g_c+(qkevc3`<>w3WmBunM>-a@+d_n^G z`u(!#5bi=g95^Lf?A$`Wm3Ck^bdHgQqhE@J?UAHJ<%+2I*PhgBl#8l2n~6p4Q*p~; zMpnj^it#m`gs(|rqDKUoG__XLW?M+onfGGbg?KV<%yqHb+j%6?k#jhUc90CKaxrs8 zf6{BpBknKXN}k5@dE#L=QWAYmL@n;0sjUpJ^k}OoWPeVD=>2pV zXs$$E-H7u>`;+6s2hn7O1FimrjWH3AsC+f^0 z=6;pp!(9UjXDo9_y%GL)Z%KcX&E_^83bO<5t zniZmJ)k&F>%#iJF)TT;X@=N4={2$x)%a(;(kpWXU&*W_i(YHG)b`JF-?y7?5r07dL zKP?ca);}O`wGW8gD@H!WzY%wLuaIpbw?#oUg$PO4#YKtJh@8WmVirh-n|~BlJ7=9Q zTlQ8oaEd0^LmNcLpq<2!Ju5Cze^abGRpTF@OYzA|amJa?C4cUBVk6s*kXL)Wn5A9- zF+NZx*5pKxG;b!BjI)%@J08OZ^h+SeV|p;1kz+{b&@!NOFPNQvs%P` z*#X((Q4!(~&0DgM#h*prt&~hW{zx=S)*y8^T1C!5A|caWh`o<{ko36E;^=Q}vWS-t z#O*0BWc|xOil>t!2do|4C`NwQBZII;>}#n+t}S^a-c^j1eR!_S&V_+r$wUc94O?UWo@P=971hE^PY#;pAxTH*r<&J(+z^o)?*S zi1W;jiN*P=Nc@nCVz$NT5;+(2?zD%*z{i^fE%c_9j#}bkR}V34u!lH(N;Jt%=jZf8 zNixrGO`_TweUj(&SzIxAfyb)pYRq8zDAM&{iHKweNm*4bJ~Ub+`>dqJR=po1TVA2d zQfi!K@snD_>K1KT_zYbJ1{!>Xdrou-A6D#sd^2go|xj@Xs*Sv*|g-zb9N_ChhBjHSeJhD*oNxPFwty1%Q@!^4lB!8 z{IcDm+#5YK+!G%>E)&oHHDDFrs);?s@jNllL-H$o1gHv7;u3!aF}zCVHK z`3bUt&kA8k7KP}|9bt#ICD>fKN9}Dk(TE|FY4a)yE0+m4=z0YB|Cfdp$LtBe8)DV) zo!E3Lnsp1a+RI5aV9IIc<~5GWJ=v9JYuKdug*=<<&hi@O;D$BvY}%7yn5XcJ z4Yt}N_P@M}Ji44lc}FZAd~GiCH!{SWd*#?PZ7`z3FVr627mQxaNA;y`pto)f{nWPz z`rg|~IOmRr<^%dDWRi_5Y|ua}P7K>VjtwdZ#5lb!IO0z_ACu0-S-)MeQ)VmS9kwhc zFB#@+Rz&A*=1_V+k)^j^WgA_2Z|~sa?7@peBKI#*Q>6*G?*1goXC3&i{0|vutnLw? zKZcG?E~Ncd^kSN`)0tTtW4ve)^Y5>wJsT=Y=@e}ZBh{&GzJ_Uo+)L`eEjkIEX1sopy zoXF>1eL6?du~ie;i&>Ltqoy|N_CsVp^2_MQZYM-&dQ9CbY~kCoJ#x-I*k*=NEA?QO z(IHoiv{FS2+evJXI9`9^( zq+PvNBG?bSDo_Xvah9Sx2+3R+18P-&S&ir(5mvqcV) zik?h&L^&SHwq|QzuEfACZzww%!#mD9K-#V@IPLx&ac$cK>Sx>o6keOb=UyqGbv=gG zz4N8N<7d%LL)YONs}Hof%S^0jy-UhR+#w$lQ<0iG_sHU>8nCLH#@vyYQ)3DDlCuRWDMWLqjD1-!mgtXhXI8|6%+YdW_cWr(fG|&1PqfIW zoc`?ImIcJ<)fkp^;jS#LY8*4OTOqp*)~s>QH(BkkVJ!2hC6U>=vHYid{>^2kzm6br z3CfHE2gn)Ser)`AW3qbB5cYIV8rP34xo;|*)Q*?2KRXYR{>_H}e6MrgJ2AzUD3X82 zg>}7_N*@0=?4M32=k_fX7RuZ*wuqY^A0tn%bz|4|Z6kBf3}Af(LZ<5wHqAVO+#Ke_ zu1m8>@B&x1H0~hby$bAnP$%+q5@Ct5PbCZ0hO?U$%gH)@2NvhPgFJaOhz&@bOQiW8 z%%OG(*}ThvT?~jI^3N0VqDkv>H}>>~6L~+4GJXz|aRvknX+1HdF3VPBc38WrLnF5}M$}u5CU1 zuNNwRF~6_P0%o&)FR7@vVSP5m|MTTzIfSkDJxq45 z>@8j#_m1!k7OS|wkj!eaPqNJFV^pDGMQy*!#FdHG)?Kvs=AhwZfk9s?ads*HFP#P=_M($G-G}~NYZmG zSw7~FEBCvwE83+*(X%J>+*3FOT1?yQWok)Aci z>}{XboY(Kb>W1zmj>AW==pIRQ3l3tv8}AU!{hisumxIXH4%^tGS<1BAcK$qkmWWbU zMP}hsKz?d0`<3DegjELnk=`j3NLq-994Jd|A@ zb%}KP=*h->Jw=MJNpzfCNrqov!p`mF_x46Vwr`LYVG9h|u=@R+uV%xRAAL(U_?fXk z?{dlWr+U2ivXYDl(P6V4Qpu8SCam$GKb3PGoE9G-x8BCHFDEs~r9vyF>a>_>s`X~u z{9R@8xq2NMkdpB{ADX;oTC6%qab5FNOrJQDm$*{~sUYScAPl7m6I%pmtWvAWLn*;}C`d-F3wpk^DqOroUb!wx5kzu9Qv8mZ`8U^Usm*9~IfAV?`upydrbW zDJRO#8tl+7M(7a}xjsz$jXE#pRq(EWKXHs_0HE4pCk^vg7gT!IgVUcB=yTx@^y`?z zHRUr<75dOSal>@9xRwZVmx55xJP4{@S}udtoX!{t*_L49l| z$ln@@U8Uo)s(^hS?nPn;cUhJ)cCvKy&%-q;_axbM&@S@7X1Tr(l|pBKCJ)D2nJZy(|C4{MsD^cK+V z6uUmXKX5M~o>Fk&=hDuYdpevt)bC{)hst4GhjlbSxe_>IuEc55cr+Y*2K~2NNsk6} zl;-~k$Kd(uXzCbC0&f=q?|H$ChgzWSy{TkX+YE0iMxwlbIxRmQvhLW!wkh0`d3+nT zy^Ai<&OVH5mSoF9#wp_R=?7S1nl@gVd;pUlB;!NnFdTV54-XIRPkr+8u)5+I?bWge zcb=(W<9}Rb6U}Y0yylwh;&3JK_g2H()J}e>DPPc&>Z)wm$TM&M-*%_<-$Qx&W?SyTr_Os(9~A z0PW(}U&}qo(?>$b&I@NTx|IBT=Go=!)Sr)>HAU7xs&4P1LfSpqIg5f$98dbLrK4$P< z;O-yUmPg&`=A@hQd^0xh+>4*}Kci(*Gn?6Q9Ud`%!K_klFs-SF=-a;DIOVp5&~Eqy z?V9|BFU^M1yCH`m^PQG7rSKlkv&dwzSuPUibimAx#!{a;C1HAry5#rFK%v65Mg3FH4vgP^6Jw-4#Z!La!*&pX<#UaX6rJvdbXyR4RwRk)1 zQ1%4&1muIa+5}LpsDbrOAT1qtjKz0|mdo1bchmP z+x$VbZtca^)tTtg-C7vfdXtX!o+bP~T0!+Tb{59ojwYNJD+Dt?&|A}0IAwJMHpU(U z?rDIS&hN#g2@))?`AT0_4S~c5@o?O2H06F-_I#cSH0`d&W%?$N*)a+Sud+nP5iZ#M z{8BtT$PG=-CNkN;Paw~8C3TAyynDPA7uAmtYILuPW@ArL+WiJ;-Z=oDCMCf~PitCq zJsa8=4uItGS2$Na73zZ$al*W3;50FZ6?OCmk1q-^;4s0vr)IFq_G+BuriT5l9Y?Fd z!(myE1#D8#B3QEa2xXHLF}rRVUa9#&rnNarI;~V_kLo13d94=iKk;Y5kIH}p`040= z{jjC+5_;%Hp-0mlbdN4$8xItSt9){4k2L!cG^=A`;9y)Qb(MnCjilOTT_rfT5$k3i zr`8v1#Ii00pjqIDdK24Fy?;KdE}t&-%1nZ(Nqgzz*0GSPXeXQ$4$0pO{IlGeEC^X6 z9 zj9Kd@jJ4g3YP)+2*HgoA(+?ZAb^m^RopKKsXdgo5OGl_(Z6O^thzQ%iJq87Zfx?vP zSMbf|nmFp%8Yu4XDqVi80S$c>rKJf^sbx|hP?;1tV5?&|oc~1G{LHUx_Nz$Z`Qjrh zon-?B+?#DX(hoYfe1o}<8pXL&B0)1@F(ci#aZEuKtZ)acE4#B5g zj$m29H@1D)59GbJtl!GcQtMJR)a=n!+U7L|E*~kyg4zbS;CU4Ou6;}Unzi^RWkU8sSAn&$3t`COZxo#J{mdxHy%9_g+=dbFlnC;j(-}@-cOki5jQh1 zWpXq9(a=HCduJ?hU#NIUH9*?*#ZTC{v=Gllj}`iPtbwfuQ(E5jE(5Eb$2AnO1uWz&Pl-Va@zHOou7v6%8UeA{AkmMkDrhmJSVVX2I!hQdaXhI%B$()YCE!HZK2x=f^5BCrcMRzN{EmrK^H} z+9kxe*^n0(i35H2LVj8ZbBR$FO6K>I^HBbMH*ZCAtgkP?d*d^3$j?SBuuX<_q3z7Z z-B#dzA1uiKHRZE4Tz2R^e+QJLxY4=z%6BYHNI2z_Aeat{mTDu0v)hi1@rk`lm?0Tv>;~D*U!Bnc< z%(HNNd?h=bNo>7ZCaR?CV~^d+pkHB!jj6%ZEICFtB*qK>+;WGnC+*QCRu3F?YiXVO zTBiL+0FQeSc-iGGyP&)endehH^H3k92f_I8RuR2#vKo9p&VtcjtD&^J7r>h$a=`c) z<#Tk5@e;*6#S1vUzb#w2po&Z#7t1(116D8fC^vjpPACf~h z#`S~!cAc?d{s5@!eumW^GlF{o#=vI|&}I}1fA(zy^DQ>;r*HvGn&=~*^|JA(bUDfV zTu-9&s4RAA@^AECq=aLq_rk^L`_L!g6!f-!hWUjZh2`ryqtUA~z_k>xeCb<-bpkFB4{p%p> zw`(Cx`W*=eeop|+=9Q3Eu}$~T`p1+Co!R=~Pe!QJ>{wlg(ya6HWRK&0GhJvNX7;2Z=i&l1y#zN;R%=u=B zG3V_);yT5^p327{sta&u&SU!9FB&5!A};Im4p*#=#bX7xQ9jdH9g>41>U{Cj^3H6| z9XDJubqo9)-W6=juhLm&kx-@I4_ggtL_z%{d$RQ;s%%SUtV&rD8#_w(PCP}kx>*u* z1Nvf*ndF#eFMPSV4lDoj7WVpi;n9rCz<&>;p4z|%cSCsBT@`G9oP`OR`r^)$;c!i9 z6J9p%4JkGoSVE8z?O_|leV#Jx_M{gMm~30pbHzdII@%33+z8}6qii^`ydBn!z6xB+ zfg14e&>8idv|*YjG;X_x@c96{xF8T$XLKj)y)NNM zpI0z#sj~DS^bn-SCE>0Cy|8So8rmI5VL?AvA@>ggxiS!Uw=!rEHCfcN(YT;#*1tX$ z`Tw{Aa?pOfg7`wy5O=%mrpK&v*sr9QydSd`gZzg{WhaPqYQPg*G2km6*7(cWlOJj1 ziAflD&>3>NPsfG%W2xWeR{CFg3JFLCxTxLAoO|}5QSB1)*)r%)n1i{(eR%H~2=7Aj zVV$oFnfJ^HN@u!Da_(sFWOwQ8_-jmcUjy6awwUewkVQv3sX$w6I9+ti0hA`oz&s<3 zURx{BQ{IfBB2>{5}n=-+GK#RR3mQZV#ZY2D2cjYz+RHcOE!b z9?g9oFz!Rd73RIAKC$Y!snJxz@d3DX|06miC=&mS&==b7uVhJ^?BQ=xG>mlWDm|)< z1%)_OiR-{vplT*$7@S87+<&mtd?`z;0Yr06G8S8@AQcUewEq43Wav| zs4^Fpnu!qQ_X-|#uY$nP5rTTG2Aq37R5;M?g@N8HA%3T^6cP~5@(o5w-1|nCD0P$$ z51vUMT8FS~K5_9oevGy|+em*stLc8VDcGQY0EDyu!LauT55m;pZ2m3!agi!caMhx^ z2UcP1xChYCuZi{DWG7smVFrh9?}AlxE5PNM7n@RZ3Us$+^Q%#iFQ-ZjT)+!67b(r;$0qa^Si zGp6l2148qn;P2@~Ad{#GC(oUS=K6)u zf%O!dRQ&0+gd)sPv84PA!uEB&CLRpy1{;4*rvt((vD2w(7=N-IjpBMrJr#yg_9h)? zLJ({zYl5uEC1P7>3dDarS|VGcER`N82ET?L(){o7U|*V0Vs|?OdoBJ1c@}08&neTk zLrEBLhM%)f^uZ7X0|{l_B>zqeas8I5lFi>daGT>VeGh*HoLwtD(Or*i-d4!D!P0@& z_q2IgB67bOsIBWJJQ%T)JsQ7IFr4yQ9G>uu=Sus-r=y?f?mlm6Y4;3B+dUGS6MjI- z;3~XepAK6dwqxU$-Z1-dM`=;60YJ|XTxJkPEjKaox^^TTcPtYXx+KBEo6eHu$#mF2 zI!cS3cQEWf<=>h3zlpQ4#lVye`mb2FH)aQY+*w(u_bcURXhos%le;un@icokuaneU z(*busE=0~7166xR>4)b9C|~3yE$kYI9YapwcEcjQ z^M^rwD_|0x)K3Lc!3TVE&*IO;6OLZL6o0Ywa)new0=+r)WREI6F|-b@?rg*<~*D zt$7sm@vmf5o0D^r$@Z#yQWYF5D%xLpB+1IOGg|_*rbcWqH zA;EYjn3)s2(==< z?&gBk^gUqba1{d2*TL(=PN1f!D>w;}MLC-5`CZNpsIvm3ybFLCAI`uV_fdGs{tA_| z*%ERbq|)wXcqFHXq}x6=(qA7 zWIMM&h>5kJ>e(0l11AGtuQ2ONA;!4h7ysUj!|FOGN}{*Z1zW37-n$kS)Qks9`bolB zU8<9whJS*WLu}Sph%xLT)@(ZpmVt=`Mw&^3Cl!HzUngny;S8|ru?U^NoWaX6kMNm$ z7imJ;2fP~Bflkd2LF@Njgi_7NOn*lcblTX?_FPq_M(#oQ=Nx6Nf&Fk!^HF}*bD-UP zodoU~ft$sv@|Mh{EJ3aB^k`Z87 zyPw*hDhB1uS}>jP8Q$l|K?+=hWlo3im}-Rldy#T)Y=JXQI?|2Zox%OLlY9@y@y_qz zW2Cmkbvl}LRzRI_CCN6tn@}I9E48KX5#)0blg;ylTor2^Rvik8LMc9(vxtQos7OXD z?P)~86fBtG4m|HktrM)E;YSPH2&xtL_R$rN9#X~|Z?AxV;C_~$CcyV?bJ-Z}V78>G zJ9b{^&UpsWxclcBa`(63Q8WK8=tz3Pt8PZZ$4nO?`ExVKGYX}9m%^fe7Z}rJJq?jP z#nEkvto1qP-QU@PE1$h*v%MQ|Q9uWY{&V_Z0Yp;8OBj zH^&C>&Nv^bR`wp^FaVyJltb5&t%9dcC)!)E7y2a`VE^5dnY`~$cH(i#%6@&}!+=9z zHRuWDXJvkGjKS=OVbVx_4=JKLmfvNUNU7sz;J)uRQvUD`Lc=ISbTDlLIqSr~_iTus znuy~pztDHR)THm+6WzNe4DU{j$Ga*E;Pep-)Y!TLnl@$w$zFzQZZyE{r_b^4^cWfz zv5VE`d_lc6&tUnCE4Xca9c1iO5@UbqiWfFcg+RT__)cRje2RIFcHIi#yYv}Xd$q&u zG)pPSEs(!|CX#7h5mOlR7&{on)5Os`QE%XTsCcy=$MwGsvws|9RbRfdy5J&M*;a#1 zYQqII(ZF$TUc&08wNTxj48L~n0E6&eIO_6gYM&p5BVtBSmu2G_XF7w1zB5#|9R{X+ z2Hv`Cg+ViwgD3j3eShLu<$BzpsD%@cuaU-Q z?S)A5M5)Gi7|a{1Be`_G!7iOrgs{v8Y|-p2oZVH%hAb6f*8~PkRa9vC(Ea-gr4SFYU^zoSkqa z9)nqBn<+TD3iG$mfw49{giE%cMWX~Cpn=9xzc8FGH6kE z$^6h$rgyTJBpDh?E^R83)87{;ISt02`+idSbJjzJ&r^0>gJSP88RtiVsfDuSeJcj7 zdUTYgbvBfm8dJ#1wmy>c*MpFBv%hrp_DYtw%2AS^Q(auXqR}!X$;EGs9f#~^Nx;`oOYDL#ZP^uk1_k8M}f5z+HjEHBiy9vkEftqYxolU z7{84@K=;J%Mf8{oFISI&kSog(zH}4JM=45NYZP*??-q~#$Is}?tOfm?JM>Y4t6*4k z4st?oL0g!$@O0Ho*x3IAgp3g5pO-X`bmz`e=N2X`k*0+_<9-9T%L0*q8xmv2VEGQWr>zEKA@+F5f>jPvdl_ zjzW*d8|ZI%5S|$uLvD&ICZ%=|rf+S>fPonh^+#ReU5+duxgGcQ*um#)+R}vW?c%xx zjW|{5GN|PpU;4${NjxwQNKC~WrN`r?aRxPJZ+x-RT4n2!v_ z#2@cr>#PmHA$Wwp7ed+!YcyG}Bg`6)bny{oVNZTKm>KH`b0>?S9_tOW^@_mF>kba= zdj@_h@E+A{UC7rjqc!QpkJ?HP?KLExqs3t# zx=Y8dX@Sz;R9tu14u-_$aW+pi*S9o;M+%M*gXFbryZ7DrB)&+QG2aW#z;B$(B zl6);~KVl|%cN>Eq_bO;7&q#E#+70a)lQ4X^Ir6SzP;%di%c~a($8Hp{y`9qF^OxP& z;FyQb4|+>Fb&kmQY3Ytmsr(sczfBB<3@HM(4Sov;OU?lAh@*Q)-34X8IJ|JW8(f)Z zfk(0|v8ba46kpP3>dzfe)Xo8$EIX+rvlKGd+`-c(Zg5i56Kb>G)A7Bx%{eG zL+<_Yr4}{3V@0VAnpY-)N1+7zU3$Y&{Q~M1Yz3YtLZL*j3f`+-fQxqu;ZThN$eDDn zR*oj;%*LQb=y1Mg&ZnEzdcvsK{a||`8{-z-gz1NW;>PxCFma_3b}+Vs2j_RumZ9a? zb^b|q`0NinSj4dIj=A_C)r9AFyMe~W;(wges)$M)S@N2WlMRqIb$bo>R@Gwk-ZNmf zFA4&!_agGllVEfC1C#4U9@8_h>~Ciwz5il7J=T%A*)>6A zUK9FiP8Xa*Bl-NvK4BGNx)VO9S{I(rz z7rcNC$!oCR0bxKl{cYHF>mA&?FcJDsx`_W(CxUx?5!ZG2v!Y@xrJb59Op|kYLfEnS8aC?vl?nC>aC&$))sMS~ z&jLrle|9G5_xW1M$gPiHX!=GL=08$!+mi~GRwjb~Lucw+SO$&5y`^yN8fY41DB0aB zhe02;gnjylpd@Uf(5uc87M#04HTMo=-0O`_ucPpgx;9+18I6%^=D@4r6fQL;!on0K z!E$l~oNrPQ%KDeXn6+I+hkzuUGNS>HlxZ5zY^HhXZx|zV@^S&~FFfWBN|2)Ww8Zcho`sic45pTa1N|oA~~2B+0Jb zV4}d_c>XmE58Vu;uip=+^7Gi5u3PDXPd`}HVsGT$I_#~kCpGA% zNOO0tkmluFkS=Z7AQ_JQhJCy9^GYvMA#%+{usGdWh+O#+M4j=p>b|}FZh%*{?x5b( z3Oq}7p~u)Y#f7~!gu<^N4Bgm5E`8W2cu%jxk>3mid4JceXZf(nv7Hk-DxmL_Gc38M zJ~)0JD4n>dkGH%6(6`_MT{75S(7v(@QgcKwZ3sfUyL<5M5qHVVcOO&B$wFssMWNQ@ z1Uy|^236mxV3B_s`+ZKMcyw!-(o$&@;OL#)RyItJSG6w~Gg)`_zg1r`zBz6D6+2beHxd zZ{tkrSyJ@WPxOsj3(xAmgD>{u;Gn*}aPU9|M0~hKPyd*WyfYkamR`iCry5c2yZ^EJ zJKL5#Sjr7P7a0{bz>(YOz}%fE`2vQTht7G1fJ#_z^Q5voU|^2y+;p%ztaw8 z)81dm9{mJ^Tn7qYEHlK|EJGox^F!$4x=t9BY{vJZLqg2NA>3Q9D)4R)lED&C`=ufr zzf#DSzWK#GzHEhpPivS@WgX*uOK8*0#(uALgrZ3lbiz1mqop3?_k>yID_F&qDsAym0TBcPt?0Oipzd; z9e*o58u%JIhRwwQ>sK(h)Lhmtfx(O5ztm>TZ5q0x3YB0tzXSG^zL}Xz<$lAlX|j!U zYmx)xy}W^P{av2dzwBR(6^hQn++LDt8!Zgsx#Kt4)i9?UqcbNhl2$#IL~&NEG~($7yl%IcjR?%ci52F8 z*4j)A7gxb98DC%D%GgK$?Na`TQGoTU`Mu~A@HG%q_WY)LHyBk^_r|&z2nSon;b`Zn zFg#%f(>q~=w```Mu2wpepKJ1#`|#O9XDRQ+VVa^|in}}KIpg+sO0g3Iy?Z1O_^2Rulnh2G1h53AgSPOu!;H(3f(K4=R3?2h}qOX=T91!%vM zpEH>j)xOwGD$N-}&d;gD>E4Jlw3MXPI$rT^ZIq+JP3TT_9pz8``Ot zL(RxOcwxh5`doJl^gd&WV@h1%6zH&(u2)!KP92#M{F==r&v4ocOI)DqDuoOS+b3A4uZvNaETu{REyc2+iPFlxk(l1Ei=>&-6L^n} z?Bna8?pkHbEh55Ok6i?lcJ7PFOZ zqt^RiX=!~8{hRD2^~wl_r&R#j?PEcD=m2qx{XscaQA+q-R>HLh>3Yv4IPGE_Evw96 zaSK!7$WPwgx9= z)fN&%t#TCg#+1yg{z_K*A1CjfmWU7g2_&aWLO(TMbY{dA@>OLLsyh`5<3(pg=kd5?i6(pdDy>6sm)MZCL89i6H;8h#Nt4Go zzNK56f354p`=+-<4=M&wsP+*%4i;TBigV18wV|WM)P(_NcH|_^fjoIJAYY&zdwIEc|WWJ z4g2Fq+P0*TYF%q6zY!@Wg{mUurd`B0TtLe_f_a%Qr_t{d&9Tkn=-!qr#&=0ZKomKy zd4P;JI*tldjv$rJ;VjKq@oj!SVn+CK_;9NIx&@)Z2g8p-r{~JS{2l!~Vi|Q3E%>6&FN;bhleZGlIE9}7bJtkuA-Cl^+_Ml633@RF|iJCu|5~sh1V63jb zs2w|=$Z86nevL%tk!>}cEaF;sp8I7j9#&W2r)6D4&AJwp=@7x&tt=zU2Nxh(Hy{s} zjV1M`PGk4H7MKxLgw7;pg4Ik*zT9y;Dfr~f>&!PIVl0c!-ha(SO}7$#M=c`s>?Ib} zBXN1~H4?!&h;%EwPg@f+VV#Dh9$mjYglBB;6QU52YdMhVhd{jhobXJOmFN?^BHYM?d(QsIQ z*B9M~i%8yY8w&8z<%jp-VXH;5Apbjyql|dc#qL8nn-mH4qfr)YAkk~R$;s__M`~>` zDxVxdUih9wIw^WcyL2VUxI)v8zlT}lvIw(WKu(gEaBPz{@4xFhyKK%*w8=RID>_f5 znfG*bGFgtVzv049m}A2S-g%3vGH#ctpIdVXDhk=RfP+> zA#k647ooV@YJAbB4(@&9LoO-GPF%a-C5bVcAy&IgW4af`lP!vN{Do(a(WVb)k;3QW zX!Mui?AoRpaC*@fxzs))ujkyNeJvfa=drFR{c{4g#|{;h54rN+Z(GBm&imCS0?KM<A6M2*0aI$tIW$l^sbv3`B zp?3=xajlyClAkB8x4%RBf0!j6>Qz8YlD$-{V%5R?IAZ%;ixFSTkaC-9&5v&w$0`)U#~+JBh$&XV}3}FDIGzF z`!dbyGS0py7lj|ILe+k&5Yu`Rot)i5R+?|1Ia5P%Z=SiRGs+WW{?|+`DQC=h^7I@- z1|4wWWuN=c#s==}rc`oednu6%-$J-BZ*fxkPI`WwE#6vv2CvfVBEg|EkmctBCjatw z2w9s5iO=*z>lXzyTiREwmaAh{mfa<{!=Itho2S@yXLM-xN|{eTxq(E~h47(qx~x&_ zeI&~)S^wP&?kAri&h!fG8lQUI<`42l!xQDjkYd6Pfocuk{cQyFrB{?dntaE)hW@>oN~m$Giv=I5h&%IFn6|WuJEx+AWUSPGlMWG@pCq?#G=o-aGTfTv$7hT_#ZC7$=Ql3w z!PU<{!Ow0R5W>PI+A`Ux>-^=X9SFun|j(NvGLOWHe1n zH2t!l>6S~P>ru08=AGNXRuJmT;>t5kh?apqt6Z|43{Ce#&uu=DYu7W-kdLjT@b4*D z=|7672RMpf-=E-(QIz)3!Z%@ocluJ!yeQVc~6Uc^%gZU{2FOj5U9RKLi8+7`x9zViw z79A#$mWLz8f9ZT9 z%3q4uz!f&9q1m&g*nMgrewNX7}G?tL@ zJ*CW*XN80t#?F?$Ar)KJb01%sh^JOBL2Obk zR?3Q_RGI;N0MiS{$NBT$#(#y_n|&xxb37Ae6GbNVlG3xByqN!cJdO|d7RU8mr%o(W z(YLvglB#06KL{aL^+WktvCV|$j(}gYht6>rqn-MC zv}WzeUm1NH`Ih#h&c7s-alu%u9_>Sn>|dg*I@d|~DQAA@djm3k^jUN;L>pBYVJxE5-X5o1{lZWmnDxh6En$R-X3{wmuc*Cd?MDHoS!*o5Xlee2` z^6(Q6A1fx|D+Qw0Tafhw_|CXg;uosG_fu}ftK*eLTJwX9*JH((50RLwR7X5bmxxIaz&-hw0J2`2jZPM+4C)}gZyJf8=ilbiS9~FhKPQ9w z4INIFi|FUF7li~nkh6JvI9ku99#3@={pTsGkd(pczfc!FB#RI|`;*0$j%;W71CG8k z$?X?PByr(j@^}3Zv3I*2OfO#|(%c0&Zv0GQqW3_^hD&%>NPBmY&wAM?$Z#@9N57ve8-C9N(YgecM$4o7H#i%km@TD z0X@7@Qf5*P1V?)Y{TL z(MFMSAkfJnJ%r|*$=lV*$m;ewI$P<;>vwyK_be3AJj9VmkrQb?oY>ye6<5tIB6Oyh zCplAb@2pzh+CLxBJPcZrHi{cFH%R)Uq^ z(tmABrY~5GijTbD)~2ZN)pW*IHiP4HBob)PmJ5kkM#61R!Oh@x=;-|{ocduTe`s+Y zUhFoOPYzTfgNrTs_d_po(H#d-wxKrP^1_+y@$}^XwW*5sv1a^HzxU*vk`7;yVn|xg zKSSN!zc`PHXE|Bsj-KTp{zWHJaP>wK=QBtkO8oGLc1V_;{JKY;_^g=7b%h)yGQt+uMt>&V^F@C0kQ0piJnjHBC74adi4- zZpmb2;=g`4GqHUNn*79-D{hrX-g}MF1m7X-FZnu7KK2}^N;A9%u5I9G9YZ`@y$fdf z^b;FRtVv=_5YZ_vgimiP;E|>>t&67kiBPvJw{iQQN<%hfY)UQ7$o2RO^i9?+R zi2)nEK692&61a5K1=p-XA*?1A6lx41DmxmzRhWo&U#mj0T>Qm{ZQNx=4L-nQHk5ri zj7)}?(Syi4+fdAkA4Q*MA2DjO7fH`*q%$O`WNX-Qx^5|<2Yueqy;=v_liZ)QJ`Y0E z&z?g=)b9MB5$X4-lRToC?h${>xdz3#TpZ05vlYjo-x@kQd~_F`1vC;LMcpF14<7)U z%O!u>`|$SyvPtYo6@H85XHtDhgSa&OB~71M+7nG8nxC@hJkBiH{uG`5D$gaxniG{d zQ&2)kHO;uty^YoZ$eeiqJ--%>?xfkUYFBrlI+c5r(S3z`II)p)zo$gjMrWf(v$V+T zt=GAw7d=rA&69+kzJX|7hm4L}LbFP>*d}@}>wJD6TDmQrbDz3^qr7r7_<0sty85By z!nAxGG|`^u7`LEPz58IcZaZ=f+=lr0L~`%YIaKIdK|Wq8MTy$qaeCb^?rE+8oI8c+ z_b4FE?FWhRl6Ye2HiYDNo1nN;osblD9;v4l5=j*O{iO;botH;}d@I@3I+45bE0|dK zE8`kY6>_qD&(^JRX#A)x$T-{;?a=k*t|~sou`?Q={P9;DtfB}9uDT%g#k! z^@}7n1*4*THL-lq7;fOtKhWFKk8K|^6CG2y!rCMnBPXMwta#uo9-p+2k@+j1FK}np zkNSyUh4*G}wCdB$xP-}>qXr$#(-`CaiV$8gi;;O5W;$(S=o~N1iJ2sQw(}#l;jS?| z^)z5h3Ckp?%YzZhVVcsu2zVr;$V+U|6+qES9bN^zxYpoUv|*vPTa4w4;w`JuJ+mPtaNr4w*TqD z-d&*r>k}N=!bu7c?mmgt3iym)jU2|lTKEYcof*$wyY~t^hD>1lT>FdzlP9yYtAFFp zajtB`#BSVm$%)mw(2l1B&Srn;Q!lJt92+!6#Q!pCnTYHze357~D?3yn7kRJ_YdUfA z31ybnS>fS$dA6l@2i9~PN&ONpaNd!9OoYEa80$GQxhr)c6GtEHTjaA+7+|OZ-zWPEVKYa;Ym0asJTD0n z&%9-#UUgxGU%MIgt%~6B)$V_3rBMhoMj18R@OX$9edY+fDHe!uw?`r{ek`- z2IHki2YvZAF7Y$)R`DI)-SylLvpNAifOkye&b>XGP+%pf{{@`aXO_>}U z1vs<&F%x`A5pFICU=E#Cgdda2C0QdCq0%9isn=G7f;m|dI!_N>;Ol6tLdm4ZYA|JygXGc? zz}r7H2)BdNa4+jXc1K7Iq$N*e^S<9)Ayv!&PP$@Ps=oDcqw1pPqy< zE8beehxucK9ar?=u#*}~UDkNU_Jz#cKXTBx=6%4^+cP9E|Kn}wTy6ybwc zrnI-qV{B|5$g0@>!{HS+OqrT7%x~9bE??`xUwu~8JP6Q$+5KxIcT>M%^X4o`Wy?DZ zY>xlbT2**>cWbR{u^gD5oGmO^uMZtX5z<`6S2%LnCBfhQ0^ajTlpa{ufE)e$280J{ zf+M#;m?Ek}jol6@H?s$u5)b^8@>pm+n)TUz9M_<^?A%AUu!`DtOx=+9O8*MxbI>RJ zofXp5)-Vr8Sb)fwoXt9w|IItp?6R=LyT_n!wzL6NDGH)M3E4VBz~D9k45#FC2a; z4<@;(!rsH0@M*#%VdEiPn7?_c;8v;y@iBXam04==R%L*&VSpak&fO+lQ`Coc%`{<= zl@>gmy+pY8*bplJP7p5bR)>?uUczQuOK3HguNgAqA-;7|0Xq#lBYb%ERB)VP3InKDN*Obwn+ zmBZ_zb-^ywP&(I93xXZ7P(QlR+sq6shYuE(E_8tfUX{|TY5MTKSG>e7-2w`O?g-R}f#oAq zuvJMeo_7AJu-8`~c5A#8DopgD@}ZoN=x7fYx+;Y{Qxj;v|3w&Yq6{5plZ16nwlFp8 zkRapMe7ibcka4f}xva*ueh#p}?7I-I_Z2HNE@w>r)S&9RlW=zy4^f?YxUcdee1Un7 zy-L+F8y3J&whrLlGw{R?Wf*+IO|USb-s$WbVeWYwXuLK`C|TkHM%5X@>iFKUC3uRk zP0ktUZ!GBhJA+%i38roUtd-Ftw3IYp%2mS7UcF#nS`OYh@D?6C-Wd=4jd2}6UsyQK z8DdAs2_BQ&pz_-xp|r&o7VZ2el)p6v(-jj0ndjJfOO)U^tT!0H7$ zo?vA31CvLNjCAIA+}>{)UKw*AAKT=PHMeHtIW-YDeCaZb6)d6KD;mo<=z0GFg_(|i zFz!WxKRDdPxvwngn#R)Uw7RyoZLmd4TI&d)>L1pTBIPIJjxwn zxi!+c-rmsm<(*()>;~t=9YRb=Z^*DOmFQn(pknXdTA6>FLARxkR16_?rk+45sn`1H zONoq&w)u!Nc6vpx|63bOo$Ns7JgK4kQkc8NL$dHS12fKI;q|#bFzXvDW1mCr^#o~b zP%sz_-Yn4BCzw{ULZIs&w4_usGQV*ZZkMj7>s-8HtFR-fKU~Z2mNX{$f%{Kg!QwXu zQ3DMHlLY~=;P*6P;bb22aIPTpX0N=gC6)Q51D0qCG7h2M1r4d}I(%!|Aw)MKSZFd1 z2Z#8=iI8-G`d^^$y=?*0I1V&#I17-?Q2w5R(4RvvEvQ9WQ7VBqI}C++11XQvO;I=~ zNI)avDSwX1#I0#Di77H{t0VZL3(myjjpnP=G89GM@ z*Ot7OKD*7s^XUmv>e++=w_ZtKWlLbm&M@JsCkKPt3TuN65TuLag}5^kFkQV%C`|Q& z0Kb9KwkAL5^IS!^J=OnR-Pd-cefIFnC`t}DM*KLwdZ(X6f%7$?m$$^FBkmQU3 z1E24n7bu+%$gWDtv6jH9k#i(xr2`-~-$jy=&qMNs6OxNV2g4$^Tq^Y+0G;xiC1%c) z=ULb(>AR7K-nX|&!oCDRW3INex`Kf}dg+qO>_CVdsKBTkb@{`(tj&s|BRW@d2CmxE+I1ITT!Tz~_xCYHKsB$T|A~T*;qwf+bD^8t182t*hmRvm?^j{(J!eU? z!BpyO8%MTSJ%yOyH^RK>#aJTIW_Qee!_n*z@$}BckK8wtjKi5=e|{X9emRmm@zaWC zW8QJ)ijO&+&UKW}M7i2Z`vk$Lktmzxa$fTelH-yi?C#Z;Y^cgW;$HuldzPIg>xFQX zjY0A+gdnXqN@U=(LbNG+5->-nVv{La@U!C%xzv6aigsrb&7;eN`-QC>xlvS$d#p z|2%-SWOMKS8IvGeQ*P%kH}2iVQ=GKB90l4rV;O%(e8dv_6klS*PbHKcK@xv{fakr; z$>VqxTzGLE8GL>RSG4XRQT@1)+qU$uboT8>?BVIBkj9Vi-0ABhWPAi<@+X319ea#Q z$tJRF{}42;r4(+un81A_d9w1ZP!qCdA(71!fJzU*fElRyOn`9)634|ib z8#3Mtxmj;u_NiiSclc)TsLMqMtdc=>wE=o)y^=kK*ARs{hNw=nf?PE)L3av@Le$HQZS-?J~Ys$7n#6Rp}#5N7|cIF{iUh&ETWhK$iV*pgm@5@@8)lIsGmRO{m#Rl)ZPN zpv2LVIcAM6i9W!pV)@2bwy^K_g@gmpuCmE6dyab*|;s z-yDi$>-5i^#@z8e`Xr!Jf#^;B0}1z8&Pq+4PzN!}xEMj^In*FJze`-V*>RZ>ZcxAR zvOpP**ybJe->k`k^kE@5zG4zg8Itiow{!6970i_Z_Ha37t&q`t0_)XXXY2H@VEgua z($YDO;AS|Cb!{+*%9-0HrEjpv_fHf@)gE}@}{Gg&1F~IF@|Xq zN3cbsBOa7s&CZWFe&JMnTYV?iT=J7KKmGvUc)g8Heqjoh zV|KFJR=C00CI$A> zVck}J)?k7;g&XOwS9$qu`X;)usJ9uh|EM26YyN|k-47G zgH^p%*nSa|3p&|={S~7R)Um_L_D9PU)Y+TKa**@Ence(G4Q?z}WdrVN!`3HL*@R~{ zzz^8LSeH1%@~eGW-_xJ5-|aYdd6@-l{4^UC4T7?S;xQcE1>}M)|zQ>9uMlp-blJv*j&K^ zY$M$9=eJ7mGg6V=KKLY_9a1QH`ppiCtEUM=9sc08mMdA68TWAGk1A$onkDec=a_Qi z&-jeZ3+9oxI^_3nVHhg~$WDCB=)O~dx1~pzYwLdEejCe~n9;gWK71xSWX)S#-)}nm z{ZJ>){A!40dv&*QwU_&YsFcr0N? zS?Ge2!#3t{^e=1?u$E1jJqk|Md}3(61{#MwlAbFwgGB)`(!Fzyp>ZE!Y8G^2lakfU zqjWR4JfKx7O;dqP-$=$L4CDNSP}c7$y}o_gSne@p&K+CC_N{w~M@RN%^b!oA{aiMa z@b4F1rQ^(41<3*RGBayd{>I9c5rxyYDf`4Q2VjrPXuK+Kfd7Jq$!4M?jjglyn zkGOc4JF9e85k$6C+UuekY<`)`$Uj$rA%i|k&d&RQReW+8+50uUt5C3;`vzD2c^{y+ zwi`d69>~b}zDXdOk zUhWVm2N#MXe+eN6RY5iQn>0PX1!t`fk?d>uf~U85N@9Qh#8-j?0%BbL;U_!p3(g0> z<8PzPg(;rRxO^;@-kqlcobMpqeDXNnyWbW+c=`v+S>KWzuTqxz)dbBm`hX{o6ymJE zVXd6C!n%{H;QwW_kRRBIx0fE4TFJ}9>s(u*V9yWyb3n87Y=8o+7*H-a#lFTzzNle3 ztB9*>!-P{&@9A}3D9jtD4BxU1h0^9vc-j0siIMy_?Ei1Ha4J;+hAYc4s+a-@@mE+$=y_mB543zB~jEfUrW4B|;!qd;naO_H* zuzp?xj(bunIUauw|Ks!}ck(S@R-(4hz_nrS^dE`s16w%ZHDAcQYXa+M&y*;&xSS}NaRe@WI1l*|)Um_iZ zqO&S6N`Aah;-Uhn2e%7zBqeONGSgig4)H8md>(fwQmm@X3ifFiCP+ zn#Gi1pNAU4AZtxX8m59bj(LU$PfcRyT~ovIV>H>#30H7aV7E|^AP*_iXA9>Bo5GE; z2L%nSzc{x~gH)rk8&5qlMmqnXI+XNHsf~=M`_)|!h3ngzaqYoX%=r|1N=-%*4R2G( zRPTl9-VT?02IwrHjHT%g&iZnCfC}r_-2h_a`#$_w}AIaRvq4hnM~*AbhQv$ACu-LeNY1*xWE(pJ(vtK!(+yta zU7R}Y(L~wzBddE4r}eN*;@KpT@#~0o)d#$PXfRy+_ZqtU4~DxB z2f^^DndIg@9W{t#?<1Nvs)#_;7oEcoyiCf%&t3!$6 znx9}`^p9w6R3X&yL#j~&6R~qS7p*-5v~E?SQ6E>58#>--hVERVwxVP z=WZ9U=(*AX8UG7?`7)dwySNn>AGalKMXgY4vy-%L{)6qRmXaNhvpBy~FLB#Cbv#Ab zj7@b0ljPNoWX(Wx@^Z@_h|b?a``rFWZs#uU^olr?l+X=c4L7;UxesNo4QPyg!|mFc zLc)g1A*?GW(z6-Fx#UxJPakf`Y8F|WZ%0KgchPOXStv<|L2}mhoYCrX@OE}VYkoxF zu^RDc;dlc??;Gycw3pn*3zIn+hx%l)Es9WaAa0XdIEMvkP|rVvoP91Fbyb1Y2P^Qc z=w^$vM-%DgZhX5^mwfh3C;Nx*;}ZYml4(=YxY&+Xpk@%Ld-IwdY_tpHuFgPbALvp> zGC{6)ospiM2a-9SQX{iS_@oMMT84u7@y#mqusMaqX7@uev7Vgn?6F)^Ne|9ga+&L) z<7cnKT#<~yHZbiW+pj*F`#yTA>}-V&95CamLkF_CL7F%-|0Xoo4r1?)b}vi6=z5GbQTj@&u8VhBKUo+MAEPJD7U(L8fpI?gjl~= z!u?1=^qN556OF87&n?)n`~%Iaq>{KZ=3>F#P!c#Lk+kGf7x<2D_5;d+u&#U@GPo0- z{~JoCX+03=Zv#f}4skisHC)rCPU%;tvz$_s88=q9UQkGwjSkzY5c#qZ=;_EfSm-~9 zLlFhkQ`E{m*`tkJZ*4){FUF9pDK02sUoiO)*cbYTnJ{PO_a!oZv+3RiWX-B1a{g)r zS@N247oRv&`2P}+&2ePK-+?EJ+WeCAc=B5B3-=(B_V6cZl2V5dHepV07!Xs7UkzP= zodO?IpO_}TkYf+MyRGo&7zs@NmVzq-F5x}R7TChu7w8;_AnRkgckIQ@1KaV5HbYqL zUJ!h$w?KpbT%7Qdra{04}C$_uaTXP4Ea*Q=Bz)s4GxgX_3*&& z^TOE|IOx&ef{#421ikohf#zFbXZsK=m*E26J01(T!T`SXaurOrP`|}d1%_Sf2y6Aq z1^)=@RT<=t|JFC*tv%N4(D?~CI&HfktE08`?mja?I}m1RTxK#VSa44i>bpWeWt#`v7VD1*!I*u`r@*F5am(2c|sr7N%Pa zfPFI)h5Yd0FsE>$!2R@ve(k}+*051v<(!OXcm+Tkdp2C#)Yl*>_uzyF3hjZmwjW9i|z5#R4|#Z3YC}9ugwtX2Ftz^ni)ubYS+-*@B+q zVwkt0L1J~=4l;T&q&qyPz}6*Mct=w>%yWDpHQVh5bt^ih;nN4g;YZ%W?}KB($GaT! zIpLt(u?;V|Y6;)kVuWK+Lt)!ey8pT%0ovbY@-{Aqut7G0iO9p6=c}dkj0DMDOBk64 zrtn>*AU`@74u8KU^jS0wqS7`<9}Mpg<&TQ}8v=*JQ(^9WXRtkWijldPhJ{N6^EzK>)q5r^(C!URT2h8O;oxXjt0Y%-KFo&e{-HLE z%=5&sj0cpJEtMGRAXqE<_ctBRg4THp#@93elxA*aT6zK?A>M@PmF@wW8jei<^I*_8 zw_cbF?$Dst!`!YM1og&B!F_`dJ`|MjB2OYBHhE(xU-!n}=Lc<=0Stn7Esljt&o z4h#d+NpZsY@L|w5da zd!RkgYyg{B%0TZMn#}24gCKHVu5@<~?N4;Slk|D)4W=(wFh?(r0S7G?tQzkEy7~r8 z!6R#MnsAUQR~iftyW*JJr2$}Yu0!fxH56VBW*Cd-eZjI)E+8v61dKPxN%RJdm$4@C zqoK|~&o)fi09!ckc8mGCCK$*xOJ@JU064r+OG2}wa6#RK?fuXUY6mn(eBOG%gU~C| zf_sBt-_tOrW%K};IOQXg=P{TlCeOm@N!tY3=ic8L z%s4G{gEt{&SoXXc>y4`&>8 zZ6Md>;s4&UrmN?ed5T^TR6&^Lj=n%$=l}12_l&D!s1pyg_qQ_MYJFkQjMI#4y-Ufi zV(6?B4BC5wkv$KaJ2o*gKh^Wi6Pd;PT%nKpUM4)=38>qbNh)-J75c_3-9tdxQ3`0rpoN!``X~Z z2#oN>5ath3WQ^j>VPxDOM%9S=gdb@$vcA%rV#GLEIKr(>518s#mVhN?%oElDVlDMp z+4bqA*C_ZMyM~+QSTm8+Fdpp^&nDORh2eU~82b=g$XWiJp?hlR$hT!=9vqt;I;?WH z1*jZ&%gEkCY^X%~y4V&D7i?y#X4t}!8ekS&wT4JP753&TW5^xW!|X}5huYA3X5MOF z$b7nuk$Knx6_+vJMH4tW{TlOFP9Mqwn;FG62UwT8gW0O94@SAg0qvj-n{PjtQa2&c zIca7fF$Z%GU?|H3wk#l$@6XMkxm1H)r{n-uYkx9V#z>&o;8JOzrvc1&?8VM3wuBQe zs-*3~e(NOSf+KFc+-M;7q^Yk`iZ2=p3xg4tcE%=eq@=t%v%a1_TJ!Vah=3 zLM-K#fo0Krfx6T{Z~9^8#u_~c+N8#crB+Zfql{T_QxBF@Rx>wG89Ec8WQVR#bz>SC2E2jWp+>m%+SU zrv?6F&oGk>HQ`X;QpWYU0jxhXnfdxi8EXEPGx`QPGXD@0`A`#z4Yb+4v0w4qp(oij zxB9?`Nu@#>?dz`R%vo^yhY#*6V#KE$^#o+qI@3^5oM8#$bhQ^Wqa#d>=m1}g7BLUq zG(r3q!^|wT=iDRL%*!c<|GgE14bQXe)kBbc~m z4REQ7W>9Y}uzIVId!^Shpv`%|+jJqV`pc>5yJd(s2=>m?DFwebJAl&A#MMqCe_j!H+2}LsxOiGx#zsMW>(v@Vab~S>9O~k zu=heg$-|1T)LZQ+S=q-}_V=V~QuOG4p|dvqNH=CTPOmMpQvC0QD-2u(XrNRIg_f!p+YN$3s@$a9I77#{D&i_&*Um;z;(ce-BE_@oOPtx->O|<~sErNy{$&#?}j5 z8R|&JCz`%V`oGbDBKROAKJx;fty=;XjrfjfEmvrK{}Z>q87{cTyv2_WO_4s)>Bb9_ zyri=F<*FL%+Ru0B=Pr9Fl{+B^xmSaP1S>`89QRS`z2pbh%bzd2b^L_e6H+7rt@6-o zw}!-QEUoK43Kudu9^$~DjsL%2d{6J+IjP`a za)NvRc_&!B+|1223WckU1rXg3O*T1QfW7NQ;vIFD={o)mOrAPI#d$v@{X3oPKlvIT zoHLzh6xI^*WF@YtP9}8!%&l1bLE^vUt8B&`2L8E1j^7$WQW}4d*%#GeG(G2rU;oGr zH0$JUFWv)6S4$C0%_Fl1M!~#0B+?hKIH{0r zoi9hK{a3@Y-KXHtUR|I$VUjbeiL-5}A{w6ZDAaBzxD21j4!$%JOgp1$187tIykdP6Fh2fimDbv7{+h%1?*nj;|M7AyA z3@y;5z@xH$3b*Y|F8s}tM-8pxN%;+Xv@vNDsjRMo+N}Mcv~vnk-e8R{F3rJb_Xd$` z*OHKA_9QY-wTWxlJc+%Y5=wqxFYeV;GvYe^4-8(a&HYIzByHzQ;EY!&=~_9K^s>Id zy|}I;0ND;x*KOnW>yCoG^%>~WKvU9xZ5R8h zPLcc_oJ=mW=eUBL)6ikBg<_1xqQP`ps@A~@yx;G({ZT(R0(Bi ze?az1EVKFOJrvVrN&@edbKqr?JgXmZD4K-uCo@uFb(5>^Ka!mNzLMKm zXhrDykLQ=qh0vqF*me8<3e!h@=Y%KQWM_De@?*LFEgxlY@AD2C9 z6p7otp3NLK71G!ABI{K*qN06>& z_&<)$JD$on4CD6Rdyni$Hs_r8B}zg=Q+^7iA!StB%Ss8Mg_d?&S}Nzc??* zO(kh4{hrrfeLg;)PN(-c@B7@>eO=$n$72@98E^0LYwtc{0c>Xr-FxrA0GVqLefJHy zuwX6JH|9VR^Pj%-s)ZTLt?|@6FPLuBAMW>m0v~hB$j(j28JqqNu$c@Z)@Y!@>v2R z!(V^r0?Q;}u|^Jzx^0IpX{8XAsDTsHbKnPio{LUy1xWP7844?)&=Dc!u&nssLPZ?7 zZzycr)ej>(lAzH^7mE~v;LI9Vbkh6}7QZmXO5zMBf4HFa{DJWDfFc{kVC%hGQ2)0PEPjuNIcxf0@DLRc{CMT6QE=o{5XJ>hf^Z#o^ibtM?yeoyR1b&K zTbbX|*aGyIvR>Au2_VeZ`WMV##}YR*vycIn(L(lof%-p&$h}d9$YMt{o)ig;kBvTM z{XpQi*&J}_@ewi|5W40#;o+B7kowyjiDN2QnYtn|41tt3RrD17BZ6)vQym0?4~)$w z5Hj8#OH1@1`h-0`YH|`dQn=zp00?^GE2ja__`{j?nGS#hcU*BR1c0x)4_1vchLaH< zC_B>&Ja7AA@J$;K{H4-*34D|@z(bXGAZVZRTkPS{FMkXUa|OL^j%dEY6+ZTr;1HWw zm|Uohp1tM*V_`8L+ej$dWQMeDD5*`-Ln_lnX2gxg)_)#7?_UY86JZ|vEOmmP-W;B- zmW8^gewh9*7Q!sNFzlB;sJwE*0&{;5{I=;9A#iD`7wZvLflX`NF!$JcBIrWZhl8Ma zwJVM)SqSmtdSiRj*|R3&H5AzL>r`9+p@5;jrW5 zd-ORm<^g-rkh>Z4NO#7n{HA@*aylexeKX7M0n`J%z)$A*p zz&btAEYk}LznCLCrjoSBe&~C(lDyjQg`>i&q0uV=moB{ta!ZS_sq`qQxGG@T4>@>s zTNfV$FCv0JxZAKuyj3j#WBqc;%jmQJRg=!bW%Qj#F&kLK@fNU|P> zCCie@wLv^?@f}GD(G5dhSCG>#Y%ehAsFdwhu*IM^DVr6DnaxQ=@XPwmk0p zU+jBFz2`pg@6K^ix@tdc{*q13+zLQ(>hl2MdGL8bxA<~GD5{@KmkRdn^Li(0STXbnb{sfpHf2;OJHE%zVc{flFlFK8BC& zRmS;D*e0&EQmor%I-rM3w)?Rz@8tCjWuj-%If2Munh5lMTL>QqH5hqdJKguao6BEc zO23?ULyv|WzHjm=`oaG=U%34#9a=dKPxUc)R3J`W%ysY zg*@9Uqlr=>f9BIZs=U;IacEyrpGPkIonx!%Y*z*Tk#RnEcmv=RuW`JM#$xJwv0P%< ztwH-A?8fKAia1S+C@%8w4Q{Kt9q+$)4W4~-6fZvYrBx-A7I)WzkbcnH zID3olQmybq*fO)6md`-+k`01CDZ_Xx!+$t_m22QIrM+|!H4k)8ub{<+4>+YKUaV(0 zkNW&5;jeAY#Z3VstlOq2VY3+>7}7}n48K9-<*>S=%~u4LAU!#yfNvPO8+@`E+g@!f zI$GVPcF&3_d**Om8Jiia?Jgf-uS=tQl?c97e)mobK66P2?RKP!>){R~g>+q&9O z#u_=tv@>*^jV!IY@B*2Cn}&v~@c(J5QpS$O7t@a6Pt!*<;Xp3;PyRX`pZAYbUt~tV ze~rTb5~I-T%4Iax`HwFD)v+{}wY@~Fm_wG#f4u4Ld1W11e6z)MF*QRVp~gx*1_S#^qgQszdDqN>m- zvWS}fo=QEk8fm}^SL%7UfL6X23R`hnBS<&hB$ce zQqna`o$27J5(lgAu=ar^`T4>ZIes_TXgcAoq6uWE>1PnmBkR|i;k7#|AoSXcN6jZY zEM*ZUs*sLQ4Se2lkMy}EkL#ED)y}ll#<|K7qDSi$(eBnbsWq>S*`vY&E=ZuVf-n>eBW|ApBkS`FoS*zo)<@sV0s*3Y=JrQ{->fp;Q>7rsYeN4Kt zlIVHqpnH~s#OqThv}mQ0G#$1FTJ}ns7&il!Uiu;y*WQBTZvZ7q@#s{*2;^R#{g5p zvc(S`8=!RTLBhC45PrcIguUkYc2!C2m%s2lcPdQ(Ck314P}0@#4&nk7#2vwkSW)gp zeCyson(82F-CzTJU2;xRA8Ckv`isQ1DGK=6#Q=O_KEV-74bpOsd2Y5(hUWNO-0f(E7h7DD7eEa8j*mh<&Nn`#u)=N*)bGxAI zbC~pFk_9^8Y!LhhCtfv^L2Jf;c!OLK>l~`w zBtDYT3Ctq{zuX=0>7y*@^85i6S7c$eCi}kaRDci*9sKL-L-MYFfqENjhS|2dD#oXI^nQ%ZVG&}mB(}YLSg6E-{9C)NKQv8;8{~!P*nN{!M#qA5ec#=EqhNo zVt&Dapn9UB(*bPn13x-{g61{_*xsRt&)a)}+q$ptX=gRLHSR0)udNlgePur1Wtp(| zoj1<@bOeG2TjH-lX(I4WCjSQXlgxh@4(`vc5P9ZPzq>e>jGTS|erapL{x~~!4sQj< zbVCFAa5ykh4W5OC!-|GRIQ;sSSeRoT9DGdP9vhC&pGCld+vb>jR$bt;;H6O>P&8K- z?JVz;`nE4n_c;&}8~=hwvl49o^#dAI)nLSwCU`#13qE*jpnI|ftbe6|y>>c-mx?mZ zL-%B3-$|p$5r(T5uB*IY+gbzc_Eaum!Em@%zelIieW!;G^-*wjkTOoI91H3u`ZzW( z8x9p|U`Z2G%i7(B!R-FnYTzI+2l3IKmRwtr-RWE=H)I?E&xZ8)D!4uFx_@1uK0gL7AB? z{wiDu$1N>cK79{tI{XQ)A1(mv=qGUGsv_L_sDM?KSs?7+Rfrqx7^Q(J*BVK#fidok z$N`npR%pQmfc;NH%vKo;pMM+S?Sp>My-Nr4k^;f>t}!N+ML?m3F?!pK0U<9wW{EqD z+o+4RgLGiPV|HCHML|QF9_HK|4Ev@TOEDV*!6U+L--qEIbcTX~W`(c67%n4BCql^n` zN5c3zGkmhN2$nyz!r0Y|VYVaV#%$jMf=*J`Issg_OOTr|5?-+UNz>vOnDx^hn_VUW zyC*V+*IHm5er%44f(gG&aHLi+jJ2{C@}}@u+Y=4cqv3X#DgJp_4lCpzfz_`ZFpV<6 zDT=p2x$OW5*@CET6)F&qvaLIjQ9fjn}3sIk0hA) zs|;ejTVejlMu?xIiu#u#d$G|t)Q*K@vnkU0;8{7FEnp*#P&bl zFr(HE|225Pv3LuNEU1JpGiC9;Y&kgi=;M&u#bD#?kG8QJdi0m>!||Y)=Z2+)Q+hu4 zpoCGtYxF@^Q8s+Hvc#6CJWzaRf~#k5f)~sC;H7>^J$k0@_rXvz#s?inXF`RoHAcTn z1^uZu_%1S{=XL$uN5GO;4~)IK6^@jM(AF#){1tkkuBHSwq}Nd zkWcW1c@p@>1z^xXH4ywtCQlR~{v(gA z3G6yCpW)=Yh9Kmc86yFN{CT~JF39Q!pvDnxIMM8ZEzg!f;7d;oq);)O3ouuLkn%=Mc{z!DyLL2VXL@sq%POed z=|DDLpP@msrt|Z37SYR#Mq}L^ABlT(B^5Y#3rBJEVoD9YJG+4E{hF^5)BB*&Tb-|MSwMAv{3X4c_fek*Bk9b2YEaQS<`*cT7UHrUp{~)S2{b_w=6qj{XC4UiaK; z{(GbX-+Rm*^x=>79JU<2!mKVZnMzPW%39eIcQt)u%VuHwVLkEf$j zC-S%NWzqqAr}84#iS*&Y;~eu-a_e$TY3kl0zRoZoC(WP1#U2gf|8~UFmv%3?vJ3+{ zY%Jr)hT3t(7W=5q@mb)8*O2u&Q`H$m(dO7?d}N?P*UYKq-bmuf!K-zg;%;aDcZdpQ zopSt>qj#u|eVh2zrN`7daV-cP7bWf|@jnF}?%3lz`Y?mVy6==w#>}KG-My*YguOK2 zu{o7=t)llQe!<%lr_qb2wfR;p=ITWl9Nl62lUD{fIzB@SDxgUz4LavquK-0O$CIPHNm@K&xP4V$%q z*3oO!LuUXz=~_b-2B!qv>syL;%e}bU_hfj1$NABv3C6yf!Pu}lbk67me(}RObe&!{ zcSAv*uF$(HDQYXgfg@Ld*QIvu`mw1rWy}^{>B%vAaHb#c8%NOFWG8074C4dZCrhq{ z-Q}W#PV&sNjgEUy^59gA1LABr)-S>dTE_u%11=)hmUHw`rG`fnVbtm?DD()hOf>(Z zPYG5`tmf@1A8|^M#j~DBzN#o0=dCc{8Pk=T*XHp0n-}7^f2ln=_u)o?oWMSmnY;yl zChh`(akph)19rA8?CCQSdIQGqy+dQ7Oz_TvBD!y94qx!y3wBTa&E0m`jPm1j=-wcp zheIOJC##;G>MzfqQk{p&x6`;Us}i{Ij$^1>wG>kXjZ!xi|h>Ou6gLlM_9UjoHdw{foD zBrfZY1O0FjxE;4Voar;$PaAqZy0xaYEnMgUF0omiTi{ zMy?Lq6{PXPWFkAmg0PAdJyd61_;D_A%WR`U8G7Hqn*C73- zIpey(c<~;2I}{&}5x4y4h2zWprI&u#pzPjnqTzdu@#5|z(XK!Cn4#n%USw*68`5_L z+#Y3$XLnx|EitpextVLEr(2D2sVpzrlxu0x2 z%7)f%_fy0im!qQ92W@f0A$Rd?TN~_JHbgpni3S>L?l?7Ks5bU7pDNnq%jQ|nP|=9V zCU{FGm8>JGj63;W)bvOXr+f+&PkQf;ReQCif4VfVI!Kr7jnl)b$Tm@7u_CtR#fW_J z4DgHeNx=9tbDZ$0DPYo%e274alJ%Kt95Z# z#|9$IYZ)&QuCBWPGukzX@II$rH6iNlTDb3#4v~%4M6HuSWL3Nd2E>;VlNJNqKKeiD z=@u=Ioz ziFvP$=1cU*?Z4_+{B)c&m8jumht*{LeN`bBLKx!>Z&~iEHCm{J4r&pE^*p2Q4hNE_ ztAX?Hm=flj!Qju1w0$L4eJ{5jR z9se7}q{OKzxV6JisyUD4-uJwe4qmH*Zkm6j!y4G%8+2J3@`v45XGcqAhbUm&ht0Kb z!&PwJM;+5sBaS4}OhlkP3PEaVyQq@Cs#|zVN=Z zW|TZuTn{16|H)%SffIQ}wJ~V03t`*{>@!c9B%hMQ8Eu+G##;ez{YWP-8UOLoRZFt( zqawZ-J%yyZ$)h<&5hdowRMyQT2~8?E?WcrD{N!*={y z`gvj&_Yc!<3?7mfHHtV_?2fYwv)S%}>a+ z8?xy8@dp`R`U@s#bdvIEU9dml7qQ9k-xGS*x5KlG9`Nc~CA4kwgvZk#z!0$v4D&R>X(i7{!M2AG_a+GL7l>9J&ZDu!Qi~hqME@`aKJB=w1|rsH`WX^tQ>Jz(oS;4i21B0 z4F-7!MO2K9g#}S8@Aga{V(WGAxql!S8h-@4Cg!X9{2tswjlngr1AJ;6;lDIB42Vo7 zw_hKGh2H99=zt(>)L98I@{2@gw=#F&x&c3UbOZPK=6_hU^`xZoMlwx*pM$xNs_~Tt z>&Ig@M!6>k=>ADE^n2qYnrH4onO}^4E+-P9f7xY&IiITj0=|c*a*4s)>6O>_F>2;G zTCHhF-AfC&qR%$;)8!qK!Knsx3#!mR!wu-TQR+0aqOFI8l@-r+zeagjCG(hXcsmde z&Ya87c1uI#ReG|jQztK?#pl)OOUneje6x`YP*LEf{FuaFFPCFImIFECKK*DHe-yW! zkx=G2lDv5!rFMoFImNt>^m1@M?lM)U?u9<|^dlYK;qB)u2Jq2=>imVmFI)q+n#;Os$!!vAvvc1c44bJ%6)bkp)5Du#k}aVxe}0f039z6w zW7S}NhX=nwH3%b|m+;r%J+7`E!8sl*LbYx^yuw@Zhd+Jd-jQ^kWubWHz2cbnfOXgG zN42dx_+ZU=&cfj^Upi5SKbZlN#mjOq*(iy2-|?r6hX)^)m_pFMC46^#IKNe8B9}Zg zj?eL!M}Iw0VO(WvYGCij6{!s5T;306=a6iP;N$$1IUW644^V;m_4)HzI{$76&P_kU zIz`g)p??WK%l8{9tdFcqjf!L5b7`B`;j5ATc*eftuI2df zsV`RJh3G|m&Tv&a%Xb!U`qLclMeW2_FRQrbQ`>30(;RGGzYB#qZ<^(3-t_PSDhfFO z|Hi6Nud$_cTuVGI`u-JLD-$^XE_qJ0$cewOwZmUtbrmnUBtw&vV|xCqk}1cxF;_74 z*$sL&x&vc=-=@0%j^Owi4d^cS3U9zRakI)A8nVEeet3P568FV)@3>~1(hx}NbjNe% zn+LIO({&g!*NPraFsB*!b$QP{y(RNC4)b3Mu5pXEp5`>u&*DOn8=YKOOwC_BlT7JI zq)N5hcs5hunU*l#ZQ@uiX2x1<+5d|Z*k(4Lg7^=`N4PZ~rtqQC8jgJzxL)-^9P=|# z)0iUB{d;@qG@F^QMt%d8`cC6|Rc%E5gt2^ct|b?-IF(;kt(bBr>X>K0Usu z9gkm3hWU;)XdASN>wb`kueasm?uM!Sb8~q*D5aj~)uOrn=})<>OQZM?BkZWl`Y#;I zy>emy?MH(}Rs3C(K6Jv&06wQafx4ZlL6LWF7yUuq0#626%O;;W_$@J$I<(g5`QfHoBXUXWn4*ZW;;0&&p*08e8UFL%1eOYx z*swGS{!X^Qn$qcDd&Lb&MmC(eXokZp^I_{?J-iT~3UfZ_V9B6N=zhfX>mNz*m7U+4 z`=x-u4b9yX3z3`+o;V)?URDlFA9aLbSMAYq#v)jH#TIwJE`VhdtWbMQAJAIifSEJJ z;8^U0f5uD)AwTvsej;R~Y*gg0fucwajiN)(N- z?#UD|xYHL~ES+FC4aSEVYd!Hv= zgL1=F7&Lnfo?OY!8|QiG>uiZ?9r>W@=Z>Dg`@!rKc^n(VIu3I5uu1PKIojlmMKdfx z==PqRG9L~smB9y|mXH+}fDO9k5MSYsj%u&KC!-g#3${2aw8=6oKmF|s(vj--lseeO{-%T~u9_D5<-sqMiaO95_{`JlQzf+d@sn2AH zIq!k2dkcg-;Pr9KL3dRj>~>Cu!2c}p!1Zx?*MrF#kLr|4|Nkhx=lG@{S1i&%WVm zcs{BeG)}((<+---f19aH^o|ONz!(wYii0hn=4c=dfS;QLX6>&k5SH1@}tXTz2 zhr%@r_e12-rC=GSLG~WyF=6TgSX|Cyq3b;&s&_ zCpjo~MSlw;7;(2hj{Fx2me(X0H%kx5kN|9)w+K2vitt!vD8zmqj3>N)JUk%1U?-3AqOoRrF`XJ1C+KOW#>Oc}+QDghFAZ-mqnoE3VHp zfnZA;ytF|Lj5>WVc$-$w{%OQ=Gst)!f#cnkVQ^6tdTh@I>MtKFlJ5zWwp;?H*qt zyLZA~PK?TL8S6pW8)f_Y6Wx~)g09_@tA02AD-rmZTDF!T(&Di6nFM}4VfpK&s_@L& z3qhUvKbW8Ng^@Chej0$*7utxxi;a_W1R+mudRzuXcieGMqa}=3#^a>y+a%+%F*1)b zM4S|3lKc@!F1175Mmb1d;D%*kpNQ;A33{ICAhT8m;0Ns{;&-$k-kNI!)0(|-ME5S@ zcFG5<_t-+&ejhwP(5FYg7xD*!pM2ykIgly0MYYyjq;_eSwP)NKh&+WV3{2^Of0=dY!>mDZXX4oMw{X2K~~WH zmz~dtR}$g>)!Y0b@r?CC{~d9}_a2WE7coX;od|z9UnYXTI&ITFlIqnPW1a>}8wS~7 z=IAUkyx0zRzWYcvfFdSeZy>(4DyZk(B{s}>2wzz2?U2YEJ@IUj-1M`~mhCTqX9O8OPLe0_k@)l<|lC zVdpuve}5ANg1^(DxrG>vbHj}ZYl+|s8*&1O{9=D0Pf1RH^+nSKb4kTk53F-8AQ9Npt3a1x3CKlZwXugT3iW88ON7m`Z#P~%EMfUuu>CcCP3 zv%U@HR0Og;x;;J#%qN4qTroV1CoNqrc==rkiEwnndF`voq&cp*%p;uyYx`o8P6erv z*ki171qr+2ipnceiE!qfso(&kJAfI~MqvHIDWO}wU z?rU8to$6?U>}TZ8;XbGrHi>Ab+Oj>YG7&gcG4oy$=KaQhBPC?M*cK;TQv#N4#SiOt zkc2PRSbnvX2+zl-W=W*B!;0lLj+1gR%b!fzLZZt}vBWEwWLjIJ3k@NxTL#CUup}=` z?eJR@C5)4VZ4O~1Plx3^u5l#(oh^>JFH1fxu)|HN*3x%v4k+|N#AMi_`ts+Zj&OaJ z+qV-pnmXe7vu#Ashh+;Yq_@Y~U}f4YA~rWh&yl-GhNc!8>c1iLCb?mx$6F$1esylf zY3UJdrkBAma{Hti7R^j0{Vw%FiNSvIVedc4uqo-uO^{&Ij;;X2mSz)fN=5Tpts=t;=T0VdQ+6O&nIJzzQT%~uCQZf zJ4Cl#BbQ&Zz4W3p(hteTxOk!^B!(NH-Xbr!;9LjK+q1-5#;c%B_%710gXtez>cz^- ze?Y22IZ-NL`Jj2<$b&JucyZJ5055x%!=?Q|dfFN{j%L%=GF2hRBt3G*7Vj#ikd&*2 zcriyOAT84rseG+;XA5JYZk$6Hiyl=&>!r`E4SKlSv2upE!qpRo7cyVh^!EYU7fo?? zw}d!1GTvaw4e8|qBMdE?PcB9q;WXQ=BG%D{-1FWr!RY`9_b18L7vPe-Nt)N@DRz~h2jJ6yCnwk&b=9c|L`8&H$?8zaU%2jd7ZhN@1&QkF0 z!K+q9eE&I`c>FQK6KC_u+-7C84|^otvBL&0r(YK72CHLok%j2c*H>^u6ie!f5elq* z{2f{y7x_?S zeEZE#Jhx~AIPQ%RwNK~-NA(ZVLqE*1p}dIvSD?uFLWjuhWBM3zJCPJJ-Oza76!Q7< zUzoIiM=jg+BfO|5!hOA=*Ljl3DPrpTcGCZo9{$xHPR?}7;fl{YN$_T6{J1HYWKY$= zqdN5@xJe0_hnr|^=z@q1Ysm$Db&MXKO5}IS;tYSL_w`f2hXuB9c+Vd=Z2L^Sf37i( zylIS!D)&Rc(@VHw|2N6{_6Ahbi{;+;T2A{l9pHxDETLg@7V;0TRG@h75&n(qQ;>^v z=S+_8#`S|<(tf+*ImREO1=APvbLzi9Ww94;a!kz6{eBAsZieF1**IZ;43$o0oina` z#rs1Q=*PzJ9#$+%cJaQ0FH2$yZgJ~1GWmjno!rmot9XH%lqYuM@5dj;m|t(WTjU}) zWbz&E{DJ`fa_9+e{$p#QSCCVFzfLm1*nnR<+<`x}IF~;#SCd~WSHQ2-JA~@}C$T+f z3o_3$b<-%7+#B+eo-G-Tng1-neAy0~dM=5taL%Ue91MMattOF2*(_?L$~kSz=WIv5 z0=ubM{I`Sl+}BJmK3Xk{d)d053!Q382i9!igr3|NmMjW}9j+509=UDUyCYto?V_ElW2_GHSKVKnn@6je)FT)9uiJOgxkW~Fgw_-q2 zQ#h6Pi;)l9yS|B^VIZuZP%`c_D2m0`3^EXL~7FO}H9$UGk2NU>e=}zu|agn5P z*A5}Ogsnpqsb+IIGEcE2NhJ(9dY99A=a17%yCu6K;^6SYe%vDQ3~~t)`Hl7)aLd;v z{KBr|^z{8bexl^0A(MN`}`BpDqp1p4_?A;BbXeSQ%%=)4m3(J$m@DvqL+y)N;eCLBh?TiRTgk|WR9eDokE z%d3ZqIri@$%cSuN);ZY6WdvL(OvmxYfpqUYPr63ImPTDDqKlWB(5Ks*F-gIk&emrA zj|0qVH#ZL8!!C^$xb56_S6#|-z1*kJ44UW z?t$t`I@zpmj;xQ+9QPVvr=#JtF!~fyeN3UsHv@AD0yOpwDWPa9)8`v-Uq$Fl{ z2lyJ*s-?=%2ySkVJSjV>Me+zZwYJjSq~P!60R}7*w;-V5Vnow3a+n) zo8F!9OJO7YR`~Q5m=~dpj$771_RW6~da@kS zE~(*~Ayx2YK@GJ3JO~F*M?m|ja+u($jaK)IVZ?!7@M6YL7|nPf2fgNl&?8XVQVB_} zY8cXlK-MYXym{3ybeI;J)RuvjnGvS-TMy5t%3|q=)4&)2xUuXUoc{S2-fr-O4Ovp) z16G3}J7=v)T@M+18IRd*56E{aqiy-pp6vH5?{VNhQXj`0+5oZb_h3k~1=IJFrAN1{ zgPL1)@S}VuL_F4GxdRn&^p?k$9|ppX*VbrqtpWm;8{(j2OJL6f9W2OR4jLm2Q8Y6L zZ1UK-KsF!dAWIKE~zE1&nHLrtx%k|JqYdr{D zHo+(Vb)-5nofP;Lns}`=7nYq@!-zS1V6DMlIH`3U^us$KRC5nF=_+B&)`L*pu7K(r zieaRw4(@ZVf&@(^{N%U=_6=3W>$*jd^2iW1omYVq%NwuwyA`-I`q;X<1`=>BAa%@BV$$U zzw$N=TjP#bbWTFvfA=8TaWRY=-UeH`cfiSGT4g&5fX$;eX11`dS+o#~%JRgo+?u#jvmEhi+`4;o$ zf{-Jv{Ph-2&-e-}E0=-5rQ57n0`uFPaoP7;h@4=6kJ7S0$dS&7T?Z=dy|8`FE_jyU zj4nTBLYk);27j3kqf&d}fEPPiCr~ep3M_}^dKP%=G5g+Kw8yhCyWzWbFH}(41#X+| z(cEJvq+YkhV;$RjH~&by{zgG_1yzH=Z+#Yz9!uVg8PJzI$@?Td1 z0#Enl*PT7S(oWem5aHVk%l?%>g18UP9LPHDKEDCM2Y<@h8wXwA0ZXd=P;Mj#y(+9x z^z{Km^;g5amoLHr^^toU&T)=ds*nelDxHyaQozjUzSy0# z7LpTnaX?8Es0KWTbq=|ZF4GG+pA{hJhbez6;Dett@<&!ehKoDagsz0vCvNC%xgHV@ zd!U;_PLJQwe0EXK``YS01-A57!27$df)@MSE3B`;fEgZWcYP_8uk=UP&N6tO=7R&u zvcURB9~8R!EakoMK}`WHXt(b1$9su<@uJrfsBAREvMt5X@LLPdg>Qs@xdCX^{T=!! z+GC%Awam-SxZ-_7VPCfeQlDfvJgg1+_MY979~W4EqQxUHEOizXjrA`m@urh?E< zb9>UV9$)fo$@M^}~ zZ#<0Fu0JJb3-@tC--V^TUSO2}ZLZNDy`PeDTOw%oo zOz748POkWt8P>#n=eOJ&4hmP50+n*da7;_)S)P--P~C^BTrT5S_K=>;|13^=r$*0_ zuNX4FgGT+$p&J>p?Ri=azSyo(_g_vKKTNY2ikv_3%&*LA-00A6t0_kJTM!GHCk4~Skfeu&| zOidChIr$|!=!7G8Bx^5F>JVH`H~*ET_6ZqO|3n7w{6&kWtFH40Vy5ynZ=Ukr7dE5L zb=|;?O1tR-JN>|)X4!Peo9$5_@GLXVx?E%eO)aD7@%OX&(K}C3He=w8 zYM^l=AHp%sr)Y9>4ln4A4)f3PyDT)Q=L8@A;={2J+BcETw5sNK|fKBKIc2)T~&^j?Jx$2yX@%VkISZjCg)+NFeN z96jFR^G+PO_>shK_8_`!r6#rCbO05j2GcR^o-|SFfUHZKN>+LE-5WLPitJ_iF8RsS zDvZJiO$mQ5vXDFd^#re`w}~HV{YgCh?1cY0I`43--Y||MJ6l#nMpW9gIEPePzjkTw zslAhijHIL_8fXY9X^@2Xdp{&8WVA&|lu()?4f#Fq`)AkH<$8NN&NXVngigB_gMgc=ngP9=*ofO}?5X+FgGc{A&-*AkYrBY;)AR|%6- zTbwl$t;CAAflT|6C$0Dq$0GgI+2!wh#EJ*EaA=c@OQG>O!M*vpcwwTPIHEnCDR%86 ze$3O!+H(H8AfNl4?qyFB($)P%2g71vdx;X~*wzU19e%OOzYme2l*H2e^7~HRNO8E@ z7IDisUs28IhREmZY}@u5!eYlH#aPt)cZ{8FC4%){G+q7IbFN}Gu$`w^g zOxT0IxuUjYpSa1x#O3D}b@BA8FQWIA*(||Uo?Sb0iAn=Zvy?sOikWx*u&Ybug>t)> z7#((;wRCLZe)A04Hp`FY-wF{YZ`94&g+Os$VJ7433TIp6iDdjM3I5#ApjV+UDeK>d zR$k@3tpr1h(9DB&s))dwVxzLJ_giqs!4KeYG=;Psh5=cS`c?Z zN@_D*C|&!h#4g&1^I7~Q(*EGt>;EJ(I;qo>+77r{phmUr7Lq_iYZ|#TS|Z)S8N6~t zFWv4m-c3t#;E^%Cj2I@#8f`^AKOU2eura5xF-s&;u8+;J!xD`hCbaBsPkb6`L0$7Z z;&ne$Ix}^s#GU&}XNAs@l#Axn)Z><9e}XQ(-M>$A{W{NImL*Gks(JqM?^TIir6C>r z6(*_b)0MiIg-MEfb)hL{(UK<)##GfcP4aP~8Qr>>ExFZ=&)t;TB=(=xs8YW|QkC}y zJd20J@%l71TnT6NbSP@nXGz7Q&v=n|Ng~bjM*m)p^+uifJm<7TXQwW`cJAdAzDa}k z%~B-4hN#KBhL)zEWOGaN%Zj8j?X~2kl?Bb-dS0SAMuT{F6CVfb6aQ?G|E5T{%-&>j z7B}fcWk@dUF{bek{AC`uF>+6bUR~ozyIoHWg{_S&XZ(WqgC>bn@$8HO-92~Gxi+OE zd3L+%w4(1^+O2O+zn}J}i-leiDJSyGxf;oh&hm7%cp$zhYS6q_I@mSPl(rZClPu)i z%`?+iOT2f=(TiycnLF;t)6QwSkk0?}ceMlJ z`kvp?&6Id=6aT!kG3ku_P@nEWcy2Szx#qPcUFoe1r@D){r#=SM+6ZHn-AFnrY(Dk`DAXvRd+fc?YU}q=~R;N~G;Bk4gTY&@XZlau$8!T*i-*n#4wMhBro~ z4j}t2iTHHRj(o@xrhO~1?YcfHDlc(1)*wl&Un(4Hy@5PUX)YMwX4fNa${A!N)S_p= zO^k2XlX);I-hIWIo`)dyGA`fP2}`aXh1-TjI2>4pbDOFqo6dG6&a{MaaUt}W24;V4 z#DdEQuxyq)9qzRiQjYI*?@h2P8;aly$1pf=DWuou7q1TFowEtsyh|YMFYda)`Jg>K zU_!s3@$;qRXfGvMfAd69f2nVZGc@DT{j`td{hedbc-8?e>58;w+yL|v-$5nX09t0s zG;!`2Oq=@w*Usw0Xq-H~_g{tesuonWeK$TlQKG>P7Fgu?5G!p(q`4|nipe5`?61Rm zuJ5~GP$kR#&26bbpR3CF>GloMyh=ia0%a}o!=54QFm0R*BsIola4i;n3gl^9%qeL0 z{>we#fs%0mBg(JzAB-~Nvd`DD*s>*Xjo~}T$drju!3_qwJQf7+Lob zGq)ba(9^&0VcIN|yS&5R**aifId9@wZ%iQWy)I0`nz?-Lq&o?_GSsB|DlB>b4L_Dy zJoe-3)-#gyVsA{A%fr$N&T+Rek>vv1`#(j*!9kGbsgHCX0kvc2<*3W_AQZJXW5nzo7&S?b z&g=<>T91KjNr;6tpiYQ26?gGPLVGw={(>gzgmDN!k%xuR6%Sgy;%8~!9Fa&qv{bk3K&{fi+v7L{h z^YAKotlwh=Q-v@{)?Yxw%Sxcq5Bl4qotO1ebIy3`Ijq{^J z;9B|{-E%KucE4}9_4yEr8x$W>nxA6YHA`S1PK;vguKtzhSb)r34a|wLo|2* zsXsh7b3b}AIl6H*8Fh8l=&E}Sai+=?PrDF$DirUg1mfAKhZw)_hV1<&P%RF*4u4Uq zd>PN9`TX^83i>@&CinfxDA7`&qr>CSJxGb9_cZ+&dE$9ytXQN*J^yCm_*hNKkKsOb z7hU?{dR>|^ri-Q}E|y|WrV;I!5QjIPIDeSOs56!1>16-Q=)b2gscyK5dx4xwV=O_|Nev2F zS^&v_?quMZgNCx_@bTJ@xxV~;EIA)v#we524X(a_BHZL8DE-QN5}R&eu7@dwN9JIa zp9(!zti{w=Il8(0I(!#t(av4>5OPwVetQ&RM}jiVT6G$~UYgTzm&2&Z)g{Z^NvQ9m zMkmy=ar~nmb@94@L-RRT-R2gy-_oT$IT=Vd)uOXLN%%M0n1YvFl5q`6iu_SD!dA+o z!ME2}POI!?iNlMFkNFHQ4m!h4>A5QBrK;M|frnS&Y+yj%qi!MkeHRL!brqWFnzSVD z3JSt)xKA=2J{lVIpi>E^d1%ti(|IsC^#E|0wTg6TdGNe>MzZOY~>6%7%&b zwn`;ySMPedI;E8L_&At#9;D9hIEDy|9fpy9uOp(=<|`QJX(AbIE%?=%@t?34`CPg#ydADt&?_ZS&w8ncIx~l*3{Ed zIQiB?G%NXw0HZdM=jW-+I6_RwG!P$ly^Qk{u8Dj9NN8-|4?-`zp=~?HFe#hYY>x)p zXrsd>y!a=Gk}z7iM_s(_Wy)@D8i@eUb*ypo1^n6JN%41H2wm@1IW^rID~@b6Vn3BF z#1V-i+tNo#G&<)mEWe~KzBqG4%&UyVqTz=%JCt>Sc^m_b$E&<1!rmIc6o7l ziU)HkJ;x#rnzEOZzp>$;W63*0o%K4skG(qgpEzu70edihm8kEo%Es!Nh@8PH&WLsu zy_F9Ow{1TPKY~`US7v9NSE|ovGoEY{q;8ZwmhXf?r|U7=dMAcz+p%3glz9ezG4nC- zlsH{AWfOaL7D~*H(PXD7tkB4jPOrYowC(31!mtmE6PkpwX9t~!G#wISgdk_f);~gn z-D%;?i*R;QVXFA^LoOS2LPOki=q%WNCE?}48{*V-2l39qOX9tR6|{|Y-MC7^6r@MrrAVPwI~!edCaB}oE=g;Z;`Yg$>NRDgmK|s z2paHI2=n>l94(jYTpl@`4X=-(1HI3%3C-{5YD+Se)F`o^mS2Q&wGC{P?Hl23a1wjM zw8Wn?Y}ns~jpDmb^F>K>XHj?N5pl)7!Ge8|A(NB07poH5na|KIqWYOKR=oEghD}(< z96Cp`+^BI(x@&HGrNcP8Kx}IaLsW^DsPdKPskyVuqIU^>woa#;5h~7~!iISES^XHOur*_2BmpU96 zkHx3e=r`lJU(VxfY?zH2-qRge7l>E>-Dtv>AX#3$a}?)({AW-1*M)+22Pmd`f-HYd zwV~L4whPq+9G2~k+IRHD_Kp4M`mn8Vy565wu9+p{t}V!)4XK|aqH??}H!Yn<_x_+S z_1;422RUk|4=G<)xElqjx8;0T7CvPxrstDvhK?GnLH*C3hJ0r^YWq_sdAYV1adrlF z{pvz$%A-(H*@wCucqEZNXQvNbfu8(%)x+x?NptoMQDaH!-O_nD3!d-CQp^xv_{BYu z`dWuB&g((?oucsTzmD{YJRvc)pgGZ_zt=KMG1wAA@`9c)J#3r%F) zGfOErowsI>VvT( zuM3stXyN^9O`cD+#FQPK>7A`34rS|+54K7ov%B#8=zGauIUACmi_!;~ll0uT;(Ql+ zC92E%!1_+!;GAokQl*Ix?^VdoLl;oCBF>UQ)-?lCn$;PnR5;wxtrudtccy4<7i{J4 zf8ISNLsd?T{C5n&;m16e88Q!x=ctoq!4=uLv~+VQ&UNii2Buq3^^?zc-b}{lB+d^y zWR4HBb;vEQ6tQxqRDAddY8|vmbB`PD4B=d=33u_5_Xn%1gW&(4E(N;qe8ylin$~R` zLSJxBTH15T*_}3Y`S>*qYc!;akJ}|Vhg|4CWjTBm^=ROGZwcc*p@3ffaDnGJ)9N(Q zdf9+Zy_<{PWx9Ote<8EwaVIj{zE3hie-IT9cm~dfqm=a5lI!JiB<(cpmNO%M&C2Ge zP6kfcKArcXlMO(V)yb!q7HWbz(ffhJ@K!^cX6k9fov}nczNPRO?Y9`{En<6P1aSj*f zL3O<_1}jJRq(Q?w<8i_V+>jrQBfK9yrgSMLxTw$u>%JJ+{tE}Ej6$uNA`MbL18F{# z=W6l$Y8|>rLgCqb7qdc~@UCM!$~LZr7*GwK0fsj3@qYa`4YO*vN9R4iURJ1+>e8JE zbZNtY`AhL=688lk?~BJ<2O1XOf+UW5AsXNpSH>DYLrx!{Y4mi2P59Y~}Xi zWt$Rpws1qYOUmTd&k8fk{z7L%o#cx9Zv>~+MJ)))MS7=-U@Uo*c07H z%agKO8072UL*2|3{qq%QN#!)GYVSZAyx?WH{uhoe+KR1ua%6QUTcR_SbB?a9#pmU^ zB!9sYQ{BJemvqe`@5 zt^gO`Ke*B~6mpOFdHhrtxO!DW$D}I?YyM$d%1FH6J!|f^M{RZs1e;OVFDQ_0&M;g` zZ^ksUUYOhc2UzqF9R2bWoVASbr>*exbwjD{FU;LG90ikpU~;#icsu_cz7D=| z)=BfQ+qD9UG11W6QVI9%8}Kcl3Ge3xb3UdbJ?Xs&DxoiNXj~{Pu63j?5gnm<-=L@0P9&&tNAItLSh-4pEZTxm zvh+2!#B(-e>RZm@I)$ZsI*{SIBha^Kz=#be(9-`IJWaM>$Da2vmH!WeV!mKqt3OI^ zRN`4kf8<$JL-+V!L^uA%#vgUgR*SN*cQ5mILusT_ZLmx zj$q##QUv$j6NGzHGATQ~K{&YEi1Fv7;H3YKd$3LmJ-Y1^J${`P`2JPQ?a@T4a~lP_ z>_Dbov5e%V_h(iEKT${9ekTP$ou=%YF5DJu*!LK1$%eiSZ1eslLVdp~wn4I7yjGz@&*t?J z+tzrCz0QPE{CYdF=!+wJSoev~<4@9?F)=iJ*ETx-yh>PQK8##vMvG;uwMF0WC&lM- zfud*XYT?f{RZ*dLC55@^3p3{^Gr#Ng!rey(%&_!>^S-4gh{-uK%h|UD&8Zv7c#o1e zhXsg9s_E3a?tnP*&n$NT@=s?exBIDku&_P&nNT`0L5zq!E<~LS5Y@Av3X(noTeA8; zaa3e6GwLlT_L#emy&JF3<~w-P%L^0O`v_lfM=2$)oyAs9*n(;G#5N4S=alm1EOSz} z5q|~vk$!rxDE&9IHzK9macIDuuI!Dy6FcEHkj;qR zOGdAwoz;H^i`_Fjh`;&-ie^16Y0fh}(*8V=bsZ25!zubKKW(w-mZ89;KeIh~Ih*-0 zp4m-SWUgT;EuZ#W^K5M#dYyw zl^-J6!0JoRoaw~6+FFV{Xv4iezl8Z!LG;8y6b{(&6q$jc9f;qMM)JUgRR7~zJAF3-JjOy=!v=Gl$hMTEz+|r9dr9Ee2tsK&Wn}z&*YLe;XCdh2 zc~TsDfZ0y($GkrZe1BZ;d?iF#Y^vdVSGnHYee_h|-AS7FaE9}q zbeAli^hZp7u#w$3NI27(eRfk7jCr1Q z+1&lY)9(S|l=5UD5-0fiU4fZqeikaN=dzsbhtN@TB-Q$B(ce%zs6>_G!S~Ko5LSY7 z!Q9_I@-|#I9tVx6#bK9@w5$CYc=j8T|7v91#92G@alIfL>)uIle(`e@AFKfz@(r2Z zkvPB2oF26Ply)GgdD%ZK9@&WC-vu%s@Y%GNSm)V^{{1e)m42h0{yIGXyNk^3z0>h_ zPe&THtqSAA%J8)IHLh5+VAq^boC&^;_xic`ouo@GzE9!bAq{$6GGIJ@0P5EmlFH0l zm<`LuuhMtuyFCR`Puc|KFjSVkf|K`J?jWwfrw!RK+u4ySH$BCY(s~rBBqHYQTPzzL ziMS~b(Czm-v8CxShK_lQriYKQuu~JPThz!r{~40K`$4+Xb*i&Nm(S%eINg!-{uW|EQWIW{ ze2lpLauogL1pMXretOgs**w##y$DuI+o5Up5XC>_X>QdcjPsPI>HSI(-@hGux>VwX zZxc56eTI(X73fRfBAgkgOy)(e@cz6U`3}8{sWys~7Q|3AjL*LgKgNas+9736{Ub&4 z%fFBG*bX#0w*)JDHN(7>$?~W@KjlKST(|V?=HRGco(r7 zRY-9N|NGClS12w8Eyq>p_u<<(C)?Ll(lyvR~x=lOHiR((@Nl0ZAC|M6~o?kp=HihaOtH^b;r-6Q(iOl>oaiJ z(~xfFRiKo6hOS)70e8!DPvHkB@8v$%Sq3s*+rE`uoMR*%Xxm>xh;t{3^5MN+;SRhV zZp!$W{%5Yhefmi(4>|*BpDX%h2{KaENm(}$|Dvv-`PCWx=xt1EIm^fE{7|~tk;mXFaUadDr;u{OaxSRSz5Gw8ahIU!V-5Zl<-spcnFIp(sF-RO(z7g|(p zQ;dT5J?Y2Ta9GS%r-gRS;2tjeY1RhsRZns8lN07AokF9=8A!der578Jki)&BTSHLP zL!NY&s^V4iR8o%ngwVBp=*04+xOt!${%Xgtq=ox!@6VEXqy_g>^gceAj{lhk&+K+s z6{ljRnj!5w^%acYu7GrX>e~INr8l1;khFN|(G;D1H z>L>Q27hB$<&qPZq+ns=n-g0Eu^%ZJ&_a|w#!K+GxuB%VSeouksuI+a^!zFRIdn z1=k?u?M>L0j@d#IbR9dAZAL791ly5TU@6|DcBTbgZXtcJ8s!!pz>u4Iv}79Ji}U>c zh37FS=xs*&u`{8?^6)BuKQ?YwBge+mP#~qy;>&8TLF%EE z%gMo7-mB$3C7h_$;U2^=6nC*A_B8?#^`=xGc}doreWLOTN)EOp-OWpRs@ICTLEW75 zcYmIP^s`+YR0&t38~D9-Ka4W%$m!xgED6$}Tz#a0w#>np>^gewCnyt1YgXB%j;&SeN4lP8iuTP7I0vjiQ;Clkp|bhP^7BlXjyET_wLG8B=PQ%^{PFO)8Of)HKv*-FZUldl z@xgOV51_@&iq6_!z>HCxJL?mI!oSv(f9y31^Na~6W8ipPo5~9U(VpCmK3g7P+>p(IMiK{z+>8s;jSk-0)F#xgtV$qUQZjLd0iO^l5Hdh|KN zrC`pONiO64NM(9Cobv!@@VwTaLo%+gOaJreanGFUZymv$uU4d-6oZgewsg!l2(KS^ zrRp)eVA4;Inyo`+J}gbwaD;nzrfEOZQP+{rEv_7qT|d&y=E`fF>)^8vk3F2o@!b*J zROMVGQ0pphFnKk6P1%XQAlh z#<|IAq1Y5;M9GR*(F6-RlXVugMxE$Xf-0T*VMf7SOE5*3=ZPC*K-@#e`D~c8+*ayQ zhljbojB_F7`x-Qd<6cfTT9}mt^(iK#-8~vopZDLj2a(@Ri#~dn$IiI_M|DFnlGS0@f zduH^W|9s66ok{og2plxjrU94cV()WHDsn!B49-uhT5$qt^{qH%I|vrHOz6t)K&-7a zBHf(%c+gWd*`MhzSFV3%Ypr*r~ zShUB8o~$^H`{AEq>c12}cu&V`^+BBL$#aCB$=G+W8HR1UvGuq<9ohU0Gal4ok;yPb zoYkTiDn5{`QKJdFBJj}1hL&yPdsOXyWYT>h*7bh})msWF$e?6!~V`(T7qw?SxQxE%jUFrJscj%mZ8kt>hq0ZzF9!x!t8vcB@ zj;e(;(>;2XFZ%o&L=$G0-|9KVly(P&Nwl;~$^03gZ}HwkuaudHKgs!WxtrnhRfm@A z@;UW9Q+hQk3h#F5aBuQ|u=^-aNu4&~#btdOwO|IUu4<4WXylQ=wHEk%`&E2%x zboo+0OuoiFHTu19Z$PZG zqUlWG`tU*_v?^NsaPok7`K*R$yyLT=Skp}P{R=rS+>oJQj?;s3Q2%siv2wRHOBNl3 z5{E%dAxf2jpg#J5*I+gxP z6!zbDVLk=-gwKznY1!;#vMVulzPc`$)oh*09A2MbQwC(RAMxW^*1mD9)H8`(qLZB( z<6WriS33Qd5{FV2#D?yEEZi8A%C_hDirfFDvyy=tB6k6>LEFq(SDTLF(@cN%u56OH zIWC{1yR8c?v4k(D*dyyv%sGDzrDs{wqloz;&u@s#W2Sg&ot+@xGDUd(R)cXCg|K-0 zW#=_tU4-~)GezzNrZF|O!kF6!*a)9S&agPb}D+_ z-lZT2IZpSj;AKVMJCy-^2-b1tX4-ucm z%Q~+Xv|XGwcQwtmG7|d?{Vb#^csd(Jtr3$H{}Z_fPP7Y%6W3H7bLRd|;X$V+3LANy zo?mfb-YesrJ+karbmC;ucbo^CqrFf(U?0SME*1!d39l$NRfCyVnR6C%DbxRIFK+pn zDdk4fs;eWM_bo}HPPXI42U9JPlNU^nyWFJ~)in04`8d;=aNFS z19QF9QA|(m%b1>?^VG3j*dmv5Dq5t$ZfMTqzW70;|0K}4$!v-key_Xex?#0AwPT1wk*AP0dUc_Xv(`b;~gEVtQxidfhns zakq&MKP=_@{!lhiV=Vnk*JCRJt=KEma~Tc}TbO2h1I0!iqp|ibY|4u~;iPr8^FZ5? z;vgQshV0YR%+y`Gj(erl#Lhvjlsd88QYV1x79^vlY18fkq5vy$sE+4LC7!%fHV>f{T&*|QTT`lMp3PAPv6I){1L zoWsT6jkDE{Lv`J2RJxyn@}qX7G`&Prb}NQ-Ntd0wHj*J-zOcn<@dg`7fsTQb_w@7d zzRGzE1KZ(ebRSXV?UcDdjYjt?97^!s&7I_gE=J5uS7mg&SrGrS5MoG1Hl;lL$ zF>w6mfZg_bbkXv%B~ zI5kF#PM>@Vt4-RJI^-(8n{Al`vBp851`$y0!=~5IAUA_oqr#YwDKq3#pPkgfX`5|%)`L_UvPXUFrd925#95U z(xnbt3HXA!DcLYx-Ueg;$I$5V1_e2{Fk;OI_~t#tvhf{h zX}=2GO{l`~Ntv7%SIhIw@6hx5S3DPahFLxh!^QwpbgJ>WwHD*M%8}A81sYv z1hv1_a6Mm-UoF?Ln6=>3fnSnP#|)IFTBELgv!wsuZwTGeBFkWJ=3KkIu{W@+_Yd5C zcLSm2|G?+;h-r902i%=_CT z@4#(DJT9+z4we5N!f&4uWvA9i8M*jn{thS3zd_&pZ-|X* zg&%O-p%8t4ox`!U*^obW4uz9?Qd_+`X}(vdQ6ug_QL_=IW!c!?Plc}iPRGAF9cakk z0_5&jpqBMDI5bI~>Q6pK(+@d%J}n8w51+tSz61mP>+vT0sVql);@(rd9l|{=?K!v; z^9@hTYoThaAe-mT@vKAhpfKKFO+fvkSU3tQlvkGs&Q7ImN6RqQqX1Lq6d+*KZ<}u?+wNlwIN_bGhCP|4Ovl+@(ER_RYlp_7Md#%3OGL62HhjK?b+LFp0Xw90k`PIPRLAZQ42BUv{ zMq#fyv~|~}$F;>UX!(ay$wzGNu1U8gZ!mJbG7VV!8orZM=zg&T0qfOi(Q@v{zVQN2 z^bP5LU&4pjGORMxq`d(xn5q98Eq&wB->L-%uT;Qsk{U&uK7o3M2Hi_}2ODb*?iKzH zt8zX=&wdH_Q7WWR{t*pKgOuNwp%>%4r2Bl9>ZDI{)%D0RXvRDJ8%QzYTlp!G1le;2q)7$we^cjx_1D@XVovXTpEt_{K7H-(HSeb@f=aM~*yS zyvF{rcktc*2r*qXs8jR@7*6g^#@AB${85?AI1^ghqgMT*OrKZ$gAxp(~{2lEV=Ip+X)w`5v@1SCA z?`uYiu*c2{W!h+)0H1d=sP*hiv`3mznszP>n)RvF=s7Yp^hpw*hxi-Zdw8=DJ=3kJ zk+a>nBhpY!Kd zDs&|V6uBoC%2nD_@goIWP0cw^F;JGVR_c2W6^(Il%-5&+7n~`?hkr+f3S>9=E#KvMT6k{sfL#jl>K!k-R#qM>7Nv4f(q z(Cv7lkf3#gNs8@;a&8HW@Ej}Tg{d&!_opRey;){;Is;o9$-FGKQZo07lN`!WsPVbDb^h8 zkX2^M84aE)Z0eJ1OrhD9{c7FEk_U$|cb%1VYXYAK&)LM-lqB(zoN?BuTjAo=?;n_l z{#{{+`&uO4IVX%+_0oCw*UqA(buP;YeI*R8IzWNDCkq;4tf+fMiH5CyA{s>vNvd1<`(P`g8x^{;WP+~qFI9&>=z znF;KnT8_y1mx5Q;LYH?z>Y`fpBk{UUij#+4BpWyEn`C2ZD}6MdEnXk4?xHGe6#r9r zD&E}omE1<%W|H%XOl^o_){p*ok=^knXFe5hCvu`_*jgy2OqFLP_tW^f_a*BwyeI3w zXe=vA^JF?!k-|-{QS9W_2;um*4UEq`1&w)U*~kswgyW0Nn9ZpV!plJ#qLe|{ccLJ+ z>1|?TCp9`%+s5(ybfS=d!&oJ(GCu$z|1yK~$}BQM{&koTR*_cjev0(+5}a zCuh4D6`>%SY$+2bs7-ZN@8wH92idapN4MFJyag=v_(_)VcoItSEed8`Wlx_DAh$go#f=ZmnOpiyF<`$7YguxM%I)N6 zrZp%~*-Bi#;h<JhLmk&8m}##lQ)U%-{=HkR*fHj zCqjw(pFPC>J5C7;TI@wB1MtR@4C))(Timt}aSJMyz*ujyU%r zMEm}eSbaYWmq*^kY)R`SarK(J!qKKUY*UbkMN5k*V4ktCJi(F17`RiaYct-zSTDJ; z-iZDLS3s_-0+|en!P;^@zcs&t>2K6zzO#+5j463rB79Vu5wWrZ`L&eexK1x>xoA#_ z0fhB_i7>oUgd z?$o3M5y=vjTF&dZumB5ZS<|jztMPG58m{&o?3}*z1N2raV^cRn>Y3^#B_;$z78U_b!}ro&~k4(ZFE!qY=KxUXKU1d6K zBTVR8w}TiHAV>FAf;cDn5h4%HLXD#qEk1Bs+L49A&yxsBXhgHkPW&|LNIz5NgJ-m< z}vQ`CrF}~E0Ow@$LKwP=eDO2!n_Yc67P;-ukA_cb1+<1bDu5uTxj*=J@-UOdfe+S zw6QEfV%GgDPU?tgn3IQ~L{+3UWZ_)CJ`U!-g|4zU)GE}-u3r~$mojuL`$!J+z1^#4 zNs@7PdNkv=WJt~n4Kg#ji#~b3;XY*-{P)UH>6BQ+XZ*nGykj`Pxr#kP9^=aFxN1(s=5Q67+E66>{`i~^Qg4Z?%}iL${f1c`V=?203Pq+3 zb;439(mr+Cvt)8JfwA+bB;svv5o@KV_Yj%`1XT|b2WsMThOn*1jl)M=vT;Z zd|I^x(JwT)AMYk+{^8!BiZ5_I@*Y;2L(p?t5f+CzVfngNbSqqd>fmP>yy!gKjho>Y zlnT8m7qMqqZzpL_U+SfKVW365{M%(`yvUE`kb1onMqEUmc?Gg}4*+*@;Q7B2l&q}9 z^)mrjoqiFU3g7mnr7xpK23a#>BTD~QV*hG^e#M_{sZ5u ziV=SC3T7-Y!dEvF^7KE6=2R=H%T2|flkHI3=z)&Uim~IL5BhyEq{PvD2H{eIr-$9} zovtJOt3VnjlhHZ#B{mywM{dv8PQ3e04Sn7rYpo&Jj4n8qJ{Z-Uo7L|;?@g`?#6#K{8HgHQhmDDR z7SdD+`6H)f=Xq6q&RaTK58lDSs5Qq2Ar93zd))^~_P^0< zWDqJMTk+?|5%fLu4&w^_;nU$WlEg5apZOiT+x8;b>nA$+N5C?c^XWE(AoF5K@pyWo3U8lWg|ss^$SDN+{EW8EDhCPc zZs5lJj|j*ho*{|F$})!D*G{0rfg%hUdkR@049aOd?|C8xuCL}xGA!@m@}*=+`kW#Z zZ&`)IsSWU)umtTVc;1F{li%=NxA*(ylKhC9$Xcd~G0&Ch$)s>eK&Tn@AG#MS-KSH4 z>kVjz2sB{jeROeX#&Rn;I`*F5=gT>p>V7nuIiq&J#c2d*d_iDxHaOoO+z|oigwHrR z@(O;e_T|5OHIhDKR$)df|L(iE;n3?uWW?W;Y)P7dqSoHHmtBhcYlec)cZoCUvA$O^ zZd9K`8}|-Ru9T;BmfT})d=Y6ADI_;xYYM6{!Ix+qrD@f*_D82 zZIUx5Mxs`I14?{8gL4sac9}AbU3wEWYZWOfIac<3?jy&~$RnACCWnm6{eX5YssQ^wZv`O0et+~RpMn3!S zLd=D$DHs{J;v86`b$CNj%ICriI z_tHMfIzFMApE1MvDSqvmi1G9JzV1agj7-1eB=tX+R;M9gcV}9El{p0GNpZmV9 z*X#LQwGrzI)}nFoU|btIq@&9pxn~1Lm+<-Nw%hpA+Lh9zN%-ENM=Ja`S<6R_eoEVL z=F&Z=?Q6xVseETWRhRUXLNGe2*(vG8QrzAWg!^AE@qUdtIX~ocud9}1HB^tb{wJXz zR|Viw3#Q+Sa9S7K0Q33;)c-t+R0U(FzkQ86Tf6Pa8uXKbY z6e&<4QGxsnXe#O;w4|rZrmD+c!QAlzxp1){A@(?}`8*E4? zwH`ve!L!|eLt%MmIdbY(@Mru5{8?Mh=Y!|4j_2J!56eZLbWKulXhP&V1=9Y;S#N6! zuxZw7?jOFyX9ck+ICc?A?~;&uxB@zUCm>g$Mk8zL@u!P1Wj$|1Q3CH14AG`TguJhVn4}dcTxHhtDm>%XK2zOlh}MJBEa6 zQSLJ(nwG$`cJBO)+qVq6%fI25{&eh}R)KY+e55*yfVGeAqeSZpa+R{s&hv){-83m^ zVJ?hcamo@ixKJDxX5=)s!TSgEW;EB0>1<;Z&e z{a+%DA9({y&A;;5X9C)7+QA*Jn8Lr;&rTTgnNAP#ozFYH&D^Knn=^d%d5?ZsJ#w=d zgyySg9g~B(1xb(_@D7XY__M#O9!34viL+|#sg-9fd3O=DYAVDtpfoYti-IYbDU32= zvp$!R+LcWV`<@72n-;Md8y3*0Ha#XaoXeWOXj9Rzd-P~^l#udv0)-!%FAn^)iG6r? zTD0q&!H#tqC>BHSr%O%NVqK7i=)HM>XeB;FWRRiQws0Q1ttl_uO;u**ctF*rpvvbn*}|HmY+(3t zW+b1+KFvJMv&xQ4n8CSWc_*A|6n!LBl33w{ot`-C*e9C6&KlW1Y<%$`OoiZPCIsBO3=x%$sRbRA&FzHFQ!td%ZeRW-ffQyId% zri>C@hAv{NAydS*LT9>sc8AmD@JhkP!h!8gT_N_{wvvrdbQSq;C)?dMO4zIUgzWxR z(X;pd!iF_5v~XjDy#o+>I4_#!jYv3dgB# zv!2XT%eZfW62@=rV7hTWBa^vG4*%oTzz`d8O^6DAKgSE{k)6fjq^_hNI7W0=s21iw zA1PXou@DQb?+M?HLn$b1ZEN96@=4ii*^R17w$)A;2;|C-5tLmvF z@z_Mc&eaBQj>tp(*LEhkn#^RK@=Fnl_bTMLj3p+>xw?x7Z``BpPGQ2hzROtV z>jCWMe=At~jekxP<37{Ih~DC0k899aa}9q>?g}$wXRvJ_Ipg_iDlJRzDW=E&m7Mly z6IyydBF(4r()D3#jPtKMJYboduCaUUu()|^0^8PYop|AlKZ^+-C~7ROrJFi~1O@Z1 z0^gz2lUemr&G$`^=RIEIjM?HwyHb`nYpuArBng|hjHE8-E{b#Pd$XWKb@7Rgr^IVj z8_gZnRjjE{7PjqqAX28(YdHVC;1s-k{O4~hDL z&6q8@Pi5abGapZN$~_*(9o@EK(C~?3V2Oix%Iu)HV8JiyHc6Lz#TK(XvmY$pvmXY0 z^=I2W)kMe0bCNF>lZCg6pXqUHIBh&|hPiJW#2QZ((bhvtS7(4&XP5ih08dKtV9IqLtDDmb->UltgCO!z4 z2ECh0Mx3RR`ClFa-Us8OfjSNvWWbd3+$x`NmhOBtDt`VO<$a4$;a85A_vJ{&D3<-! z-%&`1ILV2UHUhT{c>nLYCiU7olq7i;6mQgnR&b_-*2Na+T{fU|37RyqA_I$hp2g0F zv$*Klz#TQW;oPJ~i)~XdDJB#BolVi-)|f8K8^H6zS~5=I91Y_BiylvKWs4g*P7I(K zTl{e|)k~_ZD>uAPfQ@w6kQuUGFJ%t($1rKQM0VyXt1?NOT~vctUVhjtaL$|ZMymhv zg0mQ}5Z`4<9FIxS%|8GSc1dx> z<-^cDX%|@+cSf^&EgrrqkZ|uCmF??}N#7<=w+-@Y%z;XLqbAQZT1un&CKV7lh^7-Y{MF z46gg{L2;!Tbx#k4a;JBQ$mBfDecF7sCt_qY&zpbthur~Fs=gJ4)xp*@x%X;}>uyG+ zD|D%;n)j4=zF^!TLt2_V6ZJWU6t|PJxc)OFooTJu@Lq+6hF9Uho~I~^Pls$blkN}( zh2H%C>HJLena*F6%Rw}rcA~nET}kF|x~%se1D16m8FQz1Zw-1RbtJhI3mCp#hO?`x z@mcF0I#s+ur|&Tc{Q3jtwjuDSuE2tjy(mk4j%)725%bK99$TM5561IcDyPxp--X6& zp2Qj*Yf8KvgG;G)WVn;?%d{``&wGUIc|GWj0e7`%ZzUPKcdOoF8dj%64+Hy?Tf{@S zEa2}0-~B!K(uuIbf@B`GPWoFAH2E2KP2HC6jN*>0E4hfX|Ao?`AdKaGP?yE`_Tu($9a5K7;aH-(Q+9f1(!bsn zn;X?>T+m(A^LfiZX&~IM_TsNki=rNx(4^y$xZ`gs%l}GukD5UEFh24bFks;(# zSdMRNb?J&pS6XngKM8l#DN@aZgs5W3ycDv0_(=Cc>^s?jyWXA1F)$Lns**9H&jZxA ztZ+IWwo=;VM3`ve;U=>7Cf8Ok+J95Cc3K{OnlByj&5CKPABlb9bzXR!taBL zkg<&K{Ok?gN^1(r-;A{inW%191&er9vN^+hB%_*exPsW#T%lcRJFvbx|Gc-xo&%81$mah7Cql}bZ zl)e5Lvg9l&((^BF{B|Vu&`-$r)+FwZpizr%z6iE!+41kSCW;oI*Np5_^m{9}{#2`gEdUbp&QvMWUr^Y03mL*jN>EVvUWfb?ZlAIeTPv3j>Kq2pW zPpjXLIRQ3gG)alG_ZHH_=3g+pK8P%M2WQ*wY$Q8Ig1f^cEWtr?mhNJ#-yZn;Wy5+y zTH5&$2DE8wQ3n@HmYK3Nt;e>t0UaD*$r3G+@n;H6HR#mYZzy~jj&Zivv1#&bY+n(9 z=CV{&d4K6(I(wb{CtVw@O(PYPrRzC6;q&(wxMLWG&0Z_`+}IE0dyVLOvoCgDdx5wK zmKZjl_iVL1aGLWO`yUR(^HLqEyW&npo2FB4FFq^XVoSY>3*f`Kcm^BZfx@&YG1w5Bkk+!S?r3g!KQ2!(GEMxFi^Bs>b11`Z)Ca^gwE8eI15tPD!I|_;XoK1991% z$zc2~SPfc9UZvcp+GbC`ZPsG4$0x)rdy5sCDzbhA?K@hGb1|GHw>1@;H~*I2Y$?U* zzjF9pDo2@yL3rBNgtX!>pm;pz74z@X=#r(7ahWjs@k z^J)DG)f^KT`%R=ReZ%43?Jt@T0;_yuQj5HUV%}TvmBzw%eFcvEeFf7iPoQY@l0W7fL?;h<}Fu zVB53{(x{c(8T4g0cgAt|?wnFIW)6{fcix2>Pe)11qipUN$dm9LDw*0`hE~E6DpOLX z!uiouz2C|4lyHu+To#b>#rt?$xRi7oUOD+~xq!&YeI$00KTcU&FG?O4uanlC%z=kE z7vuRJKQAdx+Vqg;i+`Vm)xa*KAw**9v7e~+7wBTL1)Uo_R&ws#EbJ|@Vtm&?NgIB! zVbh1Oud8-3w-`&d_URX4=rL#JkZmFQ*llMaZzqaVwiz?K*~7$Fi#oH}uJ34+(N5O3 zjo9e_R3zN}y9ZF=wQO$~K!lBzillr=q47&R!0o~}3|*}bJ1|66BFU-G|8_8*B8 zMptKpcWcG-2iDUftEl0R>of%AZ>QMSpN7KG+kUL*-WIC7;46e!pMZ_oLgB&3Rv|So9N7Js;N|jB814O9 z*l#EIF3PxHsHMDfSp!vsRH^@1jD==h~=xcFXYM^0ky$YfQ&)?{S*@wW}ce zd){xB>?#gsg;y`q#+VFpb8^M?L<4 z4>xoXPyF{yNVFlb@1%3Wj~{}#C?<|pEiIyc5@+@zPoEulA~4SLqM|{ENvF3mTcvzb z`f8La`(OT(j$HqsH}hB+XQLPA7b5uAH1_?)Y4P8f>FnIy&EogiS9D}~9P#`m%Xqhi z%FFzj|I5?j>DUQu@f{{UD{*9t3`#kt-j&@B$YI@{82|Us;C>ctLfsD}^qvIm z3qxqufnMUj;>Ofa3h7ycLodHl{)C%s-nC&Y&e5L@h+fB(y$=Yr|4XB|m+``f z9WC@>ei&Oez>96SI>9_F*06s^dy847daUH=bFr63Ep6-@AR0Mcr}iES!jIxQNufHcme>-~iXRtwxa zF6=lgCx))?ETVZnJ6cvLyinN2M!Op0*>z9xPcH94?*Abc^$n!3dx0XS5U{#{A*9r9 z#*D&t5NGzX%6p@j;fQKlXB)_7r7mKop+6;Onh&zc`_72l&X}>m=L1DkLqfe~wvb#tz3wO%sE+xg=6jqLPm{$F?DiXIaxdKgpi2QpK4T zc(E-XJ*8!kIZypF_tLc9+>y)ANALWP(x#>*kU2q*?_Gcf?Y_u8QGt`CA>ckmymUT; zA5Twq>|Z)Xn3B2OLzF($BfU^((jPugV*cy|3fdZRZlJf5j7L^LK|I zB1`S0bor%j=;j#cWa?(wF|&0Tdj>~uuEBxv?_sHtfr0vAFwQ%ZChLc!JLr;v24JqW zgw{83&ZX7}>V0Ap_~(ptvUhC^oS4RSf)M0txmQUh)-;%EMzF3>gIt8FI$dM-$_+-(N1~!+=+2|7a6SZp@c7cDWcAu6nZWona4J)#+NuN zK@z*{r*x5vwnUb(HknY5tTsD3@?QqTO^d-D;`HIJI;p*yNj)pmV1IM~&hDv!`-v_z zbD=uvME!@H7pX4OFH59hSztB~w7wot7R z*VgKg>Hd5;edWHSH}bTt_ci3De1hy=@>)rcuDcc>%Pb3%x+s$8+X#eyJkhZuQ(MHl z=nIN)*-D$v?^+C#$k8+_>^)4sJqCq4(ebu1Fxi)YtOI$Nc;gtv#kX*1Nk&?r^LeUW zzlfHeI)&KWakShxOlq7SPe&Cp5PkJDrC5y+`tP)$-vzOF{4Wusqj~S{dZsjH-DHxz zAM3OC)17${kTGgptCrEFENwcKw4EGwSd#uQd0ORp2Kg5T(4c%P@(C@)GwykjWkeQL z{KdI~H#qL|9@Pbv;0{_$I&MM3mDMPS%qi?^A?$3wW2)C58xuj3y0+gx! z?(+`cbXeOBtTa%fQ``SRd|^hiEWp@D+LTRC;ZvhQ9%20bOR=Nw4?58u&Z%E>VLH7W zPy*R}@Lx|iyzlEk7GpT?U`akW%Mf~oZ#we7oD*%OUDVI5~bjm&a zv~i=)DQ?&>=@y>fy8xMcIV^y?nD+j}zSR}5eEJeEXZfHqa0;pa%)|U(BQmTzkES)l zX$HT~S{8I7nFH0Tj%Rgm{>JWky-3EMUOG7!b|ZNAGdTjbUz;)K#e1AAxWRX~|FC$+ zcZ4T$zUbc+>^N#k0ko3-_&88_^$zMMRi?B4+ySBF;1i%RjkL^yY?k)O%?s(mpPQ+&(=jUZ+K;{J)~(G&XlW14x6xhzurDuJ&U}EcFI<|z*r_S}K z=Hoi_^yDRs*ueKhVP$uXdZbE;`J=ZGIULfHzbeyDbkM| zSIM-{VUpuPDr)c7~E$89lpfp0v;#H@sld~xeByBAQv8YUSaM(O@faVssC<6%shFTQTGD3 ztIO~wt_u4`V>5un({x_0j zjK921r!e9~8DdUW;D3vh5$lqI+?PDxG5eCVB7i%#IFG`nr5|S1=#$(DUtEfCre!{x zNVeZu%e&#Sd`WO|XR78oHigAA>7zjq7L4spH%62@$@t#&pQ7MWB1el(^^~q?kCCj3 zwxK(1$*7zbPPQ|xD5TSTs{0p04-KzUyxtz_UlkxhsJgVLyauxD6sYF!B}~uAz^;J` z6n^$K;x)!VGZUEZG8L_d*TD9SE1fX`&}dJtPj7Ve>2GI!$Bz3$Y1Kf^TIQTT+4D>0 zt+u~Z*Wnkh>X8rOn+cKaG>Y*vBz`ZU`_2laB2=KPs2;2J6LF!-P0Vihk?Pi#!8A$* z2jc3`cX*9-uWcT-Z1U*v$p=3>hU50nIY(xS)GlcxZ98+;scYyG%6Rz(2XBm^53y0w zQ#rmA)%g}aJx}S_-OGCF@n!kA`KT3Dz0O0tU`f~iTtkqTHia*U!}DrYQjbo8%l> zCG*^JY_-v#Tm5n&tmjs^;;yGQbc_9e zE{ETUTe#(;HU*UF}m$PT`D@ zOC=a&u137Fk7HTN)YbAn?uZ&R)%__}eo*VkbIVuC>6F-O{hnoVt9NgMxWP3=-vMvhHp&5?oAf7?lI3%%>IZm zukT}Q|IZln=oXBs6zKe)SlGWZBdfu{Z8zRU@XkWTDLMKXk`A{Vee!KSgVdW{NH#Oo zj(dxqJNa*eTp_Nf1WR}8e#MK!<%sLc-)F@H?#l~7^9>2sC||~#ibb$gNyDgnJ}B(U z`2ZJ}!#d&(WM@*nMjxCVzXA3;HAr`GGSoPy&S`W64Ax%8>rzd~X6UMk1Bo(PVA9BE znaT{iVMsC`}=Ld8-C&-w;&VG;3#-DdH zszza8-~+UFT>xprb^Nj5`*GfjjyZS%Lvy)PuW>3hE_r~R9%F^dF;!BJ&b>v8{da{4 zx>3?K7j?yc|CTbVnu(%ng8{oNxh|SEJ{OcHMT@#O*NN>Jt&-%B<-*$X`&6H5z)arj zps!XtWcS94={uP1`0HZb6$`dCvRYc7pDgLW)|**;(vfzb=gvMymC&bZHJ1Bm z14WOJXBKB~l3O1)`Y3mW-8yq$$WJS0$?G(T=XCjAc^6A;%93Oa)L<)J6 zMNNtj3N+1`!k(qV*~Z)K?TNK?9uJs~-9Q#6S3)fT{!m`ENjT6vOf)@}Kw~~G6}DS; z>~&eVZ+~I2dl2(`H$#ju+sL+l9VV!)J;cWKb7bw7>g?CrE!-K_O`I-oPWrzNiP8Dx zLg=vHLg+p>c(`haOGf%I?;~r)h(tejS)sGYcTM8$E$Y+@X=1DyrF9lu#Q5`C;=#wZ zLgFIlC6)3kRLGHPEBJ5|(1KhRu()V)LhE(b-Oe(xz#55tm#K6Nf5Th)OTw#rJow zQ~c__B4>oM`#RbpXX%lnk%zeNy}kG%v|3Ovo>8)kS4Q>MB|?MIB`Ebj3_&1WZ(dt-%yuj(wd>v(!R%$sf1pUmDFOR2br zKlN&>O?#kZE$%B)W#hDGiq@X{n9B_Q*=@Z*zf;@OoQHH5|HPRwr8PGMqb=F2e1bI_ zq)@?PW`wd$pPgBwn>|1{W$xQzf6#O%m70Q1WjN_iM84>bA*tCT# z@UAxFj88!^^o#6{DyZ~2Lf$zSgdQ7=SlNJf;r*mDlyp&!uFmNpcFX9^M(Ljv5BA8y z#G#dfk>wX`VFMsdtTDR*ZQz~{hhYUyNO>vb57EQ2=U60LgDh; zW+Akn9BmtKNc~MVvny9x1#>SO?#db~-uH46*8R^`-ZGDmJ zT?W;r0$3kBi-Av$LH>yy35A^Lq|}wNl=r~uBw>P;D&6vZg11(~Fw7wuvU#%XJfZhU zib48oanZYvQ+6!(PiWu7guv87qMOZZ)g^bl9rWsPpYiKJ}f6``pN5ZdbZ&mxEp#@?m*jl`J%iF)h0ixzEQVe8O{3Ck>httxRpc zJmV|gL&nP$(D~j6daL*xB6t9Da{j_CdQ?i2ZixTX8 zw>Q<&jQ2l#C1d!3XBhr69=RiOI(!^^UOmR$70F1s+6GxKE%*uFX*}ThtwXQyW;XxR zs|vGg&ml6i7P&T8P}K4cuLk&IVUO~TyNg&;&R@g+4oA|FJpoYVyp#Hha70B^!{Ks1 z)I4)}p8O~x9Mj;wgR^Aq_#QS}gKp$sLTw*K@=2G%Wn~1)hCRTP&oNM$UWuftDilso zrB5shgFimO-jl)5O)G*;*d@rkrL}$x-~J`wko#$@U+@LmlTU-QH%awVHfoYeaAQ;g z%ufD*_~>Xy*4E@l9A0|y9oFkY9GW$lewrMHJm&;GjMU`*TO;zPAY6UG|Gh_64uANU z_xDtdX#K`vWK)p|)$KRa8u@&IcK|R^u`_-49!+5(v8m~DnsjOEVPwk%V$07LaM|jI zsZ+hNqOA%c<$Uiq?kQSAi%=G94NuO2`{aBaDhayyUa}QaM{a@qel3aYZXsj7xK3G& z(_0m3`T_3eoL`Cdz{w~aSBXzAf~2yHnT*NP^#tP_o-@cSD8+JPT^jT(5dV4mA$e>V zyp;}Pg6aqC{*;9yS#MzUjdifdi{A6IiKHjZceI7%BWEDo2@erw&(^%8>-zYN*6F z*JdaVdWk-JxpQ>ZOKcnG10kXntmrcGH1+F3y4Q+`ZptLB ze}*!R0PJ~x5HB=8gRj6x#9z301jB^h`&QiV=O7*PZUm$HO;AThjb)Ez%}uI*r*im|ncQ z*U>>p+J6T#-~7XPn`h__RcaY_6UJNlKK@e}2H1aqclIkd@BD{RK_{H{Zsq=yhhNj2 zMz_Gd^eO@q4N2I20hQDLA;jSn9_~`0kUnqlJeubQD|f(|yJ1F$@LgwPXIiMjaECL_ zl2p7qIws?W$iaEvbJ+e*PnsC4OZoQpXyDF`!{&FfY2-`rE*&Ovck~B~D;+!dsii!3 zs-#ZuFE{dCoIF)Dr*VE8zi)GP40p%F^XDTdt>ts9zT8#IpKEHo%QNCh116+DLfAD` zTCyUQKO6OF;dY)G=iQp$gT2AeCHzilD=jVlf#L(M9rKG}?q)QkJr%!SzeV^X{(0W1 zNq0PXpQtj6=d>H3+&2xfd~!|nTUhM2CanZ5^4>O(f^BYNn&l6StMtW_lSVXZ%R`KM zr$d#RQq&DHAm{Gs9WJ8rL(k*>NbXACU542aji|gi9>W)PqM6O17&&h|HE9(gVSInO zpi;+O?F;Gf{WDmz>NIsTkCg`Q8BK|w4@&YbPoVnlaoFEUB$pu$^q^_Fw5jVUK8HOF zlNk#1jx(p4V`m^`p1LeAjF!325HVJj8v7~J(VPHGNc(`VKW<~=wp#EV49W7Arz(^v zGw(b)Cvg|o)po2rcL|Oy>u8>;GNoszP}ZMn3}`xt`?j4(cE^~~xdzj}U&o|=|8O~^ z6mZvc&5RDjlr|08So^nwZC5muGnzhBVqwh9c6lp`KYoW!cP~t6jK;4bHCnGw3VH4VI@JFw1{Zgw`up5%mS2g7Y0YSI z;SQE(1vnG)3cGS%NK1b5@AZ8RYJRFjr^B-0`(+>To~YBLn9-CvQ;qnU7!sit#VO7_ zjdT#QymhxCcj+T}BkF(6Lu&HEoNVqSply3kYMNk<<{FERxpQW@68)U;xPwvl&bB*@ z=kwpG0T+>|_6O{YKl~&nG_Hxyka_R_uKNWXls-qU)p->B<)7EjMIEmD9xplbjyv6_ z+ZChW(Ldz;y^m2YV@Puw=O3me!7k=3D{IP literal 0 HcmV?d00001 From 7e21282dece5c2df76b9613d1c65a4522f3b9904 Mon Sep 17 00:00:00 2001 From: agelonch Date: Tue, 13 May 2014 13:31:08 +0200 Subject: [PATCH 09/15] Code CRC Reshaped to libLTE format --- lte/include/lte/fec/crc.h | 30 ++++++++- lte/lib/fec/src/crc.c | 117 ++++++++++++++++-------------------- lte/lib/fec/test/crc_test.c | 15 +++-- lte/lib/fec/test/crc_test.h | 2 + 4 files changed, 90 insertions(+), 74 deletions(-) diff --git a/lte/include/lte/fec/crc.h b/lte/include/lte/fec/crc.h index 010122973..dd9b58ef6 100644 --- a/lte/include/lte/fec/crc.h +++ b/lte/include/lte/fec/crc.h @@ -36,8 +36,34 @@ #define LTE_CRC8 0x19B -int init_crc(int lorder, unsigned long polynom); +#define _WITHMALLOC + +#ifndef _WITHMALLOC +#define MAX_LENGTH 1024*16 +#endif + +typedef struct { + unsigned long table[256]; +#ifdef _WITHMALLOC + unsigned char *data0; +#else + unsigned char data0[MAX_LENGTH]; +#endif + int polynom; + int order; + unsigned long crcinit; + unsigned long crcxor; + unsigned long crcmask; + unsigned long crchighbit; + unsigned int crc_out; +} crc_t; + +//ELIMINATE//////////////////// unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc,unsigned int poly, int paste_word); + int long_crc, unsigned int poly, int paste_word); +/////////////////////////////// + +int crc_init(crc_t *crc_par); +unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params); #endif diff --git a/lte/lib/fec/src/crc.c b/lte/lib/fec/src/crc.c index 3cbd1ad11..73c87d14d 100644 --- a/lte/lib/fec/src/crc.c +++ b/lte/lib/fec/src/crc.c @@ -31,116 +31,103 @@ #include #include "lte/utils/pack.h" -#define _WITHMALLOC +#include "lte/fec/crc.h" -unsigned int cword; -char *cdata; -const unsigned long crcinit = 0x00000000; //initial CRC value -const unsigned long crcxor = 0x00000000; //final XOR value -unsigned long crcmask; -unsigned long crchighbit; -unsigned long crctab[256]; +void gen_crc_table(crc_t *crc_params) { - - -void gen_crc_table(int lorder, unsigned long poly) { - - int i, j, ord=(lorder-8); + int i, j, ord=(crc_params->order-8); unsigned long bit, crc; for (i=0; i<256; i++) { crc = ((unsigned long)i)<crchighbit; crc<<= 1; - if (bit) crc^= poly; + if (bit) crc^= crc_params->polynom; } - crctab[i] = crc & crcmask; + crc_params->table[i]=crc & crc_params->crcmask; } } -unsigned long crctable (unsigned char* data, unsigned long length, int lorder) { +unsigned long crctable (unsigned long length, crc_t *crc_params) { - // Polynom lorders of 8, 16, 24 or 32. - unsigned long crc = crcinit; + // Polynom order 8, 16, 24 or 32 only. + int ord=crc_params->order-8; + unsigned long crc = crc_params->crcinit; + unsigned char* data = crc_params->data0; - while (length--) crc = (crc << 8) ^ crctab[ ((crc >> (lorder-8)) & 0xff) ^ *data++]; - - return((crc^crcxor)&crcmask); + while (length--){ + crc = (crc << 8) ^ crc_params->table[ ((crc >> (ord)) & 0xff) ^ *data++]; + } + return((crc ^ crc_params->crcxor) & crc_params->crcmask); } -unsigned long reversecrcbit(unsigned int crc, unsigned int polynom, int lorder, int nbits) { +unsigned long reversecrcbit(unsigned int crc, int nbits, crc_t *crc_params) { unsigned long m, rmask=0x1; for(m=0; m>1; + if((rmask & crc) == 0x01 )crc = (crc ^ crc_params->polynom)>>1; else crc = crc >> 1; } - return((crc^crcxor)&crcmask); + return((crc ^ crc_params->crcxor) & crc_params->crcmask); } -int init_crc(int lorder, unsigned long polynom){ - - unsigned long polyhighbit; +int crc_init(crc_t *crc_par){ // Compute bit masks for whole CRC and CRC high bit - crcmask = ((((unsigned long)1<<(lorder-1))-1)<<1)|1; - polyhighbit=0xFFFFFFFF ^ (crcmask+1); - crchighbit = (unsigned long)1<<(lorder-1); + crc_par->crcmask = ((((unsigned long)1<<(crc_par->order-1))-1)<<1)|1; + crc_par->crchighbit = (unsigned long)1<<(crc_par->order-1); - // Eliminate highest bit in polynom word - polynom=polynom & polyhighbit; + printf("crcmask=%x, crchightbit=%x\n", + (unsigned int)crc_par->crcmask, (unsigned int)crc_par->crchighbit); // check parameters - if (lorder < 1 || lorder > 32) { - printf("ERROR, invalid order, it must be between 1..32.\n"); + if (crc_par->order%8 != 0) { + printf("ERROR, invalid order=%d, it must be 8, 16, 24 or 32.\n", crc_par->order); return(0); } - if (lorder%8 != 0) { - printf("ERROR, invalid order=%d, it must be 8, 16, 24 or 32.\n", lorder); - return(0); - } - if (polynom != (polynom & crcmask)) { - printf("ERROR, invalid polynom.\n"); - return(0); - } - if (crcinit != (crcinit & crcmask)) { + + if (crc_par->crcinit != (crc_par->crcinit & crc_par->crcmask)) { printf("ERROR, invalid crcinit.\n"); return(0); } - if (crcxor != (crcxor & crcmask)) { + if (crc_par->crcxor != (crc_par->crcxor & crc_par->crcmask)) { printf("ERROR, invalid crcxor.\n"); return(0); } // generate lookup table - gen_crc_table(lorder, polynom); + gen_crc_table(crc_par); return(1); } -#define MAX_LENGTH 8192 - +///ELIMINATE////////////////////////// unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc, unsigned int poly, int paste_word) { + int long_crc, unsigned int poly, int paste_word){ + return(0); +} +/////////////////////////////////////// + + + +unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params) { int i, len8, res8, a; -#ifdef _WITHMALLOC - char *data0, *pter; + unsigned int crc; + char *pter; - data0 = (char *)malloc(sizeof(char) * (len+long_crc)*2); - if (!data0) { +#ifdef _WITHMALLOC + crc_params->data0 = (unsigned char *)malloc(sizeof(*crc_params->data0) * (len+crc_params->order)*2); + if (!crc_params->data0) { perror("malloc ERROR: Allocating memory for data pointer in crc() function"); return(-1); } -#endif -#ifndef _WITHMALLOC - char data0[MAX_LENGTH], *pter; - - if((((len+long_crc)>>3) + 1) > MAX_LENGTH){ +#else + if((((len+crc_params->order)>>3) + 1) > MAX_LENGTH){ printf("ERROR: Not enough memory allocated\n"); return(-1); } @@ -156,24 +143,22 @@ unsigned int crc(unsigned int crc, char *bufptr, int len, for(i=0; idata0[i]=(unsigned char)(unpack_bits(&pter, 8)&0xFF); } // Calculate CRC - pter=data0; - crc=crctable ((unsigned char *)pter, len8+a, long_crc); + crc=crctable(len8+a, crc_params); // Reverse CRC res8 positions - if(a==1)crc=reversecrcbit(crc, poly, long_crc, res8); + if(a==1)crc=reversecrcbit(crc, res8, crc_params); // Add CRC pter=(char *)(bufptr+len); - pack_bits(crc, &pter, long_crc); + pack_bits(crc, &pter, crc_params->order); #ifdef _WITHMALLOC - free(data0); - data0=NULL; + free(crc_params->data0); + crc_params->data0=NULL; #endif //Return CRC value return crc; } - diff --git a/lte/lib/fec/test/crc_test.c b/lte/lib/fec/test/crc_test.c index c1eeba49b..28c389002 100644 --- a/lte/lib/fec/test/crc_test.c +++ b/lte/lib/fec/test/crc_test.c @@ -77,6 +77,7 @@ int main(int argc, char **argv) { int i; char *data; unsigned int crc_word, expected_word; + crc_t crc_p; parse_args(argc, argv); @@ -96,22 +97,24 @@ int main(int argc, char **argv) { data[i] = rand()%2; } - //Initialize crc params and tables - if(!init_crc(crc_length, crc_poly))exit(0); + //Initialize CRC params and tables + crc_p.polynom=crc_poly; + crc_p.order=crc_length; + crc_p.crcinit=0x00000000; + crc_p.crcxor=0x00000000; + if(!crc_init(&crc_p))exit(0); // generate CRC word - crc_word = crc(0, data, num_bits, crc_length, crc_poly, 1); + crc_word = crc_attach(data, num_bits, &crc_p); // check if result is zero - if (crc(0, data, num_bits + crc_length, crc_length, crc_poly, 0)) { + if (crc_attach(data, num_bits + crc_length, &crc_p)) { printf("CRC check is non-zero\n"); exit(-1); } free(data); - printf("CRC word: 0x%x\n", crc_word); - // check if generated word is as expected if (get_expected_word(num_bits, crc_length, crc_poly, seed, &expected_word)) { fprintf(stderr, "Test parameters not defined in test_results.h\n"); diff --git a/lte/lib/fec/test/crc_test.h b/lte/lib/fec/test/crc_test.h index 164032b7b..54c7a8283 100644 --- a/lte/lib/fec/test/crc_test.h +++ b/lte/lib/fec/test/crc_test.h @@ -39,11 +39,13 @@ typedef struct { static expected_word_t expected_words[] = { +//ELIMINATE/////////// /* {5000, 24, LTE_CRC24A, 1, 0x4D0836}, // LTE CRC24A (36.212 Sec 5.1.1) {5000, 24, LTE_CRC24B, 1, 0x9B68F8}, // LTE CRC24B {5000, 16, LTE_CRC16, 1, 0xBFFA}, // LTE CRC16: 0xBFFA {5000, 8, LTE_CRC8, 1, 0xF8}, // LTE CRC8 0xF8 */ +////////////////////// {5001, 24, LTE_CRC24A, 1, 0x1C5C97}, // LTE CRC24A (36.212 Sec 5.1.1) {5001, 24, LTE_CRC24B, 1, 0x36D1F0}, // LTE CRC24B {5001, 16, LTE_CRC16, 1, 0x7FF4}, // LTE CRC16: 0x7FF4 From 7972076b0e623d1fcc2b72e05012eb94bcbeb626 Mon Sep 17 00:00:00 2001 From: agelonch Date: Tue, 13 May 2014 13:41:10 +0200 Subject: [PATCH 10/15] Code CRC Reshaped to libLTE format --- lte/include/lte/fec/crc.h | 4 ---- lte/lib/fec/src/crc.c | 8 -------- lte/lib/fec/test/crc_test.h | 8 +------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/lte/include/lte/fec/crc.h b/lte/include/lte/fec/crc.h index dd9b58ef6..fe35f992c 100644 --- a/lte/include/lte/fec/crc.h +++ b/lte/include/lte/fec/crc.h @@ -58,10 +58,6 @@ typedef struct { unsigned int crc_out; } crc_t; -//ELIMINATE//////////////////// -unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc, unsigned int poly, int paste_word); -/////////////////////////////// int crc_init(crc_t *crc_par); unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params); diff --git a/lte/lib/fec/src/crc.c b/lte/lib/fec/src/crc.c index 73c87d14d..b74569d9e 100644 --- a/lte/lib/fec/src/crc.c +++ b/lte/lib/fec/src/crc.c @@ -105,14 +105,6 @@ int crc_init(crc_t *crc_par){ return(1); } -///ELIMINATE////////////////////////// -unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc, unsigned int poly, int paste_word){ - return(0); -} -/////////////////////////////////////// - - unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params) { diff --git a/lte/lib/fec/test/crc_test.h b/lte/lib/fec/test/crc_test.h index 54c7a8283..e52faeaf4 100644 --- a/lte/lib/fec/test/crc_test.h +++ b/lte/lib/fec/test/crc_test.h @@ -39,13 +39,7 @@ typedef struct { static expected_word_t expected_words[] = { -//ELIMINATE/////////// - /* {5000, 24, LTE_CRC24A, 1, 0x4D0836}, // LTE CRC24A (36.212 Sec 5.1.1) - {5000, 24, LTE_CRC24B, 1, 0x9B68F8}, // LTE CRC24B - {5000, 16, LTE_CRC16, 1, 0xBFFA}, // LTE CRC16: 0xBFFA - {5000, 8, LTE_CRC8, 1, 0xF8}, // LTE CRC8 0xF8 - */ -////////////////////// + {5001, 24, LTE_CRC24A, 1, 0x1C5C97}, // LTE CRC24A (36.212 Sec 5.1.1) {5001, 24, LTE_CRC24B, 1, 0x36D1F0}, // LTE CRC24B {5001, 16, LTE_CRC16, 1, 0x7FF4}, // LTE CRC16: 0x7FF4 From caff6d85ad0beb0f62ed9df8f14e4649e63e3d31 Mon Sep 17 00:00:00 2001 From: ismagom Date: Tue, 13 May 2014 16:00:49 +0100 Subject: [PATCH 11/15] Added Turbo Decoder --- examples/pbch_enodeb.c | 8 +++---- examples/pbch_ue.c | 8 +++---- examples/scan_rssi.c | 8 +++---- examples/synch_file.c | 12 +++++------ lte/include/lte.h | 3 +++ lte/include/lte/common/base.h | 5 +++++ lte/lib/common/src/lte.c | 37 +++++++++++++++++++++++++++++++++ lte/lib/fec/test/CMakeLists.txt | 12 +++++++++++ lte/lib/fec/test/viterbi_test.c | 12 +++++------ 9 files changed, 81 insertions(+), 24 deletions(-) diff --git a/examples/pbch_enodeb.c b/examples/pbch_enodeb.c index 21a10b514..4cf26bb68 100644 --- a/examples/pbch_enodeb.c +++ b/examples/pbch_enodeb.c @@ -39,7 +39,7 @@ #endif char *output_file_name = NULL; -int nof_slots=-1; +int nof_frames=-1; int cell_id = 1; int nof_prb = 6; char *uhd_args = ""; @@ -66,7 +66,7 @@ void usage(char *prog) { printf("\t UHD is disabled. CUHD library not available\n"); #endif printf("\t-o output_file [Default USRP]\n"); - printf("\t-n number of frames [Default %d]\n", nof_slots); + printf("\t-n number of frames [Default %d]\n", nof_frames); printf("\t-c cell id [Default %d]\n", cell_id); printf("\t-p nof_prb [Default %d]\n", nof_prb); printf("\t-v [set verbose to debug, default none]\n"); @@ -92,7 +92,7 @@ void parse_args(int argc, char **argv) { output_file_name = argv[optind]; break; case 'n': - nof_slots = atoi(argv[optind]); + nof_frames = atoi(argv[optind]); break; case 'p': nof_prb = atoi(argv[optind]); @@ -238,7 +238,7 @@ int main(int argc, char **argv) { nf = 0; - while(nf= 0 && index < NOF_TC_CB_SIZES) { + return tc_cb_sizes[index]; + } else { + return -1; + } +} + +int lte_find_cb_index(int long_cb) { + int j = 0; + while (j < NOF_TC_CB_SIZES && tc_cb_sizes[j] < long_cb) { + j++; + } + + if (j == NOF_TC_CB_SIZES) { + return -1; + } else { + return j; + } +} + const int lte_symbol_sz(int nof_prb) { if (nof_prb<=0) { return -1; diff --git a/lte/lib/fec/test/CMakeLists.txt b/lte/lib/fec/test/CMakeLists.txt index 72768b3b2..2428a2d34 100644 --- a/lte/lib/fec/test/CMakeLists.txt +++ b/lte/lib/fec/test/CMakeLists.txt @@ -19,6 +19,18 @@ # and at http://www.gnu.org/licenses/. # + +######################################################################## +# Turbo Coder TEST +######################################################################## +ADD_EXECUTABLE(turbocoder_test turbocoder_test.c) +TARGET_LINK_LIBRARIES(turbocoder_test lte) + +ADD_TEST(turbocoder_test_504_1 turbocoder_test -n 100 -s 1 -l 504 -e 1.0 -t) +ADD_TEST(turbocoder_test_504_2 turbocoder_test -n 100 -s 1 -l 504 -e 2.0 -t) +ADD_TEST(turbocoder_test_6114_1_5 turbocoder_test -n 100 -s 1 -l 6144 -e 1.5 -t) +ADD_TEST(turbocoder_test_known turbocoder_test -n 1 -s 1 -k -e 0.5) + ######################################################################## # Viterbi TEST ######################################################################## diff --git a/lte/lib/fec/test/viterbi_test.c b/lte/lib/fec/test/viterbi_test.c index e7ee9e92c..76dd14754 100644 --- a/lte/lib/fec/test/viterbi_test.c +++ b/lte/lib/fec/test/viterbi_test.c @@ -39,7 +39,7 @@ typedef _Complex float cf_t; -int frame_length = 1000, nof_slots = 128; +int frame_length = 1000, nof_frames = 128; float ebno_db = 100.0; unsigned int seed = 0; bool tail_biting = false; @@ -54,7 +54,7 @@ int K = -1; void usage(char *prog) { printf("Usage: %s [nlestk]\n", prog); - printf("\t-n nof_frames [Default %d]\n", nof_slots); + printf("\t-n nof_frames [Default %d]\n", nof_frames); printf("\t-l frame_length [Default %d]\n", frame_length); printf("\t-e ebno in dB [Default scan]\n"); printf("\t-s seed [Default 0=time]\n"); @@ -67,7 +67,7 @@ void parse_args(int argc, char **argv) { while ((opt = getopt(argc, argv, "nlstek")) != -1) { switch (opt) { case 'n': - nof_slots = atoi(argv[optind]); + nof_frames = atoi(argv[optind]); break; case 'l': frame_length = atoi(argv[optind]); @@ -254,7 +254,7 @@ int main(int argc, char **argv) { for (j = 0; j < NTYPES; j++) { errors[j] = 0; } - while (frame_cnt < nof_slots) { + while (frame_cnt < nof_frames) { /* generate data_tx */ for (j = 0; j < frame_length; j++) { @@ -291,7 +291,7 @@ int main(int argc, char **argv) { } frame_cnt++; printf("Eb/No: %3.2f %10d/%d ", - SNR_MIN + i * ebno_inc,frame_cnt,nof_slots); + SNR_MIN + i * ebno_inc,frame_cnt,nof_frames); for (n=0;n<1+ncods;n++) { printf("BER: %.2e ",(float) errors[n] / (frame_cnt * frame_length)); } @@ -324,7 +324,7 @@ int main(int argc, char **argv) { } if (snr_points == 1) { - int expected_errors = get_expected_errors(nof_slots, + int expected_errors = get_expected_errors(nof_frames, seed, frame_length, K, tail_biting, ebno_db); if (expected_errors == -1) { fprintf(stderr, "Test parameters not defined in test_results.h\n"); From bcdf2f088653feb57ce785ebfb7129eca95654b6 Mon Sep 17 00:00:00 2001 From: ismagom Date: Tue, 13 May 2014 16:03:39 +0100 Subject: [PATCH 12/15] Added Turbo Decoder --- lte/include/lte/fec/tc_interl.h | 41 ++++ lte/include/lte/fec/turbocoder.h | 45 ++++ lte/include/lte/fec/turbodecoder.h | 44 ++++ lte/include/lte/fec/turbodecoder.h2 | 68 ++++++ lte/lib/fec/src/tc_interl_lte.c | 117 ++++++++++ lte/lib/fec/src/tc_interl_umts.c | 257 ++++++++++++++++++++++ lte/lib/fec/src/turbocoder.c | 134 ++++++++++++ lte/lib/fec/src/turbodecoder.c | 299 +++++++++++++++++++++++++ lte/lib/fec/src/turbodecoder.c2 | 304 ++++++++++++++++++++++++++ lte/lib/fec/test/turbocoder_test.c | 324 ++++++++++++++++++++++++++++ lte/lib/fec/test/turbocoder_test.h | 168 +++++++++++++++ 11 files changed, 1801 insertions(+) create mode 100644 lte/include/lte/fec/tc_interl.h create mode 100644 lte/include/lte/fec/turbocoder.h create mode 100644 lte/include/lte/fec/turbodecoder.h create mode 100644 lte/include/lte/fec/turbodecoder.h2 create mode 100644 lte/lib/fec/src/tc_interl_lte.c create mode 100644 lte/lib/fec/src/tc_interl_umts.c create mode 100644 lte/lib/fec/src/turbocoder.c create mode 100644 lte/lib/fec/src/turbodecoder.c create mode 100644 lte/lib/fec/src/turbodecoder.c2 create mode 100644 lte/lib/fec/test/turbocoder_test.c create mode 100644 lte/lib/fec/test/turbocoder_test.h diff --git a/lte/include/lte/fec/tc_interl.h b/lte/include/lte/fec/tc_interl.h new file mode 100644 index 000000000..1fbaa8293 --- /dev/null +++ b/lte/include/lte/fec/tc_interl.h @@ -0,0 +1,41 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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/. + * + */ + +#ifndef _PERMUTE_H +#define _PERMUTE_H + +typedef struct { + int *forward; + int *reverse; +}tc_interl_t; + +int tc_interl_LTE_init(tc_interl_t *h, int long_cb); +int tc_interl_UMTS_init(tc_interl_t *h, int long_cb); + +void tc_interl_free(tc_interl_t *h); + +#endif diff --git a/lte/include/lte/fec/turbocoder.h b/lte/include/lte/fec/turbocoder.h new file mode 100644 index 000000000..ea215972d --- /dev/null +++ b/lte/include/lte/fec/turbocoder.h @@ -0,0 +1,45 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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/. + * + */ + + +#define NUMREGS 3 + +#define RATE 3 +#define TOTALTAIL 12 + +typedef struct { + int long_cb; + tc_interl_t interl; + +}tcod_t; + +int tcod_init(tcod_t *h, int long_cb); +void tcod_free(tcod_t *h); +void tcod_encode(tcod_t *h, char *input, char *output); + + + diff --git a/lte/include/lte/fec/turbodecoder.h b/lte/include/lte/fec/turbodecoder.h new file mode 100644 index 000000000..fc97de5cd --- /dev/null +++ b/lte/include/lte/fec/turbodecoder.h @@ -0,0 +1,44 @@ +#define RATE 3 +#define TOTALTAIL 12 + +#define LOG18 -2.07944 + +#define NUMSTATES 8 +#define NINPUTS 2 +#define TAIL 3 +#define TOTALTAIL 12 + +#define INF 9e4 +#define ZERO 9e-4 + +#define MAX_LONG_CB 6114 +#define MAX_LONG_CODED (RATE*MAX_LONG_CB+TOTALTAIL) + +typedef float llr_t; + +typedef struct { + int long_cb; + llr_t *beta; +}map_gen_t; + +typedef struct { + int long_cb; + + map_gen_t dec; + + llr_t *llr1; + llr_t *llr2; + llr_t *w; + llr_t *syst; + llr_t *parity; + + tc_interl_t interleaver; +}tdec_t; + +int tdec_init(tdec_t *h, int long_cb); +void tdec_free(tdec_t *h); + +void tdec_reset(tdec_t *h); +void tdec_iteration(tdec_t *h, llr_t *input); +void tdec_decision(tdec_t *h, char *output); +void tdec_run_all(tdec_t *h, llr_t *input, char *output, int nof_iterations); diff --git a/lte/include/lte/fec/turbodecoder.h2 b/lte/include/lte/fec/turbodecoder.h2 new file mode 100644 index 000000000..003cfbbde --- /dev/null +++ b/lte/include/lte/fec/turbodecoder.h2 @@ -0,0 +1,68 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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/. + * + */ + +#define RATE 3 +#define TOTALTAIL 12 + +#define LOG18 -2.07944 + +#define NUMSTATES 8 +#define NINPUTS 2 +#define TOTALTAIL 12 + +#define SCALE 80 + +#define INF 9e4 +#define ZERO 9e-4 + +#define MAX_LONG_CB 6114 +#define MAX_LONG_CODED (RATE*MAX_LONG_CB+TOTALTAIL) + +typedef float llr_t; + +typedef struct { + int long_cb; + int max_iterations; + int halt_threshold; + enum { HALT_MIN = 2, HALT_MEAN = 1, HALT_NONE = 0} halt_mode; + llr_t alfa[NUMSTATES]; + llr_t beta[(MAX_LONG_CB + 1) * NUMSTATES]; + llr_t LLR1[MAX_LONG_CB + TOTALTAIL]; + llr_t LLR2[MAX_LONG_CB + TOTALTAIL]; + llr_t W[MAX_LONG_CB + TOTALTAIL]; + llr_t data[RATE*(MAX_LONG_CB + TOTALTAIL)]; + llr_t *parity; + struct permute_t permuta; + int iteration; + int HALT_min; + +}tdec_t; + +int tdec_init(tdec_t *h); + +int turbo_decoder(tdec_t *h, llr_t *input, char *output, int *halt); + diff --git a/lte/lib/fec/src/tc_interl_lte.c b/lte/lib/fec/src/tc_interl_lte.c new file mode 100644 index 000000000..1db75cf92 --- /dev/null +++ b/lte/lib/fec/src/tc_interl_lte.c @@ -0,0 +1,117 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte/common/base.h" +#include "lte/fec/tc_interl.h" +#include "lte/fec/turbocoder.h" +#include "lte/utils/debug.h" + +/************************************************ + * + * LTE TURBO CODE INTERLEAVER + * + ************************************************/ + +const int f1_list[NOF_TC_CB_SIZES] = { 3, 7, 19, 7, 7, 11, 5, 11, 7, 41, 103, 15, 9, 17, + 9, 21, 101, 21, 57, 23, 13, 27, 11, 27, 85, 29, 33, 15, 17, 33, 103, 19, + 19, 37, 19, 21, 21, 115, 193, 21, 133, 81, 45, 23, 243, 151, 155, 25, + 51, 47, 91, 29, 29, 247, 29, 89, 91, 157, 55, 31, 17, 35, 227, 65, 19, + 37, 41, 39, 185, 43, 21, 155, 79, 139, 23, 217, 25, 17, 127, 25, 239, + 17, 137, 215, 29, 15, 147, 29, 59, 65, 55, 31, 17, 171, 67, 35, 19, 39, + 19, 199, 21, 211, 21, 43, 149, 45, 49, 71, 13, 17, 25, 183, 55, 127, 27, + 29, 29, 57, 45, 31, 59, 185, 113, 31, 17, 171, 209, 253, 367, 265, 181, + 39, 27, 127, 143, 43, 29, 45, 157, 47, 13, 111, 443, 51, 51, 451, 257, + 57, 313, 271, 179, 331, 363, 375, 127, 31, 33, 43, 33, 477, 35, 233, + 357, 337, 37, 71, 71, 37, 39, 127, 39, 39, 31, 113, 41, 251, 43, 21, 43, + 45, 45, 161, 89, 323, 47, 23, 47, 263 }; + +const int f2_list[NOF_TC_CB_SIZES] = { 10, 12, 42, 16, 18, 20, 22, 24, 26, 84, 90, 32, + 34, 108, 38, 120, 84, 44, 46, 48, 50, 52, 36, 56, 58, 60, 62, 32, 198, + 68, 210, 36, 74, 76, 78, 120, 82, 84, 86, 44, 90, 46, 94, 48, 98, 40, + 102, 52, 106, 72, 110, 168, 114, 58, 118, 180, 122, 62, 84, 64, 66, 68, + 420, 96, 74, 76, 234, 80, 82, 252, 86, 44, 120, 92, 94, 48, 98, 80, 102, + 52, 106, 48, 110, 112, 114, 58, 118, 60, 122, 124, 84, 64, 66, 204, 140, + 72, 74, 76, 78, 240, 82, 252, 86, 88, 60, 92, 846, 48, 28, 80, 102, 104, + 954, 96, 110, 112, 114, 116, 354, 120, 610, 124, 420, 64, 66, 136, 420, + 216, 444, 456, 468, 80, 164, 504, 172, 88, 300, 92, 188, 96, 28, 240, + 204, 104, 212, 192, 220, 336, 228, 232, 236, 120, 244, 248, 168, 64, + 130, 264, 134, 408, 138, 280, 142, 480, 146, 444, 120, 152, 462, 234, + 158, 80, 96, 902, 166, 336, 170, 86, 174, 176, 178, 120, 182, 184, 186, + 94, 190, 480 }; + +int tc_interl_LTE_init(tc_interl_t *h, int long_cb) { + int cb_table_idx, f1, f2; + unsigned long long i, j; + + cb_table_idx = lte_find_cb_index(long_cb); + if (cb_table_idx == -1) { + fprintf(stderr, "Can't find long_cb=%d in valid TC CB table\n", long_cb); + return -1; + } + + h->forward = h->reverse = NULL; + h->forward = malloc(sizeof(int) * (long_cb)); + if (!h->forward) { + return -1; + } + h->reverse = malloc(sizeof(int) * (long_cb)); + if (!h->reverse) { + perror("malloc"); + free(h->forward); + h->forward = h->reverse = NULL; + return -1; + } + + f1 = f1_list[cb_table_idx]; + f2 = f2_list[cb_table_idx]; + + DEBUG("table_idx: %d, f1: %d, f2: %d\n", cb_table_idx, f1, f2); + + h->forward[0] = 0; + h->reverse[0] = 0; + for (i = 1; i < long_cb; i++) { + j = (f1*i + f2*i*i) % (long_cb); + h->forward[i] = j; + h->reverse[j] = i; + } + return 0; + +} + + + + + + + + + + + diff --git a/lte/lib/fec/src/tc_interl_umts.c b/lte/lib/fec/src/tc_interl_umts.c new file mode 100644 index 000000000..a71d2f0e0 --- /dev/null +++ b/lte/lib/fec/src/tc_interl_umts.c @@ -0,0 +1,257 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte/fec/tc_interl.h" +#include "lte/fec/turbocoder.h" + +#define TURBO_RATE 3 + +int mcd(int x, int y); + +/************************************************ + * + * UMTS TURBO CODE INTERLEAVER + * + ************************************************/ + +#define MAX_ROWS 20 +#define MAX_COLS 256 + +const unsigned short table_p[52] = { 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, + 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257 }; +const unsigned char table_v[52] = { 3, 2, 2, 3, 2, 5, 2, 3, 2, 6, 3, 5, 2, 2, 2, + 2, 7, 5, 3, 2, 3, 5, 2, 5, 2, 6, 3, 3, 2, 3, 2, 2, 6, 5, 2, 5, 2, 2, 2, + 19, 5, 2, 3, 2, 3, 2, 6, 3, 7, 7, 6, 3 }; + +void tc_interl_free(tc_interl_t *h) { + if (h->forward) { + free(h->forward); + } + if (h->reverse) { + free(h->reverse); + } + h->forward = h->reverse = NULL; +} + +int tc_interl_UMTS_init(tc_interl_t *h, int long_cb) { + + int i, j; + int res, prim, aux; + int kp, k; + int *per, *desper; + unsigned char v; + unsigned short p; + unsigned short s[MAX_COLS], q[MAX_ROWS], r[MAX_ROWS], T[MAX_ROWS]; + unsigned short U[MAX_COLS * MAX_ROWS]; + int M_Rows, M_Cols, M_long; + + h->forward = h->reverse = NULL; + h->forward = malloc(sizeof(int) * (long_cb)); + if (!h->forward) { + return -1; + } + h->reverse = malloc(sizeof(int) * (long_cb)); + if (!h->reverse) { + perror("malloc"); + free(h->forward); + h->forward = h->reverse = NULL; + return -1; + } + M_long = long_cb; + + /* Find R*/ + if ((40 <= M_long) && (M_long <= 159)) + M_Rows = 5; + else if (((160 <= M_long) && (M_long <= 200)) + || ((481 <= M_long) && (M_long <= 530))) + M_Rows = 10; + else + M_Rows = 20; + + /* Find p i v*/ + if ((481 <= M_long) && (M_long <= 530)) { + p = 53; + v = 2; + M_Cols = p; + } else { + i = 0; + do { + p = table_p[i]; + v = table_v[i]; + i++; + } while (M_long > (M_Rows * (p + 1))); + + } + + /* Find C*/ + if ((M_long) <= (M_Rows) * ((p) - 1)) + M_Cols = (p) - 1; + else if (((M_Rows) * (p - 1) < M_long) && (M_long <= (M_Rows) * (p))) + M_Cols = p; + else if ((M_Rows) * (p) < M_long) + M_Cols = (p) + 1; + + q[0] = 1; + prim = 6; + + for (i = 1; i < M_Rows; i++) { + do { + prim++; + res = mcd(prim, p - 1); + } while (res != 1); + q[i] = prim; + } + + s[0] = 1; + for (i = 1; i < p - 1; i++) { + s[i] = (v * s[i - 1]) % p; + } + + if (M_long <= 159 && M_long >= 40) { + T[0] = 4; + T[1] = 3; + T[2] = 2; + T[3] = 1; + T[4] = 0; + } else if ((M_long <= 200 && M_long >= 160) + || (M_long <= 530 && M_long >= 481)) { + T[0] = 9; + T[1] = 8; + T[2] = 7; + T[3] = 6; + T[4] = 5; + T[5] = 4; + T[6] = 3; + T[7] = 2; + T[8] = 1; + T[9] = 0; + } else if ((M_long <= 2480 && M_long >= 2281) + || (M_long <= 3210 && M_long >= 3161)) { + T[0] = 19; + T[1] = 9; + T[2] = 14; + T[3] = 4; + T[4] = 0; + T[5] = 2; + T[6] = 5; + T[7] = 7; + T[8] = 12; + T[9] = 18; + T[10] = 16; + T[11] = 13; + T[12] = 17; + T[13] = 15; + T[14] = 3; + T[15] = 1; + T[16] = 6; + T[17] = 11; + T[18] = 8; + T[19] = 10; + } else { + T[0] = 19; + T[1] = 9; + T[2] = 14; + T[3] = 4; + T[4] = 0; + T[5] = 2; + T[6] = 5; + T[7] = 7; + T[8] = 12; + T[9] = 18; + T[10] = 10; + T[11] = 8; + T[12] = 13; + T[13] = 17; + T[14] = 3; + T[15] = 1; + T[16] = 16; + T[17] = 6; + T[18] = 15; + T[19] = 11; + } + + for (i = 0; i < M_Rows; i++) { + r[T[i]] = q[i]; + } + + for (i = 0; i < M_Rows; i++) { + for (j = 0; j < p - 1; j++) { + U[i * M_Cols + j] = s[(j * r[i]) % (p - 1)]; + if (M_Cols == (p - 1)) + U[i * M_Cols + j] -= 1; + } + } + + if (M_Cols == p) { + for (i = 0; i < M_Rows; i++) + U[i * M_Cols + p - 1] = 0; + } else if (M_Cols == p + 1) { + for (i = 0; i < M_Rows; i++) { + U[i * M_Cols + p - 1] = 0; + U[i * M_Cols + p] = p; + } + if (M_long == M_Cols * M_Rows) { + aux = U[(M_Rows - 1) * M_Cols + p]; + U[(M_Rows - 1) * M_Cols + p] = U[(M_Rows - 1) * M_Cols + 0]; + U[(M_Rows - 1) * M_Cols + 0] = aux; + } + } + + per = h->forward; + desper = h->reverse; + + k = 0; + for (j = 0; j < M_Cols; j++) { + for (i = 0; i < M_Rows; i++) { + kp = T[i] * M_Cols + U[i * M_Cols + j]; + if (kp < M_long) { + desper[kp] = k; + per[k] = kp; + k++; + } + } + } + + return 0; + +} + +int mcd(int x, int y) { + int r = 1; + + while (r) { + r = x % y; + x = y; + y = r; + } + return x; +} diff --git a/lte/lib/fec/src/turbocoder.c b/lte/lib/fec/src/turbocoder.c new file mode 100644 index 000000000..4ff72df26 --- /dev/null +++ b/lte/lib/fec/src/turbocoder.c @@ -0,0 +1,134 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte/fec/tc_interl.h" +#include "lte/fec/turbocoder.h" + +#define NOF_REGS 3 + +int tcod_init(tcod_t *h, int long_cb) { + + if (tc_interl_LTE_init(&h->interl, long_cb)) { + return -1; + } + h->long_cb = long_cb; + return 0; +} + +void tcod_free(tcod_t *h) { + tc_interl_free(&h->interl); + h->long_cb = 0; +} + +void tcod_encode(tcod_t *h, char *input, char *output) { + + char reg1_0,reg1_1,reg1_2, reg2_0,reg2_1,reg2_2; + int i,k=0,j; + char bit; + char in,out; + int *per; + + per=h->interl.forward; + + reg1_0=0; + reg1_1=0; + reg1_2=0; + + reg2_0=0; + reg2_1=0; + reg2_2=0; + + k=0; + for (i=0;ilong_cb;i++) { + bit=input[i]; + + output[k]=bit; + k++; + + in=bit^(reg1_2^reg1_1); + out=reg1_2^(reg1_0^in); + + reg1_2=reg1_1; + reg1_1=reg1_0; + reg1_0=in; + + output[k]=out; + k++; + + bit=input[per[i]]; + + in=bit^(reg2_2^reg2_1); + out=reg2_2^(reg2_0^in); + + reg2_2=reg2_1; + reg2_1=reg2_0; + reg2_0=in; + + output[k]=out; + k++; + } + + k=3*h->long_cb; + + /* TAILING CODER #1 */ + for (j=0;j +#include +#include +#include + +#include "lte/fec/tc_interl.h" +#include "lte/fec/turbodecoder.h" + +/************************************************ + * + * MAP_GEN is the MAX-LOG-MAP generic implementation of the + * Decoder + * + ************************************************/ +void map_gen_beta(map_gen_t *s, llr_t *input, llr_t *parity) { + llr_t m_b[8], new[8], old[8]; + llr_t x, y, xy; + int k; + int end = s->long_cb + RATE; + llr_t *beta = s->beta; + int i; + + for (i=0;i<8;i++) { + old[i] = beta[8 * (end) + i]; + } + + for (k = end - 1; k >= 0; k--) { + x = input[k]; + y = parity[k]; + + xy = x + y; + + m_b[0] = old[4] + xy; + m_b[1] = old[4]; + m_b[2] = old[5] + y; + m_b[3] = old[5] + x; + m_b[4] = old[6] + x; + m_b[5] = old[6] + y; + m_b[6] = old[7]; + m_b[7] = old[7] + xy; + + new[0] = old[0]; + new[1] = old[0] + xy; + new[2] = old[1] + x; + new[3] = old[1] + y; + new[4] = old[2] + y; + new[5] = old[2] + x; + new[6] = old[3] + xy; + new[7] = old[3]; + + for (i=0;i<8;i++) { + if (m_b[i] > new[i]) + new[i] = m_b[i]; + beta[8 * k + i] = new[i]; + old[i] = new[i]; + } + } +} + +void map_gen_alpha(map_gen_t *s, llr_t *input, llr_t *parity, llr_t *output) { + llr_t m_b[8], new[8], old[8], max1[8], max0[8]; + llr_t m1, m0; + llr_t x, y, xy; + llr_t out; + int k; + int end = s->long_cb; + llr_t *beta = s->beta; + int i; + + old[0] = 0; + for (i=1;i<8;i++) { + old[i] = -INF; + } + + for (k = 1; k < end + 1; k++) { + x = input[k - 1]; + y = parity[k - 1]; + + xy = x + y; + + m_b[0] = old[0]; + m_b[1] = old[3] + y; + m_b[2] = old[4] + y; + m_b[3] = old[7]; + m_b[4] = old[1]; + m_b[5] = old[2] + y; + m_b[6] = old[5] + y; + m_b[7] = old[6]; + + new[0] = old[1] + xy; + new[1] = old[2] + x; + new[2] = old[5] + x; + new[3] = old[6] + xy; + new[4] = old[0] + xy; + new[5] = old[3] + x; + new[6] = old[4] + x; + new[7] = old[7] + xy; + + for (i=0;i<8;i++) { + max0[i] = m_b[i] + beta[8 * k + i]; + max1[i] = new[i] + beta[8 * k + i]; + } + + m1 = max1[0]; + m0 = max0[0]; + + for (i=1;i<8;i++) { + if (max1[i] > m1) + m1 = max1[i]; + if (max0[i] > m0) + m0 = max0[i]; + } + + for (i=0;i<8;i++) { + if (m_b[i] > new[i]) + new[i] = m_b[i]; + old[i] = new[i]; + } + + out = m1 - m0; + output[k - 1] = out; + } +} + +int map_gen_init(map_gen_t *h, int long_cb) { + bzero(h, sizeof(map_gen_t)); + h->beta = malloc(sizeof(llr_t) * (long_cb + TOTALTAIL + 1)* NUMSTATES); + if (!h->beta) { + perror("malloc"); + return -1; + } + h->long_cb = long_cb; + return 0; +} + +void map_gen_free(map_gen_t *h) { + if (h->beta) { + free(h->beta); + } + bzero(h, sizeof(map_gen_t)); +} + +void map_gen_dec(map_gen_t *h, llr_t *input, llr_t *parity, llr_t *output) { + int k; + + h->beta[(h->long_cb + TAIL) * NUMSTATES] = 0; + for (k = 1; k < NUMSTATES; k++) + h->beta[(h->long_cb + TAIL) * NUMSTATES + k] = -INF; + + map_gen_beta(h, input, parity); + map_gen_alpha(h, input, parity, output); +} + + + + + + + + + +/************************************************ + * + * TURBO DECODER INTERFACE + * + ************************************************/ +int tdec_init(tdec_t *h, int long_cb) { + int ret = -1; + bzero(h, sizeof(tdec_t)); + int len = long_cb + TOTALTAIL; + + h->llr1 = malloc(sizeof(llr_t) * len); + if (!h->llr1) { + perror("malloc"); + goto clean_and_exit; + } + h->llr2 = malloc(sizeof(llr_t) * len); + if (!h->llr2) { + perror("malloc"); + goto clean_and_exit; + } + h->w = malloc(sizeof(llr_t) * len); + if (!h->w) { + perror("malloc"); + goto clean_and_exit; + } + h->syst = malloc(sizeof(llr_t) * len); + if (!h->syst) { + perror("malloc"); + goto clean_and_exit; + } + h->parity = malloc(sizeof(llr_t) * len); + if (!h->parity) { + perror("malloc"); + goto clean_and_exit; + } + + if (map_gen_init(&h->dec, long_cb)) { + goto clean_and_exit; + } + + h->long_cb = long_cb; + + if (tc_interl_LTE_init(&h->interleaver, h->long_cb) < 0) { + goto clean_and_exit; + } + + ret = 0; +clean_and_exit: + if (ret == -1) { + tdec_free(h); + } + return ret; +} + +void tdec_free(tdec_t *h) { + if (h->llr1) { + free(h->llr1); + } + if (h->llr2) { + free(h->llr2); + } + if (h->w) { + free(h->w); + } + if (h->syst) { + free(h->syst); + } + if (h->parity) { + free(h->parity); + } + + map_gen_free(&h->dec); + + tc_interl_free(&h->interleaver); + + bzero(h, sizeof(tdec_t)); +} + +void tdec_iteration(tdec_t *h, llr_t *input) { + int i; + + // Prepare systematic and parity bits for MAP DEC #1 + for (i = 0; i < h->long_cb; i++) { + h->syst[i] = input[RATE * i] + h->w[i]; + h->parity[i] = input[RATE * i + 1]; + } + for (i=h->long_cb;ilong_cb+RATE;i++) { + h->syst[i] = input[RATE * h->long_cb + NINPUTS * (i - h->long_cb)]; + h->parity[i] = input[RATE * h->long_cb + NINPUTS * (i - h->long_cb) + 1]; + } + + // Run MAP DEC #1 + map_gen_dec(&h->dec, h->syst, h->parity, h->llr1); + + // Prepare systematic and parity bits for MAP DEC #1 + for (i = 0; i < h->long_cb; i++) { + h->syst[i] = h->llr1[h->interleaver.forward[i]] - h->w[h->interleaver.forward[i]]; + h->parity[i] = input[RATE * i + 2]; + } + for (i=h->long_cb;ilong_cb+RATE;i++) { + h->syst[i] = input[RATE * h->long_cb + NINPUTS * RATE + NINPUTS * (i - h->long_cb)]; + h->parity[i] = input[RATE * h->long_cb + NINPUTS * RATE + NINPUTS * (i - h->long_cb) + 1]; + } + + // Run MAP DEC #1 + map_gen_dec(&h->dec, h->syst, h->parity, h->llr2); + + // Update a-priori LLR from the last iteration + for (i = 0; i < h->long_cb; i++) { + h->w[i] += h->llr2[h->interleaver.reverse[i]] - h->llr1[i]; + } + +} + +void tdec_reset(tdec_t *h) { + memset(h->w, 0, sizeof(llr_t) * h->long_cb); +} + +void tdec_decision(tdec_t *h, char *output) { + int i; + for (i = 0; i < h->long_cb; i++) { + output[i] = (h->llr2[h->interleaver.reverse[i]] > 0) ? 1 : 0; + } +} + +void tdec_run_all(tdec_t *h, llr_t *input, char *output, int nof_iterations) { + int iter = 0; + + tdec_reset(h); + + do { + tdec_iteration(h, input); + iter++; + } while (iter < nof_iterations); + + tdec_decision(h, output); +} + diff --git a/lte/lib/fec/src/turbodecoder.c2 b/lte/lib/fec/src/turbodecoder.c2 new file mode 100644 index 000000000..a146236da --- /dev/null +++ b/lte/lib/fec/src/turbodecoder.c2 @@ -0,0 +1,304 @@ +#include +#include +#include + +#include "lte/fec/permute.h" + +#include "lte/fec/turbodecoder.h" + + +void compute_beta(llr_t *beta, llr_t *data, llr_t *parity, int long_cb, int dec) { + llr_t m_b0, m_b1, m_b2, m_b3, m_b4, m_b5, m_b6, m_b7; + llr_t new0, new1, new2, new3, new4, new5, new6, new7; + llr_t old0, old1, old2, old3, old4, old5, old6, old7; + + llr_t x, y, xy; + int k; + int end = long_cb + RATE; + + old0 = beta[8 * (end) + 0]; + old1 = beta[8 * (end) + 1]; + old2 = beta[8 * (end) + 2]; + old3 = beta[8 * (end) + 3]; + old4 = beta[8 * (end) + 4]; + old5 = beta[8 * (end) + 5]; + old6 = beta[8 * (end) + 6]; + old7 = beta[8 * (end) + 7]; + + for (k = end - 1; k >= 0; k--) { + if (k > long_cb - 1) { + if (dec == 1) { + x = data[RATE * (long_cb ) + NINPUTS * (k - long_cb)]; + y = data[RATE * (long_cb ) + NINPUTS * (k - long_cb) + 1]; + } else { + x = data[RATE * (long_cb ) + NINPUTS * RATE + NINPUTS * (k - long_cb)]; + y = data[RATE * (long_cb ) + NINPUTS * RATE + NINPUTS * (k - long_cb) + 1]; + } + } else { + x = data[RATE * k]; + y = parity[RATE * k]; + } + xy = x + y; + + m_b0 = old4 + xy; + m_b1 = old4; + m_b2 = old5 + y; + m_b3 = old5 + x; + m_b4 = old6 + x; + m_b5 = old6 + y; + m_b6 = old7; + m_b7 = old7 + xy; + + new0 = old0; + new1 = old0 + xy; + new2 = old1 + x; + new3 = old1 + y; + new4 = old2 + y; + new5 = old2 + x; + new6 = old3 + xy; + new7 = old3; + + if (m_b0 > new0) new0 = m_b0; + beta[8 * k + 0] = new0; + old0 = new0; + + if (m_b1 > new1) new1 = m_b1; + beta[8 * k + 1] = new1; + old1 = new1; + + if (m_b2 > new2) new2 = m_b2; + beta[8 * k + 2] = new2; + old2 = new2; + + if (m_b3 > new3) new3 = m_b3; + beta[8 * k + 3] = new3; + old3 = new3; + + if (m_b4 > new4) new4 = m_b4; + beta[8 * k + 4] = new4; + old4 = new4; + + if (m_b5 > new5) new5 = m_b5; + beta[8 * k + 5] = new5; + old5 = new5; + + if (m_b6 > new6) new6 = m_b6; + beta[8 * k + 6] = new6; + old6 = new6; + + if (m_b7 > new7) new7 = m_b7; + beta[8 * k + 7] = new7; + old7 = new7; + + } + +} + +void compute_alfa(llr_t *alfa, llr_t *beta, llr_t *data, llr_t *parity, llr_t *output, int long_cb, int dec) { + llr_t m_b0, m_b1, m_b2, m_b3, m_b4, m_b5, m_b6, m_b7; + llr_t new0, new1, new2, new3, new4, new5, new6, new7; + llr_t old0, old1, old2, old3, old4, old5, old6, old7; + llr_t max1_0, max1_1, max1_2, max1_3, max1_4, max1_5, max1_6, max1_7; + llr_t max0_0, max0_1, max0_2, max0_3, max0_4, max0_5, max0_6, max0_7; + llr_t m1, m0; + llr_t x, y, xy; + llr_t out; + int k; + int end = long_cb; + + old0 = alfa[0]; + old1 = alfa[1]; + old2 = alfa[2]; + old3 = alfa[3]; + old4 = alfa[4]; + old5 = alfa[5]; + old6 = alfa[6]; + old7 = alfa[7]; + + for (k = 1; k < end + 1; k++) { + x = data[RATE * (k - 1)]; + y = parity[RATE * (k - 1)]; + + xy = x + y; + + m_b0 = old0; + m_b1 = old3 + y; + m_b2 = old4 + y; + m_b3 = old7; + m_b4 = old1; + m_b5 = old2 + y; + m_b6 = old5 + y; + m_b7 = old6; + + new0 = old1 + xy; + new1 = old2 + x; + new2 = old5 + x; + new3 = old6 + xy; + new4 = old0 + xy; + new5 = old3 + x; + new6 = old4 + x; + new7 = old7 + xy; + + max0_0 = m_b0 + beta[8 * k + 0]; + max0_1 = m_b1 + beta[8 * k + 1]; + max0_2 = m_b2 + beta[8 * k + 2]; + max0_3 = m_b3 + beta[8 * k + 3]; + max0_4 = m_b4 + beta[8 * k + 4]; + max0_5 = m_b5 + beta[8 * k + 5]; + max0_6 = m_b6 + beta[8 * k + 6]; + max0_7 = m_b7 + beta[8 * k + 7]; + + max1_0 = new0 + beta[8 * k + 0]; + max1_1 = new1 + beta[8 * k + 1]; + max1_2 = new2 + beta[8 * k + 2]; + max1_3 = new3 + beta[8 * k + 3]; + max1_4 = new4 + beta[8 * k + 4]; + max1_5 = new5 + beta[8 * k + 5]; + max1_6 = new6 + beta[8 * k + 6]; + max1_7 = new7 + beta[8 * k + 7]; + + m1 = max1_0; + if (max1_1 > m1) m1 = max1_1; + if (max1_2 > m1) m1 = max1_2; + if (max1_3 > m1) m1 = max1_3; + if (max1_4 > m1) m1 = max1_4; + if (max1_5 > m1) m1 = max1_5; + if (max1_6 > m1) m1 = max1_6; + if (max1_7 > m1) m1 = max1_7; + + m0 = max0_0; + if (max0_1 > m0) m0 = max0_1; + if (max0_2 > m0) m0 = max0_2; + if (max0_3 > m0) m0 = max0_3; + if (max0_4 > m0) m0 = max0_4; + if (max0_5 > m0) m0 = max0_5; + if (max0_6 > m0) m0 = max0_6; + if (max0_7 > m0) m0 = max0_7; + + + if (m_b0 > new0) new0 = m_b0; + old0 = new0; + + if (m_b1 > new1) new1 = m_b1; + old1 = new1; + + if (m_b2 > new2) new2 = m_b2; + old2 = new2; + + if (m_b3 > new3) new3 = m_b3; + old3 = new3; + + if (m_b4 > new4) new4 = m_b4; + old4 = new4; + + if (m_b5 > new5) new5 = m_b5; + old5 = new5; + + if (m_b6 > new6) new6 = m_b6; + old6 = new6; + + if (m_b7 > new7) new7 = m_b7; + old7 = new7; + + out = m1 - m0; + + /* + if (dec == 2) { + if (abs(out) < HALT_min) { + HALT_min = abs(out); + } + } + */ + output[k - 1] = out; + } + + alfa[0] = old0; + alfa[1] = old1; + alfa[2] = old2; + alfa[3] = old3; + alfa[4] = old4; + alfa[5] = old5; + alfa[6] = old6; + alfa[7] = old7; +} + +void DEC_RSC(tdec_t *q, llr_t *input, llr_t *output, int *per, int dec) { + int k; + int i; + int last_state = q->long_cb + RATE; + + /** Initialize alfa states */ + q->alfa[0] = 0; + for (k = 1; k < NUMSTATES; k++) { + q->alfa[k] = -INF; + } + + q->beta[last_state * NUMSTATES] = 0; + for (k = 1; k < NUMSTATES; k++) + q->beta[last_state * NUMSTATES + k] = -INF; + + /* copy data temporal buffer (to allow fastest loops)*/ + memcpy(q->data, input, RATE * last_state * sizeof (llr_t)); + + q->parity = &input[dec]; + + if (dec == 1) { + for (i = 0; i < last_state; i++) { + q->data[RATE * i] += q->W[i ]; + } + } else { + for (i = 0; i < last_state; i++) { + q->data[RATE * i] = q->LLR1[per[i ]] - q->W[per[i ]]; + } + } + + compute_beta(q->beta, q->data, &input[dec], q->long_cb, dec); + compute_alfa(q->alfa, q->beta, q->data, &input[dec], output, q->long_cb, dec); +} + +void decide(llr_t *LLR2, char *output, int *desper, int long_cb) { + int i; + + for (i = 0; i < long_cb; i++) + output[i] = (LLR2[desper[i]] > 0) ? 1 : 0; + +} + +void update_W(llr_t *W, llr_t *LLR1, llr_t *LLR2, int *desper, int long_cb) { + int i; + + for (i = 0; i < long_cb; i++) { + W[i] += LLR2[desper[i]] - LLR1[i]; + } +} + +int turbo_decoder(tdec_t *q, llr_t *input, char *output, int *halt) { + + int i; + long halt_mean=0; + int stop=0; + q->iteration = 0; + + + if (ComputePermutation(&q->permuta, q->long_cb,PER_UMTS)<0) + return -1; + + memset(q->W, 0, sizeof (llr_t) * q->long_cb); + + do { + if (q->iteration) + update_W(q->W, q->LLR1, q->LLR2, q->permuta.DESPER, q->long_cb); + + + DEC_RSC(q, input, q->LLR1, q->permuta.PER, 1); + + DEC_RSC(q, input, q->LLR2, q->permuta.PER, 2); + + q->iteration++; + + } while (q->iteration < q->max_iterations && stop==0); + decide(q->LLR2, output, q->permuta.DESPER, q->long_cb); + + return q->iteration; +} + diff --git a/lte/lib/fec/test/turbocoder_test.c b/lte/lib/fec/test/turbocoder_test.c new file mode 100644 index 000000000..ad28ee470 --- /dev/null +++ b/lte/lib/fec/test/turbocoder_test.c @@ -0,0 +1,324 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte.h" + +#include "turbocoder_test.h" + +typedef _Complex float cf_t; + +int frame_length = 1000, nof_frames=100; +float ebno_db = 100.0; +unsigned int seed = 0; +int K = -1; + +#define MAX_ITERATIONS 4 +int nof_iterations = MAX_ITERATIONS; +int test_known_data = 0; +int test_errors = 0; + +#define SNR_POINTS 8 +#define SNR_MIN 0.0 +#define SNR_MAX 4.0 + +void usage(char *prog) { + printf("Usage: %s [nlesv]\n", prog); + printf("\t-k Test with known data (ignores frame_length) [Default disabled]\n"); + printf("\t-i nof_iterations [Default %d]\n", nof_iterations); + printf("\t-n nof_frames [Default %d]\n", nof_frames); + printf("\t-l frame_length [Default %d]\n", frame_length); + printf("\t-e ebno in dB [Default scan]\n"); + printf("\t-t test: check errors on exit [Default disabled]\n"); + printf("\t-s seed [Default 0=time]\n"); +} + +void parse_args(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "inlstvekt")) != -1) { + switch (opt) { + case 'n': + nof_frames = atoi(argv[optind]); + break; + case 'k': + test_known_data = 1; + break; + case 't': + test_errors = 1; + break; + case 'i': + nof_iterations = atoi(argv[optind]); + break; + case 'l': + frame_length = atoi(argv[optind]); + break; + case 'e': + ebno_db = atof(argv[optind]); + break; + case 's': + seed = (unsigned int) strtoul(argv[optind], NULL, 0); + break; + case 'v': + verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +void output_matlab(float ber[MAX_ITERATIONS][SNR_POINTS], int snr_points) { + int i, j; + FILE *f = fopen("turbocoder_snr.m", "w"); + if (!f) { + perror("fopen"); + exit(-1); + } + fprintf(f, "ber=["); + for (j=0;j known_data_errors[j]) { + fprintf(stderr, "Expected %d errors but got %d\n", + known_data_errors[j], errors[j]); + exit(-1); + }else { + printf("Iter %d ok\n", j+1); + } + } + } else { + for (j=0;j get_expected_errors(frame_cnt, seed, j+1, frame_length, ebno_db)) { + fprintf(stderr, "Expected %d errors but got %d\n", + get_expected_errors(frame_cnt, seed, j+1, frame_length, ebno_db), + errors[j]); + exit(-1); + } else { + printf("Iter %d ok\n", j+1); + } + } + } + } + } + } + + free(data_tx); + free(symbols); + free(llr); + free(llr_c); + free(data_rx); + + tdec_free(&tdec); + tcod_free(&tcod); + + printf("\n"); + output_matlab(ber, snr_points); + printf("Done\n"); + exit(0); +} diff --git a/lte/lib/fec/test/turbocoder_test.h b/lte/lib/fec/test/turbocoder_test.h new file mode 100644 index 000000000..768f5406b --- /dev/null +++ b/lte/lib/fec/test/turbocoder_test.h @@ -0,0 +1,168 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 + +typedef struct { + int n; + unsigned int s; + int iterations; + int len; + float ebno; + int errors; +} expected_errors_t; + +static expected_errors_t expected_errors[] = { + { 100, 1, 1, 504, 1.0, 3989 }, + { 100, 1, 2, 504, 1.0, 1922 }, + { 100, 1, 3, 504, 1.0, 1096 }, + { 100, 1, 4, 504, 1.0, 957 }, + + { 100, 1, 1, 504, 2.0, 803 }, + { 100, 1, 2, 504, 2.0, 47 }, + { 100, 1, 3, 504, 2.0, 7 }, + { 100, 1, 4, 504, 2.0, 0 }, + + { 100, 1, 1, 6144, 1.5, 24719 }, + { 100, 1, 2, 6144, 1.5, 897 }, + { 100, 1, 3, 6144, 1.5, 2 }, + { 100, 1, 4, 6144, 1.5, 0 }, + { -1, 0, -1, -1, -1.0, -1} +}; + + +int get_expected_errors(int n, unsigned int s, int iterations, int len, float ebno) { + int i; + i = 0; + while (expected_errors[i].n != -1) { + if (expected_errors[i].n == n + && expected_errors[i].s == s + && expected_errors[i].len == len + && expected_errors[i].iterations == iterations + && expected_errors[i].ebno == ebno) { + break; + } else { + i++; + } + } + return expected_errors[i].errors; +} + +#define KNOWN_DATA_NFRAMES 1 +#define KNOWN_DATA_SEED 1 +#define KNOWN_DATA_EBNO 0.5 +const int known_data_errors[4] = {47, 18, 0, 0}; + +#define KNOWN_DATA_LEN 504 +const char known_data[KNOWN_DATA_LEN] = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, + 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, + 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, + 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, + 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, + 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1 }; + +const char known_data_encoded[3 * KNOWN_DATA_LEN + 12] = { 0, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, + 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, + 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, + 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, + 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, + 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, + 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, + 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, + 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, + 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, + 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, + 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, + 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, + 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, + 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, + 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, + 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, + 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, + 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, + 1, 0, 1, 1, 1 }; From 887c3a8e800e8eb9baf719cd205fa80e7d959b9b Mon Sep 17 00:00:00 2001 From: ismagom Date: Tue, 13 May 2014 16:43:16 +0100 Subject: [PATCH 13/15] Restored old CRC --- lte/include/lte/fec/crc.h | 2 ++ lte/lib/fec/src/crc.c | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/lte/include/lte/fec/crc.h b/lte/include/lte/fec/crc.h index fe35f992c..0844afca4 100644 --- a/lte/include/lte/fec/crc.h +++ b/lte/include/lte/fec/crc.h @@ -61,5 +61,7 @@ typedef struct { int crc_init(crc_t *crc_par); unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params); +unsigned int crc(unsigned int crc, char *bufptr, int len, + int long_crc,unsigned int poly, int paste_word); #endif diff --git a/lte/lib/fec/src/crc.c b/lte/lib/fec/src/crc.c index b74569d9e..8a0cd92bf 100644 --- a/lte/lib/fec/src/crc.c +++ b/lte/lib/fec/src/crc.c @@ -154,3 +154,62 @@ unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params) { //Return CRC value return crc; } + + +unsigned int cword; + +unsigned int icrc1(unsigned int crc, unsigned short onech,int long_crc, + int left_shift,unsigned int poly) +{ + int i; + unsigned int tmp=(unsigned int) (crc ^ (onech << (long_crc >> 1) )); + + for (i=0;i>long_crc), + data,long_crc,i,poly)<>long_crc; + } + + ret=cword; + if (paste_word) { + cword<<=32-long_crc; + for (i=0;i>31); + cword<<=1; + } + } + return (ret); +} From 6fe5a0595243cfeee17a083f6f0609dab0573272 Mon Sep 17 00:00:00 2001 From: ismagom Date: Tue, 3 Jun 2014 14:59:15 +0200 Subject: [PATCH 14/15] Added CRC. Changed ratematching to FEC directory --- lte/include/lte.h | 4 +- lte/include/lte/common/base.h | 5 + lte/include/lte/fec/crc.h | 29 +-- .../lte/{ratematching => fec}/rm_conv.h | 0 .../lte/fec/{turbodecoder.h2 => rm_turbo.h} | 127 +++++----- lte/include/lte/phch/pbch.h | 3 +- lte/include/lte/phch/pdcch.h | 4 +- lte/lib/fec/src/crc.c | 230 +++++++----------- lte/lib/{ratematching => fec}/src/rm_conv.c | 4 +- lte/lib/fec/src/rm_turbo.c | 180 ++++++++++++++ lte/lib/fec/test/CMakeLists.txt | 11 + lte/lib/fec/test/crc_test.c | 29 +-- .../{ratematching => fec}/test/rm_conv_test.c | 0 lte/lib/fec/test/rm_turbo_test.c | 135 ++++++++++ lte/lib/phch/src/pbch.c | 11 +- lte/lib/phch/src/pdcch.c | 18 +- lte/lib/ratematching/test/CMakeLists.txt | 33 --- 17 files changed, 521 insertions(+), 302 deletions(-) rename lte/include/lte/{ratematching => fec}/rm_conv.h (100%) rename lte/include/lte/fec/{turbodecoder.h2 => rm_turbo.h} (51%) rename lte/lib/{ratematching => fec}/src/rm_conv.c (96%) create mode 100644 lte/lib/fec/src/rm_turbo.c rename lte/lib/{ratematching => fec}/test/rm_conv_test.c (100%) create mode 100644 lte/lib/fec/test/rm_turbo_test.c delete mode 100644 lte/lib/ratematching/test/CMakeLists.txt diff --git a/lte/include/lte.h b/lte/include/lte.h index 7a9c6a15c..d8e7ca567 100644 --- a/lte/include/lte.h +++ b/lte/include/lte.h @@ -57,6 +57,8 @@ #include "lte/fec/tc_interl.h" #include "lte/fec/turbocoder.h" #include "lte/fec/turbodecoder.h" +#include "lte/fec/rm_conv.h" +#include "lte/fec/rm_turbo.h" #include "lte/filter/filter2d.h" @@ -81,8 +83,6 @@ #include "lte/phch/pcfich.h" #include "lte/phch/phich.h" -#include "lte/ratematching/rm_conv.h" - #include "lte/scrambling/scrambling.h" #include "lte/resampling/interp.h" diff --git a/lte/include/lte/common/base.h b/lte/include/lte/common/base.h index 1d8cc5387..22de8bd37 100644 --- a/lte/include/lte/common/base.h +++ b/lte/include/lte/common/base.h @@ -39,6 +39,11 @@ #define MAX_LAYERS 8 #define MAX_CODEWORDS 2 +#define LTE_CRC24A 0x1864CFB +#define LTE_CRC24B 0X1800063 +#define LTE_CRC16 0x11021 +#define LTE_CRC8 0x19B + typedef enum {CPNORM, CPEXT} lte_cp_t; #define SIRNTI 0xFFFF diff --git a/lte/include/lte/fec/crc.h b/lte/include/lte/fec/crc.h index 0844afca4..a08e1568c 100644 --- a/lte/include/lte/fec/crc.h +++ b/lte/include/lte/fec/crc.h @@ -26,42 +26,23 @@ */ - #ifndef CRC_ #define CRC_ -#define LTE_CRC24A 0x1864CFB -#define LTE_CRC24B 0X1800063 -#define LTE_CRC16 0x11021 -#define LTE_CRC8 0x19B - - -#define _WITHMALLOC - -#ifndef _WITHMALLOC -#define MAX_LENGTH 1024*16 -#endif - typedef struct { unsigned long table[256]; -#ifdef _WITHMALLOC - unsigned char *data0; -#else - unsigned char data0[MAX_LENGTH]; -#endif + unsigned char byte; int polynom; int order; unsigned long crcinit; - unsigned long crcxor; unsigned long crcmask; unsigned long crchighbit; unsigned int crc_out; } crc_t; - -int crc_init(crc_t *crc_par); -unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params); -unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc,unsigned int poly, int paste_word); +int crc_init(crc_t *h, unsigned int crc_poly, int crc_order); +int crc_set_init(crc_t *h, unsigned long crc_init_value); +void crc_attach(crc_t *h, char *data, int len); +unsigned int crc_checksum(crc_t *h, char *data, int len); #endif diff --git a/lte/include/lte/ratematching/rm_conv.h b/lte/include/lte/fec/rm_conv.h similarity index 100% rename from lte/include/lte/ratematching/rm_conv.h rename to lte/include/lte/fec/rm_conv.h diff --git a/lte/include/lte/fec/turbodecoder.h2 b/lte/include/lte/fec/rm_turbo.h similarity index 51% rename from lte/include/lte/fec/turbodecoder.h2 rename to lte/include/lte/fec/rm_turbo.h index 003cfbbde..96aefadb3 100644 --- a/lte/include/lte/fec/turbodecoder.h2 +++ b/lte/include/lte/fec/rm_turbo.h @@ -1,68 +1,59 @@ -/** - * - * \section COPYRIGHT - * - * Copyright 2013-2014 The libLTE Developers. See the - * COPYRIGHT file at the top-level directory of this distribution. - * - * \section LICENSE - * - * This file is part of the libLTE library. - * - * libLTE is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * libLTE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * A copy of the GNU Lesser 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/. - * - */ - -#define RATE 3 -#define TOTALTAIL 12 - -#define LOG18 -2.07944 - -#define NUMSTATES 8 -#define NINPUTS 2 -#define TOTALTAIL 12 - -#define SCALE 80 - -#define INF 9e4 -#define ZERO 9e-4 - -#define MAX_LONG_CB 6114 -#define MAX_LONG_CODED (RATE*MAX_LONG_CB+TOTALTAIL) - -typedef float llr_t; - -typedef struct { - int long_cb; - int max_iterations; - int halt_threshold; - enum { HALT_MIN = 2, HALT_MEAN = 1, HALT_NONE = 0} halt_mode; - llr_t alfa[NUMSTATES]; - llr_t beta[(MAX_LONG_CB + 1) * NUMSTATES]; - llr_t LLR1[MAX_LONG_CB + TOTALTAIL]; - llr_t LLR2[MAX_LONG_CB + TOTALTAIL]; - llr_t W[MAX_LONG_CB + TOTALTAIL]; - llr_t data[RATE*(MAX_LONG_CB + TOTALTAIL)]; - llr_t *parity; - struct permute_t permuta; - int iteration; - int HALT_min; - -}tdec_t; - -int tdec_init(tdec_t *h); - -int turbo_decoder(tdec_t *h, llr_t *input, char *output, int *halt); - +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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/. + * + */ + + +#ifndef RM_CONV_ +#define RM_CONV_ + +#define RX_NULL 10000 +#define TX_NULL 80 + + +int rm_turbo_tx(char *input, char *output, int in_len, int out_len); +int rm_turbo_rx(float *input, float *output, int in_len, int out_len); + + +/* High-level API */ +typedef struct { + struct rm_turbo_init { + int direction; + } init; + void *input; // input type may be char or float depending on hard + int in_len; + struct rm_turbo_ctrl_in { + int E; + int S; + } ctrl_in; + void *output; + int out_len; +}rm_turbo_hl; + +int rm_turbo_initialize(rm_turbo_hl* h); +int rm_turbo_work(rm_turbo_hl* hl); +int rm_turbo_stop(rm_turbo_hl* hl); + +#endif diff --git a/lte/include/lte/phch/pbch.h b/lte/include/lte/phch/pbch.h index 9cb3ae113..877dd6439 100644 --- a/lte/include/lte/phch/pbch.h +++ b/lte/include/lte/phch/pbch.h @@ -35,7 +35,7 @@ #include "lte/modem/mod.h" #include "lte/modem/demod_soft.h" #include "lte/scrambling/scrambling.h" -#include "lte/ratematching/rm_conv.h" +#include "lte/fec/rm_conv.h" #include "lte/fec/convcoder.h" #include "lte/fec/viterbi.h" #include "lte/fec/crc.h" @@ -79,6 +79,7 @@ typedef struct { demod_soft_t demod; sequence_t seq_pbch; viterbi_t decoder; + crc_t crc; convcoder_t encoder; }pbch_t; diff --git a/lte/include/lte/phch/pdcch.h b/lte/include/lte/phch/pdcch.h index 75c8e768c..b7767d0b5 100644 --- a/lte/include/lte/phch/pdcch.h +++ b/lte/include/lte/phch/pdcch.h @@ -35,7 +35,7 @@ #include "lte/modem/mod.h" #include "lte/modem/demod_soft.h" #include "lte/scrambling/scrambling.h" -#include "lte/ratematching/rm_conv.h" +#include "lte/fec/rm_conv.h" #include "lte/fec/convcoder.h" #include "lte/fec/viterbi.h" #include "lte/fec/crc.h" @@ -88,7 +88,7 @@ typedef struct { demod_soft_t demod; sequence_t seq_pdcch[NSUBFRAMES_X_FRAME]; viterbi_t decoder; - + crc_t crc; }pdcch_t; int pdcch_init(pdcch_t *q, regs_t *regs, int nof_prb, int nof_ports, int cell_id, lte_cp_t cp); diff --git a/lte/lib/fec/src/crc.c b/lte/lib/fec/src/crc.c index 8a0cd92bf..e2de27c4f 100644 --- a/lte/lib/fec/src/crc.c +++ b/lte/lib/fec/src/crc.c @@ -25,191 +25,141 @@ * */ - #include #include #include -#include "lte/utils/pack.h" +#include "lte/utils/pack.h" #include "lte/fec/crc.h" +void gen_crc_table(crc_t *h) { -void gen_crc_table(crc_t *crc_params) { - - int i, j, ord=(crc_params->order-8); + int i, j, ord = (h->order - 8); unsigned long bit, crc; - for (i=0; i<256; i++) { - crc = ((unsigned long)i)<crchighbit; - crc<<= 1; - if (bit) crc^= crc_params->polynom; - } - crc_params->table[i]=crc & crc_params->crcmask; + for (i = 0; i < 256; i++) { + crc = ((unsigned long) i) << ord; + for (j = 0; j < 8; j++) { + bit = crc & h->crchighbit; + crc <<= 1; + if (bit) + crc ^= h->polynom; + } + h->table[i] = crc & h->crcmask; } } - -unsigned long crctable (unsigned long length, crc_t *crc_params) { +unsigned long crctable(crc_t *h) { // Polynom order 8, 16, 24 or 32 only. - int ord=crc_params->order-8; - unsigned long crc = crc_params->crcinit; - unsigned char* data = crc_params->data0; + int ord = h->order - 8; + unsigned long crc = h->crcinit; + unsigned char byte = h->byte; - while (length--){ - crc = (crc << 8) ^ crc_params->table[ ((crc >> (ord)) & 0xff) ^ *data++]; - } - return((crc ^ crc_params->crcxor) & crc_params->crcmask); + crc = (crc << 8) ^ h->table[((crc >> (ord)) & 0xff) ^ byte]; + h->crcinit = crc; + return (crc & h->crcmask); } -unsigned long reversecrcbit(unsigned int crc, int nbits, crc_t *crc_params) { +unsigned long reversecrcbit(unsigned int crc, int nbits, crc_t *h) { + + unsigned long m, rmask = 0x1; + + for (m = 0; m < nbits; m++) { + if ((rmask & crc) == 0x01) + crc = (crc ^ h->polynom) >> 1; + else + crc = crc >> 1; + } + return (crc & h->crcmask); +} - unsigned long m, rmask=0x1; +int crc_set_init(crc_t *crc_par, unsigned long crc_init_value) { - for(m=0; mpolynom)>>1; - else crc = crc >> 1; + crc_par->crcinit = crc_init_value; + if (crc_par->crcinit != (crc_par->crcinit & crc_par->crcmask)) { + printf("ERROR, invalid crcinit in crc_set_init().\n"); + return -1; } - return((crc ^ crc_params->crcxor) & crc_params->crcmask); + return 0; } +int crc_init(crc_t *h, unsigned int crc_poly, int crc_order) { -int crc_init(crc_t *crc_par){ + // Set crc working default parameters + h->polynom = crc_poly; + h->order = crc_order; + h->crcinit = 0x00000000; // Compute bit masks for whole CRC and CRC high bit - crc_par->crcmask = ((((unsigned long)1<<(crc_par->order-1))-1)<<1)|1; - crc_par->crchighbit = (unsigned long)1<<(crc_par->order-1); - - printf("crcmask=%x, crchightbit=%x\n", - (unsigned int)crc_par->crcmask, (unsigned int)crc_par->crchighbit); + h->crcmask = ((((unsigned long) 1 << (h->order - 1)) - 1) << 1) + | 1; + h->crchighbit = (unsigned long) 1 << (h->order - 1); // check parameters - if (crc_par->order%8 != 0) { - printf("ERROR, invalid order=%d, it must be 8, 16, 24 or 32.\n", crc_par->order); - return(0); + if (h->order % 8 != 0) { + fprintf(stderr, "ERROR, invalid order=%d, it must be 8, 16, 24 or 32.\n", + h->order); + return -1; } - if (crc_par->crcinit != (crc_par->crcinit & crc_par->crcmask)) { - printf("ERROR, invalid crcinit.\n"); - return(0); - } - if (crc_par->crcxor != (crc_par->crcxor & crc_par->crcmask)) { - printf("ERROR, invalid crcxor.\n"); - return(0); + if (crc_set_init(h, h->crcinit)) { + fprintf(stderr, "Error setting CRC init word\n"); + return -1; } + // generate lookup table - gen_crc_table(crc_par); + gen_crc_table(h); - return(1); + return 0; } - -unsigned int crc_attach(char *bufptr, int len, crc_t *crc_params) { - - int i, len8, res8, a; - unsigned int crc; +unsigned int crc_checksum(crc_t *h, char *data, int len) { + int i, k, len8, res8, a = 0; + unsigned int crc = 0; char *pter; -#ifdef _WITHMALLOC - crc_params->data0 = (unsigned char *)malloc(sizeof(*crc_params->data0) * (len+crc_params->order)*2); - if (!crc_params->data0) { - perror("malloc ERROR: Allocating memory for data pointer in crc() function"); - return(-1); - } -#else - if((((len+crc_params->order)>>3) + 1) > MAX_LENGTH){ - printf("ERROR: Not enough memory allocated\n"); - return(-1); - } -#endif - //# Pack bits into bytes - len8=(len>>3); - res8=8-(len - (len8<<3)); - if(res8>0)a=1; - else a=0; - - // Zeroed additional bits - memset((char *)(bufptr+len),0,(32)*sizeof(char)); - - for(i=0; idata0[i]=(unsigned char)(unpack_bits(&pter, 8)&0xFF); + crc_set_init(h, 0); + + // Pack bits into bytes + len8 = (len >> 3); + res8 = (len - (len8 << 3)); + if (res8 > 0) { + a = 1; } + // Calculate CRC - crc=crctable(len8+a, crc_params); + for (i = 0; i < len8 + a; i++) { + pter = (char *) (data + 8 * i); + if (i == len8) { + h->byte = 0x00; + for (k = 0; k < res8; k++) { + h->byte |= ((unsigned char) *(pter + k)) << (7 - k); + } + } else { + h->byte = (unsigned char) (unpack_bits(&pter, 8) & 0xFF); + } + crc = crctable(h); + } // Reverse CRC res8 positions - if(a==1)crc=reversecrcbit(crc, res8, crc_params); - - // Add CRC - pter=(char *)(bufptr+len); - pack_bits(crc, &pter, crc_params->order); + if (a == 1) { + crc = reversecrcbit(crc, 8 - res8, h); + } -#ifdef _WITHMALLOC - free(crc_params->data0); - crc_params->data0=NULL; -#endif //Return CRC value return crc; -} - - -unsigned int cword; -unsigned int icrc1(unsigned int crc, unsigned short onech,int long_crc, - int left_shift,unsigned int poly) -{ - int i; - unsigned int tmp=(unsigned int) (crc ^ (onech << (long_crc >> 1) )); +} - for (i=0;iorder); } -unsigned int crc(unsigned int crc, char *bufptr, int len, - int long_crc,unsigned int poly, int paste_word) { - - int i,k; - unsigned int data; - int stop; - unsigned int ret; - - cword=crc; - - k=0; - stop=0; - while(!stop) { - data=0; - for (i=0;i>long_crc), - data,long_crc,i,poly)<>long_crc; - } - - ret=cword; - if (paste_word) { - cword<<=32-long_crc; - for (i=0;i>31); - cword<<=1; - } - } - return (ret); -} diff --git a/lte/lib/ratematching/src/rm_conv.c b/lte/lib/fec/src/rm_conv.c similarity index 96% rename from lte/lib/ratematching/src/rm_conv.c rename to lte/lib/fec/src/rm_conv.c index d7b605cb7..ea90b83cf 100644 --- a/lte/lib/ratematching/src/rm_conv.c +++ b/lte/lib/fec/src/rm_conv.c @@ -28,7 +28,7 @@ #include #include -#include "lte/ratematching/rm_conv.h" +#include "lte/fec/rm_conv.h" #define NCOLS 32 #define NROWS_MAX NCOLS @@ -59,6 +59,7 @@ int rm_conv_tx(char *input, char *output, int in_len, int out_len) { if (ndummy < 0) { ndummy = 0; } + /* Sub-block interleaver 5.1.4.2.1 */ k=0; for (s = 0; s < 3; s++) { for (j = 0; j < NCOLS; j++) { @@ -72,6 +73,7 @@ int rm_conv_tx(char *input, char *output, int in_len, int out_len) { } } } + /* Bit collection, selection and transmission 5.1.4.2.2 */ k = 0; j = 0; while (k < out_len) { diff --git a/lte/lib/fec/src/rm_turbo.c b/lte/lib/fec/src/rm_turbo.c new file mode 100644 index 000000000..ee0561f36 --- /dev/null +++ b/lte/lib/fec/src/rm_turbo.c @@ -0,0 +1,180 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2014 The libLTE Developers. See the + * COPYRIGHT file at the top-level directory of this distribution. + * + * \section LICENSE + * + * This file is part of the libLTE library. + * + * libLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * A copy of the GNU Lesser 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 "lte/fec/rm_turbo.h" + +#define NCOLS 32 +#define NROWS_MAX NCOLS +#define RATE 3 + +unsigned char RM_PERM_CC[NCOLS] = + { 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30, 1, 17, 9, + 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31 }; + +unsigned char RM_PERM_CC_INV[NCOLS] = { 16, 0, 24, 8, 20, 4, 28, 12, 18, 2, 26, + 10, 22, 6, 30, 14, 17, 1, 25, 9, 21, 5, 29, 13, 19, 3, 27, 11, 23, 7, + 31, 15 }; + +int rm_turbo_tx(char *input, char *output, int in_len, int out_len) { + + char tmp[RATE * NCOLS * NROWS_MAX]; + int nrows, ndummy, K_p; + + int i, j, k, s; + + nrows = (int) (in_len / RATE - 1) / NCOLS + 1; + if (nrows > NROWS_MAX) { + fprintf(stderr, "Input too large. Max input length is %d\n", + RATE * NCOLS * NROWS_MAX); + return -1; + } + K_p = nrows * NCOLS; + ndummy = K_p - in_len / RATE; + if (ndummy < 0) { + ndummy = 0; + } + + /* Sub-block interleaver 5.1.4.1.1 */ + k = 0; + for (s = 0; s < 3; s++) { + for (j = 0; j < NCOLS; j++) { + for (i = 0; i < nrows; i++) { + if (i * NCOLS + RM_PERM_CC[j] < ndummy) { + tmp[k] = TX_NULL; + } else { + tmp[k] = input[(i * NCOLS + RM_PERM_CC[j] - ndummy) * 3 + s]; + } + k++; + } + } + } + /* Bit collection, selection and transmission 5.1.4.1.2 */ + k = 0; + j = 0; + while (k < out_len) { + if (tmp[j] != TX_NULL) { + output[k] = tmp[j]; + k++; + } + j++; + if (j == RATE * K_p) { + j = 0; + } + } + return 0; +} + +/* Undoes Convolutional Code Rate Matching. + * 3GPP TS 36.212 v10.1.0 section 5.1.4.2 + */ +int rm_turbo_rx(float *input, float *output, int in_len, int out_len) { + + int nrows, ndummy, K_p; + int i, j, k; + int d_i, d_j; + + float tmp[RATE * NCOLS * NROWS_MAX]; + + nrows = (int) (out_len / RATE - 1) / NCOLS + 1; + if (nrows > NROWS_MAX) { + fprintf(stderr, "Output too large. Max output length is %d\n", + RATE * NCOLS * NROWS_MAX); + return -1; + } + K_p = nrows * NCOLS; + + ndummy = K_p - out_len / RATE; + if (ndummy < 0) { + ndummy = 0; + } + + for (i = 0; i < RATE * K_p; i++) { + tmp[i] = RX_NULL; + } + + /* Undo bit collection. Account for dummy bits */ + k = 0; + j = 0; + while (k < in_len) { + d_i = (j % K_p) / nrows; + d_j = (j % K_p) % nrows; + + if (d_j * NCOLS + RM_PERM_CC[d_i] >= ndummy) { + if (tmp[j] == RX_NULL) { + tmp[j] = input[k]; + } else if (input[k] != RX_NULL) { + tmp[j] += input[k]; /* soft combine LLRs */ + } + k++; + } + j++; + if (j == RATE * K_p) { + j = 0; + } + } + + /* interleaving and bit selection */ + for (i = 0; i < out_len / RATE; i++) { + d_i = (i + ndummy) / NCOLS; + d_j = (i + ndummy) % NCOLS; + for (j = 0; j < RATE; j++) { + float o = tmp[K_p * j + RM_PERM_CC_INV[d_j] * nrows + d_i]; + if (o != RX_NULL) { + output[i * RATE + j] = o; + } else { + output[i * RATE + j] = 0; + } + } + } + return 0; +} + +/** High-level API */ + +int rm_turbo_initialize(rm_turbo_hl* h) { + + return 0; +} + +/** This function can be called in a subframe (1ms) basis */ +int rm_turbo_work(rm_turbo_hl* hl) { + if (hl->init.direction) { + //rm_turbo_tx(hl->input, hl->output, hl->in_len, hl->ctrl_in.S); + hl->out_len = hl->ctrl_in.S; + } else { + rm_turbo_rx(hl->input, hl->output, hl->in_len, hl->ctrl_in.E); + hl->out_len = hl->ctrl_in.E; + } + return 0; +} + +int rm_turbo_stop(rm_turbo_hl* hl) { + return 0; +} + diff --git a/lte/lib/fec/test/CMakeLists.txt b/lte/lib/fec/test/CMakeLists.txt index 1f83b8411..95a9d5974 100644 --- a/lte/lib/fec/test/CMakeLists.txt +++ b/lte/lib/fec/test/CMakeLists.txt @@ -20,6 +20,17 @@ # +######################################################################## +# RATEMATCHING TEST +######################################################################## + +ADD_EXECUTABLE(rm_conv_test rm_conv_test.c) +TARGET_LINK_LIBRARIES(rm_conv_test lte) + +ADD_TEST(rm_conv_test_1 rm_conv_test -t 480 -r 1920) +ADD_TEST(rm_conv_test_2 rm_conv_test -t 1920 -r 480) + + ######################################################################## # Turbo Coder TEST ######################################################################## diff --git a/lte/lib/fec/test/crc_test.c b/lte/lib/fec/test/crc_test.c index 28c389002..e21c89c4d 100644 --- a/lte/lib/fec/test/crc_test.c +++ b/lte/lib/fec/test/crc_test.c @@ -25,7 +25,6 @@ * */ - #include #include #include @@ -37,11 +36,10 @@ #include "lte.h" #include "crc_test.h" -int num_bits = 5000, crc_length = 24; +int num_bits = 5001, crc_length = 24; unsigned int crc_poly = 0x1864CFB; unsigned int seed = 1; - void usage(char *prog) { printf("Usage: %s [nlps]\n", prog); printf("\t-n num_bits [Default %d]\n", num_bits); @@ -81,7 +79,7 @@ int main(int argc, char **argv) { parse_args(argc, argv); - data = malloc(sizeof(char) * (num_bits+crc_length)*2); + data = malloc(sizeof(char) * (num_bits + crc_length * 2)); if (!data) { perror("malloc"); exit(-1); @@ -93,30 +91,23 @@ int main(int argc, char **argv) { srand(seed); // Generate data - for (i=0;i +#include +#include +#include +#include +#include +#include +#include + +#include "lte.h" + +int nof_tx_bits=-1, nof_rx_bits=-1; + +void usage(char *prog) { + printf("Usage: %s -t nof_tx_bits -r nof_rx_bits\n", prog); +} + +void parse_args(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "tr")) != -1) { + switch (opt) { + case 't': + nof_tx_bits = atoi(argv[optind]); + break; + case 'r': + nof_rx_bits = atoi(argv[optind]); + break; + default: + usage(argv[0]); + exit(-1); + } + } + if (nof_tx_bits == -1) { + usage(argv[0]); + exit(-1); + } + if (nof_rx_bits == -1) { + usage(argv[0]); + exit(-1); + } +} + +int main(int argc, char **argv) { + int i; + char *bits, *rm_bits; + float *rm_symbols, *unrm_symbols; + int nof_errors; + + parse_args(argc, argv); + + bits = malloc(sizeof(char) * nof_tx_bits); + if (!bits) { + perror("malloc"); + exit(-1); + } + rm_bits = malloc(sizeof(char) * nof_rx_bits); + if (!rm_bits) { + perror("malloc"); + exit(-1); + } + rm_symbols = malloc(sizeof(float) * nof_rx_bits); + if (!rm_symbols) { + perror("malloc"); + exit(-1); + } + unrm_symbols = malloc(sizeof(float) * nof_tx_bits); + if (!unrm_symbols) { + perror("malloc"); + exit(-1); + } + + for (i=0;i 0) != bits[i]) { + nof_errors++; + } + } + if (nof_rx_bits > nof_tx_bits) { + if (nof_errors) { + printf("nof_errors=%d\n", nof_errors); + exit(-1); + } + } + + free(bits); + free(rm_bits); + free(rm_symbols); + free(unrm_symbols); + + printf("Ok\n"); + exit(0); +} diff --git a/lte/lib/phch/src/pbch.c b/lte/lib/phch/src/pbch.c index a20684380..e6a413f08 100644 --- a/lte/lib/phch/src/pbch.c +++ b/lte/lib/phch/src/pbch.c @@ -133,6 +133,9 @@ int pbch_init(pbch_t *q, int nof_prb, int cell_id, lte_cp_t cp) { if (viterbi_init(&q->decoder, viterbi_37, poly, 40, true)) { goto clean; } + if (crc_init(&q->crc, LTE_CRC16, 16)) { + goto clean; + } q->encoder.K = 7; q->encoder.R = 3; q->encoder.tail_biting = true; @@ -356,11 +359,11 @@ void crc_set_mask(char *data, int nof_ports) { * * Returns 0 if the data is correct, -1 otherwise */ -int pbch_crc_check(char *bits, int nof_ports) { +int pbch_crc_check(pbch_t *q, char *bits, int nof_ports) { char data[40]; memcpy(data, bits, 40 * sizeof(char)); crc_set_mask(data, nof_ports); - return crc(0, data, 40, 16, LTE_CRC16, 0); + return crc_checksum(&q->crc, data, 40); } int pbch_decode_frame(pbch_t *q, pbch_mib_t *mib, int src, int dst, int n, int nof_bits, int nof_ports) { @@ -399,7 +402,7 @@ int pbch_decode_frame(pbch_t *q, pbch_mib_t *mib, int src, int dst, int n, int n c=1; } - if (!pbch_crc_check(q->data, nof_ports)) { + if (!pbch_crc_check(q, q->data, nof_ports)) { /* unpack MIB */ pbch_mib_unpack(q->data, mib); @@ -523,7 +526,7 @@ void pbch_encode(pbch_t *q, pbch_mib_t *mib, cf_t *slot1_symbols[MAX_PORTS_CTRL] pbch_mib_pack(mib, q->data); /* encode & modulate */ - crc(0, q->data, 24, 16, 0x11021, 1); + crc_attach(&q->crc, q->data, 24); crc_set_mask(q->data, nof_ports); convcoder_encode(&q->encoder, q->data, q->data_enc, 40); diff --git a/lte/lib/phch/src/pdcch.c b/lte/lib/phch/src/pdcch.c index a98b6a47c..0a8fe4e7a 100644 --- a/lte/lib/phch/src/pdcch.c +++ b/lte/lib/phch/src/pdcch.c @@ -221,6 +221,9 @@ int pdcch_init(pdcch_t *q, regs_t *regs, int nof_prb, int nof_ports, if (modem_table_std(&q->mod, LTE_QPSK, true)) { goto clean; } + if (crc_init(&q->crc, LTE_CRC16, 16)) { + goto clean; + } demod_soft_init(&q->demod); demod_soft_table_set(&q->demod, &q->mod); @@ -318,7 +321,7 @@ void pdcch_free(pdcch_t *q) { * * TODO: UE transmit antenna selection CRC mask */ -unsigned short dci_decode(viterbi_t *decoder, float *e, char *data, int E, +unsigned short dci_decode(pdcch_t *q, float *e, char *data, int E, int nof_bits) { float tmp[3 * (DCI_MAX_BITS + 16)]; @@ -343,7 +346,7 @@ unsigned short dci_decode(viterbi_t *decoder, float *e, char *data, int E, } /* viterbi decoder */ - viterbi_decode_f(decoder, tmp, data, nof_bits + 16); + viterbi_decode_f(&q->decoder, tmp, data, nof_bits + 16); if (VERBOSE_ISDEBUG()) { bit_fprint(stdout, data, nof_bits+16); @@ -351,8 +354,7 @@ unsigned short dci_decode(viterbi_t *decoder, float *e, char *data, int E, x = &data[nof_bits]; p_bits = (unsigned short) bit_unpack(&x, 16); - crc_res = ((unsigned short) crc(0, data, nof_bits, 16, LTE_CRC16, 0) - & 0xffff); + crc_res = ((unsigned short) crc_checksum(&q->crc, data, nof_bits) & 0xffff); DEBUG("p_bits: 0x%x, crc_res: 0x%x, tot: 0x%x\n", p_bits, crc_res, p_bits ^ crc_res); return (p_bits ^ crc_res); } @@ -363,7 +365,7 @@ int pdcch_decode_candidate(pdcch_t *q, float *llr, dci_candidate_t *c, DEBUG("Trying Candidate: Nbits: %d, E: %d, nCCE: %d, L: %d, RNTI: 0x%x\n", c->nof_bits, PDCCH_FORMAT_NOF_BITS(c->L), c->ncce, c->L, c->rnti); - crc_res = dci_decode(&q->decoder, &llr[72 * c->ncce], msg->data, + crc_res = dci_decode(q, &llr[72 * c->ncce], msg->data, PDCCH_FORMAT_NOF_BITS(c->L), c->nof_bits); if (c->rnti == crc_res) { @@ -519,7 +521,7 @@ void crc_set_mask_rnti(char *crc, unsigned short rnti) { /** 36.212 5.3.3.2 to 5.3.3.4 * TODO: UE transmit antenna selection CRC mask */ -void dci_encode(char *data, char *e, int nof_bits, int E, unsigned short rnti) { +void dci_encode(pdcch_t *q, char *data, char *e, int nof_bits, int E, unsigned short rnti) { convcoder_t encoder; char tmp[3 * (DCI_MAX_BITS + 16)]; @@ -531,7 +533,7 @@ void dci_encode(char *data, char *e, int nof_bits, int E, unsigned short rnti) { encoder.tail_biting = true; memcpy(encoder.poly, poly, 3 * sizeof(int)); - crc(0, data, nof_bits, 16, LTE_CRC16, 1); + crc_attach(&q->crc, data, nof_bits); crc_set_mask_rnti(&data[nof_bits], rnti); convcoder_encode(&encoder, data, tmp, nof_bits + 16); @@ -579,7 +581,7 @@ int pdcch_encode(pdcch_t *q, dci_t *dci, cf_t *slot1_symbols[MAX_PORTS_CTRL], i, dci->msg[i].location.nof_bits, PDCCH_FORMAT_NOF_BITS(dci->msg[i].location.L), dci->msg[i].location.ncce, dci->msg[i].location.L, dci->msg[i].location.rnti); - dci_encode(dci->msg[i].data, &q->pdcch_e[72 * dci->msg[i].location.ncce], + dci_encode(q, dci->msg[i].data, &q->pdcch_e[72 * dci->msg[i].location.ncce], dci->msg[i].location.nof_bits, PDCCH_FORMAT_NOF_BITS(dci->msg[i].location.L), dci->msg[i].location.rnti); } diff --git a/lte/lib/ratematching/test/CMakeLists.txt b/lte/lib/ratematching/test/CMakeLists.txt deleted file mode 100644 index 1c4ded6df..000000000 --- a/lte/lib/ratematching/test/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2012-2013 The libLTE Developers. See the -# COPYRIGHT file at the top-level directory of this distribution. -# -# This file is part of the libLTE library. -# -# libLTE is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# libLTE is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# A copy of the GNU Lesser 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/. -# - -######################################################################## -# RATEMATCHING TEST -######################################################################## - -ADD_EXECUTABLE(rm_conv_test rm_conv_test.c) -TARGET_LINK_LIBRARIES(rm_conv_test lte) - -ADD_TEST(rm_conv_test_1 rm_conv_test -t 480 -r 1920) -ADD_TEST(rm_conv_test_2 rm_conv_test -t 1920 -r 480) - - - From eb52aeb1abdfbe3a7b781d39b32061bbfcdf375a Mon Sep 17 00:00:00 2001 From: ismagom Date: Fri, 6 Jun 2014 17:09:15 +0200 Subject: [PATCH 15/15] Fixed B210 USRP Clock issue. Added Turbo Rate matching --- cuhd/lib/cuhd_imp.cpp | 6 +- examples/pbch_ue.c | 2 +- lte/include/lte/fec/rm_conv.h | 4 +- lte/include/lte/fec/rm_turbo.h | 22 ++- lte/lib/fec/src/rm_conv.c | 16 +- lte/lib/fec/src/rm_turbo.c | 176 +++++++++++++----- lte/lib/fec/src/turbodecoder.c2 | 304 ------------------------------- lte/lib/fec/test/CMakeLists.txt | 3 + lte/lib/fec/test/rm_conv_test.c | 4 +- lte/lib/fec/test/rm_turbo_test.c | 37 ++-- lte/lib/phch/src/pbch.c | 4 +- lte/lib/phch/src/pdcch.c | 4 +- 12 files changed, 193 insertions(+), 389 deletions(-) delete mode 100644 lte/lib/fec/src/turbodecoder.c2 diff --git a/cuhd/lib/cuhd_imp.cpp b/cuhd/lib/cuhd_imp.cpp index 2f63ddbcc..732a2be34 100644 --- a/cuhd/lib/cuhd_imp.cpp +++ b/cuhd/lib/cuhd_imp.cpp @@ -100,12 +100,14 @@ int cuhd_start_rx_stream_nsamples(void *h, int nsamples) { return 0; } - - int cuhd_open(char *args, void **h) { cuhd_handler* handler = new cuhd_handler(); std::string _args=std::string(args); handler->usrp = uhd::usrp::multi_usrp::make(_args); + + // Try to set LTE clock + handler->usrp->set_master_clock_rate(30720000); + handler->usrp->set_clock_source("internal"); std::string otw, cpu; diff --git a/examples/pbch_ue.c b/examples/pbch_ue.c index a817dd738..1b70d3b68 100644 --- a/examples/pbch_ue.c +++ b/examples/pbch_ue.c @@ -57,7 +57,7 @@ #define NOF_PORTS 2 -float find_threshold = 30.0, track_threshold = 10.0; +float find_threshold = 20.0, track_threshold = 10.0; int max_track_lost = 20, nof_frames = -1; int track_len=300; char *input_file_name = NULL; diff --git a/lte/include/lte/fec/rm_conv.h b/lte/include/lte/fec/rm_conv.h index 58d3e2eb5..0eee8c0ad 100644 --- a/lte/include/lte/fec/rm_conv.h +++ b/lte/include/lte/fec/rm_conv.h @@ -33,8 +33,8 @@ #define TX_NULL 80 -int rm_conv_tx(char *input, char *output, int in_len, int out_len); -int rm_conv_rx(float *input, float *output, int in_len, int out_len); +int rm_conv_tx(char *input, int in_len, char *output, int out_len); +int rm_conv_rx(float *input, int in_len, float *output, int out_len); /* High-level API */ diff --git a/lte/include/lte/fec/rm_turbo.h b/lte/include/lte/fec/rm_turbo.h index 96aefadb3..fcee08fe3 100644 --- a/lte/include/lte/fec/rm_turbo.h +++ b/lte/include/lte/fec/rm_turbo.h @@ -26,19 +26,32 @@ */ -#ifndef RM_CONV_ -#define RM_CONV_ +#ifndef RM_TURBO_ +#define RM_TURBO_ +#ifndef RX_NULL #define RX_NULL 10000 +#endif + +#ifndef TX_NULL #define TX_NULL 80 +#endif +typedef struct { + int buffer_len; + char *buffer; + int *d2_perm; +} rm_turbo_t; -int rm_turbo_tx(char *input, char *output, int in_len, int out_len); -int rm_turbo_rx(float *input, float *output, int in_len, int out_len); +int rm_turbo_init(rm_turbo_t *q, int max_codeblock_len); +void rm_turbo_free(rm_turbo_t *q); +int rm_turbo_tx(rm_turbo_t *q, char *input, int in_len, char *output, int out_len, int rv_idx); +int rm_turbo_rx(rm_turbo_t *q, float *input, int in_len, float *output, int out_len, int rv_idx); /* High-level API */ typedef struct { + rm_turbo_t q; struct rm_turbo_init { int direction; } init; @@ -47,6 +60,7 @@ typedef struct { struct rm_turbo_ctrl_in { int E; int S; + int rv_idx; } ctrl_in; void *output; int out_len; diff --git a/lte/lib/fec/src/rm_conv.c b/lte/lib/fec/src/rm_conv.c index ea90b83cf..d8270d243 100644 --- a/lte/lib/fec/src/rm_conv.c +++ b/lte/lib/fec/src/rm_conv.c @@ -34,14 +34,14 @@ #define NROWS_MAX NCOLS #define RATE 3 -unsigned char RM_PERM_CC[NCOLS] = +unsigned char RM_PERM_TC[NCOLS] = { 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31, 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30 }; -unsigned char RM_PERM_CC_INV[NCOLS] = { 16, 0, 24, 8, 20, 4, 28, 12, 18, 2, 26, +unsigned char RM_PERM_TC_INV[NCOLS] = { 16, 0, 24, 8, 20, 4, 28, 12, 18, 2, 26, 10, 22, 6, 30, 14, 17, 1, 25, 9, 21, 5, 29, 13, 19, 3, 27, 11, 23, 7, 31, 15 }; -int rm_conv_tx(char *input, char *output, int in_len, int out_len) { +int rm_conv_tx(char *input, int in_len, char *output, int out_len) { char tmp[RATE * NCOLS * NROWS_MAX]; int nrows, ndummy, K_p; @@ -64,10 +64,10 @@ int rm_conv_tx(char *input, char *output, int in_len, int out_len) { for (s = 0; s < 3; s++) { for (j = 0; j < NCOLS; j++) { for (i = 0; i < nrows; i++) { - if (i*NCOLS + RM_PERM_CC[j] < ndummy) { + if (i*NCOLS + RM_PERM_TC[j] < ndummy) { tmp[k] = TX_NULL; } else { - tmp[k] = input[(i*NCOLS + RM_PERM_CC[j]-ndummy)*3+s]; + tmp[k] = input[(i*NCOLS + RM_PERM_TC[j]-ndummy)*3+s]; } k++; } @@ -93,7 +93,7 @@ int rm_conv_tx(char *input, char *output, int in_len, int out_len) { /* Undoes Convolutional Code Rate Matching. * 3GPP TS 36.212 v10.1.0 section 5.1.4.2 */ -int rm_conv_rx(float *input, float *output, int in_len, int out_len) { +int rm_conv_rx(float *input, int in_len, float *output, int out_len) { int nrows, ndummy, K_p; int i, j, k; @@ -125,7 +125,7 @@ int rm_conv_rx(float *input, float *output, int in_len, int out_len) { d_i = (j % K_p) / nrows; d_j = (j % K_p) % nrows; - if (d_j * NCOLS + RM_PERM_CC[d_i] >= ndummy) { + if (d_j * NCOLS + RM_PERM_TC[d_i] >= ndummy) { if (tmp[j] == RX_NULL) { tmp[j] = input[k]; } else if (input[k] != RX_NULL) { @@ -144,7 +144,7 @@ int rm_conv_rx(float *input, float *output, int in_len, int out_len) { d_i = (i + ndummy) / NCOLS; d_j = (i + ndummy) % NCOLS; for (j = 0; j < RATE; j++) { - float o = tmp[K_p * j + RM_PERM_CC_INV[d_j] * nrows + float o = tmp[K_p * j + RM_PERM_TC_INV[d_j] * nrows + d_i]; if (o != RX_NULL) { output[i * RATE + j] = o; diff --git a/lte/lib/fec/src/rm_turbo.c b/lte/lib/fec/src/rm_turbo.c index ee0561f36..d057eb9df 100644 --- a/lte/lib/fec/src/rm_turbo.c +++ b/lte/lib/fec/src/rm_turbo.c @@ -27,87 +27,135 @@ #include #include +#include +#include +#include + #include "lte/fec/rm_turbo.h" #define NCOLS 32 #define NROWS_MAX NCOLS #define RATE 3 -unsigned char RM_PERM_CC[NCOLS] = +unsigned char RM_PERM_TC[NCOLS] = { 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31 }; -unsigned char RM_PERM_CC_INV[NCOLS] = { 16, 0, 24, 8, 20, 4, 28, 12, 18, 2, 26, - 10, 22, 6, 30, 14, 17, 1, 25, 9, 21, 5, 29, 13, 19, 3, 27, 11, 23, 7, - 31, 15 }; +int rm_turbo_init(rm_turbo_t *q, int buffer_len) { + q->buffer_len = buffer_len; + q->buffer = malloc(buffer_len * sizeof(float)); + if (!q->buffer) { + perror("malloc"); + return -1; + } + q->d2_perm = malloc(buffer_len * sizeof(int) / 3 + 1); + if (!q->d2_perm) { + perror("malloc"); + return -1; + } + return 0; +} + +void rm_turbo_free(rm_turbo_t *q) { + if (q->buffer) { + free(q->buffer); + } +} -int rm_turbo_tx(char *input, char *output, int in_len, int out_len) { +/* Turbo Code Rate Matching. + * 3GPP TS 36.212 v10.1.0 section 5.1.4.1 + * + * TODO: Soft buffer size limitation according to UE category + */ +int rm_turbo_tx(rm_turbo_t *q, char *input, int in_len, char *output, int out_len, int rv_idx) { - char tmp[RATE * NCOLS * NROWS_MAX]; + char *tmp = (char*) q->buffer; int nrows, ndummy, K_p; - int i, j, k, s; + int i, j, k, s, kidx, N_cb, k0; nrows = (int) (in_len / RATE - 1) / NCOLS + 1; - if (nrows > NROWS_MAX) { - fprintf(stderr, "Input too large. Max input length is %d\n", - RATE * NCOLS * NROWS_MAX); + K_p = nrows * NCOLS; + if (3 * K_p > q->buffer_len) { + fprintf(stderr, + "Input too large. Max input length including dummy bits is %d\n", + q->buffer_len); return -1; } - K_p = nrows * NCOLS; + ndummy = K_p - in_len / RATE; if (ndummy < 0) { ndummy = 0; } - /* Sub-block interleaver 5.1.4.1.1 */ + /* Sub-block interleaver (5.1.4.1.1) and bit collection */ k = 0; - for (s = 0; s < 3; s++) { + for (s = 0; s < 2; s++) { for (j = 0; j < NCOLS; j++) { for (i = 0; i < nrows; i++) { - if (i * NCOLS + RM_PERM_CC[j] < ndummy) { - tmp[k] = TX_NULL; + if (s == 0) { + kidx = k%K_p; } else { - tmp[k] = input[(i * NCOLS + RM_PERM_CC[j] - ndummy) * 3 + s]; + kidx = K_p + 2 * (k%K_p); + } + if (i * NCOLS + RM_PERM_TC[j] < ndummy) { + tmp[kidx] = TX_NULL; + } else { + tmp[kidx] = input[(i * NCOLS + RM_PERM_TC[j] - ndummy) * 3 + s]; } k++; } } } - /* Bit collection, selection and transmission 5.1.4.1.2 */ + + // d_k^(2) goes through special permutation + for (k = 0; k < K_p; k++) { + kidx = (RM_PERM_TC[k / nrows] + NCOLS * (k % nrows) + 1) % K_p; + if ((kidx - ndummy) < 0) { + tmp[K_p + 2 * k + 1] = TX_NULL; + } else { + tmp[K_p + 2 * k + 1] = input[3 * (kidx - ndummy) + 2]; + } + } + + /* Bit selection and transmission 5.1.4.1.2 */ + N_cb = 3 * K_p; // TODO: Soft buffer size limitation + + k0 = nrows * (2 * (int) ceilf((float) N_cb / (float) (8 * nrows)) + * rv_idx + 2); k = 0; j = 0; + while (k < out_len) { - if (tmp[j] != TX_NULL) { - output[k] = tmp[j]; + if (tmp[(k0 + j) % N_cb] != TX_NULL) { + output[k] = tmp[(k0 + j) % N_cb]; k++; } j++; - if (j == RATE * K_p) { - j = 0; - } } return 0; } -/* Undoes Convolutional Code Rate Matching. - * 3GPP TS 36.212 v10.1.0 section 5.1.4.2 +/* Undoes Turbo Code Rate Matching. + * 3GPP TS 36.212 v10.1.0 section 5.1.4.1 */ -int rm_turbo_rx(float *input, float *output, int in_len, int out_len) { +int rm_turbo_rx(rm_turbo_t *q, float *input, int in_len, float *output, int out_len, int rv_idx) { - int nrows, ndummy, K_p; + int nrows, ndummy, K_p, k0, N_cb, jp, kidx; int i, j, k; int d_i, d_j; + bool isdummy; - float tmp[RATE * NCOLS * NROWS_MAX]; + float *tmp = (float*) q->buffer; nrows = (int) (out_len / RATE - 1) / NCOLS + 1; - if (nrows > NROWS_MAX) { - fprintf(stderr, "Output too large. Max output length is %d\n", - RATE * NCOLS * NROWS_MAX); + K_p = nrows * NCOLS; + if (3 * K_p > q->buffer_len) { + fprintf(stderr, + "Input too large. Max input length including dummy bits is %d\n", + q->buffer_len); return -1; } - K_p = nrows * NCOLS; ndummy = K_p - out_len / RATE; if (ndummy < 0) { @@ -119,24 +167,51 @@ int rm_turbo_rx(float *input, float *output, int in_len, int out_len) { } /* Undo bit collection. Account for dummy bits */ + N_cb = 3 * K_p; // TODO: Soft buffer size limitation + k0 = nrows * (2 * (int) ceilf((float) N_cb / (float) (8 * nrows)) + * rv_idx + 2); k = 0; j = 0; while (k < in_len) { - d_i = (j % K_p) / nrows; - d_j = (j % K_p) % nrows; + jp = (k0 + j) % N_cb; - if (d_j * NCOLS + RM_PERM_CC[d_i] >= ndummy) { - if (tmp[j] == RX_NULL) { - tmp[j] = input[k]; + if (jp == 32 || jp == 95 || jp == 0) { + i=0; + } + + if (jp < K_p || !(jp%2)) { + if (jp >= K_p) { + d_i = ((jp-K_p) / 2) / nrows; + d_j = ((jp-K_p) / 2) % nrows; + } else { + d_i = jp / nrows; + d_j = jp % nrows; + } + if (d_j * NCOLS + RM_PERM_TC[d_i] >= ndummy) { + isdummy = false; + } else { + isdummy = true; + } + } else { + int jpp = (jp-K_p-1)/2; + kidx = (RM_PERM_TC[jpp / nrows] + NCOLS * (jpp % nrows) + 1) % K_p; + q->d2_perm[kidx] = jpp; // save the permutation in a temporary buffer + if ((kidx - ndummy) < 0) { + isdummy = true; + } else { + isdummy = false; + } + } + + if (!isdummy) { + if (tmp[jp] == RX_NULL) { + tmp[jp] = input[k]; } else if (input[k] != RX_NULL) { - tmp[j] += input[k]; /* soft combine LLRs */ + tmp[jp] += input[k]; /* soft combine LLRs */ } k++; } j++; - if (j == RATE * K_p) { - j = 0; - } } /* interleaving and bit selection */ @@ -144,7 +219,13 @@ int rm_turbo_rx(float *input, float *output, int in_len, int out_len) { d_i = (i + ndummy) / NCOLS; d_j = (i + ndummy) % NCOLS; for (j = 0; j < RATE; j++) { - float o = tmp[K_p * j + RM_PERM_CC_INV[d_j] * nrows + d_i]; + if (j != 2) { + kidx = K_p * j + (j+1)*(RM_PERM_TC[d_j] * nrows + d_i); + } else { + // use the saved permuatation function to avoid computing the inverse + kidx = 2*q->d2_perm[(i+ndummy)%K_p]+K_p+1; + } + float o = tmp[kidx]; if (o != RX_NULL) { output[i * RATE + j] = o; } else { @@ -155,26 +236,27 @@ int rm_turbo_rx(float *input, float *output, int in_len, int out_len) { return 0; } + /** High-level API */ int rm_turbo_initialize(rm_turbo_hl* h) { - - return 0; + return rm_turbo_init(&h->q, 7000); } /** This function can be called in a subframe (1ms) basis */ int rm_turbo_work(rm_turbo_hl* hl) { if (hl->init.direction) { - //rm_turbo_tx(hl->input, hl->output, hl->in_len, hl->ctrl_in.S); - hl->out_len = hl->ctrl_in.S; - } else { - rm_turbo_rx(hl->input, hl->output, hl->in_len, hl->ctrl_in.E); + rm_turbo_tx(&hl->q, hl->input, hl->in_len, hl->output, hl->ctrl_in.E, hl->ctrl_in.rv_idx); hl->out_len = hl->ctrl_in.E; + } else { + rm_turbo_rx(&hl->q, hl->input, hl->in_len, hl->output, hl->ctrl_in.S, hl->ctrl_in.rv_idx); + hl->out_len = hl->ctrl_in.S; } return 0; } int rm_turbo_stop(rm_turbo_hl* hl) { + rm_turbo_free(&hl->q); return 0; } diff --git a/lte/lib/fec/src/turbodecoder.c2 b/lte/lib/fec/src/turbodecoder.c2 deleted file mode 100644 index a146236da..000000000 --- a/lte/lib/fec/src/turbodecoder.c2 +++ /dev/null @@ -1,304 +0,0 @@ -#include -#include -#include - -#include "lte/fec/permute.h" - -#include "lte/fec/turbodecoder.h" - - -void compute_beta(llr_t *beta, llr_t *data, llr_t *parity, int long_cb, int dec) { - llr_t m_b0, m_b1, m_b2, m_b3, m_b4, m_b5, m_b6, m_b7; - llr_t new0, new1, new2, new3, new4, new5, new6, new7; - llr_t old0, old1, old2, old3, old4, old5, old6, old7; - - llr_t x, y, xy; - int k; - int end = long_cb + RATE; - - old0 = beta[8 * (end) + 0]; - old1 = beta[8 * (end) + 1]; - old2 = beta[8 * (end) + 2]; - old3 = beta[8 * (end) + 3]; - old4 = beta[8 * (end) + 4]; - old5 = beta[8 * (end) + 5]; - old6 = beta[8 * (end) + 6]; - old7 = beta[8 * (end) + 7]; - - for (k = end - 1; k >= 0; k--) { - if (k > long_cb - 1) { - if (dec == 1) { - x = data[RATE * (long_cb ) + NINPUTS * (k - long_cb)]; - y = data[RATE * (long_cb ) + NINPUTS * (k - long_cb) + 1]; - } else { - x = data[RATE * (long_cb ) + NINPUTS * RATE + NINPUTS * (k - long_cb)]; - y = data[RATE * (long_cb ) + NINPUTS * RATE + NINPUTS * (k - long_cb) + 1]; - } - } else { - x = data[RATE * k]; - y = parity[RATE * k]; - } - xy = x + y; - - m_b0 = old4 + xy; - m_b1 = old4; - m_b2 = old5 + y; - m_b3 = old5 + x; - m_b4 = old6 + x; - m_b5 = old6 + y; - m_b6 = old7; - m_b7 = old7 + xy; - - new0 = old0; - new1 = old0 + xy; - new2 = old1 + x; - new3 = old1 + y; - new4 = old2 + y; - new5 = old2 + x; - new6 = old3 + xy; - new7 = old3; - - if (m_b0 > new0) new0 = m_b0; - beta[8 * k + 0] = new0; - old0 = new0; - - if (m_b1 > new1) new1 = m_b1; - beta[8 * k + 1] = new1; - old1 = new1; - - if (m_b2 > new2) new2 = m_b2; - beta[8 * k + 2] = new2; - old2 = new2; - - if (m_b3 > new3) new3 = m_b3; - beta[8 * k + 3] = new3; - old3 = new3; - - if (m_b4 > new4) new4 = m_b4; - beta[8 * k + 4] = new4; - old4 = new4; - - if (m_b5 > new5) new5 = m_b5; - beta[8 * k + 5] = new5; - old5 = new5; - - if (m_b6 > new6) new6 = m_b6; - beta[8 * k + 6] = new6; - old6 = new6; - - if (m_b7 > new7) new7 = m_b7; - beta[8 * k + 7] = new7; - old7 = new7; - - } - -} - -void compute_alfa(llr_t *alfa, llr_t *beta, llr_t *data, llr_t *parity, llr_t *output, int long_cb, int dec) { - llr_t m_b0, m_b1, m_b2, m_b3, m_b4, m_b5, m_b6, m_b7; - llr_t new0, new1, new2, new3, new4, new5, new6, new7; - llr_t old0, old1, old2, old3, old4, old5, old6, old7; - llr_t max1_0, max1_1, max1_2, max1_3, max1_4, max1_5, max1_6, max1_7; - llr_t max0_0, max0_1, max0_2, max0_3, max0_4, max0_5, max0_6, max0_7; - llr_t m1, m0; - llr_t x, y, xy; - llr_t out; - int k; - int end = long_cb; - - old0 = alfa[0]; - old1 = alfa[1]; - old2 = alfa[2]; - old3 = alfa[3]; - old4 = alfa[4]; - old5 = alfa[5]; - old6 = alfa[6]; - old7 = alfa[7]; - - for (k = 1; k < end + 1; k++) { - x = data[RATE * (k - 1)]; - y = parity[RATE * (k - 1)]; - - xy = x + y; - - m_b0 = old0; - m_b1 = old3 + y; - m_b2 = old4 + y; - m_b3 = old7; - m_b4 = old1; - m_b5 = old2 + y; - m_b6 = old5 + y; - m_b7 = old6; - - new0 = old1 + xy; - new1 = old2 + x; - new2 = old5 + x; - new3 = old6 + xy; - new4 = old0 + xy; - new5 = old3 + x; - new6 = old4 + x; - new7 = old7 + xy; - - max0_0 = m_b0 + beta[8 * k + 0]; - max0_1 = m_b1 + beta[8 * k + 1]; - max0_2 = m_b2 + beta[8 * k + 2]; - max0_3 = m_b3 + beta[8 * k + 3]; - max0_4 = m_b4 + beta[8 * k + 4]; - max0_5 = m_b5 + beta[8 * k + 5]; - max0_6 = m_b6 + beta[8 * k + 6]; - max0_7 = m_b7 + beta[8 * k + 7]; - - max1_0 = new0 + beta[8 * k + 0]; - max1_1 = new1 + beta[8 * k + 1]; - max1_2 = new2 + beta[8 * k + 2]; - max1_3 = new3 + beta[8 * k + 3]; - max1_4 = new4 + beta[8 * k + 4]; - max1_5 = new5 + beta[8 * k + 5]; - max1_6 = new6 + beta[8 * k + 6]; - max1_7 = new7 + beta[8 * k + 7]; - - m1 = max1_0; - if (max1_1 > m1) m1 = max1_1; - if (max1_2 > m1) m1 = max1_2; - if (max1_3 > m1) m1 = max1_3; - if (max1_4 > m1) m1 = max1_4; - if (max1_5 > m1) m1 = max1_5; - if (max1_6 > m1) m1 = max1_6; - if (max1_7 > m1) m1 = max1_7; - - m0 = max0_0; - if (max0_1 > m0) m0 = max0_1; - if (max0_2 > m0) m0 = max0_2; - if (max0_3 > m0) m0 = max0_3; - if (max0_4 > m0) m0 = max0_4; - if (max0_5 > m0) m0 = max0_5; - if (max0_6 > m0) m0 = max0_6; - if (max0_7 > m0) m0 = max0_7; - - - if (m_b0 > new0) new0 = m_b0; - old0 = new0; - - if (m_b1 > new1) new1 = m_b1; - old1 = new1; - - if (m_b2 > new2) new2 = m_b2; - old2 = new2; - - if (m_b3 > new3) new3 = m_b3; - old3 = new3; - - if (m_b4 > new4) new4 = m_b4; - old4 = new4; - - if (m_b5 > new5) new5 = m_b5; - old5 = new5; - - if (m_b6 > new6) new6 = m_b6; - old6 = new6; - - if (m_b7 > new7) new7 = m_b7; - old7 = new7; - - out = m1 - m0; - - /* - if (dec == 2) { - if (abs(out) < HALT_min) { - HALT_min = abs(out); - } - } - */ - output[k - 1] = out; - } - - alfa[0] = old0; - alfa[1] = old1; - alfa[2] = old2; - alfa[3] = old3; - alfa[4] = old4; - alfa[5] = old5; - alfa[6] = old6; - alfa[7] = old7; -} - -void DEC_RSC(tdec_t *q, llr_t *input, llr_t *output, int *per, int dec) { - int k; - int i; - int last_state = q->long_cb + RATE; - - /** Initialize alfa states */ - q->alfa[0] = 0; - for (k = 1; k < NUMSTATES; k++) { - q->alfa[k] = -INF; - } - - q->beta[last_state * NUMSTATES] = 0; - for (k = 1; k < NUMSTATES; k++) - q->beta[last_state * NUMSTATES + k] = -INF; - - /* copy data temporal buffer (to allow fastest loops)*/ - memcpy(q->data, input, RATE * last_state * sizeof (llr_t)); - - q->parity = &input[dec]; - - if (dec == 1) { - for (i = 0; i < last_state; i++) { - q->data[RATE * i] += q->W[i ]; - } - } else { - for (i = 0; i < last_state; i++) { - q->data[RATE * i] = q->LLR1[per[i ]] - q->W[per[i ]]; - } - } - - compute_beta(q->beta, q->data, &input[dec], q->long_cb, dec); - compute_alfa(q->alfa, q->beta, q->data, &input[dec], output, q->long_cb, dec); -} - -void decide(llr_t *LLR2, char *output, int *desper, int long_cb) { - int i; - - for (i = 0; i < long_cb; i++) - output[i] = (LLR2[desper[i]] > 0) ? 1 : 0; - -} - -void update_W(llr_t *W, llr_t *LLR1, llr_t *LLR2, int *desper, int long_cb) { - int i; - - for (i = 0; i < long_cb; i++) { - W[i] += LLR2[desper[i]] - LLR1[i]; - } -} - -int turbo_decoder(tdec_t *q, llr_t *input, char *output, int *halt) { - - int i; - long halt_mean=0; - int stop=0; - q->iteration = 0; - - - if (ComputePermutation(&q->permuta, q->long_cb,PER_UMTS)<0) - return -1; - - memset(q->W, 0, sizeof (llr_t) * q->long_cb); - - do { - if (q->iteration) - update_W(q->W, q->LLR1, q->LLR2, q->permuta.DESPER, q->long_cb); - - - DEC_RSC(q, input, q->LLR1, q->permuta.PER, 1); - - DEC_RSC(q, input, q->LLR2, q->permuta.PER, 2); - - q->iteration++; - - } while (q->iteration < q->max_iterations && stop==0); - decide(q->LLR2, output, q->permuta.DESPER, q->long_cb); - - return q->iteration; -} - diff --git a/lte/lib/fec/test/CMakeLists.txt b/lte/lib/fec/test/CMakeLists.txt index 95a9d5974..1d0cc28bb 100644 --- a/lte/lib/fec/test/CMakeLists.txt +++ b/lte/lib/fec/test/CMakeLists.txt @@ -27,6 +27,9 @@ ADD_EXECUTABLE(rm_conv_test rm_conv_test.c) TARGET_LINK_LIBRARIES(rm_conv_test lte) +ADD_EXECUTABLE(rm_turbo_test rm_turbo_test.c) +TARGET_LINK_LIBRARIES(rm_turbo_test lte) + ADD_TEST(rm_conv_test_1 rm_conv_test -t 480 -r 1920) ADD_TEST(rm_conv_test_2 rm_conv_test -t 1920 -r 480) diff --git a/lte/lib/fec/test/rm_conv_test.c b/lte/lib/fec/test/rm_conv_test.c index dc79ecceb..a6e8ab63f 100644 --- a/lte/lib/fec/test/rm_conv_test.c +++ b/lte/lib/fec/test/rm_conv_test.c @@ -100,7 +100,7 @@ int main(int argc, char **argv) { bits[i] = rand()%2; } - if (rm_conv_tx(bits, rm_bits, nof_tx_bits, nof_rx_bits)) { + if (rm_conv_tx(bits, nof_tx_bits, rm_bits, nof_rx_bits)) { exit(-1); } @@ -108,7 +108,7 @@ int main(int argc, char **argv) { rm_symbols[i] = rm_bits[i]?1:-1; } - if (rm_conv_rx(rm_symbols, unrm_symbols, nof_rx_bits, nof_tx_bits)) { + if (rm_conv_rx(rm_symbols, nof_rx_bits, unrm_symbols, nof_tx_bits)) { exit(-1); } diff --git a/lte/lib/fec/test/rm_turbo_test.c b/lte/lib/fec/test/rm_turbo_test.c index dc79ecceb..97adc0bfe 100644 --- a/lte/lib/fec/test/rm_turbo_test.c +++ b/lte/lib/fec/test/rm_turbo_test.c @@ -37,14 +37,15 @@ #include "lte.h" int nof_tx_bits=-1, nof_rx_bits=-1; +int rv_idx = 0; void usage(char *prog) { - printf("Usage: %s -t nof_tx_bits -r nof_rx_bits\n", prog); + printf("Usage: %s -t nof_tx_bits -r nof_rx_bits [-i rv_idx]\n", prog); } void parse_args(int argc, char **argv) { int opt; - while ((opt = getopt(argc, argv, "tr")) != -1) { + while ((opt = getopt(argc, argv, "tri")) != -1) { switch (opt) { case 't': nof_tx_bits = atoi(argv[optind]); @@ -52,6 +53,9 @@ void parse_args(int argc, char **argv) { case 'r': nof_rx_bits = atoi(argv[optind]); break; + case 'i': + rv_idx = atoi(argv[optind]); + break; default: usage(argv[0]); exit(-1); @@ -72,6 +76,7 @@ int main(int argc, char **argv) { char *bits, *rm_bits; float *rm_symbols, *unrm_symbols; int nof_errors; + rm_turbo_t rm_turbo; parse_args(argc, argv); @@ -100,36 +105,38 @@ int main(int argc, char **argv) { bits[i] = rand()%2; } - if (rm_conv_tx(bits, rm_bits, nof_tx_bits, nof_rx_bits)) { - exit(-1); - } + rm_turbo_init(&rm_turbo, 1000); + + rm_turbo_tx(&rm_turbo, bits, nof_tx_bits, rm_bits, nof_rx_bits, rv_idx); for (i=0;i 0) != bits[i]) { nof_errors++; + printf("%.2f != %d\n", unrm_symbols[i], bits[i]); } } - if (nof_rx_bits > nof_tx_bits) { - if (nof_errors) { - printf("nof_errors=%d\n", nof_errors); - exit(-1); - } - } + + rm_turbo_free(&rm_turbo); free(bits); free(rm_bits); free(rm_symbols); free(unrm_symbols); + if (nof_tx_bits >= nof_rx_bits) { + if (nof_errors) { + printf("nof_errors=%d\n", nof_errors); + exit(-1); + } + } + printf("Ok\n"); exit(0); } diff --git a/lte/lib/phch/src/pbch.c b/lte/lib/phch/src/pbch.c index e6a413f08..146bc9710 100644 --- a/lte/lib/phch/src/pbch.c +++ b/lte/lib/phch/src/pbch.c @@ -382,7 +382,7 @@ int pbch_decode_frame(pbch_t *q, pbch_mib_t *mib, int src, int dst, int n, int n } /* unrate matching */ - rm_conv_rx(q->temp, q->pbch_rm_f, 4 * nof_bits, 120); + rm_conv_rx(q->temp, 4 * nof_bits, q->pbch_rm_f, 120); /* FIXME: If channel estimates are zero, received LLR are NaN. Check and return error */ for (j=0;j<120;j++) { @@ -531,7 +531,7 @@ void pbch_encode(pbch_t *q, pbch_mib_t *mib, cf_t *slot1_symbols[MAX_PORTS_CTRL] convcoder_encode(&q->encoder, q->data, q->data_enc, 40); - rm_conv_tx(q->data_enc, q->pbch_rm_b, 120, 4 * nof_bits); + rm_conv_tx(q->data_enc, 120, q->pbch_rm_b, 4 * nof_bits); } diff --git a/lte/lib/phch/src/pdcch.c b/lte/lib/phch/src/pdcch.c index 0a8fe4e7a..33bf768ac 100644 --- a/lte/lib/phch/src/pdcch.c +++ b/lte/lib/phch/src/pdcch.c @@ -338,7 +338,7 @@ unsigned short dci_decode(pdcch_t *q, float *e, char *data, int E, } */ /* unrate matching */ - rm_conv_rx(e, tmp, E, 3 * (nof_bits + 16)); + rm_conv_rx(e, E, tmp, 3 * (nof_bits + 16)); DEBUG("Viterbi input: ", 0); if (VERBOSE_ISDEBUG()) { @@ -543,7 +543,7 @@ void dci_encode(pdcch_t *q, char *data, char *e, int nof_bits, int E, unsigned s vec_fprint_b(stdout, tmp, 3 * (nof_bits + 16)); } - rm_conv_tx(tmp, e, 3 * (nof_bits + 16), E); + rm_conv_tx(tmp, 3 * (nof_bits + 16), e, E); } /** Converts the MIB message to symbols mapped to SLOT #1 ready for transmission