diff --git a/.gitignore b/.gitignore index 912b3441d4..0898d51eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.gcno *.o *.so +*~ # ignore swap files for vim editing *.swp @@ -30,6 +31,9 @@ config.status makefile xautotest *.m +autotest.json +autotest/logs/*.bin +autotest/logs/*.gnu # miscellany octave-core diff --git a/HISTORY b/HISTORY index 881d18ca9f..ded8d297a3 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,106 @@ ## Latest improvements ## +## Improvements for v1.6.0 ## + +Version 1.6.0 includes a new qdsync object to greatly simplify the frame +synchronization process in liquid, allowing for both detection and channel +impairment correction with a simplified interface. Additionally, code +coverage has been increased to 85% across the entire project, with numerous +bug fixes, stability improvements, and massive testing enhancements. From +an "architectural" standpoint, objects have been migrated to use standard +methods for consistency. + + * build + - increased code coverage to 85% globally across entire project. This + is the single largest effort included in this version and touches + most modules in some way, most particularly the framing objects + - cleaning build to remove compiler warnings (e.g. unused variables) + - stripped version number off archive + * dotprod + - added support for AVX512-F (thanks, @vankxr!) + * framing + - added numerous tests to increase coverage to 84% + - framesync64: using new qdsync object for simplified operation + - qdsync: new frame detector and synchronizer to much more easily + support frame processing. The object not only detects the frame, but + also provides an initial carrier frequency, phase, and timign offset, + and also corrects for these impairments, passing the results to the + user in a clean callback function. + * modem + - cpfskmod: increasing phase stability for long runs + * multichannel + - added numerous tests to increase coverage to 88% + * optim + - added numerous tests to increase coverage to 92% + * sequence + - msequence: extended support for state variables up to m=31, reversed + order for generator polynomial and internal state definition to be + more consistent with literature and readily-available genpolys + +## Improvements for v1.5.0 ## + +This release includes substantially improved testing coverage, deep copy() +methods for nearly all objects, improved speed, and resolves a number of +issues and pull requests. + + * build + - added support for PlatformIO (https://platformio.org) for embeedded + development (thanks, @jcw!) + - incorporated recursive copy() methods to objects to facilitate c++ copy + constructors for bindings; now all objects can be deep copied to a new + object with their entire memory and state preserved + - added convenience method to malloc and copy memory arrays + - improved support for error codes across objects and methods + - cleaned up spelling errors across project (thanks, @nowls!) + - scrubbed function argument variable names to avoid underscore followed + by a capital letter, causing trouble with pre-compiler processing + - added basic test to check linking to installed library, incorporating + into CI/CD pipelines + - added more example programs + * autotest + - increased coverage testing (81% across entire project) + - added the ability to "hammer" a particular test by running repeatedly on + incremental seeds to assess specific edge cases (e.g. with random data) + - added timer to show execution time for each test and identify areas for + speed improvements + - added methods for testing spectral response of various fields + - added special directory for storing output logs: autotest/logs/ + * benchmark + - replacing old C-based benchmark comparison with simpler python version + * dotprod + - added support for AVX SIMD in vector dot products (thanks, @vankxr!) + * fft + - adding macro to allow for aligned memory allocation if FFTW is used + (thanks, @nowls!) + * filter + - added new halfband filter design using Parks-McClellan algorithm and + qs1dsearch method to provide as exact a specification as possible + - added method to retrieve filter response from coefficients array + - dds: adding methods to get/set scale + - firhilb, iirhilb: added block processing method + - msresamp, resamp: adding method to provide the exact number of output + samples with provided input size + - msresamp2, resamp2: using better halfband filter design for exact user + specifications + - resamp: adding methods to get/set scale, fixing filter bank resolution + (was hard-coded, now respects user configuration) + * framing + - framesync64: added methods to set callback and userdata (context) + fields, adding support for exporting debugging files for post-analysis + as well as python script for processing, adding better estimate of + error vector magnitude + - msource: added convenience method to recall number of samples generated + by each source + - ofdmflexframesync: added methods to set callback and userdata (context) + fields + - qpacketmodem: returning much better estimate of error vector magnitude + - qsource: fixed issue with carrier frequency adjustment + * optim + - added qs1dsearch object to perform quad-section 1-dimensional search: + similar to bisection search, but to find potentially non-continuous + minimum/maximum of function + ## Improvements for v1.4.0 ## * autotest diff --git a/README.md b/README.md index 0ddd8eb3c6..7f8451a624 100644 --- a/README.md +++ b/README.md @@ -296,11 +296,36 @@ lib_deps = https://github.com/jgaeddert/liquid-dsp.git ### License ### liquid projects are released under the X11/MIT license. +By default, this project will try to link to [FFTW](http://www.fftw.org) if it +is available on your build platform. +Because FFTW starting with version 1.3 is +[licensed](http://www.fftw.org/faq/section1.html) +under the [GNU General Public License v2](http://www.fftw.org/doc/License-and-Copyright.html) +this unfortunately means that (and I'm clearly not a lawyer, here) +you cannot distribute `liquid-dsp` without also distributing the source code +if you link to FFTW. +This is a similar situation with the classic +[libfec](https://github.com/quiet/libfec) +which uses the +[GNU Lesser GPL](https://www.gnu.org/licenses/licenses.html#LGPL). +Finally, `liquid-dsp` makes extensive use of GNU +[autoconf](https://www.gnu.org/software/autoconf/), +[automake](https://www.gnu.org/software/automake/), +and related tools. +These are fantastic libraires with amazing functionality and their authors +should be lauded for their efforts. +In a similar vain, much the software I write for a living I give away for +free; +however I believe in more permissive licenses to allow individuals the +flexibility to use software with fewer limitations. +If these restrictions are not acceptible, `liquid-dsp` can be compiled and run +without use of these external libraries, albeit a bit slower and with limited +functionality. + Short version: this code is copyrighted to me (Joseph D. Gaeddert), I give you full permission to do whatever you want with it except remove my name from the credits. -Seriously, go nuts. -See the LICENSE file or -[https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) -for specific terms. +Seriously, go nuts! but take caution when linking to other libraries with +different licenses. +See the [license](https://opensource.org/licenses/MIT) for specific terms. diff --git a/configure.ac b/configure.ac index 5fdd6f8299..9a55622c5a 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -# Copyright (c) 2007 - 2022 Joseph Gaeddert +# Copyright (c) 2007 - 2023 Joseph Gaeddert # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ # Process with autoconf to generate configure script # -AC_INIT([liquid-dsp],[1.4],[joseph@liquidsdr.org]) +AC_INIT([liquid-dsp],[1.6],[joseph@liquidsdr.org]) AC_CONFIG_SRCDIR([src/libliquid.c]) AC_CONFIG_MACRO_DIR([scripts]) @@ -170,9 +170,18 @@ else # SSSE3 : tmmintrin.h # SSE4.1/2: smmintrin.h # AVX : immintrin.h + # AVX2 : immintrin.h + # AVX512 : immintrin.h AX_EXT - if [ test "$ax_cv_have_avx2_ext" = yes ]; then + if [ test "$ax_cv_have_avx512f_ext" = yes ]; then + # AVX512 extensions + MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.avx512f.o \ + src/dotprod/src/dotprod_crcf.avx512f.o \ + src/dotprod/src/dotprod_rrrf.avx512f.o \ + src/dotprod/src/sumsq.avx512f.o" + ARCH_OPTION='-mavx512f' + elif [ test "$ax_cv_have_avx2_ext" = yes ]; then # AVX2 extensions MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.avx.o \ src/dotprod/src/dotprod_crcf.avx.o \ @@ -188,24 +197,24 @@ else ARCH_OPTION='-mavx' elif [ test "$ax_cv_have_sse41_ext" = yes ]; then # SSE4.1/2 extensions - MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.mmx.o \ - src/dotprod/src/dotprod_crcf.mmx.o \ - src/dotprod/src/dotprod_rrrf.sse4.o \ - src/dotprod/src/sumsq.mmx.o" + MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.sse.o \ + src/dotprod/src/dotprod_crcf.sse.o \ + src/dotprod/src/dotprod_rrrf.sse.o \ + src/dotprod/src/sumsq.sse.o" ARCH_OPTION='-msse4.1' elif [ test "$ax_cv_have_sse3_ext" = yes ]; then # SSE3 extensions - MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.mmx.o \ - src/dotprod/src/dotprod_crcf.mmx.o \ - src/dotprod/src/dotprod_rrrf.mmx.o \ - src/dotprod/src/sumsq.mmx.o" + MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.sse.o \ + src/dotprod/src/dotprod_crcf.sse.o \ + src/dotprod/src/dotprod_rrrf.sse.o \ + src/dotprod/src/sumsq.sse.o" ARCH_OPTION='-msse3' elif [ test "$ax_cv_have_sse2_ext" = yes ]; then # SSE2 extensions - MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.mmx.o \ - src/dotprod/src/dotprod_crcf.mmx.o \ - src/dotprod/src/dotprod_rrrf.mmx.o \ - src/dotprod/src/sumsq.mmx.o" + MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.sse.o \ + src/dotprod/src/dotprod_crcf.sse.o \ + src/dotprod/src/dotprod_rrrf.sse.o \ + src/dotprod/src/sumsq.sse.o" ARCH_OPTION='-msse2' else # portable C version diff --git a/examples/framesync64_performance_example.c b/examples/framesync64_performance_example.c index fc43291c32..7242ca5635 100644 --- a/examples/framesync64_performance_example.c +++ b/examples/framesync64_performance_example.c @@ -10,10 +10,11 @@ // add noise to channel void frame64_add_noise(float complex * _buf, float _SNRdB) { - float nstd = powf(10.0f, -_SNRdB/20.0f) * M_SQRT1_2; + float nstd = powf(10.0f, -_SNRdB/20.0f); + nstd *= M_SQRT2; // scale noise to account for signal being over-sampled by 2 unsigned int i; for (i=0; i= per_target) { per_0 = per; snr_0 = SNRdB; } + else if (per_1 < 0.0f ) { per_1 = per; snr_1 = SNRdB; } + printf(" %8.3f %6u %6u %6u %12.4e\n", + SNRdB,stats.num_frames_detected,stats.num_payloads_valid,num_trials,per); fprintf(fid,"SNR(end+1)=%g; pdetect(end+1)=%g; pvalid(end+1)=%g;\n", SNRdB, (float)stats.num_frames_detected / (float)num_trials, @@ -70,6 +77,10 @@ int main(int argc, char*argv[]) break; SNRdB += 0.5f; } + float m = (logf(per_1) - logf(per_0)) / (snr_1 - snr_0); + float snr_target = snr_0 + (logf(per_target) - logf(per_0)) / m; + printf("per:{%12.4e,%12.4e}, snr:{%5.2f,%5.2f} => %.3f dB for PER %12.4e\n", + per_0, per_1, snr_0, snr_1, snr_target, per_target); fprintf(fid,"figure;\n"); fprintf(fid,"hold on;\n"); fprintf(fid," semilogy(SNR, 1-pdetect+eps,'-o', 'LineWidth',2, 'MarkerSize',2);\n"); @@ -88,3 +99,4 @@ int main(int argc, char*argv[]) framesync64_destroy(fs); return 0; } + diff --git a/examples/fskframesync_example.c b/examples/fskframesync_example.c index c50c49b9f2..52445cf0bb 100644 --- a/examples/fskframesync_example.c +++ b/examples/fskframesync_example.c @@ -1,13 +1,8 @@ -// -// fskframesync_example.c -// // This example demonstrates the interfaces to the fskframegen and // fskframesync objects used to completely encapsulate data for // over-the-air transmission. // // SEE ALSO: flexframesync_example.c -// - #include #include #include diff --git a/examples/gasearch_example.c b/examples/gasearch_example.c index 677bb3fbac..ddc9036519 100644 --- a/examples/gasearch_example.c +++ b/examples/gasearch_example.c @@ -1,7 +1,4 @@ -// -// gasearch_example.c -// - +// example demonstrating performance of GA search algorithm for finding basic function peak #include #include #include @@ -10,57 +7,59 @@ #define OUTPUT_FILENAME "gasearch_example.m" -// utility callback function -float utility_callback(void * _userdata, chromosome _c); - -// peak callback function; value nearest {0.5, 0.5, 0.5, ...} -float peak_callback(void * _userdata, chromosome _c); - -// rosenbrock callback function -float rosenbrock_callback(void * _userdata, chromosome _c); +// peak callback function; value nearest {p, p, p, ...} where p = 1/sqrt(2) +float peak_callback(void * _userdata, chromosome _c) +{ + unsigned int i, n = chromosome_get_num_traits(_c); + float u_global = 1.0f; + float sig = 0.2f; + float p = M_SQRT1_2; + for (i=0; i +#include +#include +#include + +#include "liquid.h" + +#define OUTPUT_FILENAME "msequence_autocorr_example.m" + +int main(int argc, char*argv[]) +{ + // options + unsigned int m=5; // shift register length, n=2^m - 1 + + // create and initialize m-sequence + msequence ms = msequence_create_default(m); + msequence_print(ms); + unsigned int n = msequence_get_length(ms); + signed int rxx[n]; // auto-correlation + + // create and initialize first binary sequence on m-sequence + bsequence bs1 = bsequence_create(n); + bsequence_init_msequence(bs1, ms); + + // create and initialize second binary sequence on same m-sequence + bsequence bs2 = bsequence_create(n); + bsequence_init_msequence(bs2, ms); + + // when sequences are aligned, autocorrelation is equal to length + rxx[0] = 2*bsequence_correlate(bs1, bs2) - n; + + // when sequences are misaligned, autocorrelation is equal to -1 + unsigned int i; + for (i=0; i #include #include @@ -17,90 +8,28 @@ #include "liquid.h" -#define OUTPUT_FILENAME "msequence_example.m" - int main(int argc, char*argv[]) { - // options - unsigned int m=5; // shift register length, n=2^m - 1 - // create and initialize m-sequence - msequence ms = msequence_create_default(m); - msequence_print(ms); - unsigned int n = msequence_get_length(ms); - signed int rxx[n]; // auto-correlation - - // create and initialize first binary sequence on m-sequence - bsequence bs1 = bsequence_create(n); - bsequence_init_msequence(bs1, ms); + msequence q = msequence_create_default(5); + msequence_print(q); - // create and initialize second binary sequence on same m-sequence - bsequence bs2 = bsequence_create(n); - bsequence_init_msequence(bs2, ms); - - // when sequences are aligned, autocorrelation is equal to length - rxx[0] = 2*bsequence_correlate(bs1, bs2) - n; - - // when sequences are misaligned, autocorrelation is equal to -1 + // cycle through values and print state unsigned int i; - for (i=0; i 0 && msequence_get_state(q)==1) { + printf("invalid state!\n"); + break; + } + printf("%u\n",msequence_get_state(q)); + msequence_advance(q); } - - // p/n sequence - signed int x[n]; - for (i=0; i +#include +#include +#include +#include +#include +#include "liquid.h" + +#define OUTPUT_FILENAME "qdsync_cccf_example.m" + +// synchronization callback, return 0:continue, 1:reset +int callback(float complex * _buf, + unsigned int _buf_len, + void * _context) +{ + printf("callback got %u samples\n", _buf_len); + unsigned int i; + for (i=0; i<_buf_len; i++) + fprintf((FILE*)_context, "y(end+1) = %12.8f + %12.8fj;\n", crealf(_buf[i]), cimagf(_buf[i])); + return 0; +} + +int main(int argc, char*argv[]) +{ + // options + unsigned int seq_len = 80; // number of sync symbols + unsigned int k = 2; // samples/symbol + unsigned int m = 7; // filter delay [symbols] + float beta = 0.3f; // excess bandwidth factor + int ftype = LIQUID_FIRFILT_ARKAISER; + float nstd = 0.01f; + + // generate synchronization sequence (QPSK symbols) + float complex seq[seq_len]; + unsigned int i; + for (i=0; i #include #include @@ -21,39 +16,41 @@ void usage() { printf("qpacketmodem_performance_example [options]\n"); - printf(" h : print usage\n"); - printf(" p : payload length [bytes], default: 400\n"); - printf(" m : modulation scheme (qpsk default)\n"); + printf(" -h : print usage\n"); + printf(" -p : payload length [bytes], default: 400\n"); + printf(" -m : modulation scheme (qpsk default)\n"); liquid_print_modulation_schemes(); - printf(" v : data integrity check: crc32 default\n"); + printf(" -v : data integrity check: crc32 default\n"); liquid_print_crc_schemes(); - printf(" c : coding scheme (inner): g2412 default\n"); - printf(" k : coding scheme (outer): none default\n"); + printf(" -c : coding scheme (inner): g2412 default\n"); + printf(" -k : coding scheme (outer): none default\n"); liquid_print_fec_schemes(); - printf(" s : SNR start [dB], default: -2\n"); - printf(" x : SNR max [dB], default: 13\n"); - printf(" n : number of SNR steps, default: 16\n"); - printf(" t : number of trials, default: 800\n"); + printf(" -s : SNR start [dB], default: -5\n"); + printf(" -x : SNR max [dB], default: 20\n"); + printf(" -d : SNR step [dB], default: 0.5\n"); + printf(" -e : minimum number of packet errors per step, default: 50\n"); + printf(" -t : minimum number of packet trials per step, default: 2000\n"); + printf(" -T : maximum number of packet trials per step, default: 40000\n"); } int main(int argc, char *argv[]) { - //srand( time(NULL) ); - - // options + // options (defaults to frame64 parameters) modulation_scheme ms = LIQUID_MODEM_QPSK; // mod. scheme - crc_scheme check = LIQUID_CRC_32; // data validity check - fec_scheme fec0 = LIQUID_FEC_GOLAY2412; // fec (inner) - fec_scheme fec1 = LIQUID_FEC_NONE; // fec (outer) - unsigned int payload_len = 400; // payload length + crc_scheme check = LIQUID_CRC_24; // data validity check + fec_scheme fec0 = LIQUID_FEC_NONE; // fec (inner) + fec_scheme fec1 = LIQUID_FEC_GOLAY2412; // fec (outer) + unsigned int payload_len = 72; // payload length float SNRdB_min = -5.0f; // signal-to-noise ratio (minimum) - float SNRdB_max = 10.0f; // signal-to-noise ratio (maximum) - unsigned int num_snr = 31; // number of SNR steps - unsigned int num_packet_trials = 800; // number of trials + float SNRdB_max = 20.0f; // signal-to-noise ratio (maximum) + float SNRdB_step = 0.5f; // signal-to-noise ratio (maximum) + unsigned int min_packet_errors = 50; // minimum errors to observe for each step + unsigned int min_packet_trials = 2000; // minimum number of packets for each step + unsigned int max_packet_trials =40000; // maximum number of packets for each step // get options int dopt; - while((dopt = getopt(argc,argv,"hp:m:v:c:k:s:x:n:t:")) != EOF){ + while((dopt = getopt(argc,argv,"hp:m:v:c:k:s:x:d:e:t:T:")) != EOF){ switch (dopt) { case 'h': usage(); return 0; case 'p': payload_len = atol(optarg); break; @@ -63,18 +60,16 @@ int main(int argc, char *argv[]) case 'k': fec1 = liquid_getopt_str2fec(optarg); break; case 's': SNRdB_min = atof(optarg); break; case 'x': SNRdB_max = atof(optarg); break; - case 'n': num_snr = atoi(optarg); break; - case 't': num_packet_trials = atoi(optarg); break; + case 'd': SNRdB_step = atof(optarg); break; + case 'e': min_packet_errors = atoi(optarg); break; + case 't': min_packet_trials = atoi(optarg); break; + case 'T': max_packet_trials = atoi(optarg); break; default: exit(-1); } } - unsigned int i; - // derived values - float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1); - // create and configure packet encoder/decoder object qpacketmodem q = qpacketmodem_create(); qpacketmodem_configure(q, payload_len, check, fec0, fec1, ms); @@ -82,32 +77,27 @@ int main(int argc, char *argv[]) // get frame length unsigned int frame_len = qpacketmodem_get_frame_len(q); - unsigned int num_bit_trials = 8*num_packet_trials*payload_len; // initialize payload unsigned char payload_tx [payload_len]; // payload (transmitted) unsigned char payload_rx [payload_len]; // payload (received) float complex frame_tx [frame_len]; // frame samples (transmitted) float complex frame_rx [frame_len]; // frame samples (received) - unsigned int num_bit_errors [num_snr]; // bit errors for each SNR point - unsigned int num_packet_errors[num_snr]; // packet errors for each SNR point - float BER [num_snr]; // bit error rate - float PER [num_snr]; // packet error rate + + // output file + FILE* fid = fopen(OUTPUT_FILENAME, "w"); + fprintf(fid,"%% %s: auto-generated file\n", OUTPUT_FILENAME); + fprintf(fid,"clear all; close all; SNR=[]; ber=[]; per=[];\n"); printf(" %8s %8s %8s %12s %8s %8s %6s\n", "SNR [dB]", "errors", "bits", "BER", "errors", "packets", "PER"); - unsigned int s; - for (s=0; s= min_packet_errors) + break; + if (num_packet_trials >= max_packet_trials) + break; } - BER[s] = (float)num_bit_errors[s] / (float)num_bit_trials; - PER[s] = (float)num_packet_errors[s] / (float)num_packet_trials; + float BER = (float)num_bit_errors / (float)num_bit_trials; + float PER = (float)num_packet_errors / (float)num_packet_trials; printf(" %8.2f %8u %8u %12.4e %8u %8u %6.2f%%\n", SNRdB, - num_bit_errors[s], num_bit_trials, BER[s], - num_packet_errors[s], num_packet_trials, PER[s]*100.0f); + num_bit_errors, num_bit_trials, BER, + num_packet_errors, num_packet_trials, PER*100.0f); + fprintf(fid,"SNR(end+1)=%g; ber(end+1)=%g; per(end+1)=%g;\n", SNRdB, BER, PER); + if (num_packet_errors < min_packet_errors) + break; + SNRdB += SNRdB_step; } // destroy allocated objects qpacketmodem_destroy(q); - // - // export output file - // - FILE * fid = fopen(OUTPUT_FILENAME, "w"); - fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME); - fprintf(fid,"\n\n"); - fprintf(fid,"clear all\n"); - fprintf(fid,"close all\n"); - fprintf(fid,"payload_len = %u; %% payload length [bytes]\n", payload_len); - fprintf(fid,"frame_len = %u; %% frame length [symbols]\n", frame_len); - fprintf(fid,"num_snr = %u;\n", num_snr); - fprintf(fid,"num_packet_trials = %u;\n", num_packet_trials); - fprintf(fid,"num_bit_trials = %u;\n", num_bit_trials); - fprintf(fid,"SNRdB_min = %8.2f;\n", SNRdB_min); - fprintf(fid,"SNRdB_max = %8.2f;\n", SNRdB_max); - fprintf(fid,"scheme = '%s/%s/%s';\n", - fec_scheme_str[fec0][1], - fec_scheme_str[fec1][1], - modulation_types[ms].name); - fprintf(fid,"rate = 8*payload_len / frame_len; %% true rate [bits/symbol]\n"); - for (i=0; i 0 -// _k : samples/symbol -// _m : filter delay -// _beta : filter bandwidth parameter, _beta > 0 -// _type : filter type (e.g. LIQUID_CPFSK_SQUARE) -qdetector_cccf qdetector_cccf_create_cpfsk(unsigned char * _sequence, - unsigned int _sequence_len, - unsigned int _bps, - float _h, - unsigned int _k, - unsigned int _m, - float _beta, - int _type); - -// Copy object including all internal objects and state -qdetector_cccf qdetector_cccf_copy(qdetector_cccf _q); - -int qdetector_cccf_destroy(qdetector_cccf _q); -int qdetector_cccf_print (qdetector_cccf _q); -int qdetector_cccf_reset (qdetector_cccf _q); - -// run detector, looking for sequence; return pointer to aligned, buffered samples -void * qdetector_cccf_execute(qdetector_cccf _q, - liquid_float_complex _x); - -// get detection threshold -float qdetector_cccf_get_threshold(qdetector_cccf _q); +#define LIQUID_QDETECTOR_MANGLE_CCCF(name) LIQUID_CONCAT(qdetector_cccf,name) + +#define LIQUID_QDETECTOR_DEFINE_API(QDETECTOR,TO,TC,TI) \ + \ +/* Frame detector and synchronizer; uses a novel correlation method to */ \ +/* detect a synchronization pattern, estimate carrier frequency and */ \ +/* phase offsets as well as timing phase, then correct for these */ \ +/* impairments in a simple interface suitable for custom frame recovery.*/ \ +typedef struct QDETECTOR(_s) * QDETECTOR(); \ + \ +/* Create detector with generic sequence */ \ +/* _s : sample sequence */ \ +/* _s_len : length of sample sequence */ \ +QDETECTOR() QDETECTOR(_create)(TI * _s, \ + unsigned int _s_len); \ + \ +/* Create detector from sequence of symbols using internal linear */ \ +/* interpolator */ \ +/* _sequence : symbol sequence */ \ +/* _sequence_len : length of symbol sequence */ \ +/* _ftype : filter prototype (e.g. LIQUID_FIRFILT_RRC) */ \ +/* _k : samples/symbol */ \ +/* _m : filter delay */ \ +/* _beta : excess bandwidth factor */ \ +QDETECTOR() QDETECTOR(_create_linear)(TI * _sequence, \ + unsigned int _sequence_len, \ + int _ftype, \ + unsigned int _k, \ + unsigned int _m, \ + float _beta); \ + \ +/* create detector from sequence of GMSK symbols */ \ +/* _sequence : bit sequence */ \ +/* _sequence_len : length of bit sequence */ \ +/* _k : samples/symbol */ \ +/* _m : filter delay */ \ +/* _beta : excess bandwidth factor */ \ +QDETECTOR() QDETECTOR(_create_gmsk)(unsigned char * _sequence, \ + unsigned int _sequence_len, \ + unsigned int _k, \ + unsigned int _m, \ + float _beta); \ + \ +/* create detector from sequence of CP-FSK symbols (assuming one */ \ +/* bit/symbol) */ \ +/* _sequence : bit sequence */ \ +/* _sequence_len : length of bit sequence */ \ +/* _bps : bits per symbol, 0 < _bps <= 8 */ \ +/* _h : modulation index, _h > 0 */ \ +/* _k : samples/symbol */ \ +/* _m : filter delay */ \ +/* _beta : filter bandwidth parameter, _beta > 0 */ \ +/* _type : filter type (e.g. LIQUID_CPFSK_SQUARE) */ \ +QDETECTOR() QDETECTOR(_create_cpfsk)(unsigned char * _sequence, \ + unsigned int _sequence_len, \ + unsigned int _bps, \ + float _h, \ + unsigned int _k, \ + unsigned int _m, \ + float _beta, \ + int _type); \ + \ +/* Copy object including all internal objects and state */ \ +QDETECTOR() QDETECTOR(_copy)(QDETECTOR() _q); \ + \ +/* Destroy synchronizer object and free all internal memory */ \ +int QDETECTOR(_destroy)(QDETECTOR() _q); \ + \ +/* Reset synchronizer object's internal buffer */ \ +int QDETECTOR(_reset)(QDETECTOR() _q); \ + \ +/* Print synchronizer object information to stdout */ \ +int QDETECTOR(_print)(QDETECTOR() _q); \ + \ +/* run detector, looking for sequence; return pointer to aligned, */ \ +/* buffered samples */ \ +void * QDETECTOR(_execute)(QDETECTOR() _q, TI _x); \ + \ +/* get detection threshold */ \ +float QDETECTOR(_get_threshold)(QDETECTOR() _q); \ + \ +/* set detection threshold (should be between 0 and 1, good starting */ \ +/* point is 0.5) */ \ +int QDETECTOR(_set_threshold)(QDETECTOR() _q, \ + float _threshold); \ + \ +/* Set carrier offset search range */ \ +int QDETECTOR(_set_range)(QDETECTOR() _q, \ + float _dphi_max); \ + \ +/* Get sequence length */ \ +unsigned int QDETECTOR(_get_seq_len)(QDETECTOR() _q); \ + \ +/* Get pointer to sequence of detected frame */ \ +const void * QDETECTOR(_get_sequence)(QDETECTOR() _q); \ + \ +/* Get buffer length */ \ +unsigned int QDETECTOR(_get_buf_len)(QDETECTOR() _q); \ + \ +/* Get correlator output of detected frame */ \ +float QDETECTOR(_get_rxy)(QDETECTOR() _q); \ + \ +/* Get fractional timing offset estimate of detected frame */ \ +float QDETECTOR(_get_tau)(QDETECTOR() _q); \ + \ +/* Get channel gain of detected frame */ \ +float QDETECTOR(_get_gamma)(QDETECTOR() _q); \ + \ +/* Get carrier frequency offset estimateof detected frame */ \ +float QDETECTOR(_get_dphi)(QDETECTOR() _q); \ + \ +/* Get carrier phase offset estimate of detected frame */ \ +float QDETECTOR(_get_phi)(QDETECTOR() _q); \ -// set detection threshold (should be between 0 and 1, good starting point is 0.5) -int qdetector_cccf_set_threshold(qdetector_cccf _q, - float _threshold); +LIQUID_QDETECTOR_DEFINE_API(LIQUID_QDETECTOR_MANGLE_CCCF, + liquid_float_complex, + liquid_float_complex, + liquid_float_complex) -// set carrier offset search range -int qdetector_cccf_set_range(qdetector_cccf _q, - float _dphi_max); +// metadata struct: +// - sample count since object was created +// - sample count since beginning of frame + +// +// qdsync +// +#define LIQUID_QDSYNC_MANGLE_CCCF(name) LIQUID_CONCAT(qdsync_cccf,name) + +#define LIQUID_QDSYNC_DEFINE_API(QDSYNC,TO,TC,TI) \ + \ +/* Frame detector and synchronizer; uses a novel correlation method to */ \ +/* detect a synchronization pattern, estimate carrier frequency and */ \ +/* phase offsets as well as timing phase, then correct for these */ \ +/* impairments in a simple interface suitable for custom frame recovery.*/ \ +typedef struct QDSYNC(_s) * QDSYNC(); \ + \ +/* synchronization callback, return 0:continue, 1:reset */ \ +typedef int (*QDSYNC(_callback))(TO * _buf, \ + unsigned int _buf_len, \ + void * _context); \ + \ +/* create detector with generic sequence */ \ +/* _s : sample sequence */ \ +/* _s_len : length of sample sequence */ \ +/* _ftype : filter type */ \ +/* _k : samples per symbol */ \ +/* _m : filter semi-length */ \ +/* _beta : filter excess bandwidth factor */ \ +/* _callback : user-defined callback */ \ +/* _context : user-defined context */ \ +QDSYNC() QDSYNC(_create_linear)(TI * _s, \ + unsigned int _s_len, \ + int _ftype, \ + unsigned int _k, \ + unsigned int _m, \ + float _beta, \ + QDSYNC(_callback) _callback, \ + void * _context); \ + \ +/* Copy object recursively including all internal objects and state */ \ +QDSYNC() QDSYNC(_copy)(QDSYNC() _q); \ + \ +/* Destroy synchronizer object and free all internal memory */ \ +int QDSYNC(_destroy)(QDSYNC() _q); \ + \ +/* Reset synchronizer object's internal buffer */ \ +int QDSYNC(_reset)(QDSYNC() _q); \ + \ +/* Print synchronizer object information to stdout */ \ +int QDSYNC(_print)(QDSYNC() _q); \ + \ +/* Get detection threshold */ \ +float QDSYNC(_get_threshold)(QDSYNC() _q); \ + \ +/* Set detection threshold */ \ +int QDSYNC(_set_threshold)(QDSYNC() _q, \ + float _threshold); \ + \ +/* set carrier offset search range */ \ +int QDSYNC(_set_range)(QDSYNC() _q, \ + float _dphi_max); \ + \ +/* Set callback method */ \ +int QDSYNC(_set_callback)(QDSYNC() _q, \ + QDSYNC(_callback) _callback); \ + \ +/* Set context value */ \ +int QDSYNC(_set_context)(QDSYNC() _q, void * _context); \ + \ +/* Set callback buffer size (the number of symbol provided to the */ \ +/* callback whenever it is invoked). */ \ +int QDSYNC(_set_buf_len)(QDSYNC() _q, unsigned int _buf_len); \ + \ +/* execute block of samples */ \ +int QDSYNC(_execute)(QDSYNC() _q, \ + TI * _buf, \ + unsigned int _buf_len); \ + \ +/* Return flag indicating if synchronizer actively running. */ \ +int QDSYNC(_is_open)(QDSYNC() _q); \ + \ +/* Get synchronizer correlator output after frame was detected */ \ +float QDSYNC(_get_rxy) (QDSYNC() _q); \ + \ +/* Get synchronizer fractional timing offset after frame was detected */ \ +float QDSYNC(_get_tau) (QDSYNC() _q); \ + \ +/* Get synchronizer channel gain after frame was detected */ \ +float QDSYNC(_get_gamma)(QDSYNC() _q); \ + \ +/* Get synchronizer frequency offset estimate after frame was detected */ \ +float QDSYNC(_get_dphi) (QDSYNC() _q); \ + \ +/* Get synchronizer phase offset estimate after frame was detected */ \ +float QDSYNC(_get_phi) (QDSYNC() _q); \ -// access methods -unsigned int qdetector_cccf_get_seq_len (qdetector_cccf _q); // sequence length -const void * qdetector_cccf_get_sequence(qdetector_cccf _q); // pointer to sequence -unsigned int qdetector_cccf_get_buf_len (qdetector_cccf _q); // buffer length -float qdetector_cccf_get_rxy (qdetector_cccf _q); // correlator output -float qdetector_cccf_get_tau (qdetector_cccf _q); // fractional timing offset estimate -float qdetector_cccf_get_gamma (qdetector_cccf _q); // channel gain -float qdetector_cccf_get_dphi (qdetector_cccf _q); // carrier frequency offset estimate -float qdetector_cccf_get_phi (qdetector_cccf _q); // carrier phase offset estimate +LIQUID_QDSYNC_DEFINE_API(LIQUID_QDSYNC_MANGLE_CCCF, + liquid_float_complex, + liquid_float_complex, + liquid_float_complex) // // Pre-demodulation detector @@ -6926,7 +7109,7 @@ float liquid_flattop(unsigned int _i, // Triangular window // _i : window index, _i in [0,_wlen-1] // _wlen : full window length -// _n : triangle length, _n in {_wlen-1, _wlen, _wlen+1} +// _n : triangle length, _n in {_wlen-1, _wlen, _wlen+1} float liquid_triangular(unsigned int _i, unsigned int _wlen, unsigned int _n); @@ -8374,7 +8557,7 @@ FIRPFBCH() FIRPFBCH(_create)(int _type, \ /* _type : type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER) */ \ /* _M : number of channels */ \ /* _m : filter delay (symbols) */ \ -/* _As : stop-band attenuation [dB] */ \ +/* _As : stop-band attenuation [dB] */ \ FIRPFBCH() FIRPFBCH(_create_kaiser)(int _type, \ unsigned int _M, \ unsigned int _m, \ @@ -9441,23 +9624,37 @@ int bsequence_create_ccodes(bsequence _a, bsequence _b); // M-Sequence -#define LIQUID_MAX_MSEQUENCE_LENGTH 32767 - -// default m-sequence generators: g (hex) m n g (oct) g (binary) -#define LIQUID_MSEQUENCE_GENPOLY_M2 0x0007 // 2 3 7 111 -#define LIQUID_MSEQUENCE_GENPOLY_M3 0x000B // 3 7 13 1011 -#define LIQUID_MSEQUENCE_GENPOLY_M4 0x0013 // 4 15 23 10011 -#define LIQUID_MSEQUENCE_GENPOLY_M5 0x0025 // 5 31 45 100101 -#define LIQUID_MSEQUENCE_GENPOLY_M6 0x0043 // 6 63 103 1000011 -#define LIQUID_MSEQUENCE_GENPOLY_M7 0x0089 // 7 127 211 10001001 -#define LIQUID_MSEQUENCE_GENPOLY_M8 0x011D // 8 255 435 100101101 -#define LIQUID_MSEQUENCE_GENPOLY_M9 0x0211 // 9 511 1021 1000010001 -#define LIQUID_MSEQUENCE_GENPOLY_M10 0x0409 // 10 1023 2011 10000001001 -#define LIQUID_MSEQUENCE_GENPOLY_M11 0x0805 // 11 2047 4005 100000000101 -#define LIQUID_MSEQUENCE_GENPOLY_M12 0x1053 // 12 4095 10123 1000001010011 -#define LIQUID_MSEQUENCE_GENPOLY_M13 0x201b // 13 8191 20033 10000000011011 -#define LIQUID_MSEQUENCE_GENPOLY_M14 0x402b // 14 16383 40053 100000000101011 -#define LIQUID_MSEQUENCE_GENPOLY_M15 0x8003 // 15 32767 100003 1000000000000011 +// default m-sequence generators: g (hex) m n +#define LIQUID_MSEQUENCE_GENPOLY_M2 0x00000003 // 2 3 +#define LIQUID_MSEQUENCE_GENPOLY_M3 0x00000006 // 3 7 +#define LIQUID_MSEQUENCE_GENPOLY_M4 0x0000000c // 4 15 +#define LIQUID_MSEQUENCE_GENPOLY_M5 0x00000014 // 5 31 +#define LIQUID_MSEQUENCE_GENPOLY_M6 0x00000030 // 6 63 +#define LIQUID_MSEQUENCE_GENPOLY_M7 0x00000060 // 7 127 +#define LIQUID_MSEQUENCE_GENPOLY_M8 0x000000b8 // 8 255 +#define LIQUID_MSEQUENCE_GENPOLY_M9 0x00000110 // 9 511 +#define LIQUID_MSEQUENCE_GENPOLY_M10 0x00000240 // 10 1,023 +#define LIQUID_MSEQUENCE_GENPOLY_M11 0x00000500 // 11 2,047 +#define LIQUID_MSEQUENCE_GENPOLY_M12 0x00000e08 // 12 4,095 +#define LIQUID_MSEQUENCE_GENPOLY_M13 0x00001c80 // 13 8,191 +#define LIQUID_MSEQUENCE_GENPOLY_M14 0x00003802 // 14 16,383 +#define LIQUID_MSEQUENCE_GENPOLY_M15 0x00006000 // 15 32,767 +#define LIQUID_MSEQUENCE_GENPOLY_M16 0x0000d008 // 16 65,535 +#define LIQUID_MSEQUENCE_GENPOLY_M17 0x00012000 // 17 131,071 +#define LIQUID_MSEQUENCE_GENPOLY_M18 0x00020400 // 18 262,143 +#define LIQUID_MSEQUENCE_GENPOLY_M19 0x00072000 // 19 524,287 +#define LIQUID_MSEQUENCE_GENPOLY_M20 0x00090000 // 20 1,048,575 +#define LIQUID_MSEQUENCE_GENPOLY_M21 0x00140000 // 21 2,097,151 +#define LIQUID_MSEQUENCE_GENPOLY_M22 0x00300000 // 22 4,194,303 +#define LIQUID_MSEQUENCE_GENPOLY_M23 0x00420000 // 23 8,388,607 +#define LIQUID_MSEQUENCE_GENPOLY_M24 0x00e10000 // 24 16,777,215 +#define LIQUID_MSEQUENCE_GENPOLY_M25 0x01000004 // 25 33,554,431 +#define LIQUID_MSEQUENCE_GENPOLY_M26 0x02000023 // 26 67,108,863 +#define LIQUID_MSEQUENCE_GENPOLY_M27 0x04000013 // 27 134,217,727 +#define LIQUID_MSEQUENCE_GENPOLY_M28 0x08000004 // 28 268,435,455 +#define LIQUID_MSEQUENCE_GENPOLY_M29 0x10000002 // 29 536,870,911 +#define LIQUID_MSEQUENCE_GENPOLY_M30 0x20000029 // 30 1,073,741,823 +#define LIQUID_MSEQUENCE_GENPOLY_M31 0x40000004 // 31 2,147,483,647 typedef struct msequence_s * msequence; @@ -9489,7 +9686,7 @@ unsigned int msequence_advance(msequence _ms); // advancing _bps bits and returning compacted symbol // _ms : m-sequence object // _bps : bits per symbol of output -unsigned int msequence_generate_symbol(msequence _ms, +unsigned int msequence_generate_symbol(msequence _ms, unsigned int _bps); // reset msequence shift register to original state, typically '1' @@ -9501,9 +9698,15 @@ int msequence_reset(msequence _ms); int bsequence_init_msequence(bsequence _bs, msequence _ms); -// get the length of the sequence +// get the length of the generator polynomial, g (m) +unsigned int msequence_get_genpoly_length(msequence _ms); + +// get the length of the sequence (n=2^m-1) unsigned int msequence_get_length(msequence _ms); +// get the generator polynomial, g +unsigned int msequence_get_genpoly(msequence _ms); + // get the internal state of the sequence unsigned int msequence_get_state(msequence _ms); @@ -9511,6 +9714,12 @@ unsigned int msequence_get_state(msequence _ms); int msequence_set_state(msequence _ms, unsigned int _a); +// measure the period the shift register (should be 2^m-1 with a proper generator polynomial) +unsigned int msequence_measure_period(msequence _ms); + +// measure the period of a generator polynomial +unsigned int msequence_genpoly_period(unsigned int _g); + // // MODULE : utility diff --git a/include/liquid.internal.h b/include/liquid.internal.h index 64b0e0b60d..4e2f448e03 100644 --- a/include/liquid.internal.h +++ b/include/liquid.internal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -1117,7 +1117,7 @@ int QSOURCE(_generate)(QSOURCE() _q, \ \ int QSOURCE(_generate_into)(QSOURCE() _q, \ TO * _buf); \ - + LIQUID_QSOURCE_DEFINE_API(LIQUID_QSOURCE_MANGLE_CFLOAT, liquid_float_complex) // @@ -1482,55 +1482,6 @@ int ofdmframe_init_S1(unsigned char * _p, float complex * _s1, unsigned int * _M_S1); -// generate symbol (add cyclic prefix/postfix, overlap) -int ofdmframegen_gensymbol(ofdmframegen _q, - float complex * _buffer); - -int ofdmframesync_cpcorrelate(ofdmframesync _q); -int ofdmframesync_findrxypeak(ofdmframesync _q); -int ofdmframesync_rxpayload(ofdmframesync _q); - -int ofdmframesync_execute_seekplcp(ofdmframesync _q); -int ofdmframesync_execute_S0a(ofdmframesync _q); -int ofdmframesync_execute_S0b(ofdmframesync _q); -int ofdmframesync_execute_S1( ofdmframesync _q); -int ofdmframesync_execute_rxsymbols(ofdmframesync _q); - -int ofdmframesync_S0_metrics(ofdmframesync _q, - float complex * _G, - float complex * _s_hat); - -// estimate short sequence gain -// _q : ofdmframesync object -// _x : input array (time) -// _G : output gain (freq) -int ofdmframesync_estimate_gain_S0(ofdmframesync _q, - float complex * _x, - float complex * _G); - -// estimate long sequence gain -// _q : ofdmframesync object -// _x : input array (time) -// _G : output gain (freq) -int ofdmframesync_estimate_gain_S1(ofdmframesync _q, - float complex * _x, - float complex * _G); - -// estimate complex equalizer gain from G0 and G1 -// _q : ofdmframesync object -// _ntaps : number of time-domain taps for smoothing -int ofdmframesync_estimate_eqgain(ofdmframesync _q, - unsigned int _ntaps); - -// estimate complex equalizer gain from G0 and G1 using polynomial fit -// _q : ofdmframesync object -// _order : polynomial order -int ofdmframesync_estimate_eqgain_poly(ofdmframesync _q, - unsigned int _order); - -// recover symbol, correcting for gain, pilot phase, etc. -int ofdmframesync_rxsymbol(ofdmframesync _q); - // // MODULE : nco (numerically-controlled oscillator) // @@ -1694,20 +1645,6 @@ float randgammaf_delta(float _delta); // MODULE : sequence // -// maximal-length sequence -struct msequence_s { - unsigned int m; // length generator polynomial, shift register - unsigned int g; // generator polynomial - unsigned int a; // initial shift register state, default: 1 - - unsigned int n; // length of sequence, n = (2^m)-1 - unsigned int v; // shift register - unsigned int b; // return bit -}; - -// Default msequence generator objects -extern struct msequence_s msequence_default[16]; - // // MODULE : utility diff --git a/library.json b/library.json index fea268c461..d96b4a3f56 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "liquid-dsp", - "version": "1.4.0", + "version": "1.6.0", "description": "Software-defined radio digital signal processing library", "homepage": "https://liquidsdr.org", "keywords": @@ -39,10 +39,10 @@ "-<.git/>", "-<*/src/*.proto.c>", "-<*/src/*.av.c>", - "-<*/src/*.mmx.c>", "-<*/src/*.neon.c>", - "-<*/src/*.sse4.c>", + "-<*/src/*.sse.c>", "-<*/src/*.avx.c>", + "-<*/src/*.avx512f.c>", "-<*/src/*.x86.s>" ] }, diff --git a/makefile.in b/makefile.in index 5349762944..0971ac195f 100644 --- a/makefile.in +++ b/makefile.in @@ -1,4 +1,4 @@ -# Copyright (c) 2007 - 2022 Joseph Gaeddert +# Copyright (c) 2007 - 2023 Joseph Gaeddert # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -304,6 +304,7 @@ $(fec_objects) : %.o : %.c $(include_headers) fec_autotests := \ src/fec/tests/crc_autotest.c \ src/fec/tests/fec_autotest.c \ + src/fec/tests/fec_config_autotest.c \ src/fec/tests/fec_copy_autotest.c \ src/fec/tests/fec_soft_autotest.c \ src/fec/tests/fec_golay2412_autotest.c \ @@ -630,10 +631,6 @@ filter_benchmarks := \ framing_objects := \ src/framing/src/bpacketgen.o \ src/framing/src/bpacketsync.o \ - src/framing/src/bpresync_cccf.o \ - src/framing/src/bsync_rrrf.o \ - src/framing/src/bsync_crcf.o \ - src/framing/src/bsync_cccf.o \ src/framing/src/detector_cccf.o \ src/framing/src/dsssframegen.o \ src/framing/src/dsssframesync.o \ @@ -641,33 +638,46 @@ framing_objects := \ src/framing/src/framesyncstats.o \ src/framing/src/framegen64.o \ src/framing/src/framesync64.o \ + src/framing/src/framingcf.o \ + src/framing/src/framing_rrrf.o \ + src/framing/src/framing_crcf.o \ + src/framing/src/framing_cccf.o \ src/framing/src/flexframegen.o \ src/framing/src/flexframesync.o \ src/framing/src/fskframegen.o \ src/framing/src/fskframesync.o \ src/framing/src/gmskframegen.o \ src/framing/src/gmskframesync.o \ - src/framing/src/msourcecf.o \ src/framing/src/ofdmflexframegen.o \ src/framing/src/ofdmflexframesync.o \ - src/framing/src/presync_cccf.o \ - src/framing/src/symstreamcf.o \ - src/framing/src/symstreamrcf.o \ - src/framing/src/symtrack_cccf.o \ - src/framing/src/qdetector_cccf.o \ - src/framing/src/qpacketmodem.o \ src/framing/src/qpilotgen.o \ src/framing/src/qpilotsync.o \ # list explicit targets and dependencies here +framing_prototypes_gen := \ + src/framing/src/msource.proto.c \ + src/framing/src/qpacketmodem.proto.c \ + src/framing/src/qsource.proto.c \ + src/framing/src/symstream.proto.c \ + src/framing/src/symstreamr.proto.c \ + +framing_prototypes_sync := \ + src/framing/src/bpresync.proto.c \ + src/framing/src/bsync.proto.c \ + src/framing/src/presync.proto.c \ + src/framing/src/qdetector.proto.c \ + src/framing/src/qdsync.proto.c \ + src/framing/src/symtrack.proto.c \ + +src/framing/src/framingcf.o : %.o : %.c $(include_headers) ${framing_prototypes_gen} +src/framing/src/framing_rrrf.o : %.o : %.c $(include_headers) ${framing_prototypes_sync} +src/framing/src/framing_crcf.o : %.o : %.c $(include_headers) ${framing_prototypes_sync} +src/framing/src/framing_cccf.o : %.o : %.c $(include_headers) ${framing_prototypes_sync} + src/framing/src/bpacketgen.o : %.o : %.c $(include_headers) src/framing/src/bpacketsync.o : %.o : %.c $(include_headers) -src/framing/src/bpresync_cccf.o : %.o : %.c $(include_headers) src/framing/src/bpresync.proto.c -src/framing/src/bsync_rrrf.o : %.o : %.c $(include_headers) src/framing/src/bsync.proto.c -src/framing/src/bsync_crcf.o : %.o : %.c $(include_headers) src/framing/src/bsync.proto.c -src/framing/src/bsync_cccf.o : %.o : %.c $(include_headers) src/framing/src/bsync.proto.c src/framing/src/detector_cccf.o : %.o : %.c $(include_headers) src/framing/src/dsssframegen.o : %.o : %.c $(include_headers) src/framing/src/dsssframesync.o : %.o : %.c $(include_headers) @@ -677,15 +687,8 @@ src/framing/src/framegen64.o : %.o : %.c $(include_headers) src/framing/src/framesync64.o : %.o : %.c $(include_headers) src/framing/src/flexframegen.o : %.o : %.c $(include_headers) src/framing/src/flexframesync.o : %.o : %.c $(include_headers) -src/framing/src/msourcecf.o : %.o : %.c $(include_headers) src/framing/src/msource.proto.c src/framing/src/qsource.proto.c src/framing/src/ofdmflexframegen.o : %.o : %.c $(include_headers) src/framing/src/ofdmflexframesync.o : %.o : %.c $(include_headers) -src/framing/src/presync_cccf.o : %.o : %.c $(include_headers) src/framing/src/presync.proto.c -src/framing/src/qpacketmodem.o : %.o : %.c $(include_headers) -src/framing/src/symstreamcf.o : %.o : %.c $(include_headers) src/framing/src/symstream.proto.c -src/framing/src/symstreamrcf.o : %.o : %.c $(include_headers) src/framing/src/symstreamr.proto.c -src/framing/src/symtrack_cccf.o : %.o : %.c $(include_headers) src/framing/src/symtrack.proto.c - framing_autotests := \ src/framing/tests/bpacketsync_autotest.c \ @@ -694,11 +697,13 @@ framing_autotests := \ src/framing/tests/dsssframesync_autotest.c \ src/framing/tests/flexframesync_autotest.c \ src/framing/tests/framesync64_autotest.c \ + src/framing/tests/fskframesync_autotest.c \ src/framing/tests/gmskframe_autotest.c \ src/framing/tests/msource_autotest.c \ src/framing/tests/ofdmflexframe_autotest.c \ src/framing/tests/qdetector_cccf_autotest.c \ src/framing/tests/qdetector_cccf_copy_autotest.c \ + src/framing/tests/qdsync_cccf_autotest.c \ src/framing/tests/qpacketmodem_autotest.c \ src/framing/tests/qpilotsync_autotest.c \ src/framing/tests/qsource_autotest.c \ @@ -947,7 +952,8 @@ multichannel_autotests := \ src/multichannel/tests/firpfbch2_crcf_autotest.c \ src/multichannel/tests/firpfbch_crcf_synthesizer_autotest.c \ src/multichannel/tests/firpfbch_crcf_analyzer_autotest.c \ - src/multichannel/tests/ofdmframesync_autotest.c \ + src/multichannel/tests/firpfbch_crcf_autotest.c \ + src/multichannel/tests/ofdmframe_autotest.c \ # benchmarks multichannel_benchmarks := \ @@ -1011,8 +1017,11 @@ $(optim_objects) : %.o : %.c $(include_headers) # autotests optim_autotests := \ + src/optim/tests/gasearch_autotest.c \ src/optim/tests/gradsearch_autotest.c \ + src/optim/tests/qnsearch_autotest.c \ src/optim/tests/qs1dsearch_autotest.c \ + src/optim/tests/utility_autotest.c \ # benchmarks optim_benchmarks := @@ -1373,7 +1382,7 @@ install: all @echo "" mkdir -p $(DESTDIR)$(prefix)/include/liquid mkdir -p $(DESTDIR)$(libdir) - install -m 644 -p libliquid.${AR_LIB} $(DESTDIR)$(libdir)/libliquid.${AR_LIB}.${VERSION} + install -m 644 -p libliquid.${AR_LIB} $(DESTDIR)$(libdir)/libliquid.${AR_LIB} install -m 644 -p libliquid.${SH_LIB} $(DESTDIR)$(libdir)/libliquid.${SH_LIB}.${VERSION} install -m 644 -p include/liquid.h $(DESTDIR)$(prefix)/include/liquid/liquid.h ln -sf libliquid.${SH_LIB}.${VERSION} $(DESTDIR)$(libdir)/libliquid.${SH_LIB} @@ -1400,7 +1409,7 @@ install: all uninstall: @echo "uninstalling..." $(RM) $(DESTDIR)$(prefix)/include/liquid/liquid.h - $(RM) $(DESTDIR)$(libdir)/libliquid.${AR_LIB}.${VERSION} + $(RM) $(DESTDIR)$(libdir)/libliquid.${AR_LIB} $(RM) $(DESTDIR)$(libdir)/libliquid.${SH_LIB}.${VERSION} $(RM) $(DESTDIR)$(libdir)/libliquid.${SH_LIB}.1 $(RM) $(DESTDIR)$(libdir)/libliquid.${SH_LIB} @@ -1575,10 +1584,6 @@ $(bench_prog): $(bench_prog).o $(benchmark_obj) $(benchmark_extra_obj) ${ARCHIVE bench: $(bench_prog) ./$(bench_prog) -o benchmark.json -# benchmark compare script -scripts/benchmark_compare : % : %.c - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) - # fftbench program bench/fftbench.o : %.o : %.c $(CC) $(BENCH_CPPFLAGS) $(BENCH_CFLAGS) $< -c -o $@ @@ -1589,7 +1594,6 @@ bench/fftbench : % : %.o ${ARCHIVE_LIB} # clean up the generated files clean-bench: $(RM) benchmark_include.h $(bench_prog).o $(bench_prog) - $(RM) scripts/benchmark_compare $(RM) $(benchmark_obj) $(RM) $(benchmark_extra_obj) $(RM) bench/fftbench.o @@ -1710,8 +1714,9 @@ example_programs := \ examples/modem_pi4dqpsk_example \ examples/modem_soft_example \ examples/modular_arithmetic_example \ - examples/msequence_generator_example \ + examples/msequence_autocorr_example \ examples/msequence_example \ + examples/msequence_generator_example \ examples/msourcecf_example \ examples/msresamp_crcf_example \ examples/msresamp_crcf_noise_example \ @@ -1734,6 +1739,7 @@ example_programs := \ examples/polyfit_lagrange_example \ examples/poly_findroots_example \ examples/qdetector_cccf_example \ + examples/qdsync_cccf_example \ examples/qpacketmodem_performance_example \ examples/qpacketmodem_example \ examples/qpilotsync_example \ diff --git a/sandbox/fec_spc2216_test.c b/sandbox/fec_spc2216_test.c index edd5f27a1d..9a66df79a9 100644 --- a/sandbox/fec_spc2216_test.c +++ b/sandbox/fec_spc2216_test.c @@ -253,9 +253,9 @@ int main(int argc, char*argv[]) // // run SNR trials // - float SNRdB_min = -1.0f; // signal-to-noise ratio (minimum) - float SNRdB_max = 7.0f; // signal-to-noise ratio (maximum) - unsigned int num_snr = 33; // number of SNR steps + float SNRdB_min = -5.0f; // signal-to-noise ratio (minimum) + float SNRdB_max = 10.0f; // signal-to-noise ratio (maximum) + unsigned int num_snr = 31; // number of SNR steps unsigned int num_trials=10000; // number of trials // arrays @@ -484,14 +484,15 @@ void spc2216_decode(unsigned char * _msg_rec, spc2216_transpose_col(w, m_hat, parity_row); // compute syndromes on rows and decode +#if DEBUG_SPC2216 unsigned int num_uncorrected_errors = 0; +#endif for (i=0; i<16; i++) { sym_enc[0] = parity_row[i]; sym_enc[1] = m_hat[2*i+0]; sym_enc[2] = m_hat[2*i+1]; - int syndrome_flag = fec_secded2216_estimate_ehat(sym_enc, e_hat); - #if DEBUG_SPC2216 + int syndrome_flag = fec_secded2216_estimate_ehat(sym_enc, e_hat); if (syndrome_flag == 0) { printf("%3u : no errors detected\n", i); } else if (syndrome_flag == 1) { @@ -499,10 +500,10 @@ void spc2216_decode(unsigned char * _msg_rec, } else { printf("%3u : multiple errors detected\n", i); } -#endif if (syndrome_flag == 2) num_uncorrected_errors++; +#endif // apply error vector estimate to appropriate arrays parity_col[i] ^= e_hat[0]; @@ -510,7 +511,9 @@ void spc2216_decode(unsigned char * _msg_rec, m_hat[2*i+1] ^= e_hat[2]; } - //printf("number of uncorrected errors: %u\n", num_uncorrected_errors); +#if DEBUG_SPC2216 + printf("number of uncorrected errors: %u\n", num_uncorrected_errors); +#endif // copy decoded message to output memmove(_msg_dec, m_hat, 32*sizeof(unsigned char)); diff --git a/scripts/autoscript.c b/scripts/autoscript.c index fd669808e8..f71533af81 100644 --- a/scripts/autoscript.c +++ b/scripts/autoscript.c @@ -297,11 +297,7 @@ void autoscript_parsefile(autoscript _q, // generate tag (e.g. "void benchmark_"); unsigned int tag_len = 5 + strlen(_q->type) + 2; char tag[tag_len]; - strcpy(tag, "void "); - strcpy(tag+5, _q->type); - tag[tag_len-2] = '_'; - tag[tag_len-1] = '\0'; - //printf("// tag : '%s'\n", tag); + sprintf(tag, "void %s_", _q->type); // parse file, looking for key char buffer[1024]; // line buffer @@ -343,6 +339,7 @@ void autoscript_parsefile(autoscript _q, package_added = 1; } autoscript_addscript(_q, _package_name, basename); + printf("// adding %s to package %s from %s:%u\n", basename, _package_name, _filename, n); } } while (!feof(fid)); diff --git a/scripts/benchmark_compare.c b/scripts/benchmark_compare.c deleted file mode 100644 index dbb138aef5..0000000000 --- a/scripts/benchmark_compare.c +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (c) 2007 - 2015 Joseph Gaeddert - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// -// benchmark_compare.c -// -// compare benchmark runs -// - -#include -#include -#include -#include - -// print usage/help message -void usage() -{ - printf("benchmark_compare [old_benchmark] [new_benchmark]\n"); -} - -// define benchmark_t -struct benchmark_t { - //unsigned int id; - char name[64]; - //unsigned int name_len; - //unsigned int num_trials; - //float extime; - //float rate; - float cycles_per_trial; - - // link to other benchmark - struct benchmark_t * link; -}; - -struct benchlist_s { - struct benchmark_t * benchmarks; - unsigned int num_benchmarks; -}; - -typedef struct benchlist_s * benchlist; - -// -benchlist benchlist_create(); -void benchlist_destroy(benchlist _q); -void benchlist_print(benchlist _q); -void benchlist_append(benchlist _q, - char * _name, - float _cycles_per_trial); - -void benchlist_link(benchlist _q0, - benchlist _q1); - -// is line a comment? -int parse_file(const char * _filename, - benchlist _benchmarks); - -// read line from file -// _fid : input file -// _buffer : output buffer -// _n : buffer length -void readline(FILE * _fid, - char * _buffer, - unsigned int _n); - -// is line a comment? -int line_is_comment(char * _buffer); - -int main(int argc, char*argv[]) -{ - if (argc != 3) { - usage(); - exit(1); - } - - // parse old benchmarks - benchlist benchmarks_old = benchlist_create(); - parse_file(argv[1], benchmarks_old); - //benchlist_print(benchmarks_old); - - // parse new benchmarks - benchlist benchmarks_new = benchlist_create(); - parse_file(argv[2], benchmarks_new); - //benchlist_print(benchmarks_new); - - // link benchmarks and print results - benchlist_link(benchmarks_old, benchmarks_new); - benchlist_print(benchmarks_old); - - // destroy benchmark lists - benchlist_destroy(benchmarks_old); - benchlist_destroy(benchmarks_new); - - printf("done.\n"); - return 0; -} - -// -benchlist benchlist_create() -{ - benchlist q = (benchlist) malloc(sizeof(struct benchlist_s)); - - // initialize internals - q->benchmarks = NULL; - q->num_benchmarks = 0; - - // return main object - return q; -} - -void benchlist_destroy(benchlist _q) -{ - // free internals - if (_q->benchmarks != NULL) - free(_q->benchmarks); - - // free main object memory - free(_q); -} - -#if 0 -void benchlist_print(benchlist _q) -{ - unsigned int i; - printf("benchlist [%u]:\n", _q->num_benchmarks); - for (i=0; i<_q->num_benchmarks; i++) { - printf(" - %-32s : %14.2f", _q->benchmarks[i].name, - _q->benchmarks[i].cycles_per_trial); - if (_q->benchmarks[i].link == NULL) { - printf("\n"); - } else { - // print delta - float cycles_old = _q->benchmarks[i].cycles_per_trial; - float cycles_new = _q->benchmarks[i].link->cycles_per_trial; - float delta = (cycles_new - cycles_old)/cycles_old; - - // print static value - printf(" : %12.3f %8.2f %%\n", cycles_new, 100*delta); - } - - } -} -#else -void benchlist_print(benchlist _q) -{ - unsigned int i; - printf("benchlist [%u]:\n", _q->num_benchmarks); - for (i=0; i<_q->num_benchmarks; i++) { - if (_q->benchmarks[i].link == NULL) - continue; - - printf(" - %-28s ", _q->benchmarks[i].name); - // print delta - float cycles_old = _q->benchmarks[i].cycles_per_trial; - float cycles_new = _q->benchmarks[i].link->cycles_per_trial; - float speedup = (cycles_old + 1e-6f) / (cycles_new + 1e-6f); - - // print bar graph style response - unsigned int num_spaces = 20; - unsigned int num_hashes = (unsigned int) (0.4*fabsf(log2f(speedup)) * num_spaces); - if (num_hashes > num_spaces) num_hashes = num_spaces; - unsigned int j; - if (speedup > 1.0) { - // newer is better - for (j=0; jnum_benchmarks; i++) { - if ( strncmp(_q->benchmarks[i].name, _name, 64) == 0) { - fprintf(stderr,"warning: cannot append benchmark '%s'; benchmark already exists\n", _name); - return; - } - } - - // re-allocate memory array - _q->num_benchmarks++; - _q->benchmarks = (struct benchmark_t*)realloc(_q->benchmarks, (_q->num_benchmarks)*sizeof(struct benchmark_t)); - - //printf("appending '%s' (%f)\n", _name, _cycles_per_trial); - - // - // append new element - // - - // copy name - strncpy(_q->benchmarks[_q->num_benchmarks-1].name, _name, 64); - - // copy properties - _q->benchmarks[_q->num_benchmarks-1].cycles_per_trial = _cycles_per_trial; - - // set link to NULL - _q->benchmarks[_q->num_benchmarks-1].link = NULL; -} - -void benchlist_link(benchlist _q0, - benchlist _q1) -{ - // link nodes - unsigned int i; - unsigned int j; - for (i=0; i<_q0->num_benchmarks; i++) { - for (j=0; j<_q1->num_benchmarks; j++) { - if ( strncmp(_q0->benchmarks[i].name, _q1->benchmarks[j].name, 64)==0 ) { - // match found; link nodes - _q0->benchmarks[i].link = &_q1->benchmarks[j]; - _q1->benchmarks[j].link = &_q0->benchmarks[i]; - } - } - } -} - - -// is line a comment? -int parse_file(const char * _filename, - benchlist _benchmarks) -{ - // - FILE * fid = fopen(_filename,"r"); - if (!fid) { - fprintf(stderr,"error: parse_file(), could not open '%s' for reading\n", _filename); - exit(1); - } - - printf("parsing '%s'...\n", _filename); - char buffer[256]; // line buffer - - int id; - char name[64]; - unsigned long int num_trials; - float execution_time; - float rate; - float cycles_per_trial; - - do { - // read line into buffer - readline(fid, buffer, 256); - - // skip comment lines - if (line_is_comment(buffer)) - continue; - - // scan line for results - int results = sscanf(buffer,"%d %s %lu %f %f %f\n", - &id, - name, - &num_trials, - &execution_time, - &rate, - &cycles_per_trial); - if (results < 6) { - //fprintf(stderr,"warning: skipping line '%s'\n", buffer); - continue; - } - - // append... - benchlist_append(_benchmarks, name, cycles_per_trial); - - } while (!feof(fid)); - - // close the file - fclose(fid); - - // return - return 0; -} - - -// read line from file -// _fid : input file -// _buffer : output buffer -// _n : buffer length -void readline(FILE * _fid, - char * _buffer, - unsigned int _n) -{ - unsigned int i=0; - char c; - while ( (c = fgetc(_fid)) != EOF && c != '\n' && i < _n-1) { - _buffer[i] = c; - i++; - } - _buffer[i] = '\0'; -} - -// is line a comment? -int line_is_comment(char * _buffer) -{ - // read first non-whitespace character - unsigned int i=0; - char c; - do { - c = _buffer[i]; - i++; - } while (c == ' ' || c == '\t'); - - if (c == '#' || c == '\0') - return 1; - - return 0; -} - diff --git a/scripts/benchmark_compare.py b/scripts/benchmark_compare.py new file mode 100755 index 0000000000..0fdc7295f0 --- /dev/null +++ b/scripts/benchmark_compare.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +'''Compare benchmark results''' +import argparse, json, numpy as np + +# parse command-line arguments +p = argparse.ArgumentParser(description=__doc__) +p.add_argument('old', help='old benchmark file (.json)') +p.add_argument('new', help='new benchmark file (.json)') +p.add_argument('-thresh', default=1.5, type=float, help='threshold for displaying deltas') +args = p.parse_args() + +# load json files +v0 = json.load(open(args.old,'r')) +v1 = json.load(open(args.new,'r')) + +# convert to dictionary and get set of common names +b0 = {v['name']:v for v in v0['benchmarks']} +b1 = {v['name']:v for v in v1['benchmarks']} +set_common = set(b0.keys()).intersection(set(b1.keys())) + +# iterate over common benchmarks and print results +for key in set_common: + r0,r1 = b0[key], b1[key] + if 0 in (r0['trials'],r1['trials']): + continue + rate = r1['rate'] / r0['rate'] + if np.exp(np.abs(np.log(rate))) < args.thresh: + continue + + print(' %32s, rate = %12.8f' % (key, rate)) + diff --git a/scripts/config.guess b/scripts/config.guess index 7f76b6228f..69188da73d 100644 --- a/scripts/config.guess +++ b/scripts/config.guess @@ -1,10 +1,10 @@ #! /bin/sh # Attempt to guess a canonical system name. -# Copyright 1992-2022 Free Software Foundation, Inc. +# Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2022-01-09' +timestamp='2023-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. -Copyright 1992-2022 Free Software Foundation, Inc. +Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -966,6 +966,12 @@ EOF GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC ;; + x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-pc-managarm-mlibc" + ;; + *:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" + ;; *:Minix:*:*) GUESS=$UNAME_MACHINE-unknown-minix ;; @@ -1036,7 +1042,7 @@ EOF k1om:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; - loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*) + loongarch32:Linux:*:* | loongarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m32r*:Linux:*:*) @@ -1151,16 +1157,27 @@ EOF ;; x86_64:Linux:*:*) set_cc_for_build + CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then - if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ - (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_X32 >/dev/null - then - LIBCABI=${LIBC}x32 - fi + ABI=64 + sed 's/^ //' << EOF > "$dummy.c" + #ifdef __i386__ + ABI=x86 + #else + #ifdef __ILP32__ + ABI=x32 + #endif + #endif +EOF + cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` + eval "$cc_set_abi" + case $ABI in + x86) CPU=i686 ;; + x32) LIBCABI=${LIBC}x32 ;; + esac fi - GUESS=$UNAME_MACHINE-pc-linux-$LIBCABI + GUESS=$CPU-pc-linux-$LIBCABI ;; xtensa*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC @@ -1367,8 +1384,11 @@ EOF BePC:Haiku:*:*) # Haiku running on Intel PC compatible. GUESS=i586-pc-haiku ;; - x86_64:Haiku:*:*) - GUESS=x86_64-unknown-haiku + ppc:Haiku:*:*) # Haiku running on Apple PowerPC + GUESS=powerpc-apple-haiku + ;; + *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) + GUESS=$UNAME_MACHINE-unknown-haiku ;; SX-4:SUPER-UX:*:*) GUESS=sx4-nec-superux$UNAME_RELEASE diff --git a/scripts/config.h b/scripts/config.h index d3c717e33b..c7914ac073 100644 --- a/scripts/config.h +++ b/scripts/config.h @@ -169,7 +169,7 @@ #define PACKAGE_NAME "liquid-dsp" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "liquid-dsp 1.4.0" +#define PACKAGE_STRING "liquid-dsp 1.6.0" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "liquid-dsp" @@ -178,7 +178,7 @@ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "1.4.0" +#define PACKAGE_VERSION "1.6.0" /* The size of `int', as computed by sizeof. */ #define SIZEOF_INT 4 diff --git a/scripts/config.sub b/scripts/config.sub index dba16e84c7..de4259e404 100644 --- a/scripts/config.sub +++ b/scripts/config.sub @@ -1,10 +1,10 @@ #! /bin/sh # Configuration validation subroutine script. -# Copyright 1992-2022 Free Software Foundation, Inc. +# Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2022-01-03' +timestamp='2023-01-21' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ Report bugs and patches to ." version="\ GNU config.sub ($timestamp) -Copyright 1992-2022 Free Software Foundation, Inc. +Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -145,7 +145,7 @@ case $1 in nto-qnx* | linux-* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ - | storm-chaos* | os2-emx* | rtmk-nova*) + | storm-chaos* | os2-emx* | rtmk-nova* | managarm-*) basic_machine=$field1 basic_os=$maybe_os ;; @@ -1075,7 +1075,7 @@ case $cpu-$vendor in pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) cpu=i586 ;; - pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*) + pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*) cpu=i686 ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) @@ -1207,7 +1207,7 @@ case $cpu-$vendor in | k1om \ | le32 | le64 \ | lm32 \ - | loongarch32 | loongarch64 | loongarchx32 \ + | loongarch32 | loongarch64 \ | m32c | m32r | m32rle \ | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ @@ -1341,6 +1341,10 @@ EOF kernel=linux os=`echo "$basic_os" | sed -e 's|linux|gnu|'` ;; + managarm*) + kernel=managarm + os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'` + ;; *) kernel= os=$basic_os @@ -1754,7 +1758,7 @@ case $os in | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ - | fiwix* ) + | fiwix* | mlibc* ) ;; # This one is extra strict with allowed versions sco3.2v2 | sco3.2v[4-9]* | sco5v6*) @@ -1762,6 +1766,9 @@ case $os in ;; none) ;; + kernel* ) + # Restricted further below + ;; *) echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 exit 1 @@ -1772,16 +1779,26 @@ esac # (given a valid OS), if there is a kernel. case $kernel-$os in linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ - | linux-musl* | linux-relibc* | linux-uclibc* ) + | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* ) ;; uclinux-uclibc* ) ;; - -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) + managarm-mlibc* | managarm-kernel* ) + ;; + -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* ) # These are just libc implementations, not actual OSes, and thus # require a kernel. echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 exit 1 ;; + -kernel* ) + echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2 + exit 1 + ;; + *-kernel* ) + echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2 + exit 1 + ;; kfreebsd*-gnu* | kopensolaris*-gnu*) ;; vxworks-simlinux | vxworks-simwindows | vxworks-spe) diff --git a/scripts/framesync64_debug.py b/scripts/framesync64_debug.py index 27c09c0aad..47c455f4fe 100755 --- a/scripts/framesync64_debug.py +++ b/scripts/framesync64_debug.py @@ -11,9 +11,9 @@ def main(argv=None): args = p.parse_args() for fname in args.sources: - filename = framesync64_plot(fname,args.export) + filename = framesync64_plot(fname,args.export,args.nodisplay) -def framesync64_plot(filename,export=None): +def framesync64_plot(filename,export=None,nodisplay=True): # open file and read values fid = open(filename,'rb') buf = np.fromfile(fid, count=1440, dtype=np.csingle) @@ -26,7 +26,7 @@ def framesync64_plot(filename,export=None): payload_sym = np.fromfile(fid, count= 600, dtype=np.csingle) payload_dec = np.fromfile(fid, count= 72, dtype=np.int8) - # compute filter response in dB + # compute smooth spectral response in dB nfft = 2400 f = np.arange(nfft)/nfft-0.5 psd = np.abs(np.fft.fftshift(np.fft.fft(buf, nfft)))**2 @@ -35,7 +35,6 @@ def framesync64_plot(filename,export=None): h = np.concatenate((w[m:], np.zeros(nfft-2*m-1), w[:m])) / (sum(w) * nfft) H = np.fft.fft(h) psd = 10*np.log10( np.real(np.fft.ifft(H * np.fft.fft(psd))) ) - #psd = 20*np.log10(np.abs(np.fft.fftshift(np.fft.fft(buf, nfft)))) # plot impulse and spectral responses fig, _ax = plt.subplots(2,2,figsize=(12,12)) @@ -58,11 +57,13 @@ def framesync64_plot(filename,export=None): _ax.set_ylabel('Imag') for _ax in ax: _ax.grid(True) - fig.suptitle('frame64, tau:%.6f, dphi:%.6f, phi:%.6f, rssi:%.3f dB, evm:%.3f' % \ - (tau_hat, dphi_hat, phi_hat, 20*np.log10(gamma_hat), evm)) - if export==None: + title = '%s, tau:%9.6f, dphi:%9.6f, phi:%9.6f, rssi:%6.3f dB, evm:%6.3f' % \ + (filename, tau_hat, dphi_hat, phi_hat, 20*np.log10(gamma_hat), evm) + print(title) + fig.suptitle(title) + if not nodisplay: plt.show() - else: + if export is not None: fig.savefig(os.path.splitext(filename)[0]+'.png',bbox_inches='tight') plt.close() diff --git a/scripts/liquid_linker_test.c b/scripts/liquid_linker_test.c index a63d3f9d1a..79a0b153b0 100644 --- a/scripts/liquid_linker_test.c +++ b/scripts/liquid_linker_test.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2022 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,10 +28,14 @@ int main() { - // test liquid string version + // test liquid version number + printf("checking installed liquid version numbers...\n"); + printf(" header : %d\n", LIQUID_VERSION_NUMBER); + printf(" library : %d\n", liquid_libversion_number()); LIQUID_VALIDATE_LIBVERSION; // create object, print and return + printf("creating test object...\n"); resamp_crcf q = resamp_crcf_create(0.12345f, 12, 0.25f, 60.0f, 256); resamp_crcf_print(q); resamp_crcf_destroy(q); diff --git a/src/dotprod/src/dotprod_cccf.avx512f.c b/src/dotprod/src/dotprod_cccf.avx512f.c new file mode 100644 index 0000000000..abb7768c60 --- /dev/null +++ b/src/dotprod/src/dotprod_cccf.avx512f.c @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2007 - 2022 Joseph Gaeddert + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// +// Floating-point dot product (AVX512-F) +// + +#include +#include +#include + +#include "liquid.internal.h" + +// include proper SIMD extensions for x86 platforms +// NOTE: these pre-processor macros are defined in config.h + +#include // AVX + +#define DEBUG_DOTPROD_CCCF_AVX 0 + +// forward declaration of internal methods +int dotprod_cccf_execute_avx512f(dotprod_cccf _q, + float complex * _x, + float complex * _y); + +int dotprod_cccf_execute_avx512f4(dotprod_cccf _q, + float complex * _x, + float complex * _y); + +// basic dot product (ordinal calculation) +int dotprod_cccf_run(float complex * _h, + float complex * _x, + unsigned int _n, + float complex * _y) +{ + float complex r = 0; + unsigned int i; + for (i=0; i<_n; i++) + r += _h[i] * _x[i]; + *_y = r; + return LIQUID_OK; +} + +// basic dot product (ordinal calculation) with loop unrolled +int dotprod_cccf_run4(float complex * _h, + float complex * _x, + unsigned int _n, + float complex * _y) +{ + float complex r = 0; + + // t = 4*(floor(_n/4)) + unsigned int t=(_n>>2)<<2; + + // compute dotprod in groups of 4 + unsigned int i; + for (i=0; in = _n; + + // allocate memory for coefficients, 64-byte aligned + q->hi = (float*) _mm_malloc( 2*q->n*sizeof(float), 64 ); + q->hq = (float*) _mm_malloc( 2*q->n*sizeof(float), 64 ); + + // set coefficients, repeated + // hi = { crealf(_h[0]), crealf(_h[0]), ... crealf(_h[n-1]), crealf(_h[n-1])} + // hq = { cimagf(_h[0]), cimagf(_h[0]), ... cimagf(_h[n-1]), cimagf(_h[n-1])} + unsigned int i; + for (i=0; in; i++) { + unsigned int k = _rev ? q->n-i-1 : i; + q->hi[2*i+0] = crealf(_h[k]); + q->hi[2*i+1] = crealf(_h[k]); + + q->hq[2*i+0] = cimagf(_h[k]); + q->hq[2*i+1] = cimagf(_h[k]); + } + + // return object + return q; +} + +dotprod_cccf dotprod_cccf_create(float complex * _h, + unsigned int _n) +{ + return dotprod_cccf_create_opt(_h, _n, 0); +} + +dotprod_cccf dotprod_cccf_create_rev(float complex * _h, + unsigned int _n) +{ + return dotprod_cccf_create_opt(_h, _n, 1); +} + +// re-create the structured dotprod object +dotprod_cccf dotprod_cccf_recreate(dotprod_cccf _q, + float complex * _h, + unsigned int _n) +{ + // completely destroy and re-create dotprod object + dotprod_cccf_destroy(_q); + return dotprod_cccf_create(_h,_n); +} + +// re-create the structured dotprod object, coefficients reversed +dotprod_cccf dotprod_cccf_recreate_rev(dotprod_cccf _q, + float complex * _h, + unsigned int _n) +{ + // completely destroy and re-create dotprod object + dotprod_cccf_destroy(_q); + return dotprod_cccf_create_rev(_h,_n); +} + +dotprod_cccf dotprod_cccf_copy(dotprod_cccf q_orig) +{ + // validate input + if (q_orig == NULL) + return liquid_error_config("dotprod_cccf_copy().avx512f, object cannot be NULL"); + + dotprod_cccf q_copy = (dotprod_cccf)malloc(sizeof(struct dotprod_cccf_s)); + q_copy->n = q_orig->n; + + // allocate memory for coefficients, 64-byte aligned (repeated) + q_copy->hi = (float*) _mm_malloc( 2*q_copy->n*sizeof(float), 64 ); + q_copy->hq = (float*) _mm_malloc( 2*q_copy->n*sizeof(float), 64 ); + + // copy coefficients array (repeated) + // hi = { crealf(_h[0]), crealf(_h[0]), ... crealf(_h[n-1]), crealf(_h[n-1])} + // hq = { cimagf(_h[0]), cimagf(_h[0]), ... cimagf(_h[n-1]), cimagf(_h[n-1])} + memmove(q_copy->hi, q_orig->hi, 2*q_orig->n*sizeof(float)); + memmove(q_copy->hq, q_orig->hq, 2*q_orig->n*sizeof(float)); + + // return object + return q_copy; +} + +int dotprod_cccf_destroy(dotprod_cccf _q) +{ + _mm_free(_q->hi); + _mm_free(_q->hq); + free(_q); + return LIQUID_OK; +} + +int dotprod_cccf_print(dotprod_cccf _q) +{ + printf("dotprod_cccf [avx512f, %u coefficients]\n", _q->n); + unsigned int i; + for (i=0; i<_q->n; i++) + printf(" %3u : %12.9f +j%12.9f\n", i, _q->hi[i], _q->hq[i]); + return LIQUID_OK; +} + +// execute structured dot product +// _q : dotprod object +// _x : input array +// _y : output sample +int dotprod_cccf_execute(dotprod_cccf _q, + float complex * _x, + float complex * _y) +{ + // switch based on size + if (_q->n < 128) { + return dotprod_cccf_execute_avx512f(_q, _x, _y); + } + return dotprod_cccf_execute_avx512f4(_q, _x, _y); +} + +// use AVX512-F extensions +// +// (a + jb)(c + jd) = (ac - bd) + j(ad + bc) +// +// mm_x = { x[0].real, x[0].imag, x[1].real, x[1].imag, x[2].real, x[2].imag, x[3].real, x[3].imag } +// mm_hi = { h[0].real, h[0].real, h[1].real, h[1].real, h[2].real, h[2].real, h[3].real, h[3].real } +// mm_hq = { h[0].imag, h[0].imag, h[1].imag, h[1].imag, h[2].imag, h[2].imag, h[3].imag, h[3].imag } +// +// mm_y0 = mm_x * mm_hi +// = { x[0].real * h[0].real, +// x[0].imag * h[0].real, +// x[1].real * h[1].real, +// x[1].imag * h[1].real, +// x[2].real * h[2].real, +// x[2].imag * h[2].real, +// x[3].real * h[3].real, +// x[3].imag * h[3].real }; +// +// mm_y1 = mm_x * mm_hq +// = { x[0].real * h[0].imag, +// x[0].imag * h[0].imag, +// x[1].real * h[1].imag, +// x[1].imag * h[1].imag, +// x[2].real * h[2].imag, +// x[2].imag * h[2].imag, +// x[3].real * h[3].imag, +// x[3].imag * h[3].imag }; +// +int dotprod_cccf_execute_avx512f(dotprod_cccf _q, + float complex * _x, + float complex * _y) +{ + // type cast input as floating point array + float * x = (float*) _x; + + // double effective length + unsigned int n = 2*_q->n; + + // temporary buffers + __m512 v; // input vector + __m512 hi; // coefficients vector (real) + __m512 hq; // coefficients vector (imag) + __m512 ci; // output multiplication (v * hi) + __m512 cq; // output multiplication (v * hq) + + __m512 s; // dot product + __m512 sum = _mm512_setzero_ps(); // load zeros into sum register + __m512 one = _mm512_set1_ps(1.0f); // load ones into register + + // t = 16*(floor(_n/16)) + unsigned int t = (n >> 4) << 4; + + // + unsigned int i; + for (i=0; ihi[i]); + hq = _mm512_load_ps(&_q->hq[i]); + + // compute parallel multiplications + ci = _mm512_mul_ps(v, hi); + cq = _mm512_mul_ps(v, hq); + + // shuffle values + cq = _mm512_shuffle_ps( cq, cq, _MM_SHUFFLE(2,3,0,1) ); + + // combine using addsub_ps() + s = _mm512_fmaddsub_ps( ci, one, cq ); + + // accumulate + sum = _mm512_add_ps(sum, s); + } + + // output array + float w[2]; + + // fold down I/Q components into single value + w[0] = _mm512_mask_reduce_add_ps(0x5555, sum); + w[1] = _mm512_mask_reduce_add_ps(0xAAAA, sum); + + //float complex total = *((float complex*)w); + float complex total = w[0] + w[1] * _Complex_I; + + // cleanup + for (i=t/2; i<_q->n; i++) + total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I ); + + // set return value + *_y = total; + return LIQUID_OK; +} + +// use AVX512-F extensions +int dotprod_cccf_execute_avx512f4(dotprod_cccf _q, + float complex * _x, + float complex * _y) +{ + // type cast input as floating point array + float * x = (float*) _x; + + // double effective length + unsigned int n = 2*_q->n; + + // first cut: ... + __m512 v0, v1, v2, v3; // input vectors + __m512 hi0, hi1, hi2, hi3; // coefficients vectors (real) + __m512 hq0, hq1, hq2, hq3; // coefficients vectors (imag) + __m512 ci0, ci1, ci2, ci3; // output multiplications (v * hi) + __m512 cq0, cq1, cq2, cq3; // output multiplications (v * hq) + + // load zeros into sum registers + __m512 sumi = _mm512_setzero_ps(); + __m512 sumq = _mm512_setzero_ps(); + + + __m512 one = _mm512_set1_ps(1.0f); // load ones into register + + // r = 16*floor(n/64) + unsigned int r = (n >> 6) << 4; + + // + unsigned int i; + for (i=0; ihi[4*i+0]); + hi1 = _mm512_load_ps(&_q->hi[4*i+16]); + hi2 = _mm512_load_ps(&_q->hi[4*i+32]); + hi3 = _mm512_load_ps(&_q->hi[4*i+48]); + + // load real coefficients into registers (aligned) + hq0 = _mm512_load_ps(&_q->hq[4*i+0]); + hq1 = _mm512_load_ps(&_q->hq[4*i+16]); + hq2 = _mm512_load_ps(&_q->hq[4*i+32]); + hq3 = _mm512_load_ps(&_q->hq[4*i+48]); + + // compute parallel multiplications (real) + ci0 = _mm512_mul_ps(v0, hi0); + ci1 = _mm512_mul_ps(v1, hi1); + ci2 = _mm512_mul_ps(v2, hi2); + ci3 = _mm512_mul_ps(v3, hi3); + + // compute parallel multiplications (imag) + cq0 = _mm512_mul_ps(v0, hq0); + cq1 = _mm512_mul_ps(v1, hq1); + cq2 = _mm512_mul_ps(v2, hq2); + cq3 = _mm512_mul_ps(v3, hq3); + + // accumulate + sumi = _mm512_add_ps(sumi, ci0); sumq = _mm512_add_ps(sumq, cq0); + sumi = _mm512_add_ps(sumi, ci1); sumq = _mm512_add_ps(sumq, cq1); + sumi = _mm512_add_ps(sumi, ci2); sumq = _mm512_add_ps(sumq, cq2); + sumi = _mm512_add_ps(sumi, ci3); sumq = _mm512_add_ps(sumq, cq3); + } + + // shuffle values + sumq = _mm512_shuffle_ps( sumq, sumq, _MM_SHUFFLE(2,3,0,1) ); + + // combine using addsub_ps() + sumi = _mm512_fmaddsub_ps( sumi, one, sumq ); + + // output array + float w[2]; + + // fold down I/Q components into single value + w[0] = _mm512_mask_reduce_add_ps(0x5555, sumi); + w[1] = _mm512_mask_reduce_add_ps(0xAAAA, sumi); + + float complex total = w[0] + w[1] * _Complex_I; + + // cleanup (note: n _must_ be even) + // TODO : clean this method up + for (i=2*r; i<_q->n; i++) { + total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I ); + } + + // set return value + *_y = total; + return LIQUID_OK; +} + diff --git a/src/dotprod/src/dotprod_cccf.mmx.c b/src/dotprod/src/dotprod_cccf.sse.c similarity index 95% rename from src/dotprod/src/dotprod_cccf.mmx.c rename to src/dotprod/src/dotprod_cccf.sse.c index 6794bfcfeb..6bfc100533 100644 --- a/src/dotprod/src/dotprod_cccf.mmx.c +++ b/src/dotprod/src/dotprod_cccf.sse.c @@ -21,7 +21,7 @@ */ // -// Floating-point dot product (MMX) +// Floating-point dot product (SSE) // #include @@ -33,10 +33,6 @@ // include proper SIMD extensions for x86 platforms // NOTE: these pre-processor macros are defined in config.h -#if HAVE_MMX -#include // MMX -#endif - #if HAVE_SSE #include // SSE #endif @@ -49,14 +45,14 @@ #include // SSE3 #endif -#define DEBUG_DOTPROD_CCCF_MMX 0 +#define DEBUG_DOTPROD_CCCF_sse 0 // forward declaration of internal methods -int dotprod_cccf_execute_mmx(dotprod_cccf _q, +int dotprod_cccf_execute_sse(dotprod_cccf _q, float complex * _x, float complex * _y); -int dotprod_cccf_execute_mmx4(dotprod_cccf _q, +int dotprod_cccf_execute_sse4(dotprod_cccf _q, float complex * _x, float complex * _y); @@ -104,7 +100,7 @@ int dotprod_cccf_run4(float complex * _h, // -// structured MMX dot product +// structured sse dot product // struct dotprod_cccf_s { @@ -177,7 +173,7 @@ dotprod_cccf dotprod_cccf_copy(dotprod_cccf q_orig) { // validate input if (q_orig == NULL) - return liquid_error_config("dotprod_cccf_copy().mmx, object cannot be NULL"); + return liquid_error_config("dotprod_cccf_copy().sse, object cannot be NULL"); dotprod_cccf q_copy = (dotprod_cccf)malloc(sizeof(struct dotprod_cccf_s)); q_copy->n = q_orig->n; @@ -206,7 +202,7 @@ int dotprod_cccf_destroy(dotprod_cccf _q) int dotprod_cccf_print(dotprod_cccf _q) { - printf("dotprod_cccf [mmx, %u coefficients]\n", _q->n); + printf("dotprod_cccf [sse, %u coefficients]\n", _q->n); unsigned int i; for (i=0; i<_q->n; i++) printf(" %3u : %12.9f +j%12.9f\n", i, _q->hi[i], _q->hq[i]); @@ -223,12 +219,12 @@ int dotprod_cccf_execute(dotprod_cccf _q, { // switch based on size if (_q->n < 32) { - return dotprod_cccf_execute_mmx(_q, _x, _y); + return dotprod_cccf_execute_sse(_q, _x, _y); } - return dotprod_cccf_execute_mmx4(_q, _x, _y); + return dotprod_cccf_execute_sse4(_q, _x, _y); } -// use MMX/SSE extensions +// use SSE extensions // // (a + jb)(c + jd) = (ac - bd) + j(ad + bc) // @@ -248,7 +244,7 @@ int dotprod_cccf_execute(dotprod_cccf _q, // x[1].real * h[1].imag, // x[1].imag * h[1].imag }; // -int dotprod_cccf_execute_mmx(dotprod_cccf _q, +int dotprod_cccf_execute_sse(dotprod_cccf _q, float complex * _x, float complex * _y) { @@ -341,8 +337,8 @@ int dotprod_cccf_execute_mmx(dotprod_cccf _q, return LIQUID_OK; } -// use MMX/SSE extensions -int dotprod_cccf_execute_mmx4(dotprod_cccf _q, +// use SSE extensions +int dotprod_cccf_execute_sse4(dotprod_cccf _q, float complex * _x, float complex * _y) { diff --git a/src/dotprod/src/dotprod_crcf.avx.c b/src/dotprod/src/dotprod_crcf.avx.c index 6e14af3adc..5251482104 100644 --- a/src/dotprod/src/dotprod_crcf.avx.c +++ b/src/dotprod/src/dotprod_crcf.avx.c @@ -274,10 +274,7 @@ int dotprod_crcf_execute_avx4(dotprod_crcf _q, __m256 s0, s1, s2, s3; // dot products [re, im, re, im] // load zeros into sum registers - __m256 sum0 = _mm256_setzero_ps(); - __m256 sum1 = _mm256_setzero_ps(); - __m256 sum2 = _mm256_setzero_ps(); - __m256 sum3 = _mm256_setzero_ps(); + __m256 sum = _mm256_setzero_ps(); // r = 8*floor(n/32) unsigned int r = (n >> 5) << 3; @@ -304,22 +301,17 @@ int dotprod_crcf_execute_avx4(dotprod_crcf _q, s3 = _mm256_mul_ps(v3, h3); // parallel addition - sum0 = _mm256_add_ps( sum0, s0 ); - sum1 = _mm256_add_ps( sum1, s1 ); - sum2 = _mm256_add_ps( sum2, s2 ); - sum3 = _mm256_add_ps( sum3, s3 ); + sum = _mm256_add_ps( sum, s0 ); + sum = _mm256_add_ps( sum, s1 ); + sum = _mm256_add_ps( sum, s2 ); + sum = _mm256_add_ps( sum, s3 ); } - // fold down - sum0 = _mm256_add_ps( sum0, sum1 ); - sum2 = _mm256_add_ps( sum2, sum3 ); - sum0 = _mm256_add_ps( sum0, sum2 ); - // aligned output array float w[8] __attribute__((aligned(32))); // unload packed array and perform manual sum - _mm256_store_ps(w, sum0); + _mm256_store_ps(w, sum); w[0] += w[2] + w[4] + w[6]; w[1] += w[3] + w[5] + w[7]; diff --git a/src/dotprod/src/dotprod_crcf.avx512f.c b/src/dotprod/src/dotprod_crcf.avx512f.c new file mode 100644 index 0000000000..fae077f3ff --- /dev/null +++ b/src/dotprod/src/dotprod_crcf.avx512f.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2007 - 2022 Joseph Gaeddert + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// +// Floating-point dot product (AVX512-F) +// + +#include +#include +#include +#include +#include + +#include "liquid.internal.h" + +#define DEBUG_DOTPROD_CRCF_AVX 0 + +// forward declaration of internal methods +int dotprod_crcf_execute_avx512f(dotprod_crcf _q, + float complex * _x, + float complex * _y); +int dotprod_crcf_execute_avx512f4(dotprod_crcf _q, + float complex * _x, + float complex * _y); + +// basic dot product (ordinal calculation) +int dotprod_crcf_run(float * _h, + float complex * _x, + unsigned int _n, + float complex * _y) +{ + float complex r = 0; + unsigned int i; + for (i=0; i<_n; i++) + r += _h[i] * _x[i]; + *_y = r; + return LIQUID_OK; +} + +// basic dot product (ordinal calculation) with loop unrolled +int dotprod_crcf_run4(float * _h, + float complex * _x, + unsigned int _n, + float complex * _y) +{ + float complex r = 0; + + // t = 4*(floor(_n/4)) + unsigned int t=(_n>>2)<<2; + + // compute dotprod in groups of 4 + unsigned int i; + for (i=0; in = _n; + + // allocate memory for coefficients, 64-byte aligned + q->h = (float*) _mm_malloc( 2*q->n*sizeof(float), 64 ); + + // set coefficients, repeated + // h = { _h[0], _h[0], _h[1], _h[1], ... _h[n-1], _h[n-1]} + unsigned int i; + for (i=0; in; i++) { + unsigned int k = _rev ? q->n-i-1 : i; + q->h[2*i+0] = _h[k]; + q->h[2*i+1] = _h[k]; + } + + // return object + return q; +} + +dotprod_crcf dotprod_crcf_create(float * _h, + unsigned int _n) +{ + return dotprod_crcf_create_opt(_h, _n, 0); +} + +dotprod_crcf dotprod_crcf_create_rev(float * _h, + unsigned int _n) +{ + return dotprod_crcf_create_opt(_h, _n, 1); +} + +// re-create the structured dotprod object +dotprod_crcf dotprod_crcf_recreate(dotprod_crcf _q, + float * _h, + unsigned int _n) +{ + // completely destroy and re-create dotprod object + dotprod_crcf_destroy(_q); + return dotprod_crcf_create(_h,_n); +} + +// re-create the structured dotprod object, coefficients reversed +dotprod_crcf dotprod_crcf_recreate_rev(dotprod_crcf _q, + float * _h, + unsigned int _n) +{ + // completely destroy and re-create dotprod object + dotprod_crcf_destroy(_q); + return dotprod_crcf_create_rev(_h,_n); +} + +dotprod_crcf dotprod_crcf_copy(dotprod_crcf q_orig) +{ + // validate input + if (q_orig == NULL) + return liquid_error_config("dotprod_crcf_copy().avx512f, object cannot be NULL"); + + dotprod_crcf q_copy = (dotprod_crcf)malloc(sizeof(struct dotprod_crcf_s)); + q_copy->n = q_orig->n; + + // allocate memory for coefficients, 64-byte aligned (repeated) + q_copy->h = (float*) _mm_malloc( 2*q_copy->n*sizeof(float), 64 ); + + // copy coefficients array (repeated) + // h = { _h[0], _h[0], _h[1], _h[1], ... _h[n-1], _h[n-1]} + memmove(q_copy->h, q_orig->h, 2*q_orig->n*sizeof(float)); + + // return object + return q_copy; +} + + +int dotprod_crcf_destroy(dotprod_crcf _q) +{ + _mm_free(_q->h); + free(_q); + return LIQUID_OK; +} + +int dotprod_crcf_print(dotprod_crcf _q) +{ + // print coefficients to screen, skipping odd entries (due + // to repeated coefficients) + printf("dotprod_crcf [avx512f, %u coefficients]\n", _q->n); + unsigned int i; + for (i=0; i<_q->n; i++) + printf(" %3u : %12.9f\n", i, _q->h[2*i]); + return LIQUID_OK; +} + +// +int dotprod_crcf_execute(dotprod_crcf _q, + float complex * _x, + float complex * _y) +{ + // switch based on size + if (_q->n < 128) { + return dotprod_crcf_execute_avx512f(_q, _x, _y); + } + return dotprod_crcf_execute_avx512f4(_q, _x, _y); +} + +// use AVX512-F extensions +int dotprod_crcf_execute_avx512f(dotprod_crcf _q, + float complex * _x, + float complex * _y) +{ + // type cast input as floating point array + float * x = (float*) _x; + + // double effective length + unsigned int n = 2*_q->n; + + // first cut: ... + __m512 v; // input vector + __m512 h; // coefficients vector + __m512 s; // dot product + __m512 sum = _mm512_setzero_ps(); // load zeros into sum register + + // t = 16*(floor(_n/16)) + unsigned int t = (n >> 4) << 4; + + // + unsigned int i; + for (i=0; ih[i]); + + // compute multiplication + s = _mm512_mul_ps(v, h); + + // accumulate + sum = _mm512_add_ps(sum, s); + } + + // output array + float w[2]; + + // fold down I/Q components into single value + w[0] = _mm512_mask_reduce_add_ps(0x5555, sum); + w[1] = _mm512_mask_reduce_add_ps(0xAAAA, sum); + + // cleanup (note: n _must_ be even) + for (; ih[i ]; + w[1] += x[i+1] * _q->h[i+1]; + } + + // set return value + *_y = w[0] + _Complex_I*w[1]; + return LIQUID_OK; +} + +// use AVX512-F extensions +int dotprod_crcf_execute_avx512f4(dotprod_crcf _q, + float complex * _x, + float complex * _y) +{ + // type cast input as floating point array + float * x = (float*) _x; + + // double effective length + unsigned int n = 2*_q->n; + + // first cut: ... + __m512 v0, v1, v2, v3; // input vectors + __m512 h0, h1, h2, h3; // coefficients vectors + __m512 s0, s1, s2, s3; // dot products [re, im, re, im] + + // load zeros into sum registers + __m512 sum = _mm512_setzero_ps(); + + // r = 16*floor(n/64) + unsigned int r = (n >> 6) << 4; + + // + unsigned int i; + for (i=0; ih[4*i+0]); + h1 = _mm512_load_ps(&_q->h[4*i+16]); + h2 = _mm512_load_ps(&_q->h[4*i+32]); + h3 = _mm512_load_ps(&_q->h[4*i+48]); + + // compute multiplication + s0 = _mm512_mul_ps(v0, h0); + s1 = _mm512_mul_ps(v1, h1); + s2 = _mm512_mul_ps(v2, h2); + s3 = _mm512_mul_ps(v3, h3); + + // parallel addition + sum = _mm512_add_ps( sum, s0 ); + sum = _mm512_add_ps( sum, s1 ); + sum = _mm512_add_ps( sum, s2 ); + sum = _mm512_add_ps( sum, s3 ); + } + + // output array + float w[2]; + + // fold down I/Q components into single value + w[0] = _mm512_mask_reduce_add_ps(0x5555, sum); + w[1] = _mm512_mask_reduce_add_ps(0xAAAA, sum); + + // cleanup (note: n _must_ be even) + for (i=4*r; ih[i ]; + w[1] += x[i+1] * _q->h[i+1]; + } + + // set return value + *_y = w[0] + w[1]*_Complex_I; + return LIQUID_OK; +} + diff --git a/src/dotprod/src/dotprod_crcf.mmx.c b/src/dotprod/src/dotprod_crcf.sse.c similarity index 88% rename from src/dotprod/src/dotprod_crcf.mmx.c rename to src/dotprod/src/dotprod_crcf.sse.c index 4ba9d320dc..182133d22e 100644 --- a/src/dotprod/src/dotprod_crcf.mmx.c +++ b/src/dotprod/src/dotprod_crcf.sse.c @@ -21,7 +21,7 @@ */ // -// Floating-point dot product (MMX) +// Floating-point dot product (SSE) // #include @@ -32,13 +32,28 @@ #include "liquid.internal.h" -#define DEBUG_DOTPROD_CRCF_MMX 0 +// include proper SIMD extensions for x86 platforms +// NOTE: these pre-processor macros are defined in config.h + +#if HAVE_SSE +#include // SSE +#endif + +#if HAVE_SSE2 +#include // SSE2 +#endif + +#if HAVE_SSE3 +#include // SSE3 +#endif + +#define DEBUG_DOTPROD_CRCF_SSE 0 // forward declaration of internal methods -int dotprod_crcf_execute_mmx(dotprod_crcf _q, +int dotprod_crcf_execute_sse(dotprod_crcf _q, float complex * _x, float complex * _y); -int dotprod_crcf_execute_mmx4(dotprod_crcf _q, +int dotprod_crcf_execute_sse4(dotprod_crcf _q, float complex * _x, float complex * _y); @@ -86,7 +101,7 @@ int dotprod_crcf_run4(float * _h, // -// structured MMX dot product +// structured SSE dot product // struct dotprod_crcf_s { @@ -153,7 +168,7 @@ dotprod_crcf dotprod_crcf_copy(dotprod_crcf q_orig) { // validate input if (q_orig == NULL) - return liquid_error_config("dotprod_crcf_copy().mmx, object cannot be NULL"); + return liquid_error_config("dotprod_crcf_copy().sse, object cannot be NULL"); dotprod_crcf q_copy = (dotprod_crcf)malloc(sizeof(struct dotprod_crcf_s)); q_copy->n = q_orig->n; @@ -181,7 +196,7 @@ int dotprod_crcf_print(dotprod_crcf _q) { // print coefficients to screen, skipping odd entries (due // to repeated coefficients) - printf("dotprod_crcf [mmx, %u coefficients]\n", _q->n); + printf("dotprod_crcf [sse, %u coefficients]\n", _q->n); unsigned int i; for (i=0; i<_q->n; i++) printf(" %3u : %12.9f\n", i, _q->h[2*i]); @@ -195,13 +210,13 @@ int dotprod_crcf_execute(dotprod_crcf _q, { // switch based on size if (_q->n < 32) { - return dotprod_crcf_execute_mmx(_q, _x, _y); + return dotprod_crcf_execute_sse(_q, _x, _y); } - return dotprod_crcf_execute_mmx4(_q, _x, _y); + return dotprod_crcf_execute_sse4(_q, _x, _y); } -// use MMX/SSE extensions -int dotprod_crcf_execute_mmx(dotprod_crcf _q, +// use SSE extensions +int dotprod_crcf_execute_sse(dotprod_crcf _q, float complex * _x, float complex * _y) { @@ -257,8 +272,8 @@ int dotprod_crcf_execute_mmx(dotprod_crcf _q, return LIQUID_OK; } -// use MMX/SSE extensions -int dotprod_crcf_execute_mmx4(dotprod_crcf _q, +// use SSE extensions +int dotprod_crcf_execute_sse4(dotprod_crcf _q, float complex * _x, float complex * _y) { @@ -274,10 +289,7 @@ int dotprod_crcf_execute_mmx4(dotprod_crcf _q, __m128 s0, s1, s2, s3; // dot products [re, im, re, im] // load zeros into sum registers - __m128 sum0 = _mm_setzero_ps(); - __m128 sum1 = _mm_setzero_ps(); - __m128 sum2 = _mm_setzero_ps(); - __m128 sum3 = _mm_setzero_ps(); + __m128 sum = _mm_setzero_ps(); // r = 4*floor(n/16) unsigned int r = (n >> 4) << 2; @@ -304,22 +316,17 @@ int dotprod_crcf_execute_mmx4(dotprod_crcf _q, s3 = _mm_mul_ps(v3, h3); // parallel addition - sum0 = _mm_add_ps( sum0, s0 ); - sum1 = _mm_add_ps( sum1, s1 ); - sum2 = _mm_add_ps( sum2, s2 ); - sum3 = _mm_add_ps( sum3, s3 ); + sum = _mm_add_ps( sum, s0 ); + sum = _mm_add_ps( sum, s1 ); + sum = _mm_add_ps( sum, s2 ); + sum = _mm_add_ps( sum, s3 ); } - // fold down - sum0 = _mm_add_ps( sum0, sum1 ); - sum2 = _mm_add_ps( sum2, sum3 ); - sum0 = _mm_add_ps( sum0, sum2 ); - // aligned output array float w[4] __attribute__((aligned(16))); // unload packed array and perform manual sum - _mm_store_ps(w, sum0); + _mm_store_ps(w, sum); w[0] += w[2]; w[1] += w[3]; diff --git a/src/dotprod/src/dotprod_rrrf.avx.c b/src/dotprod/src/dotprod_rrrf.avx.c index 2af478375a..08a87539cd 100644 --- a/src/dotprod/src/dotprod_rrrf.avx.c +++ b/src/dotprod/src/dotprod_rrrf.avx.c @@ -219,12 +219,17 @@ int dotprod_rrrf_execute_avx(dotprod_rrrf _q, h = _mm256_load_ps(&_q->h[i]); // compute dot product - s = _mm256_dp_ps(v, h, 0xff); - + s = _mm256_mul_ps(v, h); + // parallel addition sum = _mm256_add_ps( sum, s ); } + // fold down into single value + __m256 z = _mm256_setzero_ps(); + sum = _mm256_hadd_ps(sum, z); + sum = _mm256_hadd_ps(sum, z); + // aligned output array float w[8] __attribute__((aligned(32))); @@ -270,19 +275,23 @@ int dotprod_rrrf_execute_avxu(dotprod_rrrf _q, h3 = _mm256_load_ps(&_q->h[4*i+24]); // compute dot products - s0 = _mm256_dp_ps(v0, h0, 0xff); - s1 = _mm256_dp_ps(v1, h1, 0xff); - s2 = _mm256_dp_ps(v2, h2, 0xff); - s3 = _mm256_dp_ps(v3, h3, 0xff); + s0 = _mm256_mul_ps(v0, h0); + s1 = _mm256_mul_ps(v1, h1); + s2 = _mm256_mul_ps(v2, h2); + s3 = _mm256_mul_ps(v3, h3); // parallel addition - // FIXME: these additions are by far the limiting factor sum = _mm256_add_ps( sum, s0 ); sum = _mm256_add_ps( sum, s1 ); sum = _mm256_add_ps( sum, s2 ); sum = _mm256_add_ps( sum, s3 ); } + // fold down into single value + __m256 z = _mm256_setzero_ps(); + sum = _mm256_hadd_ps(sum, z); + sum = _mm256_hadd_ps(sum, z); + // aligned output array float w[8] __attribute__((aligned(32))); diff --git a/src/dotprod/src/dotprod_rrrf.sse4.c b/src/dotprod/src/dotprod_rrrf.avx512f.c similarity index 67% rename from src/dotprod/src/dotprod_rrrf.sse4.c rename to src/dotprod/src/dotprod_rrrf.avx512f.c index 5bddc12d56..c11a744a98 100644 --- a/src/dotprod/src/dotprod_rrrf.sse4.c +++ b/src/dotprod/src/dotprod_rrrf.avx512f.c @@ -21,7 +21,7 @@ */ // -// Floating-point dot product (SSE4.1/2) +// Floating-point dot product (AVX512-F) // #include @@ -34,21 +34,15 @@ // include proper SIMD extensions for x86 platforms // NOTE: these pre-processor macros are defined in config.h -#if 0 -#include // MMX -#include // SSE -#include // SSE2 -#include // SSE3 -#endif -#include // SSE4.1/2 +#include // AVX -#define DEBUG_DOTPROD_RRRF_SSE4 0 +#define DEBUG_DOTPROD_RRRF_AVX 0 // internal methods -int dotprod_rrrf_execute_sse4(dotprod_rrrf _q, +int dotprod_rrrf_execute_avx512f(dotprod_rrrf _q, float * _x, float * _y); -int dotprod_rrrf_execute_sse4u(dotprod_rrrf _q, +int dotprod_rrrf_execute_avx512fu(dotprod_rrrf _q, float * _x, float * _y); @@ -96,7 +90,7 @@ int dotprod_rrrf_run4(float * _h, // -// structured MMX dot product +// structured AVX512-F dot product // struct dotprod_rrrf_s { @@ -111,8 +105,8 @@ dotprod_rrrf dotprod_rrrf_create_opt(float * _h, dotprod_rrrf q = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s)); q->n = _n; - // allocate memory for coefficients, 16-byte aligned - q->h = (float*) _mm_malloc( q->n*sizeof(float), 16); + // allocate memory for coefficients, 64-byte aligned + q->h = (float*) _mm_malloc( q->n*sizeof(float), 64); // set coefficients unsigned int i; @@ -159,13 +153,13 @@ dotprod_rrrf dotprod_rrrf_copy(dotprod_rrrf q_orig) { // validate input if (q_orig == NULL) - return liquid_error_config("dotprod_rrrf_copy().sse4, object cannot be NULL"); + return liquid_error_config("dotprod_rrrf_copy().avx512f, object cannot be NULL"); dotprod_rrrf q_copy = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s)); q_copy->n = q_orig->n; - // allocate memory for coefficients, 16-byte aligned - q_copy->h = (float*) _mm_malloc( q_copy->n*sizeof(float), 16 ); + // allocate memory for coefficients, 64-byte aligned + q_copy->h = (float*) _mm_malloc( q_copy->n*sizeof(float), 64 ); // copy coefficients array memmove(q_copy->h, q_orig->h, q_orig->n*sizeof(float)); @@ -183,7 +177,7 @@ int dotprod_rrrf_destroy(dotprod_rrrf _q) int dotprod_rrrf_print(dotprod_rrrf _q) { - printf("dotprod_rrrf [sse4.1/4.2, %u coefficients]\n", _q->n); + printf("dotprod_rrrf [avx512f, %u coefficients]\n", _q->n); unsigned int i; for (i=0; i<_q->n; i++) printf("%3u : %12.9f\n", i, _q->h[i]); @@ -196,47 +190,43 @@ int dotprod_rrrf_execute(dotprod_rrrf _q, float * _y) { // switch based on size - if (_q->n < 16) { - return dotprod_rrrf_execute_sse4(_q, _x, _y); + if (_q->n < 64) { + return dotprod_rrrf_execute_avx512f(_q, _x, _y); } - return dotprod_rrrf_execute_sse4u(_q, _x, _y); + return dotprod_rrrf_execute_avx512fu(_q, _x, _y); } -// use MMX/SSE extensions -int dotprod_rrrf_execute_sse4(dotprod_rrrf _q, +// use AVX512-F extensions +int dotprod_rrrf_execute_avx512f(dotprod_rrrf _q, float * _x, float * _y) { - __m128 v; // input vector - __m128 h; // coefficients vector - __m128 s; // dot product - __m128 sum = _mm_setzero_ps(); // load zeros into sum register + __m512 v; // input vector + __m512 h; // coefficients vector + __m512 s; // dot product + __m512 sum = _mm512_setzero_ps(); // load zeros into sum register - // t = 4*(floor(_n/4)) - unsigned int t = (_q->n >> 2) << 2; + // t = 16*(floor(_n/16)) + unsigned int t = (_q->n >> 4) << 4; // unsigned int i; - for (i=0; ih[i]); + h = _mm512_load_ps(&_q->h[i]); // compute dot product - s = _mm_dp_ps(v, h, 0xff); - + s = _mm512_mul_ps(v, h); + // parallel addition - sum = _mm_add_ps( sum, s ); + sum = _mm512_add_ps( sum, s ); } - // aligned output array - float w[4] __attribute__((aligned(16))); - - // unload packed array - _mm_store_ps(w, sum); - float total = w[0]; + // fold down into single value + float total = _mm512_reduce_add_ps(sum); // cleanup for (; i<_q->n; i++) @@ -247,54 +237,49 @@ int dotprod_rrrf_execute_sse4(dotprod_rrrf _q, return LIQUID_OK; } -// use MMX/SSE extensions (unrolled) -int dotprod_rrrf_execute_sse4u(dotprod_rrrf _q, +// use AVX512-F extensions (unrolled) +int dotprod_rrrf_execute_avx512fu(dotprod_rrrf _q, float * _x, float * _y) { - __m128 v0, v1, v2, v3; - __m128 h0, h1, h2, h3; - __m128 s0, s1, s2, s3; - __m128 sum = _mm_setzero_ps(); // load zeros into sum register + __m512 v0, v1, v2, v3; + __m512 h0, h1, h2, h3; + __m512 s0, s1, s2, s3; + __m512 sum = _mm512_setzero_ps(); // load zeros into sum register - // t = 4*(floor(_n/16)) - unsigned int r = (_q->n >> 4) << 2; + // t = 16*(floor(_n/64)) + unsigned int r = (_q->n >> 6) << 4; // unsigned int i; - for (i=0; ih[4*i+ 0]); - h1 = _mm_load_ps(&_q->h[4*i+ 4]); - h2 = _mm_load_ps(&_q->h[4*i+ 8]); - h3 = _mm_load_ps(&_q->h[4*i+12]); + h0 = _mm512_load_ps(&_q->h[4*i+ 0]); + h1 = _mm512_load_ps(&_q->h[4*i+16]); + h2 = _mm512_load_ps(&_q->h[4*i+32]); + h3 = _mm512_load_ps(&_q->h[4*i+48]); // compute dot products - s0 = _mm_dp_ps(v0, h0, 0xff); - s1 = _mm_dp_ps(v1, h1, 0xff); - s2 = _mm_dp_ps(v2, h2, 0xff); - s3 = _mm_dp_ps(v3, h3, 0xff); + s0 = _mm512_mul_ps(v0, h0); + s1 = _mm512_mul_ps(v1, h1); + s2 = _mm512_mul_ps(v2, h2); + s3 = _mm512_mul_ps(v3, h3); // parallel addition - // FIXME: these additions are by far the limiting factor - sum = _mm_add_ps( sum, s0 ); - sum = _mm_add_ps( sum, s1 ); - sum = _mm_add_ps( sum, s2 ); - sum = _mm_add_ps( sum, s3 ); + sum = _mm512_add_ps( sum, s0 ); + sum = _mm512_add_ps( sum, s1 ); + sum = _mm512_add_ps( sum, s2 ); + sum = _mm512_add_ps( sum, s3 ); } - // aligned output array - float w[4] __attribute__((aligned(16))); - - // unload packed array - _mm_store_ps(w, sum); - float total = w[0]; + // fold down into single value + float total = _mm512_reduce_add_ps(sum); // cleanup for (i=4*r; i<_q->n; i++) diff --git a/src/dotprod/src/dotprod_rrrf.mmx.c b/src/dotprod/src/dotprod_rrrf.sse.c similarity index 86% rename from src/dotprod/src/dotprod_rrrf.mmx.c rename to src/dotprod/src/dotprod_rrrf.sse.c index 613eb707a0..a9f4c58f19 100644 --- a/src/dotprod/src/dotprod_rrrf.mmx.c +++ b/src/dotprod/src/dotprod_rrrf.sse.c @@ -21,7 +21,7 @@ */ // -// Floating-point dot product (MMX) +// Floating-point dot product (SSE) // #include @@ -34,10 +34,6 @@ // include proper SIMD extensions for x86 platforms // NOTE: these pre-processor macros are defined in config.h -#if HAVE_MMX -#include // MMX -#endif - #if HAVE_SSE #include // SSE #endif @@ -50,13 +46,13 @@ #include // SSE3 #endif -#define DEBUG_DOTPROD_RRRF_MMX 0 +#define DEBUG_DOTPROD_RRRF_SSE 0 // internal methods -int dotprod_rrrf_execute_mmx(dotprod_rrrf _q, +int dotprod_rrrf_execute_sse(dotprod_rrrf _q, float * _x, float * _y); -int dotprod_rrrf_execute_mmx4(dotprod_rrrf _q, +int dotprod_rrrf_execute_sse4(dotprod_rrrf _q, float * _x, float * _y); @@ -104,7 +100,7 @@ int dotprod_rrrf_run4(float * _h, // -// structured MMX dot product +// structured SSE dot product // struct dotprod_rrrf_s { @@ -167,7 +163,7 @@ dotprod_rrrf dotprod_rrrf_copy(dotprod_rrrf q_orig) { // validate input if (q_orig == NULL) - return liquid_error_config("dotprod_rrrf_copy().mmx, object cannot be NULL"); + return liquid_error_config("dotprod_rrrf_copy().sse, object cannot be NULL"); dotprod_rrrf q_copy = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s)); q_copy->n = q_orig->n; @@ -191,7 +187,7 @@ int dotprod_rrrf_destroy(dotprod_rrrf _q) int dotprod_rrrf_print(dotprod_rrrf _q) { - printf("dotprod_rrrf [mmx, %u coefficients]\n", _q->n); + printf("dotprod_rrrf [sse, %u coefficients]\n", _q->n); unsigned int i; for (i=0; i<_q->n; i++) printf("%3u : %12.9f\n", i, _q->h[i]); @@ -205,13 +201,13 @@ int dotprod_rrrf_execute(dotprod_rrrf _q, { // switch based on size if (_q->n < 16) { - return dotprod_rrrf_execute_mmx(_q, _x, _y); + return dotprod_rrrf_execute_sse(_q, _x, _y); } - return dotprod_rrrf_execute_mmx4(_q, _x, _y); + return dotprod_rrrf_execute_sse4(_q, _x, _y); } -// use MMX/SSE extensions -int dotprod_rrrf_execute_mmx(dotprod_rrrf _q, +// use SSE extensions +int dotprod_rrrf_execute_sse(dotprod_rrrf _q, float * _x, float * _y) { @@ -235,7 +231,7 @@ int dotprod_rrrf_execute_mmx(dotprod_rrrf _q, // compute multiplication s = _mm_mul_ps(v, h); - + // parallel addition sum = _mm_add_ps( sum, s ); } @@ -267,8 +263,8 @@ int dotprod_rrrf_execute_mmx(dotprod_rrrf _q, return LIQUID_OK; } -// use MMX/SSE extensions, unrolled loop -int dotprod_rrrf_execute_mmx4(dotprod_rrrf _q, +// use SSE extensions, unrolled loop +int dotprod_rrrf_execute_sse4(dotprod_rrrf _q, float * _x, float * _y) { @@ -278,10 +274,7 @@ int dotprod_rrrf_execute_mmx4(dotprod_rrrf _q, __m128 s0, s1, s2, s3; // load zeros into sum registers - __m128 sum0 = _mm_setzero_ps(); - __m128 sum1 = _mm_setzero_ps(); - __m128 sum2 = _mm_setzero_ps(); - __m128 sum3 = _mm_setzero_ps(); + __m128 sum = _mm_setzero_ps(); // r = 4*floor(n/16) unsigned int r = (_q->n >> 4) << 2; @@ -308,32 +301,27 @@ int dotprod_rrrf_execute_mmx4(dotprod_rrrf _q, s3 = _mm_mul_ps(v3, h3); // parallel addition - sum0 = _mm_add_ps( sum0, s0 ); - sum1 = _mm_add_ps( sum1, s1 ); - sum2 = _mm_add_ps( sum2, s2 ); - sum3 = _mm_add_ps( sum3, s3 ); + sum = _mm_add_ps( sum, s0 ); + sum = _mm_add_ps( sum, s1 ); + sum = _mm_add_ps( sum, s2 ); + sum = _mm_add_ps( sum, s3 ); } - // fold down into single 4-element register - sum0 = _mm_add_ps( sum0, sum1 ); - sum2 = _mm_add_ps( sum2, sum3 ); - sum0 = _mm_add_ps( sum0, sum2); - // aligned output array float w[4] __attribute__((aligned(16))); #if HAVE_SSE3 // SSE3: fold down to single value using _mm_hadd_ps() __m128 z = _mm_setzero_ps(); - sum0 = _mm_hadd_ps(sum0, z); - sum0 = _mm_hadd_ps(sum0, z); + sum = _mm_hadd_ps(sum, z); + sum = _mm_hadd_ps(sum, z); // unload single (lower value) - _mm_store_ss(w, sum0); + _mm_store_ss(w, sum); float total = w[0]; #else // SSE2 and below: unload packed array and perform manual sum - _mm_store_ps(w, sum0); + _mm_store_ps(w, sum); float total = w[0] + w[1] + w[2] + w[3]; #endif diff --git a/src/dotprod/src/sumsq.avx.c b/src/dotprod/src/sumsq.avx.c index 76e3c59deb..88685fca70 100644 --- a/src/dotprod/src/sumsq.avx.c +++ b/src/dotprod/src/sumsq.avx.c @@ -21,7 +21,7 @@ */ // -// sumsq.mmx.c : floating-point sum of squares (MMX) +// sumsq.avx.c : floating-point sum of squares (AVX) // #include @@ -56,12 +56,12 @@ // sum squares, basic loop // _v : input array [size: 1 x _n] // _n : input length -float liquid_sumsqf(float * _v, - unsigned int _n) +float liquid_sumsqf_avx(float * _v, + unsigned int _n) { // first cut: ... __m256 v; // input vector - __m256 s; // dot product + __m256 s; // product __m256 sum = _mm256_setzero_ps(); // load zeros into sum register // t = 8*(floor(_n/8)) @@ -80,27 +80,94 @@ float liquid_sumsqf(float * _v, sum = _mm256_add_ps( sum, s ); } + // fold down into single value + __m256 z = _mm256_setzero_ps(); + sum = _mm256_hadd_ps(sum, z); + sum = _mm256_hadd_ps(sum, z); + // aligned output array - float total; + float w[8] __attribute__((aligned(32))); + + _mm256_store_ps(w, sum); + float total = w[0] + w[4]; + + // cleanup + for (; i<_n; i++) + total += _v[i] * _v[i]; + + // set return value + return total; +} + +// sum squares, unrolled loop +// _v : input array [size: 1 x _n] +// _n : input length +float liquid_sumsqf_avxu(float * _v, + unsigned int _n) +{ + // first cut: ... + __m256 v0, v1, v2, v3; // input vector + __m256 s0, s1, s2, s3; // product + __m256 sum = _mm256_setzero_ps(); // load zeros into sum register + + // t = 8*(floor(_n/32)) + unsigned int t = (_n >> 5) << 3; + + // + unsigned int i; + for (i=0; i +#include +#include + +#include "liquid.internal.h" + +// include proper SIMD extensions for x86 platforms +// NOTE: these pre-processor macros are defined in config.h + +#if HAVE_MMX +#include // MMX +#endif + +#if HAVE_SSE +#include // SSE +#endif + +#if HAVE_SSE2 +#include // SSE2 +#endif + +#if HAVE_SSE3 +#include // SSE3 +#endif + +#if HAVE_AVX +#include // AVX +#endif + +// sum squares, basic loop +// _v : input array [size: 1 x _n] +// _n : input length +float liquid_sumsqf_avx(float * _v, + unsigned int _n) +{ + // first cut: ... + __m512 v; // input vector + __m512 s; // product + __m512 sum = _mm512_setzero_ps(); // load zeros into sum register + + // t = 16*(floor(_n/16)) + unsigned int t = (_n >> 4) << 4; + + // + unsigned int i; + for (i=0; i> 6) << 4; + + // + unsigned int i; + for (i=0; i @@ -33,10 +33,6 @@ // include proper SIMD extensions for x86 platforms // NOTE: these pre-processor macros are defined in config.h -#if HAVE_MMX -#include // MMX -#endif - #if HAVE_SSE #include // SSE #endif @@ -52,8 +48,8 @@ // sum squares, basic loop // _v : input array [size: 1 x _n] // _n : input length -float liquid_sumsqf(float * _v, - unsigned int _n) +float liquid_sumsqf_sse(float * _v, + unsigned int _n) { // first cut: ... __m128 v; // input vector @@ -102,7 +98,82 @@ float liquid_sumsqf(float * _v, return total; } -// sum squares, basic loop +// sum squares, unrolled loop +// _v : input array [size: 1 x _n] +// _n : input length +float liquid_sumsqf_sseu(float * _v, + unsigned int _n) +{ + // first cut: ... + __m128 v0, v1, v2, v3; // input vector + __m128 s0, s1, s2, s3; // product + __m128 sum = _mm_setzero_ps(); // load zeros into sum register + + // t = 4*(floor(_n/16)) + unsigned int t = (_n >> 4) << 2; + + // + unsigned int i; + for (i=0; i +#include +#include "autotest/autotest.h" #include "liquid.internal.h" -// -#define BSYNC(name) LIQUID_CONCAT(bsync_rrrf,name) - -#define PRINTVAL(x) printf("%12.4e", x) - -#define TO float -#define TC float -#define TI float -#define ABS(X) fabsf(X) -#define WINDOW(name) LIQUID_CONCAT(windowf,name) -#define DOTPROD(name) LIQUID_CONCAT(dotprod_rrrf,name) - -#undef TO_COMPLEX -#undef TC_COMPLEX -#undef TI_COMPLEX - -// prototypes -#include "bsync.proto.c" +void autotest_fec_config() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping fec_hamming3126 config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + // symbols too large + CONTEND_EQUALITY(fec_golay2412_encode_symbol(1u<<12), 0) + CONTEND_EQUALITY(fec_golay2412_decode_symbol(1u<<24), 0) + + CONTEND_EQUALITY(fec_hamming3126_encode_symbol(1u<<26), 0) + CONTEND_EQUALITY(fec_hamming3126_decode_symbol(1u<<31), 0) + + CONTEND_EQUALITY(fec_hamming1511_encode_symbol(1u<<11), 0) + CONTEND_EQUALITY(fec_hamming1511_decode_symbol(1u<<15), 0) + + CONTEND_EQUALITY(fec_hamming128_encode_symbol(1u<<8), 0) + CONTEND_EQUALITY(fec_hamming128_decode_symbol(1u<<12), 0) +} diff --git a/src/fec/tests/fec_copy_autotest.c b/src/fec/tests/fec_copy_autotest.c index 34344eb5b0..a47addb786 100644 --- a/src/fec/tests/fec_copy_autotest.c +++ b/src/fec/tests/fec_copy_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,26 +29,27 @@ void fec_test_copy(fec_scheme _fs) { #if !LIBFEC_ENABLED - if ( _fs == LIQUID_FEC_CONV_V27 || - _fs == LIQUID_FEC_CONV_V29 || - _fs == LIQUID_FEC_CONV_V39 || - _fs == LIQUID_FEC_CONV_V615 || - _fs == LIQUID_FEC_CONV_V27P23 || - _fs == LIQUID_FEC_CONV_V27P34 || - _fs == LIQUID_FEC_CONV_V27P45 || - _fs == LIQUID_FEC_CONV_V27P56 || - _fs == LIQUID_FEC_CONV_V27P67 || - _fs == LIQUID_FEC_CONV_V27P78 || - _fs == LIQUID_FEC_CONV_V29P23 || - _fs == LIQUID_FEC_CONV_V29P34 || - _fs == LIQUID_FEC_CONV_V29P45 || - _fs == LIQUID_FEC_CONV_V29P56 || - _fs == LIQUID_FEC_CONV_V29P67 || - _fs == LIQUID_FEC_CONV_V29P78 || - _fs == LIQUID_FEC_RS_M8) - { - AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)\n"); + switch (_fs) { + case LIQUID_FEC_CONV_V27: + case LIQUID_FEC_CONV_V29: + case LIQUID_FEC_CONV_V39: + case LIQUID_FEC_CONV_V615: + case LIQUID_FEC_CONV_V27P23: + case LIQUID_FEC_CONV_V27P34: + case LIQUID_FEC_CONV_V27P45: + case LIQUID_FEC_CONV_V27P56: + case LIQUID_FEC_CONV_V27P67: + case LIQUID_FEC_CONV_V27P78: + case LIQUID_FEC_CONV_V29P23: + case LIQUID_FEC_CONV_V29P34: + case LIQUID_FEC_CONV_V29P45: + case LIQUID_FEC_CONV_V29P56: + case LIQUID_FEC_CONV_V29P67: + case LIQUID_FEC_CONV_V29P78: + case LIQUID_FEC_RS_M8: + AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)"); return; + default:; } #endif unsigned int n_dec = 64; diff --git a/src/fec/tests/fec_hamming3126_autotest.c b/src/fec/tests/fec_hamming3126_autotest.c index a3731c0f4d..4e0090fe9b 100644 --- a/src/fec/tests/fec_hamming3126_autotest.c +++ b/src/fec/tests/fec_hamming3126_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2015 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/fec/tests/fec_soft_autotest.c b/src/fec/tests/fec_soft_autotest.c index 34c92803d6..c0d2ef247b 100644 --- a/src/fec/tests/fec_soft_autotest.c +++ b/src/fec/tests/fec_soft_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2015 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,26 +32,27 @@ void fec_test_soft_codec(fec_scheme _fs, void * _opts) { #if !LIBFEC_ENABLED - if ( _fs == LIQUID_FEC_CONV_V27 || - _fs == LIQUID_FEC_CONV_V29 || - _fs == LIQUID_FEC_CONV_V39 || - _fs == LIQUID_FEC_CONV_V615 || - _fs == LIQUID_FEC_CONV_V27P23 || - _fs == LIQUID_FEC_CONV_V27P34 || - _fs == LIQUID_FEC_CONV_V27P45 || - _fs == LIQUID_FEC_CONV_V27P56 || - _fs == LIQUID_FEC_CONV_V27P67 || - _fs == LIQUID_FEC_CONV_V27P78 || - _fs == LIQUID_FEC_CONV_V29P23 || - _fs == LIQUID_FEC_CONV_V29P34 || - _fs == LIQUID_FEC_CONV_V29P45 || - _fs == LIQUID_FEC_CONV_V29P56 || - _fs == LIQUID_FEC_CONV_V29P67 || - _fs == LIQUID_FEC_CONV_V29P78 || - _fs == LIQUID_FEC_RS_M8) - { - AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)\n"); + switch (_fs) { + case LIQUID_FEC_CONV_V27: + case LIQUID_FEC_CONV_V29: + case LIQUID_FEC_CONV_V39: + case LIQUID_FEC_CONV_V615: + case LIQUID_FEC_CONV_V27P23: + case LIQUID_FEC_CONV_V27P34: + case LIQUID_FEC_CONV_V27P45: + case LIQUID_FEC_CONV_V27P56: + case LIQUID_FEC_CONV_V27P67: + case LIQUID_FEC_CONV_V27P78: + case LIQUID_FEC_CONV_V29P23: + case LIQUID_FEC_CONV_V29P34: + case LIQUID_FEC_CONV_V29P45: + case LIQUID_FEC_CONV_V29P56: + case LIQUID_FEC_CONV_V29P67: + case LIQUID_FEC_CONV_V29P78: + case LIQUID_FEC_RS_M8: + AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)"); return; + default:; } #endif diff --git a/src/fft/src/asgram.proto.c b/src/fft/src/asgram.proto.c index d8adcbc318..c4d62c5960 100644 --- a/src/fft/src/asgram.proto.c +++ b/src/fft/src/asgram.proto.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -256,7 +256,7 @@ int ASGRAM(_print)(ASGRAM() _q) float maxval; float maxfreq; char ascii[_q->nfft+1]; - ascii[_q->nfft] = '\0'; // append null character to end of string + memset(ascii, '\0', _q->nfft+1); // fill buffer with null characters // execute the spectrogram ASGRAM(_execute)(_q, ascii, &maxval, &maxfreq); diff --git a/src/framing/src/bsync.proto.c b/src/framing/src/bsync.proto.c index 22cc681a3a..2246df36a7 100644 --- a/src/framing/src/bsync.proto.c +++ b/src/framing/src/bsync.proto.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,11 +36,11 @@ struct BSYNC(_s) { bsequence sync_i; // synchronization pattern (in-phase) bsequence sym_i; // received symbols (in-phase) -//#if defined TC_COMPLEX +//#if TC_COMPLEX==1 bsequence sync_q; // synchronization pattern (quadrature) //#endif -//#if defined TI_COMPLEX +//#if TI_COMPLEX==1 bsequence sym_q; // received symbols (quadrature) //#endif TO rxy; // cross correlation @@ -52,19 +52,19 @@ BSYNC() BSYNC(_create)(unsigned int _n, TC * _v) fs->n = _n; fs->sync_i = bsequence_create(fs->n); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 fs->sync_q = bsequence_create(fs->n); #endif fs->sym_i = bsequence_create(fs->n); -#ifdef TI_COMPLEX +#if TI_COMPLEX==1 fs->sym_q = bsequence_create(fs->n); #endif unsigned int i; for (i=0; in; i++) { bsequence_push(fs->sync_i, crealf(_v[i])>0); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 bsequence_push(fs->sync_q, cimagf(_v[i])>0); #endif } @@ -89,12 +89,12 @@ BSYNC() BSYNC(_create_msequence)(unsigned int _g, unsigned int n = msequence_get_length(ms); fs->sync_i = bsequence_create(n * _k); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 fs->sync_q = bsequence_create(n * _k); #endif fs->sym_i = bsequence_create(n * _k); -#ifdef TI_COMPLEX +#if TI_COMPLEX==1 fs->sym_q = bsequence_create(n * _k); #endif @@ -102,7 +102,7 @@ BSYNC() BSYNC(_create_msequence)(unsigned int _g, #if 0 bsequence_init_msequence(fs->sync_i,ms); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 msequence_reset(ms); bsequence_init_msequence(fs->sync_q,ms); #endif @@ -114,7 +114,7 @@ BSYNC() BSYNC(_create_msequence)(unsigned int _g, for (j=0; j<_k; j++) { bsequence_push(fs->sync_i, bit); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 bsequence_push(fs->sync_q, bit); #endif } @@ -131,12 +131,12 @@ BSYNC() BSYNC(_create_msequence)(unsigned int _g, void BSYNC(_destroy)(BSYNC() _fs) { bsequence_destroy(_fs->sync_i); -#ifdef TC_COMPLEX +#if TC_COMPLEX==1 bsequence_destroy(_fs->sync_q); #endif bsequence_destroy(_fs->sym_i); -#ifdef TI_COMPLEX +#if TI_COMPLEX==1 bsequence_destroy(_fs->sym_q); #endif free(_fs); @@ -151,12 +151,12 @@ void BSYNC(_correlate)(BSYNC() _fs, TI _sym, TO *_y) { // push symbol into buffers bsequence_push(_fs->sym_i, crealf(_sym)>0.0 ? 1 : 0); -#ifdef TI_COMPLEX +#if TI_COMPLEX==1 bsequence_push(_fs->sym_q, cimagf(_sym)>0.0 ? 1 : 0); #endif // compute dotprod -#if defined TC_COMPLEX && defined TI_COMPLEX +#if TC_COMPLEX==1 && TI_COMPLEX==1 // cccx TO rxy_ii = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_i) - (float)(_fs->n); TO rxy_qq = 2.*bsequence_correlate(_fs->sync_q, _fs->sym_q) - (float)(_fs->n); @@ -164,7 +164,7 @@ void BSYNC(_correlate)(BSYNC() _fs, TI _sym, TO *_y) TO rxy_qi = 2.*bsequence_correlate(_fs->sync_q, _fs->sym_i) - (float)(_fs->n); _fs->rxy = (rxy_ii - rxy_qq) + _Complex_I*(rxy_iq + rxy_qi); -#elif defined TI_COMPLEX +#elif TI_COMPLEX==1 // crcx float rxy_ii = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_i) - (float)(_fs->n); float rxy_iq = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_q) - (float)(_fs->n); diff --git a/src/framing/src/bsync_cccf.c b/src/framing/src/bsync_cccf.c deleted file mode 100644 index 4ac9fe834d..0000000000 --- a/src/framing/src/bsync_cccf.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2007 - 2022 Joseph Gaeddert - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// -// Framing API: floating-point -// - -#include "liquid.internal.h" - -// -#define BSYNC(name) LIQUID_CONCAT(bsync_cccf,name) - -#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) - -#define TO float complex -#define TC float complex -#define TI float complex -#define ABS(X) cabsf(X) -#define WINDOW(name) LIQUID_CONCAT(windowcf,name) -#define DOTPROD(name) LIQUID_CONCAT(dotprod_cccf,name) - -#define TO_COMPLEX -#define TC_COMPLEX -#define TI_COMPLEX - -// prototypes -#include "bsync.proto.c" - diff --git a/src/framing/src/bsync_crcf.c b/src/framing/src/bsync_crcf.c deleted file mode 100644 index b6b4707843..0000000000 --- a/src/framing/src/bsync_crcf.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2007 - 2022 Joseph Gaeddert - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// -// Framing API: floating-point -// - -#include "liquid.internal.h" - -// -#define BSYNC(name) LIQUID_CONCAT(bsync_crcf,name) - -#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) - -#define TO float complex -#define TC float -#define TI float complex -#define ABS(X) cabsf(X) -#define WINDOW(name) LIQUID_CONCAT(windowcf,name) -#define DOTPROD(name) LIQUID_CONCAT(dotprod_crcf,name) - -#define TO_COMPLEX -#undef TC_COMPLEX -#define TI_COMPLEX - -// prototypes -#include "bsync.proto.c" - diff --git a/src/framing/src/framesync64.c b/src/framing/src/framesync64.c index 1861c06e27..6eca7a759b 100644 --- a/src/framing/src/framesync64.c +++ b/src/framing/src/framesync64.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,27 +31,10 @@ #include "liquid.internal.h" -#define FRAMESYNC64_ENABLE_EQ 0 - -// push samples through detection stage -int framesync64_execute_seekpn(framesync64 _q, - float complex _x); - -// step receiver mixer, matched filter, decimator -// _q : frame synchronizer -// _x : input sample -// _y : output symbol -int framesync64_step(framesync64 _q, - float complex _x, - float complex * _y); - -// push samples through synchronizer, saving received p/n symbols -int framesync64_execute_rxpreamble(framesync64 _q, - float complex _x); - -// receive payload symbols -int framesync64_execute_rxpayload(framesync64 _q, - float complex _x); +// synchronization callback, return 0:continue, 1:reset +int framesync64_callback_internal(float complex * _buf, + unsigned int _buf_len, + void * _context); // export debugging based on return value // 0 : do not write file @@ -59,55 +42,31 @@ int framesync64_execute_rxpayload(framesync64 _q, // -1 : number of packets detected // -2 : id using first 4 bytes of header // -3 : write with random extension -int framesync64_debug_export(framesync64 _q, int _code); +int framesync64_debug_export(framesync64 _q, int _code, float complex * _payload_rx); // framesync64 object structure struct framesync64_s { // callback - framesync_callback callback; // user-defined callback function - void * userdata; // user-defined data structure + framesync_callback callback; // user-defined callback function + void * userdata; // user-defined data structure framesyncstats_s framesyncstats; // frame statistic object (synchronizer) framedatastats_s framedatastats; // frame statistic object (packet statistics) // synchronizer objects - unsigned int m; // filter delay (symbols) - float beta; // filter excess bandwidth factor - qdetector_cccf detector; // pre-demod detector - float tau_hat; // fractional timing offset estimate - float dphi_hat; // carrier frequency offset estimate - float phi_hat; // carrier phase offset estimate - float gamma_hat; // channel gain estimate - nco_crcf mixer; // coarse carrier frequency recovery - - // timing recovery objects, states - firpfb_crcf mf; // matched filter decimator - unsigned int npfb; // number of filters in symsync - int mf_counter; // matched filter output timer - unsigned int pfb_index; // filterbank index -#if FRAMESYNC64_ENABLE_EQ - eqlms_cccf equalizer; // equalizer (trained on p/n sequence) -#endif + unsigned int m; // filter delay (symbols) + float beta; // filter excess bandwidth factor + qdsync_cccf sync; // sequence detector // preamble - float complex preamble_pn[64]; // known 64-symbol p/n sequence - float complex preamble_rx[64]; // received p/n symbols - + float complex preamble_pn[64]; // known 64-symbol p/n sequence + float complex preamble_rx[64]; // received p/n symbols + // payload decoder - float complex payload_rx [630]; // received payload symbols with pilots - float complex payload_sym[600]; // received payload symbols - unsigned char payload_dec[ 72]; // decoded payload bytes - qpacketmodem dec; // packet demodulator/decoder - qpilotsync pilotsync; // pilot extraction, carrier recovery - int payload_valid; // did payload pass crc? - - // status variables - enum { - FRAMESYNC64_STATE_DETECTFRAME=0, // detect frame (seek p/n sequence) - FRAMESYNC64_STATE_RXPREAMBLE, // receive p/n sequence - FRAMESYNC64_STATE_RXPAYLOAD, // receive payload data - } state; - unsigned int preamble_counter; // counter: num of p/n syms received - unsigned int payload_counter; // counter: num of payload syms received + float complex payload_sym[600]; // received payload symbols + unsigned char payload_dec[ 72]; // decoded payload bytes + qpacketmodem dec; // packet demodulator/decoder + qpilotsync pilotsync; // pilot extraction, carrier recovery + int payload_valid; // did payload pass crc? windowcf buf_debug; // debug: raw input samples char * prefix; // debug: filename prefix @@ -137,25 +96,11 @@ framesync64 framesync64_create(framesync_callback _callback, } msequence_destroy(ms); - // create frame detector - unsigned int k = 2; // samples/symbol - q->detector = qdetector_cccf_create_linear(q->preamble_pn, 64, LIQUID_FIRFILT_ARKAISER, k, q->m, q->beta); - qdetector_cccf_set_threshold(q->detector, 0.5f); - - // create symbol timing recovery filters - q->npfb = 64; // number of filters in the bank - q->mf = firpfb_crcf_create_rnyquist(LIQUID_FIRFILT_ARKAISER, q->npfb,k,q->m,q->beta); - -#if FRAMESYNC64_ENABLE_EQ - // create equalizer - unsigned int p = 3; - q->equalizer = eqlms_cccf_create_lowpass(2*k*p+1, 0.4f); - eqlms_cccf_set_bw(q->equalizer, 0.05f); -#endif - - // create down-coverters for carrier phase tracking - q->mixer = nco_crcf_create(LIQUID_NCO); - + // create frame detector with callback to run frame sync in one step + q->sync = qdsync_cccf_create_linear(q->preamble_pn, 64, + LIQUID_FIRFILT_ARKAISER, 2, q->m, q->beta, framesync64_callback_internal, q); + qdsync_cccf_set_buf_len(q->sync, 64 + 630); + // create payload demodulator/decoder object int check = LIQUID_CRC_24; int fec0 = LIQUID_FEC_NONE; @@ -163,13 +108,12 @@ framesync64 framesync64_create(framesync_callback _callback, int mod_scheme = LIQUID_MODEM_QPSK; q->dec = qpacketmodem_create(); qpacketmodem_configure(q->dec, 72, check, fec0, fec1, mod_scheme); - //qpacketmodem_print(q->dec); assert( qpacketmodem_get_frame_len(q->dec)==600 ); // create pilot synchronizer q->pilotsync = qpilotsync_create(600, 21); assert( qpilotsync_get_frame_len(q->pilotsync)==630); - + // reset global data counters framesync64_reset_framedatastats(q); @@ -199,22 +143,24 @@ framesync64 framesync64_copy(framesync64 q_orig) memmove(q_copy, q_orig, sizeof(struct framesync64_s)); // set callback and userdata fields - q_copy->callback = q_orig->callback; - q_copy->userdata = q_orig->userdata; + //q_copy->callback = q_orig->callback; + //q_copy->userdata = q_orig->userdata; // copy objects - q_copy->detector = qdetector_cccf_copy(q_orig->detector); - q_copy->mixer = nco_crcf_copy (q_orig->mixer); - q_copy->mf = firpfb_crcf_copy (q_orig->mf); - q_copy->dec = qpacketmodem_copy (q_orig->dec); - q_copy->pilotsync= qpilotsync_copy (q_orig->pilotsync); - q_copy->buf_debug= windowcf_copy (q_orig->buf_debug); + q_copy->sync = qdsync_cccf_copy (q_orig->sync); + q_copy->dec = qpacketmodem_copy(q_orig->dec); + q_copy->pilotsync= qpilotsync_copy (q_orig->pilotsync); + q_copy->buf_debug= windowcf_copy (q_orig->buf_debug); // set prefix value q_copy->prefix = NULL; q_copy->filename = NULL; framesync64_set_prefix(q_copy, q_orig->prefix); + // update the context for the sync object to that detected frames will + // apply to this new frame synchronizer object + qdsync_cccf_set_context(q_copy->sync, q_copy); + return q_copy; } @@ -222,15 +168,10 @@ framesync64 framesync64_copy(framesync64 q_orig) int framesync64_destroy(framesync64 _q) { // destroy synchronization objects - qdetector_cccf_destroy(_q->detector); // frame detector - firpfb_crcf_destroy (_q->mf); // matched filter - nco_crcf_destroy (_q->mixer); // coarse NCO - qpacketmodem_destroy (_q->dec); // payload demodulator - qpilotsync_destroy (_q->pilotsync); // pilot synchronizer -#if FRAMESYNC64_ENABLE_EQ - eqlms_cccf_destroy (_q->equalizer); // LMS equalizer -#endif - windowcf_destroy(_q->buf_debug); + qdsync_cccf_destroy (_q->sync); // frame detector/synchronizer + qpacketmodem_destroy(_q->dec); // payload demodulator + qpilotsync_destroy (_q->pilotsync); // pilot synchronizer + windowcf_destroy (_q->buf_debug); // free main object memory free(_q); @@ -247,23 +188,8 @@ int framesync64_print(framesync64 _q) // reset frame synchronizer object int framesync64_reset(framesync64 _q) { - // reset binary pre-demod synchronizer - qdetector_cccf_reset(_q->detector); - - // reset carrier recovery objects - nco_crcf_reset(_q->mixer); - - // reset symbol timing recovery state - firpfb_crcf_reset(_q->mf); - - // reset state - _q->state = FRAMESYNC64_STATE_DETECTFRAME; - _q->preamble_counter= 0; - _q->payload_counter = 0; - - // reset frame statistics - _q->framesyncstats.evm = 0.0f; - + // reset synchronizer + qdsync_cccf_reset(_q->sync); return LIQUID_OK; } @@ -291,233 +217,64 @@ int framesync64_execute(framesync64 _q, float complex * _x, unsigned int _n) { - unsigned int i; - for (i=0; i<_n; i++) { - // push sample into debug buffer - windowcf_push(_q->buf_debug, _x[i]); - - switch (_q->state) { - case FRAMESYNC64_STATE_DETECTFRAME: - // detect frame (look for p/n sequence) - framesync64_execute_seekpn(_q, _x[i]); - break; - case FRAMESYNC64_STATE_RXPREAMBLE: - // receive p/n sequence symbols - framesync64_execute_rxpreamble(_q, _x[i]); - break; - case FRAMESYNC64_STATE_RXPAYLOAD: - // receive payload symbols - framesync64_execute_rxpayload(_q, _x[i]); - break; - default: - return liquid_error(LIQUID_EINT,"framesync64_exeucte(), unknown/unsupported state"); - } - } - return LIQUID_OK; + return qdsync_cccf_execute(_q->sync, _x, _n); } -// +// // internal methods // -// execute synchronizer, seeking p/n sequence -// _q : frame synchronizer object -// _x : input sample -// _sym : demodulated symbol -int framesync64_execute_seekpn(framesync64 _q, - float complex _x) -{ - // push through pre-demod synchronizer - float complex * v = qdetector_cccf_execute(_q->detector, _x); - - // check if frame has been detected - if (v != NULL) { - // get estimates - _q->tau_hat = qdetector_cccf_get_tau (_q->detector); - _q->gamma_hat = qdetector_cccf_get_gamma(_q->detector); - _q->dphi_hat = qdetector_cccf_get_dphi (_q->detector); - _q->phi_hat = qdetector_cccf_get_phi (_q->detector); - - // set appropriate filterbank index - if (_q->tau_hat > 0) { - _q->pfb_index = (unsigned int)( _q->tau_hat * _q->npfb) % _q->npfb; - _q->mf_counter = 0; - } else { - _q->pfb_index = (unsigned int)((1.0f+_q->tau_hat) * _q->npfb) % _q->npfb; - _q->mf_counter = 1; - } - //printf("***** frame detected! tau-hat:%8.4f(%u/%u), dphi-hat:%8.4f, gamma:%8.2f dB\n", - // _q->tau_hat, _q->pfb_index, _q->npfb, _q->dphi_hat, 20*log10f(_q->gamma_hat)); - - // output filter scale - firpfb_crcf_set_scale(_q->mf, 0.5f / _q->gamma_hat); - - // set frequency/phase of mixer - nco_crcf_set_frequency(_q->mixer, _q->dphi_hat); - nco_crcf_set_phase (_q->mixer, _q->phi_hat ); - - // update state - _q->state = FRAMESYNC64_STATE_RXPREAMBLE; - - // run buffered samples through synchronizer - unsigned int buf_len = qdetector_cccf_get_buf_len(_q->detector); - framesync64_execute(_q, v, buf_len); - } - return LIQUID_OK; -} - -// step receiver mixer, matched filter, decimator -// _q : frame synchronizer -// _x : input sample -// _y : output symbol -int framesync64_step(framesync64 _q, - float complex _x, - float complex * _y) -{ - // mix sample down - float complex v; - nco_crcf_mix_down(_q->mixer, _x, &v); - nco_crcf_step (_q->mixer); - - // push sample into filterbank - firpfb_crcf_push (_q->mf, v); - firpfb_crcf_execute(_q->mf, _q->pfb_index, &v); - -#if FRAMESYNC64_ENABLE_EQ - // push sample through equalizer - eqlms_cccf_push(_q->equalizer, v); -#endif - - // increment counter to determine if sample is available - _q->mf_counter++; - int sample_available = (_q->mf_counter >= 1) ? 1 : 0; - - // set output sample if available - if (sample_available) { -#if FRAMESYNC64_ENABLE_EQ - // compute equalizer output - eqlms_cccf_execute(_q->equalizer, &v); -#endif - - // set output - *_y = v; - - // decrement counter by k=2 samples/symbol - _q->mf_counter -= 2; - } - - // return flag - return sample_available; -} - -// execute synchronizer, receiving p/n sequence -// _q : frame synchronizer object -// _x : input sample -// _sym : demodulated symbol -int framesync64_execute_rxpreamble(framesync64 _q, - float complex _x) +// synchronization callback, return 0:continue, 1:reset +int framesync64_callback_internal(float complex * _buf, + unsigned int _buf_len, + void * _context) { - // step synchronizer - float complex mf_out = 0.0f; - int sample_available = framesync64_step(_q, _x, &mf_out); - - // compute output if timeout - if (sample_available) { - - // save output in p/n symbols buffer -#if FRAMESYNC64_ENABLE_EQ - unsigned int delay = 2*_q->m + 3; // delay from matched filter and equalizer -#else - unsigned int delay = 2*_q->m; // delay from matched filter -#endif - if (_q->preamble_counter >= delay) { - unsigned int index = _q->preamble_counter-delay; - - _q->preamble_rx[index] = mf_out; - -#if FRAMESYNC64_ENABLE_EQ - // train equalizer - eqlms_cccf_step(_q->equalizer, _q->preamble_pn[index], mf_out); -#endif - } - - // update p/n counter - _q->preamble_counter++; - - // update state - if (_q->preamble_counter == 64 + delay) - _q->state = FRAMESYNC64_STATE_RXPAYLOAD; + // type cast context input as frame synchronizer object + framesync64 _q = (framesync64)_context; + + // recover data symbols from pilots (input buffer with 64-symbol preamble offset) + qpilotsync_execute(_q->pilotsync, _buf + 64, _q->payload_sym); + + // decode payload + _q->payload_valid = qpacketmodem_decode(_q->dec, + _q->payload_sym, + _q->payload_dec); + + // update statistics + _q->framedatastats.num_frames_detected++; + _q->framedatastats.num_headers_valid += _q->payload_valid; + _q->framedatastats.num_payloads_valid += _q->payload_valid; + _q->framedatastats.num_bytes_received += _q->payload_valid ? 64 : 0; + + // set framesyncstats internals + _q->framesyncstats.evm = qpacketmodem_get_demodulator_evm(_q->dec); + _q->framesyncstats.rssi = 20*log10f( qdsync_cccf_get_gamma(_q->sync) ); + _q->framesyncstats.cfo = qdsync_cccf_get_dphi(_q->sync) + qpilotsync_get_dphi(_q->pilotsync) / 2.0f; + _q->framesyncstats.framesyms = _q->payload_sym; + _q->framesyncstats.num_framesyms = 600; + _q->framesyncstats.mod_scheme = LIQUID_MODEM_QPSK; + _q->framesyncstats.mod_bps = 2; + _q->framesyncstats.check = LIQUID_CRC_24; + _q->framesyncstats.fec0 = LIQUID_FEC_NONE; + _q->framesyncstats.fec1 = LIQUID_FEC_GOLAY2412; + + // invoke callback + if (_q->callback != NULL) { + int rc = + _q->callback(&_q->payload_dec[0], // header is first 8 bytes + _q->payload_valid, + &_q->payload_dec[8], // payload is last 64 bytes + 64, + _q->payload_valid, + _q->framesyncstats, + _q->userdata); + + // export debugging based on return value + framesync64_debug_export(_q, rc, _buf+64); } - return LIQUID_OK; -} -// execute synchronizer, receiving payload -// _q : frame synchronizer object -// _x : input sample -// _sym : demodulated symbol -int framesync64_execute_rxpayload(framesync64 _q, - float complex _x) -{ - // step synchronizer - float complex mf_out = 0.0f; - int sample_available = framesync64_step(_q, _x, &mf_out); - - // compute output if timeout - if (sample_available) { - // save payload symbols (modem input/output) - _q->payload_rx[_q->payload_counter] = mf_out; - - // increment counter - _q->payload_counter++; - - if (_q->payload_counter == 630) { - // recover data symbols from pilots - qpilotsync_execute(_q->pilotsync, _q->payload_rx, _q->payload_sym); - - // decode payload - _q->payload_valid = qpacketmodem_decode(_q->dec, - _q->payload_sym, - _q->payload_dec); - - // update statistics - _q->framedatastats.num_frames_detected++; - _q->framedatastats.num_headers_valid += _q->payload_valid; - _q->framedatastats.num_payloads_valid += _q->payload_valid; - _q->framedatastats.num_bytes_received += _q->payload_valid ? 64 : 0; - - // invoke callback - if (_q->callback != NULL) { - // set framesyncstats internals - _q->framesyncstats.evm = qpacketmodem_get_demodulator_evm(_q->dec); //qpilotsync_get_evm(_q->pilotsync); - _q->framesyncstats.rssi = 20*log10f(_q->gamma_hat); - _q->framesyncstats.cfo = nco_crcf_get_frequency(_q->mixer); - _q->framesyncstats.framesyms = _q->payload_sym; - _q->framesyncstats.num_framesyms = 600; - _q->framesyncstats.mod_scheme = LIQUID_MODEM_QPSK; - _q->framesyncstats.mod_bps = 2; - _q->framesyncstats.check = LIQUID_CRC_24; - _q->framesyncstats.fec0 = LIQUID_FEC_NONE; - _q->framesyncstats.fec1 = LIQUID_FEC_GOLAY2412; - - // invoke callback method - int rc = - _q->callback(&_q->payload_dec[0], // header is first 8 bytes - _q->payload_valid, - &_q->payload_dec[8], // payload is last 64 bytes - 64, - _q->payload_valid, - _q->framesyncstats, - _q->userdata); - - // export debugging based on return value - framesync64_debug_export(_q, rc); - } - - // reset frame synchronizer - return framesync64_reset(_q); - } - } - return LIQUID_OK; + // reset frame synchronizer + return framesync64_reset(_q); } // DEPRECATED: enable debugging @@ -542,14 +299,14 @@ int framesync64_debug_print(framesync64 _q, // get detection threshold float framesync64_get_threshold(framesync64 _q) { - return qdetector_cccf_get_threshold(_q->detector); + return qdsync_cccf_get_threshold(_q->sync); } // set detection threshold int framesync64_set_threshold(framesync64 _q, float _threshold) { - return qdetector_cccf_set_threshold(_q->detector, _threshold); + return qdsync_cccf_set_threshold(_q->sync, _threshold); } // set prefix for exporting debugging files, default: "framesync64" @@ -606,8 +363,9 @@ framedatastats_s framesync64_get_framedatastats(framesync64 _q) } // export debugging samples to file -int framesync64_debug_export(framesync64 _q, - int _code) +int framesync64_debug_export(framesync64 _q, + int _code, + float complex * _payload_rx) { // determine what to do based on callback return code if (_code == 0) { @@ -651,14 +409,16 @@ int framesync64_debug_export(framesync64 _q, //framesyncstats_export(_q->framesyncstats, fid); // export measured offsets - fwrite(&(_q->tau_hat), sizeof(float), 1, fid); - fwrite(&(_q->dphi_hat), sizeof(float), 1, fid); - fwrite(&(_q->phi_hat), sizeof(float), 1, fid); - fwrite(&(_q->gamma_hat),sizeof(float), 1, fid); - fwrite(&(_q->framesyncstats.evm), sizeof(float), 1, fid); + float tau_hat = 0.0f; + float phi_hat = 0.0f; + fwrite(&tau_hat, sizeof(float), 1, fid); + fwrite(&(_q->framesyncstats.cfo), sizeof(float), 1, fid); + fwrite(&phi_hat, sizeof(float), 1, fid); + fwrite(&(_q->framesyncstats.rssi), sizeof(float), 1, fid); + fwrite(&(_q->framesyncstats.evm), sizeof(float), 1, fid); // export payload values - fwrite(_q->payload_rx, sizeof(float complex), 630, fid); + fwrite(_payload_rx, sizeof(float complex), 630, fid); fwrite(_q->payload_sym, sizeof(float complex), 600, fid); fwrite(_q->payload_dec, sizeof(unsigned char), 72, fid); diff --git a/src/framing/src/bpresync_cccf.c b/src/framing/src/framing_cccf.c similarity index 57% rename from src/framing/src/bpresync_cccf.c rename to src/framing/src/framing_cccf.c index e1b3b987a3..8cdd68b923 100644 --- a/src/framing/src/bpresync_cccf.c +++ b/src/framing/src/framing_cccf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,34 +20,52 @@ * THE SOFTWARE. */ -// -// Binary pre-demod synchronizer -// +// complex floating-point synchronization and supporting objects #include "liquid.internal.h" -// -#define BPRESYNC(name) LIQUID_CONCAT(bpresync_cccf,name) - -// print and naming extensions -#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) +// naming extensions (useful for print statements) #define EXTENSION_SHORT "f" #define EXTENSION_FULL "cccf" +#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) + +#define T float #define TO float complex #define TC float complex #define TI float complex - #define ABS(X) cabsf(X) #define REAL(X) crealf(X) #define IMAG(X) cimagf(X) +#define TO_COMPLEX 1 +#define TC_COMPLEX 1 +#define TI_COMPLEX 1 + +// supporting references +#define AGC(name) LIQUID_CONCAT(agc_crcf,name) #define BSYNC(name) LIQUID_CONCAT(bsync_cccf,name) +#define DOTPROD(name) LIQUID_CONCAT(dotprod_rrrf,name) +#define EQLMS(name) LIQUID_CONCAT(eqlms_cccf,name) +#define MODEM(name) LIQUID_CONCAT(modemcf,name) +#define NCO(name) LIQUID_CONCAT(nco_crcf,name) +#define QDETECTOR(name) LIQUID_CONCAT(qdetector_cccf,name) +#define SYMSYNC(name) LIQUID_CONCAT(symsync_crcf,name) +#define WINDOW(name) LIQUID_CONCAT(windowf,name) -#define TO_COMPLEX -#define TC_COMPLEX -#define TI_COMPLEX +// object references +#define BPRESYNC(name) LIQUID_CONCAT(bpresync_cccf,name) +#define BSYNC(name) LIQUID_CONCAT(bsync_cccf,name) +#define PRESYNC(name) LIQUID_CONCAT(presync_cccf,name) +#define QDETECTOR(name) LIQUID_CONCAT(qdetector_cccf,name) +#define QDSYNC(name) LIQUID_CONCAT(qdsync_cccf,name) +#define SYMTRACK(name) LIQUID_CONCAT(symtrack_cccf,name) // prototypes #include "bpresync.proto.c" +#include "bsync.proto.c" +#include "presync.proto.c" +#include "qdetector.proto.c" +#include "qdsync.proto.c" +#include "symtrack.proto.c" diff --git a/src/framing/src/symtrack_cccf.c b/src/framing/src/framing_crcf.c similarity index 73% rename from src/framing/src/symtrack_cccf.c rename to src/framing/src/framing_crcf.c index 26f863fe13..330f6c1880 100644 --- a/src/framing/src/symtrack_cccf.c +++ b/src/framing/src/framing_crcf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,36 +20,41 @@ * THE SOFTWARE. */ -// -// Framing API: floating-point -// +// complex floating-point synchronization and supporting objects #include "liquid.internal.h" // naming extensions (useful for print statements) #define EXTENSION_SHORT "f" -#define EXTENSION_FULL "cccf" +#define EXTENSION_FULL "crcf" #define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) #define T float #define TO float complex -#define TC float complex +#define TC float #define TI float complex #define ABS(X) cabsf(X) +#define REAL(X) crealf(X) +#define IMAG(X) cimagf(X) -// object references -#define SYMTRACK(name) LIQUID_CONCAT(symtrack_cccf,name) +#define TO_COMPLEX 1 +#define TC_COMPLEX 0 +#define TI_COMPLEX 1 + +// supporting references #define AGC(name) LIQUID_CONCAT(agc_crcf,name) -#define SYMSYNC(name) LIQUID_CONCAT(symsync_crcf,name) -#define EQLMS(name) LIQUID_CONCAT(eqlms_cccf,name) -#define NCO(name) LIQUID_CONCAT(nco_crcf,name) +#define BSYNC(name) LIQUID_CONCAT(bsync_crcf,name) +#define DOTPROD(name) LIQUID_CONCAT(dotprod_rrrf,name) +#define EQLMS(name) LIQUID_CONCAT(eqlms_crcf,name) #define MODEM(name) LIQUID_CONCAT(modemcf,name) +#define NCO(name) LIQUID_CONCAT(nco_crcf,name) +#define SYMSYNC(name) LIQUID_CONCAT(symsync_crcf,name) +#define WINDOW(name) LIQUID_CONCAT(windowf,name) -#define TO_COMPLEX 1 -#define TC_COMPLEX 1 -#define TI_COMPLEX 1 +// object references +#define BSYNC(name) LIQUID_CONCAT(bsync_crcf,name) // prototypes -#include "symtrack.proto.c" +#include "bsync.proto.c" diff --git a/src/framing/src/presync_cccf.c b/src/framing/src/framing_rrrf.c similarity index 62% rename from src/framing/src/presync_cccf.c rename to src/framing/src/framing_rrrf.c index 12ad70a19a..361f343a67 100644 --- a/src/framing/src/presync_cccf.c +++ b/src/framing/src/framing_rrrf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,37 +20,41 @@ * THE SOFTWARE. */ -// -// Binary pre-demod synchronizer -// +// real floating-point synchronization and supporting objects #include "liquid.internal.h" -// -#define PRESYNC(name) LIQUID_CONCAT(presync_cccf,name) - -// print and naming extensions -#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) +// naming extensions (useful for print statements) #define EXTENSION_SHORT "f" -#define EXTENSION_FULL "cccf" +#define EXTENSION_FULL "rrrf" -#define T float // primitive type -#define TO float complex // output type -#define TC float complex // coefficient type -#define TI float complex // input type +#define PRINTVAL(x) printf("%12.4e + j%12.4e", crealf(x), cimagf(x)) +#define T float +#define TO float +#define TC float +#define TI float #define ABS(X) cabsf(X) #define REAL(X) crealf(X) #define IMAG(X) cimagf(X) -#define WINDOW(name) LIQUID_CONCAT(windowf,name) +#define TO_COMPLEX 0 +#define TC_COMPLEX 0 +#define TI_COMPLEX 0 + +// supporting references +#define AGC(name) LIQUID_CONCAT(agc_rrrf,name) +#define BSYNC(name) LIQUID_CONCAT(bsync_rrrf,name) #define DOTPROD(name) LIQUID_CONCAT(dotprod_rrrf,name) -#define BSYNC(name) LIQUID_CONCAT(bsync_cccf,name) +#define EQLMS(name) LIQUID_CONCAT(eqlms_rrrf,name) +#define MODEM(name) LIQUID_CONCAT(modemcf,name) +#define NCO(name) LIQUID_CONCAT(nco_rrrf,name) +#define SYMSYNC(name) LIQUID_CONCAT(symsync_rrrf,name) +#define WINDOW(name) LIQUID_CONCAT(windowf,name) -#define TO_COMPLEX -#define TC_COMPLEX -#define TI_COMPLEX +// object references +#define BSYNC(name) LIQUID_CONCAT(bsync_rrrf,name) // prototypes -#include "presync.proto.c" +#include "bsync.proto.c" diff --git a/src/framing/src/symstreamrcf.c b/src/framing/src/framingcf.c similarity index 68% rename from src/framing/src/symstreamrcf.c rename to src/framing/src/framingcf.c index b1d2b91e74..aeb19f8b5e 100644 --- a/src/framing/src/symstreamrcf.c +++ b/src/framing/src/framingcf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,7 @@ * THE SOFTWARE. */ -// -// API: floating-point -// +// complex floating-point framing and signal generators #include "liquid.internal.h" @@ -35,11 +33,25 @@ #define TO_COMPLEX 1 #define T_COMPLEX 0 -// object references +// supporting references +#define FIRINTERP(name) LIQUID_CONCAT(firinterp_crcf,name) +#define IIRFILT(name) LIQUID_CONCAT(iirfilt_crcf, name) +#define MODEM(name) LIQUID_CONCAT(modemcf, name) #define MSRESAMP(name) LIQUID_CONCAT(msresamp_crcf,name) +#define NCO(name) LIQUID_CONCAT(nco_crcf, name) +#define SYMSTREAM(name) LIQUID_CONCAT(symstreamcf, name) + +// object references +#define MSOURCE(name) LIQUID_CONCAT(msourcecf, name) +#define QPACKETMODEM(name) LIQUID_CONCAT(qpacketmodem, name) +#define QSOURCE(name) LIQUID_CONCAT(qsourcecf, name) #define SYMSTREAM(name) LIQUID_CONCAT(symstreamcf, name) #define SYMSTREAMR(name) LIQUID_CONCAT(symstreamrcf, name) // prototypes +#include "msource.proto.c" +#include "qpacketmodem.proto.c" +#include "qsource.proto.c" +#include "symstream.proto.c" #include "symstreamr.proto.c" diff --git a/src/framing/src/fskframegen.c b/src/framing/src/fskframegen.c index 8aac7cf344..90557e53c9 100644 --- a/src/framing/src/fskframegen.c +++ b/src/framing/src/fskframegen.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -321,7 +321,7 @@ int fskframegen_assemble(fskframegen _q, // encode payload symbols qpacketmodem_encode_syms(_q->payload_encoder, _payload, _q->payload_sym); -#if 1 +#if 0 printf("tx payload symbols (%u)\n", _q->payload_sym_len); unsigned int i; for (i=0; i<_q->payload_sym_len; i++) @@ -404,7 +404,7 @@ int fskframegen_encode_header(fskframegen _q, // run packet encoder, encoding into symbols qpacketmodem_encode_syms(_q->header_encoder, _q->header_dec, _q->header_sym); -#if 1 +#if 0 printf("tx header symbols (%u):\n", _q->header_sym_len); unsigned int i; for (i=0; i<_q->header_sym_len; i++) diff --git a/src/framing/src/fskframesync.c b/src/framing/src/fskframesync.c index 47ec916a59..8d30274e6f 100644 --- a/src/framing/src/fskframesync.c +++ b/src/framing/src/fskframesync.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -456,15 +456,16 @@ int fskframesync_execute_detectframe(fskframesync _q, // NOTE: because this is a ratio of energies in frequency, we don't need // to take the absolute value here; only positive values should work if (rxy > 0.5f) { - printf("### fskframe detected! ###\n"); + //printf("### fskframe detected! ###\n"); _q->frame_detected = 1; } } else { // frame has already been detected; wait for signal to peak if (_q->rxy[1] > _q->rxy[2]) { - printf("signal peaked! %12.8f %12.8f %12.8f\n", - _q->rxy[0], _q->rxy[1], _q->rxy[2]); + //printf("signal peaked! %12.8f %12.8f %12.8f\n", + // _q->rxy[0], _q->rxy[1], _q->rxy[2]); +#if 0 // compute estimate, apply bias compensation float gamma = (_q->rxy[2] - _q->rxy[0]) / _q->rxy[1]; float p2 = 9.54907046918287e-01f; @@ -474,6 +475,7 @@ int fskframesync_execute_detectframe(fskframesync _q, int num_samples = round(tau_hat * _q->k); printf("timing offset estimate : %12.8f -> %12.8f (%d samples)\n", gamma, tau_hat, num_samples); +#endif // TODO: set timer and filterbank index accordingly _q->timer = 2*_q->k; @@ -481,7 +483,7 @@ int fskframesync_execute_detectframe(fskframesync _q, // update state... _q->state = STATE_RXHEADER; } else { - printf("signal not yet peaked...\n"); + //printf("signal not yet peaked...\n"); } } return LIQUID_OK; @@ -524,7 +526,7 @@ int fskframesync_execute_rxheader(fskframesync _q, int header_valid = qpacketmodem_decode_syms(_q->header_decoder, _q->header_sym, _q->header_dec); -#if 1 +#if 0 printf("rx header symbols (%u):\n", _q->header_sym_len); unsigned int i; for (i=0; i<_q->header_sym_len; i++) @@ -612,7 +614,7 @@ int fskframesync_execute_rxpayload(fskframesync _q, // decode payload if appropriate if (_q->symbol_counter == _q->payload_sym_len) { -#if 1 +#if 0 printf("rx payload symbols (%u)\n", _q->payload_sym_len); unsigned int i; for (i=0; i<_q->payload_sym_len; i++) @@ -623,7 +625,7 @@ int fskframesync_execute_rxpayload(fskframesync _q, int payload_valid = qpacketmodem_decode_syms(_q->payload_decoder, _q->payload_sym, _q->payload_dec); - printf("payload: %s\n", payload_valid ? "valid" : "INVALID"); + //printf("payload: %s\n", payload_valid ? "valid" : "INVALID"); // invoke callback if (_q->callback != NULL) { diff --git a/src/framing/src/ofdmflexframegen.c b/src/framing/src/ofdmflexframegen.c index 52a7a7fef0..90191a8144 100644 --- a/src/framing/src/ofdmflexframegen.c +++ b/src/framing/src/ofdmflexframegen.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2021 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -157,10 +157,12 @@ ofdmflexframegen ofdmflexframegen_create(unsigned int _M, ofdmflexframegenprops_s * _fgprops) { // validate input - if (_M < 2) - return liquid_error_config("ofdmflexframegen_create(), number of subcarriers must be at least 2"); + if (_M < 8) + return liquid_error_config("ofdmflexframegen_create(), number of subcarriers must be at least 8"); if (_M % 2) return liquid_error_config("ofdmflexframegen_create(), number of subcarriers must be even"); + if (_cp_len > _M) + return liquid_error_config("ofdmflexframegen_create(), cyclic prefix length cannot exceed number of subcarriers"); ofdmflexframegen q = (ofdmflexframegen) malloc(sizeof(struct ofdmflexframegen_s)); q->M = _M; // number of subcarriers diff --git a/src/framing/src/ofdmflexframesync.c b/src/framing/src/ofdmflexframesync.c index a18165eed2..44f9fcedc9 100644 --- a/src/framing/src/ofdmflexframesync.c +++ b/src/framing/src/ofdmflexframesync.c @@ -152,7 +152,7 @@ ofdmflexframesync ofdmflexframesync_create(unsigned int _M, // validate input if (_M < 8) - return liquid_error_config("ofdmflexframesync_create(), less than 8 subcarriers"); + return liquid_error_config("ofdmflexframesync_create(), number of subcarriers must be at least 8"); if (_M % 2) return liquid_error_config("ofdmflexframesync_create(), number of subcarriers must be even"); if (_cp_len > _M) diff --git a/src/framing/src/qdetector_cccf.c b/src/framing/src/qdetector.proto.c similarity index 75% rename from src/framing/src/qdetector_cccf.c rename to src/framing/src/qdetector.proto.c index f520b4a283..5c13f45eb0 100644 --- a/src/framing/src/qdetector_cccf.c +++ b/src/framing/src/qdetector.proto.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,22 +35,22 @@ #define DEBUG_QDETECTOR_FILENAME "qdetector_cccf_debug.m" // seek signal (initial detection) -int qdetector_cccf_execute_seek(qdetector_cccf _q, float complex _x); +int QDETECTOR(_execute_seek)(QDETECTOR() _q, TI _x); // align signal in time, compute offset estimates -int qdetector_cccf_execute_align(qdetector_cccf _q, float complex _x); +int QDETECTOR(_execute_align)(QDETECTOR() _q, TI _x); // main object definition -struct qdetector_cccf_s { +struct QDETECTOR(_s) { unsigned int s_len; // template (time) length: k * (sequence_len + 2*m) - float complex * s; // template (time), [size: s_len x 1] - float complex * S; // template (freq), [size: nfft x 1] + TI * s; // template (time), [size: s_len x 1] + TI * S; // template (freq), [size: nfft x 1] float s2_sum; // sum{ s^2 } - float complex * buf_time_0; // time-domain buffer (FFT) - float complex * buf_freq_0; // frequence-domain buffer (FFT) - float complex * buf_freq_1; // frequence-domain buffer (IFFT) - float complex * buf_time_1; // time-domain buffer (IFFT) + TI * buf_time_0; // time-domain buffer (FFT) + TI * buf_freq_0; // frequence-domain buffer (FFT) + TI * buf_freq_1; // frequence-domain buffer (IFFT) + TI * buf_time_1; // time-domain buffer (IFFT) unsigned int nfft; // fft size FFT_PLAN fft; // FFT object: buf_time_0 > buf_freq_0 FFT_PLAN ifft; // IFFT object: buf_freq_1 > buf_freq_1 @@ -80,38 +80,38 @@ struct qdetector_cccf_s { // create detector with generic sequence // _s : sample sequence // _s_len : length of sample sequence -qdetector_cccf qdetector_cccf_create(float complex * _s, - unsigned int _s_len) +QDETECTOR() QDETECTOR(_create)(TI * _s, + unsigned int _s_len) { // validate input if (_s_len == 0) - return liquid_error_config("qdetector_cccf_create(), sequence length cannot be zero"); + return liquid_error_config("QDETECTOR(_create)(), sequence length cannot be zero"); // allocate memory for main object and set internal properties - qdetector_cccf q = (qdetector_cccf) malloc(sizeof(struct qdetector_cccf_s)); + QDETECTOR() q = (QDETECTOR()) malloc(sizeof(struct QDETECTOR(_s))); q->s_len = _s_len; // allocate memory and copy sequence - q->s = (float complex*) malloc(q->s_len * sizeof(float complex)); - memmove(q->s, _s, q->s_len*sizeof(float complex)); + q->s = (TI*) malloc(q->s_len * sizeof(TI)); + memmove(q->s, _s, q->s_len*sizeof(TI)); q->s2_sum = liquid_sumsqcf(q->s, q->s_len); // compute sum{ s^2 } // prepare transforms q->nfft = 1 << liquid_nextpow2( (unsigned int)( 2 * q->s_len ) ); // NOTE: must be even - q->buf_time_0 = (float complex*) FFT_MALLOC(q->nfft * sizeof(float complex)); - q->buf_freq_0 = (float complex*) FFT_MALLOC(q->nfft * sizeof(float complex)); - q->buf_freq_1 = (float complex*) FFT_MALLOC(q->nfft * sizeof(float complex)); - q->buf_time_1 = (float complex*) FFT_MALLOC(q->nfft * sizeof(float complex)); + q->buf_time_0 = (TI*) FFT_MALLOC(q->nfft * sizeof(TI)); + q->buf_freq_0 = (TI*) FFT_MALLOC(q->nfft * sizeof(TI)); + q->buf_freq_1 = (TI*) FFT_MALLOC(q->nfft * sizeof(TI)); + q->buf_time_1 = (TI*) FFT_MALLOC(q->nfft * sizeof(TI)); q->fft = FFT_CREATE_PLAN(q->nfft, q->buf_time_0, q->buf_freq_0, FFT_DIR_FORWARD, 0); q->ifft = FFT_CREATE_PLAN(q->nfft, q->buf_freq_1, q->buf_time_1, FFT_DIR_BACKWARD, 0); // create frequency-domain template by taking nfft-point transform on 's', storing in 'S' - q->S = (float complex*) malloc(q->nfft * sizeof(float complex)); - memset(q->buf_time_0, 0x00, q->nfft*sizeof(float complex)); - memmove(q->buf_time_0, q->s, q->s_len*sizeof(float complex)); + q->S = (TI*) malloc(q->nfft * sizeof(TI)); + memset(q->buf_time_0, 0x00, q->nfft*sizeof(TI)); + memmove(q->buf_time_0, q->s, q->s_len*sizeof(TI)); FFT_EXECUTE(q->fft); - memmove(q->S, q->buf_freq_0, q->nfft*sizeof(float complex)); + memmove(q->S, q->buf_freq_0, q->nfft*sizeof(TI)); // reset state variables q->counter = q->nfft/2; @@ -120,7 +120,7 @@ qdetector_cccf qdetector_cccf_create(float complex * _s, q->x2_sum_1 = 0.0f; q->state = QDETECTOR_STATE_SEEK; q->frame_detected = 0; - memset(q->buf_time_0, 0x00, q->nfft*sizeof(float complex)); + memset(q->buf_time_0, 0x00, q->nfft*sizeof(TI)); // reset estimates q->rxy = 0.0f; @@ -129,8 +129,8 @@ qdetector_cccf qdetector_cccf_create(float complex * _s, q->dphi_hat = 0.0f; q->phi_hat = 0.0f; - qdetector_cccf_set_threshold(q,0.5f); - qdetector_cccf_set_range (q,0.3f); // set initial range for higher detection + QDETECTOR(_set_threshold)(q,0.5f); + QDETECTOR(_set_range )(q,0.3f); // set initial range for higher detection // return object return q; @@ -144,26 +144,26 @@ qdetector_cccf qdetector_cccf_create(float complex * _s, // _k : samples/symbol // _m : filter delay // _beta : excess bandwidth factor -qdetector_cccf qdetector_cccf_create_linear(float complex * _sequence, - unsigned int _sequence_len, - int _ftype, - unsigned int _k, - unsigned int _m, - float _beta) +QDETECTOR() QDETECTOR(_create_linear)(TI * _sequence, + unsigned int _sequence_len, + int _ftype, + unsigned int _k, + unsigned int _m, + float _beta) { // validate input if (_sequence_len == 0) - return liquid_error_config("qdetector_cccf_create_linear(), sequence length cannot be zero"); + return liquid_error_config("QDETECTOR(_create_linear)(), sequence length cannot be zero"); if (_k < 2 || _k > 80) - return liquid_error_config("qdetector_cccf_create_linear(), samples per symbol must be in [2,80]"); + return liquid_error_config("QDETECTOR(_create_linear)(), samples per symbol must be in [2,80]"); if (_m < 1 || _m > 100) - return liquid_error_config("qdetector_cccf_create_linear(), filter delay must be in [1,100]"); + return liquid_error_config("QDETECTOR(_create_linear)(), filter delay must be in [1,100]"); if (_beta < 0.0f || _beta > 1.0f) - return liquid_error_config("qdetector_cccf_create_linear(), excess bandwidth factor must be in [0,1]"); + return liquid_error_config("QDETECTOR(_create_linear)(), excess bandwidth factor must be in [0,1]"); // create time-domain template unsigned int s_len = _k * (_sequence_len + 2*_m); - float complex * s = (float complex*) malloc(s_len * sizeof(float complex)); + TI * s = (TI*) malloc(s_len * sizeof(TI)); firinterp_crcf interp = firinterp_crcf_create_prototype(_ftype, _k, _m, _beta, 0); unsigned int i; for (i=0; i<_sequence_len + 2*_m; i++) @@ -171,7 +171,7 @@ qdetector_cccf qdetector_cccf_create_linear(float complex * _sequence, firinterp_crcf_destroy(interp); // create main object - qdetector_cccf q = qdetector_cccf_create(s, s_len); + QDETECTOR() q = QDETECTOR(_create)(s, s_len); // free allocated temporary array free(s); @@ -186,25 +186,25 @@ qdetector_cccf qdetector_cccf_create_linear(float complex * _sequence, // _k : samples/symbol // _m : filter delay // _beta : excess bandwidth factor -qdetector_cccf qdetector_cccf_create_gmsk(unsigned char * _sequence, - unsigned int _sequence_len, - unsigned int _k, - unsigned int _m, - float _beta) +QDETECTOR() QDETECTOR(_create_gmsk)(unsigned char * _sequence, + unsigned int _sequence_len, + unsigned int _k, + unsigned int _m, + float _beta) { // validate input if (_sequence_len == 0) - return liquid_error_config("qdetector_cccf_create_gmsk(), sequence length cannot be zero"); + return liquid_error_config("QDETECTOR(_create_gmsk)(), sequence length cannot be zero"); if (_k < 2 || _k > 80) - return liquid_error_config("qdetector_cccf_create_gmsk(), samples per symbol must be in [2,80]"); + return liquid_error_config("QDETECTOR(_create_gmsk)(), samples per symbol must be in [2,80]"); if (_m < 1 || _m > 100) - return liquid_error_config("qdetector_cccf_create_gmsk(), filter delay must be in [1,100]"); + return liquid_error_config("QDETECTOR(_create_gmsk)(), filter delay must be in [1,100]"); if (_beta < 0.0f || _beta > 1.0f) - return liquid_error_config("qdetector_cccf_create_gmsk(), excess bandwidth factor must be in [0,1]"); + return liquid_error_config("QDETECTOR(_create_gmsk)(), excess bandwidth factor must be in [0,1]"); // create time-domain template using GMSK modem unsigned int s_len = _k * (_sequence_len + 2*_m); - float complex * s = (float complex*) malloc(s_len * sizeof(float complex)); + TI * s = (TI*) malloc(s_len * sizeof(TI)); gmskmod mod = gmskmod_create(_k, _m, _beta); unsigned int i; for (i=0; i<_sequence_len + 2*_m; i++) @@ -212,7 +212,7 @@ qdetector_cccf qdetector_cccf_create_gmsk(unsigned char * _sequence, gmskmod_destroy(mod); // create main object - qdetector_cccf q = qdetector_cccf_create(s, s_len); + QDETECTOR() q = QDETECTOR(_create)(s, s_len); // free allocated temporary array free(s); @@ -230,28 +230,28 @@ qdetector_cccf qdetector_cccf_create_gmsk(unsigned char * _sequence, // _m : filter delay // _beta : filter bandwidth parameter, _beta > 0 // _type : filter type (e.g. LIQUID_CPFSK_SQUARE) -qdetector_cccf qdetector_cccf_create_cpfsk(unsigned char * _sequence, - unsigned int _sequence_len, - unsigned int _bps, - float _h, - unsigned int _k, - unsigned int _m, - float _beta, - int _type) +QDETECTOR() QDETECTOR(_create_cpfsk)(unsigned char * _sequence, + unsigned int _sequence_len, + unsigned int _bps, + float _h, + unsigned int _k, + unsigned int _m, + float _beta, + int _type) { // validate input if (_sequence_len == 0) - return liquid_error_config("qdetector_cccf_create_cpfsk(), sequence length cannot be zero"); + return liquid_error_config("QDETECTOR(_create_cpfsk)(), sequence length cannot be zero"); if (_k < 2 || _k > 80) - return liquid_error_config("qdetector_cccf_create_cpfsk(), samples per symbol must be in [2,80]"); + return liquid_error_config("QDETECTOR(_create_cpfsk)(), samples per symbol must be in [2,80]"); if (_m < 1 || _m > 100) - return liquid_error_config("qdetector_cccf_create_cpfsk(), filter delay must be in [1,100]"); + return liquid_error_config("QDETECTOR(_create_cpfsk)(), filter delay must be in [1,100]"); if (_beta < 0.0f || _beta > 1.0f) - return liquid_error_config("qdetector_cccf_create_cpfsk(), excess bandwidth factor must be in [0,1]"); + return liquid_error_config("QDETECTOR(_create_cpfsk)(), excess bandwidth factor must be in [0,1]"); // create time-domain template using GMSK modem - unsigned int s_len = _k * (_sequence_len + 2*_m); - float complex * s = (float complex*) malloc(s_len * sizeof(float complex)); + unsigned int s_len = _k * (_sequence_len + 2*_m); + TI * s = (TI*) malloc(s_len * sizeof(TI)); cpfskmod mod = cpfskmod_create(_bps, _h, _k, _m, _beta, _type); unsigned int i; for (i=0; i<_sequence_len + 2*_m; i++) @@ -259,7 +259,7 @@ qdetector_cccf qdetector_cccf_create_cpfsk(unsigned char * _sequence, cpfskmod_destroy(mod); // create main object - qdetector_cccf q = qdetector_cccf_create(s, s_len); + QDETECTOR() q = QDETECTOR(_create)(s, s_len); // free allocated temporary array free(s); @@ -269,20 +269,20 @@ qdetector_cccf qdetector_cccf_create_cpfsk(unsigned char * _sequence, } // copy object -qdetector_cccf qdetector_cccf_copy(qdetector_cccf q_orig) +QDETECTOR() QDETECTOR(_copy)(QDETECTOR() q_orig) { // validate input if (q_orig == NULL) - return liquid_error_config("qdetector_%s_copy(), object cannot be NULL", "cccf"); + return liquid_error_config("qdetector_%s_copy(), object cannot be NULL", EXTENSION_FULL); // create new object from internal sequence - qdetector_cccf q_copy = qdetector_cccf_create(q_orig->s, q_orig->s_len); + QDETECTOR() q_copy = QDETECTOR(_create)(q_orig->s, q_orig->s_len); // copy buffer contents - memmove(q_copy->buf_time_0, q_orig->buf_time_0, q_orig->nfft*sizeof(float complex)); - memmove(q_copy->buf_freq_0, q_orig->buf_freq_0, q_orig->nfft*sizeof(float complex)); - memmove(q_copy->buf_time_1, q_orig->buf_time_1, q_orig->nfft*sizeof(float complex)); - memmove(q_copy->buf_freq_1, q_orig->buf_freq_1, q_orig->nfft*sizeof(float complex)); + memmove(q_copy->buf_time_0, q_orig->buf_time_0, q_orig->nfft*sizeof(TI)); + memmove(q_copy->buf_freq_0, q_orig->buf_freq_0, q_orig->nfft*sizeof(TI)); + memmove(q_copy->buf_time_1, q_orig->buf_time_1, q_orig->nfft*sizeof(TI)); + memmove(q_copy->buf_freq_1, q_orig->buf_freq_1, q_orig->nfft*sizeof(TI)); // copy internal state q_copy->counter = q_orig->counter; @@ -300,7 +300,7 @@ qdetector_cccf qdetector_cccf_copy(qdetector_cccf q_orig) return q_copy; } -int qdetector_cccf_destroy(qdetector_cccf _q) +int QDETECTOR(_destroy)(QDETECTOR() _q) { // free allocated arrays free(_q->s); @@ -319,9 +319,9 @@ int qdetector_cccf_destroy(qdetector_cccf _q) return LIQUID_OK; } -int qdetector_cccf_print(qdetector_cccf _q) +int QDETECTOR(_print)(QDETECTOR() _q) { - printf("qdetector_cccf:\n"); + printf("qdetector_%s:\n", EXTENSION_FULL); printf(" template length (time): %-u\n", _q->s_len); printf(" FFT size : %-u\n", _q->nfft); printf(" search range (bins) : %-d\n", _q->range); @@ -330,23 +330,22 @@ int qdetector_cccf_print(qdetector_cccf _q) return LIQUID_OK; } -int qdetector_cccf_reset(qdetector_cccf _q) +int QDETECTOR(_reset)(QDETECTOR() _q) { return LIQUID_OK; } -void * qdetector_cccf_execute(qdetector_cccf _q, - float complex _x) +void * QDETECTOR(_execute)(QDETECTOR() _q, TI _x) { switch (_q->state) { case QDETECTOR_STATE_SEEK: // seek signal - qdetector_cccf_execute_seek(_q, _x); + QDETECTOR(_execute_seek)(_q, _x); break; case QDETECTOR_STATE_ALIGN: // align signal - qdetector_cccf_execute_align(_q, _x); + QDETECTOR(_execute_align)(_q, _x); break; } @@ -364,13 +363,13 @@ void * qdetector_cccf_execute(qdetector_cccf _q, } // get detection threshold -float qdetector_cccf_get_threshold(qdetector_cccf _q) +float QDETECTOR(_get_threshold)(QDETECTOR() _q) { return _q->threshold; } // set detection threshold (should be between 0 and 1, good starting point is 0.5) -int qdetector_cccf_set_threshold(qdetector_cccf _q, +int QDETECTOR(_set_threshold)(QDETECTOR() _q, float _threshold) { if (_threshold <= 0.0f || _threshold > 2.0f) @@ -382,7 +381,7 @@ int qdetector_cccf_set_threshold(qdetector_cccf _q, } // set carrier offset search range -int qdetector_cccf_set_range(qdetector_cccf _q, +int QDETECTOR(_set_range)(QDETECTOR() _q, float _dphi_max) { if (_dphi_max < 0.0f || _dphi_max > 0.5f) @@ -396,49 +395,49 @@ int qdetector_cccf_set_range(qdetector_cccf _q, } // get sequence length -unsigned int qdetector_cccf_get_seq_len(qdetector_cccf _q) +unsigned int QDETECTOR(_get_seq_len)(QDETECTOR() _q) { return _q->s_len; } // pointer to sequence -const void * qdetector_cccf_get_sequence(qdetector_cccf _q) +const void * QDETECTOR(_get_sequence)(QDETECTOR() _q) { return (const void*) _q->s; } // buffer length -unsigned int qdetector_cccf_get_buf_len(qdetector_cccf _q) +unsigned int QDETECTOR(_get_buf_len)(QDETECTOR() _q) { return _q->nfft; } // correlator output -float qdetector_cccf_get_rxy(qdetector_cccf _q) +float QDETECTOR(_get_rxy)(QDETECTOR() _q) { return _q->rxy; } // fractional timing offset estimate -float qdetector_cccf_get_tau(qdetector_cccf _q) +float QDETECTOR(_get_tau)(QDETECTOR() _q) { return _q->tau_hat; } // channel gain -float qdetector_cccf_get_gamma(qdetector_cccf _q) +float QDETECTOR(_get_gamma)(QDETECTOR() _q) { return _q->gamma_hat; } // carrier frequency offset estimate -float qdetector_cccf_get_dphi(qdetector_cccf _q) +float QDETECTOR(_get_dphi)(QDETECTOR() _q) { return _q->dphi_hat; } // carrier phase offset estimate -float qdetector_cccf_get_phi(qdetector_cccf _q) +float QDETECTOR(_get_phi)(QDETECTOR() _q) { return _q->phi_hat; } @@ -449,8 +448,7 @@ float qdetector_cccf_get_phi(qdetector_cccf _q) // // seek signal (initial detection) -int qdetector_cccf_execute_seek(qdetector_cccf _q, - float complex _x) +int QDETECTOR(_execute_seek)(QDETECTOR() _q, TI _x) { // write sample to buffer and increment counter _q->buf_time_0[_q->counter++] = _x; @@ -554,7 +552,7 @@ int qdetector_cccf_execute_seek(qdetector_cccf _q, // TODO: check for edge case where rxy_index is zero (signal already aligned) // copy last part of fft input buffer to front - memmove(_q->buf_time_0, _q->buf_time_0 + rxy_index, (_q->nfft - rxy_index)*sizeof(float complex)); + memmove(_q->buf_time_0, _q->buf_time_0 + rxy_index, (_q->nfft - rxy_index)*sizeof(TI)); _q->counter = _q->nfft - rxy_index; return LIQUID_OK; @@ -564,7 +562,7 @@ int qdetector_cccf_execute_seek(qdetector_cccf _q, #endif // copy last half of fft input buffer to front - memmove(_q->buf_time_0, _q->buf_time_0 + _q->nfft/2, (_q->nfft/2)*sizeof(float complex)); + memmove(_q->buf_time_0, _q->buf_time_0 + _q->nfft/2, (_q->nfft/2)*sizeof(TI)); // swap accumulated signal levels _q->x2_sum_0 = _q->x2_sum_1; @@ -573,8 +571,7 @@ int qdetector_cccf_execute_seek(qdetector_cccf _q, } // align signal in time, compute offset estimates -int qdetector_cccf_execute_align(qdetector_cccf _q, - float complex _x) +int QDETECTOR(_execute_align)(QDETECTOR() _q, TI _x) { // write sample to buffer and increment counter _q->buf_time_0[_q->counter++] = _x; @@ -611,7 +608,7 @@ int qdetector_cccf_execute_align(qdetector_cccf _q, // TODO: revise estimate of rxy here // copy buffer to preserve data integrity - memmove(_q->buf_time_1, _q->buf_time_0, _q->nfft*sizeof(float complex)); + memmove(_q->buf_time_1, _q->buf_time_0, _q->nfft*sizeof(TI)); // estimate carrier frequency offset for (i=0; i<_q->nfft; i++) @@ -670,7 +667,7 @@ int qdetector_cccf_execute_align(qdetector_cccf _q, // METHOD 2: compute metric by de-rotating signal and measuring resulting phase // NOTE: this is possibly more accurate than the above method but might also // be more computationally complex - float complex metric = 0; + TI metric = 0; for (i=0; i<_q->s_len; i++) metric += _q->buf_time_0[i] * cexpf(-_Complex_I*_q->dphi_hat*i); //printf("metric : %12.8f <%12.8f>\n", cabsf(metric), cargf(metric)); @@ -696,7 +693,7 @@ int qdetector_cccf_execute_align(qdetector_cccf _q, // reset state // copy saved buffer state (last half of buf_time_1 to front half of buf_time_0) - memmove(_q->buf_time_0, _q->buf_time_1 + _q->nfft/2, (_q->nfft/2)*sizeof(float complex)); + memmove(_q->buf_time_0, _q->buf_time_1 + _q->nfft/2, (_q->nfft/2)*sizeof(TI)); _q->state = QDETECTOR_STATE_SEEK; _q->x2_sum_0 = liquid_sumsqcf(_q->buf_time_0, _q->nfft/2); _q->x2_sum_1 = 0; diff --git a/src/framing/src/qdsync.proto.c b/src/framing/src/qdsync.proto.c new file mode 100644 index 0000000000..06f8fc0a0a --- /dev/null +++ b/src/framing/src/qdsync.proto.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2007 - 2023 Joseph Gaeddert + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Frame detector and synchronizer; uses a novel correlation method to +// detect a synchronization pattern, estimate carrier frequency and +// phase offsets as well as timing phase, then correct for these +// impairments in a simple interface suitable for custom frame recovery. + +#include +#include +#include +#include +#include + +#include "liquid.internal.h" + +// push samples through detection stage +int QDSYNC(_execute_detect)(QDSYNC() _q, float complex _x); + +// step receiver mixer, matched filter, decimator +// _q : frame synchronizer +// _x : input sample +int QDSYNC(_step)(QDSYNC() _q, float complex _x); + +// append sample to output buffer +int QDSYNC(_buf_append)(QDSYNC() _q, float complex _x); + +// main object definition +struct QDSYNC(_s) { + unsigned int seq_len; // preamble sequence length + int ftype; // filter type + unsigned int k; // samples per symbol + unsigned int m; // filter semi-length + float beta; // excess bandwidth factor + + QDSYNC(_callback) callback; // user-defined callback function + void * context; // user-defined context object + QDETECTOR() detector; // detector + + // status variables + enum { + QDSYNC_STATE_DETECT=0, // detect frame + QDSYNC_STATE_SYNC, // apply carrier offset correction and matched filter + } state; // frame synchronization state + unsigned int symbol_counter;// counter: total number of symbols received including preamble sequence + + nco_crcf mixer; // coarse carrier frequency recovery + + // timing recovery objects, states + firpfb_crcf mf; // matched filter/decimator + unsigned int npfb; // number of filters in symsync + int mf_counter; // matched filter output timer + unsigned int pfb_index; // filterbank index + + // symbol buffer + unsigned int buf_out_len;// output buffer length + float complex * buf_out; // output buffer + unsigned int buf_out_counter; // output counter +}; + +// create detector with generic sequence +QDSYNC() QDSYNC(_create_linear)(TI * _seq, + unsigned int _seq_len, + int _ftype, + unsigned int _k, + unsigned int _m, + float _beta, + QDSYNC(_callback) _callback, + void * _context) +{ + // validate input + if (_seq_len == 0) + return liquid_error_config("QDSYNC(_create)(), sequence length cannot be zero"); + + // allocate memory for main object and set internal properties + QDSYNC() q = (QDSYNC()) malloc(sizeof(struct QDSYNC(_s))); + q->seq_len = _seq_len; + q->ftype = _ftype; + q->k = _k; + q->m = _m; + q->beta = _beta; + + // create detector + q->detector = QDETECTOR(_create_linear)(_seq, _seq_len, _ftype, _k, _m, _beta); + + // create down-coverters for carrier phase tracking + q->mixer = nco_crcf_create(LIQUID_NCO); + + // create symbol timing recovery filters + q->npfb = 256; // number of filters in the bank + q->mf = firpfb_crcf_create_rnyquist(q->ftype, q->npfb, q->k, q->m, q->beta); + + // allocate buffer for storing output samples + q->buf_out_len = 64; // user can re-size this later + q->buf_out = (float complex*) malloc(q->buf_out_len*sizeof(float complex)); + + // set callback and context values + QDSYNC(_set_callback)(q, _callback); + QDSYNC(_set_context )(q, _context ); + + // reset and return object + QDSYNC(_reset)(q); + return q; +} + +// copy object +QDSYNC() QDSYNC(_copy)(QDSYNC() q_orig) +{ + // validate input + if (q_orig == NULL) + return liquid_error_config("qdetector_%s_copy(), object cannot be NULL", "cccf"); + + // create new object and copy base parameters + QDSYNC() q_copy = (QDSYNC())malloc(sizeof(struct QDSYNC(_s))); + memmove(q_copy, q_orig, sizeof(struct QDSYNC(_s))); + + // set callback and userdata fields + q_copy->callback = q_orig->callback; + q_copy->context = q_orig->context; + + // copy sub-objects + q_copy->detector = QDETECTOR(_copy)(q_orig->detector); + q_copy->mixer = nco_crcf_copy (q_orig->mixer); + q_copy->mf = firpfb_crcf_copy (q_orig->mf); + + // copy memory in new allocation + q_copy->buf_out = (float complex*)liquid_malloc_copy(q_orig->buf_out, q_orig->buf_out_len, sizeof(float complex)); + + // return new object + return q_copy; +} + +int QDSYNC(_destroy)(QDSYNC() _q) +{ + // destroy internal objects + QDETECTOR(_destroy)(_q->detector); + nco_crcf_destroy(_q->mixer); + firpfb_crcf_destroy(_q->mf); + + // free output buffer + free(_q->buf_out); + + // free main object memory + free(_q); + return LIQUID_OK; +} + +int QDSYNC(_reset)(QDSYNC() _q) +{ + QDETECTOR(_reset)(_q->detector); + _q->state = QDSYNC_STATE_DETECT; + _q->symbol_counter = 0; + _q->buf_out_counter = 0; + firpfb_crcf_reset(_q->mf); + return LIQUID_OK; +} + +int QDSYNC(_print)(QDSYNC() _q) +{ + printf("\n"); + return LIQUID_OK; +} + +// get detection threshold +float QDSYNC(_get_threshold)(QDSYNC() _q) +{ + return QDETECTOR(_get_threshold)(_q->detector); +} + +// set detection threshold +int QDSYNC(_set_threshold)(QDSYNC() _q, float _threshold) +{ + return QDETECTOR(_set_threshold)(_q->detector, _threshold); +} + +// set carrier offset search range +int QDSYNC(_set_range)(QDSYNC() _q, float _dphi_max) +{ + return QDETECTOR(_set_range)(_q->detector, _dphi_max); +} + +// set callback method +int QDSYNC(_set_callback)(QDSYNC() _q, QDSYNC(_callback) _callback) +{ + _q->callback = _callback; + return LIQUID_OK; +} + +// set context value +int QDSYNC(_set_context)(QDSYNC() _q, void * _context) +{ + _q->context = _context; + return LIQUID_OK; +} + +// Set callback buffer size (the number of symbol provided to the callback +// whenever it is invoked). +int QDSYNC(_set_buf_len)(QDSYNC() _q, unsigned int _buf_len) +{ + if (_buf_len == 0) + return liquid_error(LIQUID_EICONFIG,"QDSYNC(_set_buf_len)(), buffer length must be greater than 0"); + + // check current state + if (_q->buf_out_counter < _buf_len) { + // buffer might not be empty, but we aren't resizing within this space; + // ok to resize so long as old samples are copied + _q->buf_out_len = _buf_len; + float complex * buf_new = (float complex*)realloc(_q->buf_out, _q->buf_out_len*sizeof(float complex)); + if (buf_new == NULL) + return liquid_error(LIQUID_EIMEM,"QDSYNC(_set_buf_len)(), could not allocate %u samples", _buf_len); + _q->buf_out = buf_new; + } else { + // we are shrinking the buffer below the number of samples it currently + // holds; invoke the callback as many times as needed to reduce its size + unsigned int index = 0; + while (_q->buf_out_counter >= _buf_len) { + if (_q->callback != NULL) + _q->callback(_q->buf_out + index, _buf_len, _q->context); + + // adjust counters + index += _buf_len; + _q->buf_out_counter -= _buf_len; + } + + // copy old values to front of buffer + memmove(_q->buf_out, _q->buf_out + index, _q->buf_out_counter*sizeof(float complex)); + + // now resize the buffer appropriately + _q->buf_out_len = _buf_len; + float complex * buf_new = (float complex*)realloc(_q->buf_out, _q->buf_out_len*sizeof(float complex)); + if (buf_new == NULL) + return liquid_error(LIQUID_EIMEM,"QDSYNC(_set_buf_len)(), could not allocate %u samples", _buf_len); + _q->buf_out = buf_new; + } + return LIQUID_OK; +} + +// execute synchronizer on a block of samples +int QDSYNC(_execute)(QDSYNC() _q, + TI * _buf, + unsigned int _buf_len) +{ + unsigned int i; + for (i=0; i<_buf_len; i++) { + switch (_q->state) { + case QDSYNC_STATE_DETECT: + // detect frame (look for p/n sequence) + QDSYNC(_execute_detect)(_q, _buf[i]); + break; + case QDSYNC_STATE_SYNC: + // receive preamble sequence symbols + QDSYNC(_step)(_q, _buf[i]); + break; + default: + return liquid_error(LIQUID_EINT,"QDSYNC(_exeucte)(), unknown/unsupported state"); + } + } + return LIQUID_OK; +} + +int QDSYNC(_is_open)(QDSYNC() _q) +{ + return _q->state == QDSYNC_STATE_DETECT ? 0 : 1; +} + +// correlator output +float QDSYNC(_get_rxy)(QDSYNC() _q) +{ + return QDETECTOR(_get_rxy)(_q->detector); +} + +// fractional timing offset estimate +float QDSYNC(_get_tau)(QDSYNC() _q) +{ + return QDETECTOR(_get_tau)(_q->detector); +} + +// channel gain +float QDSYNC(_get_gamma)(QDSYNC() _q) +{ + return QDETECTOR(_get_gamma)(_q->detector); +} + +// carrier frequency offset estimate +float QDSYNC(_get_dphi)(QDSYNC() _q) +{ + return QDETECTOR(_get_dphi)(_q->detector); +} + +// carrier phase offset estimate +float QDSYNC(_get_phi)(QDSYNC() _q) +{ + return QDETECTOR(_get_phi)(_q->detector); +} + +// +// internal methods +// + +// execute synchronizer, seeking preamble sequence +// _q : frame synchronizer object +// _x : input sample +// _sym : demodulated symbol +int QDSYNC(_execute_detect)(QDSYNC() _q, + TI _x) +{ + // push through pre-demod synchronizer + float complex * v = QDETECTOR(_execute)(_q->detector, _x); + + // check if frame has been detected + if (v != NULL) { + // get estimates + float tau_hat = QDETECTOR(_get_tau) (_q->detector); + float gamma_hat = QDETECTOR(_get_gamma)(_q->detector); + float dphi_hat = QDETECTOR(_get_dphi) (_q->detector); + float phi_hat = QDETECTOR(_get_phi) (_q->detector); + + // set appropriate filterbank index + _q->mf_counter = _q->k - 2; + _q->pfb_index = 0; + int index = (int)(tau_hat * _q->npfb); + if (index < 0) { + _q->mf_counter++; + index += _q->npfb; + } + _q->pfb_index = index; + //printf("* qdsync detected! tau:%6.3f, dphi:%12.4e, phi:%6.3f, gamma:%6.2f dB, mf:%u, pfb idx:%u\n", + // tau_hat, dphi_hat, phi_hat, 20*log10f(gamma_hat), _q->mf_counter, _q->pfb_index); + + // output filter scale + firpfb_crcf_set_scale(_q->mf, 1.0f / (_q->k * gamma_hat)); + + // set frequency/phase of mixer + nco_crcf_set_frequency(_q->mixer, dphi_hat); + nco_crcf_set_phase (_q->mixer, phi_hat ); + + // update state + _q->state = QDSYNC_STATE_SYNC; + + // run buffered samples through synchronizer + unsigned int buf_len = QDETECTOR(_get_buf_len)(_q->detector); + QDSYNC(_execute)(_q, v, buf_len); + } + return LIQUID_OK; +} + +// step receiver mixer, matched filter, decimator +// _q : frame synchronizer +// _x : input sample +// _y : output symbol +int QDSYNC(_step)(QDSYNC() _q, TI _x) +{ + // mix sample down + float complex v; + nco_crcf_mix_down(_q->mixer, _x, &v); + nco_crcf_step (_q->mixer); + + // push sample into filterbank + firpfb_crcf_push (_q->mf, v); + firpfb_crcf_execute(_q->mf, _q->pfb_index, &v); + + // increment counter to determine if sample is available + _q->mf_counter++; + int sample_available = (_q->mf_counter >= _q->k-1) ? 1 : 0; + + // set output sample if available + if (sample_available) { + // decrement counter by k=2 samples/symbol + _q->mf_counter -= _q->k; + + // append to output + QDSYNC(_buf_append)(_q, v); + } + + // return flag + return LIQUID_OK; +} + +// append sample to output buffer +int QDSYNC(_buf_append)(QDSYNC() _q, TO _x) +{ + // account for filter delay + _q->symbol_counter++; + if (_q->symbol_counter <= 2*_q->m) + return LIQUID_OK; + + // append sample to end of buffer + _q->buf_out[_q->buf_out_counter] = _x; + _q->buf_out_counter++; + + // check if buffer is full + if (_q->buf_out_counter == _q->buf_out_len) { + // reset counter + _q->buf_out_counter = 0; + + // invoke callback + if (_q->callback != NULL) { + int rc = _q->callback(_q->buf_out, _q->buf_out_len, _q->context); + if (rc) + return QDSYNC(_reset)(_q); + } + } + return LIQUID_OK; +} + diff --git a/src/framing/src/qpacketmodem.c b/src/framing/src/qpacketmodem.proto.c similarity index 78% rename from src/framing/src/qpacketmodem.c rename to src/framing/src/qpacketmodem.proto.c index 6b5a3e4337..6f4a1ef9e6 100644 --- a/src/framing/src/qpacketmodem.c +++ b/src/framing/src/qpacketmodem.proto.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,9 +31,9 @@ #include "liquid.internal.h" -struct qpacketmodem_s { +struct QPACKETMODEM(_s) { // properties - modemcf mod_payload; // payload modulator/demodulator + MODEM() mod_payload; // payload modulator/demodulator packetizer p; // packet encoder/decoder unsigned int bits_per_symbol; // modulator bits/symbol unsigned int payload_dec_len; // number of decoded payload bytes @@ -47,13 +47,13 @@ struct qpacketmodem_s { }; // create packet encoder -qpacketmodem qpacketmodem_create() +QPACKETMODEM() QPACKETMODEM(_create)() { // allocate memory for main object - qpacketmodem q = (qpacketmodem) malloc(sizeof(struct qpacketmodem_s)); + QPACKETMODEM() q = (QPACKETMODEM()) malloc(sizeof(struct QPACKETMODEM(_s))); // create payload modem (initially QPSK, overridden by properties) - q->mod_payload = modemcf_create(LIQUID_MODEM_QPSK); + q->mod_payload = MODEM(_create)(LIQUID_MODEM_QPSK); q->bits_per_symbol = 2; // initial memory allocation for payload @@ -87,22 +87,22 @@ qpacketmodem qpacketmodem_create() } // copy object -qpacketmodem qpacketmodem_copy(qpacketmodem q_orig) +QPACKETMODEM() QPACKETMODEM(_copy)(QPACKETMODEM() q_orig) { // validate input if (q_orig == NULL) return liquid_error_config("qpacketmodem_copy(), object cannot be NULL"); // create new object - qpacketmodem q_copy = qpacketmodem_create(); + QPACKETMODEM() q_copy = QPACKETMODEM(_create)(); // configure identically as original unsigned int payload_len = q_orig->payload_dec_len; crc_scheme check = packetizer_get_crc (q_orig->p); fec_scheme fec0 = packetizer_get_fec0(q_orig->p); fec_scheme fec1 = packetizer_get_fec1(q_orig->p); - int ms = modemcf_get_scheme (q_orig->mod_payload); - qpacketmodem_configure(q_copy, payload_len, check, fec0, fec1, ms); + int ms = MODEM(_get_scheme )(q_orig->mod_payload); + QPACKETMODEM(_configure)(q_copy, payload_len, check, fec0, fec1, ms); // return new object return q_copy; @@ -110,11 +110,11 @@ qpacketmodem qpacketmodem_copy(qpacketmodem q_orig) // destroy object, freeing all internal arrays -int qpacketmodem_destroy(qpacketmodem _q) +int QPACKETMODEM(_destroy)(QPACKETMODEM() _q) { // free objects packetizer_destroy(_q->p); - modemcf_destroy(_q->mod_payload); + MODEM(_destroy)(_q->mod_payload); // free arrays free(_q->payload_enc); @@ -125,19 +125,19 @@ int qpacketmodem_destroy(qpacketmodem _q) } // reset object -int qpacketmodem_reset(qpacketmodem _q) +int QPACKETMODEM(_reset)(QPACKETMODEM() _q) { - return modemcf_reset(_q->mod_payload); + return MODEM(_reset)(_q->mod_payload); } // print object internals -int qpacketmodem_print(qpacketmodem _q) +int QPACKETMODEM(_print)(QPACKETMODEM() _q) { printf("qpacketmodem:\n"); printf(" check : %s\n", crc_scheme_str[packetizer_get_crc(_q->p)][1]); printf(" fec (inner) : %s\n", fec_scheme_str[packetizer_get_fec0(_q->p)][1]); printf(" fec (outer) : %s\n", fec_scheme_str[packetizer_get_fec1(_q->p)][1]); - printf(" modulation scheme : %s\n", modulation_types[modemcf_get_scheme(_q->mod_payload)].name); + printf(" modulation scheme : %s\n", modulation_types[MODEM(_get_scheme)(_q->mod_payload)].name); printf(" payload dec len : %u\n", _q->payload_dec_len); printf(" payload enc len : %u\n", _q->payload_enc_len); printf(" payload bit len : %u\n", _q->payload_bit_len); @@ -146,7 +146,7 @@ int qpacketmodem_print(qpacketmodem _q) } // -int qpacketmodem_configure(qpacketmodem _q, +int QPACKETMODEM(_configure)(QPACKETMODEM() _q, unsigned int _payload_len, crc_scheme _check, fec_scheme _fec0, @@ -157,8 +157,8 @@ int qpacketmodem_configure(qpacketmodem _q, _q->payload_dec_len = _payload_len; // recreate modem object and get new bits per symbol - _q->mod_payload = modemcf_recreate(_q->mod_payload, _ms); - _q->bits_per_symbol = modemcf_get_bps(_q->mod_payload); + _q->mod_payload = MODEM(_recreate)(_q->mod_payload, _ms); + _q->bits_per_symbol = MODEM(_get_bps)(_q->mod_payload); // recreate packetizer object and compute new encoded payload length _q->p = packetizer_recreate(_q->p, _q->payload_dec_len, _check, _fec0, _fec1); @@ -185,44 +185,44 @@ int qpacketmodem_configure(qpacketmodem _q, } // get length of encoded frame in symbols -unsigned int qpacketmodem_get_frame_len(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_frame_len)(QPACKETMODEM() _q) { return _q->payload_mod_len; } // get unencoded/decoded payload length (bytes) -unsigned int qpacketmodem_get_payload_len(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_payload_len)(QPACKETMODEM() _q) { // number of decoded payload bytes return _q->payload_dec_len; } -unsigned int qpacketmodem_get_crc(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_crc)(QPACKETMODEM() _q) { return packetizer_get_crc(_q->p); } -unsigned int qpacketmodem_get_fec0(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_fec0)(QPACKETMODEM() _q) { return packetizer_get_fec0(_q->p); } -unsigned int qpacketmodem_get_fec1(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_fec1)(QPACKETMODEM() _q) { return packetizer_get_fec1(_q->p); } -unsigned int qpacketmodem_get_modscheme(qpacketmodem _q) +unsigned int QPACKETMODEM(_get_modscheme)(QPACKETMODEM() _q) { - return modemcf_get_scheme(_q->mod_payload); + return MODEM(_get_scheme)(_q->mod_payload); } -float qpacketmodem_get_demodulator_phase_error(qpacketmodem _q) +float QPACKETMODEM(_get_demodulator_phase_error)(QPACKETMODEM() _q) { - return modemcf_get_demodulator_phase_error(_q->mod_payload); + return MODEM(_get_demodulator_phase_error)(_q->mod_payload); } -float qpacketmodem_get_demodulator_evm(qpacketmodem _q) +float QPACKETMODEM(_get_demodulator_evm)(QPACKETMODEM() _q) { return _q->evm; } @@ -231,7 +231,7 @@ float qpacketmodem_get_demodulator_evm(qpacketmodem _q) // _q : qpacketmodem object // _payload : unencoded payload bytes // _syms : encoded but un-modulated payload symbol indices -int qpacketmodem_encode_syms(qpacketmodem _q, +int QPACKETMODEM(_encode_syms)(qpacketmodem _q, const unsigned char * _payload, unsigned char * _syms) { @@ -256,7 +256,7 @@ int qpacketmodem_encode_syms(qpacketmodem _q, // _q : qpacketmodem object // _syms : received hard-decision symbol indices // _payload : recovered decoded payload bytes -int qpacketmodem_decode_syms(qpacketmodem _q, +int QPACKETMODEM(_decode_syms)(qpacketmodem _q, unsigned char * _syms, unsigned char * _payload) { @@ -276,7 +276,7 @@ int qpacketmodem_decode_syms(qpacketmodem _q, // _q : qpacketmodem object // _bits : received soft-decision bits // _payload : recovered decoded payload bytes -int qpacketmodem_decode_bits(qpacketmodem _q, +int QPACKETMODEM(_decode_bits)(qpacketmodem _q, unsigned char * _bits, unsigned char * _payload) { @@ -288,17 +288,17 @@ int qpacketmodem_decode_bits(qpacketmodem _q, // _q : qpacketmodem object // _payload : unencoded payload bytes // _frame : encoded/modulated payload symbols -int qpacketmodem_encode(qpacketmodem _q, +int QPACKETMODEM(_encode)(qpacketmodem _q, const unsigned char * _payload, - float complex * _frame) + TO * _frame) { // encode payload symbols into internal buffer - qpacketmodem_encode_syms(_q, _payload, _q->payload_mod); + QPACKETMODEM(_encode_syms)(_q, _payload, _q->payload_mod); // modulate symbols unsigned int i; for (i=0; i<_q->payload_mod_len; i++) - modemcf_modulate(_q->mod_payload, _q->payload_mod[i], &_frame[i]); + MODEM(_modulate)(_q->mod_payload, _q->payload_mod[i], &_frame[i]); return LIQUID_OK; } @@ -306,9 +306,9 @@ int qpacketmodem_encode(qpacketmodem _q, // _q : qpacketmodem object // _frame : encoded/modulated payload symbols // _payload : recovered decoded payload bytes -int qpacketmodem_decode(qpacketmodem _q, - float complex * _frame, - unsigned char * _payload) +int QPACKETMODEM(_decode)(qpacketmodem _q, + TO * _frame, + unsigned char * _payload) { unsigned int i; @@ -318,10 +318,10 @@ int qpacketmodem_decode(qpacketmodem _q, _q->evm = 0.0f; for (i=0; i<_q->payload_mod_len; i++) { // demodulate symbol - modemcf_demodulate(_q->mod_payload, _frame[i], &sym); + MODEM(_demodulate)(_q->mod_payload, _frame[i], &sym); // accumulate error vector magnitude estimate - float e = modemcf_get_demodulator_evm(_q->mod_payload); + float e = MODEM(_get_demodulator_evm)(_q->mod_payload); _q->evm += e*e; // pack decoded symbol into array @@ -343,9 +343,9 @@ int qpacketmodem_decode(qpacketmodem _q, // _q : qpacketmodem object // _frame : encoded/modulated payload symbols // _payload : recovered decoded payload bytes -int qpacketmodem_decode_soft(qpacketmodem _q, - float complex * _frame, - unsigned char * _payload) +int QPACKETMODEM(_decode_soft)(qpacketmodem _q, + TO * _frame, + unsigned char * _payload) { unsigned int i; @@ -356,11 +356,11 @@ int qpacketmodem_decode_soft(qpacketmodem _q, _q->evm = 0.0f; for (i=0; i<_q->payload_mod_len; i++) { // demodulate symbol - modemcf_demodulate_soft(_q->mod_payload, _frame[i], &sym, _q->payload_enc+n); + MODEM(_demodulate_soft)(_q->mod_payload, _frame[i], &sym, _q->payload_enc+n); n += _q->bits_per_symbol; // accumulate error vector magnitude estimate - float e = modemcf_get_demodulator_evm(_q->mod_payload); + float e = MODEM(_get_demodulator_evm)(_q->mod_payload); _q->evm += e*e; } //printf("received %u bits (expected %u)\n", n, _q->payload_mod_len * _q->bits_per_symbol); @@ -374,21 +374,23 @@ int qpacketmodem_decode_soft(qpacketmodem _q, } // decode symbol from modulated frame samples, returning flag if all symbols received -// _q : qpacketmodem object -// _frame : encoded/modulated symbol -int qpacketmodem_decode_soft_sym(qpacketmodem _q, - float complex _symbol) +int QPACKETMODEM(_decode_soft_sym)(qpacketmodem _q, + TO _symbol) { unsigned int sym; - modemcf_demodulate_soft(_q->mod_payload, _symbol, &sym, _q->payload_enc + _q->n); + MODEM(_demodulate_soft)(_q->mod_payload, _symbol, &sym, _q->payload_enc + _q->n); _q->n += _q->bits_per_symbol; return _q->n == _q->payload_mod_len * _q->bits_per_symbol; } -int qpacketmodem_decode_soft_payload(qpacketmodem _q, - unsigned char * _payload) +int QPACKETMODEM(_decode_soft_payload)(qpacketmodem _q, + unsigned char * _payload) { - assert( _q->n == _q->payload_mod_len * _q->bits_per_symbol); + if ( _q->n != _q->payload_mod_len * _q->bits_per_symbol) { + liquid_error(LIQUID_ENOINIT,"qpacketmodem_decode_soft_payload(), insufficient number of symbols received"); + return 0; + } _q->n = 0; return packetizer_decode_soft(_q->p, _q->payload_enc, _payload); } + diff --git a/src/framing/src/symstreamcf.c b/src/framing/src/symstreamcf.c deleted file mode 100644 index 780157f7b8..0000000000 --- a/src/framing/src/symstreamcf.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2007 - 2022 Joseph Gaeddert - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// -// API: floating-point -// - -#include "liquid.internal.h" - -// naming extensions (useful for print statements) -#define EXTENSION "cf" - -#define TO float complex // output type -#define T float // primitive type - -#define TO_COMPLEX 1 -#define T_COMPLEX 0 - -// object references -#define SYMSTREAM(name) LIQUID_CONCAT(symstreamcf,name) -#define MODEM(name) LIQUID_CONCAT(modemcf,name) -#define FIRINTERP(name) LIQUID_CONCAT(firinterp_crcf,name) - -// prototypes -#include "symstream.proto.c" - diff --git a/src/framing/tests/framesync64_autotest.c b/src/framing/tests/framesync64_autotest.c index ba495c99e7..7a1b78cc78 100644 --- a/src/framing/tests/framesync64_autotest.c +++ b/src/framing/tests/framesync64_autotest.c @@ -121,6 +121,8 @@ void autotest_framesync64_copy() // copy object, but set different context framesync64 fs1 = framesync64_copy(fs0); framesync64_set_userdata(fs1, (void*)&frames_recovered_1); + framesync64_print(fs0); + framesync64_print(fs1); // try to receive the frame with each receiver for (i=0; i +#include +#include +#include +#include "autotest/autotest.h" +#include "liquid.h" + +static int callback_fskframesync_autotest( + unsigned char * _header, + int _header_valid, + unsigned char * _payload, + unsigned int _payload_len, + int _payload_valid, + framesyncstats_s _stats, + void * _userdata) +{ + //printf("callback invoked, payload valid: %s\n", _payload_valid ? "yes" : "no"); + *((int*)(_userdata)) += _header_valid && _payload_valid ? 1 : 0; + return 0; +} + +// AUTOTEST : test simple recovery of frame in noise +void autotest_fskframesync() +{ + // options + float SNRdB = 20.0f; // signal-to-noise ratio + float noise_floor = -20.0f; // noise floor + //float dphi = 0.01f; // carrier frequency offset + //float theta = 0.0f; // carrier phase offset + //float dt = -0.2f; // fractional sample timing offset + + crc_scheme check = LIQUID_CRC_32; // data validity check + fec_scheme fec0 = LIQUID_FEC_NONE; // fec (inner) + fec_scheme fec1 = LIQUID_FEC_NONE; // fec (outer) + + // derived values + float nstd = powf(10.0f, noise_floor/20.0f); // noise std. dev. + float gamma = powf(10.0f, (SNRdB+noise_floor)/20.0f); // channel gain + + unsigned int i; + int frames_recovered = 0; + + // create objects + fskframegen fg = fskframegen_create(); + fskframesync fs = fskframesync_create(callback_fskframesync_autotest, + (void*)&frames_recovered); + + // assemble the frame + unsigned char header [ 8]; + unsigned char payload[200]; + for (i=0; i< 8; i++) header [i] = i; + for (i=0; i<200; i++) payload[i] = rand() & 0xff; + fskframegen_assemble(fg, header, payload, 200, check, fec0, fec1); + + // allocate memory for the frame samples + unsigned int buf_len = 256; + float complex buf_tx[buf_len]; // receive buffer + float complex buf_rx[buf_len]; // transmit buffer + + // try to receive the frame (operate in blocks) + int frame_complete = 0; + while (!frame_complete) + { + // generate frame samples + frame_complete = fskframegen_write_samples(fg, buf_tx, buf_len); + + // add noise, channel gain + for (i=0; i +#include +#include +#include "autotest/autotest.h" +#include "liquid.h" + +// common structure for relaying information to/from callback +typedef struct { + int id; + float complex * buf; + unsigned int buf_len; + unsigned int count; +} autotest_qdsync_s; + +// synchronization callback, return 0:continue, 1:reset +int autotest_qdsync_callback(float complex * _buf, + unsigned int _buf_len, + void * _context) +{ + // save samples to buffer as appropriate + autotest_qdsync_s * q = (autotest_qdsync_s *) _context; + if (liquid_autotest_verbose) + printf("[%d] qdsync callback got %u samples\n", q->id, _buf_len); + unsigned int i; + for (i=0; i<_buf_len; i++) { + if (q->count == q->buf_len) + return 1; // buffer full; reset synchronizer + + // save payload + q->buf[q->count++] = _buf[i]; + } + return 0; +} + +void testbench_qdsync_linear(unsigned int _k, + unsigned int _m, + float _beta) +{ + // options + unsigned int seq_len = 1200; // total number of sync symbols + unsigned int k = _k; // samples/symbol + unsigned int m = _m; // filter delay [symbols] + float beta = _beta; // excess bandwidth factor + int ftype = LIQUID_FIRFILT_ARKAISER; + float nstd = 0.001f; + float tau = 0.400f; // fractional sample timing offset + + // generate synchronization sequence (QPSK symbols) + float complex seq_tx[seq_len]; // transmitted + float complex seq_rx[seq_len]; // received with initial correction + unsigned int i; + for (i=0; ih > 0.66667f) { - cpfskdem_init_noncoherent(q); - } else { - cpfskdem_init_coherent(q); + //cpfskdem_init_noncoherent(q); + fprintf(stderr,"warning: cpfskdem_create(), coherent demodulation with h > 2/3 not recommended\n"); } + cpfskdem_init_coherent(q); // reset modem object cpfskdem_reset(q); @@ -235,6 +235,7 @@ int cpfskdem_init_coherent(cpfskdem _q) // initialize non-coherent demodulator int cpfskdem_init_noncoherent(cpfskdem _q) { +#if 0 // specify non-coherent receiver _q->demod_type = CPFSKDEM_NONCOHERENT; @@ -250,6 +251,9 @@ int cpfskdem_init_noncoherent(cpfskdem _q) break; } return LIQUID_OK; +#else + return liquid_error(LIQUID_EUMODE,"cpfskdem_init_noncoherent(), unsupported mode"); +#endif } // destroy modem object diff --git a/src/modem/src/cpfskmod.c b/src/modem/src/cpfskmod.c index 51f553adb4..843022c75d 100644 --- a/src/modem/src/cpfskmod.c +++ b/src/modem/src/cpfskmod.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,7 @@ * THE SOFTWARE. */ -// // continuous phase frequency-shift keying modulator -// #include #include @@ -61,7 +59,7 @@ struct cpfskmod_s { // phase integrator float * phase_interp; // phase interpolation buffer - iirfilt_rrrf integrator; // integrator + float b0, b1, a1, v0, v1; // integrator }; // create cpfskmod object (frequency modulator) @@ -105,8 +103,9 @@ cpfskmod cpfskmod_create(unsigned int _bps, q->M = 1 << q->bps; // constellation size // create object depending upon input type - float b[2] = {0.5f, 0.5f}; // integrator feed-forward coefficients - float a[2] = {1.0f, -1.0f}; // integrator feed-back coefficients + q->b0 = 0.5f; + q->b1 = 0.5f; + q->a1 = -1.0f; q->ht_len = 0; q->ht = NULL; unsigned int i; @@ -115,8 +114,8 @@ cpfskmod cpfskmod_create(unsigned int _bps, q->ht_len = q->k; q->symbol_delay = 1; // modify integrator - b[0] = 0.0f; - b[1] = 1.0f; + q->b0 = 0.0f; + q->b1 = 1.0f; break; case LIQUID_CPFSK_RCOS_FULL: q->ht_len = q->k; @@ -142,9 +141,8 @@ cpfskmod cpfskmod_create(unsigned int _bps, q->ht[i] *= M_PI * q->h; q->interp = firinterp_rrrf_create(q->k, q->ht, q->ht_len); - // create phase integrator + // allocate buffer for phase interpolation q->phase_interp = (float*) malloc(q->k*sizeof(float)); - q->integrator = iirfilt_rrrf_create(b,2,a,2); // reset modem object cpfskmod_reset(q); @@ -160,9 +158,6 @@ int cpfskmod_destroy(cpfskmod _q) free(_q->phase_interp); firinterp_rrrf_destroy(_q->interp); - // destroy phase integrator - iirfilt_rrrf_destroy(_q->integrator); - // free main object memory free(_q); return LIQUID_OK; @@ -200,7 +195,8 @@ int cpfskmod_reset(cpfskmod _q) firinterp_rrrf_reset(_q->interp); // reset phase integrator - iirfilt_rrrf_reset(_q->integrator); + _q->v0 = 0.0f; + _q->v1 = 0.0f; return LIQUID_OK; } @@ -227,7 +223,15 @@ int cpfskmod_modulate(cpfskmod _q, float theta; for (i=0; i<_q->k; i++) { // push phase through integrator - iirfilt_rrrf_execute(_q->integrator, _q->phase_interp[i], &theta); + _q->v0 = _q->phase_interp[i] - _q->v1*_q->a1; + theta = _q->v0*_q->b0 + _q->v1*_q->b1; + _q->v1 = _q->v0; + + // constrain state + if (_q->v1 > 2*M_PI) + _q->v1 -= 2*M_PI; + if (_q->v1 < -2*M_PI) + _q->v1 += 2*M_PI; // compute output _y[i] = liquid_cexpjf(theta); diff --git a/src/modem/tests/cpfskmodem_autotest.c b/src/modem/tests/cpfskmodem_autotest.c index 354144338a..27f57c073a 100644 --- a/src/modem/tests/cpfskmodem_autotest.c +++ b/src/modem/tests/cpfskmodem_autotest.c @@ -118,3 +118,56 @@ void autotest_cpfskmodem_bps2_h0p0250_k4_m3_square() { cpfskmodem_test_mod_de void autotest_cpfskmodem_bps3_h0p1250_k4_m3_square() { cpfskmodem_test_mod_demod( 3, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); } void autotest_cpfskmodem_bps4_h0p0625_k4_m3_square() { cpfskmodem_test_mod_demod( 4, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); } +// test spectral response +void autotest_cpfskmodem_spectrum() +{ + // create modulator + unsigned int bps = 1; + float h = 0.5f; + unsigned int k = 4; + unsigned int m = 3; + float beta = 0.35f; + int type = LIQUID_CPFSK_RCOS_PARTIAL; + cpfskmod mod = cpfskmod_create(bps, h, k, m, beta, type); + + // spectral periodogram options + unsigned int nfft = 2400; // spectral periodogram FFT size + unsigned int num_symbols = 192000; // number of symbols to generate + float complex buf[k]; + unsigned int i; + + // modulate many, many symbols + for (i=0; i<(1U<<24U); i++) + cpfskmod_modulate(mod, 0, buf); + + // modulate several symbols and run result through spectral estimate + spgramcf periodogram = spgramcf_create_default(nfft); + for (i=0; i #include @@ -77,7 +73,7 @@ FIRPFBCH() FIRPFBCH(_create)(int _type, { // validate input if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) - return liquid_error_config("firpfbch_%s_create(), invalid type %d", EXTENSION_FULL, _type); + return liquid_error_config("firpfbch_%s_create(), invalid type: %d", EXTENSION_FULL, _type); if (_M == 0) return liquid_error_config("firpfbch_%s_create(), number of channels must be greater than 0", EXTENSION_FULL); if (_p == 0) @@ -148,6 +144,8 @@ FIRPFBCH() FIRPFBCH(_create_kaiser)(int _type, float _as) { // validate input + if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) + return liquid_error_config("firpfbch_%s_create_kaiser(), invalid type: %d", EXTENSION_FULL, _type); if (_M == 0) return liquid_error_config("firpfbch_%s_create_kaiser(), number of channels must be greater than 0", EXTENSION_FULL); if (_m == 0) @@ -190,7 +188,7 @@ FIRPFBCH() FIRPFBCH(_create_rnyquist)(int _type, { // validate input if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) - return liquid_error_config("firpfbch_%s_create_rnyquist(), invalid type %d", EXTENSION_FULL, _type); + return liquid_error_config("firpfbch_%s_create_rnyquist(), invalid type: %d", EXTENSION_FULL, _type); if (_M == 0) return liquid_error_config("firpfbch_%s_create_rnyquist(), number of channels must be greater than 0", EXTENSION_FULL); if (_m == 0) @@ -199,26 +197,8 @@ FIRPFBCH() FIRPFBCH(_create_rnyquist)(int _type, // design filter based on requested prototype unsigned int h_len = 2*_M*_m + 1; float h[h_len]; - switch (_ftype) { - case LIQUID_FIRFILT_ARKAISER: - // root-Nyquist Kaiser (approximate optimum) - liquid_firdes_arkaiser(_M, _m, _beta, 0.0f, h); - break; - case LIQUID_FIRFILT_RKAISER: - // root-Nyquist Kaiser (true optimum) - liquid_firdes_rkaiser(_M, _m, _beta, 0.0f, h); - break; - case LIQUID_FIRFILT_RRC: - // root raised-cosine - liquid_firdes_rrcos(_M, _m, _beta, 0.0f, h); - break; - case LIQUID_FIRFILT_hM3: - // harris-Moerder-3 filter - liquid_firdes_hM3(_M, _m, _beta, 0.0f, h); - break; - default: - return liquid_error_config("firpfbch_%s_create_rnyquist(), unknown/invalid prototype (%d)", EXTENSION_FULL, _ftype); - } + if (liquid_firdes_prototype(_ftype, _M, _m, _beta, 0.0f, h) != LIQUID_OK) + return liquid_error_config("firpfbch_%s_create_rnyquist(), invalid filter type/configuration", EXTENSION_FULL); // copy coefficients to type-specfic array, reversing order if // channelizer is an analyzer, matched filter: g(-t) diff --git a/src/multichannel/src/ofdmframe.common.c b/src/multichannel/src/ofdmframe.common.c index cc30554c2a..ab7e4d5b15 100644 --- a/src/multichannel/src/ofdmframe.common.c +++ b/src/multichannel/src/ofdmframe.common.c @@ -294,10 +294,18 @@ int ofdmframe_validate_sctype(unsigned char * _p, } } - // set outputs - *_M_null = M_null; - *_M_pilot = M_pilot; - *_M_data = M_data; + // validate subcarrier allocation + if ( (M_pilot + M_data) == 0) + return liquid_error(LIQUID_EICONFIG,"ofdmframe_validate_sctype(), must have at least one enabled subcarrier"); + if (M_data == 0) + return liquid_error(LIQUID_EICONFIG,"ofdmframe_validate_sctype(), must have at least one data subcarrier"); + if (M_pilot < 2) + return liquid_error(LIQUID_EICONFIG,"ofdmframe_validate_sctype(), must have at least two pilot subcarriers"); + + // set outputs if requested + if (_M_null != NULL) *_M_null = M_null; + if (_M_pilot != NULL) *_M_pilot = M_pilot; + if (_M_data != NULL) *_M_data = M_data; return LIQUID_OK; } diff --git a/src/multichannel/src/ofdmframegen.c b/src/multichannel/src/ofdmframegen.c index 8553ef5383..150cb85320 100644 --- a/src/multichannel/src/ofdmframegen.c +++ b/src/multichannel/src/ofdmframegen.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,6 +36,10 @@ #define DEBUG_OFDMFRAMEGEN 1 +// generate symbol (add cyclic prefix/postfix, overlap) +int ofdmframegen_gensymbol(ofdmframegen _q, + float complex * _buffer); + struct ofdmframegen_s { unsigned int M; // number of subcarriers unsigned int cp_len; // cyclic prefix length @@ -84,8 +88,8 @@ ofdmframegen ofdmframegen_create(unsigned int _M, unsigned char * _p) { // validate input - if (_M < 2) - return liquid_error_config("ofdmframegen_create(), number of subcarriers must be at least 2"); + if (_M < 8) + return liquid_error_config("ofdmframegen_create(), number of subcarriers must be at least 8"); if (_M % 2) return liquid_error_config("ofdmframegen_create(), number of subcarriers must be even"); if (_cp_len > _M) @@ -111,12 +115,6 @@ ofdmframegen ofdmframegen_create(unsigned int _M, // validate and count subcarrier allocation if (ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data)) return liquid_error_config("ofdmframegen_create(), invalid subcarrier allocation"); - if ( (q->M_pilot + q->M_data) == 0) - return liquid_error_config("ofdmframegen_create(), must have at least one enabled subcarrier"); - if (q->M_data == 0) - return liquid_error_config("ofdmframegen_create(), must have at least one data subcarriers"); - if (q->M_pilot < 2) - return liquid_error_config("ofdmframegen_create(), must have at least two pilot subcarriers"); unsigned int i; diff --git a/src/multichannel/src/ofdmframesync.c b/src/multichannel/src/ofdmframesync.c index 0e7ce54488..7f008b0542 100644 --- a/src/multichannel/src/ofdmframesync.c +++ b/src/multichannel/src/ofdmframesync.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +20,7 @@ * THE SOFTWARE. */ -// -// ofdmframesync.c -// // OFDM frame synchronizer -// #include #include @@ -34,13 +30,55 @@ #include "liquid.internal.h" -#define DEBUG_OFDMFRAMESYNC 1 +#define DEBUG_OFDMFRAMESYNC 0 #define DEBUG_OFDMFRAMESYNC_PRINT 0 #define DEBUG_OFDMFRAMESYNC_FILENAME "ofdmframesync_internal_debug.m" #define DEBUG_OFDMFRAMESYNC_BUFFER_LEN (2048) #define OFDMFRAMESYNC_ENABLE_SQUELCH 0 +// forward declaration of internal methods + +int ofdmframesync_execute_seekplcp(ofdmframesync _q); +int ofdmframesync_execute_S0a(ofdmframesync _q); +int ofdmframesync_execute_S0b(ofdmframesync _q); +int ofdmframesync_execute_S1( ofdmframesync _q); +int ofdmframesync_execute_rxsymbols(ofdmframesync _q); + +int ofdmframesync_S0_metrics(ofdmframesync _q, + float complex * _G, + float complex * _s_hat); + +// estimate short sequence gain +// _q : ofdmframesync object +// _x : input array (time) +// _G : output gain (freq) +int ofdmframesync_estimate_gain_S0(ofdmframesync _q, + float complex * _x, + float complex * _G); + +// estimate long sequence gain +// _q : ofdmframesync object +// _x : input array (time) +// _G : output gain (freq) +int ofdmframesync_estimate_gain_S1(ofdmframesync _q, + float complex * _x, + float complex * _G); + +// estimate complex equalizer gain from G0 and G1 +// _q : ofdmframesync object +// _ntaps : number of time-domain taps for smoothing +//int ofdmframesync_estimate_eqgain(ofdmframesync _q, unsigned int _ntaps); + +// estimate complex equalizer gain from G0 and G1 using polynomial fit +// _q : ofdmframesync object +// _order : polynomial order +int ofdmframesync_estimate_eqgain_poly(ofdmframesync _q, + unsigned int _order); + +// recover symbol, correcting for gain, pilot phase, etc. +int ofdmframesync_rxsymbol(ofdmframesync _q); + struct ofdmframesync_s { unsigned int M; // number of subcarriers unsigned int M2; // number of subcarriers (divided by 2) @@ -149,11 +187,13 @@ ofdmframesync ofdmframesync_create(unsigned int _M, // validate input if (_M < 8) - return liquid_error_config("ofdmframesync_create(), less than 8 subcarriers"); + return liquid_error_config("ofdmframesync_create(), number of subcarriers must be at least 8"); if (_M % 2) return liquid_error_config("ofdmframesync_create(), number of subcarriers must be even"); if (_cp_len > _M) return liquid_error_config("ofdmframesync_create(), cyclic prefix length cannot exceed number of subcarriers"); + if (_taper_len > _cp_len) + return liquid_error_config("ofdmframesync_create(), taper length cannot exceed cyclic prefix"); q->M = _M; q->cp_len = _cp_len; @@ -172,12 +212,6 @@ ofdmframesync ofdmframesync_create(unsigned int _M, // validate and count subcarrier allocation if (ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data)) return liquid_error_config("ofdmframesync_create(), invalid subcarrier allocation"); - if ( (q->M_pilot + q->M_data) == 0) - return liquid_error_config("ofdmframesync_create(), must have at least one enabled subcarrier"); - if (q->M_data == 0) - return liquid_error_config("ofdmframesync_create(), must have at least one data subcarriers"); - if (q->M_pilot < 2) - return liquid_error_config("ofdmframesync_create(), must have at least two pilot subcarriers"); // create transform object q->X = (float complex*) FFT_MALLOC((q->M)*sizeof(float complex)); @@ -417,8 +451,7 @@ float ofdmframesync_get_cfo(ofdmframesync _q) // set receiver carrier frequency offset estimate int ofdmframesync_set_cfo(ofdmframesync _q, float _cfo) { - nco_crcf_set_frequency(_q->nco_rx, _cfo); - return LIQUID_OK; + return nco_crcf_set_frequency(_q->nco_rx, _cfo); } // @@ -757,7 +790,7 @@ int ofdmframesync_execute_rxsymbols(ofdmframesync _q) } // compute S0 metrics -int ofdmframesync_S0_metrics(ofdmframesync _q, +int ofdmframesync_S0_metrics(ofdmframesync _q, float complex * _G, float complex * _s_hat) { @@ -845,6 +878,7 @@ int ofdmframesync_estimate_gain_S1(ofdmframesync _q, return LIQUID_OK; } +#if 0 // estimate complex equalizer gain from G0 and G1 // _q : ofdmframesync object // _ntaps : number of time-domain taps for smoothing @@ -905,6 +939,7 @@ int ofdmframesync_estimate_eqgain(ofdmframesync _q, } return LIQUID_OK; } +#endif // estimate complex equalizer gain from G0 and G1 using polynomial fit // _q : ofdmframesync object @@ -955,12 +990,7 @@ int ofdmframesync_estimate_eqgain_poly(ofdmframesync _q, return liquid_error(LIQUID_EINT,"ofdmframesync_estimate_eqgain_poly(), pilot subcarrier mismatch"); // try to unwrap phase - for (i=1; i M_PI) - y_arg[i] -= 2*M_PI; - while ((y_arg[i] - y_arg[i-1]) < -M_PI) - y_arg[i] += 2*M_PI; - } + liquid_unwrap_phase(y_arg, N); // fit to polynomial polyf_fit(x_freq, y_abs, N, p_abs, _order+1); @@ -1036,12 +1066,7 @@ int ofdmframesync_rxsymbol(ofdmframesync _q) return liquid_error(LIQUID_EINT,"ofdmframesync_estimate_eqgain_poly(), pilot subcarrier mismatch"); // try to unwrap phase - for (i=1; i<_q->M_pilot; i++) { - while ((y_phase[i] - y_phase[i-1]) > M_PI) - y_phase[i] -= 2*M_PI; - while ((y_phase[i] - y_phase[i-1]) < -M_PI) - y_phase[i] += 2*M_PI; - } + liquid_unwrap_phase(y_phase, _q->M_pilot); // fit phase to 1st-order polynomial (2 coefficients) polyf_fit(x_phase, y_phase, _q->M_pilot, p_phase, 2); @@ -1127,7 +1152,7 @@ int ofdmframesync_debug_enable(ofdmframesync _q) _q->debug_objects_created = 1; return LIQUID_OK; #else - fprintf(stderr,"ofdmframesync_debug_enable(): compile-time debugging disabled\n"); + return liquid_error(LIQUID_EICONFIG,"ofdmframesync_debug_enable(): compile-time debugging disabled"); #endif } @@ -1138,7 +1163,7 @@ int ofdmframesync_debug_disable(ofdmframesync _q) _q->debug_enabled = 0; return LIQUID_OK; #else - fprintf(stderr,"ofdmframesync_debug_disable(): compile-time debugging disabled\n"); + return liquid_error(LIQUID_EICONFIG,"ofdmframesync_debug_disable(): compile-time debugging disabled"); #endif } @@ -1300,7 +1325,7 @@ int ofdmframesync_debug_print(ofdmframesync _q, printf("ofdmframesync/debug: results written to '%s'\n", _filename); return LIQUID_OK; #else - fprintf(stderr,"ofdmframesync_debug_print(): compile-time debugging disabled\n"); + return liquid_error(LIQUID_EICONFIG,"ofdmframesync_debug_print(): compile-time debugging disabled"); #endif } diff --git a/src/multichannel/tests/firpfbch2_crcf_autotest.c b/src/multichannel/tests/firpfbch2_crcf_autotest.c index c00a728b59..9c5f135ec8 100644 --- a/src/multichannel/tests/firpfbch2_crcf_autotest.c +++ b/src/multichannel/tests/firpfbch2_crcf_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2022 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -97,7 +97,6 @@ void autotest_firpfbch2_crcf_n16() { firpfbch2_crcf_runtest( 16, 5, 60.0f); } void autotest_firpfbch2_crcf_n32() { firpfbch2_crcf_runtest( 32, 5, 60.0f); } void autotest_firpfbch2_crcf_n64() { firpfbch2_crcf_runtest( 64, 5, 60.0f); } - void autotest_firpfbch2_crcf_copy() { // create channelizer @@ -140,3 +139,36 @@ void autotest_firpfbch2_crcf_copy() firpfbch2_crcf_destroy(q_copy); } +void autotest_firpfbch2_crcf_config() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping firpfbch2_crcf config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + // check invalid function calls + CONTEND_ISNULL(firpfbch2_crcf_create( 77, 76, 12, NULL)) // invalid type + CONTEND_ISNULL(firpfbch2_crcf_create(LIQUID_ANALYZER, 0, 12, NULL)) // invalid number of channels + CONTEND_ISNULL(firpfbch2_crcf_create(LIQUID_ANALYZER, 17, 12, NULL)) // invalid number of channels + CONTEND_ISNULL(firpfbch2_crcf_create(LIQUID_ANALYZER, 76, 0, NULL)) // invalid filter semi-length + + CONTEND_ISNULL(firpfbch2_crcf_create_kaiser( 77, 76, 12, 60.0f)) // invalid type + CONTEND_ISNULL(firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER, 0, 12, 60.0f)) // invalid number of channels + CONTEND_ISNULL(firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER, 17, 12, 60.0f)) // invalid number of channels + CONTEND_ISNULL(firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER, 76, 0, 60.0f)) // invalid filter semi-length + + CONTEND_ISNULL(firpfbch2_crcf_copy(NULL)) + + // create proper object and test configurations + firpfbch2_crcf q = firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER, 76, 12, 60.0f); + + CONTEND_EQUALITY(LIQUID_OK, firpfbch2_crcf_print(q)) + CONTEND_EQUALITY(LIQUID_ANALYZER, firpfbch2_crcf_get_type(q)) + CONTEND_EQUALITY( 76, firpfbch2_crcf_get_M(q)) + CONTEND_EQUALITY( 12, firpfbch2_crcf_get_m(q)) + + firpfbch2_crcf_destroy(q); +} + diff --git a/src/multichannel/tests/firpfbch_crcf_autotest.c b/src/multichannel/tests/firpfbch_crcf_autotest.c new file mode 100644 index 0000000000..7ada5fd56b --- /dev/null +++ b/src/multichannel/tests/firpfbch_crcf_autotest.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2007 - 2023 Joseph Gaeddert + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "autotest/autotest.h" +#include "liquid.h" + +void autotest_firpfbch_crcf_config() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping firpfbch_crcf config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + // check invalid function calls + CONTEND_ISNULL(firpfbch_crcf_create( 77, 76, 12, NULL)) // invalid type + CONTEND_ISNULL(firpfbch_crcf_create(LIQUID_ANALYZER, 0, 12, NULL)) // invalid number of channels + CONTEND_ISNULL(firpfbch_crcf_create(LIQUID_ANALYZER, 76, 0, NULL)) // invalid filter semi-length + + CONTEND_ISNULL(firpfbch_crcf_create_kaiser( 77, 76, 12, 60.0f)) // invalid type + CONTEND_ISNULL(firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, 0, 12, 60.0f)) // invalid number of channels + CONTEND_ISNULL(firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, 76, 0, 60.0f)) // invalid filter semi-length + + CONTEND_ISNULL(firpfbch_crcf_create_rnyquist( 77, 76, 12, 0.2f, LIQUID_FIRFILT_ARKAISER)) // invalid type + CONTEND_ISNULL(firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, 0, 12, 0.2f, LIQUID_FIRFILT_ARKAISER)) // invalid number of channels + CONTEND_ISNULL(firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, 76, 0, 0.2f, LIQUID_FIRFILT_ARKAISER)) // invalid filter semi-length + CONTEND_ISNULL(firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, 76, 12, 77.f, LIQUID_FIRFILT_ARKAISER)) // invalid filter excess bandwidth + CONTEND_ISNULL(firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, 76, 12, 0.2f, LIQUID_FIRFILT_UNKNOWN )) // invalid filter type + + //CONTEND_ISNULL(firpfbch_crcf_copy(NULL)) + + // create proper object and test configurations + firpfbch_crcf q = firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, 76, 12, 60.0f); + + CONTEND_EQUALITY(LIQUID_OK, firpfbch_crcf_print(q)) + + firpfbch_crcf_destroy(q); +} + diff --git a/src/multichannel/tests/ofdmframesync_autotest.c b/src/multichannel/tests/ofdmframe_autotest.c similarity index 51% rename from src/multichannel/tests/ofdmframesync_autotest.c rename to src/multichannel/tests/ofdmframe_autotest.c index 4d43d72a0f..2e0aed52e8 100644 --- a/src/multichannel/tests/ofdmframesync_autotest.c +++ b/src/multichannel/tests/ofdmframe_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2019 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,7 +41,8 @@ int ofdmframesync_autotest_callback(float complex * _X, unsigned int _M, void * _userdata) { - printf("******** callback invoked!\n"); + if (liquid_autotest_verbose) + printf("******** callback invoked!\n"); // type cast _userdata as complex float array float complex * X = (float complex *)_userdata; @@ -143,3 +144,99 @@ void autotest_ofdmframesync_acquire_n128() { ofdmframesync_acquire_test(128, 16 void autotest_ofdmframesync_acquire_n256() { ofdmframesync_acquire_test(256, 32, 0); } void autotest_ofdmframesync_acquire_n512() { ofdmframesync_acquire_test(512, 64, 0); } +void autotest_ofdmframe_common_config() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping ofdmframe common config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + // check invalid function calls + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_default_sctype(0, NULL)) // too few subcarriers + + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range( 0, -0.4f, +0.4f, NULL)) // too few subcarriers + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(64, -0.7f, +0.4f, NULL)) // frequency out of range + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(64, -0.4f, +0.7f, NULL)) // frequency out of range + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(64, -0.2f, -0.3f, NULL)) // frequency out of range + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(64, 0.3f, 0.2f, NULL)) // frequency out of range + CONTEND_INEQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(64, -0.02f,+0.02f,NULL)) // too few effective subcarriers + + // generate valid subcarrier allocation + unsigned int M = 120; + unsigned char p[M]; + + // default subcarrier allocation + CONTEND_EQUALITY(LIQUID_OK, ofdmframe_init_default_sctype(M, p)) + CONTEND_EQUALITY(LIQUID_OK, ofdmframe_validate_sctype(p, M, NULL, NULL, NULL)) + + // subcarrier allocation within an occupied frequency range + CONTEND_EQUALITY(LIQUID_OK, ofdmframe_init_sctype_range(M, -0.4f,+0.4f,p)) + CONTEND_EQUALITY(LIQUID_OK, ofdmframe_validate_sctype(p, M, NULL, NULL, NULL)) + + // invalid subcarrier allocations + unsigned int i; + for (i=0; i LIQUID_CHROMOSOME_MAX_SIZE) + return liquid_error_config("chromosome_create(), bits/trait cannot exceed %u", LIQUID_CHROMOSOME_MAX_SIZE); + } + chromosome q; q = (chromosome) malloc( sizeof(struct chromosome_s) ); q->num_traits = _num_traits; - // validate input - if (q->num_traits < 1) - return liquid_error_config("chromosome_create(), must have at least one trait"); - // initialize internal arrays q->bits_per_trait = (unsigned int *) malloc(q->num_traits*sizeof(unsigned int)); q->max_value = (unsigned long*) malloc(q->num_traits*sizeof(unsigned long)); q->traits = (unsigned long*) malloc(q->num_traits*sizeof(unsigned long)); // copy/initialize values - unsigned int i; q->num_bits = 0; for (i=0; inum_traits; i++) { q->bits_per_trait[i] = _bits_per_trait[i]; - if (q->bits_per_trait[i] > LIQUID_CHROMOSOME_MAX_SIZE) - return liquid_error_config("chromosome_create(), bits/trait cannot exceed %u", LIQUID_CHROMOSOME_MAX_SIZE); - - q->max_value[i] = 1 << q->bits_per_trait[i]; - q->traits[i] = 0; + q->max_value[i] = 1LU << q->bits_per_trait[i]; + q->traits[i] = 0LU; q->num_bits += q->bits_per_trait[i]; } @@ -79,6 +80,8 @@ chromosome chromosome_create_basic(unsigned int _num_traits, // validate input if (_num_traits == 0) return liquid_error_config("chromosome_create_basic(), must have at least one trait"); + if (_bits_per_trait == 0 || _bits_per_trait > 64) + return liquid_error_config("chromosome_create_basic(), bits per trait out of range"); unsigned int * bpt = (unsigned int *) malloc(_num_traits*sizeof(unsigned int)); unsigned int i; @@ -175,6 +178,7 @@ int chromosome_init(chromosome _c, { unsigned int i; for (i=0; i<_c->num_traits; i++) { + //printf("===> [%3u] bits:%3u, max:%12lu, value:%12lu\n", i, _c->bits_per_trait[i], _c->max_value[i], _v[i]); if (_v[i] >= _c->max_value[i]) return liquid_error(LIQUID_EIRANGE,"chromosome_init(), value exceeds maximum"); @@ -189,12 +193,14 @@ int chromosome_initf(chromosome _c, { unsigned int i; for (i=0; i<_c->num_traits; i++) { - if (_v[i] > 1.0f || _v[i] < 0.0f) + if (_v[i] < 0.0f || _v[i] > 1.0f) return liquid_error(LIQUID_EIRANGE,"chromosome_initf(), value must be in [0,1]"); // quantize sample - unsigned int N = 1 << _c->bits_per_trait[i]; - _c->traits[i] = (unsigned int) floorf( _v[i] * N ); + unsigned long N = 1LU << _c->bits_per_trait[i]; + _c->traits[i] = (unsigned long) floorf( _v[i] * N ); + //printf("===> [%3u] quantizing %8.2f, bits:%3u, N:%12lu, trait:%12lu/%12lu => %12.8f\n", + // i, _v[i], _c->bits_per_trait[i], N, _c->traits[i], _c->max_value[i], chromosome_valuef(_c,i)); } return LIQUID_OK; } @@ -212,10 +218,10 @@ int chromosome_mutate(chromosome _q, for (i=0; i<_q->num_traits; i++) { unsigned int b = _q->bits_per_trait[i]; if (t == _index) { - _q->traits[i] ^= (unsigned long)(1 << (b-1)); + _q->traits[i] ^= (unsigned long)(1LU << (b-1)); return LIQUID_OK; } else if (t > _index) { - _q->traits[i-1] ^= (unsigned long)(1 << (t-_index-1)); + _q->traits[i-1] ^= (unsigned long)(1LU << (t-_index-1)); return LIQUID_OK; } else { t += b; @@ -289,18 +295,18 @@ int chromosome_init_random(chromosome _q) { unsigned int i; for (i=0; i<_q->num_traits; i++) - _q->traits[i] = rand() & (_q->max_value[i]-1); + _q->traits[i] = rand() & (_q->max_value[i]-1LU); return LIQUID_OK; } -float chromosome_valuef(chromosome _q, +float chromosome_valuef(chromosome _q, unsigned int _index) { if (_index > _q->num_traits) { liquid_error(LIQUID_EIRANGE,"chromosome_valuef(), trait index exceeded"); return 0.0f; } - return (float) (_q->traits[_index]) / (float)(_q->max_value[_index] - 1); + return (float) (_q->traits[_index]) / (float)(_q->max_value[_index]-1LU); } unsigned int chromosome_value(chromosome _q, diff --git a/src/optim/src/gasearch.c b/src/optim/src/gasearch.c index 9ab3fb7ee8..a846aabdfa 100644 --- a/src/optim/src/gasearch.c +++ b/src/optim/src/gasearch.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -63,15 +63,20 @@ gasearch gasearch_create_advanced(gasearch_utility _utility, unsigned int _population_size, float _mutation_rate) { - gasearch ga; - ga = (gasearch) malloc( sizeof(struct gasearch_s) ); - + // validate input + if (_utility == NULL) + return liquid_error_config("gasearch_create(), utility function cannot be NULL") + if (_parent == NULL) + return liquid_error_config("gasearch_create(), parent cannot be NULL") + if (_population_size < 2) + return liquid_error_config("gasearch_create(), population size exceeds minimum"); if (_population_size > LIQUID_GA_SEARCH_MAX_POPULATION_SIZE) - return liquid_error_config("gasearch_create(), population size exceeds maximum"); + return liquid_error_config("gasearch_create(), population size exceeds maximum (%u)",LIQUID_GA_SEARCH_MAX_POPULATION_SIZE); if (_mutation_rate < 0.0f || _mutation_rate > 1.0f) return liquid_error_config("gasearch_create(), mutation rate must be in [0,1]"); - // initialize public values + // create object and initialize values + gasearch ga = (gasearch) malloc( sizeof(struct gasearch_s) ); ga->userdata = _userdata; ga->num_parameters = _parent->num_traits; ga->population_size = _population_size; @@ -162,6 +167,8 @@ int gasearch_set_population_size(gasearch _g, // validate input if (_population_size < 2) return liquid_error(LIQUID_EICONFIG,"gasearch_set_population_size(), population must be at least 2"); + if (_population_size > LIQUID_GA_SEARCH_MAX_POPULATION_SIZE) + return liquid_error(LIQUID_EICONFIG,"gasearch_set_population_size(), population exceeds maximum (%u)",LIQUID_GA_SEARCH_MAX_POPULATION_SIZE); if (_selection_size == 0) return liquid_error(LIQUID_EICONFIG,"gasearch_set_population_size(), selection size must be greater than zero"); if (_selection_size >= _population_size) diff --git a/src/optim/src/qnsearch.c b/src/optim/src/qnsearch.c index 168ba3162c..61bef05569 100644 --- a/src/optim/src/qnsearch.c +++ b/src/optim/src/qnsearch.c @@ -72,9 +72,14 @@ qnsearch qnsearch_create(void * _userdata, utility_function _u, int _minmax) { - qnsearch q = (qnsearch) malloc( sizeof(struct qnsearch_s) ); + // validate input + if (_u == NULL) + return liquid_error_config("qnsearch_create(), utility function cannot be NULL") + if (_num_parameters == 0) + return liquid_error_config("qnsearch_create(), number of parameters must be greater than zero"); - // initialize public values + // create object and initialize public values + qnsearch q = (qnsearch) malloc( sizeof(struct qnsearch_s) ); q->delta = 1e-6f; //_delta; q->gamma = 1e-3f; //_gamma; q->dgamma = 0.99f; @@ -146,7 +151,6 @@ int qnsearch_step(qnsearch _q) // compute normalized gradient vector qnsearch_compute_gradient(_q); - //qnsearch_normalize_gradient(_q); // TODO : perform line search to find optimal gamma @@ -192,9 +196,9 @@ int qnsearch_step(qnsearch _q) return LIQUID_OK; } -float qnsearch_run(qnsearch _q, - unsigned int _max_iterations, - float _target_utility) +float qnsearch_execute(qnsearch _q, + unsigned int _max_iterations, + float _target_utility) { unsigned int i=0; do { @@ -231,22 +235,6 @@ int qnsearch_compute_gradient(qnsearch _q) return LIQUID_OK; } -// normalize gradient vector to unity -int qnsearch_normalize_gradient(qnsearch _q) -{ - // normalize gradient - float sig = 0.0f; - unsigned int i; - for (i=0; i<_q->num_parameters; i++) - sig += _q->gradient[i] * _q->gradient[i]; - - sig = 1.0f / sqrtf(sig/(float)(_q->num_parameters)); - - for (i=0; i<_q->num_parameters; i++) - _q->gradient[i] *= sig; - return LIQUID_OK; -} - // compute Hessian int qnsearch_compute_Hessian(qnsearch _q) { diff --git a/src/optim/src/qs1dsearch.c b/src/optim/src/qs1dsearch.c index 627fedcc11..35d051729c 100644 --- a/src/optim/src/qs1dsearch.c +++ b/src/optim/src/qs1dsearch.c @@ -78,6 +78,7 @@ int qs1dsearch_print(qs1dsearch _q) int qs1dsearch_reset(qs1dsearch _q) { _q->init = 0; + _q->num_steps = 0; return LIQUID_OK; } diff --git a/src/optim/tests/gasearch_autotest.c b/src/optim/tests/gasearch_autotest.c new file mode 100644 index 0000000000..77ba6e2f80 --- /dev/null +++ b/src/optim/tests/gasearch_autotest.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2007 - 2023 Joseph Gaeddert + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "autotest/autotest.h" +#include "liquid.h" + +#include +#include +#include +#include +#include + +#include "liquid.internal.h" + +// peak callback function; value nearest {p, p, p, ...} where p = 1/sqrt(2) +float gasearch_autotest_peak_callback(void * _userdata, chromosome _c) +{ + unsigned int i, n = chromosome_get_num_traits(_c); + float u = 1.0f; + float sig = 0.2f; + float p = M_SQRT1_2; + for (i=0; i +#include +#include +#include +#include + +#include "liquid.internal.h" + +void autotest_qnsearch_rosenbrock() +{ + float tol = 1e-2f; // error tolerance + unsigned int num_parameters = 6; // dimensionality of search (minimum 2) + unsigned int num_iterations = 4000; // number of iterations to run + + // initialize vector for optimization + float v_opt[num_parameters]; + unsigned int i; + for (i=0; i +#include +#include +#include +#include -#define TO_COMPLEX 1 -#define T_COMPLEX 0 - -// object references -#define QSOURCE(name) LIQUID_CONCAT(qsourcecf,name) -#define MSOURCE(name) LIQUID_CONCAT(msourcecf,name) - -#define IIRFILT(name) LIQUID_CONCAT(iirfilt_crcf,name) -#define NCO(name) LIQUID_CONCAT(nco_crcf,name) -#define SYMSTREAM(name) LIQUID_CONCAT(symstreamcf,name) +#include "liquid.internal.h" -// prototypes -#include "msource.proto.c" -#include "qsource.proto.c" +void autotest_optim_rosenbrock() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping rosenbrock config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + + // optimum + float v_ones[8] = {1,1,1,1,1,1,1,1}; + CONTEND_DELTA( liquid_rosenbrock(NULL, v_ones, 8), 0.0f, 1e-6f ) + CONTEND_DELTA( liquid_rosenbrock(NULL, v_ones, 1), 0.0f, 1e-6f ) + + // very far from optimum + float v_misc[8] = {0.3, 1.0, 4.5,-2.2, 6.7,-0.2, 1.1,-0.9,}; + CONTEND_GREATER_THAN( liquid_rosenbrock(NULL, v_misc, 8), 1000.0f ) + + // invalid configuration + CONTEND_EQUALITY( liquid_rosenbrock(NULL, v_misc, 0), 0.0f ) +} diff --git a/src/random/src/randgamma.c b/src/random/src/randgamma.c index 43f8197984..2faa8c18ff 100644 --- a/src/random/src/randgamma.c +++ b/src/random/src/randgamma.c @@ -145,7 +145,7 @@ float randgammaf_delta(float _delta) float V1 = 0.0f; float V2 = 0.0f; - unsigned int m = 1; + //unsigned int m = 1; float xi = 0.0f; float eta = 0.0f; @@ -168,7 +168,7 @@ float randgammaf_delta(float _delta) // step 6 if ( eta > powf(xi,_delta-1.0f)*expf(-xi) ) { - m++; + //m++; } else { break; } diff --git a/src/sequence/src/msequence.c b/src/sequence/src/msequence.c index e0a98cbcfb..ecad555ffd 100644 --- a/src/sequence/src/msequence.c +++ b/src/sequence/src/msequence.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2020 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,7 @@ * THE SOFTWARE. */ -// -// m-sequence -// +// maximum-length sequence #include #include @@ -32,30 +30,17 @@ #include "liquid.internal.h" #define LIQUID_MIN_MSEQUENCE_M 2 -#define LIQUID_MAX_MSEQUENCE_M 15 - -// msequence structure -// Note that 'g' is stored as the default polynomial shifted to the -// right by one bit; this bit is implied and not actually used in -// the shift register's feedback bit computation. -struct msequence_s msequence_default[16] = { -// m, g, a, n, v, b - {0, 0, 1, 0, 1, 0}, // dummy placeholder - {0, 0, 1, 0, 1, 0}, // dummy placeholder - {2, 0x0003, 0x0002, 3, 0x0002, 0}, - {3, 0x0005, 0x0004, 7, 0x0004, 0}, - {4, 0x0009, 0x0008, 15, 0x0008, 0}, - {5, 0x0012, 0x0010, 31, 0x0010, 0}, - {6, 0x0021, 0x0020, 63, 0x0020, 0}, - {7, 0x0044, 0x0040, 127, 0x0040, 0}, - {8, 0x008E, 0x0080, 255, 0x0080, 0}, - {9, 0x0108, 0x0100, 511, 0x0100, 0}, - {10, 0x0204, 0x0200, 1023, 0x0200, 0}, - {11, 0x0402, 0x0400, 2047, 0x0400, 0}, - {12, 0x0829, 0x0800, 4095, 0x0800, 0}, - {13, 0x100d, 0x1000, 8191, 0x1000, 0}, - {14, 0x2015, 0x2000, 16383, 0x2000, 0}, - {15, 0x4001, 0x4000, 32767, 0x4000, 0} +#define LIQUID_MAX_MSEQUENCE_M 31 + +// maximal-length sequence +struct msequence_s { + unsigned int m; // length generator polynomial, shift register + unsigned int g; // generator polynomial, form: { x^m + ... + 1 } + unsigned int a; // initial shift register state, default: 1 + + // derived values + unsigned int n; // length of sequence, n = (2^m)-1 + unsigned int state; // shift register }; // create a maximal-length sequence (m-sequence) object with @@ -69,29 +54,20 @@ msequence msequence_create(unsigned int _m, { // validate input if (_m > LIQUID_MAX_MSEQUENCE_M || _m < LIQUID_MIN_MSEQUENCE_M) - return liquid_error_config("msequence_create(), m not in range"); + return liquid_error_config("msequence_create(), m (%u) not in range", _m); + //if (_a == 0) + // return liquid_error_config("msequence_create(), state 'a' cannot be 0"); // allocate memory for msequence object msequence ms = (msequence) malloc(sizeof(struct msequence_s)); // set internal values ms->m = _m; // generator polynomial length - ms->g = _g >> 1; // generator polynomial (clip off most significant bit) - - // initialize state register, reversing order - // 0001 -> 1000 - unsigned int i; - ms->a = 0; - for (i=0; im; i++) { - ms->a <<= 1; - ms->a |= (_a & 0x01); - _a >>= 1; - } + ms->g = _g; // generator polynomial + ms->a = _a; // generator polynomial ms->n = (1<<_m)-1; // sequence length, (2^m)-1 - ms->v = ms->a; // shift register - ms->b = 0; // return bit - + ms->state = ms->a; // shift register state return ms; } @@ -100,14 +76,14 @@ msequence msequence_create(unsigned int _m, msequence msequence_create_genpoly(unsigned int _g) { unsigned int t = liquid_msb_index(_g); - + // validate input if (t < 2) return liquid_error_config("msequence_create_genpoly(), invalid generator polynomial: 0x%x", _g); // compute derived values - unsigned int m = t - 1; // m-sequence shift register length - unsigned int a = 1; // m-sequence initial state + unsigned int m = t; // m-sequence shift register length + unsigned int a = 1; // m-sequence initial state // generate object and return return msequence_create(m,_g,a); @@ -116,18 +92,44 @@ msequence msequence_create_genpoly(unsigned int _g) // creates a default maximal-length sequence msequence msequence_create_default(unsigned int _m) { - // validate input - if (_m > LIQUID_MAX_MSEQUENCE_M || _m < LIQUID_MIN_MSEQUENCE_M) - return liquid_error_config("msequence_create(), m not in range"); - - // allocate memory for msequence object - msequence ms = (msequence) malloc(sizeof(struct msequence_s)); - - // copy default sequence - memmove(ms, &msequence_default[_m], sizeof(struct msequence_s)); + unsigned int g = 0; + switch (_m) { + case 2: g = LIQUID_MSEQUENCE_GENPOLY_M2; break; + case 3: g = LIQUID_MSEQUENCE_GENPOLY_M3; break; + case 4: g = LIQUID_MSEQUENCE_GENPOLY_M4; break; + case 5: g = LIQUID_MSEQUENCE_GENPOLY_M5; break; + case 6: g = LIQUID_MSEQUENCE_GENPOLY_M6; break; + case 7: g = LIQUID_MSEQUENCE_GENPOLY_M7; break; + case 8: g = LIQUID_MSEQUENCE_GENPOLY_M8; break; + case 9: g = LIQUID_MSEQUENCE_GENPOLY_M9; break; + case 10: g = LIQUID_MSEQUENCE_GENPOLY_M10; break; + case 11: g = LIQUID_MSEQUENCE_GENPOLY_M11; break; + case 12: g = LIQUID_MSEQUENCE_GENPOLY_M12; break; + case 13: g = LIQUID_MSEQUENCE_GENPOLY_M13; break; + case 14: g = LIQUID_MSEQUENCE_GENPOLY_M14; break; + case 15: g = LIQUID_MSEQUENCE_GENPOLY_M15; break; + case 16: g = LIQUID_MSEQUENCE_GENPOLY_M16; break; + case 17: g = LIQUID_MSEQUENCE_GENPOLY_M17; break; + case 18: g = LIQUID_MSEQUENCE_GENPOLY_M18; break; + case 19: g = LIQUID_MSEQUENCE_GENPOLY_M19; break; + case 20: g = LIQUID_MSEQUENCE_GENPOLY_M20; break; + case 21: g = LIQUID_MSEQUENCE_GENPOLY_M21; break; + case 22: g = LIQUID_MSEQUENCE_GENPOLY_M22; break; + case 23: g = LIQUID_MSEQUENCE_GENPOLY_M23; break; + case 24: g = LIQUID_MSEQUENCE_GENPOLY_M24; break; + case 25: g = LIQUID_MSEQUENCE_GENPOLY_M25; break; + case 26: g = LIQUID_MSEQUENCE_GENPOLY_M26; break; + case 27: g = LIQUID_MSEQUENCE_GENPOLY_M27; break; + case 28: g = LIQUID_MSEQUENCE_GENPOLY_M28; break; + case 29: g = LIQUID_MSEQUENCE_GENPOLY_M29; break; + case 30: g = LIQUID_MSEQUENCE_GENPOLY_M30; break; + case 31: g = LIQUID_MSEQUENCE_GENPOLY_M31; break; + default: + return liquid_error_config("msequence_create_default(), m (%u) not in range", _m); + } // return - return ms; + return msequence_create_genpoly(g); } // destroy an msequence object, freeing all internal memory @@ -138,23 +140,10 @@ int msequence_destroy(msequence _ms) } // prints the sequence's internal state to the screen -int msequence_print(msequence _m) +int msequence_print(msequence _ms) { - unsigned int i; - - printf("msequence: m=%u (n=%u):\n", _m->m, _m->n); - - // print shift register - printf(" shift register: "); - for (i=0; i<_m->m; i++) - printf("%c", ((_m->v) >> (_m->m-i-1)) & 0x01 ? '1' : '0'); - printf("\n"); - - // print generator polynomial - printf(" generator poly: "); - for (i=0; i<_m->m; i++) - printf("%c", ((_m->g) >> (_m->m-i-1)) & 0x01 ? '1' : '0'); - printf("\n"); + printf("\n", + _ms->m, _ms->n, _ms->g, _ms->state); return LIQUID_OK; } @@ -163,13 +152,12 @@ unsigned int msequence_advance(msequence _ms) { // compute return bit as binary dot product between the // internal shift register and the generator polynomial - _ms->b = liquid_bdotprod( _ms->v, _ms->g ); - - _ms->v <<= 1; // shift internal register - _ms->v |= _ms->b; // push bit onto register - _ms->v &= _ms->n; // apply mask to register + unsigned int b = liquid_bdotprod( _ms->state, _ms->g); - return _ms->b; // return result + _ms->state <<= 1; // shift internal register + _ms->state |= b; // push bit onto register + _ms->state &= _ms->n; // apply mask to register + return b; // return result } @@ -191,7 +179,7 @@ unsigned int msequence_generate_symbol(msequence _ms, // reset msequence shift register to original state, typically '1' int msequence_reset(msequence _ms) { - _ms->v = _ms->a; + _ms->state = _ms->a; return LIQUID_OK; } @@ -210,16 +198,28 @@ int bsequence_init_msequence(bsequence _bs, return LIQUID_OK; } +// get the length of the generator polynomial, g (m) +unsigned int msequence_get_genpoly_length(msequence _ms) +{ + return _ms->m; +} + // get the length of the sequence unsigned int msequence_get_length(msequence _ms) { return _ms->n; } +// get the generator polynomial, g +unsigned int msequence_get_genpoly(msequence _ms) +{ + return _ms->g; +} + // get the internal state of the sequence unsigned int msequence_get_state(msequence _ms) { - return _ms->v; + return _ms->state; } // set the internal state of the sequence @@ -229,7 +229,40 @@ int msequence_set_state(msequence _ms, // set internal state // NOTE: if state is set to zero, this will lock the sequence generator, // but let the user set this value if they wish - _ms->v = _a; + _ms->state = _a; return LIQUID_OK; } +// measure the period the shift register (should be 2^m-1 with a proper generator polynomial) +unsigned int msequence_measure_period(msequence _ms) +{ + // get current state + unsigned int s = msequence_get_state(_ms); + + // cycle through sequence and look for initial state + unsigned int i; + unsigned int period = 0; + for (i=0; i<_ms->n+1; i++) { + msequence_advance(_ms); + period++; + if (msequence_get_state(_ms)==s) + break; + } + + // assert that state has been returned + return period; +} + +// measure the period of a generator polynomial +unsigned int msequence_genpoly_period(unsigned int _g) +{ + msequence q = msequence_create_genpoly(_g); + if (q == NULL) { + liquid_error(LIQUID_EICONFIG,"msequence_genpoly_period(), invalid generator polynomial 0x%x\n", _g); + return 0; + } + unsigned int period = msequence_measure_period(q); + msequence_destroy(q); + return period; +} + diff --git a/src/sequence/tests/msequence_autotest.c b/src/sequence/tests/msequence_autotest.c index 21b62c9a78..2bb3dd72af 100644 --- a/src/sequence/tests/msequence_autotest.c +++ b/src/sequence/tests/msequence_autotest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007 - 2015 Joseph Gaeddert + * Copyright (c) 2007 - 2023 Joseph Gaeddert * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,7 @@ void autotest_bsequence_init_msequence() { // create and initialize m-sequence msequence ms = msequence_create_default(4); - + // create and initialize binary sequence on m-sequence bsequence bs; bs = bsequence_create( msequence_get_length(ms) ); @@ -48,7 +48,7 @@ void msequence_test_autocorrelation(unsigned int _m) // create and initialize m-sequence msequence ms = msequence_create_default(_m); unsigned int n = msequence_get_length(ms); - + // create and initialize first binary sequence on m-sequence bsequence bs1 = bsequence_create(n); bsequence_init_msequence(bs1, ms); @@ -80,15 +80,85 @@ void msequence_test_autocorrelation(unsigned int _m) msequence_destroy(ms); } -void autotest_msequence_m2() { msequence_test_autocorrelation(2); } // n = 3 -void autotest_msequence_m3() { msequence_test_autocorrelation(3); } // n = 7 -void autotest_msequence_m4() { msequence_test_autocorrelation(4); } // n = 15 -void autotest_msequence_m5() { msequence_test_autocorrelation(5); } // n = 31 -void autotest_msequence_m6() { msequence_test_autocorrelation(6); } // n = 63 -void autotest_msequence_m7() { msequence_test_autocorrelation(7); } // n = 127 -void autotest_msequence_m8() { msequence_test_autocorrelation(8); } // n = 255 -void autotest_msequence_m9() { msequence_test_autocorrelation(9); } // n = 511 -void autotest_msequence_m10() { msequence_test_autocorrelation(10); } // n = 1023 -void autotest_msequence_m11() { msequence_test_autocorrelation(11); } // n = 2047 -void autotest_msequence_m12() { msequence_test_autocorrelation(12); } // n = 4095 +void autotest_msequence_xcorr_m2() { msequence_test_autocorrelation(2); } // n = 3 +void autotest_msequence_xcorr_m3() { msequence_test_autocorrelation(3); } // n = 7 +void autotest_msequence_xcorr_m4() { msequence_test_autocorrelation(4); } // n = 15 +void autotest_msequence_xcorr_m5() { msequence_test_autocorrelation(5); } // n = 31 +void autotest_msequence_xcorr_m6() { msequence_test_autocorrelation(6); } // n = 63 +void autotest_msequence_xcorr_m7() { msequence_test_autocorrelation(7); } // n = 127 +void autotest_msequence_xcorr_m8() { msequence_test_autocorrelation(8); } // n = 255 +void autotest_msequence_xcorr_m9() { msequence_test_autocorrelation(9); } // n = 511 +void autotest_msequence_xcorr_m10() { msequence_test_autocorrelation(10); } // n = 1023 +void autotest_msequence_xcorr_m11() { msequence_test_autocorrelation(11); } // n = 2047 +void autotest_msequence_xcorr_m12() { msequence_test_autocorrelation(12); } // n = 4095 + +// helper function to test cyclic period of sequences +void msequence_test_period(unsigned int _m) +{ + // create and initialize m-sequence + msequence q = msequence_create_default(_m); + + // measure period and compare to expected + unsigned int n = (1U << _m) - 1; + unsigned int p = msequence_measure_period(q); + CONTEND_EQUALITY(p,n) + + // clean up objects + msequence_destroy(q); +} +void autotest_msequence_period_m2() { msequence_test_period(2); } +void autotest_msequence_period_m3() { msequence_test_period(3); } +void autotest_msequence_period_m4() { msequence_test_period(4); } +void autotest_msequence_period_m5() { msequence_test_period(5); } +void autotest_msequence_period_m6() { msequence_test_period(6); } +void autotest_msequence_period_m7() { msequence_test_period(7); } +void autotest_msequence_period_m8() { msequence_test_period(8); } +void autotest_msequence_period_m9() { msequence_test_period(9); } +void autotest_msequence_period_m10() { msequence_test_period(10); } +void autotest_msequence_period_m11() { msequence_test_period(11); } +void autotest_msequence_period_m12() { msequence_test_period(12); } +void autotest_msequence_period_m13() { msequence_test_period(13); } +void autotest_msequence_period_m14() { msequence_test_period(14); } +void autotest_msequence_period_m15() { msequence_test_period(15); } +void autotest_msequence_period_m16() { msequence_test_period(16); } +void autotest_msequence_period_m17() { msequence_test_period(17); } +void autotest_msequence_period_m18() { msequence_test_period(18); } +void autotest_msequence_period_m19() { msequence_test_period(19); } +void autotest_msequence_period_m20() { msequence_test_period(20); } +void autotest_msequence_period_m21() { msequence_test_period(21); } +void autotest_msequence_period_m22() { msequence_test_period(22); } +void autotest_msequence_period_m23() { msequence_test_period(23); } +void autotest_msequence_period_m24() { msequence_test_period(24); } +void autotest_msequence_period_m25() { msequence_test_period(25); } +void autotest_msequence_period_m26() { msequence_test_period(26); } +void autotest_msequence_period_m27() { msequence_test_period(27); } +void autotest_msequence_period_m28() { msequence_test_period(28); } +void autotest_msequence_period_m29() { msequence_test_period(29); } +void autotest_msequence_period_m30() { msequence_test_period(30); } +void autotest_msequence_period_m31() { msequence_test_period(31); } + +void autotest_msequence_config() +{ +#if LIQUID_STRICT_EXIT + AUTOTEST_WARN("skipping firfilt config test with strict exit enabled\n"); + return; +#endif +#if !LIQUID_SUPPRESS_ERROR_OUTPUT + fprintf(stderr,"warning: ignore potential errors here; checking for invalid configurations\n"); +#endif + // check invalid configurations + CONTEND_ISNULL(msequence_create (100, 0, 0)) + CONTEND_ISNULL(msequence_create_default( 32)) // too long + CONTEND_ISNULL(msequence_create_genpoly( 0)) + + // create proper object and test configurations + msequence q = msequence_create_genpoly(LIQUID_MSEQUENCE_GENPOLY_M11); + + CONTEND_EQUALITY(LIQUID_OK, msequence_print(q)) + CONTEND_EQUALITY(1, msequence_get_state(q)) + CONTEND_EQUALITY(LIQUID_OK, msequence_set_state(q, 0x8a)) + CONTEND_EQUALITY(0x8a, msequence_get_state(q)) + + msequence_destroy(q); +}