From 271756b4c785a646f0e3a863520f7579dbd51682 Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Fri, 6 Aug 2021 09:57:08 +0300 Subject: [PATCH] Some cleanup --- decode_ft8.c | 4 +-- ft8/constants.h | 66 ++++++++++++++++++++++++------------------------- ft8/decode.c | 45 +++++++++++++++------------------ ft8/decode.h | 49 ++++++++++++++++++++++++------------ ft8/encode.c | 43 +++++++++++++++----------------- ft8/ldpc.c | 31 +++++++---------------- ft8/unpack.c | 1 + 7 files changed, 117 insertions(+), 122 deletions(-) diff --git a/decode_ft8.c b/decode_ft8.c index e288c0a..0c07695 100644 --- a/decode_ft8.c +++ b/decode_ft8.c @@ -221,7 +221,7 @@ int main(int argc, char **argv) // Find top candidates by Costas sync score and localize them in time and frequency Candidate candidate_list[kMax_candidates]; - int num_candidates = find_sync(&power, kFT8_Costas_pattern, kMax_candidates, candidate_list, kMin_score); + int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score); // TODO: sort the candidates by strongest sync first? @@ -238,7 +238,7 @@ int main(int argc, char **argv) float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev; float log174[FT8_N]; - extract_likelihood(&power, cand, kFT8_Gray_map, log174); + extract_likelihood(&power, cand, log174); for (int k = 100; k < 174; ++k) { diff --git a/ft8/constants.h b/ft8/constants.h index 80d4c41..680cdb5 100644 --- a/ft8/constants.h +++ b/ft8/constants.h @@ -4,49 +4,47 @@ #include // Define FT8 symbol counts -#define FT8_ND (58) // Data symbols -#define FT8_NS (21) // Sync symbols (3 @ Costas 7x7) -#define FT8_NN (FT8_NS + FT8_ND) // Total channel symbols (79) +#define FT8_ND (58) ///< Data symbols +#define FT8_NS (21) ///< Sync symbols (3 @ Costas 7x7) +#define FT8_NN (FT8_NS + FT8_ND) ///< Total channel symbols (79) -// Define the LDPC sizes -#define FT8_N (174) // Number of bits in the encoded message (payload + checksum) -#define FT8_K (91) // Number of payload bits -#define FT8_M (FT8_N - FT8_K) // Number of checksum bits -#define FT8_K_BYTES ((FT8_K + 7) / 8) // Number of whole bytes needed to store K bits +// Define LDPC parameters +#define FT8_N (174) ///< Number of bits in the encoded message (payload + checksum) +#define FT8_K (91) ///< Number of payload bits +#define FT8_M (FT8_N - FT8_K) ///< Number of LDPC checksum bits +#define FT8_N_BYTES ((FT8_N + 7) / 8) ///< Number of whole bytes needed to store N bits (full message) +#define FT8_K_BYTES ((FT8_K + 7) / 8) ///< Number of whole bytes needed to store K bits (payload only) // Define CRC parameters -#define CRC_POLYNOMIAL ((uint16_t)0x2757u) // CRC-14 polynomial without the leading (MSB) 1 -#define CRC_WIDTH (14) +#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1 +#define FT8_CRC_WIDTH (14) -// Costas 7x7 tone pattern -extern const uint8_t kCostas_map[7]; +/// Costas 7x7 tone pattern for synchronization +extern const uint8_t kFT8_Costas_pattern[7]; -// Gray code map -extern const uint8_t kGray_map[8]; +/// Gray code map to encode 8 symbols (tones) +extern const uint8_t kFT8_Gray_map[8]; -// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) -extern const uint8_t kGenerator[FT8_M][FT8_K_BYTES]; +/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +extern const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES]; -// Column order (permutation) in which the bits in codeword are stored -// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted) -extern const uint8_t kColumn_order[FT8_N]; +/// Column order (permutation) in which the bits in codeword are stored +/// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted) +extern const uint8_t kFT8_LDPC_column_order[FT8_N]; -// this is the LDPC(174,91) parity check matrix. -// 83 rows. -// each row describes one parity check. -// each number is an index into the codeword (1-origin). -// the codeword bits mentioned in each row must xor to zero. -// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. -extern const uint8_t kNm[FT8_M][7]; +/// LDPC(174,91) parity check matrix, containing 83 rows, +/// each row describes one parity check, +/// each number is an index into the codeword (1-origin). +/// The codeword bits mentioned in each row must xor to zero. +/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. +extern const uint8_t kFT8_LDPC_Nm[FT8_M][7]; -// Mn from WSJT-X's bpdecode174.f90. -// each row corresponds to a codeword bit. -// the numbers indicate which three parity -// checks (rows in Nm) refer to the codeword bit. -// 1-origin. -extern const uint8_t kMn[FT8_N][3]; +/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit. +/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit. +/// The numbers use 1 as the origin (first entry). +extern const uint8_t kFT8_LDPC_Mn[FT8_N][3]; -// Number of rows (columns in C/C++) in the array Nm. -extern const uint8_t kNrw[FT8_M]; +/// Number of rows (columns in C/C++) in the array Nm. +extern const uint8_t kFT8_LDPC_num_rows[FT8_M]; #endif // _INCLUDE_CONSTANTS_H_ diff --git a/ft8/decode.c b/ft8/decode.c index dea9351..803a85d 100644 --- a/ft8/decode.c +++ b/ft8/decode.c @@ -6,19 +6,17 @@ static float max2(float a, float b); static float max4(float a, float b, float c, float d); -static void heapify_down(Candidate *heap, int heap_size); -static void heapify_up(Candidate *heap, int heap_size); -static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174); -static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174); +static void heapify_down(Candidate heap[], int heap_size); +static void heapify_up(Candidate heap[], int heap_size); +static void decode_symbol(const uint8_t *power, int bit_idx, float *log174); +static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, int bit_idx, float *log174); static int get_index(const MagArray *power, int block, int time_sub, int freq_sub, int bin) { return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin; } -// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) -// We treat and organize the candidate list as a min-heap (empty initially). -int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score) +int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int min_score) { int heap_size = 0; int num_alt = power->time_osr * power->freq_osr; @@ -48,18 +46,17 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates if (time_offset + k + m >= power->num_blocks) break; - // int offset = ((time_offset + k + m) * num_alt + alt) * power->num_bins + freq_offset; int offset = get_index(power, time_offset + k + m, time_sub, freq_sub, freq_offset); const uint8_t *p8 = power->mag + offset; // Weighted difference between the expected and all other symbols // Does not work as well as the alternative score below - // score += 8 * p8[sync_map[k]] - + // score += 8 * p8[sync_pattern[k]] - // p8[0] - p8[1] - p8[2] - p8[3] - // p8[4] - p8[5] - p8[6] - p8[7]; // Check only the neighbors of the expected symbol frequency- and time-wise - int sm = sync_map[k]; // Index of the expected bin + int sm = kFT8_Costas_pattern[k]; // Index of the expected bin if (sm > 0) { // look at one frequency bin lower @@ -119,9 +116,7 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates return heap_size; } -// Compute log likelihood log(p(1) / p(0)) of 174 message bits -// for later use in soft-decision LDPC decoding -void extract_likelihood(const MagArray *power, const Candidate *cand, const uint8_t *code_map, float *log174) +void extract_likelihood(const MagArray *power, const Candidate *cand, float *log174) { int num_alt = power->time_osr * power->freq_osr; int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset); @@ -139,7 +134,7 @@ void extract_likelihood(const MagArray *power, const Candidate *cand, const uint // Pointer to 8 bins of the current symbol const uint8_t *ps = power->mag + (offset + sym_idx * num_alt * power->num_bins); - decode_symbol(ps, code_map, bit_idx, log174); + decode_symbol(ps, bit_idx, log174); } // Compute the variance of log174 @@ -172,7 +167,7 @@ static float max4(float a, float b, float c, float d) return max2(max2(a, b), max2(c, d)); } -static void heapify_down(Candidate *heap, int heap_size) +static void heapify_down(Candidate heap[], int heap_size) { // heapify from the root down int current = 0; @@ -202,7 +197,7 @@ static void heapify_down(Candidate *heap, int heap_size) } } -static void heapify_up(Candidate *heap, int heap_size) +static void heapify_up(Candidate heap[], int heap_size) { // heapify from the last node up int current = heap_size - 1; @@ -222,14 +217,14 @@ static void heapify_up(Candidate *heap, int heap_size) } // Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol) -static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174) +static void decode_symbol(const uint8_t *power, int bit_idx, float *log174) { // Cleaned up code for the simple case of n_syms==1 float s2[8]; for (int j = 0; j < 8; ++j) { - s2[j] = (float)power[code_map[j]]; + s2[j] = (float)power[kFT8_Gray_map[j]]; } log174[bit_idx + 0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]); @@ -238,7 +233,7 @@ static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit } // Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once -static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174) +static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, int bit_idx, float *log174) { // The following section implements what seems to be multiple-symbol decode at one go, // corresponding to WSJT-X's ft8b.f90. Experimentally found not to be any better than @@ -254,20 +249,20 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, int j1 = j & 0x07; if (n_syms == 1) { - s2[j] = (float)power[code_map[j1]]; + s2[j] = (float)power[kFT8_Gray_map[j1]]; continue; } int j2 = (j >> 3) & 0x07; if (n_syms == 2) { - s2[j] = (float)power[code_map[j2]]; - s2[j] += (float)power[code_map[j1] + 4 * num_bins]; + s2[j] = (float)power[kFT8_Gray_map[j2]]; + s2[j] += (float)power[kFT8_Gray_map[j1] + 4 * num_bins]; continue; } int j3 = (j >> 6) & 0x07; - s2[j] = (float)power[code_map[j3]]; - s2[j] += (float)power[code_map[j2] + 4 * num_bins]; - s2[j] += (float)power[code_map[j1] + 8 * num_bins]; + s2[j] = (float)power[kFT8_Gray_map[j3]]; + s2[j] += (float)power[kFT8_Gray_map[j2] + 4 * num_bins]; + s2[j] += (float)power[kFT8_Gray_map[j1] + 8 * num_bins]; } // No need to go back to linear scale any more. Works better in dB. // for (int j = 0; j < n_tones; ++j) { diff --git a/ft8/decode.h b/ft8/decode.h index e56cb81..1a27965 100644 --- a/ft8/decode.h +++ b/ft8/decode.h @@ -3,30 +3,47 @@ #include +/// Input structure to find_sync() function. This structure describes stored waterfall data over the whole message slot. +/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution. +/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds. +/// Values time_osr > 1 mean each symbol is further subdivided in time. +/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing. +/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis. typedef struct { - int num_blocks; // number of total blocks (symbols) - int num_bins; // number of FFT bins - int time_osr; // number of time subdivisions - int freq_osr; // number of frequency subdivisions - uint8_t *mag; // FFT magnitudes as [blocks][time_sub][freq_sub][num_bins] + int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods + int num_bins; ///< number of FFT bins in terms of 6.25 Hz + int time_osr; ///< number of time subdivisions + int freq_osr; ///< number of frequency subdivisions + uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins] } MagArray; +/// Output structure of find_sync() and input structure of extract_likelihood(). +/// Holds the position of potential start of a message in time and frequency. typedef struct { - int16_t score; - int16_t time_offset; - int16_t freq_offset; - uint8_t time_sub; - uint8_t freq_sub; + int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood) + int16_t time_offset; ///< Index of the time block + int16_t freq_offset; ///< Index of the frequency bin + uint8_t time_sub; ///< Index of the time subdivision used + uint8_t freq_sub; ///< Index of the frequency subdivision used } Candidate; -// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) -// We treat and organize the candidate list as a min-heap (empty initially). -int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score); +/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) +/// We treat and organize the candidate list as a min-heap (empty initially). +/// @param[in] power Waterfall data collected during message slot +/// @param[in] sync_pattern Synchronization pattern +/// @param[in] num_candidates Number of maximum candidates (size of heap array) +/// @param[in,out] heap Array of Candidate type entries (with num_candidates allocated entries) +/// @param[in] min_score Minimal score allowed for trimming unlikely candidates (can be zero for no effect) +/// @return Number of candidates filled in the heap +int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int min_score); -// Compute log likelihood log(p(1) / p(0)) of 174 message bits -// for later use in soft-decision LDPC decoding -void extract_likelihood(const MagArray *power, const Candidate *cand, const uint8_t *code_map, float *log174); +/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding +/// @param[in] power Waterfall data collected during message slot +/// @param[in] cand Candidate to extract the message from +/// @param[in] code_map Symbol encoding map +/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits +void extract_likelihood(const MagArray *power, const Candidate *cand, float *log174); #endif // _INCLUDE_DECODE_H_ diff --git a/ft8/encode.c b/ft8/encode.c index a448415..65c52a8 100644 --- a/ft8/encode.c +++ b/ft8/encode.c @@ -3,15 +3,15 @@ #include -#define TOPBIT (1 << (FT8_CRC_WIDTH - 1)) +#define TOPBIT (1u << (FT8_CRC_WIDTH - 1)) // Returns 1 if an odd number of bits are set in x, zero otherwise uint8_t parity8(uint8_t x) { - x ^= x >> 4; // a b c d ae bf cg dh - x ^= x >> 2; // a b ac bd cae dbf aecg bfdh - x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh - return (x)&1; + x ^= x >> 4; // a b c d ae bf cg dh + x ^= x >> 2; // a b ac bd cae dbf aecg bfdh + x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh + return x % 2; // modulo 2 } // Encode a 91-bit message and return a 174-bit codeword. @@ -37,17 +37,18 @@ void encode174(const uint8_t *message, uint8_t *codeword) // printf("\n"); // Fill the codeword with message and zeros, as we will only update binary ones later - for (int j = 0; j < (7 + FT8_N) / 8; ++j) + for (int j = 0; j < FT8_N_BYTES; ++j) { codeword[j] = (j < FT8_K_BYTES) ? message[j] : 0; } - uint8_t col_mask = (0x80 >> (FT8_K % 8)); // bitmask of current byte - uint8_t col_idx = FT8_K_BYTES - 1; // index into byte array + // Compute the byte index and bit mask for the first checksum bit + uint8_t col_mask = (0x80u >> (FT8_K % 8u)); // bitmask of current byte + uint8_t col_idx = FT8_K_BYTES - 1; // index into byte array - // Compute the first part of itmp (1:FT8_M) and store the result in codeword + // Compute the LDPC checksum bits and store them in codeword for (int i = 0; i < FT8_M; ++i) - { // do i=1,FT8_M + { // Fast implementation of bitwise multiplication and parity checking // Normally nsum would contain the result of dot product between message and kFT8_LDPC_generator[i], // but we only compute the sum modulo 2. @@ -57,25 +58,21 @@ void encode174(const uint8_t *message, uint8_t *codeword) uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication) nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) } - // Check if we need to set a bit in codeword + + // Set the current checksum bit in codeword if nsum is odd if (nsum % 2) - { // pchecks(i)=mod(nsum,2) + { codeword[col_idx] |= col_mask; } + // Update the byte index and bit mask for the next checksum bit col_mask >>= 1; if (col_mask == 0) { - col_mask = 0x80; + col_mask = 0x80u; ++col_idx; } } - - // printf("Result "); - // for (int i = 0; i < (FT8_N + 7) / 8; ++i) { - // printf("%02x ", codeword[i]); - // } - // printf("\n"); } // Compute 14-bit CRC for a sequence of given number of bits @@ -108,7 +105,7 @@ uint16_t ft8_crc(uint8_t *message, int num_bits) } } - return remainder & ((1 << FT8_CRC_WIDTH) - 1); + return remainder & ((TOPBIT << 1) - 1u); } // Generate FT8 tone sequence from payload data @@ -123,7 +120,7 @@ void genft8(const uint8_t *payload, uint8_t *itone) a91[i] = payload[i]; // Clear 3 bits after the payload to make 80 bits - a91[9] &= 0xF8; + a91[9] &= 0xF8u; a91[10] = 0; a91[11] = 0; @@ -149,10 +146,10 @@ void genft8(const uint8_t *payload, uint8_t *itone) int k = 7; // Skip over the first set of Costas symbols - uint8_t mask = 0x80; + uint8_t mask = 0x80u; int i_byte = 0; for (int j = 0; j < FT8_ND; ++j) - { // do j=1,FT8_ND + { if (j == 29) { k += 7; // Skip over the second set of Costas symbols diff --git a/ft8/ldpc.c b/ft8/ldpc.c index a4da265..07c0106 100644 --- a/ft8/ldpc.c +++ b/ft8/ldpc.c @@ -19,6 +19,8 @@ static int ldpc_check(uint8_t codeword[]); static float fast_tanh(float x); static float fast_atanh(float x); +static float pltanh(float x); +static float platanh(float x); // Packs a string of bits each represented as a zero/non-zero byte in plain[], // as a string of packed bits starting from the MSB of the first byte of packed[] @@ -163,15 +165,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) int nclast = 0; int ncnt = 0; - // initialize messages to checks - for (int i = 0; i < FT8_M; ++i) - { - for (int j = 0; j < kFT8_LDPC_num_rows[i]; ++j) - { - toc[i][j] = codeword[kFT8_LDPC_Nm[i][j] - 1]; - } - } - + // initialize message data for (int i = 0; i < FT8_N; ++i) { for (int j = 0; j < 3; ++j) @@ -211,37 +205,30 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) for (int j = 0; j < kFT8_LDPC_num_rows[i]; ++j) { int ibj = kFT8_LDPC_Nm[i][j] - 1; - toc[i][j] = zn[ibj]; + float Tnm = zn[ibj]; for (int kk = 0; kk < 3; ++kk) { // subtract off what the bit had received from the check - if (kFT8_LDPC_Mn[ibj][kk] - 1 == i) + if ((kFT8_LDPC_Mn[ibj][kk] - 1) == i) { - toc[i][j] -= tov[ibj][kk]; + Tnm -= tov[ibj][kk]; break; } } + toc[i][j] = fast_tanh(-Tnm / 2); } } // send messages from check nodes to variable nodes - for (int i = 0; i < FT8_M; ++i) - { - for (int j = 0; j < kFT8_LDPC_num_rows[i]; ++j) - { - toc[i][j] = fast_tanh(-toc[i][j] / 2); - } - } - for (int i = 0; i < FT8_N; ++i) { for (int j = 0; j < 3; ++j) { - int ichk = kFT8_LDPC_Mn[i][j] - 1; // kFT8_LDPC_Mn(:,j) are the checks that include bit j + int ichk = kFT8_LDPC_Mn[i][j] - 1; float Tmn = 1.0f; for (int k = 0; k < kFT8_LDPC_num_rows[ichk]; ++k) { - if (kFT8_LDPC_Nm[ichk][k] - 1 != i) + if ((kFT8_LDPC_Nm[ichk][k] - 1) != i) { Tmn *= toc[ichk][k]; } diff --git a/ft8/unpack.c b/ft8/unpack.c index d8cb1cf..412b098 100644 --- a/ft8/unpack.c +++ b/ft8/unpack.c @@ -393,6 +393,7 @@ int unpack77(const uint8_t *a77, char *message) char field1[14]; char field2[14]; char field3[7]; + int rc = unpack77_fields(a77, field1, field2, field3); if (rc < 0) return rc;