pull/12/merge
Karlis Goba 2021-08-06 09:57:08 +03:00
rodzic 74ff78663b
commit 271756b4c7
7 zmienionych plików z 117 dodań i 122 usunięć

Wyświetl plik

@ -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)
{

Wyświetl plik

@ -4,49 +4,47 @@
#include <stdint.h>
// 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_

Wyświetl plik

@ -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) {

Wyświetl plik

@ -3,30 +3,47 @@
#include <stdint.h>
/// 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_

Wyświetl plik

@ -3,15 +3,15 @@
#include <stdio.h>
#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

Wyświetl plik

@ -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];
}

Wyświetl plik

@ -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;