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 InSource {
52 PINS = 0b000,
53 X = 0b001,
54 Y = 0b010,
55 NULL = 0b011,
56 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 }
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 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 PINDIRS = 0b100,
128 }
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 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 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 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#[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 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 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#[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#[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#[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 #[allow(clippy::new_without_default)]
579 pub fn new() -> Self {
580 Assembler::new_with_side_set(SideSet::default())
581 }
582
583 #[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 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 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 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 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 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 pub fn label(&mut self) -> Label {
683 Label {
684 state: LabelState::Unbound(u8::MAX),
685 }
686 }
687
688 pub fn label_at_offset(&mut self, offset: u8) -> Label {
690 Label {
691 state: LabelState::Bound(offset),
692 }
693 }
694
695 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 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 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 wait(self, polarity: u8, source: WaitSource) {
766 InstructionOperands::WAIT {
767 polarity,
768 source,
769 }
770 }
771 );
772
773 instr!(
774 r#in(self, source: InSource, bit_count: u8) {
776 InstructionOperands::IN { source, bit_count }
777 }
778 );
779
780 instr!(
781 out(self, destination: OutDestination, bit_count: u8) {
783 InstructionOperands::OUT {
784 destination,
785 bit_count,
786 }
787 }
788 );
789
790 instr!(
791 push(self, if_full: bool, block: bool) {
793 InstructionOperands::PUSH {
794 if_full,
795 block,
796 }
797 }
798 );
799
800 instr!(
801 pull(self, if_empty: bool, block: bool) {
803 InstructionOperands::PULL {
804 if_empty,
805 block,
806 }
807 }
808 );
809
810 instr!(
811 mov(self, destination: MovDestination, op: MovOperation, source: MovSource) {
813 InstructionOperands::MOV {
814 destination,
815 op,
816 source,
817 }
818 }
819 );
820
821 instr!(
822 mov_to_rx(self, fifo_index: MovRxIndex) {
824 InstructionOperands::MOVTORX {
825 fifo_index
826 }
827 }
828 );
829
830 instr!(
831 mov_from_rx(self, fifo_index: MovRxIndex) {
833 InstructionOperands::MOVFROMRX {
834 fifo_index
835 }
836 }
837 );
838
839 instr!(
840 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 set(self, destination: SetDestination, data: u8) {
854 InstructionOperands::SET {
855 destination,
856 data,
857 }
858 }
859 );
860
861 instr!(
862 nop(self) {
865 InstructionOperands::MOV {
866 destination: MovDestination::Y,
867 op: MovOperation::None,
868 source: MovSource::Y
869 }
870 }
871 );
872}
873
874#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
882pub struct Wrap {
883 pub source: u8,
885 pub target: u8,
887}
888
889#[derive(Debug)]
891pub struct Program<const PROGRAM_SIZE: usize> {
892 pub code: ArrayVec<u16, PROGRAM_SIZE>,
894 pub origin: Option<u8>,
904 pub wrap: Wrap,
906 pub side_set: SideSet,
908 pub version: PioVersion,
910}
911
912impl<const PROGRAM_SIZE: usize> Program<PROGRAM_SIZE> {
913 pub fn set_origin(self, origin: Option<u8>) -> Self {
917 Self { origin, ..self }
918 }
919
920 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
928pub struct ProgramWithDefines<PublicDefines, const PROGRAM_SIZE: usize> {
930 pub program: Program<PROGRAM_SIZE>,
932 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, 0b111_00000_001_00001, 0b000_00000_000_00001, ]
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 0b111_00000_010_00000, 0b000_00000_011_00011, 0b000_00000_000_00000, 0b111_00000_010_00001, ]
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
1056instr_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);
1072instr_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);
1084instr_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);
1097instr_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);
1111instr_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);
1123instr_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);
1135instr_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);
1147instr_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);
1160instr_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
1175instr_test!(
1181 wait(0, WaitSource::JmpPin { offset: None }),
1182 0b001_00000_011_00000,
1183 PioVersion::V1
1184);
1185instr_test!(
1187 wait(1, WaitSource::JmpPin { offset: Some(17) }),
1188 0b001_00000_111_10001,
1189 PioVersion::V1
1190);
1191instr_test!(
1193 wait_with_delay(1, WaitSource::JmpPin { offset: Some(2) }, 30),
1194 0b001_11110_111_00010,
1195 PioVersion::V1
1196);
1197instr_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
1205instr_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#[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