├── CMakeLists.txt
├── README.md
├── build
├── rightsignal.raw
└── turbosignal.raw
├── ccsds-guess.c
├── ccsds-guess.h
├── ccsds-modem.c
├── ccsds-modem.h
├── ccsds-tc.c
├── ccsds-tc.h
├── ccsds-tool.c
├── correlator.c
├── correlator.h
├── defs.h
├── lfsr.c
├── lfsr.h
└── test.c
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | set(
4 | CCSDS_TC_SOURCES
5 | ccsds-tc.c)
6 |
7 | set(
8 | CCSDS_TC_HEADERS
9 | ccsds-tc.h)
10 |
11 | project(ccsds_tc VERSION 0.1)
12 |
13 | add_executable(
14 | ccsds-tc-test
15 | ${CCSDS_TC_SOURCES}
16 | ${CCSDS_TC_HEADERS} test.c)
17 |
18 | add_executable(
19 | ccsds-tc-test-fast
20 | ${CCSDS_TC_SOURCES}
21 | ${CCSDS_TC_HEADERS} test.c)
22 |
23 | target_link_libraries(
24 | ccsds-tc-test
25 | m)
26 |
27 | target_link_libraries(
28 | ccsds-tc-test-fast
29 | m)
30 |
31 | target_compile_options(
32 | ccsds-tc-test-fast PRIVATE
33 | -DCCSDS_TC_INT_ARITHMETICS -DCCSDS_TC_NO_MAXSTAR -O9)
34 |
35 | add_executable(
36 | ccsds-tool
37 | ${CCSDS_TC_SOURCES}
38 | ${CCSDS_TC_HEADERS}
39 | ccsds-guess.c
40 | ccsds-guess.h
41 | ccsds-modem.c
42 | ccsds-modem.h
43 | ccsds-tool.c
44 | correlator.c
45 | correlator.h
46 | defs.h
47 | lfsr.c
48 | lfsr.h)
49 |
50 | target_link_libraries(
51 | ccsds-tool
52 | m)
53 |
54 | target_compile_options(
55 | ccsds-tool PRIVATE
56 | -DCCSDS_TC_NO_MAXSTAR -O9)
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ccsds-tc
2 | `ccsds-tc` is a small project that attempts to systematize the decoding of space packets as received by ground stations of the Amateur DSN.
3 | This is basically EB3FRN's fault for talking me into this :P
4 |
5 | The project includes a custom implementation of small BCJR-based decoder for CCSDS turbo codes (according to CCSDS 131.0-B-3), a descrambler
6 | and the logic to compute the CRC16 of decoded frames.
7 |
8 | ## Build
9 | This is the typical CMake project, just change to build, run `cmake` and `make`:
10 |
11 | ```
12 | % cd build
13 | % cmake ..
14 | % make
15 | ```
16 |
17 | ## Running the project
18 | The program you may be looking for is `ccsds-tool`. It supports 2 execution modes:
19 | * **Guess mode**, in which the tool will open an already demodulated baseband signal at the channel's symbol rate and attempt to blindly guess the channel parameters, and
20 | * **Decoding mode**, in which you pass the decoder parameters to tool in the command line, and it attempts to decode the input in real time.
21 |
22 | By default, the tool operates in decoding mode, reading 32-bit complex float samples from the standard input and writing decoded (and CRC-checked) frames
23 | to the standard output. Samples can be read from a file or a TCP socket as well:
24 |
25 | ```
26 | % ./ccsds-tool -f samples-LLR.raw -o frames.bin
27 | CCSDS tool v0.1 for the Amateur DSN by EA1IYR
28 | (c) 2021 Gonzalo J. Carracedo - https://actinid.org
29 | Code rate: 1/6
30 | Block length: 8920 bits
31 | Turbocode iters: 1
32 | Channel: Q
33 | Sync SNR: 19.03 dB
34 | Input file: /tmp/samples-LLR.raw
35 | Output file: frames.bin
36 | Decode rate: 882.88 kbps
37 | ./ccsds-tool: 82510 bytes decoded
38 | ```
39 |
40 | Guess mode operates from existing files only and it is activated by means of the command line option `-g`:
41 |
42 | ```
43 | % ./ccsds-tool -g samples-LLR.raw
44 | Looking for syncwords for r = 1/2
45 | Looking for syncwords for r = 1/3
46 | Looking for syncwords for r = 1/4
47 | Looking for syncwords for r = 1/6
48 | Candidate CCSDS turbocode found
49 | Code rate: 1/6
50 | Channel Q: 94 frames
51 | Frame length: 8920 bit
52 | ```
53 |
54 | Details on other tool options can be obtained by running `ccsds-tool --help`
55 |
56 |
--------------------------------------------------------------------------------
/build/rightsignal.raw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BatchDrake/ccsds-tc/791a6638092445816147c99b3406b56f36827e86/build/rightsignal.raw
--------------------------------------------------------------------------------
/build/turbosignal.raw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BatchDrake/ccsds-tc/791a6638092445816147c99b3406b56f36827e86/build/turbosignal.raw
--------------------------------------------------------------------------------
/ccsds-guess.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include "ccsds-guess.h"
21 | #include "correlator.h"
22 |
23 | #include
24 | #include
25 |
26 | #define CCSDS_TC_SYNC_RATE_1_2 "0000001101000111011101101100011100100111001010001001010110110000"
27 | #define CCSDS_TC_SYNC_RATE_1_3 "001001011101010111000000110011101000100110010000111101101100100101000110000110111111011110011100"
28 | #define CCSDS_TC_SYNC_RATE_1_4 "00000011010001110111011011000111001001110010100010010101101100001111110010111000100010010011100011011000110101110110101001001111"
29 | #define CCSDS_TC_SYNC_RATE_1_6 "001001011101010111000000110011101000100110010000111101101100100101000110000110111111011110011100110110100010101000111111001100010111011001101111000010010011011010111001111001000000100001100011"
30 |
31 | #define CCSDS_TC_SYNCWORD_COUNT 4
32 |
33 | static const struct ccsds_tc_syncword g_syncwords[] = {
34 | {CCSDS_TC_SYNC_RATE_1_2, 2},
35 | {CCSDS_TC_SYNC_RATE_1_3, 3},
36 | {CCSDS_TC_SYNC_RATE_1_4, 4},
37 | {CCSDS_TC_SYNC_RATE_1_6, 6}
38 | };
39 |
40 | void
41 | ccsds_tc_report_init(struct ccsds_tc_report *self, const struct ccsds_tc_syncword *tcs)
42 | {
43 | memset(self, 0, sizeof(struct ccsds_tc_report));
44 | self->tc_params = tcs;
45 | }
46 |
47 | const struct ccsds_tc_syncword *
48 | ccsds_tc_syncword_lookup(unsigned int rate_inv)
49 | {
50 | unsigned int i;
51 |
52 | for (i = 0; i < 4; ++i)
53 | if (g_syncwords[i].rate_inv == rate_inv)
54 | return &g_syncwords[i];
55 |
56 | return NULL;
57 | }
58 |
59 | static inline unsigned int
60 | ccsds_tc_report_get_block_length(const struct ccsds_tc_report *self, unsigned int L)
61 | {
62 | unsigned int payload;
63 | unsigned int len;
64 | unsigned int rateinv = self->tc_params->rate_inv;
65 |
66 | if (L % rateinv != 0)
67 | return 0;
68 |
69 | L /= rateinv;
70 |
71 | payload = L - 32 - 4;
72 |
73 | if (payload % (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3) == 0) {
74 | len = payload / (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3);
75 | if (len < 1 || len > 5)
76 | return 0;
77 |
78 | return len;
79 | }
80 |
81 | return 0;
82 | }
83 |
84 | bool
85 | ccsds_tc_report_find_frames(
86 | struct ccsds_tc_report *self,
87 | const float complex *__restrict x,
88 | unsigned int L,
89 | float SNR,
90 | bool channel)
91 | {
92 | correlator_t correlator = correlator_INITIALIZER;
93 | unsigned int i;
94 | unsigned int curr, last;
95 | unsigned int step;
96 | unsigned int block_info_len;
97 |
98 | bool ok = false;
99 |
100 | step = strlen(self->tc_params->syncword);
101 |
102 | if (L < 2 * step)
103 | return true;
104 |
105 | if (!correlator_init_from_string(&correlator, self->tc_params->syncword)) {
106 | fprintf(
107 | stderr,
108 | "%s: failed to initialize correlator for 1/%d syncword\n",
109 | __FUNCTION__,
110 | self->tc_params->rate_inv);
111 | goto done;
112 | }
113 |
114 | last = L;
115 |
116 | for (i = 0; i < L - 2 * step; i += step) {
117 | if (correlator_find_complex(&correlator, x + i, 2 * step, SNR, channel)) {
118 | curr = correlator_get_pos(&correlator) + i;
119 |
120 | if (
121 | last < curr
122 | && (block_info_len = ccsds_tc_report_get_block_length(self, curr - last)) > 0) {
123 | printf(
124 | "%c channel: Potential 1/%d CCSDS frame @ pos = %7d (%7d sice last) (SNR: %.1f dB)\r",
125 | channel ? 'Q' : 'I',
126 | self->tc_params->rate_inv,
127 | correlator_get_pos(&correlator) + i,
128 | curr - last,
129 | 10 * log10(correlator_get_snr(&correlator)));
130 |
131 | ++self->histogram[block_info_len];
132 |
133 | if (channel)
134 | ++self->count_q;
135 | else
136 | ++self->count_i;
137 |
138 | fflush(stdout);
139 | }
140 |
141 | last = curr;
142 | }
143 | }
144 |
145 | if ((self->count_i + self->count_q) > 0)
146 | printf("\033[2K");
147 |
148 | ok = true;
149 |
150 | done:
151 | correlator_finalize(&correlator);
152 |
153 | return ok;
154 | }
155 |
156 | bool
157 | ccsds_tc_report_find_best(
158 | struct ccsds_tc_report *self,
159 | const float complex *__restrict x,
160 | unsigned int L,
161 | float SNR)
162 | {
163 | struct ccsds_tc_report report, best_report;
164 | unsigned int i;
165 | bool ok = false;
166 |
167 | memset(&best_report, 0, sizeof(struct ccsds_tc_report));
168 |
169 | for (i = 0; i < CCSDS_TC_SYNCWORD_COUNT; ++i) {
170 | printf("Looking for syncwords for r = 1/%d\n", g_syncwords[i].rate_inv);
171 | ccsds_tc_report_init(&report, &g_syncwords[i]);
172 |
173 | ccsds_tc_report_find_frames(&report, x, L, SNR, false);
174 | ccsds_tc_report_find_frames(&report, x, L, SNR, true);
175 |
176 | if (ccsds_tc_report_count_frames(&report)
177 | > ccsds_tc_report_count_frames(&best_report))
178 | best_report = report;
179 | }
180 |
181 | if (ccsds_tc_report_count_frames(&best_report) > 0) {
182 | printf("Candidate CCSDS turbocode found\n");
183 | printf(" Code rate: 1/%d\n", best_report.tc_params->rate_inv);
184 | if (best_report.count_i > best_report.count_q)
185 | printf(" Channel I: %d frames\n", best_report.count_i);
186 | else
187 | printf(" Channel Q: %d frames\n", best_report.count_q);
188 |
189 | printf(
190 | " Frame length: %d bits\n",
191 | ccsds_tc_report_get_candidate_len(&best_report)
192 | * CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3);
193 | } else {
194 | printf("No valid CCSDS frames found\n");
195 | }
196 |
197 | *self = best_report;
198 |
199 | ok = true;
200 |
201 | done:
202 | return ok;
203 | }
204 |
--------------------------------------------------------------------------------
/ccsds-guess.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #ifndef _CCSDS_GUESS_H
21 | #define _CCSDS_GUESS_H
22 |
23 | #include
24 | #include
25 |
26 | #include
27 |
28 | #define CCSDS_TC_BLOCK_SIZE_COUNT 6
29 | #define CCSDS_TC_MINIMUM_BLOCK_LENGTH 223
30 |
31 | struct ccsds_tc_syncword {
32 | const char *syncword;
33 | unsigned int rate_inv;
34 | };
35 |
36 | const struct ccsds_tc_syncword *ccsds_tc_syncword_lookup(
37 | unsigned int rate_inv);
38 |
39 | /*
40 | * CCSDS enables only 5 different types of block information length,
41 | * of which 3 is forbidden.
42 | */
43 | struct ccsds_tc_report {
44 | const struct ccsds_tc_syncword *tc_params;
45 | unsigned int histogram[CCSDS_TC_BLOCK_SIZE_COUNT];
46 | unsigned int count_i;
47 | unsigned int count_q;
48 | };
49 |
50 | static inline unsigned int
51 | ccsds_tc_report_count_frames(const struct ccsds_tc_report *self)
52 | {
53 | return self->count_i + self->count_q;
54 | }
55 |
56 | static inline unsigned int
57 | ccsds_tc_report_get_candidate_len(const struct ccsds_tc_report *self)
58 | {
59 | unsigned int count, max;
60 | unsigned int i;
61 |
62 | count = 0;
63 | max = 0;
64 |
65 | for (i = 0; i < CCSDS_TC_BLOCK_SIZE_COUNT; ++i)
66 | if (self->histogram[i] > count) {
67 | max = i;
68 | count = self->histogram[i];
69 | }
70 |
71 | return max;
72 | }
73 |
74 | void ccsds_tc_report_init(
75 | struct ccsds_tc_report *self,
76 | const struct ccsds_tc_syncword *tcs);
77 |
78 | bool ccsds_tc_report_find_frames(
79 | struct ccsds_tc_report *self,
80 | const float complex *x,
81 | unsigned int L,
82 | float SNR,
83 | bool channel);
84 |
85 | bool ccsds_tc_report_find_best(
86 | struct ccsds_tc_report *self,
87 | const float complex *x,
88 | unsigned int L,
89 | float SNR);
90 |
91 | #endif /* _CCSDS_GUESS_H */
92 |
93 |
--------------------------------------------------------------------------------
/ccsds-modem.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 |
31 | #include "ccsds-modem.h"
32 | #include "ccsds-guess.h"
33 |
34 | #include "lfsr.h"
35 | #include "defs.h"
36 |
37 | #define CCSDS_SCRAMBLER_LENGTH 255
38 | #define CCSDS_FECF_SEED 0xffff
39 |
40 | static bool *g_scrambler = NULL;
41 |
42 | /*
43 | * "Clarification for CCSDS CRC-16 Computation Algorithm",
44 | * code by Jackson Pang, Kenneth Andres and J. Leigh Torgerson
45 | */
46 |
47 | static uint16_t g_crc_table[256] = {
48 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
49 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
50 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
51 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
52 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
53 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
54 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
55 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
56 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
57 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
58 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
59 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
60 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
61 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
62 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
63 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
64 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
65 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
66 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
67 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
68 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
69 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
70 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
71 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
72 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
73 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
74 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
75 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
76 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
77 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
78 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
79 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
80 | };
81 |
82 | /***********************************************************************
83 | *
84 | * FUNCTION:
85 | * ccsds_fcef_crc16
86 | *
87 | * INPUTS:
88 | * seed - (uint16_t) initial value of check bits.
89 | * buf - (uint8_t *) pointer to the buffer of
90 | * data over which you wish to generate check
91 | * bits.
92 | * len - (int) number to bytes of data in the buffer.
93 | *
94 | * OUTPUTS:
95 | *
96 | * RETURNS:
97 | * - (uint16_t) the checkbits.
98 | *
99 | * EXTERNALLY READ:
100 | * g_crc_table - (uint16_t)[256] the lookup table for the CCITT SDLC
101 | * generator polynomial (local to this module).
102 | *
103 | * EXTERNALLY MODIFIED:
104 | *
105 | * DESCRIPTION:
106 | * This function implements CRC generation with the CCITT SDLC error
107 | * polynomial (X16 + X12 + X5 + 1). You must provide it with an
108 | * initial seed value, a pointer to a data buffer, and the byte length
109 | * of the data buffer. It will return the unsigned 16-bit CRC.
110 | *
111 | * You may use this function to generate a CRC over data in scattered
112 | * storage by making multiple calls to it. Just make sure that you
113 | * pass a seed of 0xFFFF on the first call. On subsequent calls, pass
114 | * a seed containing the return value of the previous call.
115 | */
116 |
117 | CCSDS_TC_INLINE uint16_t
118 | ccsds_fcef_crc16(uint16_t seed, const uint8_t *buf, unsigned int len)
119 | {
120 | uint16_t crc = seed;
121 | const uint8_t *p = buf;
122 |
123 | while (len--)
124 | crc = g_crc_table[((crc >> 8) ^ *p++) & 0xff] ^ (crc << 8);
125 |
126 | return crc;
127 | }
128 |
129 | const bool *
130 | ccsds_modem_get_scrambler(void)
131 | {
132 | unsigned int taps[] = {0, 3, 5, 7, 8};
133 | unsigned int i;
134 | bool *result = NULL;
135 | lfsr_t *lfsr = NULL;
136 |
137 | if (g_scrambler == NULL) {
138 | if ((lfsr = lfsr_new(taps, 5)) == NULL)
139 | goto done;
140 |
141 | TRY_ALLOC(result, CCSDS_SCRAMBLER_LENGTH, bool);
142 |
143 | for (i = 0; i < CCSDS_SCRAMBLER_LENGTH; ++i)
144 | result[i] = lfsr_scramble(lfsr, 0);
145 |
146 | g_scrambler = result;
147 | }
148 |
149 | done:
150 | if (lfsr != NULL)
151 | lfsr_destroy(lfsr);
152 |
153 | return g_scrambler;
154 | }
155 |
156 | void
157 | ccsds_modem_destroy(ccsds_modem_t *self)
158 | {
159 | if (self->buffer != NULL)
160 | free(self->buffer);
161 |
162 | if (self->frame_bytes != NULL)
163 | free(self->frame_bytes);
164 |
165 | if (self->syncbuf != NULL)
166 | free(self->syncbuf);
167 |
168 | ccsds_tc_decoder_finalize(&self->decoder);
169 | correlator_finalize(&self->sync_corr);
170 |
171 | free(self);
172 | }
173 |
174 | ccsds_modem_t *
175 | ccsds_modem_new(const struct ccsds_modem_params *params)
176 | {
177 | ccsds_modem_t *new = NULL;
178 | const struct ccsds_tc_syncword *syncword;
179 | struct ccsds_tc_decoder_params tc_params
180 | = ccsds_tc_decoder_params_INITIALIZER;
181 |
182 | if (ccsds_modem_get_scrambler() == NULL) {
183 | fprintf(stderr, "%s: failed to initialize scrambler\n", __FUNCTION__);
184 | return false;
185 | }
186 |
187 | if ((syncword = ccsds_tc_syncword_lookup(params->rate_inv)) == NULL) {
188 | fprintf(stderr, "%s: invalid rate 1/%d\n", __FUNCTION__, params->rate_inv);
189 | goto done;
190 | }
191 |
192 | TRY_CALLOC(new, 1, ccsds_modem_t);
193 |
194 | new->params = *params;
195 |
196 | tc_params.iters = params->iters;
197 | tc_params.rate = params->rate_inv;
198 | tc_params.L_c = CCSDS_TC_EBN0_TO_LC(CCSDS_TC_F2SB(params->EbN0));
199 | tc_params.block_len = params->block_len;
200 |
201 | new->block_size = tc_params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3;
202 |
203 | if (!ccsds_tc_decoder_init(&new->decoder, &tc_params))
204 | goto done;
205 |
206 | new->enc_size = ccsds_tc_decoder_get_codeword_size(&new->decoder);
207 | new->sync_len = strlen(syncword->syncword);
208 | new->syncing = true;
209 |
210 | TRY_ALLOC(new->buffer, new->enc_size, softbit_t);
211 | TRY_ALLOC(new->syncbuf, 2 * new->sync_len, float complex);
212 | TRY_ALLOC(
213 | new->frame_bytes,
214 | tc_params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH,
215 | uint8_t);
216 |
217 | if (!correlator_init_from_string(&new->sync_corr, syncword->syncword)) {
218 | fprintf(
219 | stderr,
220 | "%s: failed to initialize correlator for 1/%d syncword\n",
221 | __FUNCTION__,
222 | params->rate_inv);
223 | goto done;
224 | }
225 |
226 | return new;
227 |
228 | done:
229 | if (new != NULL)
230 | ccsds_modem_destroy(new);
231 |
232 | return new;
233 | }
234 |
235 | CCSDS_TC_INLINE unsigned int
236 | ccsds_modem_feed_sync(
237 | ccsds_modem_t *self,
238 | const float complex *__restrict x,
239 | unsigned int L)
240 | {
241 | bool sync_found = false;
242 | unsigned int search_len = 2 * self->sync_len;
243 | unsigned int consumed, p, i, remainder;
244 | int s;
245 |
246 | consumed = L;
247 | p = self->p;
248 |
249 | if (consumed > search_len - p)
250 | consumed = search_len - p;
251 |
252 | for (i = 0; i < consumed; ++i)
253 | self->syncbuf[p++] = x[i];
254 |
255 | if (p == search_len) {
256 | sync_found = correlator_find_complex(
257 | &self->sync_corr,
258 | self->syncbuf,
259 | search_len,
260 | self->params.sync_snr,
261 | self->params.q_channel);
262 | memmove(
263 | self->syncbuf,
264 | self->syncbuf + self->sync_len,
265 | sizeof(float complex) * self->sync_len);
266 | p = sync_found ? 0 : self->sync_len;
267 | } else {
268 | sync_found = false;
269 | }
270 |
271 | if (sync_found) {
272 | /* SYNC FOUND. Populate softbits */
273 | self->polarity = correlator_is_reverse(&self->sync_corr) ? +1 : -1;
274 | /*
275 | * If syncword was found in position p, we need to copy the remaining
276 | * search_len - (p + sync_len) softbits to the buffer. We also state
277 | * now that we only consumed the first p + sync_len softbits.
278 | */
279 |
280 | remainder = correlator_get_pos(&self->sync_corr) + self->sync_len;
281 | i = remainder;
282 | p = 0;
283 |
284 | /* Note that search_len is never bigger than the buffer */
285 | while (i < search_len) {
286 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1;
287 |
288 | if (self->params.q_channel)
289 | self->buffer[p++] = s * CCSDS_TC_F2SB(
290 | self->polarity * cimag(self->syncbuf[i++]));
291 | else
292 | self->buffer[p++] = s * CCSDS_TC_F2SB(
293 | self->polarity * creal(self->syncbuf[i++]));
294 | }
295 |
296 | self->syncing = false;
297 | }
298 |
299 | self->p = p;
300 |
301 | return consumed;
302 | }
303 |
304 | CCSDS_TC_INLINE unsigned int
305 | ccsds_modem_feed_payload(
306 | ccsds_modem_t *self,
307 | const float complex *__restrict x,
308 | unsigned int L)
309 | {
310 | unsigned int consumed, i, p;
311 | int s;
312 |
313 | /* STATE 1: DECODING. Decide how much we should consume beforehad */
314 | consumed = L;
315 | p = self->p;
316 |
317 | if (consumed > self->enc_size - p)
318 | consumed = self->enc_size - p;
319 |
320 | if (self->params.q_channel) {
321 | for (i = 0; i < consumed; ++i, ++p) {
322 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1;
323 | self->buffer[p] = s * CCSDS_TC_F2SB(self->polarity * cimag(x[i]));
324 | }
325 | } else {
326 | for (i = 0; i < consumed; ++i, ++p) {
327 | s = 2 * (int) g_scrambler[p % CCSDS_SCRAMBLER_LENGTH] - 1;
328 | self->buffer[p] = s * CCSDS_TC_F2SB(self->polarity * creal(x[i]));
329 | }
330 | }
331 |
332 | if (p == self->enc_size) {
333 | /* FULL FRAME! */
334 | ccsds_tc_decoder_feed_block(&self->decoder, self->buffer);
335 | self->y = ccsds_tc_decoder_get_output(&self->decoder);
336 | p = 0;
337 | self->syncing = true;
338 | }
339 |
340 | self->p = p;
341 |
342 | return consumed;
343 | }
344 |
345 | CCSDS_TC_INLINE bool
346 | ccsds_modem_try_extract_frame(ccsds_modem_t *self)
347 | {
348 | unsigned int i;
349 | uint16_t crc;
350 | unsigned int frame_len = self->block_size >> 3;
351 |
352 | for (i = 0; i < self->block_size; ++i)
353 | if ((i & 7) == 0)
354 | self->frame_bytes[i >> 3] = (self->y[i] > 0) << 7;
355 | else
356 | self->frame_bytes[i >> 3] |= (self->y[i] > 0) << (7 - (i & 7));
357 |
358 | /* CCSDS frames are big endian */
359 | crc = ntohs(*(const uint16_t *) &self->frame_bytes[frame_len - 2]);
360 |
361 | /* Check FECF */
362 | return ccsds_fcef_crc16(
363 | CCSDS_FECF_SEED,
364 | self->frame_bytes,
365 | frame_len - 2) == crc;
366 | }
367 |
368 | bool
369 | ccsds_modem_feed(
370 | ccsds_modem_t *self,
371 | const float complex *__restrict x,
372 | unsigned int L)
373 | {
374 | bool have_frame = false;
375 | unsigned int consumed;
376 |
377 | while (L > 0) {
378 | if (self->syncing) {
379 | consumed = ccsds_modem_feed_sync(self, x, L);
380 | } else {
381 | consumed = ccsds_modem_feed_payload(self, x, L);
382 | have_frame = have_frame || self->syncing;
383 | }
384 |
385 | x += consumed;
386 | L -= consumed;
387 | }
388 |
389 | /* Something that looks like a frame? */
390 | if (have_frame)
391 | have_frame = ccsds_modem_try_extract_frame(self);
392 |
393 | return have_frame;
394 | }
395 |
396 | bool
397 | ccsds_modem_process_area(
398 | ccsds_modem_t *self,
399 | const float complex *__restrict x,
400 | unsigned int L)
401 | {
402 | float polarity = 1, f;
403 | const bool *scrambler = NULL;
404 | unsigned int p = 0, i, q;
405 | unsigned int block_size;
406 | unsigned int enc_size;
407 | unsigned int sync_step;
408 | unsigned int frame_size;
409 | bool syncing = true;
410 | bool ok = false;
411 |
412 | enc_size = self->enc_size;
413 | sync_step = self->sync_len;
414 | frame_size = sync_step + enc_size + 4 * self->params.rate_inv;
415 |
416 | if ((scrambler = ccsds_modem_get_scrambler()) == NULL)
417 | goto done;
418 |
419 | /* Decoding loop */
420 | while (p < L - frame_size) {
421 | if (syncing) {
422 | if (correlator_find_complex(
423 | &self->sync_corr,
424 | x + p,
425 | 2 * sync_step,
426 | self->params.sync_snr,
427 | self->params.q_channel)) {
428 | polarity = correlator_is_reverse(&self->sync_corr) ? -1 : +1;
429 | p += correlator_get_pos(&self->sync_corr);
430 | syncing = false;
431 | }
432 |
433 | p += sync_step;
434 | } else {
435 | q = 0;
436 | if (self->params.q_channel) {
437 | for (i = 0; i < enc_size; ++i) {
438 | f = 2 * (int) scrambler[q++ % CCSDS_SCRAMBLER_LENGTH] - 1;
439 | self->buffer[i] = CCSDS_TC_F2SB(-polarity * cimag(x[p + i]) * f);
440 | }
441 | } else {
442 | for (i = 0; i < enc_size; ++i) {
443 | f = 2 * (int) scrambler[q++ % CCSDS_SCRAMBLER_LENGTH] - 1;
444 | self->buffer[i] = CCSDS_TC_F2SB(-polarity * creal(x[p + i]) * f);
445 | }
446 | }
447 |
448 | ccsds_tc_decoder_feed_block(&self->decoder, self->buffer);
449 | self->y = ccsds_tc_decoder_get_output(&self->decoder);
450 |
451 | /* TODO: Trigger action */
452 | p += enc_size;
453 | syncing = true;
454 | }
455 | }
456 |
457 | ok = true;
458 |
459 | done:
460 | return ok;
461 | }
462 |
463 |
--------------------------------------------------------------------------------
/ccsds-modem.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #ifndef _CCSDS_MODEM_H
21 | #define _CCSDS_MODEM_H
22 |
23 | #include "ccsds-tc.h"
24 | #include "correlator.h"
25 |
26 | #include
27 |
28 | #define CCSDS_MODEM_DEFAULT_RATE 6
29 | #define CCSDS_MODEM_DEFAULT_BLOCK_LEN CCSDS_TC_BLOCK_LENGTH_1115
30 | #define CCSDS_MODEM_DEFAULT_ITERS 1
31 | #define CCSDS_MODEM_DEFAULT_SYNC_SNR 80.
32 | #define CCSDS_MODEM_DEFAULT_EBN0 0.6310
33 | #define CCSDS_MODEM_DEFAULT_CHANNEL true
34 |
35 | struct ccsds_modem_params {
36 | unsigned int rate_inv;
37 | enum ccsds_tc_decoder_block_length block_len;
38 | unsigned int iters;
39 | float sync_snr;
40 | float EbN0;
41 | bool q_channel;
42 | };
43 |
44 | #define ccsds_modem_params_INITIALIZER \
45 | { \
46 | CCSDS_MODEM_DEFAULT_RATE, \
47 | CCSDS_MODEM_DEFAULT_BLOCK_LEN, \
48 | CCSDS_MODEM_DEFAULT_ITERS, \
49 | CCSDS_MODEM_DEFAULT_SYNC_SNR, \
50 | CCSDS_MODEM_DEFAULT_EBN0, \
51 | CCSDS_MODEM_DEFAULT_CHANNEL \
52 | }
53 |
54 | struct ccsds_modem {
55 | struct ccsds_modem_params params;
56 | correlator_t sync_corr;
57 | ccsds_tc_decoder_t decoder;
58 | bool syncing;
59 |
60 | /* Precalculated variables */
61 | softbit_t *buffer;
62 | float complex *syncbuf;
63 | const softbit_t *y;
64 | unsigned int p, q;
65 | unsigned int sync_len;
66 | float polarity;
67 |
68 | unsigned int frame_size;
69 | unsigned int block_size;
70 | unsigned int enc_size;
71 | uint8_t *frame_bytes;
72 | };
73 |
74 | typedef struct ccsds_modem ccsds_modem_t;
75 |
76 | ccsds_modem_t *ccsds_modem_new(const struct ccsds_modem_params *params);
77 |
78 | bool ccsds_modem_feed(
79 | ccsds_modem_t *self,
80 | const float complex *x,
81 | unsigned int L);
82 |
83 | CCSDS_TC_INLINE const softbit_t *
84 | ccsds_modem_get_frame_bits(const ccsds_modem_t *self)
85 | {
86 | return self->y;
87 | }
88 |
89 | CCSDS_TC_INLINE unsigned int
90 | ccsds_modem_get_block_length(const ccsds_modem_t *self)
91 | {
92 | return self->block_size;
93 | }
94 |
95 | CCSDS_TC_INLINE float
96 | ccsds_modem_get_error_rate(const ccsds_modem_t *self)
97 | {
98 | unsigned int i, count;
99 | unsigned int block_size = ccsds_modem_get_block_length(self);
100 | bool b1, b2;
101 |
102 | count = 0;
103 | for (i = 0; i < block_size; ++i) {
104 | b2 = self->y[i] > 0;
105 | b1 = self->buffer[i * self->params.rate_inv] > 0;
106 |
107 | if (b1 != b2)
108 | ++count;
109 | }
110 |
111 | return (float) count / (float) block_size;
112 | }
113 |
114 | CCSDS_TC_INLINE const uint8_t *
115 | ccsds_modem_get_frame_data(const ccsds_modem_t *self)
116 | {
117 | return self->frame_bytes;
118 | }
119 |
120 | CCSDS_TC_INLINE unsigned int
121 | ccsds_modem_get_frame_length(const ccsds_modem_t *self)
122 | {
123 | return self->block_size >> 3;
124 | }
125 |
126 | void ccsds_modem_destroy(ccsds_modem_t *self);
127 |
128 | #endif /* _CCSDS_MODEM */
129 |
--------------------------------------------------------------------------------
/ccsds-tc.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include "ccsds-tc.h"
21 | #include
22 | #include
23 | #include
24 |
25 | #define LOG2 CCSDS_TC_F2SB(0.69314718055995)
26 | #define MAXSTAR_LIN_LIMIT CCSDS_TC_F2SB(2 * 0.69314718055995)
27 | #define BIT2SOFT(b) (CCSDS_TC_SCALE * (2 * (softbit_t) (b) - 1))
28 | #define ALLOC(where, N, what) \
29 | if ((where = malloc(N * sizeof(what))) == NULL) \
30 | goto fail
31 |
32 |
33 | /******************* BCJR convolutional decoder *********************/
34 | #ifdef CCSDS_TC_NO_MAXSTAR
35 | # define ccsds_tc_softbit_maxstar CCSDS_TC_MAX
36 | #else
37 | CCSDS_TC_INLINE softbit_t
38 | ccsds_tc_softbit_maxstar(softbit_t a, softbit_t b)
39 | {
40 | softbit_t diff;
41 |
42 | if (CCSDS_TC_IMPOSSIBLE(a) || CCSDS_TC_IMPOSSIBLE(b))
43 | return CCSDS_TC_MAX(a, b);
44 |
45 | if (a > b) {
46 | diff = a - b;
47 | if (diff > MAXSTAR_LIN_LIMIT)
48 | return a;
49 | else
50 | return a + LOG2 - diff / 2;
51 | } else {
52 | diff = b - a;
53 | if (diff > MAXSTAR_LIN_LIMIT)
54 | return b;
55 | else
56 | return b + LOG2 - diff / 2;
57 | }
58 | }
59 | #endif /* CCSDS_TC_NO_MAXSTAR */
60 |
61 | /* This is derived from a Taylor expansion of
62 | fmaxf(a, b) + log(1 + exp(-fabs(a-b))) */
63 |
64 | CCSDS_TC_INLINE state_t
65 | ccsds_tc_bcjr_state_dotprod(state_t a, state_t b)
66 | {
67 | #ifdef __GNUC__
68 | return __builtin_parity(a & b);
69 | #else
70 | /* TODO: generate at compile time */
71 | return
72 | ((a >> 0) & (b >> 0) & 1)
73 | ^ ((a >> 1) & (b >> 1) & 1)
74 | ^ ((a >> 2) & (b >> 2) & 1)
75 | ^ ((a >> 3) & (b >> 3) & 1)
76 | ^ ((a >> 4) & (b >> 4) & 1);
77 | #endif
78 | }
79 |
80 | CCSDS_TC_INLINE state_t
81 | ccsds_tc_bcjr_state_next(state_t a, state_t poly_b, bool next)
82 | {
83 | next ^= ccsds_tc_bcjr_state_dotprod(a, poly_b);
84 |
85 | return CCSDS_TC_STATE_MASK & ((a << 1) | (next & 1));
86 | }
87 |
88 | CCSDS_TC_INLINE bool
89 | ccsds_tc_bcjr_connect_state(
90 | ccsds_tc_bcjr_ctx_t *self,
91 | state_t prev,
92 | state_t next,
93 | bool b)
94 | {
95 | /* Forward connection. Trivial. */
96 | self->S_next[(prev << 1) + b] = next;
97 |
98 | /*
99 | * Backward connection. Not so trivial. We check whether
100 | * the previous state is set to "unused" and update the
101 | * entry accordingly
102 | */
103 |
104 | if (next == 0) {
105 | if (b)
106 | self->S_last_1 = prev;
107 | else
108 | self->S_last_0 = prev;
109 | }
110 |
111 | if (self->S_prev[(next << 1) + 0] == CCSDS_TC_STATE_UNUSED) {
112 | self->S_prev[(next << 1) + 0] = prev;
113 | } else if (self->S_prev[(next << 1) + 1] == CCSDS_TC_STATE_UNUSED) {
114 | self->S_prev[(next << 1) + 1] = prev;
115 | } else {
116 | fprintf(stderr, "critical: more than 2 previous states?\n");
117 | return false;
118 | }
119 |
120 | return true;
121 | }
122 |
123 | void
124 | ccsds_tc_bcjr_ctx_finalize(ccsds_tc_bcjr_ctx_t *self)
125 | {
126 | if (self->S_next != NULL)
127 | free(self->S_next);
128 |
129 | if (self->S_prev != NULL)
130 | free(self->S_prev);
131 |
132 | if (self->hv != NULL)
133 | free(self->hv);
134 |
135 | if (self->rv != NULL)
136 | free(self->rv);
137 |
138 | if (self->astar != NULL)
139 | free(self->astar);
140 |
141 | if (self->bstar != NULL)
142 | free(self->bstar);
143 |
144 | if (self->gstar_dir != NULL)
145 | free(self->gstar_dir);
146 |
147 | if (self->gstar_rev != NULL)
148 | free(self->gstar_rev);
149 |
150 | if (self->min_lh_buf != NULL)
151 | free(self->min_lh_buf);
152 | }
153 |
154 | bool
155 | ccsds_tc_bcjr_ctx_init(
156 | ccsds_tc_bcjr_ctx_t *self,
157 | const poly_t *poly_f,
158 | poly_t poly_b,
159 | unsigned int n,
160 | unsigned int h)
161 | {
162 | unsigned int i, j;
163 | unsigned int cwnb; /* Codeword number and bit */
164 | unsigned int cwnb_0, cwnb_1;
165 | bool b;
166 | state_t next0, next1;
167 | softbit_t lh;
168 | bool ok = false;
169 |
170 | memset(self, 0, sizeof(ccsds_tc_bcjr_ctx_t));
171 |
172 | poly_b >>= 1; /* Discard rightmost bit */
173 |
174 | self->h = h;
175 | self->n = n;
176 | self->poly_b = poly_b;
177 | self->length = h + CCSDS_TC_STATE_BITS ;
178 |
179 | /* Allocate data */
180 | ALLOC(self->hv, 2 * CCSDS_TC_STATE_NUM, state_t);
181 | ALLOC(self->S_next, 2 * self->n * CCSDS_TC_STATE_NUM, state_t);
182 | ALLOC(self->S_prev, 2 * self->n * CCSDS_TC_STATE_NUM, state_t);
183 | ALLOC(self->rv, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t);
184 | ALLOC(self->astar, self->length * CCSDS_TC_STATE_NUM, softbit_t);
185 | ALLOC(self->bstar, (self->length + 1) * CCSDS_TC_STATE_NUM, softbit_t);
186 | ALLOC(self->gstar_dir, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t);
187 | ALLOC(self->gstar_rev, 2 * self->length * CCSDS_TC_STATE_NUM, softbit_t);
188 | ALLOC(self->min_lh_buf, 2 * CCSDS_TC_STATE_NUM, softbit_t);
189 |
190 | for (j = 0; j < 2 * self->length * CCSDS_TC_STATE_NUM; ++j)
191 | self->gstar_dir[j] = self->gstar_rev[j] = CCSDS_TC_MIN_LH;
192 |
193 | for (j = 0; j < 2 * CCSDS_TC_STATE_NUM; ++j)
194 | self->min_lh_buf[j] = CCSDS_TC_MIN_LH;
195 |
196 | memset(self->hv, 0, 2 * CCSDS_TC_STATE_NUM * sizeof(state_t));
197 | memset(self->rv, 0, 2 * self->length * CCSDS_TC_STATE_NUM * sizeof(softbit_t));
198 |
199 | /* Mark all previous states as unused */
200 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) {
201 | self->S_prev[(j << 1) + 0] = CCSDS_TC_STATE_UNUSED;
202 | self->S_prev[(j << 1) + 1] = CCSDS_TC_STATE_UNUSED;
203 | }
204 |
205 | /* Initialize path metrics */
206 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) {
207 | lh = j == 0 ? CCSDS_TC_MAX_LH : CCSDS_TC_MIN_LH;
208 | self->astar[j] = lh;
209 | self->bstar[CCSDS_TC_STATE_NUM * (self->length) + j] = lh;
210 | }
211 |
212 | /* Initialize codeword table AND trellis connections. -1 is zero. */
213 | for (j = 0; j < CCSDS_TC_STATE_NUM; ++j) {
214 | b = ccsds_tc_bcjr_state_dotprod(j, poly_b);
215 | next0 = ccsds_tc_bcjr_state_next(j, poly_b, false);
216 | next1 = ccsds_tc_bcjr_state_next(j, poly_b, true);
217 |
218 | /* Connect state j to next if input is 0 */
219 | if (!ccsds_tc_bcjr_connect_state(
220 | self,
221 | j,
222 | next0,
223 | false))
224 | goto fail;
225 |
226 | /* Connect state j to next if input is 1 */
227 | if (!ccsds_tc_bcjr_connect_state(
228 | self,
229 | j,
230 | next1,
231 | true))
232 | goto fail;
233 |
234 | for (i = 0; i < n; ++i)
235 | self->hv[(j << 1) + 0]
236 | |= ccsds_tc_bcjr_state_dotprod(poly_f[i], (j << 1) ^ b ^ false) << i;
237 | for (i = 0; i < n; ++i)
238 | self->hv[(j << 1) + 1]
239 | |= ccsds_tc_bcjr_state_dotprod(poly_f[i], (j << 1) ^ b ^ true) << i;
240 | }
241 |
242 | ok = true;
243 |
244 | fail:
245 | if (!ok)
246 | ccsds_tc_bcjr_ctx_finalize(self);
247 |
248 | return ok;
249 | }
250 |
251 | CCSDS_TC_INLINE softbit_t
252 | ccsds_tc_bcjr_ctx_semisoft_dotprod_2(const softbit_t *__restrict r, state_t hv)
253 | {
254 | switch (hv) {
255 | case 0:
256 | return -(r[0] + r[1]);
257 | case 1:
258 | return r[0] - r[1];
259 | case 2:
260 | return r[1] - r[0];
261 | case 3:
262 | return r[1] + r[0];
263 | }
264 |
265 | return 0;
266 | }
267 | CCSDS_TC_INLINE softbit_t
268 | ccsds_tc_bcjr_ctx_semisoft_dotprod(const softbit_t *__restrict r, state_t hv, unsigned int L)
269 | {
270 | softbit_t accum = 0;
271 |
272 | switch (L) {
273 | case 2:
274 | return ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r, hv);
275 |
276 | case 4:
277 | return ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r, hv & 3)
278 | + ccsds_tc_bcjr_ctx_semisoft_dotprod_2(r + 2, (hv >> 2) & 3);
279 | }
280 |
281 | while (L-- != 0) {
282 | if (hv & 1)
283 | accum += *r++;
284 | else
285 | accum -= *r++;
286 |
287 | hv >>= 1;
288 | }
289 | return accum;
290 | }
291 |
292 |
293 | CCSDS_TC_INLINE void
294 | ccsds_tc_bcjr_ctx_set_gamma(
295 | ccsds_tc_bcjr_ctx_t *self,
296 | unsigned int j, /* position */
297 | state_t state, /* previous state */
298 | bool b, /* next bit */
299 | const softbit_t *__restrict L_u) /* received codeword */
300 | {
301 | unsigned int sndx = (state << 1) + b;
302 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM << 1;
303 | unsigned int dest = pos_off + sndx;
304 | state_t next = self->S_next[sndx];
305 | state_t next2 = next << 1;
306 |
307 | /*
308 | * Gamma star is already a posterior log-probability
309 | * that the received codeword corresponds to a given bit
310 | * given the current state. It is an expression of the form:
311 | *
312 | * 1/2 (bit * L(bit) + CW * L(CW))
313 | *
314 | * In this expression we basically combine prior information
315 | * of the bit (L(bit)) + the soft information of the received
316 | * codeword. This is dot-multiplied by the bit and expected
317 | * codeword of the current branch.
318 | */
319 |
320 | /* We only set gamma in the intermediate cases */
321 | const softbit_t gstar = (j < self->h
322 | ? (BIT2SOFT(b) / CCSDS_TC_SSQRT) * (L_u[j] / (2 * CCSDS_TC_SSQRT)) : 0)
323 | + self->rv[dest];
324 |
325 | /*
326 | * For each j, gamma is actually a multivaluated function
327 | * on the state number. Since we don't actually need
328 | * STATE_NUM x STATE_NUM gammas, as many of the state connections
329 | * are forbidden, we provide a sparse representation in
330 | * which the row-to-cols and col-to-rows view are stored.
331 | * We can do this because we know that, for each departure
332 | * state there are 2 possible arrival states (gamma_dir) and
333 | * for each arrival states, there are 2 possible departure
334 | * states (gamma_rev).
335 | */
336 |
337 | /* gstar_dir*: gamma* row-to-cols view */
338 | self->gstar_dir[dest] = gstar;
339 |
340 | /*
341 | * gstar_rev: gamma* col-to-rows view. Now this is
342 | * the trick: we are going to enforce the SAME indexing
343 | * as with S_prev, so we know that the first of the
344 | * two gamma* arriving to state "next" refers to the
345 | * the first of the previous states to "next"
346 | */
347 |
348 | self->gstar_rev[pos_off + next2 + (self->S_prev[next2] != state)] = gstar;
349 | }
350 |
351 |
352 | CCSDS_TC_INLINE void
353 | ccsds_tc_bcjr_ctx_set_gamma_bulk(
354 | ccsds_tc_bcjr_ctx_t *self,
355 | const softbit_t *__restrict L_u)
356 | {
357 | unsigned int p = CCSDS_TC_STATE_NUM << 1;
358 | unsigned int i, j;
359 |
360 | #pragma GCC unroll 2
361 | for (j = 1; j < self->h; ++j) {
362 | #pragma GCC unroll 32
363 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) {
364 | const state_t next = self->S_next[i];
365 | const softbit_t gstar =
366 | (BIT2SOFT(i & 1) / CCSDS_TC_SSQRT) * (L_u[j] / (2 * CCSDS_TC_SSQRT))
367 | + self->rv[p];
368 | const unsigned int rev_p =
369 | ((j * CCSDS_TC_STATE_NUM + next) << 1) + (self->S_prev[next << 1] != (i >> 1));
370 | self->gstar_dir[p] = gstar;
371 | self->gstar_rev[rev_p] = gstar;
372 | }
373 | }
374 |
375 | #pragma GCC unroll 2
376 | for (j = self->h; j < self->length - 1; ++j) {
377 | #pragma GCC unroll 32
378 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) {
379 | const state_t next = self->S_next[i];
380 | const softbit_t gstar = self->rv[p];
381 | const unsigned int rev_p =
382 | ((j * CCSDS_TC_STATE_NUM + next) << 1) + (self->S_prev[next << 1] != (i >> 1));
383 | self->gstar_dir[p] = gstar;
384 | self->gstar_rev[rev_p] = gstar;
385 | }
386 | }
387 | }
388 |
389 | CCSDS_TC_INLINE void
390 | ccsds_tc_bcjr_ctx_set_alpha(
391 | ccsds_tc_bcjr_ctx_t *self,
392 | unsigned int j, /* next position */
393 | state_t next) /* next state */
394 | {
395 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM;
396 | unsigned int prev_off = pos_off - CCSDS_TC_STATE_NUM;
397 | unsigned int gprv_off = prev_off << 1;
398 | unsigned int i0 = (next << 1) + 0;
399 | unsigned int i1 = i0 + 1;
400 | softbit_t astar;
401 |
402 | /* There are only two previous states */
403 | astar =
404 | ccsds_tc_softbit_maxstar(
405 | CCSDS_TC_SAFE_SUM(
406 | self->gstar_rev[gprv_off + i0],
407 | self->astar[prev_off + self->S_prev[i0]]),
408 | CCSDS_TC_SAFE_SUM(
409 | self->gstar_rev[gprv_off + i1],
410 | self->astar[prev_off + self->S_prev[i1]]));
411 |
412 | self->astar[pos_off + next] = astar;
413 | }
414 |
415 | CCSDS_TC_INLINE void
416 | ccsds_tc_bcjr_ctx_set_beta(
417 | ccsds_tc_bcjr_ctx_t *self,
418 | unsigned int j, /* curr position */
419 | state_t prev) /* curr state */
420 | {
421 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM;
422 | unsigned int next_off = pos_off + CCSDS_TC_STATE_NUM;
423 | unsigned int gpos_off = pos_off << 1;
424 | unsigned int i0 = (prev << 1) + 0;
425 | unsigned int i1 = i0 + 1;
426 | softbit_t bstar;
427 |
428 | /* There are only two next states */
429 | bstar =
430 | ccsds_tc_softbit_maxstar(
431 | CCSDS_TC_SAFE_SUM(
432 | self->gstar_dir[gpos_off + i0],
433 | self->bstar[next_off + self->S_next[i0]]),
434 | CCSDS_TC_SAFE_SUM(
435 | self->gstar_dir[gpos_off + i1],
436 | self->bstar[next_off + self->S_next[i1]]));
437 |
438 | self->bstar[pos_off + prev] = bstar;
439 | }
440 |
441 | CCSDS_TC_INLINE softbit_t
442 | ccsds_tc_bcjr_ctx_get_Lu(ccsds_tc_bcjr_ctx_t *self, unsigned int j)
443 | {
444 | unsigned int pos_off = j * CCSDS_TC_STATE_NUM;
445 | unsigned int pos_off2 = pos_off << 1;
446 | unsigned int next_off = pos_off + CCSDS_TC_STATE_NUM;
447 | unsigned int i, state_off;
448 | softbit_t L0, L1;
449 |
450 | L0 = CCSDS_TC_MIN_LH;
451 | L1 = CCSDS_TC_MIN_LH;
452 |
453 | state_off = 0;
454 | #pragma GCC unroll 16
455 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) {
456 | L0 = ccsds_tc_softbit_maxstar(
457 | CCSDS_TC_SAFE_SUM(
458 | self->bstar[next_off + self->S_next[state_off]],
459 | CCSDS_TC_SAFE_SUM(
460 | self->gstar_dir[pos_off2 + state_off],
461 | self->astar[pos_off + i])),
462 | L0);
463 | state_off += 2;
464 | }
465 |
466 | state_off = 1;
467 |
468 | #pragma GCC unroll 16
469 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i) {
470 | L1 = ccsds_tc_softbit_maxstar(
471 | CCSDS_TC_SAFE_SUM(
472 | self->bstar[next_off + self->S_next[state_off]],
473 | CCSDS_TC_SAFE_SUM(
474 | self->gstar_dir[pos_off2 + state_off],
475 | self->astar[pos_off + i])),
476 | L1);
477 | state_off += 2;
478 | }
479 |
480 | return L1 - L0;
481 | }
482 |
483 | CCSDS_TC_INLINE softbit_t
484 | ccsds_tc_bcjr_ctx_rebuild_rv_cache(
485 | ccsds_tc_bcjr_ctx_t *self,
486 | const softbit_t *__restrict x,
487 | softbit_t L_c)
488 | {
489 | unsigned int i, j, p = 0;
490 | unsigned int q = 0;
491 |
492 | #pragma GCC unroll 2
493 | for (j = 0; j < self->length; ++j) {
494 | #pragma GCC unroll 32
495 | for (i = 0; i < (CCSDS_TC_STATE_NUM << 1); ++i, ++p) {
496 | self->rv[p]
497 | = (L_c / (2 * CCSDS_TC_SSQRT))
498 | * (ccsds_tc_bcjr_ctx_semisoft_dotprod(
499 | &x[q],
500 | self->hv[i],
501 | self->n) / CCSDS_TC_SSQRT);
502 | }
503 | q += self->n;
504 | }
505 |
506 | self->rv_cached = true;
507 | }
508 |
509 | bool
510 | ccsds_tc_bcjr_ctx_decode(
511 | ccsds_tc_bcjr_ctx_t *self,
512 | const softbit_t *__restrict x,
513 | const softbit_t *__restrict L_u,
514 | softbit_t *y,
515 | softbit_t L_c,
516 | const int *__restrict permute_out)
517 | {
518 | unsigned int i, j, i0, i1, off = 0;
519 | softbit_t lh, L0, L1;
520 | unsigned int cwnb;
521 | unsigned int cwnb_0, cwnb_1;
522 | bool ok = false;
523 |
524 | if (!self->rv_cached)
525 | ccsds_tc_bcjr_ctx_rebuild_rv_cache(self, x, L_c);
526 |
527 | ccsds_tc_bcjr_ctx_set_gamma(self, 0, 0, false, L_u);
528 | ccsds_tc_bcjr_ctx_set_gamma(self, 0, 0, true, L_u);
529 | ccsds_tc_bcjr_ctx_set_gamma(
530 | self,
531 | self->length - 1,
532 | self->S_last_0,
533 | false,
534 | L_u);
535 | ccsds_tc_bcjr_ctx_set_gamma(
536 | self,
537 | self->length - 1,
538 | self->S_last_1,
539 | true,
540 | L_u);
541 |
542 | ccsds_tc_bcjr_ctx_set_gamma_bulk(self, L_u);
543 |
544 | /* Set path metrics (forward) */
545 | #pragma GCC unroll 2
546 | for (j = 1; j < self->length; ++j)
547 | #pragma GCC unroll 16
548 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i)
549 | ccsds_tc_bcjr_ctx_set_alpha(self, j, i);
550 |
551 | /* Set path metrics (backward) */
552 | #pragma GCC unroll 2
553 | for (j = 1; j < self->length; ++j)
554 | #pragma GCC unroll 16
555 | for (i = 0; i < CCSDS_TC_STATE_NUM; ++i)
556 | ccsds_tc_bcjr_ctx_set_beta(self, self->length - j, i);
557 |
558 | /* Compute bit likelihoods and finish */
559 | if (permute_out != NULL) {
560 | #pragma GCC unroll 2
561 | for (j = 0; j < self->h; ++j)
562 | y[j] = ccsds_tc_bcjr_ctx_get_Lu(self, permute_out[j]);
563 | #pragma GCC unroll 22
564 | for (j = self->h; j < self->length; ++j)
565 | y[j] = 0;
566 | } else {
567 | #pragma GCC unroll 2
568 | for (j = 0; j < self->length; ++j)
569 | y[j] = ccsds_tc_bcjr_ctx_get_Lu(self, j);
570 | }
571 |
572 | ok = true;
573 |
574 | done:
575 | return ok;
576 | }
577 |
578 | /*********************** Full CCSDS Turbocode *******************/
579 | static int *
580 | ccsds_tc_decoder_make_permutator(int k2)
581 | {
582 | int *pi = NULL;
583 | int s, m, i, j, t, q, c;
584 | int p[] = {31, 37, 43, 47, 53, 59, 61, 67};
585 |
586 | if ((pi = malloc(sizeof(int) * k2 * 8)) == NULL)
587 | return NULL;
588 |
589 | for (s = 1; s <= k2 * 8; ++s) {
590 | m = (s - 1) & 1;
591 | i = (s - 1) / (2 * k2);
592 | j = (s - 1) / 2 - i * k2;
593 | t = (19 * i + 1) % 4;
594 | q = (t % 8) + 1;
595 | c = (p[q - 1] * j + 21 * m) % k2;
596 | pi[s - 1] = 2 * (t + c * 4 + 1) - m - 1;
597 | }
598 |
599 | return pi;
600 | }
601 |
602 | /* RATE 1/2: Handmade puncturing */
603 | static void
604 | ser2par_sb_2(ccsds_tc_decoder_t *self, const softbit_t *__restrict r)
605 | {
606 | unsigned int i;
607 |
608 | for (i = 0; i < self->terminated_len; ++i) {
609 | self->x1[2 * i + 0] = r[2 * i + 0];
610 | self->x1[2 * i + 1] = i & 1 ? CCSDS_TC_F2SB(0) : r[2 * i + 1];
611 | self->x2[i] = i & 1 ? r[2 * i + 1] : CCSDS_TC_F2SB(0);
612 | }
613 | }
614 |
615 | static void
616 | ser2par_float_2(ccsds_tc_decoder_t *self, const float *__restrict r)
617 | {
618 | unsigned int i;
619 |
620 | for (i = 0; i < self->terminated_len; ++i) {
621 | self->x1[2 * i + 0] = CCSDS_TC_F2SB(r[2 * i + 0]);
622 | self->x1[2 * i + 1] = i & 1 ? CCSDS_TC_F2SB(0) : CCSDS_TC_F2SB(r[2 * i + 1]);
623 | self->x2[i] = i & 1 ? CCSDS_TC_F2SB(r[2 * i + 1]) : CCSDS_TC_F2SB(0);
624 | }
625 | }
626 |
627 | /* RATE 1/3 */
628 | static void
629 | ser2par_sb_3(ccsds_tc_decoder_t *self, const softbit_t *__restrict r)
630 | {
631 | unsigned int i;
632 |
633 | for (i = 0; i < self->terminated_len; ++i) {
634 | self->x1[2 * i + 0] = r[3 * i + 0];
635 | self->x1[2 * i + 1] = r[3 * i + 1];
636 | self->x2[i] = r[3 * i + 2];
637 | }
638 | }
639 |
640 | static void
641 | ser2par_float_3(ccsds_tc_decoder_t *self, const float *__restrict r)
642 | {
643 | unsigned int i;
644 |
645 | for (i = 0; i < self->terminated_len; ++i) {
646 | self->x1[2 * i + 0] = CCSDS_TC_F2SB(r[3 * i + 0]);
647 | self->x1[2 * i + 1] = CCSDS_TC_F2SB(r[3 * i + 1]);
648 | self->x2[i] = CCSDS_TC_F2SB(r[3 * i + 2]);
649 | }
650 | }
651 |
652 | /* RATE 1/4 */
653 | static void
654 | ser2par_sb_4(ccsds_tc_decoder_t *self, const softbit_t *__restrict r)
655 | {
656 | unsigned int i;
657 |
658 | for (i = 0; i < self->terminated_len; ++i) {
659 | self->x1[3 * i + 0] = r[4 * i + 0];
660 | self->x1[3 * i + 1] = r[4 * i + 1];
661 | self->x1[3 * i + 2] = r[4 * i + 2];
662 | self->x2[i] = r[4 * i + 3];
663 | }
664 | }
665 |
666 | static void
667 | ser2par_float_4(ccsds_tc_decoder_t *self, const float *__restrict r)
668 | {
669 | unsigned int i;
670 |
671 | for (i = 0; i < self->terminated_len; ++i) {
672 | self->x1[3 * i + 0] = CCSDS_TC_F2SB(r[4 * i + 0]);
673 | self->x1[3 * i + 1] = CCSDS_TC_F2SB(r[4 * i + 1]);
674 | self->x1[3 * i + 2] = CCSDS_TC_F2SB(r[4 * i + 2]);
675 | self->x2[i] = CCSDS_TC_F2SB(r[4 * i + 3]);
676 | }
677 | }
678 |
679 | /* RATE 1/6 */
680 | static void
681 | ser2par_sb_6(ccsds_tc_decoder_t *self, const softbit_t *__restrict r)
682 | {
683 | unsigned int i;
684 |
685 | for (i = 0; i < self->terminated_len; ++i) {
686 | self->x1[4 * i + 0] = r[6 * i + 0];
687 | self->x1[4 * i + 1] = r[6 * i + 1];
688 | self->x1[4 * i + 2] = r[6 * i + 2];
689 | self->x1[4 * i + 3] = r[6 * i + 3];
690 | self->x2[2 * i + 0] = r[6 * i + 4];
691 | self->x2[2 * i + 1] = r[6 * i + 5];
692 | }
693 | }
694 |
695 | static void
696 | ser2par_float_6(ccsds_tc_decoder_t *self, const float *__restrict r)
697 | {
698 | unsigned int i;
699 |
700 | for (i = 0; i < self->terminated_len; ++i) {
701 | self->x1[4 * i + 0] = CCSDS_TC_F2SB(r[6 * i + 0]);
702 | self->x1[4 * i + 1] = CCSDS_TC_F2SB(r[6 * i + 1]);
703 | self->x1[4 * i + 2] = CCSDS_TC_F2SB(r[6 * i + 2]);
704 | self->x1[4 * i + 3] = CCSDS_TC_F2SB(r[6 * i + 3]);
705 | self->x2[2 * i + 0] = CCSDS_TC_F2SB(r[6 * i + 4]);
706 | self->x2[2 * i + 1] = CCSDS_TC_F2SB(r[6 * i + 5]);
707 | }
708 | }
709 |
710 | bool
711 | ccsds_tc_decoder_init(
712 | ccsds_tc_decoder_t *self,
713 | const struct ccsds_tc_decoder_params *params)
714 | {
715 | unsigned int block_len;
716 | unsigned int block_bits;
717 | unsigned int i;
718 | unsigned int terminated_len;
719 | unsigned int n1[] = {0, 2, 2, 3, 0, 4};
720 | unsigned int n2[] = {0, 1, 1, 1, 0, 2};
721 | poly_t poly_b = CCSDS_TC_G0;
722 | poly_t poly_f1[4];
723 | poly_t poly_f2[2];
724 |
725 | unsigned int rate;
726 |
727 | bool ok = false;
728 |
729 | memset(self, 0, sizeof(ccsds_tc_decoder_t));
730 |
731 | self->params = *params;
732 |
733 | /* Check block length */
734 | if (self->params.block_len < CCSDS_TC_BLOCK_LENGTH_223
735 | || self->params.block_len > CCSDS_TC_BLOCK_LENGTH_1115
736 | || self->params.block_len == 3) {
737 | fprintf(stderr, "%s: invalid block length\n", __FUNCTION__);
738 | goto fail;
739 | }
740 |
741 | /* Configure channel rate */
742 | poly_f1[0] = CCSDS_TC_G0; /* The same for all rates */
743 | poly_f2[0] = CCSDS_TC_G1; /* Ditto */
744 |
745 | switch (self->params.rate) {
746 | case 2:
747 | self->ser2par_sb_cb = ser2par_sb_2;
748 | self->ser2par_float_cb = ser2par_float_2;
749 | poly_f1[1] = CCSDS_TC_G1;
750 | break;
751 |
752 | case 3:
753 | self->ser2par_sb_cb = ser2par_sb_3;
754 | self->ser2par_float_cb = ser2par_float_3;
755 | poly_f1[1] = CCSDS_TC_G1;
756 | break;
757 |
758 | case 4:
759 | self->ser2par_sb_cb = ser2par_sb_4;
760 | self->ser2par_float_cb = ser2par_float_4;
761 | poly_f1[1] = CCSDS_TC_G2;
762 | poly_f1[2] = CCSDS_TC_G3;
763 | break;
764 |
765 | case 6:
766 | self->ser2par_sb_cb = ser2par_sb_6;
767 | self->ser2par_float_cb = ser2par_float_6;
768 | poly_f1[1] = CCSDS_TC_G1;
769 | poly_f1[2] = CCSDS_TC_G2;
770 | poly_f1[3] = CCSDS_TC_G3;
771 | poly_f2[1] = CCSDS_TC_G3;
772 | break;
773 |
774 | default:
775 | fprintf(stderr, "%s: invalid rate 1/%d\n", self->params.rate);
776 | goto fail;
777 | }
778 |
779 | rate = self->params.rate;
780 | block_len = self->params.block_len * CCSDS_TC_MINIMUM_BLOCK_LENGTH;
781 | block_bits = block_len << 3;
782 | terminated_len = block_bits + CCSDS_TC_STATE_BITS;
783 |
784 | self->length = block_bits;
785 | self->cw_length = terminated_len * self->params.rate;
786 | self->terminated_len = terminated_len;
787 |
788 | /* Allocate buffers */
789 | ALLOC(self->permutate_inv, block_bits, int);
790 | ALLOC(self->x1, n1[rate - 1] * terminated_len, softbit_t);
791 | ALLOC(self->x2, n2[rate - 1] * terminated_len, softbit_t);
792 | ALLOC(self->y1, terminated_len, softbit_t);
793 | ALLOC(self->y2, terminated_len, softbit_t);
794 | ALLOC(self->y0, terminated_len, softbit_t);
795 |
796 | memset(self->y2, 0, terminated_len * sizeof(softbit_t));
797 | memset(self->y0, 0, terminated_len * sizeof(softbit_t));
798 |
799 | /* Allocate permutator */
800 | if ((self->permutate_dir = ccsds_tc_decoder_make_permutator(block_len)) == NULL)
801 | goto fail;
802 |
803 | for (i = 0; i < block_bits; ++i)
804 | self->permutate_inv[self->permutate_dir[i]] = i;
805 |
806 | self->n1 = n1[rate - 1];
807 |
808 | /* Initialize encoders and finish */
809 | if (!ccsds_tc_bcjr_ctx_init(
810 | &self->dec1,
811 | poly_f1,
812 | poly_b,
813 | n1[rate - 1],
814 | block_bits)) {
815 | fprintf(stderr, "%s: DEC1 initialization failed\n", __FUNCTION__);
816 | goto fail;
817 | }
818 |
819 | if (!ccsds_tc_bcjr_ctx_init(
820 | &self->dec2,
821 | poly_f2,
822 | poly_b,
823 | n2[rate - 1],
824 | block_bits)) {
825 | fprintf(stderr, "%s: DEC2 initialization failed\n", __FUNCTION__);
826 | goto fail;
827 | }
828 |
829 | return true;
830 |
831 | fail:
832 | ccsds_tc_decoder_finalize(self);
833 |
834 | return false;
835 | }
836 |
837 | CCSDS_TC_INLINE void
838 | ccsds_tc_decode(ccsds_tc_decoder_t *self)
839 | {
840 | unsigned int i;
841 |
842 | /* First permutation */
843 | ccsds_tc_bcjr_ctx_decode(
844 | &self->dec1,
845 | self->x1,
846 | self->y0,
847 | self->y1,
848 | self->params.L_c,
849 | self->permutate_dir);
850 |
851 | ccsds_tc_bcjr_ctx_decode(
852 | &self->dec2,
853 | self->x2,
854 | self->y1,
855 | self->y2,
856 | self->params.L_c,
857 | self->permutate_inv);
858 |
859 | /* Next permutations */
860 | #pragma GCC unroll 2
861 | for (i = 1; i < self->params.iters; ++i) {
862 | ccsds_tc_bcjr_ctx_decode(
863 | &self->dec1,
864 | self->x1,
865 | self->y2,
866 | self->y1,
867 | self->params.L_c,
868 | self->permutate_dir);
869 |
870 | ccsds_tc_bcjr_ctx_decode(
871 | &self->dec2,
872 | self->x2,
873 | self->y1,
874 | self->y2,
875 | self->params.L_c,
876 | self->permutate_inv);
877 | }
878 |
879 | ccsds_tc_bcjr_invalidate_cache(&self->dec1);
880 | ccsds_tc_bcjr_invalidate_cache(&self->dec2);
881 | }
882 |
883 | void
884 | ccsds_tc_decoder_feed_block(ccsds_tc_decoder_t *self, const softbit_t *block)
885 | {
886 | (self->ser2par_sb_cb) (self, block);
887 | ccsds_tc_decode(self);
888 | }
889 |
890 | void
891 | ccsds_tc_decoder_feed_block_float(ccsds_tc_decoder_t *self, const float *block)
892 | {
893 | (self->ser2par_float_cb) (self, block);
894 | ccsds_tc_decode(self);
895 | }
896 |
897 | void
898 | ccsds_tc_decoder_finalize(ccsds_tc_decoder_t *self)
899 | {
900 | if (self->x1 != NULL)
901 | free(self->x1);
902 |
903 | if (self->x2 != NULL)
904 | free(self->x2);
905 |
906 | if (self->y0 != NULL)
907 | free(self->y0);
908 |
909 | if (self->y1 != NULL)
910 | free(self->y1);
911 |
912 | if (self->y2 != NULL)
913 | free(self->y2);
914 |
915 | if (self->permutate_dir != NULL)
916 | free(self->permutate_dir);
917 |
918 | if (self->permutate_inv != NULL)
919 | free(self->permutate_inv);
920 |
921 | ccsds_tc_bcjr_ctx_finalize(&self->dec1);
922 | ccsds_tc_bcjr_ctx_finalize(&self->dec2);
923 | }
924 |
--------------------------------------------------------------------------------
/ccsds-tc.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #ifndef _CCSDS_TC_H
21 | #define _CCSDS_TC_H
22 |
23 | #define _ISOC99_SOURCE
24 |
25 | #include
26 | #include
27 | #include
28 |
29 | #define CCSDS_TC_MINIMUM_BLOCK_LENGTH 223
30 |
31 | #ifdef __GNUC__
32 | # define CCSDS_TC_INLINE static inline __attribute__((always_inline))
33 | #else
34 | # define CCSDS_TC_INLINE static inline
35 | # define __restrict
36 | #endif
37 |
38 | #ifdef CCSDS_TC_INT_ARITHMETICS
39 | # define CCSDS_TC_SQRT_BITS 6
40 | # define CCSDS_TC_SSQRT (1 << CCSDS_TC_SQRT_BITS)
41 | # define CCSDS_TC_SCALE (1 << (CCSDS_TC_SQRT_BITS << 1))
42 | # define CCSDS_TC_MAX_LH 0
43 | # define CCSDS_TC_MIN_LH ((softbit_t) 0x80000000)
44 | # define CCSDS_TC_IMPOSSIBLE(x) ((x) == 0x80000000)
45 | # define CCSDS_TC_MAX(a, b) ((a) > (b) ? (a) : (b))
46 | # define CCSDS_TC_SAFE_SUM(a, b) \
47 | ((a) == CCSDS_TC_MIN_LH || (b) == CCSDS_TC_MIN_LH \
48 | ? CCSDS_TC_MIN_LH \
49 | : (a) + (b))
50 |
51 | typedef int32_t softbit_t;
52 | #else
53 | # define CCSDS_TC_SCALE 1.0
54 | # define CCSDS_TC_SSQRT 1.0
55 | # define CCSDS_TC_MAX_LH 0
56 | # define CCSDS_TC_MIN_LH -INFINITY
57 | # define CCSDS_TC_IMPOSSIBLE(x) isinf(x)
58 | # define CCSDS_TC_MAX(a, b) fmax(a, b)
59 | # define CCSDS_TC_SAFE_SUM(a, b) ((a) + (b))
60 | typedef float softbit_t;
61 | #endif
62 |
63 |
64 | #define CCSDS_TC_F2SB(x) ((softbit_t) ((x) * CCSDS_TC_SCALE))
65 | #define CCSDS_TC_SB2F(x) ((float) (x) / (float) CCSDS_TC_SCALE)
66 |
67 | #define CCSDS_TC_STATE_BITS 4
68 |
69 | #define CCSDS_TC_STATE_NUM (1 << CCSDS_TC_STATE_BITS)
70 | #define CCSDS_TC_STATE_MASK (CCSDS_TC_STATE_NUM - 1)
71 | #define CCSDS_TC_STATE_ZERO 0
72 | #define CCSDS_TC_STATE_UNUSED 0xff
73 |
74 | #define CCSDS_TC_G0 0x19 /* 11001 */
75 | #define CCSDS_TC_G1 0x1b /* 11011 */
76 | #define CCSDS_TC_G2 0x15 /* 10101 */
77 | #define CCSDS_TC_G3 0x1f /* 11111 */
78 |
79 | #define CCSDS_TC_EBN0_TO_LC(ebn0) (4 * (ebn0))
80 |
81 | /* Better when state matches word length */
82 | typedef uint32_t state_t;
83 | typedef state_t poly_t;
84 |
85 | /************************* CONVOLUTIONAL CODE API ***************************/
86 |
87 | /*
88 | * HOW IS THE CONVOLUTIONAL CODE ACTUALLY REPRESENTED
89 | *
90 | * ccsds_tc_bcjr assumes a generic convolutional code
91 | * over a recursive shift register with the following
92 | * internal wiring:
93 | *
94 | * cn-1 c3 c2 c1 c0
95 | * Dn-1 <- ... <--- D2 <--- D1 <--- D0 <---(+)---- u
96 | * | cn | | | ^
97 | * v v v v |
98 | * (?)----- ... ----(?)-----(?)-----(?)------/
99 | * bn b3 b2 b1
100 | *
101 | * In order to recover a systematic output, the output
102 | * of the XOR right before c0 must be XOR'ed back by the
103 | * backward connections again. This is easily achiveable
104 | * if the systematic forward output polynomial matches
105 | * the backward polynomial up to c1. c0 must be set to 1
106 | * in this case.
107 | *
108 | * On the other hand, the j-th bit of the backward polynomial
109 | * refers to the output of the j-1-th latch. b1 is connected to
110 | * the output of D0, b2 is connected to the otput of D1, etc.
111 | *
112 | * For instance, the example provided by Ammar Abh which was based
113 | * on a systematic convolutional code of the following structure:
114 | *
115 | * c0
116 | * D0 <---(+)--- u
117 | * | ^ v0 = c0 ^ c1
118 | * | c1 | v1 = c0
119 | * \-------/
120 | *
121 | * The forward polynomials for v0 and v1 are:
122 | * G(v0) = 11
123 | * G(v1) = 01
124 | *
125 | * While the backward connection polynomial is simply:
126 | * G(bi) = 1X
127 | *
128 | * The state_t type represents the ltches D0...Dn-1
129 | */
130 |
131 | struct ccsds_tc_bcjr_ctx {
132 | unsigned int h; /* Information bits */
133 | unsigned int length; /* Data size as accepted by the algo */
134 | unsigned int n; /* Codeword length */
135 |
136 | /* Cached data */
137 | softbit_t *rv;
138 | bool rv_cached;
139 |
140 | state_t S_last_0;
141 | state_t S_last_1;
142 |
143 | /* Trellis connections */
144 | poly_t poly_b;
145 | state_t *S_next;
146 | state_t *S_prev;
147 |
148 | /* Codeword table */
149 | state_t *hv;
150 |
151 | /* Path metrics */
152 | softbit_t *astar;
153 | softbit_t *bstar;
154 |
155 | /* Branch metric */
156 |
157 | /*
158 | * gstar_dir[2 * (STATE_NUM * l + Si) + 0]: gamma_l*(Si, next(Si, 0))
159 | * gstar_dir[2 * (STATE_NUM * l + Si) + 1]: gamma_l*(Si, next(Si, 1))
160 | */
161 | softbit_t *gstar_dir;
162 |
163 | /*
164 | * gstar_rev[2 * (STATE_NUM * l + Si) + 0]: gamma_l*(prev(Si, 0), Si)
165 | * gstar_rev[2 * (STATE_NUM * l + Si) + 1]: gamma_l*(prev(Si, 1), Si)
166 | */
167 | softbit_t *gstar_rev;
168 | softbit_t *min_lh_buf;
169 | };
170 |
171 | typedef struct ccsds_tc_bcjr_ctx ccsds_tc_bcjr_ctx_t;
172 |
173 | bool ccsds_tc_bcjr_ctx_init(
174 | ccsds_tc_bcjr_ctx_t *self,
175 | const poly_t *poly_f,
176 | poly_t poly_b,
177 | unsigned int n,
178 | unsigned int h);
179 |
180 | bool ccsds_tc_bcjr_ctx_decode(
181 | ccsds_tc_bcjr_ctx_t *self,
182 | const softbit_t *x,
183 | const softbit_t *L_u,
184 | softbit_t *y,
185 | softbit_t L_c,
186 | const int *__restrict permute_out);
187 |
188 | CCSDS_TC_INLINE void
189 | ccsds_tc_bcjr_invalidate_cache(ccsds_tc_bcjr_ctx_t *self)
190 | {
191 | self->rv_cached = false;
192 | }
193 |
194 | void ccsds_tc_bcjr_ctx_finalize(ccsds_tc_bcjr_ctx_t *self);
195 |
196 | /************************* CONVOLUTIONAL CODE API ***************************/
197 | enum ccsds_tc_decoder_block_length {
198 | CCSDS_TC_BLOCK_LENGTH_223 = 1,
199 | CCSDS_TC_BLOCK_LENGTH_446 = 2,
200 | CCSDS_TC_BLOCK_LENGTH_892 = 4,
201 | CCSDS_TC_BLOCK_LENGTH_1115 = 5
202 | };
203 |
204 | struct ccsds_tc_decoder_params {
205 | unsigned int iters;
206 | unsigned int rate;
207 | unsigned int block_len;
208 | softbit_t L_c;
209 | };
210 |
211 | #define ccsds_tc_decoder_params_INITIALIZER \
212 | { \
213 | 20, /* iters */ \
214 | 6, /* rate */ \
215 | CCSDS_TC_BLOCK_LENGTH_223, /* block_len */ \
216 | 4, /* L_c */ \
217 | }
218 |
219 | struct ccsds_tc_decoder {
220 | struct ccsds_tc_decoder_params params;
221 | softbit_t *x1, *x2;
222 | softbit_t *y1, *y2;
223 | softbit_t *y0;
224 |
225 | void (*ser2par_sb_cb) (struct ccsds_tc_decoder *, const softbit_t *);
226 | void (*ser2par_float_cb) (struct ccsds_tc_decoder *, const float *);
227 |
228 | int *permutate_dir;
229 | int *permutate_inv;
230 | unsigned int n1;
231 | unsigned int rate;
232 | unsigned int length;
233 | unsigned int terminated_len;
234 | unsigned int cw_length;
235 | ccsds_tc_bcjr_ctx_t dec1;
236 | ccsds_tc_bcjr_ctx_t dec2;
237 | };
238 |
239 | typedef struct ccsds_tc_decoder ccsds_tc_decoder_t;
240 |
241 | bool ccsds_tc_decoder_init(
242 | ccsds_tc_decoder_t *self,
243 | const struct ccsds_tc_decoder_params *params);
244 |
245 | CCSDS_TC_INLINE unsigned int
246 | ccsds_tc_decoder_get_block_size(const ccsds_tc_decoder_t *self)
247 | {
248 | return self->length;
249 | }
250 |
251 | CCSDS_TC_INLINE unsigned int
252 | ccsds_tc_decoder_get_codeword_size(const ccsds_tc_decoder_t *self)
253 | {
254 | return self->cw_length;
255 | }
256 |
257 | CCSDS_TC_INLINE float
258 | ccsds_tc_get_error_rate(const ccsds_tc_decoder_t *self)
259 | {
260 | unsigned int i, count = 0;
261 |
262 | for (i = 0; i < self->length; ++i)
263 | count += (self->x1[self->n1 * i] < 0) == (self->y2[i] < 0);
264 |
265 | return (float) count / (float) self->length;
266 | }
267 |
268 | CCSDS_TC_INLINE const softbit_t *
269 | ccsds_tc_get_intermediate_output(const ccsds_tc_decoder_t *self)
270 | {
271 | return self->y1;
272 | }
273 |
274 | CCSDS_TC_INLINE const softbit_t *
275 | ccsds_tc_decoder_get_output(const ccsds_tc_decoder_t *self)
276 | {
277 | return self->y2;
278 | }
279 |
280 | void ccsds_tc_decoder_feed_block(ccsds_tc_decoder_t *self, const softbit_t *block);
281 | void ccsds_tc_decoder_feed_block_float(ccsds_tc_decoder_t *self, const float *block);
282 |
283 | void ccsds_tc_decoder_finalize(ccsds_tc_decoder_t *self);
284 |
285 | #endif /* _CCSDS_TC_H */
286 |
--------------------------------------------------------------------------------
/ccsds-tool.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 |
38 | #include "ccsds-modem.h"
39 | #include "ccsds-guess.h"
40 | #include "defs.h"
41 |
42 | #define CCSDS_TOOL_VERSION "0.1"
43 | #define CCSDS_TOOL_RATE_REPORT_INTERVAL_S 1
44 | #define CCSDS_TOOL_READ_BUFFER_SIZE (4096 / sizeof(float complex))
45 |
46 | struct ccsds_tool_options {
47 | const char *guess;
48 | const char *file;
49 | const char *ofile;
50 | unsigned int port;
51 | unsigned int rate_inv;
52 | bool q_channel;
53 | float snr;
54 | unsigned int iters;
55 | unsigned int block_size;
56 | };
57 |
58 | #define ccsds_tool_options_INITIALIZER \
59 | { \
60 | NULL, /* guess */ \
61 | NULL, /* file */ \
62 | NULL, /* ofile */ \
63 | 0, /* port */ \
64 | 6, /* rate_inv */ \
65 | true, /* q_channel */ \
66 | 19.03, /* snr */ \
67 | 1, /* iters */ \
68 | 8920, /* block_size */ \
69 | }
70 |
71 | static int
72 | open_listening_socket(const char *argv0, uint16_t port)
73 | {
74 | struct sockaddr_in server_addr;
75 | struct sockaddr_in client_addr;
76 | unsigned int structsiz;
77 | int sfd, cfd = -1;
78 | int flg;
79 |
80 | flg = 1;
81 |
82 | structsiz = sizeof(struct sockaddr_in);
83 |
84 | server_addr.sin_family = AF_INET;
85 | server_addr.sin_addr.s_addr = INADDR_ANY;
86 | server_addr.sin_port = htons(port);
87 |
88 | if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
89 | fprintf(
90 | stderr,
91 | "%s: cannot create socket: %s\n",
92 | argv0,
93 | strerror(errno));
94 | goto done;
95 | }
96 |
97 | if (setsockopt(
98 | sfd,
99 | SOL_SOCKET,
100 | SO_REUSEADDR,
101 | &flg,
102 | sizeof(int))) {
103 | fprintf(
104 | stderr,
105 | "%s: cannot set SO_REUSEADDR: %s\n",
106 | argv0,
107 | strerror(errno));
108 | goto done;
109 | }
110 |
111 | if (bind(
112 | sfd,
113 | (struct sockaddr*) &server_addr,
114 | sizeof (struct sockaddr_in))) {
115 | fprintf(
116 | stderr,
117 | "%s: cannot bind socket address: %s\n",
118 | argv0,
119 | strerror(errno));
120 | goto done;
121 | }
122 |
123 | if (listen(sfd, 5) == -1) {
124 | fprintf(
125 | stderr,
126 | "%s: cannot listen on socket: %s\n",
127 | argv0,
128 | strerror(errno));
129 | goto done;
130 | }
131 |
132 | signal(SIGPIPE, SIG_IGN);
133 |
134 | cfd = accept(
135 | sfd,
136 | (struct sockaddr *) &client_addr,
137 | &structsiz);
138 |
139 | if (cfd == -1) {
140 | fprintf(
141 | stderr,
142 | "%s: cannot accept connection: %s\n",
143 | argv0,
144 | strerror(errno));
145 | goto done;
146 | }
147 |
148 | done:
149 | if (sfd != -1)
150 | close(sfd);
151 |
152 | return cfd;
153 | }
154 |
155 | CCSDS_TC_INLINE void
156 | report_rate(unsigned int count, const struct timeval *diff)
157 | {
158 | float rate = (count << 3) / (diff->tv_sec + diff->tv_usec * 1e-6);
159 |
160 | if (rate < 1e3)
161 | fprintf(stderr, "Decode rate: %.2f bps\r", rate);
162 | else if (rate < 1e6)
163 | fprintf(stderr, "Decode rate: %.2f kbps\r", rate * 1e-3);
164 | else
165 | fprintf(stderr, "Decode rate: %.2f Mbps\r", rate * 1e-6);
166 | }
167 |
168 | bool
169 | run_guess(const char *argv0, const struct ccsds_tool_options *opt)
170 | {
171 | struct ccsds_tc_report report;
172 | struct stat sbuf;
173 | unsigned int L;
174 | int fd = -1;
175 | float complex *samples = (float complex *) -1;
176 | bool ok = false;
177 |
178 | if (stat(opt->guess, &sbuf) == -1) {
179 | fprintf(
180 | stderr,
181 | "%s: cannot stat `%s': %s\n",
182 | argv0,
183 | opt->guess,
184 | strerror(errno));
185 | exit(EXIT_FAILURE);
186 | }
187 |
188 | if ((fd = open(opt->guess, O_RDONLY)) == -1) {
189 | fprintf(
190 | stderr,
191 | "%s: cannot open `%s' for reading: %s\n",
192 | argv0,
193 | opt->guess,
194 | strerror(errno));
195 | exit(EXIT_FAILURE);
196 | }
197 |
198 | if ((samples = mmap(
199 | NULL,
200 | sbuf.st_size,
201 | PROT_READ,
202 | MAP_PRIVATE,
203 | fd,
204 | 0)) == (const float complex *) -1) {
205 | fprintf(
206 | stderr,
207 | "%s: cannot mmap `%s': %s\n",
208 | argv0,
209 | opt->guess,
210 | strerror(errno));
211 | exit(EXIT_FAILURE);
212 | }
213 |
214 | L = sbuf.st_size / sizeof(float complex);
215 |
216 | if (!ccsds_tc_report_find_best(
217 | &report,
218 | samples,
219 | L,
220 | pow(10, .1 * opt->snr)))
221 | goto done;
222 |
223 | ok = true;
224 |
225 | done:
226 | if (fd != -1)
227 | close(fd);
228 |
229 | if (samples != (const float complex *) -1)
230 | munmap(samples, sbuf.st_size);
231 |
232 | return ok;
233 | }
234 |
235 | static bool
236 | run_decoder(const char *argv0, const struct ccsds_tool_options *opt)
237 | {
238 | FILE *ifp = stdin;
239 | FILE *ofp = stdout;
240 | float complex *buffer = NULL;
241 | int sfd = -1;
242 | size_t got;
243 | unsigned int count = 0, total = 0;
244 | ccsds_modem_t *modem = NULL;
245 | struct ccsds_modem_params params = ccsds_modem_params_INITIALIZER;
246 | float rate;
247 | struct timeval tv, otv, diff;
248 | bool ok = false;
249 |
250 | params.block_len = opt->block_size / (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3);
251 | params.iters = opt->iters;
252 | params.q_channel = opt->q_channel;
253 | params.sync_snr = powf(10, .1 * opt->snr);
254 | params.rate_inv = opt->rate_inv;
255 |
256 | fprintf(stderr, "CCSDS tool v" CCSDS_TOOL_VERSION " for the Amateur DSN by EA1IYR\n");
257 | fprintf(stderr, "(c) 2021 Gonzalo J. Carracedo - https://actinid.org\n");
258 | fprintf(stderr, " Code rate: 1/%d\n", opt->rate_inv);
259 | fprintf(stderr, " Block length: %d bits\n", opt->block_size);
260 | fprintf(stderr, " Turbocode iters: %d\n", params.iters);
261 | fprintf(stderr, " Channel: %c\n", opt->q_channel ? 'Q' : 'I');
262 | fprintf(stderr, " Sync SNR: %g dB\n", opt->snr);
263 |
264 | TRY_ALLOC(buffer, CCSDS_TOOL_READ_BUFFER_SIZE, float complex);
265 |
266 | if ((modem = ccsds_modem_new(¶ms)) == NULL)
267 | goto done;
268 |
269 | /* Open input file */
270 | if (opt->port != 0) {
271 | fprintf(stderr, " Waiting for connections to port %u (TCP)\n", opt->port);
272 | if ((sfd = open_listening_socket(argv0, opt->port)) == -1)
273 | goto done;
274 | if ((ifp = fdopen(sfd, "rb")) == NULL) {
275 | fprintf(
276 | stderr,
277 | "%s: failed to open socket as file: %s\n",
278 | argv0,
279 | strerror(errno));
280 | goto done;
281 | }
282 | } else if (opt->file != NULL) {
283 | if ((ifp = fopen(opt->file, "rb")) == NULL) {
284 | fprintf(
285 | stderr,
286 | "%s: failed to open `%s' for reading: %s\n",
287 | argv0,
288 | strerror(errno));
289 | goto done;
290 | }
291 | fprintf(stderr, " Input file: %s\n", opt->file);
292 | } else {
293 | fprintf(stderr, " Input file: stdin\n");
294 | }
295 |
296 | /* Open output file */
297 | if (opt->ofile) {
298 | if ((ofp = fopen(opt->ofile, "wb")) == NULL) {
299 | fprintf(
300 | stderr,
301 | "%s: failed to open `%s' for writing: %s\n",
302 | argv0,
303 | strerror(errno));
304 | goto done;
305 | }
306 | fprintf(stderr, " Output file: %s\n", opt->ofile);
307 | } else {
308 | fprintf(stderr, " Output file: stdout\n");
309 | }
310 |
311 | /* Reader loop */
312 | gettimeofday(&otv, NULL);
313 | while ((got =
314 | fread(
315 | buffer,
316 | sizeof(float complex),
317 | CCSDS_TOOL_READ_BUFFER_SIZE,
318 | ifp)) > 0) {
319 | if (ccsds_modem_feed(modem, buffer, got)) {
320 | /* Frame found with valid CRC! */
321 | fwrite(
322 | ccsds_modem_get_frame_data(modem),
323 | ccsds_modem_get_frame_length(modem),
324 | 1,
325 | ofp);
326 | count += ccsds_modem_get_frame_length(modem);
327 | total += ccsds_modem_get_frame_length(modem);
328 | gettimeofday(&tv, NULL);
329 | timersub(&tv, &otv, &diff);
330 |
331 | if (diff.tv_sec >= CCSDS_TOOL_RATE_REPORT_INTERVAL_S) {
332 | report_rate(count, &diff);
333 | otv = tv;
334 | count = 0;
335 | }
336 | }
337 | }
338 |
339 | gettimeofday(&tv, NULL);
340 | timersub(&tv, &otv, &diff);
341 | report_rate(count, &diff);
342 | fputc('\n', stderr);
343 | fprintf(stderr, "%s: %d bytes decoded\n", argv0, total);
344 |
345 | ok = true;
346 |
347 | done:
348 | if (modem != NULL)
349 | ccsds_modem_destroy(modem);
350 |
351 | if (sfd != -1)
352 | close(sfd);
353 |
354 | if (ofp != NULL && ofp != stdout)
355 | fclose(ofp);
356 |
357 | if (ifp != NULL && ifp != stdin)
358 | fclose(ifp);
359 |
360 | if (buffer != NULL)
361 | free(buffer);
362 |
363 | return ok;
364 | }
365 |
366 | static bool
367 | run(const char *argv0, const struct ccsds_tool_options *opt)
368 | {
369 | bool ok;
370 |
371 | if (opt->guess != NULL)
372 | ok = run_guess(argv0, opt);
373 | else
374 | ok = run_decoder(argv0, opt);
375 |
376 | return ok;
377 | }
378 |
379 | void
380 | help(const char *argv0)
381 | {
382 | fprintf(stderr, "Usage:\n");
383 | fprintf(stderr, " %s [options]\n\n", argv0);
384 | fprintf(stderr, "Attempts to guess and decode CSSDS turbo-coded frames from demodulated\n");
385 | fprintf(stderr, "32-bit complex float samples.\n\n");
386 |
387 | fprintf(stderr, "OPTIONS:\n");
388 | fprintf(stderr, " -g, --guess file Attempt to guess turbocode parameters from input file\n");
389 | fprintf(stderr, " -r, --rate 1/k Set the code rate to 1/k (default is 1/6)\n");
390 | fprintf(stderr, " -s, --block-size LEN Set the block size to LEN bits (default is 8920)\n");
391 | fprintf(stderr, " -S, --sync-snr LEVEL Set the syncword correlation SNR to LEVEL dB\n");
392 | fprintf(stderr, " (default is 19.03 dB)\n");
393 | fprintf(stderr, " -c, --component C Sets the complex channel to use (I or Q, default is\n");
394 | fprintf(stderr, " Q: quadrature channel)\n");
395 | fprintf(stderr, " -i, --iters NUM Set the number of iterations of the BCJR turbo code\n");
396 | fprintf(stderr, " decoder (default is 1)\n");
397 | fprintf(stderr, " -f, --file PATH Read complex samples from a file instead of the\n");
398 | fprintf(stderr, " standard input\n");
399 | fprintf(stderr, " -o, --output PATH Write decoded frames to PATH instead of the\n");
400 | fprintf(stderr, " standard output\n");
401 | fprintf(stderr, " -l, --listen PORT Read complex samples from a listening socket at port\n");
402 | fprintf(stderr, " PORT instead of the standard input\n\n");
403 | fprintf(stderr, " -h, --help This help\n\n");
404 |
405 | fprintf(stderr, "Copyright (C) 2021 Gonzalo J. Carracedo (EA1IYR)\n");
406 | fprintf(stderr, "Hints on BCJR optimizations by r00t and Daniel Estévez (EA4GPZ)\n\n");
407 | fprintf(stderr, "This is free software; see the source for copying conditions. There is NO\n");
408 | fprintf(stderr, "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");
409 | }
410 |
411 | static struct option g_options[] = {
412 | {"rate", 1, 0, 'r'},
413 | {"block-size", 1, 0, 's'},
414 | {"sync-snr", 1, 0, 'S'},
415 | {"component", 1, 0, 'c'},
416 | {"iters", 1, 0, 'i'},
417 | {"file", 1, 0, 'f'},
418 | {"output", 1, 0, 'o'},
419 | {"listen", 1, 0, 'l'},
420 | {"guess", 1, 0, 'g'},
421 | {"help", 0, 0, 'h'},
422 | {0, 0, 0, 0}
423 | };
424 |
425 | int
426 | main(int argc, char **argv)
427 | {
428 | int c;
429 | int digit_optind = 0;
430 | int option_index;
431 | struct ccsds_tool_options options = ccsds_tool_options_INITIALIZER;
432 |
433 | while ((c = getopt_long(
434 | argc,
435 | argv,
436 | "r:s:S:c:i:g:f:o:l:h",
437 | g_options,
438 | &option_index)) != -1) {
439 |
440 | switch (c) {
441 | case 'r':
442 | if (sscanf(optarg, "1/%u", &options.rate_inv) != 1) {
443 | fprintf(stderr, "%s: invalid rate `%s'\n", argv[0], optarg);
444 | exit(EXIT_FAILURE);
445 | }
446 | break;
447 |
448 | case 's':
449 | if (sscanf(optarg, "%u", &options.block_size) != 1) {
450 | fprintf(stderr, "%s: invalid block size `%s'\n", argv[0], optarg);
451 | exit(EXIT_FAILURE);
452 | }
453 |
454 | if ((options.block_size % (CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3)) != 0) {
455 | fprintf(
456 | stderr,
457 | "%s: invalid block size %d: it must be a multiple of %d\n",
458 | argv[0],
459 | options.block_size,
460 | CCSDS_TC_MINIMUM_BLOCK_LENGTH << 3);
461 | exit(EXIT_FAILURE);
462 | }
463 | break;
464 |
465 | case 'S':
466 | if (sscanf(optarg, "%f", &options.snr) != 1) {
467 | fprintf(stderr, "%s: invalid sync SNR `%s'\n", argv[0], optarg);
468 | exit(EXIT_FAILURE);
469 | }
470 | break;
471 |
472 | case 'c':
473 | if (tolower(*optarg) == 'i') {
474 | options.q_channel = false;
475 | } else if (tolower(*optarg) == 'q') {
476 | options.q_channel = true;
477 | } else {
478 | fprintf(stderr, "%s: invalid component `%c'\n", argv[0], *optarg);
479 | exit(EXIT_FAILURE);
480 | }
481 | break;
482 |
483 | case 'g':
484 | if ((options.guess = strdup(optarg)) == NULL) {
485 | fprintf(stderr, "%s: memory exhausted\n", argv[0]);
486 | exit(EXIT_FAILURE);
487 | }
488 | break;
489 |
490 | case 'f':
491 | if ((options.file = strdup(optarg)) == NULL) {
492 | fprintf(stderr, "%s: memory exhausted\n", argv[0]);
493 | exit(EXIT_FAILURE);
494 | }
495 | break;
496 |
497 | case 'o':
498 | if ((options.ofile = strdup(optarg)) == NULL) {
499 | fprintf(stderr, "%s: memory exhausted\n", argv[0]);
500 | exit(EXIT_FAILURE);
501 | }
502 | break;
503 |
504 | case 'l':
505 | if (sscanf(optarg, "%u", &options.port) != 1
506 | || options.port == 0
507 | || options.port > 65536) {
508 | fprintf(stderr, "%s: invalid port `%s'\n", argv[0], optarg);
509 | exit(EXIT_FAILURE);
510 | }
511 | break;
512 |
513 | case 'i':
514 | if (sscanf(optarg, "%u", &options.iters) != 1) {
515 | fprintf(stderr, "%s: invalid iteration number `%s'\n", argv[0], optarg);
516 | exit(EXIT_FAILURE);
517 | }
518 | break;
519 |
520 | case 'h':
521 | help(argv[0]);
522 | exit(EXIT_SUCCESS);
523 |
524 | case '?':
525 | exit(EXIT_FAILURE);
526 | }
527 | }
528 |
529 | if (!run(argv[0], &options))
530 | exit(EXIT_FAILURE);
531 |
532 | return 0;
533 | }
534 |
--------------------------------------------------------------------------------
/correlator.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #include "correlator.h"
25 | #include "defs.h"
26 |
27 | void
28 | correlator_finalize(correlator_t *self)
29 | {
30 | if (self->sequence != NULL)
31 | free(self->sequence);
32 | }
33 |
34 | bool
35 | correlator_init_from_string(correlator_t *self, const char *bits)
36 | {
37 | unsigned int i;
38 | bool ok = false;
39 |
40 | memset(self, 0, sizeof(correlator_t));
41 |
42 | self->seq_size = strlen(bits);
43 |
44 | TRY_ALLOC(self->sequence, self->seq_size, float);
45 |
46 | for (i = 0; i < self->seq_size; ++i) {
47 | if (bits[i] == '0')
48 | self->sequence[i] = -1;
49 | else if (bits[i] == '1')
50 | self->sequence[i] = +1;
51 | else
52 | self->sequence[i] = 0;
53 | }
54 |
55 | ok = true;
56 |
57 | done:
58 | if (!ok)
59 | correlator_finalize(self);
60 |
61 | return ok;
62 | }
63 |
64 | bool
65 | correlator_find(correlator_t *self, const float *__restrict x, unsigned int len, float SNR)
66 | {
67 | unsigned int i, j, p;
68 | float threshold = 0;
69 | float dotprod = 0;
70 | bool found = false;
71 |
72 | threshold = 0;
73 |
74 | if (len < self->seq_size)
75 | return false;
76 |
77 | for (i = 0; i < len; ++i)
78 | threshold += x[i] * x[i];
79 |
80 | /*
81 | * This is sigma2 as seen at the correlators output in the absence
82 | * of correlation.
83 | */
84 | threshold *= (float) self->seq_size / (float) len;
85 | threshold *= SNR;
86 |
87 | self->snr = 0;
88 |
89 | for (i = 0; i < len - self->seq_size; ++i) {
90 | dotprod = 0;
91 | p = i;
92 | for (j = 0; j < self->seq_size; ++j)
93 | dotprod += self->sequence[j] * x[p++];
94 |
95 | if (dotprod > threshold && fabs(dotprod / threshold) > self->snr) {
96 | self->snr = fabs(dotprod / threshold);
97 | self->reverse = dotprod < 0;
98 | self->where = i;
99 | found = true;
100 | }
101 | }
102 |
103 | return found;
104 | }
105 |
106 | bool
107 | correlator_find_complex(
108 | correlator_t *self,
109 | const float complex *__restrict x,
110 | unsigned int len,
111 | float SNR,
112 | bool quadrature)
113 | {
114 | unsigned int i, j, p;
115 | float threshold = 0;
116 | float sigma2 = 0;
117 | float dotprod = 0;
118 | float corrpwr;
119 | bool found = false;
120 |
121 | sigma2 = 0;
122 |
123 | if (len < self->seq_size)
124 | return false;
125 |
126 | if (quadrature)
127 | for (i = 0; i < len; ++i)
128 | sigma2 += cimagf(x[i]) * cimagf(x[i]);
129 | else
130 | for (i = 0; i < len; ++i)
131 | sigma2 += crealf(x[i]) * crealf(x[i]);
132 |
133 | /*
134 | * This is sigma2 as seen at the correlators output in the absence
135 | * of correlation.
136 | */
137 | sigma2 *= (float) self->seq_size / (float) len;
138 | threshold = SNR * sigma2;
139 |
140 | self->snr = 0;
141 |
142 | for (i = 0; i < len - self->seq_size; ++i) {
143 | dotprod = 0;
144 | p = i;
145 |
146 | if (quadrature)
147 | for (j = 0; j < self->seq_size; ++j)
148 | dotprod += self->sequence[j] * cimagf(x[p++]);
149 | else
150 | for (j = 0; j < self->seq_size; ++j)
151 | dotprod += self->sequence[j] * crealf(x[p++]);
152 |
153 | /*
154 | * The idea is that the mean of the square of the correlation
155 | * is given by len(seq_size) * Var(x)
156 | */
157 | corrpwr = dotprod * dotprod;
158 |
159 | if (corrpwr > threshold && corrpwr / sigma2 > self->snr) {
160 | self->snr = corrpwr / sigma2;
161 | self->reverse = dotprod < 0;
162 | self->where = i;
163 | found = true;
164 | }
165 | }
166 |
167 | return found;
168 | }
169 |
170 |
--------------------------------------------------------------------------------
/correlator.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #ifndef _CCSDS_CORRELATOR_H
21 | #define _CCSDS_CORRELATOR_H
22 |
23 | #include
24 | #include
25 |
26 | #include
27 |
28 | struct correlator {
29 | unsigned int seq_size;
30 | float *sequence;
31 |
32 | /* To be updated when a candidate is found */
33 | unsigned int where;
34 | float snr;
35 | bool reverse;
36 | };
37 |
38 | typedef struct correlator correlator_t;
39 |
40 | #define correlator_INITIALIZER {0, NULL, 0, 0, 0}
41 |
42 | bool correlator_init_from_string(correlator_t *self, const char *bits);
43 |
44 | bool correlator_find_complex(
45 | correlator_t *self,
46 | const float complex *x,
47 | unsigned int len,
48 | float SNR,
49 | bool quadrature);
50 |
51 | bool correlator_find(
52 | correlator_t *self,
53 | const float *x,
54 | unsigned int len,
55 | float SNR);
56 |
57 |
58 | void correlator_finalize(correlator_t *self);
59 |
60 | static inline float
61 | correlator_get_snr(const correlator_t *self)
62 | {
63 | return self->snr;
64 | }
65 |
66 | static inline unsigned int
67 | correlator_get_pos(const correlator_t *self)
68 | {
69 | return self->where;
70 | }
71 |
72 | static inline bool
73 | correlator_is_reverse(const correlator_t *self)
74 | {
75 | return self->reverse;
76 | }
77 |
78 | #endif /* _CCSDS_CORRELATOR_H */
79 |
--------------------------------------------------------------------------------
/defs.h:
--------------------------------------------------------------------------------
1 | #ifndef _DEFS_H
2 | #define _DEFS_H
3 |
4 | #include
5 | #include
6 |
7 | #define TRY_ALLOC(where, N, what) \
8 | if ((where = malloc(N * sizeof(what))) == NULL) { \
9 | fprintf( \
10 | stderr, \
11 | "%s: failed to allocate %d elements of type %s\n", \
12 | __FILE__, \
13 | N, \
14 | #what); \
15 | goto done; \
16 | }
17 |
18 | #define TRY_CALLOC(where, N, what) \
19 | if ((where = calloc(N, sizeof(what))) == NULL) { \
20 | fprintf( \
21 | stderr, \
22 | "%s: failed to allocate %d elements of type %s\n", \
23 | __FILE__, \
24 | N, \
25 | #what); \
26 | goto done; \
27 | }
28 |
29 | #ifndef __GNUC__
30 | # ifdef __restricted
31 | # undef __restricted
32 | # endif /* __restricted */
33 | # define __restricted
34 | #endif /* __GNUC__ */
35 |
36 | #endif /* _DEFS_H */
37 |
--------------------------------------------------------------------------------
/lfsr.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "lfsr.h"
26 |
27 | #define TRY(expr) if (!(expr)) goto fail
28 |
29 | #define STRBUILD_BSIZ 16
30 |
31 | static inline unsigned int
32 | popcount64(uint64_t b)
33 | {
34 | b = (b & 0x5555555555555555ull) + (b >> 1 & 0x5555555555555555ull);
35 | b = (b & 0x3333333333333333ull) + (b >> 2 & 0x3333333333333333ull);
36 | b = b + (b >> 4) & 0x0F0F0F0F0F0F0F0Full;
37 | b = b + (b >> 8);
38 | b = b + (b >> 16);
39 | b = b + (b >> 32) & 0x0000007F;
40 |
41 | return (unsigned int) b;
42 | }
43 |
44 | int
45 | is_asciiz(const char *buf, int lbound, int ubound)
46 | {
47 | register int i;
48 |
49 | for (i = lbound; i < ubound; i++)
50 | if (!buf[i])
51 | return i + 1;
52 | return 0;
53 | }
54 |
55 | char *
56 | vstrbuild(const char *fmt, va_list ap)
57 | {
58 | char *out;
59 | int size, zeroindex;
60 | int last;
61 | va_list copy;
62 |
63 | last = 0;
64 |
65 | if (fmt != NULL) {
66 | if (!*fmt) {
67 | out = malloc(1);
68 | out[0] = '\0';
69 | return out;
70 | }
71 |
72 | va_copy(copy, ap);
73 | size = vsnprintf(NULL, 0, fmt, copy) + 1;
74 | va_end(copy);
75 |
76 | if ((out = malloc(size)) == NULL)
77 | return NULL;
78 |
79 | va_copy(copy, ap);
80 | vsnprintf(out, size, fmt, copy);
81 | va_end(copy);
82 |
83 | for (;;) {
84 | if ((zeroindex = is_asciiz(out, last, size)) != 0)
85 | break;
86 |
87 | last = size;
88 | size += STRBUILD_BSIZ;
89 |
90 | out = realloc(out, size); /* Reasignamos */
91 |
92 | va_copy (copy, ap);
93 | vsnprintf(out, size, fmt, copy);
94 | va_end (copy);
95 | }
96 | }
97 | else
98 | out = NULL;
99 |
100 | return out;
101 | }
102 |
103 |
104 | /* Construye una cadena mediante el formato printf y devuelve un
105 | puntero a la cadena resultado. DEBES liberar tu mismo la salida. */
106 |
107 | /* FIXME: Buscar alguna alternativa mas portable */
108 | char*
109 | strbuild (const char *fmt, ...)
110 | {
111 | char *out;
112 | va_list ap;
113 |
114 | va_start (ap, fmt);
115 | out = vstrbuild (fmt, ap);
116 | va_end (ap);
117 |
118 | return out;
119 | }
120 |
121 | void
122 | lfsr_destroy(lfsr_t *self)
123 | {
124 | free(self);
125 | }
126 |
127 | char *
128 | lfsr_get_poly(const lfsr_t *self)
129 | {
130 | unsigned int i;
131 | char *prev = NULL;
132 | char *poly = NULL;
133 |
134 | for (i = 63; i >= 1; --i)
135 | if ((self->mask & (1ull << i)) != 0) {
136 | TRY(poly = strbuild("%sx^%d + ", prev == NULL ? "" : prev, i));
137 | if (prev != NULL)
138 | free(prev);
139 | prev = poly;
140 | }
141 |
142 | TRY(poly = strbuild("%s1", prev == NULL ? "" : prev));
143 |
144 | if (prev != NULL)
145 | free(prev);
146 |
147 | return poly;
148 |
149 | fail:
150 | if (prev != NULL)
151 | free(prev);
152 |
153 | return NULL;
154 | }
155 |
156 | void
157 | lfsr_reset(lfsr_t *self)
158 | {
159 | self->reg = (1ull << (2 * self->len)) - 1;
160 | }
161 |
162 | lfsr_t *
163 | lfsr_new(const unsigned int *taps, unsigned int tap_len)
164 | {
165 | lfsr_t *self = NULL;
166 | unsigned int i;
167 |
168 | if ((self = calloc(1, sizeof(lfsr_t))) == NULL)
169 | goto fail;
170 |
171 | for (i = 0; i < tap_len; ++i) {
172 | if (taps[i] >= LFSR_MAX_TAPS) {
173 | fprintf(stderr, "Invalid tap %d\n", taps[i]);
174 | goto fail;
175 | }
176 |
177 | self->mask |= 1ull << taps[i];
178 | if (self->len < taps[i])
179 | self->len = taps[i];
180 | }
181 |
182 | lfsr_reset(self);
183 |
184 | self->cycle_len = (1ull << self->len) - 1;
185 |
186 | --self->len;
187 |
188 | return self;
189 |
190 | fail:
191 | if (self != NULL)
192 | lfsr_destroy(self);
193 |
194 | return NULL;
195 | }
196 |
197 | static inline uint8_t
198 | lfsr_core(lfsr_t *self, uint8_t input, unsigned int direction)
199 | {
200 | uint8_t x = input & 1;
201 | uint8_t y = (popcount64(self->reg & self->mask) & 1) ^ x;
202 | uint8_t newbit = direction ? x : y;
203 | uint8_t output = direction ? y : self->reg & 1;
204 |
205 | self->reg = (self->reg >> 1) | (newbit << self->len);
206 |
207 | return y;
208 | }
209 |
210 | uint8_t
211 | lfsr_scramble(lfsr_t *self, uint8_t input)
212 | {
213 | return lfsr_core(self, input, 0);
214 | }
215 |
216 | uint8_t
217 | lfsr_descramble(lfsr_t *self, uint8_t input)
218 | {
219 | return lfsr_core(self, input, 1);
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/lfsr.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #ifndef _LFSR_H
21 | #define _LFSR_H
22 |
23 | #include
24 |
25 | #define LFSR_MAX_TAPS 63
26 |
27 | struct lfsr {
28 | uint64_t mask;
29 | uint64_t reg;
30 | uint64_t len;
31 | uint64_t cycle_len; /* Assuming it's primitive */
32 | };
33 |
34 | typedef struct lfsr lfsr_t;
35 |
36 | static inline uint64_t
37 | lfsr_get_cycle_len(const lfsr_t *self)
38 | {
39 | return self->cycle_len;
40 | }
41 |
42 | lfsr_t *lfsr_new(const unsigned int *taps, unsigned int tap_len);
43 | uint8_t lfsr_scramble(lfsr_t *self, uint8_t input);
44 | uint8_t lfsr_descramble(lfsr_t *self, uint8_t input);
45 | void lfsr_reset(lfsr_t *self);
46 | char *lfsr_get_poly(const lfsr_t *self);
47 | void lfsr_destroy(lfsr_t *);
48 |
49 | #endif /* _LFSR_H */
50 |
--------------------------------------------------------------------------------
/test.c:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (C) 2021 Gonzalo José Carracedo Carballal
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as
7 | published by the Free Software Foundation, version 3.
8 |
9 | This program is distributed in the hope that it will be useful, but
10 | WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Lesser General Public License for more details.
13 |
14 | You should have received a copy of the GNU Lesser General Public
15 | License along with this program. If not, see
16 |
17 |
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "ccsds-tc.h"
27 |
28 | #define CCSDS_TC_TEST_ITERS 3000
29 |
30 | enum ccsds_tc_test_type {
31 | CCSDS_TC_TEST_TYPE_SINGLE_RUN,
32 | CCSDS_TC_TEST_TYPE_DECODER_RATE,
33 | CCSDS_TC_TEST_TYPE_ITERATION_RATE
34 | };
35 |
36 | void
37 | run_benchmark(const softbit_t *u, const float *r, enum ccsds_tc_test_type type)
38 | {
39 | ccsds_tc_decoder_t decoder;
40 | const softbit_t *y;
41 | float rate;
42 | struct ccsds_tc_decoder_params params = ccsds_tc_decoder_params_INITIALIZER;
43 | struct timeval tv, otv, diff;
44 | unsigned int i;
45 | unsigned int length;
46 | bool b1, b2, b3;
47 |
48 | params.block_len = CCSDS_TC_BLOCK_LENGTH_223;
49 | params.iters = type == CCSDS_TC_TEST_TYPE_ITERATION_RATE
50 | ? CCSDS_TC_TEST_ITERS
51 | : 1;
52 |
53 | params.L_c = CCSDS_TC_EBN0_TO_LC(CCSDS_TC_F2SB(powf(10, .1 * -2)));
54 | params.rate = 6;
55 |
56 | if (!ccsds_tc_decoder_init(&decoder, ¶ms)) {
57 | fprintf(stderr, "%s: failed to create decoder\n", __FUNCTION__);
58 | goto done;
59 | }
60 |
61 | length = ccsds_tc_decoder_get_block_size(&decoder);
62 |
63 | switch (type) {
64 | case CCSDS_TC_TEST_TYPE_SINGLE_RUN:
65 | printf("Running one-shot decoding with %d iters\n", params.iters);
66 | ccsds_tc_decoder_feed_block_float(&decoder, r);
67 | y = ccsds_tc_decoder_get_output(&decoder);
68 |
69 | for (i = 0; i < length; ++i) {
70 | b1 = u[i] > 0;
71 | b2 = r[i * 6] > 0;
72 | b3 = y[i] > 0;
73 |
74 | if (b1 != b2 || b2 != b3) {
75 | printf("BIT %5d / %5d CORRUPTED: ", i + 1, length);
76 | if (b1 == b3) {
77 | printf("\033[1;32mCORRECTED\033[0m\n");
78 | } else {
79 | printf("\033[1;31mFAILED (L(u) = %g)\033[0m\n", CCSDS_TC_SB2F(y[i]));
80 | }
81 | }
82 | }
83 |
84 | break;
85 |
86 | case CCSDS_TC_TEST_TYPE_DECODER_RATE:
87 | printf(
88 | "Decoding the same %d-bit block %d times, press Ctrl+C to abort\n",
89 | ccsds_tc_decoder_get_block_size(&decoder),
90 | CCSDS_TC_TEST_ITERS);
91 | #ifdef CCSDS_TC_INT_ARITHMETICS
92 | printf("Note: integer arithmetics enabled. Expect overhead due to float-to-softbit\n");
93 | printf("conversions prior to each decoding.\n");
94 | #endif /* CCSDS_TC_INT_ARITHMETICS */
95 |
96 | for (;;) {
97 | gettimeofday(&otv, NULL);
98 | for (i = 0; i < CCSDS_TC_TEST_ITERS; ++i)
99 | ccsds_tc_decoder_feed_block_float(&decoder, r);
100 | gettimeofday(&tv, NULL);
101 |
102 | gettimeofday(&tv, NULL);
103 | timersub(&tv, &otv, &diff);
104 |
105 | rate = (CCSDS_TC_TEST_ITERS * length) / (diff.tv_sec + 1e-6 * diff.tv_usec);
106 |
107 | printf("Output rate: ");
108 | if (rate < 1e3)
109 | printf("%.3f bps\n", rate);
110 | else if (rate < 1e6)
111 | printf("%.3f kbps\n", rate * 1e-3);
112 | else
113 | printf("%.3f Mbps\n", rate * 1e-6);
114 | }
115 | break;
116 |
117 | case CCSDS_TC_TEST_TYPE_ITERATION_RATE:
118 | printf(
119 | "Performing %d iterations over the same %d-bit block, press Ctrl+C to abort\n",
120 | CCSDS_TC_TEST_ITERS,
121 | ccsds_tc_decoder_get_block_size(&decoder));
122 |
123 | for (;;) {
124 | gettimeofday(&otv, NULL);
125 | ccsds_tc_decoder_feed_block_float(&decoder, r);
126 | gettimeofday(&tv, NULL);
127 |
128 | gettimeofday(&tv, NULL);
129 | timersub(&tv, &otv, &diff);
130 |
131 | rate = (CCSDS_TC_TEST_ITERS * length) / (diff.tv_sec + 1e-6 * diff.tv_usec);
132 |
133 | printf("Iteration rate: ");
134 | if (rate < 1e3)
135 | printf("%.3f bps\n", rate);
136 | else if (rate < 1e6)
137 | printf("%.3f kbps\n", rate * 1e-3);
138 | else
139 | printf("%.3f Mbps\n", rate * 1e-6);
140 | }
141 | break;
142 | }
143 |
144 | done:
145 | ccsds_tc_decoder_finalize(&decoder);
146 | }
147 |
148 | int
149 | main(int argc, char **argv)
150 | {
151 | size_t size;
152 | enum ccsds_tc_test_type type = CCSDS_TC_TEST_TYPE_SINGLE_RUN;
153 | void *map, *map2;
154 | FILE *fp = fopen("turbosignal.raw", "rb");
155 | FILE *fp2 = fopen("rightsignal.raw", "rb");
156 |
157 | if (fp == NULL) {
158 | perror("turbosignal.raw");
159 | return 1;
160 | }
161 |
162 | if (fp2 == NULL) {
163 | perror("rightsignal.raw");
164 | return 1;
165 | }
166 |
167 | fseek(fp, 0, SEEK_END);
168 | size = ftell(fp);
169 | fseek(fp, 0, SEEK_SET);
170 |
171 | size /= sizeof(float);
172 |
173 | map = mmap(NULL, size * sizeof(float), PROT_READ, MAP_PRIVATE, fileno(fp), 0);
174 | if (map == (void *) - 1) {
175 | perror("mmap turbosignal");
176 | exit(EXIT_FAILURE);
177 | }
178 |
179 | map2 = mmap(NULL, size * sizeof(float) / 6, PROT_READ, MAP_PRIVATE, fileno(fp2), 0);
180 | if (map == (void *) - 1) {
181 | perror("mmap rightsignal");
182 | exit(EXIT_FAILURE);
183 | }
184 | fclose(fp);
185 |
186 | if (argc < 2) {
187 | fprintf(stderr, "%s: no benchmark specified, defaulting to single\n", argv[0]);
188 | } else {
189 | if (strcmp(argv[1], "single") == 0)
190 | type = CCSDS_TC_TEST_TYPE_SINGLE_RUN;
191 | else if (strcmp(argv[1], "rate") == 0)
192 | type = CCSDS_TC_TEST_TYPE_DECODER_RATE;
193 | else if (strcmp(argv[1], "iter") == 0)
194 | type = CCSDS_TC_TEST_TYPE_ITERATION_RATE;
195 | else {
196 | fprintf(stderr, "%s: invalid benchmark type `%s'\n", argv[0], argv[1]);
197 | exit(EXIT_FAILURE);
198 | }
199 | }
200 |
201 | run_benchmark(map2, map, type);
202 |
203 | return 0;
204 | }
205 |
--------------------------------------------------------------------------------