Skip to main content

pio_core/
lib.rs

1//! This crate is an implementation detail, you must not use it directly.
2//! Use the [`pio`](https://crates.io/crates/pio) crate instead.
3
4#![no_std]
5// PIO instr grouping is 3/5/3/5
6#![allow(clippy::unusual_byte_groupings)]
7#![allow(clippy::upper_case_acronyms)]
8
9pub use arrayvec::ArrayVec;
10use core::convert::TryFrom;
11use num_enum::TryFromPrimitive;
12
13/// Maximum program size of RP2040 and RP235x chips, in bytes.
14///
15/// See Chapter 3, Figure 38 for reference of the value.
16pub const RP2040_MAX_PROGRAM_SIZE: usize = 32;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20/// PIO version
21pub enum PioVersion {
22    /// Pio programs compatible with both the RP2040 and RP235x
23    V0,
24    /// Pio programs compatible with the RP235x
25    V1,
26}
27
28#[repr(u8)]
29#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
30pub enum JmpCondition {
31    /// Always
32    Always = 0b000,
33    /// `!X`: scratch X zero
34    XIsZero = 0b001,
35    /// `X--`: scratch X non-zero, post decrement
36    XDecNonZero = 0b010,
37    /// `!Y`: scratch Y zero
38    YIsZero = 0b011,
39    /// `Y--`: scratch Y non-zero, post decrement
40    YDecNonZero = 0b100,
41    /// `X!=Y`: scratch X not equal to scratch Y
42    XNotEqualY = 0b101,
43    /// `PIN`: branch on input pin
44    PinHigh = 0b110,
45    /// `!OSRE`: output shift register not empty
46    OutputShiftRegisterNotEmpty = 0b111,
47}
48
49#[repr(u8)]
50#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
51pub enum InSource {
52    PINS = 0b000,
53    X = 0b001,
54    Y = 0b010,
55    NULL = 0b011,
56    // RESERVED = 0b100,
57    // RESERVED = 0b101,
58    ISR = 0b110,
59    OSR = 0b111,
60}
61
62#[repr(u8)]
63#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
64pub enum OutDestination {
65    PINS = 0b000,
66    X = 0b001,
67    Y = 0b010,
68    NULL = 0b011,
69    PINDIRS = 0b100,
70    PC = 0b101,
71    ISR = 0b110,
72    EXEC = 0b111,
73}
74
75#[repr(u8)]
76#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
77pub enum MovDestination {
78    PINS = 0b000,
79    X = 0b001,
80    Y = 0b010,
81    PINDIRS = 0b011,
82    EXEC = 0b100,
83    PC = 0b101,
84    ISR = 0b110,
85    OSR = 0b111,
86}
87
88#[repr(u8)]
89#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
90pub enum MovOperation {
91    None = 0b00,
92    Invert = 0b01,
93    BitReverse = 0b10,
94    // RESERVED = 0b11,
95}
96
97#[repr(u8)]
98#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
99pub enum MovSource {
100    PINS = 0b000,
101    X = 0b001,
102    Y = 0b010,
103    NULL = 0b011,
104    // RESERVED = 0b100,
105    STATUS = 0b101,
106    ISR = 0b110,
107    OSR = 0b111,
108}
109
110#[repr(u8)]
111#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
112pub enum MovRxIndex {
113    RXFIFOY = 0b0000,
114    RXFIFO0 = 0b1000,
115    RXFIFO1 = 0b1001,
116    RXFIFO2 = 0b1010,
117    RXFIFO3 = 0b1011,
118}
119
120#[repr(u8)]
121#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
122pub enum SetDestination {
123    PINS = 0b000,
124    X = 0b001,
125    Y = 0b010,
126    // RESERVED = 0b011,
127    PINDIRS = 0b100,
128    // RESERVED = 0b101,
129    // RESERVED = 0b110,
130    // RESERVED = 0b111,
131}
132
133#[repr(u8)]
134#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
135pub enum IrqIndexMode {
136    DIRECT = 0b00,
137    PREV = 0b01,
138    REL = 0b10,
139    NEXT = 0b11,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum WaitSource {
144    Gpio(u8),
145    Pin(u8),
146    Irq { index_mode: IrqIndexMode, irq: u8 },
147    JmpPin { offset: Option<u8> },
148}
149impl WaitSource {
150    pub const fn opcode(&self) -> u8 {
151        match self {
152            WaitSource::Gpio(_) => 0b00,
153            WaitSource::Pin(_) => 0b01,
154            WaitSource::Irq { .. } => 0b10,
155            WaitSource::JmpPin { .. } => 0b11,
156        }
157    }
158}
159impl TryFrom<(u8, u8)> for WaitSource {
160    type Error = ();
161
162    fn try_from((o0, o1): (u8, u8)) -> Result<Self, Self::Error> {
163        match o0 & 0b11 {
164            0b00 => Ok(WaitSource::Gpio(o1 & 0b11111)),
165            0b01 => Ok(WaitSource::Pin(o1 & 0b11111)),
166            0b10 => Ok(WaitSource::Irq {
167                index_mode: IrqIndexMode::try_from_primitive((o1 >> 3) & 0b11).unwrap(),
168                irq: o1 & 0b111,
169            }),
170            0b11 => Ok(WaitSource::JmpPin {
171                offset: Some(o1 & 0b11111),
172            }),
173            _ => Err(()),
174        }
175    }
176}
177
178#[derive(Debug, Clone, Copy)]
179pub enum InstructionOperands {
180    JMP {
181        condition: JmpCondition,
182        address: u8,
183    },
184    WAIT {
185        /// 1 -> wait for 1
186        /// 0 -> wait for 0
187        polarity: u8,
188        source: WaitSource,
189    },
190    IN {
191        source: InSource,
192        bit_count: u8,
193    },
194    OUT {
195        destination: OutDestination,
196        bit_count: u8,
197    },
198    PUSH {
199        if_full: bool,
200        block: bool,
201    },
202    PULL {
203        if_empty: bool,
204        block: bool,
205    },
206    MOV {
207        destination: MovDestination,
208        op: MovOperation,
209        source: MovSource,
210    },
211    MOVTORX {
212        fifo_index: MovRxIndex,
213    },
214    MOVFROMRX {
215        fifo_index: MovRxIndex,
216    },
217    IRQ {
218        clear: bool,
219        wait: bool,
220        index: u8,
221        index_mode: IrqIndexMode,
222    },
223    SET {
224        destination: SetDestination,
225        data: u8,
226    },
227}
228
229impl InstructionOperands {
230    const fn discrim(&self) -> u16 {
231        match self {
232            InstructionOperands::JMP { .. } => 0b000,
233            InstructionOperands::WAIT { .. } => 0b001,
234            InstructionOperands::IN { .. } => 0b010,
235            InstructionOperands::OUT { .. } => 0b011,
236            InstructionOperands::PUSH { .. } => 0b100,
237            InstructionOperands::PULL { .. } => 0b100,
238            InstructionOperands::MOV { .. } => 0b101,
239            InstructionOperands::MOVTORX { .. } => 0b100,
240            InstructionOperands::MOVFROMRX { .. } => 0b100,
241            InstructionOperands::IRQ { .. } => 0b110,
242            InstructionOperands::SET { .. } => 0b111,
243        }
244    }
245
246    const fn operands(&self) -> (u8, u8) {
247        match self {
248            InstructionOperands::JMP { condition, address } => (*condition as u8, *address),
249            InstructionOperands::WAIT { polarity, source } => {
250                let o1 = match source {
251                    WaitSource::Gpio(gpio) => *gpio & 0b11111,
252                    WaitSource::Pin(pin) => *pin & 0b11111,
253                    WaitSource::Irq { index_mode, irq } => {
254                        if *irq > 7 {
255                            panic!("Index for WaitSource::IRQ should be in range 0..=7");
256                        }
257                        (*index_mode as u8) << 3 | *irq & 0b111
258                    }
259                    WaitSource::JmpPin { offset } => match offset {
260                        Some(offset) => *offset,
261                        None => 0,
262                    },
263                };
264                (((*polarity) << 2) | (source.opcode()), o1)
265            }
266            InstructionOperands::IN { source, bit_count } => {
267                if *bit_count == 0 || *bit_count > 32 {
268                    panic!("bit_count must be from 1 to 32");
269                }
270                (*source as u8, *bit_count & 0b11111)
271            }
272            InstructionOperands::OUT {
273                destination,
274                bit_count,
275            } => {
276                if *bit_count == 0 || *bit_count > 32 {
277                    panic!("bit_count must be from 1 to 32");
278                }
279                (*destination as u8, *bit_count & 0b11111)
280            }
281            InstructionOperands::PUSH { if_full, block } => {
282                (((*if_full as u8) << 1) | (*block as u8), 0)
283            }
284            InstructionOperands::PULL { if_empty, block } => {
285                ((1 << 2) | ((*if_empty as u8) << 1) | (*block as u8), 0)
286            }
287            InstructionOperands::MOV {
288                destination,
289                op,
290                source,
291            } => (*destination as u8, ((*op as u8) << 3) | (*source as u8)),
292            InstructionOperands::MOVTORX { fifo_index } => (0, (1 << 4) | *fifo_index as u8),
293            InstructionOperands::MOVFROMRX { fifo_index } => (0b100, (1 << 4) | *fifo_index as u8),
294            InstructionOperands::IRQ {
295                clear,
296                wait,
297                index,
298                index_mode,
299            } => {
300                if *index > 7 {
301                    panic!("invalid interrupt flags");
302                }
303                (
304                    ((*clear as u8) << 1) | (*wait as u8),
305                    *index | ((*index_mode as u8) << 3),
306                )
307            }
308            InstructionOperands::SET { destination, data } => {
309                if *data > 0x1f {
310                    panic!("SET argument out of range");
311                }
312                (*destination as u8, *data)
313            }
314        }
315    }
316
317    /// Encode these operands into binary representation.
318    /// Note that this output does not take side set and delay into account.
319    pub const fn encode(&self) -> u16 {
320        let mut data: u16 = 0;
321        data |= self.discrim() << 13;
322        let (o0, o1) = self.operands();
323        data |= (o0 as u16) << 5;
324        data |= o1 as u16;
325        data
326    }
327
328    /// Decode operands from binary representation.
329    /// Note that this output does not take side set and delay into account.
330    pub fn decode(instruction: u16) -> Option<Self> {
331        let discrim = instruction >> 13;
332        let o0 = ((instruction >> 5) & 0b111) as u8;
333        let o1 = (instruction & 0b11111) as u8;
334
335        match discrim {
336            0b000 => JmpCondition::try_from(o0)
337                .ok()
338                .map(|condition| InstructionOperands::JMP {
339                    condition,
340                    address: o1,
341                }),
342            0b001 => WaitSource::try_from((o0, o1))
343                .ok()
344                .map(|source| InstructionOperands::WAIT {
345                    polarity: o0 >> 2,
346                    source,
347                }),
348            0b010 => InSource::try_from(o0)
349                .ok()
350                .map(|source| InstructionOperands::IN {
351                    source,
352                    bit_count: if o1 == 0 { 32 } else { o1 },
353                }),
354            0b011 => {
355                OutDestination::try_from(o0)
356                    .ok()
357                    .map(|destination| InstructionOperands::OUT {
358                        destination,
359                        bit_count: if o1 == 0 { 32 } else { o1 },
360                    })
361            }
362            0b100 => {
363                let p_o0 = ((instruction >> 4) & 0b1111) as u8;
364
365                let if_flag = p_o0 & 0b0100 != 0;
366                let block = p_o0 & 0b0010 != 0;
367
368                let index = MovRxIndex::try_from((instruction & 0b1111) as u8);
369                if p_o0 & 0b1001 == 0b1000 {
370                    Some(InstructionOperands::PULL {
371                        if_empty: if_flag,
372                        block,
373                    })
374                } else if p_o0 & 0b1001 == 0b0000 {
375                    Some(InstructionOperands::PUSH {
376                        if_full: if_flag,
377                        block,
378                    })
379                } else if p_o0 == 0b1001 {
380                    Some(InstructionOperands::MOVFROMRX {
381                        fifo_index: index.ok()?,
382                    })
383                } else if p_o0 == 0b0001 {
384                    Some(InstructionOperands::MOVTORX {
385                        fifo_index: index.ok()?,
386                    })
387                } else {
388                    None
389                }
390            }
391            0b101 => match (
392                MovDestination::try_from(o0).ok(),
393                MovOperation::try_from((o1 >> 3) & 0b11).ok(),
394                MovSource::try_from(o1 & 0b111).ok(),
395            ) {
396                (Some(destination), Some(op), Some(source)) => Some(InstructionOperands::MOV {
397                    destination,
398                    op,
399                    source,
400                }),
401                _ => None,
402            },
403            0b110 => {
404                if o0 & 0b100 == 0 {
405                    let index_mode = IrqIndexMode::try_from((o1 >> 3) & 0b11);
406                    Some(InstructionOperands::IRQ {
407                        clear: o0 & 0b010 != 0,
408                        wait: o0 & 0b001 != 0,
409                        index: o1 & 0b00111,
410                        index_mode: index_mode.ok()?,
411                    })
412                } else {
413                    None
414                }
415            }
416            0b111 => {
417                SetDestination::try_from(o0)
418                    .ok()
419                    .map(|destination| InstructionOperands::SET {
420                        destination,
421                        data: o1,
422                    })
423            }
424            _ => None,
425        }
426    }
427}
428
429/// A PIO instruction.
430#[derive(Debug, Clone, Copy)]
431pub struct Instruction {
432    pub operands: InstructionOperands,
433    pub delay: u8,
434    pub side_set: Option<u8>,
435}
436
437impl Instruction {
438    /// Encode a single instruction.
439    pub fn encode(&self, side_set: SideSet) -> u16 {
440        let delay_max = (1 << (5 - side_set.bits)) - 1;
441        let mut data = self.operands.encode();
442
443        if self.delay > delay_max {
444            panic!(
445                "delay of {} is greater than limit {}",
446                self.delay, delay_max
447            );
448        }
449
450        let side_set = if let Some(s) = self.side_set {
451            if s > side_set.max {
452                panic!("'side' set must be >=0 and <={}", side_set.max);
453            }
454            let s = (s as u16) << (5 - side_set.bits);
455            if side_set.opt {
456                s | 0b10000
457            } else {
458                s
459            }
460        } else if side_set.bits > 0 && !side_set.opt {
461            panic!("instruction requires 'side' set");
462        } else {
463            0
464        };
465
466        data |= ((self.delay as u16) | side_set) << 8;
467
468        data
469    }
470
471    /// Decode a single instruction.
472    pub fn decode(instruction: u16, side_set: SideSet) -> Option<Instruction> {
473        InstructionOperands::decode(instruction).map(|operands| {
474            let data = ((instruction >> 8) & 0b11111) as u8;
475
476            let delay = data & ((1 << (5 - side_set.bits)) - 1);
477
478            let has_side_set = side_set.bits > 0 && (!side_set.opt || data & 0b10000 > 0);
479            let side_set_data =
480                (data & if side_set.opt { 0b01111 } else { 0b11111 }) >> (5 - side_set.bits);
481
482            let side_set = if has_side_set {
483                Some(side_set_data)
484            } else {
485                None
486            };
487
488            Instruction {
489                operands,
490                delay,
491                side_set,
492            }
493        })
494    }
495}
496
497#[derive(Debug)]
498enum LabelState {
499    Unbound(u8),
500    Bound(u8),
501}
502
503/// A label.
504#[derive(Debug)]
505pub struct Label {
506    state: LabelState,
507}
508
509impl Drop for Label {
510    fn drop(&mut self) {
511        if let LabelState::Unbound(_) = self.state {
512            panic!("label was not bound");
513        }
514    }
515}
516
517/// Data for 'side' set instruction parameters.
518#[derive(Debug, Clone, Copy)]
519pub struct SideSet {
520    opt: bool,
521    bits: u8,
522    max: u8,
523    pindirs: bool,
524}
525
526impl SideSet {
527    pub const fn new(opt: bool, bits: u8, pindirs: bool) -> SideSet {
528        SideSet {
529            opt,
530            bits: bits + opt as u8,
531            max: (1 << bits) - 1,
532            pindirs,
533        }
534    }
535
536    #[doc(hidden)]
537    pub fn new_from_proc_macro(opt: bool, bits: u8, pindirs: bool) -> SideSet {
538        SideSet {
539            opt,
540            bits,
541            max: (1 << bits) - 1,
542            pindirs,
543        }
544    }
545
546    pub fn optional(&self) -> bool {
547        self.opt
548    }
549
550    pub fn bits(&self) -> u8 {
551        self.bits
552    }
553
554    pub fn pindirs(&self) -> bool {
555        self.pindirs
556    }
557}
558
559impl Default for SideSet {
560    fn default() -> Self {
561        SideSet::new(false, 0, false)
562    }
563}
564
565/// A PIO Assembler. See chapter three of the [RP2040 Datasheet][].
566///
567/// [RP2040 Datasheet]: https://rptl.io/rp2040-datasheet
568#[derive(Debug)]
569pub struct Assembler<const PROGRAM_SIZE: usize> {
570    #[doc(hidden)]
571    pub instructions: ArrayVec<Instruction, PROGRAM_SIZE>,
572    #[doc(hidden)]
573    pub side_set: SideSet,
574}
575
576impl<const PROGRAM_SIZE: usize> Assembler<PROGRAM_SIZE> {
577    /// Create a new Assembler.
578    #[allow(clippy::new_without_default)]
579    pub fn new() -> Self {
580        Assembler::new_with_side_set(SideSet::default())
581    }
582
583    /// Create a new Assembler with `SideSet` settings.
584    #[allow(clippy::new_without_default)]
585    pub fn new_with_side_set(side_set: SideSet) -> Self {
586        Assembler {
587            instructions: ArrayVec::new(),
588            side_set,
589        }
590    }
591
592    /// Assemble the program into PIO instructions.
593    pub fn assemble(self) -> ArrayVec<u16, PROGRAM_SIZE> {
594        self.instructions
595            .iter()
596            .map(|i| i.encode(self.side_set))
597            .collect()
598    }
599
600    /// Check the program for instructions and operands available only on the RP2350.
601    pub fn version(&self) -> PioVersion {
602        for instr in &self.instructions {
603            let opr = instr.operands;
604            match opr {
605                InstructionOperands::MOVFROMRX { .. } => return PioVersion::V1,
606                InstructionOperands::MOVTORX { .. } => return PioVersion::V1,
607                InstructionOperands::MOV {
608                    destination: MovDestination::PINDIRS,
609                    ..
610                } => {
611                    return PioVersion::V1;
612                }
613                InstructionOperands::WAIT {
614                    source:
615                        WaitSource::JmpPin { .. }
616                        | WaitSource::Irq {
617                            index_mode: IrqIndexMode::PREV | IrqIndexMode::NEXT,
618                            ..
619                        },
620                    ..
621                } => {
622                    return PioVersion::V1;
623                }
624                InstructionOperands::IRQ {
625                    index_mode: IrqIndexMode::PREV | IrqIndexMode::NEXT,
626                    ..
627                } => {
628                    return PioVersion::V1;
629                }
630                _ => (),
631            }
632        }
633
634        PioVersion::V0
635    }
636
637    /// Assemble the program into [`Program`].
638    ///
639    /// The program contains the instructions and side-set info set. You can directly compile into a program with
640    /// correct wrapping with [`Self::assemble_with_wrap`], or you can set the wrapping after the compilation with
641    /// [`Program::set_wrap`].
642    pub fn assemble_program(self) -> Program<PROGRAM_SIZE> {
643        let side_set = self.side_set;
644        let version = self.version();
645        let code = self.assemble();
646        let wrap = Wrap {
647            source: (code.len() - 1) as u8,
648            target: 0,
649        };
650
651        Program {
652            code,
653            origin: None,
654            side_set,
655            wrap,
656            version,
657        }
658    }
659
660    /// Assemble the program into [`Program`] with wrapping.
661    ///
662    /// Takes pair of labels controlling the wrapping. The first label is the source (top) of the wrap while the second
663    /// label is the target (bottom) of the wrap. The source label should be positioned _after_ the instruction from
664    /// which the wrapping happens.
665    pub fn assemble_with_wrap(self, source: Label, target: Label) -> Program<PROGRAM_SIZE> {
666        let source = self.label_offset(&source) - 1;
667        let target = self.label_offset(&target);
668        self.assemble_program().set_wrap(Wrap { source, target })
669    }
670
671    /// Get the offset of a label in the program.
672    pub fn label_offset(&self, label: &Label) -> u8 {
673        match &label.state {
674            LabelState::Bound(offset) => *offset,
675            LabelState::Unbound(_) => panic!("can't get offset for unbound label"),
676        }
677    }
678}
679
680impl<const PROGRAM_SIZE: usize> Assembler<PROGRAM_SIZE> {
681    /// Create a new unbound Label.
682    pub fn label(&mut self) -> Label {
683        Label {
684            state: LabelState::Unbound(u8::MAX),
685        }
686    }
687
688    /// Create a new label bound to given offset.
689    pub fn label_at_offset(&mut self, offset: u8) -> Label {
690        Label {
691            state: LabelState::Bound(offset),
692        }
693    }
694
695    /// Bind `label` to the current instruction position.
696    pub fn bind(&mut self, label: &mut Label) {
697        match label.state {
698            LabelState::Bound(_) => panic!("cannot bind label twice"),
699            LabelState::Unbound(mut patch) => {
700                let resolved_address = self.instructions.len() as u8;
701                while patch != u8::MAX {
702                    // SAFETY: patch points to the next instruction to patch
703                    let instr = unsafe { self.instructions.get_unchecked_mut(patch as usize) };
704                    if let InstructionOperands::JMP { address, .. } = &mut instr.operands {
705                        patch = *address;
706                        *address = resolved_address;
707                    } else {
708                        unreachable!();
709                    }
710                }
711                label.state = LabelState::Bound(resolved_address);
712            }
713        }
714    }
715}
716
717macro_rules! instr_impl {
718    ( $(#[$inner:ident $($args:tt)*])* $name:ident ( $self:ident $(, $( $arg_name:ident : $arg_ty:ty ),*)? ) $body:expr, $delay:expr, $side_set:expr ) => {
719        $(#[$inner $($args)*])*
720        pub fn $name(
721            &mut $self
722            $(, $( $arg_name : $arg_ty , )*)?
723        ) {
724            $self.instructions.push(Instruction {
725                operands: $body,
726                delay: $delay,
727                side_set: $side_set,
728            })
729        }
730    }
731}
732
733macro_rules! instr {
734    ( $(#[$inner:ident $($args:tt)*])* $name:ident ( $self:ident $(, $($arg_name:ident : $arg_ty:ty ),*)? ) $body:expr ) => {
735        instr_impl!($(#[$inner $($args)*])* $name ( $self $(, $( $arg_name: $arg_ty ),*)? ) $body, 0, None );
736        paste::paste! {
737            instr_impl!($(#[$inner $($args)*])* [< $name _with_delay >] ( $self $(, $( $arg_name: $arg_ty ),*)? , delay: u8 ) $body, delay, None );
738            instr_impl!($(#[$inner $($args)*])* [< $name _with_side_set >] ( $self $(, $( $arg_name: $arg_ty ),*)? , side_set: u8 ) $body, 0, Some(side_set) );
739            instr_impl!($(#[$inner $($args)*])* [< $name _with_delay_and_side_set >] ( $self $(, $( $arg_name: $arg_ty ),*)? , delay: u8, side_set: u8 ) $body, delay, Some(side_set) );
740        }
741    }
742}
743
744impl<const PROGRAM_SIZE: usize> Assembler<PROGRAM_SIZE> {
745    instr!(
746        /// Emit a `jmp` instruction to `label` for `condition`.
747        jmp(self, condition: JmpCondition, label: &mut Label) {
748            let address = match label.state {
749                LabelState::Unbound(a) => {
750                    label.state = LabelState::Unbound(self.instructions.len() as u8);
751                    a
752                }
753                LabelState::Bound(a) => a,
754            };
755            InstructionOperands::JMP {
756                condition,
757                address,
758            }
759        }
760    );
761
762    instr!(
763        /// Emit a `wait` instruction with `polarity` from `source` with `index` which may be
764        /// `relative`.
765        wait(self, polarity: u8, source: WaitSource) {
766            InstructionOperands::WAIT {
767                polarity,
768                source,
769            }
770        }
771    );
772
773    instr!(
774        /// Emit an `in` instruction from `source` with `bit_count`.
775        r#in(self, source: InSource, bit_count: u8) {
776            InstructionOperands::IN { source, bit_count }
777        }
778    );
779
780    instr!(
781        /// Emit an `out` instruction to `source` with `bit_count`.
782        out(self, destination: OutDestination, bit_count: u8) {
783            InstructionOperands::OUT {
784                destination,
785                bit_count,
786            }
787        }
788    );
789
790    instr!(
791        /// Emit a `push` instruction with `if_full` and `block`.
792        push(self, if_full: bool, block: bool) {
793            InstructionOperands::PUSH {
794                if_full,
795                block,
796            }
797        }
798    );
799
800    instr!(
801        /// Emit a `pull` instruction with `if_empty` and `block`.
802        pull(self, if_empty: bool, block: bool) {
803            InstructionOperands::PULL {
804                if_empty,
805                block,
806            }
807        }
808    );
809
810    instr!(
811        /// Emit a `mov` instruction to `destination` using `op` from `source`.
812        mov(self, destination: MovDestination, op: MovOperation, source: MovSource) {
813            InstructionOperands::MOV {
814                destination,
815                op,
816                source,
817            }
818        }
819    );
820
821    instr!(
822        /// Emit a `mov to rx` instruction.
823        mov_to_rx(self, fifo_index: MovRxIndex) {
824            InstructionOperands::MOVTORX {
825                fifo_index
826            }
827        }
828    );
829
830    instr!(
831        /// Emit a `mov from rx` instruction.
832        mov_from_rx(self, fifo_index: MovRxIndex) {
833            InstructionOperands::MOVFROMRX {
834                fifo_index
835            }
836        }
837    );
838
839    instr!(
840        /// Emit an `irq` instruction using `clear` and `wait` with `index` which may be `relative`.
841        irq(self, clear: bool, wait: bool, index: u8, index_mode: IrqIndexMode) {
842            InstructionOperands::IRQ {
843                clear,
844                wait,
845                index,
846                index_mode
847            }
848        }
849    );
850
851    instr!(
852        /// Emit a `set` instruction
853        set(self, destination: SetDestination, data: u8) {
854            InstructionOperands::SET {
855                destination,
856                data,
857            }
858        }
859    );
860
861    instr!(
862        /// Emit a `mov` instruction from Y to Y without operation effectively acting as a `nop`
863        /// instruction.
864        nop(self) {
865            InstructionOperands::MOV {
866                destination: MovDestination::Y,
867                op: MovOperation::None,
868                source: MovSource::Y
869            }
870        }
871    );
872}
873
874/// Source and target for automatic program wrapping.
875///
876/// After the instruction at offset pointed by [`source`] has been executed, the program control flow jumps to the
877/// instruction pointed by [`target`]. If the instruction is a jump, and the condition is true, the jump takes priority.
878///
879/// [`source`]: Self::source
880/// [`target`]: Self::target
881#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
882pub struct Wrap {
883    /// Source instruction for wrap.
884    pub source: u8,
885    /// Target instruction for wrap.
886    pub target: u8,
887}
888
889/// Program ready to be executed by PIO hardware.
890#[derive(Debug)]
891pub struct Program<const PROGRAM_SIZE: usize> {
892    /// Assembled program code.
893    pub code: ArrayVec<u16, PROGRAM_SIZE>,
894    /// Offset at which the program must be loaded.
895    ///
896    /// Most often 0 if defined. This might be needed when using data based `JMP`s.
897    ///
898    /// NOTE: Instruction addresses in JMP instructions as well as
899    /// wrap source/target are calculated as if the origin was 0.
900    /// Functions loading the program into PIO instruction memory will
901    /// adjust those addresses accordingly if the program is loaded
902    /// to a non-zero origin address.
903    pub origin: Option<u8>,
904    /// Wrapping behavior for this program.
905    pub wrap: Wrap,
906    /// Side-set info for this program.
907    pub side_set: SideSet,
908    /// Pio Version required for this program.
909    pub version: PioVersion,
910}
911
912impl<const PROGRAM_SIZE: usize> Program<PROGRAM_SIZE> {
913    /// Set the program loading location.
914    ///
915    /// If `None`, the program can be loaded at any location in the instruction memory.
916    pub fn set_origin(self, origin: Option<u8>) -> Self {
917        Self { origin, ..self }
918    }
919
920    /// Set the wrapping of the program.
921    pub fn set_wrap(self, wrap: Wrap) -> Self {
922        assert!((wrap.source as usize) < self.code.len());
923        assert!((wrap.target as usize) < self.code.len());
924        Self { wrap, ..self }
925    }
926}
927
928/// Parsed program with defines.
929pub struct ProgramWithDefines<PublicDefines, const PROGRAM_SIZE: usize> {
930    /// The compiled program.
931    pub program: Program<PROGRAM_SIZE>,
932    /// Public defines.
933    pub public_defines: PublicDefines,
934}
935
936#[test]
937fn test_jump_1() {
938    let mut a = Assembler::<32>::new();
939
940    let mut l = a.label();
941    a.set(SetDestination::X, 0);
942    a.bind(&mut l);
943    a.set(SetDestination::X, 1);
944    a.jmp(JmpCondition::Always, &mut l);
945
946    assert_eq!(
947        a.assemble().as_slice(),
948        &[
949            0b111_00000_001_00000, // SET X 0
950            // L:
951            0b111_00000_001_00001, // SET X 1
952            0b000_00000_000_00001, // JMP L
953        ]
954    );
955}
956
957#[test]
958fn test_jump_2() {
959    let mut a = Assembler::<32>::new();
960
961    let mut top = a.label();
962    let mut bottom = a.label();
963    a.bind(&mut top);
964    a.set(SetDestination::Y, 0);
965    a.jmp(JmpCondition::YIsZero, &mut bottom);
966    a.jmp(JmpCondition::Always, &mut top);
967    a.bind(&mut bottom);
968    a.set(SetDestination::Y, 1);
969
970    assert_eq!(
971        a.assemble().as_slice(),
972        &[
973            // TOP:
974            0b111_00000_010_00000, // SET Y 0
975            0b000_00000_011_00011, // JMP YIsZero BOTTOM
976            0b000_00000_000_00000, // JMP Always TOP
977            // BOTTOM:
978            0b111_00000_010_00001, // SET Y 1
979        ]
980    );
981}
982
983#[test]
984fn test_assemble_with_wrap() {
985    let mut a = Assembler::<32>::new();
986
987    let mut source = a.label();
988    let mut target = a.label();
989
990    a.set(SetDestination::PINDIRS, 0);
991    a.bind(&mut target);
992    a.r#in(InSource::NULL, 1);
993    a.push(false, false);
994    a.bind(&mut source);
995    a.jmp(JmpCondition::Always, &mut target);
996
997    assert_eq!(
998        a.assemble_with_wrap(source, target).wrap,
999        Wrap {
1000            source: 2,
1001            target: 1,
1002        }
1003    );
1004}
1005
1006#[test]
1007fn test_assemble_program_default_wrap() {
1008    let mut a = Assembler::<32>::new();
1009
1010    a.set(SetDestination::PINDIRS, 0);
1011    a.r#in(InSource::NULL, 1);
1012    a.push(false, false);
1013
1014    assert_eq!(
1015        a.assemble_program().wrap,
1016        Wrap {
1017            source: 2,
1018            target: 0,
1019        }
1020    );
1021}
1022
1023macro_rules! instr_test {
1024    ($name:ident ( $( $v:expr ),* ) , $expected:expr, $side_set:expr, $version:expr) => {
1025        paste::paste! {
1026            #[test]
1027            fn [< test _ $name _ $expected >]() {
1028                let expected = $expected;
1029
1030                let mut a = Assembler::<32>::new_with_side_set($side_set);
1031                a.$name(
1032                    $( $v ),*
1033                );
1034
1035                assert_eq!(a.version(), $version);
1036
1037                let instr = a.assemble()[0];
1038                if instr != expected {
1039                    panic!("assertion failure: (left == right)\nleft:  {:#016b}\nright: {:#016b}", instr, expected);
1040                }
1041
1042                let decoded = Instruction::decode(instr, $side_set).unwrap();
1043                let encoded = decoded.encode($side_set);
1044                if encoded != expected {
1045                    panic!("assertion failure: (left == right)\nleft:  {:#016b}\nright: {:#016b}", encoded, expected);
1046                }
1047            }
1048        }
1049    };
1050
1051    ($name:ident ( $( $v:expr ),* ) , $b:expr, $version:expr) => {
1052        instr_test!( $name ( $( $v ),* ), $b, SideSet::new(false, 0, false), $version);
1053    };
1054}
1055
1056// Tests for:
1057// RP2040: wait <polarity> irq <irq_num> (rel)
1058// RP235x: wait <polarity> (prev | next) irq <irq_num> (rel)
1059
1060// wait 0 irq 2
1061instr_test!(
1062    wait(
1063        0,
1064        WaitSource::Irq {
1065            irq: 2,
1066            index_mode: IrqIndexMode::DIRECT
1067        }
1068    ),
1069    0b001_00000_010_00010,
1070    PioVersion::V0
1071);
1072// wait 1 irq 7
1073instr_test!(
1074    wait(
1075        1,
1076        WaitSource::Irq {
1077            irq: 7,
1078            index_mode: IrqIndexMode::DIRECT
1079        }
1080    ),
1081    0b001_00000_110_00111,
1082    PioVersion::V0
1083);
1084// wait 0 irq 2 [30]
1085instr_test!(
1086    wait_with_delay(
1087        0,
1088        WaitSource::Irq {
1089            irq: 2,
1090            index_mode: IrqIndexMode::DIRECT
1091        },
1092        30
1093    ),
1094    0b001_11110_010_00010,
1095    PioVersion::V0
1096);
1097// wait 0 irq 2 side 5
1098instr_test!(
1099    wait_with_side_set(
1100        0,
1101        WaitSource::Irq {
1102            irq: 2,
1103            index_mode: IrqIndexMode::DIRECT
1104        },
1105        0b10101
1106    ),
1107    0b001_10101_010_00010,
1108    SideSet::new(false, 5, false),
1109    PioVersion::V0
1110);
1111// wait 0 irq 2 (rel)
1112instr_test!(
1113    wait(
1114        0,
1115        WaitSource::Irq {
1116            irq: 2,
1117            index_mode: IrqIndexMode::REL
1118        }
1119    ),
1120    0b001_00000_010_10010,
1121    PioVersion::V0
1122);
1123// RP235x only: wait 0 prev irq 2
1124instr_test!(
1125    wait(
1126        0,
1127        WaitSource::Irq {
1128            irq: 2,
1129            index_mode: IrqIndexMode::PREV
1130        }
1131    ),
1132    0b001_00000_010_01010,
1133    PioVersion::V1
1134);
1135// RP235x only: wait 0 next irq 2
1136instr_test!(
1137    wait(
1138        0,
1139        WaitSource::Irq {
1140            irq: 2,
1141            index_mode: IrqIndexMode::NEXT
1142        }
1143    ),
1144    0b001_00000_010_11010,
1145    PioVersion::V1
1146);
1147// RP235x only: wait 1 next irq 2 [30]
1148instr_test!(
1149    wait_with_delay(
1150        1,
1151        WaitSource::Irq {
1152            irq: 2,
1153            index_mode: IrqIndexMode::NEXT
1154        },
1155        30
1156    ),
1157    0b001_11110_110_11010,
1158    PioVersion::V1
1159);
1160// RP235x only: wait 0 next irq 2 side 5
1161instr_test!(
1162    wait_with_side_set(
1163        0,
1164        WaitSource::Irq {
1165            irq: 2,
1166            index_mode: IrqIndexMode::NEXT
1167        },
1168        0b10101
1169    ),
1170    0b001_10101_010_11010,
1171    SideSet::new(false, 5, false),
1172    PioVersion::V1
1173);
1174
1175// Tests for:
1176// RP2040: -
1177// RP235x: wait <polarity> jmppin (+ <pin_offset>)
1178
1179// RP235x only: wait 0 jmppin
1180instr_test!(
1181    wait(0, WaitSource::JmpPin { offset: None }),
1182    0b001_00000_011_00000,
1183    PioVersion::V1
1184);
1185// RP235x only: wait 1 jmppin + 17
1186instr_test!(
1187    wait(1, WaitSource::JmpPin { offset: Some(17) }),
1188    0b001_00000_111_10001,
1189    PioVersion::V1
1190);
1191// RP235x only: wait 1 jmppin + 2 [30]
1192instr_test!(
1193    wait_with_delay(1, WaitSource::JmpPin { offset: Some(2) }, 30),
1194    0b001_11110_111_00010,
1195    PioVersion::V1
1196);
1197// RP235x only: wait 1 jmppin + 2 side 5
1198instr_test!(
1199    wait_with_side_set(1, WaitSource::JmpPin { offset: Some(2) }, 0b10101),
1200    0b001_10101_111_00010,
1201    SideSet::new(false, 5, false),
1202    PioVersion::V1
1203);
1204
1205// wait 1 gpio 16
1206instr_test!(
1207    wait(1, WaitSource::Gpio(16)),
1208    0b001_00000_100_10000,
1209    PioVersion::V0
1210);
1211
1212instr_test!(r#in(InSource::Y, 10), 0b010_00000_010_01010, PioVersion::V0);
1213instr_test!(r#in(InSource::Y, 32), 0b010_00000_010_00000, PioVersion::V0);
1214
1215instr_test!(
1216    out(OutDestination::Y, 10),
1217    0b011_00000_010_01010,
1218    PioVersion::V0
1219);
1220instr_test!(
1221    out(OutDestination::Y, 32),
1222    0b011_00000_010_00000,
1223    PioVersion::V0
1224);
1225
1226#[test]
1227#[should_panic(expected = "bit_count must be from 1 to 32")]
1228fn test_in_bit_width_zero_should_panic() {
1229    let mut a = Assembler::<32>::new();
1230    a.r#in(InSource::Y, 0);
1231    a.assemble_program();
1232}
1233
1234#[test]
1235#[should_panic(expected = "bit_count must be from 1 to 32")]
1236fn test_in_bit_width_exceeds_max_should_panic() {
1237    let mut a = Assembler::<32>::new();
1238    a.r#in(InSource::Y, 33);
1239    a.assemble_program();
1240}
1241
1242#[test]
1243#[should_panic(expected = "bit_count must be from 1 to 32")]
1244fn test_out_bit_width_zero_should_panic() {
1245    let mut a = Assembler::<32>::new();
1246    a.out(OutDestination::X, 0);
1247    a.assemble_program();
1248}
1249
1250#[test]
1251#[should_panic(expected = "bit_count must be from 1 to 32")]
1252fn test_out_bit_width_exceeds_max_should_panic() {
1253    let mut a = Assembler::<32>::new();
1254    a.out(OutDestination::X, 33);
1255    a.assemble_program();
1256}
1257
1258instr_test!(push(true, false), 0b100_00000_010_00000, PioVersion::V0);
1259instr_test!(push(false, true), 0b100_00000_001_00000, PioVersion::V0);
1260
1261instr_test!(pull(true, false), 0b100_00000_110_00000, PioVersion::V0);
1262instr_test!(pull(false, true), 0b100_00000_101_00000, PioVersion::V0);
1263
1264instr_test!(
1265    mov(
1266        MovDestination::Y,
1267        MovOperation::BitReverse,
1268        MovSource::STATUS
1269    ),
1270    0b101_00000_010_10101,
1271    PioVersion::V0
1272);
1273
1274instr_test!(
1275    irq(true, false, 0b11, IrqIndexMode::DIRECT),
1276    0b110_00000_010_00_011,
1277    PioVersion::V0
1278);
1279instr_test!(
1280    irq(false, true, 0b111, IrqIndexMode::REL),
1281    0b110_00000_001_10_111,
1282    PioVersion::V0
1283);
1284instr_test!(
1285    irq(true, false, 0b1, IrqIndexMode::PREV),
1286    0b110_00000_010_01_001,
1287    PioVersion::V1
1288);
1289instr_test!(
1290    irq(false, true, 0b101, IrqIndexMode::NEXT),
1291    0b110_00000_001_11_101,
1292    PioVersion::V1
1293);
1294
1295instr_test!(
1296    set(SetDestination::Y, 10),
1297    0b111_00000_010_01010,
1298    PioVersion::V0
1299);
1300
1301instr_test!(
1302    mov(MovDestination::PINDIRS, MovOperation::None, MovSource::X),
1303    0b101_00000_0110_0001,
1304    PioVersion::V1
1305);
1306
1307instr_test!(
1308    wait(0, WaitSource::JmpPin { offset: None }),
1309    0b001_00000_0110_0000,
1310    PioVersion::V1
1311);
1312
1313instr_test!(
1314    mov_to_rx(MovRxIndex::RXFIFO3),
1315    0b100_00000_0001_1011,
1316    PioVersion::V1
1317);
1318instr_test!(
1319    mov_to_rx(MovRxIndex::RXFIFOY),
1320    0b100_00000_0001_0000,
1321    PioVersion::V1
1322);
1323
1324instr_test!(
1325    mov_from_rx(MovRxIndex::RXFIFO3),
1326    0b100_00000_1001_1011,
1327    PioVersion::V1
1328);
1329instr_test!(
1330    mov_from_rx(MovRxIndex::RXFIFOY),
1331    0b100_00000_1001_0000,
1332    PioVersion::V1
1333);
1334
1335/// This block ensures that README.md is checked when `cargo test` is run.
1336#[cfg(doctest)]
1337mod test_readme {
1338    macro_rules! external_doc_test {
1339        ($x:expr) => {
1340            #[doc = $x]
1341            extern "C" {}
1342        };
1343    }
1344    external_doc_test!(include_str!("../README.md"));
1345}
1346
1347// End of file