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