1#![no_std]
5#![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
13pub const RP2040_MAX_PROGRAM_SIZE: usize = 32;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum PioVersion {
22 V0,
24 V1,
26}
27
28#[repr(u8)]
29#[derive(Debug, Clone, Copy, TryFromPrimitive, PartialEq, Eq)]
30pub enum JmpCondition {
31 Always = 0b000,
33 XIsZero = 0b001,
35 XDecNonZero = 0b010,
37 YIsZero = 0b011,
39 YDecNonZero = 0b100,
41 XNotEqualY = 0b101,
43 PinHigh = 0b110,
45 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 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 }
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 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 PINDIRS = 0b100,
137 }
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 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 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 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#[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 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 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#[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#[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#[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 #[allow(clippy::new_without_default)]
562 pub fn new() -> Self {
563 Assembler::new_with_side_set(SideSet::default())
564 }
565
566 #[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 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 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 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 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 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 pub fn label(&mut self) -> Label {
658 Label {
659 state: LabelState::Unbound(u8::MAX),
660 }
661 }
662
663 pub fn label_at_offset(&mut self, offset: u8) -> Label {
665 Label {
666 state: LabelState::Bound(offset),
667 }
668 }
669
670 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 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 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 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 r#in(self, source: InSource, bit_count: u8) {
753 InstructionOperands::IN { source, bit_count }
754 }
755 );
756
757 instr!(
758 out(self, destination: OutDestination, bit_count: u8) {
760 InstructionOperands::OUT {
761 destination,
762 bit_count,
763 }
764 }
765 );
766
767 instr!(
768 push(self, if_full: bool, block: bool) {
770 InstructionOperands::PUSH {
771 if_full,
772 block,
773 }
774 }
775 );
776
777 instr!(
778 pull(self, if_empty: bool, block: bool) {
780 InstructionOperands::PULL {
781 if_empty,
782 block,
783 }
784 }
785 );
786
787 instr!(
788 mov(self, destination: MovDestination, op: MovOperation, source: MovSource) {
790 InstructionOperands::MOV {
791 destination,
792 op,
793 source,
794 }
795 }
796 );
797
798 instr!(
799 mov_to_rx(self, fifo_index: MovRxIndex) {
801 InstructionOperands::MOVTORX {
802 fifo_index
803 }
804 }
805 );
806
807 instr!(
808 mov_from_rx(self, fifo_index: MovRxIndex) {
810 InstructionOperands::MOVFROMRX {
811 fifo_index
812 }
813 }
814 );
815
816 instr!(
817 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 set(self, destination: SetDestination, data: u8) {
831 InstructionOperands::SET {
832 destination,
833 data,
834 }
835 }
836 );
837
838 instr!(
839 nop(self) {
842 InstructionOperands::MOV {
843 destination: MovDestination::Y,
844 op: MovOperation::None,
845 source: MovSource::Y
846 }
847 }
848 );
849}
850
851#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
859pub struct Wrap {
860 pub source: u8,
862 pub target: u8,
864}
865
866#[derive(Debug)]
868pub struct Program<const PROGRAM_SIZE: usize> {
869 pub code: ArrayVec<u16, PROGRAM_SIZE>,
871 pub origin: Option<u8>,
881 pub wrap: Wrap,
883 pub side_set: SideSet,
885 pub version: PioVersion,
887}
888
889impl<const PROGRAM_SIZE: usize> Program<PROGRAM_SIZE> {
890 pub fn set_origin(self, origin: Option<u8>) -> Self {
894 Self { origin, ..self }
895 }
896
897 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
905pub struct ProgramWithDefines<PublicDefines, const PROGRAM_SIZE: usize> {
907 pub program: Program<PROGRAM_SIZE>,
909 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, 0b111_00000_001_00001, 0b000_00000_000_00001, ]
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 0b111_00000_010_00000, 0b000_00000_011_00011, 0b000_00000_000_00000, 0b111_00000_010_00001, ]
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#[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