1use crate::{
17 cli::*,
18 decoder::factory::{DecoderFactory, DecoderImplementation},
19 simulation::{
20 ber::{BerTestParameters, Report, Reporter, Statistics},
21 factory::{Ber, BerTestBuilder, Modulation},
22 },
23 sparse::SparseMatrix,
24};
25use clap::{Parser, ValueEnum};
26use console::Term;
27use std::{
28 error::Error,
29 fs::File,
30 io::Write,
31 path::PathBuf,
32 sync::mpsc::{self, Receiver},
33 time::Duration,
34};
35
36#[derive(Debug, Parser)]
38#[command(about = "Performs a BER simulation")]
39pub struct Args<Dec: DecoderFactory + ValueEnum = DecoderImplementation> {
40 pub alist: PathBuf,
42 #[arg(long)]
44 pub output_file: Option<PathBuf>,
45 #[arg(long)]
47 pub output_file_ldpc: Option<PathBuf>,
48 #[arg(long, default_value = "Phif64")]
50 pub decoder: Dec,
51 #[arg(long, default_value_t = Modulation::Bpsk)]
53 pub modulation: Modulation,
54 #[arg(long)]
56 pub puncturing: Option<String>,
57 #[arg(long)]
59 pub interleaving: Option<isize>,
60 #[arg(long)]
62 pub min_ebn0: f64,
63 #[arg(long)]
65 pub max_ebn0: f64,
66 #[arg(long)]
68 pub step_ebn0: f64,
69 #[arg(long, default_value = "100")]
71 pub max_iter: usize,
72 #[arg(long, default_value = "100")]
74 pub frame_errors: u64,
75 #[arg(long, value_parser = humantime::parse_duration)]
77 pub min_time: Option<Duration>,
78 #[arg(long, value_parser = humantime::parse_duration)]
80 pub max_time: Option<Duration>,
81 #[arg(long, default_value = "0")]
83 pub bch_max_errors: u64,
84 #[arg(long, default_value_t = num_cpus::get())]
86 pub num_threads: usize,
87}
88
89impl<Dec: DecoderFactory + ValueEnum> Run for Args<Dec> {
90 fn run(&self) -> Result<(), Box<dyn Error>> {
91 let puncturing_pattern = if let Some(p) = self.puncturing.as_ref() {
92 Some(parse_puncturing_pattern(p)?)
93 } else {
94 None
95 };
96 let h = SparseMatrix::from_alist(&std::fs::read_to_string(&self.alist)?)?;
97 let mut output_file = if let Some(f) = &self.output_file {
98 Some(File::create(f)?)
99 } else {
100 None
101 };
102 let mut output_file_ldpc = match (self.bch_max_errors > 0, &self.output_file_ldpc) {
103 (true, Some(f)) => Some(File::create(f)?),
104 _ => None,
105 };
106 let num_ebn0s = ((self.max_ebn0 - self.min_ebn0) / self.step_ebn0).floor() as usize + 1;
107 let ebn0s = (0..num_ebn0s)
108 .map(|k| (self.min_ebn0 + k as f64 * self.step_ebn0) as f32)
109 .collect::<Vec<_>>();
110 let (report_tx, report_rx) = mpsc::channel();
111 let reporter = Reporter {
112 tx: report_tx,
113 interval: Duration::from_millis(500),
114 };
115 let test = BerTestBuilder {
116 modulation: self.modulation,
117 parameters: BerTestParameters {
118 h,
119 decoder_implementation: self.decoder.clone(),
120 puncturing_pattern: puncturing_pattern.as_ref().map(|v| &v[..]),
121 interleaving_columns: self.interleaving,
122 max_frame_errors: self.frame_errors,
123 min_run_time: self.min_time,
124 max_run_time: self.max_time,
125 max_iterations: self.max_iter,
126 ebn0s_db: &ebn0s,
127 reporter: Some(reporter),
128 bch_max_errors: self.bch_max_errors,
129 num_workers: self.num_threads,
130 },
131 }
132 .build()?;
133 self.write_details(std::io::stdout(), &*test)?;
134 if let Some(f) = &mut output_file {
135 self.write_details(&*f, &*test)?;
136 if self.bch_max_errors > 0 {
137 writeln!(f)?;
138 writeln!(f, "LDPC+BCH results")?;
139 writeln!(f)?;
140 }
141 }
142 if let Some(f) = &mut output_file_ldpc {
143 self.write_details(&*f, &*test)?;
144 writeln!(f)?;
145 writeln!(f, "LDPC-only results")?;
146 writeln!(f)?;
147 }
148 let mut progress = Progress::new(report_rx, output_file, output_file_ldpc);
149 let progress = std::thread::spawn(move || progress.run());
150 test.run()?;
151 #[allow(clippy::question_mark)]
153 if let Err(e) = progress.join().unwrap() {
154 return Err(e);
155 }
156 Ok(())
157 }
158}
159
160impl<Dec: DecoderFactory + ValueEnum> Args<Dec> {
161 fn write_details<W: Write>(&self, mut f: W, test: &dyn Ber) -> std::io::Result<()> {
162 writeln!(f, "BER TEST PARAMETERS")?;
163 writeln!(f, "-------------------")?;
164 writeln!(f, "Simulation:")?;
165 writeln!(f, " - Minimum Eb/N0: {:.2} dB", self.min_ebn0)?;
166 writeln!(f, " - Maximum Eb/N0: {:.2} dB", self.max_ebn0)?;
167 writeln!(f, " - Eb/N0 step: {:.2} dB", self.step_ebn0)?;
168 writeln!(f, " - Number of frame errors: {}", self.frame_errors)?;
169 if let Some(min_time) = self.min_time {
170 writeln!(
171 f,
172 " - Minimum run time per Eb/N0: {}",
173 humantime::format_duration(min_time)
174 )?;
175 }
176 if let Some(max_time) = self.max_time {
177 writeln!(
178 f,
179 " - Maximum run time per Eb/N0: {}",
180 humantime::format_duration(max_time)
181 )?;
182 }
183 writeln!(f, " - Number of worker threads: {}", self.num_threads)?;
184 writeln!(f, "Channel:")?;
185 writeln!(f, " - Modulation: {}", self.modulation)?;
186 writeln!(f, "LDPC code:")?;
187 writeln!(f, " - alist: {}", self.alist.display())?;
188 if let Some(puncturing) = self.puncturing.as_ref() {
189 writeln!(f, " - Puncturing pattern: {puncturing}")?;
190 }
191 if let Some(interleaving) = self.interleaving.as_ref() {
192 writeln!(f, " - Interleaving columns: {interleaving}")?;
193 }
194 writeln!(f, " - Information bits (k): {}", test.k())?;
195 writeln!(f, " - Codeword size (N_cw): {}", test.n_cw())?;
196 writeln!(f, " - Frame size (N): {}", test.n())?;
197 writeln!(f, " - Code rate: {:.3}", test.rate())?;
198 writeln!(f, "LDPC decoder:")?;
199 writeln!(f, " - Implementation: {}", self.decoder)?;
200 writeln!(f, " - Maximum iterations: {}", self.max_iter)?;
201 if self.bch_max_errors > 0 {
202 writeln!(f, "BCH decoder:")?;
203 writeln!(
204 f,
205 " - Maximum bit errors correctable: {}",
206 self.bch_max_errors
207 )?;
208 }
209 writeln!(f)?;
210 Ok(())
211 }
212}
213
214pub fn parse_puncturing_pattern(s: &str) -> Result<Vec<bool>, &'static str> {
220 let mut v = Vec::new();
221 for a in s.split(',') {
222 v.push(match a {
223 "0" => false,
224 "1" => true,
225 _ => return Err("invalid puncturing pattern"),
226 });
227 }
228 Ok(v)
229}
230
231#[derive(Debug)]
232struct Progress {
233 rx: Receiver<Report>,
234 term: Term,
235 output_file: Option<File>,
236 output_file_ldpc: Option<File>,
237}
238
239impl Progress {
240 fn new(
241 rx: Receiver<Report>,
242 output_file: Option<File>,
243 output_file_ldpc: Option<File>,
244 ) -> Progress {
245 Progress {
246 rx,
247 term: Term::stdout(),
248 output_file,
249 output_file_ldpc,
250 }
251 }
252
253 fn run(&mut self) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
254 ctrlc::set_handler({
255 let term = self.term.clone();
256 move || {
257 let _ = term.write_line("");
258 let _ = term.show_cursor();
259 std::process::exit(0);
260 }
261 })?;
262
263 let ret = self.work();
264 self.term.write_line("")?;
265 self.term.show_cursor()?;
266 ret
267 }
268
269 fn work(&mut self) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
270 self.term.set_title("ldpc-toolbox ber");
271 self.term.hide_cursor()?;
272 self.term.write_line(Self::format_header())?;
273 if let Some(f) = &mut self.output_file {
274 writeln!(f, "{}", Self::format_header())?;
275 }
276 if let Some(f) = &mut self.output_file_ldpc {
277 writeln!(f, "{}", Self::format_header())?;
278 }
279 let mut last_stats = None;
280 loop {
281 let Report::Statistics(stats) = self.rx.recv().unwrap() else {
282 let last_stats = last_stats.unwrap();
284 if let Some(f) = &mut self.output_file {
285 writeln!(f, "{}", &Self::format_progress(&last_stats, false))?;
286 }
287 if let Some(f) = &mut self.output_file_ldpc {
288 writeln!(f, "{}", &Self::format_progress(&last_stats, true))?;
289 }
290 return Ok(());
291 };
292 if let Some(s) = &last_stats
293 && s.ebn0_db != stats.ebn0_db
294 {
295 if let Some(f) = &mut self.output_file {
296 writeln!(f, "{}", &Self::format_progress(s, false))?;
297 }
298 if let Some(f) = &mut self.output_file_ldpc {
299 writeln!(f, "{}", &Self::format_progress(s, true))?;
300 }
301 }
302 match &last_stats {
303 Some(s) if s.ebn0_db == stats.ebn0_db => {
304 self.term.move_cursor_up(1)?;
305 self.term.clear_line()?;
306 }
307 _ => (),
308 };
309 self.term
310 .write_line(&Self::format_progress(&stats, false))?;
311 last_stats = Some(stats);
312 }
313 }
314
315 fn format_header() -> &'static str {
316 " Eb/N0 | Frames | Bit errs | Frame er | False de | BER | FER | Avg iter | Avg corr | Throughp | Elapsed\n\
317 --------|----------|----------|----------|----------|---------|---------|----------|----------|----------|----------"
318 }
319
320 fn format_progress(stats: &Statistics, force_ldpc: bool) -> String {
321 let code_stats = match (force_ldpc, &stats.bch) {
322 (true, _) => &stats.ldpc,
323 (false, Some(bch)) => bch,
324 (false, None) => &stats.ldpc,
325 };
326 format!(
327 "{:7.2} | {:8} | {:8} | {:8} | {:8} | {:7.2e} | {:7.2e} | {:8.1} | {:8.1} | {:8.3} | {}",
328 stats.ebn0_db,
329 stats.num_frames,
330 code_stats.bit_errors,
331 code_stats.frame_errors,
332 stats.false_decodes,
333 code_stats.ber,
334 code_stats.fer,
335 stats.average_iterations,
336 code_stats.average_iterations_correct,
337 stats.throughput_mbps,
338 humantime::format_duration(Duration::from_secs(stats.elapsed.as_secs()))
339 )
340 }
341}