Commit 361d3e58 authored by prasanth's avatar prasanth

this commit includes pucch format0 receiver and testbench for pucch format0...

this commit includes pucch format0 receiver and testbench for pucch format0 without ofdm. Also it has some fixes to pucch_nr.c
parent 08db842c
...@@ -1292,6 +1292,7 @@ set(PHY_SRC_UE ...@@ -1292,6 +1292,7 @@ set(PHY_SRC_UE
${OPENAIR1_DIR}/PHY/TOOLS/lut.c ${OPENAIR1_DIR}/PHY/TOOLS/lut.c
${PHY_POLARSRC} ${PHY_POLARSRC}
${PHY_LDPCSRC} ${PHY_LDPCSRC}
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/pucch_rx.c # added by prasanth
) )
set(PHY_NR_UE_SRC set(PHY_NR_UE_SRC
...@@ -2551,6 +2552,14 @@ add_executable(nr_pbchsim ...@@ -2551,6 +2552,14 @@ add_executable(nr_pbchsim
${T_SOURCE}) ${T_SOURCE})
target_link_libraries(nr_pbchsim -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB -Wl,--end-group m pthread ${ATLAS_LIBRARIES} ${T_LIB} dl) target_link_libraries(nr_pbchsim -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB -Wl,--end-group m pthread ${ATLAS_LIBRARIES} ${T_LIB} dl)
#PUCCH ---> Prashanth
add_executable(nr_pucchsim
${OPENAIR1_DIR}/SIMULATION/NR_PHY/pucchsim.c
${OPENAIR_DIR}/common/utils/backtrace.c
${T_SOURCE})
target_link_libraries(nr_pucchsim -Wl,--start-group UTIL SIMU PHY_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB CONFIG_LIB -Wl,--end-group m pthread ${ATLAS_LIBRARIES} ${T_LIB} dl)
#PUCCH ---> Prashanth
add_executable(nr_dlsim add_executable(nr_dlsim
${OPENAIR1_DIR}/SIMULATION/NR_PHY/dlsim.c ${OPENAIR1_DIR}/SIMULATION/NR_PHY/dlsim.c
${OPENAIR_DIR}/common/utils/backtrace.c ${OPENAIR_DIR}/common/utils/backtrace.c
......
...@@ -691,7 +691,7 @@ function main() { ...@@ -691,7 +691,7 @@ function main() {
echo_info "Compiling unitary tests simulators" echo_info "Compiling unitary tests simulators"
# TODO: fix: dlsim_tm4 pucchsim prachsim pdcchsim pbchsim mbmssim # TODO: fix: dlsim_tm4 pucchsim prachsim pdcchsim pbchsim mbmssim
#simlist="dlsim_tm4 dlsim ulsim pucchsim prachsim pdcchsim pbchsim mbmssim" #simlist="dlsim_tm4 dlsim ulsim pucchsim prachsim pdcchsim pbchsim mbmssim"
simlist="dlsim ulsim polartest ldpctest nr_pbchsim nr_dlschsim nr_dlsim" simlist="nr_pucchsim dlsim ulsim polartest ldpctest nr_pbchsim nr_dlschsim nr_dlsim"
for f in $simlist ; do for f in $simlist ; do
compilations \ compilations \
phy_simulators $f \ phy_simulators $f \
......
#include<stdio.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "PHY/impl_defs_nr.h"
#include "PHY/defs_nr_common.h"
#include "PHY/defs_nr_UE.h"
#include "PHY/NR_UE_TRANSPORT/pucch_nr.h"
#include "PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h"
#include "common/utils/LOG/log.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "T.h"
void nr_decode_pucch0( int32_t **rxdataF,
pucch_GroupHopping_t pucch_GroupHopping,
uint32_t n_id, // hoppingID higher layer parameter
uint8_t *payload,
NR_DL_FRAME_PARMS *frame_parms,
int16_t amp,
int nr_tti_tx,
uint8_t m0, // should come from resource set
uint8_t nrofSymbols, // should come from resource set
uint8_t startingSymbolIndex, // should come from resource set
uint16_t startingPRB, // should come from resource set
uint8_t nr_bit) { // is number of UCI bits to be decoded
int nr_sequences;
uint8_t *mcs;
if(nr_bit==1){
mcs=table1_mcs;
nr_sequences=4;
}
else if(nr_bit==2){
mcs=table2_mcs;
nr_sequences=8;
}
/*
* Implement TS 38.211 Subclause 6.3.2.3.1 Sequence generation
*
*/
/*
* Defining cyclic shift hopping TS 38.211 Subclause 6.3.2.2.2
*/
// alpha is cyclic shift
double alpha;
// lnormal is the OFDM symbol number in the PUCCH transmission where l=0 corresponds to the first OFDM symbol of the PUCCH transmission
//uint8_t lnormal;
// lprime is the index of the OFDM symbol in the slot that corresponds to the first OFDM symbol of the PUCCH transmission in the slot given by [5, TS 38.213]
//uint8_t lprime;
// mcs is provided by TC 38.213 subclauses 9.2.3, 9.2.4, 9.2.5 FIXME!
//uint8_t mcs;
/*
* in TS 38.213 Subclause 9.2.1 it is said that:
* for PUCCH format 0 or PUCCH format 1, the index of the cyclic shift
* is indicated by higher layer parameter PUCCH-F0-F1-initial-cyclic-shift
*/
/*
* Implementing TS 38.211 Subclause 6.3.2.3.1, the sequence x(n) shall be generated according to:
* x(l*12+n) = r_u_v_alpha_delta(n)
*/
// the value of u,v (delta always 0 for PUCCH) has to be calculated according to TS 38.211 Subclause 6.3.2.2.1
uint8_t u=0,v=0;//,delta=0;
// if frequency hopping is disabled by the higher-layer parameter PUCCH-frequency-hopping
// n_hop = 0
// if frequency hopping is enabled by the higher-layer parameter PUCCH-frequency-hopping
// n_hop = 0 for first hop
// n_hop = 1 for second hop
uint8_t n_hop = 0;
//uint8_t PUCCH_Frequency_Hopping; // from higher layers FIXME!!
// x_n contains the sequence r_u_v_alpha_delta(n)
int16_t x_n_re[nr_sequences][24],x_n_im[nr_sequences][24];
int n,i,l;
for(i=0;i<nr_sequences;i++){
// we proceed to calculate alpha according to TS 38.211 Subclause 6.3.2.2.2
for (l=0; l<nrofSymbols; l++){
// if frequency hopping is enabled n_hop = 1 for second hop. Not sure frequency hopping concerns format 0. FIXME!!!
// if ((PUCCH_Frequency_Hopping == 1)&&(l == (nrofSymbols-1))) n_hop = 1;
nr_group_sequence_hopping(pucch_GroupHopping,n_id,n_hop,nr_tti_tx,&u,&v); // calculating u and v value
alpha = nr_cyclic_shift_hopping(n_id,m0,mcs[i],l,startingSymbolIndex,nr_tti_tx);
#ifdef DEBUG_NR_PUCCH_TX
printf("\t [nr_generate_pucch0] sequence generation \tu=%d \tv=%d \talpha=%lf \t(for symbol l=%d)\n",u,v,alpha,l);
#endif
for (n=0; n<12; n++){
x_n_re[i][(12*l)+n] = (int16_t)((int32_t)(amp)*(int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15)
- (((int32_t)(round(32767*sin(alpha*n))) * table_5_2_2_2_2_Im[u][n])>>15)))>>15); // Re part of base sequence shifted by alpha
x_n_im[i][(12*l)+n] =(int16_t)((int32_t)(amp)* (int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Im[u][n])>>15)
+ (((int32_t)(round(32767*sin(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15)))>>15); // Im part of base sequence shifted by alpha
#ifdef DEBUG_NR_PUCCH_TX
printf("\t [nr_generate_pucch0] sequence generation \tu=%d \tv=%d \talpha=%lf \tx_n(l=%d,n=%d)=(%d,%d)\n",
u,v,alpha,l,n,x_n_re[(12*l)+n],x_n_im[(12*l)+n]);
#endif
}
}
}
int16_t r_re[24],r_im[24];
/*
* Implementing TS 38.211 Subclause 6.3.2.3.2 Mapping to physical resources FIXME!
*/
uint32_t re_offset=0;
for (l=0; l<nrofSymbols; l++) {
if ((startingPRB < (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 0)) { // if number RBs in bandwidth is even and current PRB is lower band
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size) + (12*startingPRB) + frame_parms->first_carrier_offset;
}
if ((startingPRB >= (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 0)) { // if number RBs in bandwidth is even and current PRB is upper band
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size) + (12*(startingPRB-(frame_parms->N_RB_DL>>1)));
}
if ((startingPRB < (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 1)) { // if number RBs in bandwidth is odd and current PRB is lower band
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size) + (12*startingPRB) + frame_parms->first_carrier_offset;
}
if ((startingPRB > (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 1)) { // if number RBs in bandwidth is odd and current PRB is upper band
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size) + (12*(startingPRB-(frame_parms->N_RB_DL>>1))) + 6;
}
if ((startingPRB == (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 1)) { // if number RBs in bandwidth is odd and current PRB contains DC
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size) + (12*startingPRB) + frame_parms->first_carrier_offset;
}
for (n=0; n<12; n++){
if ((n==6) && (startingPRB == (frame_parms->N_RB_DL>>1)) && ((frame_parms->N_RB_DL & 1) == 1)) {
// if number RBs in bandwidth is odd and current PRB contains DC, we need to recalculate the offset when n=6 (for second half PRB)
re_offset = ((l+startingSymbolIndex)*frame_parms->ofdm_symbol_size);
}
r_re[(12*l)+n]=((int16_t *)&rxdataF[0][re_offset])[0];
r_im[(12*l)+n]=((int16_t *)&rxdataF[0][re_offset])[1];
#ifdef DEBUG_NR_PUCCH_TX
printf("\t [nr_generate_pucch0] mapping to RE \t amp=%d \tofdm_symbol_size=%d \tN_RB_DL=%d \tfirst_carrier_offset=%d \ttxptr(%d)=(x_n(l=%d,n=%d)=(%d,%d))\n",
amp,frame_parms->ofdm_symbol_size,frame_parms->N_RB_DL,frame_parms->first_carrier_offset,re_offset,
l,n,((int16_t *)&rxdataF[0][re_offset])[0],((int16_t *)&rxdataF[0][re_offset])[1]);
#endif
re_offset++;
}
}
double corr[nr_sequences],corr_re[nr_sequences],corr_im[nr_sequences];
memset(corr,0,nr_sequences*sizeof(double));
memset(corr_re,0,nr_sequences*sizeof(double));
memset(corr_im,0,nr_sequences*sizeof(double));
for(i=0;i<nr_sequences;i++){
for(l=0;l<nrofSymbols;l++){
for(n=0;n<12;n++){
corr_re[i]+= (double)(r_re[12*l+n])/32767*(double)(x_n_re[i][12*l+n])/32767+(double)(r_im[12*l+n])/32767*(double)(x_n_im[i][12*l+n])/32767;
corr_im[i]+= (double)(r_re[12*l+n])/32767*(double)(x_n_im[i][12*l+n])/32767-(double)(r_im[12*l+n])/32767*(double)(x_n_re[i][12*l+n])/32767;
}
}
corr[i]=corr_re[i]*corr_re[i]+corr_im[i]*corr_im[i];
}
float max_corr=corr[0];
int index=0;
for(i=1;i<nr_sequences;i++){
if(corr[i]>max_corr){
index= i;
max_corr=corr[i];
}
}
*payload=(uint8_t)index; // payload bits 00..b3b2b0, b0 is the SR bit and b3b2 are HARQ bits
}
...@@ -48,14 +48,12 @@ ...@@ -48,14 +48,12 @@
#endif #endif
//#define ONE_OVER_SQRT2 23170 // 32767/sqrt(2) = 23170 (ONE_OVER_SQRT2) //#define ONE_OVER_SQRT2 23170 // 32767/sqrt(2) = 23170 (ONE_OVER_SQRT2)
void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_nr.puch_GroupHopping, void nr_group_sequence_hopping (pucch_GroupHopping_t PUCCH_GroupHopping,
//uint8_t PUCCH_GroupHopping, uint32_t n_id,
PHY_VARS_NR_UE *ue, uint8_t n_hop,
//uint32_t n_id, int nr_tti_tx,
uint8_t n_hop, uint8_t *u,
int nr_tti_tx, uint8_t *v) {
uint8_t *u,
uint8_t *v) {
/* /*
* Implements TS 38.211 subclause 6.3.2.2.1 Group and sequence hopping * Implements TS 38.211 subclause 6.3.2.2.1 Group and sequence hopping
* The following variables are set by higher layers: * The following variables are set by higher layers:
...@@ -69,12 +67,12 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n ...@@ -69,12 +67,12 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n
* n_hop=1 for the second hop * n_hop=1 for the second hop
*/ */
// depending on the value of the PUCCH_GroupHopping, we will obtain different values for u,v // depending on the value of the PUCCH_GroupHopping, we will obtain different values for u,v
pucch_GroupHopping_t PUCCH_GroupHopping = ue->pucch_config_common_nr->pucch_GroupHopping; // from higher layers FIXME!!! //pucch_GroupHopping_t PUCCH_GroupHopping = ue->pucch_config_common_nr->pucch_GroupHopping; // from higher layers FIXME!!!
// n_id defined as per TS 38.211 subclause 6.3.2.2.1 (is given by the higher-layer parameter hoppingId) // n_id defined as per TS 38.211 subclause 6.3.2.2.1 (is given by the higher-layer parameter hoppingId)
// it is hoppingId from PUCCH-ConfigCommon: // it is hoppingId from PUCCH-ConfigCommon:
// Cell-Specific scrambling ID for group hoppping and sequence hopping if enabled // Cell-Specific scrambling ID for group hoppping and sequence hopping if enabled
// Corresponds to L1 parameter 'HoppingID' (see 38.211, section 6.3.2.2) BIT STRING (SIZE (10)) // Corresponds to L1 parameter 'HoppingID' (see 38.211, section 6.3.2.2) BIT STRING (SIZE (10))
uint16_t n_id = ue->pucch_config_common_nr->hoppingId; // from higher layers FIXME!!! //uint16_t n_id = ue->pucch_config_common_nr->hoppingId; // from higher layers FIXME!!!
#ifdef DEBUG_NR_PUCCH_TX #ifdef DEBUG_NR_PUCCH_TX
// initialization to be removed // initialization to be removed
PUCCH_GroupHopping=neither; PUCCH_GroupHopping=neither;
...@@ -84,8 +82,8 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n ...@@ -84,8 +82,8 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n
uint8_t f_ss=0,f_gh=0; uint8_t f_ss=0,f_gh=0;
*u=0; *u=0;
*v=0; *v=0;
uint32_t c_init = (1<<5)*floor(n_id/30)+(n_id%30); // we initialize c_init to calculate u,v uint32_t c_init = 0;
uint32_t x1,s = lte_gold_generic(&x1, &c_init, 1); // TS 38.211 Subclause 5.2.1 uint32_t x1,s; // TS 38.211 Subclause 5.2.1
int l = 32, minShift = ((2*nr_tti_tx+n_hop)<<3); int l = 32, minShift = ((2*nr_tti_tx+n_hop)<<3);
int tmpShift =0; int tmpShift =0;
#ifdef DEBUG_NR_PUCCH_TX #ifdef DEBUG_NR_PUCCH_TX
...@@ -97,6 +95,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n ...@@ -97,6 +95,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n
} }
if (PUCCH_GroupHopping == enable) { // PUCCH_GroupHopping 'enabled' if (PUCCH_GroupHopping == enable) { // PUCCH_GroupHopping 'enabled'
c_init = floor(n_id/30); // we initialize c_init to calculate u,v according to 6.3.2.2.1 of 38.211
for (int m=0; m<8; m++) { for (int m=0; m<8; m++) {
while(minShift >= l) { while(minShift >= l) {
s = lte_gold_generic(&x1, &c_init, 0); s = lte_gold_generic(&x1, &c_init, 0);
...@@ -118,6 +117,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n ...@@ -118,6 +117,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n
} }
if (PUCCH_GroupHopping == disable) { // PUCCH_GroupHopping 'disabled' if (PUCCH_GroupHopping == disable) { // PUCCH_GroupHopping 'disabled'
c_init = (1<<5)*floor(n_id/30)+(n_id%30); // we initialize c_init to calculate u,v
f_ss = n_id%30; f_ss = n_id%30;
l = 32, minShift = (2*nr_tti_tx+n_hop); l = 32, minShift = (2*nr_tti_tx+n_hop);
...@@ -137,7 +137,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n ...@@ -137,7 +137,7 @@ void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_n
#endif #endif
} }
double nr_cyclic_shift_hopping(PHY_VARS_NR_UE *ue, double nr_cyclic_shift_hopping(uint32_t n_id,
uint8_t m0, uint8_t m0,
uint8_t mcs, uint8_t mcs,
uint8_t lnormal, uint8_t lnormal,
...@@ -153,7 +153,7 @@ double nr_cyclic_shift_hopping(PHY_VARS_NR_UE *ue, ...@@ -153,7 +153,7 @@ double nr_cyclic_shift_hopping(PHY_VARS_NR_UE *ue,
*/ */
// alpha_init initialized to 2*PI/12=0.5235987756 // alpha_init initialized to 2*PI/12=0.5235987756
double alpha = 0.5235987756; double alpha = 0.5235987756;
uint32_t c_init = ue->pucch_config_common_nr->hoppingId; // we initialize c_init again to calculate n_cs uint32_t c_init = n_id; // we initialize c_init again to calculate n_cs
#ifdef DEBUG_NR_PUCCH_TX #ifdef DEBUG_NR_PUCCH_TX
// initialization to be remo.ved // initialization to be remo.ved
c_init=10; c_init=10;
...@@ -246,8 +246,8 @@ void nr_generate_pucch0(PHY_VARS_NR_UE *ue, ...@@ -246,8 +246,8 @@ void nr_generate_pucch0(PHY_VARS_NR_UE *ue,
for (int l=0; l<nrofSymbols; l++) { for (int l=0; l<nrofSymbols; l++) {
// if frequency hopping is enabled n_hop = 1 for second hop. Not sure frequency hopping concerns format 0. FIXME!!! // if frequency hopping is enabled n_hop = 1 for second hop. Not sure frequency hopping concerns format 0. FIXME!!!
// if ((PUCCH_Frequency_Hopping == 1)&&(l == (nrofSymbols-1))) n_hop = 1; // if ((PUCCH_Frequency_Hopping == 1)&&(l == (nrofSymbols-1))) n_hop = 1;
nr_group_sequence_hopping(ue,n_hop,nr_tti_tx,&u,&v); // calculating u and v value nr_group_sequence_hopping(ue->pucch_config_common_nr->pucch_GroupHopping,ue->pucch_config_common_nr->hoppingId,n_hop,nr_tti_tx,&u,&v); // calculating u and v value
alpha = nr_cyclic_shift_hopping(ue,m0,mcs,l,startingSymbolIndex,nr_tti_tx); alpha = nr_cyclic_shift_hopping(ue->pucch_config_common_nr->hoppingId,m0,mcs,l,startingSymbolIndex,nr_tti_tx);
#ifdef DEBUG_NR_PUCCH_TX #ifdef DEBUG_NR_PUCCH_TX
printf("\t [nr_generate_pucch0] sequence generation \tu=%d \tv=%d \talpha=%lf \t(for symbol l=%d)\n",u,v,alpha,l); printf("\t [nr_generate_pucch0] sequence generation \tu=%d \tv=%d \talpha=%lf \t(for symbol l=%d)\n",u,v,alpha,l);
#endif #endif
...@@ -435,8 +435,8 @@ void nr_generate_pucch1(PHY_VARS_NR_UE *ue, ...@@ -435,8 +435,8 @@ void nr_generate_pucch1(PHY_VARS_NR_UE *ue,
printf("\t [nr_generate_pucch1] entering function nr_group_sequence_hopping with n_hop=%d, nr_tti_tx=%d\n", printf("\t [nr_generate_pucch1] entering function nr_group_sequence_hopping with n_hop=%d, nr_tti_tx=%d\n",
n_hop,nr_tti_tx); n_hop,nr_tti_tx);
#endif #endif
nr_group_sequence_hopping(ue,n_hop,nr_tti_tx,&u,&v); // calculating u and v value nr_group_sequence_hopping(ue->pucch_config_common_nr->pucch_GroupHopping,ue->pucch_config_common_nr->hoppingId,n_hop,nr_tti_tx,&u,&v); // calculating u and v value
alpha = nr_cyclic_shift_hopping(ue,m0,mcs,l,lprime,nr_tti_tx); alpha = nr_cyclic_shift_hopping(ue->pucch_config_common_nr->hoppingId,m0,mcs,l,lprime,nr_tti_tx);
for (int n=0; n<12; n++) { for (int n=0; n<12; n++) {
r_u_v_alpha_delta_re[n] = (int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15) r_u_v_alpha_delta_re[n] = (int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15)
...@@ -753,8 +753,8 @@ void nr_generate_pucch1_old(PHY_VARS_NR_UE *ue, ...@@ -753,8 +753,8 @@ void nr_generate_pucch1_old(PHY_VARS_NR_UE *ue,
printf("\t [nr_generate_pucch1] entering function nr_group_sequence_hopping with n_hop=%d, nr_tti_tx=%d\n", printf("\t [nr_generate_pucch1] entering function nr_group_sequence_hopping with n_hop=%d, nr_tti_tx=%d\n",
n_hop,nr_tti_tx); n_hop,nr_tti_tx);
#endif #endif
nr_group_sequence_hopping(ue,n_hop,nr_tti_tx,&u,&v); // calculating u and v value nr_group_sequence_hopping(ue->pucch_config_common_nr->pucch_GroupHopping,ue->pucch_config_common_nr->hoppingId,n_hop,nr_tti_tx,&u,&v); // calculating u and v value
alpha = nr_cyclic_shift_hopping(ue,m0,mcs,lnormal,lprime,nr_tti_tx); alpha = nr_cyclic_shift_hopping(ue->pucch_config_common_nr->hoppingId,m0,mcs,lnormal,lprime,nr_tti_tx);
for (int n=0; n<12; n++) { for (int n=0; n<12; n++) {
r_u_v_alpha_delta_re[n] = (int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15) r_u_v_alpha_delta_re[n] = (int16_t)(((((int32_t)(round(32767*cos(alpha*n))) * table_5_2_2_2_2_Re[u][n])>>15)
...@@ -1571,7 +1571,7 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue, ...@@ -1571,7 +1571,7 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue,
for (int l=0; l<nrofSymbols; l++) { for (int l=0; l<nrofSymbols; l++) {
if ((intraSlotFrequencyHopping == 1) && (l >= (int)floor(nrofSymbols/2))) n_hop = 1; // n_hop = 1 for second hop if ((intraSlotFrequencyHopping == 1) && (l >= (int)floor(nrofSymbols/2))) n_hop = 1; // n_hop = 1 for second hop
nr_group_sequence_hopping(ue,n_hop,nr_tti_tx,&u,&v); // calculating u and v value nr_group_sequence_hopping(ue->pucch_config_common_nr->pucch_GroupHopping,ue->pucch_config_common_nr->hoppingId,n_hop,nr_tti_tx,&u,&v); // calculating u and v value
// Next we proceed to calculate base sequence for DM-RS signal, according to TS 38.211 subclause 6.4.1.33 // Next we proceed to calculate base sequence for DM-RS signal, according to TS 38.211 subclause 6.4.1.33
if (nrofPRB >= 3) { // TS 38.211 subclause 5.2.2.1 (Base sequences of length 36 or larger) applies if (nrofPRB >= 3) { // TS 38.211 subclause 5.2.2.1 (Base sequences of length 36 or larger) applies
...@@ -1617,7 +1617,7 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue, ...@@ -1617,7 +1617,7 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue,
} }
uint16_t j=0; uint16_t j=0;
alpha = nr_cyclic_shift_hopping(ue,m0,mcs,l,startingSymbolIndex,nr_tti_tx); alpha = nr_cyclic_shift_hopping(ue->pucch_config_common_nr->hoppingId,m0,mcs,l,startingSymbolIndex,nr_tti_tx);
for (int rb=0; rb<nrofPRB; rb++) { for (int rb=0; rb<nrofPRB; rb++) {
if ((intraSlotFrequencyHopping == 1) && (l<floor(nrofSymbols/2))) { // intra-slot hopping enabled, we need to calculate new offset PRB if ((intraSlotFrequencyHopping == 1) && (l<floor(nrofSymbols/2))) { // intra-slot hopping enabled, we need to calculate new offset PRB
......
...@@ -42,15 +42,26 @@ ...@@ -42,15 +42,26 @@
#include "T.h" #include "T.h"
#define ONE_OVER_SQRT2 23170 // 32767/sqrt(2) = 23170 (ONE_OVER_SQRT2) #define ONE_OVER_SQRT2 23170 // 32767/sqrt(2) = 23170 (ONE_OVER_SQRT2)
void nr_group_sequence_hopping (//pucch_GroupHopping_t ue->pucch_config_common_nr.puch_GroupHopping, void nr_decode_pucch0( int32_t **rxdataF,
//uint8_t PUCCH_GroupHopping, pucch_GroupHopping_t PUCCH_GroupHopping,
PHY_VARS_NR_UE *ue, uint32_t n_id, //PHY_VARS_gNB *gNB, generally rxdataf is in gNB->common_vars
//uint32_t n_id, uint8_t *payload,
NR_DL_FRAME_PARMS *frame_parms,
int16_t amp,
int nr_tti_tx,
uint8_t m0, // should come from resource set
uint8_t nrofSymbols, // should come from resource set
uint8_t startingSymbolIndex, // should come from resource set
uint16_t startingPRB, // should come from resource set
uint8_t nr_bit);
void nr_group_sequence_hopping (pucch_GroupHopping_t PUCCH_GroupHopping,
uint32_t n_id,
uint8_t n_hop, uint8_t n_hop,
int nr_tti_tx, int nr_tti_tx,
uint8_t *u, uint8_t *u,
uint8_t *v); uint8_t *v);
double nr_cyclic_shift_hopping(PHY_VARS_NR_UE *ue, double nr_cyclic_shift_hopping(uint32_t n_id,
uint8_t m0, uint8_t m0,
uint8_t mcs, uint8_t mcs,
uint8_t lnormal, uint8_t lnormal,
...@@ -111,6 +122,11 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue, ...@@ -111,6 +122,11 @@ void nr_generate_pucch3_4(PHY_VARS_NR_UE *ue,
uint8_t nr_bit, uint8_t nr_bit,
uint8_t occ_length_format4, uint8_t occ_length_format4,
uint8_t occ_index_format4); uint8_t occ_index_format4);
// tables for mcs values for different payloads
static const uint8_t table1_mcs[]={0,3,6,9};
static const uint8_t table2_mcs[]={0,1,3,4,6,7,9,10};
/* /*
* The following tables implement TS 38.211 Subclause 5.2.2.2 Base sequences of length less than 36 (rows->u {0,1,..,29} / columns->n {0,1,...,M_ZC-1) * The following tables implement TS 38.211 Subclause 5.2.2.2 Base sequences of length less than 36 (rows->u {0,1,..,29} / columns->n {0,1,...,M_ZC-1)
* Where base sequence r_u_v(n)=exp[j*phi(n)*pi/4] 0<=n<=M_ZC-1 and M_ZC={6,12,18,24} * Where base sequence r_u_v(n)=exp[j*phi(n)*pi/4] 0<=n<=M_ZC-1 and M_ZC={6,12,18,24}
......
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "common/config/config_userapi.h"
#include "common/utils/LOG/log.h"
#include "common/ran_context.h"
#include "SIMULATION/TOOLS/sim.h"
#include "SIMULATION/RF/rf.h"
#include "PHY/types.h"
#include "PHY/defs_nr_common.h"
#include "PHY/defs_nr_UE.h"
#include "PHY/defs_gNB.h"
#include "PHY/NR_REFSIG/refsig_defs_ue.h"
#include "PHY/NR_REFSIG/nr_mod_table.h"
#include "PHY/MODULATION/modulation_eNB.h"
#include "PHY/MODULATION/modulation_UE.h"
#include "PHY/INIT/phy_init.h"
#include "PHY/NR_TRANSPORT/nr_transport.h"
#include "PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h"
#include "SCHED_NR/sched_nr.h"
#include "PHY/NR_UE_TRANSPORT/pucch_nr.h"
PHY_VARS_gNB *gNB;
PHY_VARS_NR_UE *UE;
RAN_CONTEXT_t RC;
double cpuf;
// dummy functions
int nfapi_mode=0;
int oai_nfapi_hi_dci0_req(nfapi_hi_dci0_request_t *hi_dci0_req) { return(0);}
int oai_nfapi_tx_req(nfapi_tx_request_t *tx_req) { return(0); }
int oai_nfapi_dl_config_req(nfapi_dl_config_request_t *dl_config_req) { return(0); }
int oai_nfapi_ul_config_req(nfapi_ul_config_request_t *ul_config_req) { return(0); }
int oai_nfapi_nr_dl_config_req(nfapi_nr_dl_config_request_t *dl_config_req) {return(0);}
uint32_t from_nrarfcn(int nr_bandP,uint32_t dl_nrarfcn) {return(0);}
int32_t get_uldl_offset(int nr_bandP) {return(0);}
NR_IF_Module_t *NR_IF_Module_init(int Mod_id){return(NULL);}
void exit_function(const char* file, const char* function, const int line,const char *s) {
const char * msg= s==NULL ? "no comment": s;
printf("Exiting at: %s:%d %s(), %s\n", file, line, function, msg);
exit(-1);
}
// needed for some functions
PHY_VARS_NR_UE * PHY_vars_UE_g[1][1]={{NULL}};
int main(int argc, char **argv)
{
char c;
int i,aa;//,l;
double sigma2, sigma2_dB=10,SNR,snr0=-2.0,snr1=2.0;
double cfo=0;
uint8_t snr1set=0;
int **txdata;
double **s_re,**s_im,**r_re,**r_im;
double iqim = 0.0;
double ip =0.0;
unsigned char pbch_pdu[6];
// int sync_pos, sync_pos_slot;
// FILE *rx_frame_file;
FILE *output_fd = NULL;
uint8_t write_output_file=0;
//int result;
//int freq_offset;
// int subframe_offset;
// char fname[40], vname[40];
int trial,n_trials=1,n_errors=0,n_errors_payload=0;
uint8_t transmission_mode = 1,n_tx=1,n_rx=1;
uint16_t Nid_cell=0;
channel_desc_t *gNB2UE;
uint8_t extended_prefix_flag=0;
int8_t interf1=-21,interf2=-21;
FILE *input_fd=NULL,*pbch_file_fd=NULL;
//uint32_t nsymb,tx_lev,tx_lev1 = 0,tx_lev2 = 0;
//char input_val_str[50],input_val_str2[50];
//uint8_t frame_mod4,num_pdcch_symbols = 0;
//double pbch_sinr;
//int pbch_tx_ant;
SCM_t channel_model=AWGN;//Rayleigh1_anticorr;
int N_RB_DL=273,mu=1;
unsigned char frame_type = 0;
unsigned char pbch_phase = 0;
int frame=0,subframe=0;
int frame_length_complex_samples;
int frame_length_complex_samples_no_prefix;
NR_DL_FRAME_PARMS *frame_parms;
nfapi_nr_config_request_t *gNB_config;
int ret, payload_ret=0;
int run_initial_sync=0;
int loglvl=OAILOG_WARNING;
float target_error_rate = 0.01;
cpuf = get_cpu_freq_GHz();
if ( load_configmodule(argc,argv) == 0) {
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
}
randominit(0);
while ((c = getopt (argc, argv, "f:hA:pf:g:i:j:n:o:s:S:t:x:y:z:N:F:GR:dP:IL:")) != -1) {
switch (c) {
case 'f':
write_output_file=1;
output_fd = fopen(optarg,"w");
if (output_fd==NULL) {
printf("Error opening %s\n",optarg);
exit(-1);
}
break;
case 'd':
frame_type = 1;
break;
case 'g':
switch((char)*optarg) {
case 'A':
channel_model=SCM_A;
break;
case 'B':
channel_model=SCM_B;
break;
case 'C':
channel_model=SCM_C;
break;
case 'D':
channel_model=SCM_D;
break;
case 'E':
channel_model=EPA;
break;
case 'F':
channel_model=EVA;
break;
case 'G':
channel_model=ETU;
break;
default:
msg("Unsupported channel model!\n");
exit(-1);
}
break;
case 'i':
interf1=atoi(optarg);
break;
case 'j':
interf2=atoi(optarg);
break;
case 'n':
n_trials = atoi(optarg);
break;
case 'o':
cfo = atof(optarg);
msg("Setting CFO to %f Hz\n",cfo);
break;
case 's':
snr0 = atof(optarg);
msg("Setting SNR0 to %f\n",snr0);
break;
case 'S':
snr1 = atof(optarg);
snr1set=1;
msg("Setting SNR1 to %f\n",snr1);
break;
/*
case 't':
Td= atof(optarg);
break;
*/
case 'p':
extended_prefix_flag=1;
break;
/*
case 'r':
ricean_factor = pow(10,-.1*atof(optarg));
if (ricean_factor>1) {
printf("Ricean factor must be between 0 and 1\n");
exit(-1);
}
break;
*/
case 'x':
transmission_mode=atoi(optarg);
if ((transmission_mode!=1) &&
(transmission_mode!=2) &&
(transmission_mode!=6)) {
msg("Unsupported transmission mode %d\n",transmission_mode);
exit(-1);
}
break;
case 'y':
n_tx=atoi(optarg);
if ((n_tx==0) || (n_tx>2)) {
msg("Unsupported number of tx antennas %d\n",n_tx);
exit(-1);
}
break;
case 'z':
n_rx=atoi(optarg);
if ((n_rx==0) || (n_rx>2)) {
msg("Unsupported number of rx antennas %d\n",n_rx);
exit(-1);
}
break;
case 'N':
Nid_cell = atoi(optarg);
break;
case 'R':
N_RB_DL = atoi(optarg);
break;
case 'F':
input_fd = fopen(optarg,"r");
if (input_fd==NULL) {
printf("Problem with filename %s\n",optarg);
exit(-1);
}
break;
case 'P':
pbch_phase = atoi(optarg);
if (pbch_phase>3)
printf("Illegal PBCH phase (0-3) got %d\n",pbch_phase);
break;
case 'I':
run_initial_sync=1;
target_error_rate=0.1;
break;
case 'L':
loglvl = atoi(optarg);
break;
default:
case 'h':
printf("%s -h(elp) -p(extended_prefix) -N cell_id -f output_filename -F input_filename -g channel_model -n n_frames -t Delayspread -s snr0 -S snr1 -x transmission_mode -y TXant -z RXant -i Intefrence0 -j Interference1 -A interpolation_file -C(alibration offset dB) -N CellId\n",
argv[0]);
printf("-h This message\n");
printf("-p Use extended prefix mode\n");
printf("-d Use TDD\n");
printf("-n Number of frames to simulate\n");
printf("-s Starting SNR, runs from SNR0 to SNR0 + 5 dB. If n_frames is 1 then just SNR is simulated\n");
printf("-S Ending SNR, runs from SNR0 to SNR1\n");
printf("-t Delay spread for multipath channel\n");
printf("-g [A,B,C,D,E,F,G] Use 3GPP SCM (A,B,C,D) or 36-101 (E-EPA,F-EVA,G-ETU) models (ignores delay spread and Ricean factor)\n");
printf("-x Transmission mode (1,2,6 for the moment)\n");
printf("-y Number of TX antennas used in eNB\n");
printf("-z Number of RX antennas used in UE\n");
printf("-i Relative strength of first intefering eNB (in dB) - cell_id mod 3 = 1\n");
printf("-j Relative strength of second intefering eNB (in dB) - cell_id mod 3 = 2\n");
printf("-o Carrier frequency offset in Hz\n");
printf("-N Nid_cell\n");
printf("-R N_RB_DL\n");
printf("-O oversampling factor (1,2,4,8,16)\n");
printf("-A Interpolation_filname Run with Abstraction to generate Scatter plot using interpolation polynomial in file\n");
// printf("-C Generate Calibration information for Abstraction (effective SNR adjustment to remove Pe bias w.r.t. AWGN)\n");
printf("-f Output filename (.txt format) for Pe/SNR results\n");
printf("-F Input filename (.txt format) for RX conformance testing\n");
exit (-1);
break;
}
}
logInit();
set_glog(loglvl);
T_stdout = 1;
if (snr1set==0)
snr1 = snr0+10;
printf("Initializing gNodeB for mu %d, N_RB_DL %d\n",mu,N_RB_DL);
RC.gNB = (PHY_VARS_gNB***) malloc(sizeof(PHY_VARS_gNB **));
RC.gNB[0] = (PHY_VARS_gNB**) malloc(sizeof(PHY_VARS_gNB *));
RC.gNB[0][0] = malloc(sizeof(PHY_VARS_gNB));
gNB = RC.gNB[0][0];
gNB_config = &gNB->gNB_config;
frame_parms = &gNB->frame_parms; //to be initialized I suppose (maybe not necessary for PBCH)
frame_parms->nb_antennas_tx = n_tx;
frame_parms->nb_antennas_rx = n_rx;
frame_parms->N_RB_DL = N_RB_DL;
frame_parms->N_RB_UL = N_RB_DL;
frame_parms->Nid_cell = Nid_cell;
nr_phy_config_request_sim(gNB,N_RB_DL,N_RB_DL,mu,Nid_cell);
phy_init_nr_gNB(gNB,0,0);
double fs,bw,scs,eps;
if (mu == 1 && N_RB_DL == 217) {
fs = 122.88e6;
bw = 80e6;
scs = 30000;
}
else if (mu == 1 && N_RB_DL == 245) {
fs = 122.88e6;
bw = 90e6;
scs = 30000;
}
else if (mu == 1 && N_RB_DL == 273) {
fs = 122.88e6;
bw = 100e6;
scs = 30000;
}
else if (mu == 1 && N_RB_DL == 106) {
fs = 61.44e6;
bw = 40e6;
scs = 30000;
}
else AssertFatal(1==0,"Unsupported numerology for mu %d, N_RB %d\n",mu, N_RB_DL);
// cfo with respect to sub-carrier spacing
eps = cfo/scs;
// computation of integer and fractional FO to compare with estimation results
int IFO;
if(eps!=0.0){
printf("Introducing a CFO of %lf relative to SCS of %d kHz\n",eps,(int)(scs/1000));
if (eps>0)
IFO=(int)(eps+0.5);
else
IFO=(int)(eps-0.5);
printf("FFO = %lf; IFO = %d\n",eps-IFO,IFO);
}
gNB2UE = new_channel_desc_scm(n_tx,
n_rx,
channel_model,
fs,
bw,
0,
0,
0);
if (gNB2UE==NULL) {
msg("Problem generating channel model. Exiting.\n");
exit(-1);
}
frame_length_complex_samples = frame_parms->samples_per_subframe*NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
frame_length_complex_samples_no_prefix = frame_parms->samples_per_subframe_wCP;
s_re = malloc(2*sizeof(double*));
s_im = malloc(2*sizeof(double*));
r_re = malloc(2*sizeof(double*));
r_im = malloc(2*sizeof(double*));
txdata = malloc(2*sizeof(int*));
for (i=0; i<2; i++) {
s_re[i] = malloc(frame_length_complex_samples*sizeof(double));
bzero(s_re[i],frame_length_complex_samples*sizeof(double));
s_im[i] = malloc(frame_length_complex_samples*sizeof(double));
bzero(s_im[i],frame_length_complex_samples*sizeof(double));
r_re[i] = malloc(frame_length_complex_samples*sizeof(double));
bzero(r_re[i],frame_length_complex_samples*sizeof(double));
r_im[i] = malloc(frame_length_complex_samples*sizeof(double));
bzero(r_im[i],frame_length_complex_samples*sizeof(double));
printf("Allocating %d samples for txdata\n",frame_length_complex_samples);
txdata[i] = malloc(frame_length_complex_samples*sizeof(int));
bzero(r_re[i],frame_length_complex_samples*sizeof(int));
}
if (pbch_file_fd!=NULL) {
load_pbch_desc(pbch_file_fd);
}
//configure UE
UE = malloc(sizeof(PHY_VARS_NR_UE));
memcpy(&UE->frame_parms,frame_parms,sizeof(NR_DL_FRAME_PARMS));
//phy_init_nr_top(UE); //called from init_nr_ue_signal
/* if (run_initial_sync==1) UE->is_synchronized = 0;
else UE->is_synchronized = 1; */
UE->perfect_ce = 0;
if(eps!=0.0)
UE->UE_fo_compensation = 1; // if a frequency offset is set then perform fo estimation and compensation
if (init_nr_ue_signal(UE, 1, 0) != 0)
{
printf("Error at UE NR initialisation\n");
exit(-1);
}
int16_t amp=0x1FFF;
int nr_tti_tx=0; //According to standards it is Slot number within a frame for subcarrier spacing configuration μ but not sure why he made the variable name so 4.3.2,38.211
nr_gold_pbch(UE);
// generate signal
// pucch_config_common_nr should assign values for this if not done before structure in ue being used by functions
uint8_t actual_payload=0,payload_received;//payload bits b7b6...b2b1b0 where b7..b3=0 b2b1=HARQ b0 is SR. payload maximum value is 7
uint8_t mcs;
int nr_bit=1;
/*if(nr_bit==1){
mcs=table1_mcs[actual_payload];
}
else{
mcs=table2_mcs[actual_payload];
}*/
uint8_t m0=0;// higher layer paramater initial cyclic shift
uint8_t nrofSymbols=1; //number of OFDM symbols can be 1-2 for format 1
uint8_t startingSymbolIndex=0; // resource allocated see 9.2.1, 38.213 for more info.should be actually present in the resource set provided
uint16_t startingPRB=5; //PRB number not sure see 9.2.1, 38.213 for more info. Should be actually present in the resource set provided
pucch_GroupHopping_t PUCCH_GroupHopping=UE->pucch_config_common_nr->pucch_GroupHopping;
uint32_t n_id=UE->pucch_config_common_nr->hoppingId;
printf("\nsnr1=%f\n",snr1);
for(SNR=snr0;SNR<=snr1;SNR=SNR+1){
n_errors = 0;
n_errors_payload = 0;
sigma2_dB = 20*log10((double)amp/32767)-SNR;
sigma2 = pow(10,sigma2_dB/10);
printf("entering SNR value %f\n",SNR);
for (trial=0; trial<n_trials; trial++) {
bzero(txdata[0],frame_length_complex_samples*sizeof(int));
actual_payload=trial%4;
if(nr_bit==1){
mcs=table1_mcs[actual_payload];
}
else{
mcs=table2_mcs[actual_payload];
}
nr_generate_pucch0(UE,txdata,frame_parms,UE->pucch_config_dedicated,amp,nr_tti_tx,m0,mcs,nrofSymbols,startingSymbolIndex,startingPRB);
for (i=0; i<frame_length_complex_samples; i++) {
r_re[0][i]=((double)(((int16_t *)txdata[0])[(i<<1)])/32767 + sqrt(sigma2/2)*gaussdouble(0.0,1.0));
r_im[0][i]=((double)(((int16_t *)txdata[0])[(i<<1)+1])/32767 + sqrt(sigma2/2)*gaussdouble(0.0,1.0));
if(r_re[0][i]<-1)
r_re[0][i]=-1;
else if(r_re[0][i]>1)
r_re[0][i]=1;
if(r_im[0][i]<-1)
r_im[0][i]=-1;
else if(r_im[0][i]>1)
r_im[0][0]=1;
((int16_t *)txdata[0])[(i<<1)] = (int16_t)round(r_re[0][i]*32767);
((int16_t *)txdata[0])[(i<<1)+1] =(int16_t)round(r_im[0][i]*32767);
}
nr_decode_pucch0(txdata,PUCCH_GroupHopping,n_id,&(payload_received),frame_parms,amp,nr_tti_tx,m0,nrofSymbols,startingSymbolIndex,startingPRB,nr_bit);
n_errors=((actual_payload^payload_received)&1)+(((actual_payload^payload_received)&2)>>1)+(((actual_payload^payload_received)&4)>>2)+n_errors;
//printf("actual_payload=%x,payload_received=%x",actual_payload,payload_received);
}
printf("SNR=%f, n_trials=%d, n_bit_errors=%d\n",SNR,n_trials,n_errors);
}
for (i=0; i<2; i++) {
free(s_re[i]);
free(s_im[i]);
free(r_re[i]);
free(r_im[i]);
free(txdata[i]);
}
free(s_re);
free(s_im);
free(r_re);
free(r_im);
free(txdata);
if (output_fd)
fclose(output_fd);
if (input_fd)
fclose(input_fd);
return(n_errors);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment