opentitanlib/util/
bitbang.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Result, bail, ensure};
6use std::collections::HashMap;
7use std::iter::Peekable;
8use std::ops::Mul;
9use std::time::Duration;
10
11use crate::io::gpio::{BitbangEntry, DacBangEntry};
12
13#[derive(Debug, Eq, PartialEq)]
14enum Token<'a> {
15    Numeric(&'a str),
16    Alphabetic(&'a str),
17    Quoted(&'a str),
18
19    Await,
20    Linear,
21
22    LParen,
23    RParen,
24}
25
26fn get_token<'a>(
27    input: &'a str,
28    iter: &mut Peekable<std::str::CharIndices<'a>>,
29) -> Result<Option<Token<'a>>> {
30    // Skip any initial whitespace
31    let (token_start, first_char) = loop {
32        match iter.peek() {
33            Some((_, ch)) if ch.is_whitespace() => {
34                iter.next();
35            }
36            Some((idx, ch)) => break (*idx, *ch),
37            None => return Ok(None),
38        }
39    };
40    iter.next();
41    if first_char.is_numeric() || first_char == '.' {
42        let token_end = loop {
43            match iter.peek() {
44                Some((_, ch)) if ch.is_numeric() || *ch == '.' => {
45                    iter.next();
46                }
47                Some((idx, _)) => break *idx,
48                None => break input.len(),
49            }
50        };
51        Ok(Some(Token::Numeric(&input[token_start..token_end])))
52    } else if first_char.is_alphabetic() {
53        let token_end = loop {
54            match iter.peek() {
55                Some((_, ch)) if ch.is_alphabetic() => {
56                    iter.next();
57                }
58                Some((idx, _)) => break *idx,
59                None => break input.len(),
60            }
61        };
62        match &input[token_start..token_end] {
63            "await" => Ok(Some(Token::Await)),
64            "linear" => Ok(Some(Token::Linear)),
65            other => Ok(Some(Token::Alphabetic(other))),
66        }
67    } else if first_char == '\'' {
68        let token_end = loop {
69            match iter.next() {
70                Some((_, '\'')) => {
71                    if let Some((idx, _)) = iter.peek() {
72                        break *idx;
73                    } else {
74                        break input.len();
75                    }
76                }
77                Some(_) => (),
78                None => bail!("Unterminated string"),
79            }
80        };
81        Ok(Some(Token::Quoted(&input[token_start..token_end])))
82    } else if first_char == '\"' {
83        let token_end = loop {
84            match iter.next() {
85                Some((_, '\"')) => {
86                    if let Some((idx, _)) = iter.peek() {
87                        break *idx;
88                    } else {
89                        break input.len();
90                    }
91                }
92                Some(_) => (),
93                None => bail!("Unterminated string"),
94            }
95        };
96        Ok(Some(Token::Quoted(&input[token_start..token_end])))
97    } else if first_char == '(' {
98        Ok(Some(Token::LParen))
99    } else if first_char == ')' {
100        Ok(Some(Token::RParen))
101    } else {
102        bail!("Unexpected character `{}`", first_char);
103    }
104}
105
106fn get_all_tokens(input: &str) -> Result<Vec<Token<'_>>> {
107    let mut char_indices = input.char_indices().peekable();
108    let mut all_tokens = Vec::new();
109    loop {
110        let Some(token) = get_token(input, &mut char_indices)? else {
111            return Ok(all_tokens);
112        };
113        all_tokens.push(token);
114    }
115}
116
117/// This function parses a tring of the form "10 ms" or "115.2 kHz" returning the clock period as
118/// a `Duration`.
119pub fn parse_clock_frequency(input: &str) -> Result<Duration> {
120    let all_tokens = get_all_tokens(input)?;
121    let tokens: &[Token] = &all_tokens;
122    match tokens {
123        [Token::Numeric(num), Token::Alphabetic(time_unit)] => Ok(match *time_unit {
124            "ns" => Duration::from_nanos(num.parse().unwrap()),
125            "us" => Duration::from_secs_f64(num.parse::<f64>().unwrap() / 1000000.0),
126            "ms" => Duration::from_secs_f64(num.parse::<f64>().unwrap() / 1000.0),
127            "s" => Duration::from_secs_f64(num.parse().unwrap()),
128            "Hz" => Duration::from_secs(1).div_f64(num.parse().unwrap()),
129            "kHz" => Duration::from_secs(1).div_f64(num.parse::<f64>().unwrap() * 1000.0),
130            "MHz" => Duration::from_secs(1).div_f64(num.parse::<f64>().unwrap() * 1000000.0),
131            _ => bail!("Unknown unit: {}", time_unit),
132        }),
133        _ => bail!("Parse error"),
134    }
135}
136
137pub fn parse_delay(num: &str, time_unit: &str, clock: Duration) -> Result<u32> {
138    if time_unit == "ticks" {
139        let Ok(ticks) = num.parse::<u32>() else {
140            bail!("Delay must use integer number of ticks, try increasing clock frequency");
141        };
142        ensure!(
143            ticks > 0,
144            "Zero length delay requested: {} {}",
145            num,
146            time_unit
147        );
148        return Ok(ticks);
149    }
150
151    // Attempt parsing an expression such as "17 ms", and calculate how many ticks at the given
152    // clock frequency best approximates the requested delay.
153    let duration = match time_unit {
154        "us" => num.parse::<f64>().unwrap() / 1000000.0,
155        "ms" => num.parse::<f64>().unwrap() / 1000.0,
156        "s" => num.parse::<f64>().unwrap(),
157        unit => bail!("Unrecognized time unit: '{}'", unit),
158    };
159    ensure!(
160        duration > 0.0,
161        "Zero length delay requested: {} {}",
162        num,
163        time_unit
164    );
165    let duration_in_ticks = duration / clock.as_secs_f64();
166    ensure!(
167        duration_in_ticks <= u32::MAX as f64,
168        "Requested delay exceeds range, try lower clock frequency",
169    );
170    let closest_ticks = duration_in_ticks.round() as u32;
171    let actual_duration = clock.mul(closest_ticks);
172    let ratio = actual_duration.as_secs_f64() / duration;
173    ensure!(
174        (0.99..=1.01).contains(&ratio),
175        "Requested delay cannot be approximated to within 1%, try increasing clock frequency",
176    );
177    Ok(closest_ticks)
178}
179
180/// This function parses the main string argument to `opentitantool gpio bit-bang`, producing a
181/// list of `BitbangEntry` corresponding to the parsed instructions.  The slices in the entries
182/// will refer to one of the two "accumulator vectors" provided by the caller, which this function
183/// will clear out and resize according to need.
184#[allow(clippy::type_complexity)]
185pub fn parse_sequence<'a, 'wr, 'rd>(
186    input: &'a str,
187    num_pins: usize,
188    clock: Duration,
189    accumulator_rd: &'rd mut Vec<u8>,
190    accumulator_wr: &'wr mut Vec<u8>,
191) -> Result<(Box<[BitbangEntry<'rd, 'wr>]>, HashMap<&'a str, usize>)> {
192    ensure!(
193        num_pins > 0,
194        "Must specify at least one GPIO pin for bitbanging"
195    );
196    let all_tokens = get_all_tokens(input)?;
197
198    let mut token_map: HashMap<&'a str, usize> = HashMap::new();
199
200    // First pass, check how many data bytes are needed.
201    let mut needed_bytes = 0usize;
202    let mut last_token_was_capture = false;
203    let mut tokens: &[Token] = &all_tokens;
204    loop {
205        match tokens {
206            [Token::Await, Token::LParen, rest @ ..] => {
207                ensure!(
208                    !last_token_was_capture,
209                    "Capturing GPIO samples only supported immediately preceeding output, not `await`.  (Consider repeating the previous output values before the `await`.)",
210                );
211                tokens = rest;
212                loop {
213                    match tokens {
214                        [Token::Numeric(_), rest @ ..] => {
215                            tokens = rest;
216                        }
217                        [Token::Alphabetic(_), rest @ ..] => {
218                            tokens = rest;
219                        }
220                        [Token::RParen, rest @ ..] => {
221                            tokens = rest;
222                            break;
223                        }
224                        _ => {
225                            bail!("Mismatched parenthesis");
226                        }
227                    }
228                }
229                last_token_was_capture = false;
230            }
231            [Token::Numeric(_), Token::Alphabetic(_), rest @ ..] => {
232                ensure!(
233                    !last_token_was_capture,
234                    "Capturing GPIO samples only supported immediately preceeding output, not with delay.  (Consider repeating the previous output values before the delay.)",
235                );
236                tokens = rest;
237                last_token_was_capture = false;
238            }
239            [Token::Numeric(bits), rest @ ..] => {
240                ensure!(
241                    bits.len() % num_pins == 0,
242                    "Unexpected number of bits {}, should be multiple of the number of pins {}",
243                    bits.len(),
244                    num_pins,
245                );
246                needed_bytes += bits.len() / num_pins;
247                tokens = rest;
248                last_token_was_capture = false;
249            }
250            [Token::Quoted(identifier), rest @ ..] => {
251                token_map.insert(&identifier[1..identifier.len() - 1], needed_bytes);
252                tokens = rest;
253                last_token_was_capture = true;
254            }
255            [] => break,
256            _ => bail!("Parse error"),
257        }
258    }
259    accumulator_wr.clear();
260    accumulator_wr.resize(needed_bytes, 0u8);
261    accumulator_rd.clear();
262    accumulator_rd.resize(needed_bytes, 0u8);
263    let mut slice_wr: &'wr mut [u8] = accumulator_wr;
264    let mut slice_rd: &'rd mut [u8] = accumulator_rd;
265
266    // Second pass, create `BitbangEntry` instances referring to data in the accumulators.
267    let mut result = Vec::new();
268    let mut tokens: &[Token] = &all_tokens;
269    loop {
270        match tokens {
271            [Token::Await, Token::LParen, rest @ ..] => {
272                tokens = rest;
273                let mut pattern = String::new();
274                loop {
275                    match tokens {
276                        [Token::Numeric(p), rest @ ..] => {
277                            pattern = pattern + p;
278                            tokens = rest;
279                        }
280                        [Token::Alphabetic(p), rest @ ..] => {
281                            pattern = pattern + p;
282                            tokens = rest;
283                        }
284                        [Token::RParen, rest @ ..] => {
285                            tokens = rest;
286                            break;
287                        }
288                        _ => {
289                            bail!("Mismatched parenthesis");
290                        }
291                    }
292                }
293                let pattern_str = pattern.as_bytes();
294                let mut mask = 0u8;
295                let mut pattern = 0u8;
296                for (i, ch) in pattern_str.iter().enumerate() {
297                    match pattern_str[i] {
298                        b'0' => {
299                            mask |= 1 << i;
300                        }
301                        b'1' => {
302                            pattern |= 1 << i;
303                            mask |= 1 << i;
304                        }
305                        b'x' | b'X' => (),
306                        _ => bail!("Unexpected character in await pattern: `{}`", ch),
307                    }
308                }
309                ensure!(mask != 0, "Pattern consisting of only x'es not allowed");
310                result.push(BitbangEntry::Await { mask, pattern });
311            }
312            [Token::Numeric(num), Token::Alphabetic(time_unit), rest @ ..] => {
313                result.push(BitbangEntry::Delay(parse_delay(num, time_unit, clock)?));
314                tokens = rest;
315            }
316            [Token::Numeric(bits), rest @ ..] => {
317                let samples = bits.len() / num_pins;
318                let (left_wr, right_wr) = slice_wr.split_at_mut(samples);
319                let (left_rd, right_rd) = slice_rd.split_at_mut(samples);
320
321                for (sample_no, item) in left_wr.iter_mut().enumerate() {
322                    for pin_no in 0..num_pins {
323                        match bits.as_bytes()[sample_no * num_pins + pin_no] {
324                            b'0' => (),
325                            b'1' => *item |= 1 << pin_no,
326                            _ => bail!("Non-binary digit encountered in '{}'", bits),
327                        }
328                    }
329                }
330
331                result.push(BitbangEntry::Both(left_wr, left_rd));
332                slice_wr = right_wr;
333                slice_rd = right_rd;
334                tokens = rest;
335            }
336            [Token::Quoted(_), rest @ ..] => {
337                tokens = rest;
338            }
339            [] => break,
340            _ => bail!("Parse error"),
341        }
342    }
343
344    Ok((result.into(), token_map))
345}
346
347/// This function parses the main string argument to `opentitantool gpio dac-bang`, producing a
348/// list of `DacBangEntry` corresponding to the parsed instructions.  The slices in the entries
349/// will refer to the "accumulator vector" provided by the caller, which this function will clear
350/// out and resize according to need.
351pub fn parse_dac_sequence<'a, 'wr>(
352    input: &'a str,
353    num_pins: usize,
354    clock: Duration,
355    accumulator: &'wr mut Vec<f32>,
356) -> Result<Box<[DacBangEntry<'wr>]>> {
357    ensure!(
358        num_pins > 0,
359        "Must specify at least one analog pin for dac-banging"
360    );
361    let all_tokens = get_all_tokens(input)?;
362
363    // First pass, check how many data bytes are needed.
364    let mut needed_entries = 0usize;
365    let mut run_start = 0usize;
366    let mut run_lengths: Vec<usize> = Vec::new();
367    let mut tokens: &[Token] = &all_tokens;
368    loop {
369        match tokens {
370            [Token::Numeric(_), Token::Alphabetic(_), rest @ ..]
371            | [
372                Token::Linear,
373                Token::LParen,
374                Token::Numeric(_),
375                Token::Alphabetic(_),
376                Token::RParen,
377                rest @ ..,
378            ] => {
379                if needed_entries > run_start {
380                    let run_length = needed_entries - run_start;
381                    ensure!(
382                        run_length.is_multiple_of(num_pins),
383                        "Unexpected number of samples {}, should be multiple of the number of pins {}",
384                        run_length,
385                        num_pins,
386                    );
387                    run_lengths.push(run_length);
388                }
389                run_start = needed_entries;
390                tokens = rest;
391            }
392            [Token::Numeric(_), rest @ ..] => {
393                needed_entries += 1;
394                tokens = rest;
395            }
396            [] => break,
397            _ => bail!("Parse error"),
398        }
399    }
400    if needed_entries > run_start {
401        let run_length = needed_entries - run_start;
402        ensure!(
403            run_length.is_multiple_of(num_pins),
404            "Unexpected number of samples {}, should be multiple of the number of pins {}",
405            run_length,
406            num_pins,
407        );
408        run_lengths.push(run_length);
409    }
410    accumulator.clear();
411    accumulator.resize(needed_entries, 0.0);
412    let mut slice_wr: &'wr mut [f32] = accumulator;
413    let mut run_lengths: &[usize] = &run_lengths;
414
415    // Second pass, create `DacBangEntry` instances referring to data in the accumulator.
416    let mut result = Vec::new();
417    let mut tokens: &[Token] = &all_tokens;
418    loop {
419        match tokens {
420            [Token::Numeric(num), Token::Alphabetic(time_unit), rest @ ..] => {
421                result.push(DacBangEntry::Delay(parse_delay(num, time_unit, clock)?));
422                tokens = rest;
423            }
424            [
425                Token::Linear,
426                Token::LParen,
427                Token::Numeric(num),
428                Token::Alphabetic(time_unit),
429                Token::RParen,
430                rest @ ..,
431            ] => {
432                result.push(DacBangEntry::Linear(parse_delay(num, time_unit, clock)?));
433                tokens = rest;
434            }
435            [Token::Numeric(_), ..] => {
436                let samples = run_lengths[0];
437                let (left_wr, right_wr) = slice_wr.split_at_mut(samples);
438
439                for i in 0..samples {
440                    match tokens[i] {
441                        Token::Numeric(voltage) => {
442                            left_wr[i] = voltage.parse::<f32>()?;
443                        }
444                        _ => bail!("Parse error"),
445                    }
446                }
447
448                result.push(DacBangEntry::Write(left_wr));
449                slice_wr = right_wr;
450                tokens = &tokens[samples..];
451                run_lengths = &run_lengths[1..];
452            }
453            [] => break,
454            _ => bail!("Parse error"),
455        }
456    }
457
458    Ok(result.into())
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    #[test]
466    fn test_get_all_tokens() {
467        assert_eq!(
468            // Simple list of tokens.
469            get_all_tokens("01 14ms 11").unwrap(),
470            [
471                Token::Numeric("01"),
472                Token::Numeric("14"),
473                Token::Alphabetic("ms"),
474                Token::Numeric("11"),
475            ],
476        );
477        assert_eq!(
478            // Same list with funny whitespace.
479            get_all_tokens("\t01\r14\nms\x0B11\x0C").unwrap(),
480            [
481                Token::Numeric("01"),
482                Token::Numeric("14"),
483                Token::Alphabetic("ms"),
484                Token::Numeric("11"),
485            ],
486        );
487        assert_eq!(
488            // Very long numerical token (used for subsequent binary values for pins.
489            get_all_tokens("   0101010101010101010101010101010101010101010111   ").unwrap(),
490            [Token::Numeric(
491                "0101010101010101010101010101010101010101010111"
492            ),],
493        );
494        assert_eq!(
495            // Use of quoted named instants, at which input pins should be sampled.
496            get_all_tokens("'s6' 17ms10's7'").unwrap(),
497            [
498                Token::Quoted("'s6'"),
499                Token::Numeric("17"),
500                Token::Alphabetic("ms"),
501                Token::Numeric("10"),
502                Token::Quoted("'s7'"),
503            ],
504        );
505        assert_eq!(
506            // Combined example, using "await(...)".
507            get_all_tokens("01 25ms await(x1) 100us 11").unwrap(),
508            [
509                Token::Numeric("01"),
510                Token::Numeric("25"),
511                Token::Alphabetic("ms"),
512                Token::Await,
513                Token::LParen,
514                Token::Alphabetic("x"),
515                Token::Numeric("1"),
516                Token::RParen,
517                Token::Numeric("100"),
518                Token::Alphabetic("us"),
519                Token::Numeric("11"),
520            ],
521        );
522        assert_eq!(
523            // Analog voltage output example, using "linear(...)" for interpolation.
524            get_all_tokens("1.0 linear(1ms) 0.0").unwrap(),
525            [
526                Token::Numeric("1.0"),
527                Token::Linear,
528                Token::LParen,
529                Token::Numeric("1"),
530                Token::Alphabetic("ms"),
531                Token::RParen,
532                Token::Numeric("0.0"),
533            ],
534        );
535    }
536}