pio_parser/
lib.rs

1// PIO instr grouping is 3/5/3/5
2#![allow(clippy::manual_range_contains)]
3#![allow(clippy::unusual_byte_groupings)]
4#![allow(clippy::upper_case_acronyms)]
5
6use pio_core::{
7    InSource, Instruction, InstructionOperands, IrqIndexMode, JmpCondition, MovDestination,
8    MovOperation, MovRxIndex, MovSource, OutDestination, ProgramWithDefines, SetDestination,
9    WaitSource,
10};
11
12use std::collections::HashMap;
13
14mod parser {
15    #![allow(clippy::all)]
16    #![allow(unused)]
17    include!(concat!(env!("OUT_DIR"), "/pio.rs"));
18}
19
20#[derive(Debug)]
21pub(crate) enum Value<'input> {
22    I32(i32),
23    Symbol(&'input str),
24    Add(Box<Value<'input>>, Box<Value<'input>>),
25    Sub(Box<Value<'input>>, Box<Value<'input>>),
26    Mul(Box<Value<'input>>, Box<Value<'input>>),
27    Div(Box<Value<'input>>, Box<Value<'input>>),
28    Neg(Box<Value<'input>>),
29    Rev(Box<Value<'input>>),
30}
31
32impl Value<'_> {
33    fn reify(&self, state: &ProgramState) -> i32 {
34        match self {
35            Value::I32(v) => *v,
36            Value::Symbol(s) => state.resolve(s),
37            Value::Add(a, b) => a.reify(state) + b.reify(state),
38            Value::Sub(a, b) => a.reify(state) - b.reify(state),
39            Value::Mul(a, b) => a.reify(state) * b.reify(state),
40            Value::Div(a, b) => a.reify(state) / b.reify(state),
41            Value::Neg(a) => -a.reify(state),
42            Value::Rev(a) => a.reify(state).reverse_bits(),
43        }
44    }
45}
46
47#[derive(Debug)]
48pub(crate) enum Line<'input> {
49    Directive(ParsedDirective<'input>),
50    Instruction(ParsedInstruction<'input>),
51    Label { public: bool, name: &'input str },
52}
53
54#[derive(Debug)]
55pub(crate) enum ParsedDirective<'input> {
56    Define {
57        public: bool,
58        name: &'input str,
59        value: Value<'input>,
60    },
61    Origin(Value<'input>),
62    SideSet {
63        value: Value<'input>,
64        opt: bool,
65        pindirs: bool,
66    },
67    WrapTarget,
68    Wrap,
69    #[allow(unused)]
70    LangOpt(&'input str),
71}
72
73#[derive(Debug)]
74pub(crate) struct ParsedInstruction<'input> {
75    operands: ParsedOperands<'input>,
76    side_set: Option<Value<'input>>,
77    delay: Value<'input>,
78}
79
80impl ParsedInstruction<'_> {
81    fn reify(&self, state: &ProgramState) -> Instruction {
82        Instruction {
83            operands: self.operands.reify(state),
84            side_set: self.side_set.as_ref().map(|s| s.reify(state) as u8),
85            delay: self.delay.reify(state) as u8,
86        }
87    }
88}
89
90#[derive(Clone, Copy, Debug)]
91pub(crate) enum ParsedMovDestination {
92    PINS,
93    X,
94    Y,
95    PINDIRS,
96    EXEC,
97    PC,
98    ISR,
99    OSR,
100    RXFIFOY,
101    RXFIFO0,
102    RXFIFO1,
103    RXFIFO2,
104    RXFIFO3,
105}
106
107#[derive(Debug)]
108enum MovDestInternal {
109    Mov(MovDestination),
110    Fifo(MovRxIndex),
111}
112
113impl From<ParsedMovDestination> for MovDestInternal {
114    fn from(value: ParsedMovDestination) -> Self {
115        match value {
116            ParsedMovDestination::PINS => MovDestInternal::Mov(MovDestination::PINS),
117            ParsedMovDestination::X => MovDestInternal::Mov(MovDestination::X),
118            ParsedMovDestination::Y => MovDestInternal::Mov(MovDestination::Y),
119            ParsedMovDestination::PINDIRS => MovDestInternal::Mov(MovDestination::PINDIRS),
120            ParsedMovDestination::EXEC => MovDestInternal::Mov(MovDestination::EXEC),
121            ParsedMovDestination::PC => MovDestInternal::Mov(MovDestination::PC),
122            ParsedMovDestination::ISR => MovDestInternal::Mov(MovDestination::ISR),
123            ParsedMovDestination::OSR => MovDestInternal::Mov(MovDestination::OSR),
124            ParsedMovDestination::RXFIFOY => MovDestInternal::Fifo(MovRxIndex::RXFIFOY),
125            ParsedMovDestination::RXFIFO0 => MovDestInternal::Fifo(MovRxIndex::RXFIFO0),
126            ParsedMovDestination::RXFIFO1 => MovDestInternal::Fifo(MovRxIndex::RXFIFO1),
127            ParsedMovDestination::RXFIFO2 => MovDestInternal::Fifo(MovRxIndex::RXFIFO2),
128            ParsedMovDestination::RXFIFO3 => MovDestInternal::Fifo(MovRxIndex::RXFIFO3),
129        }
130    }
131}
132
133#[derive(Clone, Copy, Debug)]
134pub(crate) enum ParsedMovSource {
135    PINS,
136    X,
137    Y,
138    NULL,
139    STATUS,
140    ISR,
141    OSR,
142    RXFIFOY,
143    RXFIFO0,
144    RXFIFO1,
145    RXFIFO2,
146    RXFIFO3,
147}
148
149#[derive(Debug)]
150enum MovSrcInternal {
151    Mov(MovSource),
152    Fifo(MovRxIndex),
153}
154
155impl From<ParsedMovSource> for MovSrcInternal {
156    fn from(value: ParsedMovSource) -> Self {
157        match value {
158            ParsedMovSource::PINS => MovSrcInternal::Mov(MovSource::PINS),
159            ParsedMovSource::X => MovSrcInternal::Mov(MovSource::X),
160            ParsedMovSource::Y => MovSrcInternal::Mov(MovSource::Y),
161            ParsedMovSource::NULL => MovSrcInternal::Mov(MovSource::NULL),
162            ParsedMovSource::STATUS => MovSrcInternal::Mov(MovSource::STATUS),
163            ParsedMovSource::ISR => MovSrcInternal::Mov(MovSource::ISR),
164            ParsedMovSource::OSR => MovSrcInternal::Mov(MovSource::OSR),
165            ParsedMovSource::RXFIFOY => MovSrcInternal::Fifo(MovRxIndex::RXFIFOY),
166            ParsedMovSource::RXFIFO0 => MovSrcInternal::Fifo(MovRxIndex::RXFIFO0),
167            ParsedMovSource::RXFIFO1 => MovSrcInternal::Fifo(MovRxIndex::RXFIFO1),
168            ParsedMovSource::RXFIFO2 => MovSrcInternal::Fifo(MovRxIndex::RXFIFO2),
169            ParsedMovSource::RXFIFO3 => MovSrcInternal::Fifo(MovRxIndex::RXFIFO3),
170        }
171    }
172}
173
174#[derive(Debug)]
175pub(crate) enum ParsedOperands<'input> {
176    JMP {
177        condition: JmpCondition,
178        address: Value<'input>,
179    },
180    WAIT {
181        polarity: Value<'input>,
182        source: WaitSource,
183        index: Value<'input>,
184        relative: bool,
185    },
186    IN {
187        source: InSource,
188        bit_count: Value<'input>,
189    },
190    OUT {
191        destination: OutDestination,
192        bit_count: Value<'input>,
193    },
194    PUSH {
195        if_full: bool,
196        block: bool,
197    },
198    PULL {
199        if_empty: bool,
200        block: bool,
201    },
202    MOV {
203        destination: ParsedMovDestination,
204        op: MovOperation,
205        source: ParsedMovSource,
206    },
207    IRQ {
208        clear: bool,
209        wait: bool,
210        index: Value<'input>,
211        index_mode: IrqIndexMode,
212    },
213    SET {
214        destination: SetDestination,
215        data: Value<'input>,
216    },
217}
218
219impl ParsedOperands<'_> {
220    fn reify(&self, state: &ProgramState) -> InstructionOperands {
221        match self {
222            ParsedOperands::JMP { condition, address } => InstructionOperands::JMP {
223                condition: *condition,
224                address: address.reify(state) as u8,
225            },
226            ParsedOperands::WAIT {
227                polarity,
228                source,
229                index,
230                relative,
231            } => InstructionOperands::WAIT {
232                polarity: polarity.reify(state) as u8,
233                source: *source,
234                index: index.reify(state) as u8,
235                relative: *relative,
236            },
237            ParsedOperands::IN { source, bit_count } => InstructionOperands::IN {
238                source: *source,
239                bit_count: bit_count.reify(state) as u8,
240            },
241            ParsedOperands::OUT {
242                destination,
243                bit_count,
244            } => InstructionOperands::OUT {
245                destination: *destination,
246                bit_count: bit_count.reify(state) as u8,
247            },
248            ParsedOperands::PUSH { if_full, block } => InstructionOperands::PUSH {
249                if_full: *if_full,
250                block: *block,
251            },
252            ParsedOperands::PULL { if_empty, block } => InstructionOperands::PULL {
253                if_empty: *if_empty,
254                block: *block,
255            },
256            ParsedOperands::MOV {
257                destination,
258                op,
259                source,
260            } => {
261                let source_internal = (*source).into();
262                let dest_internal = (*destination).into();
263                match (source_internal, dest_internal) {
264                    (MovSrcInternal::Mov(MovSource::ISR), MovDestInternal::Fifo(fifo_index)) => {
265                        InstructionOperands::MOVTORX { fifo_index }
266                    }
267                    (
268                        MovSrcInternal::Fifo(fifo_index),
269                        MovDestInternal::Mov(MovDestination::OSR),
270                    ) => InstructionOperands::MOVFROMRX { fifo_index },
271                    (MovSrcInternal::Mov(s), MovDestInternal::Mov(d)) => InstructionOperands::MOV {
272                        destination: d,
273                        op: *op,
274                        source: s,
275                    },
276                    (d, s) => panic!("Illegal Mov src/dest combination: {:?} {:?}", d, s),
277                }
278            }
279            ParsedOperands::IRQ {
280                clear,
281                wait,
282                index,
283                index_mode,
284            } => InstructionOperands::IRQ {
285                clear: *clear,
286                wait: *wait,
287                index: index.reify(state) as u8,
288                index_mode: *index_mode,
289            },
290            ParsedOperands::SET { destination, data } => InstructionOperands::SET {
291                destination: *destination,
292                data: {
293                    let arg = data.reify(state);
294                    if arg < 0 || arg > 0x1f {
295                        panic!("SET argument out of range: {}", arg);
296                    }
297                    arg as u8
298                },
299            },
300        }
301    }
302}
303
304#[derive(Debug, Default)]
305struct FileState {
306    defines: HashMap<String, (bool, i32)>,
307}
308
309#[derive(Debug)]
310struct ProgramState<'a> {
311    file_state: &'a mut FileState,
312    defines: HashMap<String, (bool, i32)>,
313}
314
315impl<'a> ProgramState<'a> {
316    fn new(file_state: &'a mut FileState) -> Self {
317        ProgramState {
318            file_state,
319            defines: HashMap::new(),
320        }
321    }
322
323    fn resolve(&self, name: &str) -> i32 {
324        self.defines
325            .get(name)
326            .or_else(|| self.file_state.defines.get(name))
327            .unwrap_or_else(|| panic!("Unknown label {}", name))
328            .1
329    }
330
331    fn public_defines(&self) -> HashMap<String, i32> {
332        let mut p = HashMap::new();
333        for (name, (public, value)) in &self.file_state.defines {
334            if *public {
335                p.insert(name.to_string(), *value);
336            }
337        }
338        for (name, (public, value)) in &self.defines {
339            if *public {
340                p.insert(name.to_string(), *value);
341            }
342        }
343        p
344    }
345}
346
347pub type ParseError<'input> = lalrpop_util::ParseError<usize, parser::Token<'input>, &'static str>;
348
349pub struct Parser<const PROGRAM_SIZE: usize>;
350
351impl<const PROGRAM_SIZE: usize> Parser<PROGRAM_SIZE> {
352    /// Parse a PIO "file", which contains some number of PIO programs
353    /// separated by `.program` directives.
354    pub fn parse_file(
355        source: &str,
356    ) -> Result<HashMap<String, ProgramWithDefines<HashMap<String, i32>, PROGRAM_SIZE>>, ParseError>
357    {
358        match parser::FileParser::new().parse(source) {
359            Ok(f) => {
360                let mut state = FileState::default();
361
362                // set up global defines
363                let fake_prog_state = ProgramState::new(&mut state);
364                for d in f.0 {
365                    if let ParsedDirective::Define {
366                        public,
367                        name,
368                        value,
369                    } = d.0
370                    {
371                        fake_prog_state
372                            .file_state
373                            .defines
374                            .insert(name.to_string(), (public, value.reify(&fake_prog_state)));
375                    }
376                }
377
378                Ok(f.1
379                    .iter()
380                    .map(|p| {
381                        let program_name = p.0.to_string();
382                        (program_name, Parser::process(&p.1, &mut state))
383                    })
384                    .collect())
385            }
386            Err(e) => Err(e),
387        }
388    }
389
390    /// Parse a single PIO program, without the `.program` directive.
391    pub fn parse_program(
392        source: &str,
393    ) -> Result<ProgramWithDefines<HashMap<String, i32>, PROGRAM_SIZE>, ParseError> {
394        match parser::ProgramParser::new().parse(source) {
395            Ok(p) => Ok(Parser::process(&p, &mut FileState::default())),
396            Err(e) => Err(e),
397        }
398    }
399
400    fn process(
401        p: &[Line],
402        file_state: &mut FileState,
403    ) -> ProgramWithDefines<HashMap<String, i32>, PROGRAM_SIZE> {
404        let mut state = ProgramState::new(file_state);
405
406        // first pass
407        //   - resolve labels
408        //   - resolve defines
409        //   - read side set settings
410        let mut side_set_size = 0;
411        let mut side_set_opt = false;
412        let mut side_set_pindirs = false;
413        let mut origin = None;
414        let mut wrap_target = None;
415        let mut wrap = None;
416        let mut instr_index = 0;
417        for line in p {
418            match line {
419                Line::Instruction(..) => {
420                    instr_index += 1;
421                }
422                Line::Label { public, name } => {
423                    state
424                        .defines
425                        .insert(name.to_string(), (*public, instr_index as i32));
426                }
427                Line::Directive(d) => match d {
428                    ParsedDirective::Define {
429                        public,
430                        name,
431                        value,
432                    } => {
433                        state
434                            .defines
435                            .insert(name.to_string(), (*public, value.reify(&state)));
436                    }
437                    ParsedDirective::Origin(value) => {
438                        origin = Some(value.reify(&state) as u8);
439                    }
440                    ParsedDirective::SideSet {
441                        value,
442                        opt,
443                        pindirs,
444                    } => {
445                        assert!(instr_index == 0);
446                        side_set_size = value.reify(&state) as u8;
447                        side_set_opt = *opt;
448                        side_set_pindirs = *pindirs;
449                    }
450                    ParsedDirective::WrapTarget => {
451                        assert!(wrap_target.is_none());
452                        wrap_target = Some(instr_index);
453                    }
454                    ParsedDirective::Wrap => {
455                        assert!(wrap.is_none());
456                        wrap = Some(instr_index - 1);
457                    }
458                    _ => {}
459                },
460            }
461        }
462
463        let mut a = pio_core::Assembler::new_with_side_set(pio_core::SideSet::new(
464            side_set_opt,
465            side_set_size,
466            side_set_pindirs,
467        ));
468
469        // second pass
470        //   - emit instructions
471        for line in p {
472            if let Line::Instruction(i) = line {
473                a.instructions.push(i.reify(&state));
474            }
475        }
476
477        let program = a.assemble_program().set_origin(origin);
478
479        let program = match (wrap, wrap_target) {
480            (Some(wrap_source), Some(wrap_target)) => program.set_wrap(pio_core::Wrap {
481                source: wrap_source,
482                target: wrap_target,
483            }),
484            (None, None) => program,
485            _ => panic!(
486                "must define either both or neither of wrap and wrap_target, but not only one of them"
487            ),
488        };
489
490        ProgramWithDefines {
491            program,
492            public_defines: state.public_defines(),
493        }
494    }
495}
496
497#[test]
498fn test() {
499    let p = Parser::<32>::parse_program(
500        "
501    label:
502      pull
503      out pins, 1
504      jmp label
505    ",
506    )
507    .unwrap();
508
509    assert_eq!(
510        &p.program.code[..],
511        &[
512            // LABEL:
513            0b100_00000_101_00000, // PULL
514            0b011_00000_000_00001, // OUT PINS, 1
515            0b000_00000_000_00000, // JMP LABEL
516        ]
517    );
518    assert_eq!(p.program.origin, None);
519    assert_eq!(
520        p.program.wrap,
521        pio_core::Wrap {
522            source: 2,
523            target: 0,
524        }
525    );
526}
527
528#[test]
529fn test_rp2350() {
530    let p = Parser::<32>::parse_program(
531        "
532    label:
533      mov osr, rxfifo[0]
534      mov rxfifo[1], isr
535      mov pins, isr
536      mov osr, x
537      jmp label
538    ",
539    )
540    .unwrap();
541
542    assert_eq!(
543        &p.program.code[..],
544        &[
545            // LABEL:
546            0b100_00000_1001_1_000, // MOV OSR, RXFIFO0
547            0b100_00000_0001_1_001, // MOV RXFIFO1, ISR
548            0b101_00000_000_00_110, // MOV PINS, ISR
549            0b101_00000_111_00_001, // MOV OSR, X
550            0b000_00000_000_00000,  // JMP LABEL
551        ]
552    );
553    assert_eq!(p.program.origin, None);
554    assert_eq!(
555        p.program.wrap,
556        pio_core::Wrap {
557            source: 4,
558            target: 0,
559        }
560    );
561}
562
563#[test]
564fn test_side_set() {
565    let p = Parser::<32>::parse_program(
566        "
567    .side_set 1 opt
568    .origin 5
569
570    label:
571      pull
572      .wrap_target
573      out pins, 1
574      .wrap
575      jmp label side 1
576    ",
577    )
578    .unwrap();
579
580    assert_eq!(
581        &p.program.code[..],
582        &[
583            // LABEL:
584            0b100_00000_101_00000, // PULL
585            0b011_00000_000_00001, // OUT PINS, 1
586            0b000_11000_000_00000, // JMP LABEL, SIDE 1
587        ]
588    );
589    assert_eq!(p.program.origin, Some(5));
590    assert_eq!(
591        p.program.wrap,
592        pio_core::Wrap {
593            source: 1,
594            target: 1,
595        }
596    );
597}
598
599#[test]
600#[should_panic(expected = "Unknown label some_unknown_label")]
601fn test_unknown_label() {
602    let _ = Parser::<32>::parse_program(
603        "
604    jmp some_unknown_label
605    ",
606    )
607    .unwrap();
608}