mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-29 23:58:39 -05:00
911 lines
24 KiB
C
911 lines
24 KiB
C
// 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
// ------------------------------------------------------------------------------
|
|
|
|
// 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 <windows.h> // required only for GetTickCount(...)
|
|
#include <process.h> // _beginthread
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
|
|
// remove unwanted macros
|
|
#define __cdecl
|
|
|
|
// implements Windows API
|
|
#include <time.h>
|
|
|
|
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 <unistd.h>
|
|
#define Sleep(x) usleep(x*1000)
|
|
|
|
#endif
|
|
|
|
#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) )
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
#if __APPLE__
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#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;k<size;k++)
|
|
printf("%02hx ",x[k]);
|
|
printf("\n");
|
|
}
|
|
|
|
typedef struct {
|
|
int channel_type;
|
|
float EbNodB;
|
|
volatile int nt;
|
|
volatile int nerrs;
|
|
volatile int nerrsu;
|
|
volatile int ncrcwrong;
|
|
volatile int stop;
|
|
volatile int done;
|
|
int ap_index; // index to the a priori knowledge mask
|
|
const qracode *pcode; // pointer to the code descriptor
|
|
#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) )
|
|
pthread_t thread;
|
|
#endif
|
|
} wer_test_ds;
|
|
|
|
typedef void( __cdecl *pwer_test_thread)(wer_test_ds*);
|
|
|
|
void wer_test_thread_awgnrayl(wer_test_ds *pdata)
|
|
{
|
|
// Thread for the AWGN/Rayleigh channel types
|
|
|
|
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;
|
|
|
|
// 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 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;k<nK;k++)
|
|
x[k] = k%nM;
|
|
// printword("x", x,nK);
|
|
|
|
// Encode
|
|
q65_encode(&codec,y,x);
|
|
// printword("y", y,nN);
|
|
|
|
// Simulate the channel and decode
|
|
// as long as we are stopped by our caller
|
|
while (pdata->stop==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;k<nN;k++)
|
|
rp[k*nM+y[k]]+=A;
|
|
else if (channel_type == CHANNEL_RAYLEIGH) {
|
|
// generate Rayleigh distributed taps
|
|
normrnd_s(chp,nN,0,sigmach);
|
|
normrnd_s(chq,nN,0,sigmach);
|
|
// add Rayleigh distributed symbol amplitudes
|
|
for (k=0;k<nN;k++) {
|
|
rp[k*nM+y[k]]+=A*chp[k];
|
|
rq[k*nM+y[k]]+=A*chq[k];
|
|
}
|
|
}
|
|
else {
|
|
printf("Wrong channel_type %d\n",channel_type);
|
|
goto term_thread;
|
|
}
|
|
|
|
// Compute the received energies
|
|
for (k=0;k<nSamples;k++)
|
|
rsquared[k] = rp[k]*rp[k] + rq[k]*rq[k];
|
|
|
|
// Channel simulation end --------------------------------------------
|
|
|
|
// DECODING ----------------------------------------------------------
|
|
|
|
// Compute intrinsics probabilities from the observed energies
|
|
rc = q65_intrinsics(&codec,pIntrinsics,rsquared);
|
|
if (rc<0) {
|
|
printf("Error in qra65_intrinsics: rc=%d\n",rc);
|
|
goto term_thread;
|
|
}
|
|
|
|
// Decode with the given AP information
|
|
// This call can be repeated for any desierd apMask
|
|
// until we manage to decode the message
|
|
rc = q65_decode(&codec,ydec,xdec, pIntrinsics, apMask,x);
|
|
|
|
|
|
switch (rc) {
|
|
case -1:
|
|
printf("Error in qra65_decode: rc=%d\n",rc);
|
|
goto term_thread;
|
|
case Q65_DECODE_FAILED:
|
|
// decoder failed to converge
|
|
nerrs++;
|
|
break;
|
|
case Q65_DECODE_CRCMISMATCH:
|
|
// decoder converged but we found a bad crc
|
|
nerrs++;
|
|
ncrcwrong++;
|
|
break;
|
|
}
|
|
|
|
// compute SNR from decoded codeword ydec and observed energies
|
|
if (rc>0 && 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<<submode;
|
|
nBinsPerSymbol = nM*(2+nBinsPerTone);
|
|
nSamples = nN*nBinsPerSymbol;
|
|
|
|
// 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];
|
|
|
|
|
|
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;k<nK;k++)
|
|
x[k] = k%nM;
|
|
|
|
// printword("x", x,nK);
|
|
|
|
// encode
|
|
q65_encode(&codec,y,x);
|
|
// printword("y", y,nN);
|
|
|
|
while (pdata->stop==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;k<nN;k++) {
|
|
rp[k*nBinsPerSymbol+y[k]*nBinsPerTone+nM]+=A*chp[k];
|
|
rq[k*nBinsPerSymbol+y[k]*nBinsPerTone+nM]+=A*chq[k];
|
|
}
|
|
|
|
// compute the received energies
|
|
for (k=0;k<nSamples;k++)
|
|
rsquared[k] = rp[k]*rp[k] + rq[k]*rq[k];
|
|
|
|
// Channel simulation end --------------------------------------------
|
|
|
|
// compute intrinsics probabilities from the observed energies
|
|
// using the fast-fading version
|
|
rc = q65_intrinsics_fastfading(&codec,pIntrinsics,rsquared,submode,B90,fadingModel);
|
|
if (rc<0) {
|
|
printf("Error in q65_intrinsics: rc=%d\n",rc);
|
|
goto term_thread;
|
|
}
|
|
|
|
// decode with the given AP information (eventually with different apMasks and apSymbols)
|
|
rc = q65_decode(&codec,ydec,xdec, pIntrinsics, apMask,x);
|
|
|
|
switch (rc) {
|
|
case -1:
|
|
printf("Error in q65_decode: rc=%d\n",rc);
|
|
goto term_thread;
|
|
case Q65_DECODE_FAILED:
|
|
// decoder failed to converge
|
|
nerrs++;
|
|
break;
|
|
case Q65_DECODE_CRCMISMATCH:
|
|
// decoder converged but we found a bad crc
|
|
nerrs++;
|
|
ncrcwrong++;
|
|
break;
|
|
}
|
|
|
|
// compute SNR from decoded codeword ydec and observed energies rsquared
|
|
if (rc>0 && 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<nthreads;j++) {
|
|
wt[j].channel_type=chtype;
|
|
wt[j].ap_index = ap_index;
|
|
wt[j].pcode = pcode;
|
|
}
|
|
|
|
for (k=0;k<nitems;k++) {
|
|
|
|
printf("\nTesting at Eb/No=%4.2f dB...",EbNodB[k]);
|
|
fflush (stdout);
|
|
|
|
for (j=0;j<nthreads;j++) {
|
|
wt[j].EbNodB=EbNodB[k];
|
|
wt[j].nt=0;
|
|
wt[j].nerrs=0;
|
|
wt[j].nerrsu=0;
|
|
wt[j].ncrcwrong=0;
|
|
wt[j].done = 0;
|
|
wt[j].stop = 0;
|
|
#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) )
|
|
if (chtype==CHANNEL_FASTFADING) {
|
|
if (pthread_create (&wt[j].thread, 0, wer_test_pthread_ff, &wt[j])) {
|
|
perror ("Creating thread: ");
|
|
exit (255);
|
|
}
|
|
}
|
|
else {
|
|
if (pthread_create (&wt[j].thread, 0, wer_test_pthread_awgnrayl, &wt[j])) {
|
|
perror ("Creating thread: ");
|
|
exit (255);
|
|
}
|
|
}
|
|
#else
|
|
if (chtype==CHANNEL_FASTFADING)
|
|
_beginthread((void*)(void*)wer_test_thread_ff,0,&wt[j]);
|
|
else
|
|
_beginthread((void*)(void*)wer_test_thread_awgnrayl,0,&wt[j]);
|
|
#endif
|
|
}
|
|
|
|
nd = 0;
|
|
cini = GetTickCount();
|
|
|
|
while (1) {
|
|
// count errors
|
|
nerrs = 0;
|
|
for (j=0;j<nthreads;j++)
|
|
nerrs += wt[j].nerrs;
|
|
// stop the working threads
|
|
// if the number of errors at this Eb/No value
|
|
// reached the target value
|
|
if (nerrs>=nerrstgt[k]) {
|
|
for (j=0;j<nthreads;j++)
|
|
wt[j].stop = 1;
|
|
break;
|
|
}
|
|
else { // continue with the simulation
|
|
Sleep(2);
|
|
nd = (nd+1)%100;
|
|
if (nd==0) {
|
|
if (verbose==0) {
|
|
printf(".");
|
|
fflush (stdout);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
cend = GetTickCount();
|
|
|
|
// wait for the working threads to exit
|
|
for (j=0;j<nthreads;j++)
|
|
#if defined(__linux__) || ( defined(__MINGW32__) || defined (__MIGW64__) )
|
|
{
|
|
void *rc;
|
|
if (pthread_join (wt[j].thread, &rc)) {
|
|
perror ("Waiting working threads to exit");
|
|
exit (255);
|
|
}
|
|
}
|
|
#else
|
|
while(wt[j].done==0)
|
|
Sleep(1);
|
|
|
|
#endif
|
|
printf("\n");
|
|
fflush (stdout);
|
|
|
|
// compute the total number of transmitted codewords
|
|
// the total number of errors and the total number of undetected errors
|
|
nt = 0;
|
|
nerrs =0;
|
|
nerrsu = 0;
|
|
ncrcwrong = 0;
|
|
for (j=0;j<nthreads;j++) {
|
|
nt += wt[j].nt;
|
|
nerrs += wt[j].nerrs;
|
|
nerrsu += wt[j].nerrsu;
|
|
ncrcwrong += wt[j].ncrcwrong;
|
|
}
|
|
|
|
pe = 1.0f*nerrs/nt; // word error rate
|
|
avgt = 1.0f*(cend-cini)/nt; // average time per decode (ms)
|
|
peu = 1.0f*ncrcwrong/4095/nt;
|
|
|
|
printf("Elapsed Time=%6.1fs (%5.2fms/word)\nTransmitted=%8d Errors=%6d CRCErrors=%3d Undet=%3d - WER=%8.2e UER=%8.2e \n",
|
|
0.001f*(cend-cini),
|
|
avgt, nt, nerrs, ncrcwrong, nerrsu, pe, peu);
|
|
fflush (stdout);
|
|
|
|
// save simulation data to output file
|
|
fprintf(fout,"%01d %6.2f %6d %6d %6d %6d %6.2f %8.2e %8.2e\n",
|
|
chtype,
|
|
EbNodB[k],
|
|
nt,
|
|
nerrs,
|
|
ncrcwrong,
|
|
nerrsu,
|
|
avgt,
|
|
pe,
|
|
peu);
|
|
fflush(fout);
|
|
|
|
}
|
|
|
|
fclose(fout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const qracode *codetotest[] = {
|
|
&qra15_65_64_irr_e23,
|
|
};
|
|
|
|
void syntax(void)
|
|
{
|
|
printf("\nQ65 Word Error Rate Simulator\n");
|
|
printf("2020, Nico Palermo - IV3NWV\n\n");
|
|
printf("Syntax: q65test [-q<code_index>] [-t<threads>] [-c<ch_type>] [-a<ap_index>] [-f<fnamein>[-h]\n");
|
|
printf("Options: \n");
|
|
printf(" -q<code_index>: code to simulate. 0=qra_15_65_64_irr_e23 (default)\n");
|
|
printf(" -t<threads> : number of threads to be used for the simulation [1..24]\n");
|
|
printf(" (default=8)\n");
|
|
printf(" -c<ch_type> : channel_type. 0=AWGN 1=Rayleigh 2=Fast-Fading\n");
|
|
printf(" (default=AWGN)\n");
|
|
printf(" -a<ap_index> : 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<fnamein> : 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;
|
|
}
|
|
|