// q65test.c // Word Error Rate test example for the Q65 mode // Multi-threaded simulator version // (c) 2020 - Nico Palermo, IV3NWV // // // ------------------------------------------------------------------------------ // This file is part of the qracodes project, a Forward Error Control // encoding/decoding package based on Q-ary RA (Repeat and Accumulate) LDPC codes. // // Dependencies: // q65test.c - this file // normrnd.c/.h - random gaussian number generator // npfwht.c/.h - Fast Walsh-Hadamard Transforms // pdmath.c/.h - Elementary math on probability distributions // qra15_65_64_irr_e23.c/.h - Tables for the QRA(15,65) irregular RA code used by Q65 // qracodes.c/.h - QRA codes encoding/decoding functions // fadengauss.c - fading coefficients tables for gaussian shaped fast fading channels // fadenlorenz.c - fading coefficients tables for lorenzian shaped fast fading channels // // ------------------------------------------------------------------------------- // // This is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // qracodes is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with qracodes source distribution. // If not, see . // // ------------------------------------------------------------------------------ // OS dependent defines and includes -------------------------------------------- #if _WIN32 // note the underscore: without it, it's not msdn official! // Windows (x64 and x86) #define _CRT_SECURE_NO_WARNINGS // we don't need warnings for sprintf/fopen function usage #include // required only for GetTickCount(...) #include // _beginthread #endif #if defined(__linux__) // remove unwanted macros #define __cdecl // implements Windows API #include unsigned int GetTickCount(void) { struct timespec ts; unsigned int theTick = 0U; clock_gettime( CLOCK_REALTIME, &ts ); theTick = ts.tv_nsec / 1000000; theTick += ts.tv_sec * 1000; return theTick; } // Convert Windows millisecond sleep // // VOID WINAPI Sleep(_In_ DWORD dwMilliseconds); // // to Posix usleep (in microseconds) // // int usleep(useconds_t usec); // #include #define Sleep(x) usleep(x*1000) #endif #if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) #include #endif #if __APPLE__ #endif #include #include #include "qracodes.h" // basic qra encoding/decoding functions #include "normrnd.h" // gaussian numbers generator #include "pdmath.h" // operations on probability distributions #include "qra15_65_64_irr_e23.h" // QRA code used by Q65 #include "q65.h" #define Q65_TS 0.640f // Q65 symbol time interval in seconds #define Q65_REFBW 2500.0f // reference bandwidth in Hz for SNR estimates // ----------------------------------------------------------------------------------- #define NTHREADS_MAX 160 // if you have some big enterprise hardware // channel types #define CHANNEL_AWGN 0 #define CHANNEL_RAYLEIGH 1 #define CHANNEL_FASTFADING 2 // amount of a-priori information provided to the decoder #define AP_NONE 0 #define AP_MYCALL 1 #define AP_HISCALL 2 #define AP_BOTHCALL 3 #define AP_FULL 4 #define AP_LAST AP_FULL const char ap_str[AP_LAST+1][16] = { "None", "32 bit", "32 bit", "62 bit", "78 bit", }; const char fnameout_sfx[AP_LAST+1][64] = { "-ap00.txt", "-ap32m.txt", "-ap32h.txt", "-ap62.txt", "-ap78.txt" }; const char fnameout_pfx[3][64] = { "wer-awgn-", "wer-rayl-", "wer-ff-" }; // AP masks are computed assuming that the source message has been packed in 13 symbols s[0]..[s12] // in a little indian format, that's to say: // s[0] = {src5 src4 src3 src2 src1 src0} // s[1] = {src11 src10 src9 src8 src7 src6} // ... // s[12]= {src78 src77 src76 src75 src74 src73} // // where srcj is the j-th bit of the source message. // // It is also assumed that the source message is as indicated by the protocol specification of wsjt-x // structured messages. src78 should be always set to a value known by the decoder (and masked as an AP bit) // With this convention the field i3 of the structured message is mapped to bits src77 src76 src75, // that's to say to the 3rd,4th and 5th bit of s[12]. // Therefore, if i3 is known in advance, since src78 is always known, // the AP mask for s[12] is 0x3C (4 most significant bits of s[12] are known) const int ap_masks_q65[AP_LAST+1][13] = { // AP0 Mask { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Mask first(c28 r1) .... i3 src78 (AP32my MyCall ? ? StdMsg) { 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C}, // Mask second(c28 r1) .... i3 src78 (AP32his ? HisCall ? StdMsg) { 0x00, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x0F, 0x00, 0x00, 0x3C}, // Mask (c28 r1 c28 r1) ... i3 src78 (AP62 MyCall HisCall ? StdMsg) { 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0F, 0x00, 0x00, 0x3C}, // Mask All (c28 r1 c28 r1 R g15 StdMsg src78) (AP78) { 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F}, }; int verbose = 0; void printword(char *msg, int *x, int size) { int k; printf("\n%s ",msg); for (k=0;kchannel_type; rc = q65_init(&codec,pdata->pcode); if (rc<0) { printf("error in qra65_init\n"); goto term_thread; } nK = q65_get_message_length(&codec); nN = q65_get_codeword_length(&codec); nM = q65_get_alphabet_size(&codec); nm = q65_get_bits_per_symbol(&codec); R = q65_get_code_rate(&codec); nSamples = nN*nM; x = (int*)malloc(nK*sizeof(int)); xdec = (int*)malloc(nK*sizeof(int)); y = (int*)malloc(nN*sizeof(int)); ydec = (int*)malloc(nN*sizeof(int)); rsquared = (float*)malloc(nSamples*sizeof(float)); pIntrinsics = (float*)malloc(nSamples*sizeof(float)); // sets the AP mask to be used for this simulation if (pdata->ap_index==AP_NONE) apMask = NULL; // we simply avoid masking if ap-index specifies no AP else apMask = ap_masks_q65[pdata->ap_index]; // Channel simulation variables -------------------- rp = (float*)malloc(nSamples*sizeof(float)); rq = (float*)malloc(nSamples*sizeof(float)); chp = (float*)malloc(nN*sizeof(float)); chq = (float*)malloc(nN*sizeof(float)); EbNo = (float)powf(10,pdata->EbNodB/10); EsNo = 1.0f*nm*R*EbNo; Es = EsNo*No; A = (float)sqrt(Es); // Generate a (meaningless) test message for (k=0;kstop==0) { // Channel simulation -------------------------------------------- // Generate AWGN noise normrnd_s(rp,nSamples,0,sigma); normrnd_s(rq,nSamples,0,sigma); if (channel_type == CHANNEL_AWGN) // add symbol amplitudes for (k=0;k0 && verbose==1) { float EbNodBestimated; float SNRdBestimated; q65_esnodb(&codec, &EsNodBestimated, ydec,rsquared); EbNodBestimated = EsNodBestimated -10.0f*log10f(R*nm); SNRdBestimated = EsNodBestimated -10.0f*log10f(Q65_TS*Q65_REFBW); printf("\nEstimated Eb/No=%5.1fdB SNR2500=%5.1fdB", EbNodBestimated, SNRdBestimated); } nt = nt+1; pdata->nt=nt; pdata->nerrs=nerrs; pdata->ncrcwrong = ncrcwrong; } term_thread: free(x); free(xdec); free(y); free(ydec); free(rsquared); free(pIntrinsics); free(rp); free(rq); free(chp); free(chq); q65_free(&codec); // signal the calling thread we are quitting pdata->done=1; #if _WIN32 _endthread(); #endif } void wer_test_thread_ff(wer_test_ds *pdata) { // We don't do a realistic simulation of the fading-channel here // If required give a look to the simulator used in the QRA64 mode. // For the purpose of testing the formal correctness of the Q65 decoder // fast-fadind routines here we simulate the channel as a Rayleigh channel // with no frequency spread but use the q65....-fastfading routines // to check that they produce correct results also in this case. const int submode = 2; // Assume that we are using the Q65C tone spacing const float B90 = 4.0f; // Configure the Q65 fast-fading decoder for a the given freq. spread const int fadingModel = 1; // Assume a lorenzian frequency spread int nt = 0; // transmitted codewords int nerrs = 0; // total number of errors int ncrcwrong = 0; // number of decodes with wrong crc q65_codec_ds codec; int rc, k; int nK, nN, nM, nm, nSamples; int *x, *y, *xdec, *ydec; const int *apMask; float R; float *rsquared, *pIntrinsics; float EsNodBestimated; int nBinsPerTone, nBinsPerSymbol; // for channel simulation const float No = 1.0f; // noise spectral density const float sigma = sqrtf(No/2.0f); // std dev of I/Q noise components const float sigmach = sqrtf(1/2.0f); // std dev of I/Q channel gains (Rayleigh channel) float EbNo, EsNo, Es, A; float *rp, *rq, *chp, *chq; int channel_type = pdata->channel_type; rc = q65_init(&codec,pdata->pcode); if (rc<0) { printf("error in q65_init\n"); goto term_thread; } nK = q65_get_message_length(&codec); nN = q65_get_codeword_length(&codec); nM = q65_get_alphabet_size(&codec); nm = q65_get_bits_per_symbol(&codec); R = q65_get_code_rate(&codec); nBinsPerTone = 1<ap_index==AP_NONE) apMask = NULL; // we simply avoid masking if ap-index specifies no AP else apMask = ap_masks_q65[pdata->ap_index]; x = (int*)malloc(nK*sizeof(int)); xdec = (int*)malloc(nK*sizeof(int)); y = (int*)malloc(nN*sizeof(int)); ydec = (int*)malloc(nN*sizeof(int)); rsquared = (float*)malloc(nSamples*sizeof(float)); pIntrinsics = (float*)malloc(nN*nM*sizeof(float)); // Channel simulation variables -------------------- rp = (float*)malloc(nSamples*sizeof(float)); rq = (float*)malloc(nSamples*sizeof(float)); chp = (float*)malloc(nN*sizeof(float)); chq = (float*)malloc(nN*sizeof(float)); EbNo = (float)powf(10,pdata->EbNodB/10); EsNo = 1.0f*nm*R*EbNo; Es = EsNo*No; A = (float)sqrt(Es); // ------------------------------------------------- // generate a test message for (k=0;kstop==0) { // Channel simulation -------------------------------------------- // generate AWGN noise normrnd_s(rp,nSamples,0,sigma); normrnd_s(rq,nSamples,0,sigma); // Generate Rayleigh distributed symbol amplitudes normrnd_s(chp,nN,0,sigmach); normrnd_s(chq,nN,0,sigmach); // Don't simulate a really frequency spreaded signal. // Just place the tones in the appropriate central bins // ot the received signal for (k=0;k0 && verbose==1) { float EbNodBestimated; float SNRdBestimated; // use the fastfading version q65_esnodb_fastfading(&codec, &EsNodBestimated, ydec,rsquared); EbNodBestimated = EsNodBestimated -10.0f*log10f(R*nm); SNRdBestimated = EsNodBestimated -10.0f*log10f(Q65_TS*Q65_REFBW); printf("\nEstimated Eb/No=%5.1fdB SNR2500=%5.1fdB", EbNodBestimated, SNRdBestimated); } nt = nt+1; pdata->nt=nt; pdata->nerrs=nerrs; pdata->ncrcwrong = ncrcwrong; } term_thread: free(x); free(xdec); free(y); free(ydec); free(rsquared); free(pIntrinsics); free(rp); free(rq); free(chp); free(chq); q65_free(&codec); // signal the calling thread we are quitting pdata->done=1; #if _WIN32 _endthread(); #endif } #if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) ) void *wer_test_pthread_awgnrayl(void *p) { wer_test_thread_awgnrayl((wer_test_ds *)p); return 0; } void *wer_test_pthread_ff(void *p) { wer_test_thread_ff((wer_test_ds *)p); return 0; } #endif int wer_test_proc(const qracode *pcode, int nthreads, int chtype, int ap_index, float *EbNodB, int *nerrstgt, int nitems) { int k,j,nt,nerrs,nerrsu,ncrcwrong,nd; int cini,cend; char fnameout[128]; FILE *fout; wer_test_ds wt[NTHREADS_MAX]; float pe,peu,avgt; if (nthreads>NTHREADS_MAX) { printf("Error: nthreads should be <=%d\n",NTHREADS_MAX); return -1; } sprintf(fnameout,"%s%s%s", fnameout_pfx[chtype], pcode->name, fnameout_sfx[ap_index]); fout = fopen(fnameout,"w"); fprintf(fout,"#Code Name: %s\n",pcode->name); fprintf(fout,"#ChannelType (0=AWGN,1=Rayleigh,2=Fast-Fading)\n#Eb/No (dB)\n#Transmitted Codewords\n#Errors\n#CRC Errors\n#Undetected\n#Avg dec. time (ms)\n#WER\n#UER\n"); printf("\nTesting the code %s\nSimulation data will be saved to %s\n", pcode->name, fnameout); fflush (stdout); // init fixed thread parameters and preallocate buffers for (j=0;j=nerrstgt[k]) { for (j=0;j] [-t] [-c] [-a] [-f[-h]\n"); printf("Options: \n"); printf(" -q: code to simulate. 0=qra_15_65_64_irr_e23 (default)\n"); printf(" -t : number of threads to be used for the simulation [1..24]\n"); printf(" (default=8)\n"); printf(" -c : channel_type. 0=AWGN 1=Rayleigh 2=Fast-Fading\n"); printf(" (default=AWGN)\n"); printf(" -a : amount of a-priori information provided to decoder. \n"); printf(" 0= No a-priori (default)\n"); printf(" 1= 32 bit (Mycall)\n"); printf(" 2= 32 bit (Hiscall)\n"); printf(" 3= 62 bit (Bothcalls\n"); printf(" 4= 78 bit (full AP)\n"); printf(" -v : verbose (output SNRs of decoded messages\n"); printf(" -f : name of the file containing the Eb/No values to be simulated\n"); printf(" (default=ebnovalues.txt)\n"); printf(" This file should contain lines in this format:\n"); printf(" # Eb/No(dB) Target Errors\n"); printf(" 0.1 5000\n"); printf(" 0.6 5000\n"); printf(" 1.1 1000\n"); printf(" 1.6 1000\n"); printf(" ...\n"); printf(" (lines beginning with a # are treated as comments\n\n"); } #define SIM_POINTS_MAX 20 int main(int argc, char* argv[]) { float EbNodB[SIM_POINTS_MAX]; int nerrstgt[SIM_POINTS_MAX]; FILE *fin; char fnamein[128]= "ebnovalues.txt"; char buf[128]; int nitems = 0; int code_idx = 0; int nthreads = 8; int ch_type = CHANNEL_AWGN; int ap_index = AP_NONE; // parse command line while(--argc) { argv++; if (strncmp(*argv,"-h",2)==0) { syntax(); return 0; } else if (strncmp(*argv,"-q",2)==0) { code_idx = (int)atoi((*argv)+2); if (code_idx>7) { printf("Invalid code index\n"); syntax(); return -1; } } else if (strncmp(*argv,"-t",2)==0) { nthreads = (int)atoi((*argv)+2); // printf("nthreads = %d\n",nthreads); if (nthreads>NTHREADS_MAX) { printf("Invalid number of threads\n"); syntax(); return -1; } } else if (strncmp(*argv,"-c",2)==0) { ch_type = (int)atoi((*argv)+2); if (ch_type>CHANNEL_FASTFADING) { printf("Invalid channel type\n"); syntax(); return -1; } } else if (strncmp(*argv,"-a",2)==0) { ap_index = (int)atoi((*argv)+2); if (ap_index>AP_LAST) { printf("Invalid a-priori information index\n"); syntax(); return -1; } } else if (strncmp(*argv,"-f",2)==0) { strncpy(fnamein,(*argv)+2,127); } else if (strncmp(*argv,"-h",2)==0) { syntax(); return -1; } else if (strncmp(*argv,"-v",2)==0) verbose = TRUE; else { printf("Invalid option\n"); syntax(); return -1; } } // parse points to be simulated from the input file fin = fopen(fnamein,"r"); if (!fin) { printf("Can't open file: %s\n",fnamein); syntax(); return -1; } while (fgets(buf,128,fin)!=0) if (*buf=='#' || *buf=='\n' ) continue; else if (nitems==SIM_POINTS_MAX) break; else if (sscanf(buf,"%f %u",&EbNodB[nitems],&nerrstgt[nitems])!=2) { printf("Invalid input file format\n"); syntax(); return -1; } else nitems++; fclose(fin); if (nitems==0) { printf("No Eb/No point specified in file %s\n",fnamein); syntax(); return -1; } printf("\nQ65 Word Error Rate Simulator\n"); printf("(c) 2016-2020, Nico Palermo - IV3NWV\n\n"); printf("Nthreads = %d\n",nthreads); switch(ch_type) { case CHANNEL_AWGN: printf("Channel = AWGN\n"); break; case CHANNEL_RAYLEIGH: printf("Channel = Rayleigh\n"); break; case CHANNEL_FASTFADING: printf("Channel = Fast Fading\n"); break; } printf("Codename = %s\n",codetotest[code_idx]->name); printf("A-priori = %s\n",ap_str[ap_index]); printf("Eb/No input file = %s\n\n",fnamein); wer_test_proc(codetotest[code_idx], nthreads, ch_type, ap_index, EbNodB, nerrstgt, nitems); printf("\n\n\n"); return 0; }