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