Skip to main content

wave_decode/
decoder.rs

1// Copyright 2026 Ojima Abraham
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core instruction decoder. Reads binary instruction words and produces
5//!
6//! structured `DecodedInstruction` values.
7
8use crate::instruction::{DecodedInstruction, Operation};
9use crate::opcodes::{
10    AtomicOp, BitOpType, CmpOp, ControlOp, CvtType, F16Op, F16PackedOp, F64DivSqrtOp, F64Op,
11    FUnaryOp, MemWidth, MiscOp, Opcode, Scope, SyncOp, WaveOpType, WaveReduceType,
12    EXTENDED_RS3_MASK, EXTENDED_RS3_SHIFT, EXTENDED_RS4_MASK, EXTENDED_RS4_SHIFT, FLAGS_MASK,
13    MISC_OP_FLAG, MODIFIER_MASK, MODIFIER_SHIFT, OPCODE_MASK, OPCODE_SHIFT, PRED_MASK,
14    PRED_NEG_MASK, PRED_NEG_SHIFT, PRED_SHIFT, RD_MASK, RD_SHIFT, RS1_MASK, RS1_SHIFT, RS2_MASK,
15    RS2_SHIFT, SCOPE_MASK, SCOPE_SHIFT, SYNC_OP_FLAG,
16};
17use thiserror::Error;
18
19#[derive(Debug, Error)]
20pub enum DecodeError {
21    #[error("unexpected end of code at offset {offset}")]
22    UnexpectedEnd { offset: u32 },
23    #[error("invalid opcode 0x{opcode:02x} at offset {offset}")]
24    InvalidOpcode { opcode: u8, offset: u32 },
25    #[error("invalid modifier {modifier} for opcode {opcode:?} at offset {offset}")]
26    InvalidModifier {
27        opcode: Opcode,
28        modifier: u8,
29        offset: u32,
30    },
31}
32
33/// WAVE instruction decoder
34pub struct Decoder<'a> {
35    code: &'a [u8],
36    offset: u32,
37}
38
39impl<'a> Decoder<'a> {
40    /// Create a new decoder for the given code bytes
41    #[must_use]
42    pub fn new(code: &'a [u8]) -> Self {
43        Self { code, offset: 0 }
44    }
45
46    /// Get current offset in bytes
47    #[must_use]
48    pub fn offset(&self) -> u32 {
49        self.offset
50    }
51
52    /// Check if there are more instructions to decode
53    #[must_use]
54    pub fn has_more(&self) -> bool {
55        (self.offset as usize) + 4 <= self.code.len()
56    }
57
58    /// Decode the next instruction
59    ///
60    /// # Errors
61    ///
62    /// Returns `DecodeError::UnexpectedEnd` if there are fewer than 4 bytes remaining.
63    /// Returns `DecodeError::InvalidOpcode` if the opcode byte is not recognized.
64    /// Returns `DecodeError::InvalidModifier` if the modifier is invalid for the opcode.
65    pub fn decode_next(&mut self) -> Result<DecodedInstruction, DecodeError> {
66        let offset = self.offset;
67
68        if (offset as usize) + 4 > self.code.len() {
69            return Err(DecodeError::UnexpectedEnd { offset });
70        }
71
72        let word0 = self.read_u32();
73        let opcode_raw = ((word0 >> OPCODE_SHIFT) & OPCODE_MASK) as u8;
74        let rd = ((word0 >> RD_SHIFT) & RD_MASK) as u8;
75        let rs1 = ((word0 >> RS1_SHIFT) & RS1_MASK) as u8;
76        let rs2 = ((word0 >> RS2_SHIFT) & RS2_MASK) as u8;
77        let modifier = ((word0 >> MODIFIER_SHIFT) & MODIFIER_MASK) as u8;
78        let scope = ((word0 >> SCOPE_SHIFT) & SCOPE_MASK) as u8;
79        let predicate = ((word0 >> PRED_SHIFT) & PRED_MASK) as u8;
80        let pred_neg = ((word0 >> PRED_NEG_SHIFT) & PRED_NEG_MASK) != 0;
81        let flags = (word0 & FLAGS_MASK) as u8;
82
83        let opcode = Opcode::from_u8(opcode_raw).ok_or(DecodeError::InvalidOpcode {
84            opcode: opcode_raw,
85            offset,
86        })?;
87
88        let operation =
89            self.decode_operation(opcode, rd, rs1, rs2, modifier, scope, flags, offset)?;
90
91        let size = if self.offset > offset + 4 { 8 } else { 4 };
92
93        Ok(DecodedInstruction {
94            offset,
95            size,
96            operation,
97            predicate,
98            predicate_negated: pred_neg,
99        })
100    }
101
102    fn read_u32(&mut self) -> u32 {
103        let off = self.offset as usize;
104        let bytes = &self.code[off..off + 4];
105        self.offset += 4;
106        u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
107    }
108
109    fn peek_u32(&self) -> Option<u32> {
110        let off = self.offset as usize;
111        if off + 4 <= self.code.len() {
112            let bytes = &self.code[off..off + 4];
113            Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
114        } else {
115            None
116        }
117    }
118
119    #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
120    fn decode_operation(
121        &mut self,
122        opcode: Opcode,
123        rd: u8,
124        rs1: u8,
125        rs2: u8,
126        modifier: u8,
127        scope: u8,
128        flags: u8,
129        offset: u32,
130    ) -> Result<Operation, DecodeError> {
131        let op = match opcode {
132            Opcode::Iadd => Operation::Iadd { rd, rs1, rs2 },
133            Opcode::Isub => Operation::Isub { rd, rs1, rs2 },
134            Opcode::Imul => Operation::Imul { rd, rs1, rs2 },
135            Opcode::ImulHi => Operation::ImulHi { rd, rs1, rs2 },
136            Opcode::Imad => {
137                let word1 = self.read_u32();
138                let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
139                Operation::Imad { rd, rs1, rs2, rs3 }
140            }
141            Opcode::Idiv => Operation::Idiv { rd, rs1, rs2 },
142            Opcode::Imod => Operation::Imod { rd, rs1, rs2 },
143            Opcode::Ineg => Operation::Ineg { rd, rs1 },
144            Opcode::Iabs => Operation::Iabs { rd, rs1 },
145            Opcode::Imin => Operation::Imin { rd, rs1, rs2 },
146            Opcode::Imax => Operation::Imax { rd, rs1, rs2 },
147            Opcode::Iclamp => {
148                let word1 = self.read_u32();
149                let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
150                Operation::Iclamp { rd, rs1, rs2, rs3 }
151            }
152
153            Opcode::Fadd => Operation::Fadd { rd, rs1, rs2 },
154            Opcode::Fsub => Operation::Fsub { rd, rs1, rs2 },
155            Opcode::Fmul => Operation::Fmul { rd, rs1, rs2 },
156            Opcode::Fma => {
157                let word1 = self.read_u32();
158                let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
159                Operation::Fma { rd, rs1, rs2, rs3 }
160            }
161            Opcode::Fdiv => Operation::Fdiv { rd, rs1, rs2 },
162            Opcode::Fneg => Operation::Fneg { rd, rs1 },
163            Opcode::Fabs => Operation::Fabs { rd, rs1 },
164            Opcode::Fmin => Operation::Fmin { rd, rs1, rs2 },
165            Opcode::Fmax => Operation::Fmax { rd, rs1, rs2 },
166            Opcode::Fclamp => {
167                let word1 = self.read_u32();
168                let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
169                Operation::Fclamp { rd, rs1, rs2, rs3 }
170            }
171            Opcode::Fsqrt => Operation::Fsqrt { rd, rs1 },
172            Opcode::FUnaryOps => {
173                let op = FUnaryOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
174                    opcode,
175                    modifier,
176                    offset,
177                })?;
178                Operation::FUnary { op, rd, rs1 }
179            }
180
181            Opcode::F16Ops => {
182                let op = F16Op::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
183                    opcode,
184                    modifier,
185                    offset,
186                })?;
187                let rs3 = if op == F16Op::Hma {
188                    let word1 = self.read_u32();
189                    Some(((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8)
190                } else {
191                    None
192                };
193                Operation::F16 {
194                    op,
195                    rd,
196                    rs1,
197                    rs2,
198                    rs3,
199                }
200            }
201
202            Opcode::F16PackedOps => {
203                let op = F16PackedOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
204                    opcode,
205                    modifier,
206                    offset,
207                })?;
208                let rs3 = if op == F16PackedOp::Hma2 {
209                    let word1 = self.read_u32();
210                    Some(((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8)
211                } else {
212                    None
213                };
214                Operation::F16Packed {
215                    op,
216                    rd,
217                    rs1,
218                    rs2,
219                    rs3,
220                }
221            }
222
223            Opcode::F64Ops => {
224                let op = F64Op::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
225                    opcode,
226                    modifier,
227                    offset,
228                })?;
229                let rs3 = if op == F64Op::Dma {
230                    let word1 = self.read_u32();
231                    Some(((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8)
232                } else {
233                    None
234                };
235                Operation::F64 {
236                    op,
237                    rd,
238                    rs1,
239                    rs2,
240                    rs3,
241                }
242            }
243
244            Opcode::F64DivSqrt => {
245                let op = F64DivSqrtOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
246                    opcode,
247                    modifier,
248                    offset,
249                })?;
250                let rs2_opt = if op == F64DivSqrtOp::Ddiv {
251                    Some(rs2)
252                } else {
253                    None
254                };
255                Operation::F64DivSqrt {
256                    op,
257                    rd,
258                    rs1,
259                    rs2: rs2_opt,
260                }
261            }
262
263            Opcode::And => Operation::And { rd, rs1, rs2 },
264            Opcode::Or => Operation::Or { rd, rs1, rs2 },
265            Opcode::Xor => Operation::Xor { rd, rs1, rs2 },
266            Opcode::Not => Operation::Not { rd, rs1 },
267            Opcode::Shl => Operation::Shl { rd, rs1, rs2 },
268            Opcode::Shr => Operation::Shr { rd, rs1, rs2 },
269            Opcode::Sar => Operation::Sar { rd, rs1, rs2 },
270            Opcode::BitOps => {
271                let op = BitOpType::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
272                    opcode,
273                    modifier,
274                    offset,
275                })?;
276                match op {
277                    BitOpType::Bitcount | BitOpType::Bitfind | BitOpType::Bitrev => {
278                        Operation::BitOp {
279                            op,
280                            rd,
281                            rs1,
282                            rs2: None,
283                            rs3: None,
284                            rs4: None,
285                        }
286                    }
287                    BitOpType::Bfe => {
288                        let word1 = self.read_u32();
289                        let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
290                        Operation::BitOp {
291                            op,
292                            rd,
293                            rs1,
294                            rs2: Some(rs2),
295                            rs3: Some(rs3),
296                            rs4: None,
297                        }
298                    }
299                    BitOpType::Bfi => {
300                        let word1 = self.read_u32();
301                        let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
302                        let rs4 = ((word1 >> EXTENDED_RS4_SHIFT) & EXTENDED_RS4_MASK) as u8;
303                        Operation::BitOp {
304                            op,
305                            rd,
306                            rs1,
307                            rs2: Some(rs2),
308                            rs3: Some(rs3),
309                            rs4: Some(rs4),
310                        }
311                    }
312                }
313            }
314
315            Opcode::Icmp => {
316                let op = CmpOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
317                    opcode,
318                    modifier,
319                    offset,
320                })?;
321                Operation::Icmp {
322                    op,
323                    pd: rd,
324                    rs1,
325                    rs2,
326                }
327            }
328            Opcode::Ucmp => {
329                let op = CmpOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
330                    opcode,
331                    modifier,
332                    offset,
333                })?;
334                Operation::Ucmp {
335                    op,
336                    pd: rd,
337                    rs1,
338                    rs2,
339                }
340            }
341            Opcode::Fcmp => {
342                let op = CmpOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
343                    opcode,
344                    modifier,
345                    offset,
346                })?;
347                Operation::Fcmp {
348                    op,
349                    pd: rd,
350                    rs1,
351                    rs2,
352                }
353            }
354
355            Opcode::Select => Operation::Select {
356                rd,
357                ps: rs1,
358                rs1: rs2,
359                rs2: modifier, // rs2 is encoded in modifier field for select
360            },
361            Opcode::Cvt => {
362                let cvt_type = CvtType::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
363                    opcode,
364                    modifier,
365                    offset,
366                })?;
367                Operation::Cvt { cvt_type, rd, rs1 }
368            }
369
370            Opcode::LocalLoad => {
371                let width = MemWidth::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
372                    opcode,
373                    modifier,
374                    offset,
375                })?;
376                Operation::LocalLoad {
377                    width,
378                    rd,
379                    addr: rs1,
380                }
381            }
382            Opcode::LocalStore => {
383                let width = MemWidth::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
384                    opcode,
385                    modifier,
386                    offset,
387                })?;
388                Operation::LocalStore {
389                    width,
390                    addr: rs1,
391                    value: rs2,
392                }
393            }
394
395            Opcode::DeviceLoad => {
396                let width = MemWidth::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
397                    opcode,
398                    modifier,
399                    offset,
400                })?;
401                Operation::DeviceLoad {
402                    width,
403                    rd,
404                    addr: rs1,
405                }
406            }
407            Opcode::DeviceStore => {
408                let width = MemWidth::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
409                    opcode,
410                    modifier,
411                    offset,
412                })?;
413                Operation::DeviceStore {
414                    width,
415                    addr: rs1,
416                    value: rs2,
417                }
418            }
419
420            Opcode::LocalAtomic => {
421                if modifier == 8 && self.peek_u32().is_some() {
422                    let word1 = self.read_u32();
423                    let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
424                    let rd_opt = if rd != 0 { Some(rd) } else { None };
425                    Operation::LocalAtomicCas {
426                        rd: rd_opt,
427                        addr: rs1,
428                        expected: rs2,
429                        desired: rs3,
430                    }
431                } else {
432                    let op = AtomicOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
433                        opcode,
434                        modifier,
435                        offset,
436                    })?;
437                    let rd_opt = if rd != 0 { Some(rd) } else { None };
438                    Operation::LocalAtomic {
439                        op,
440                        rd: rd_opt,
441                        addr: rs1,
442                        value: rs2,
443                    }
444                }
445            }
446
447            Opcode::DeviceAtomic => {
448                let scope_val = Scope::from_u8(scope).ok_or(DecodeError::InvalidModifier {
449                    opcode,
450                    modifier: scope,
451                    offset,
452                })?;
453
454                if modifier == 8 && self.peek_u32().is_some() {
455                    let word1 = self.read_u32();
456                    let rs3 = ((word1 >> EXTENDED_RS3_SHIFT) & EXTENDED_RS3_MASK) as u8;
457                    let rd_opt = if rd != 0 { Some(rd) } else { None };
458                    Operation::DeviceAtomicCas {
459                        rd: rd_opt,
460                        addr: rs1,
461                        expected: rs2,
462                        desired: rs3,
463                        scope: scope_val,
464                    }
465                } else {
466                    let op = AtomicOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
467                        opcode,
468                        modifier,
469                        offset,
470                    })?;
471                    let rd_opt = if rd != 0 { Some(rd) } else { None };
472                    Operation::DeviceAtomic {
473                        op,
474                        rd: rd_opt,
475                        addr: rs1,
476                        value: rs2,
477                        scope: scope_val,
478                    }
479                }
480            }
481
482            Opcode::WaveOp => {
483                if modifier >= 8 {
484                    let op = WaveReduceType::from_u8(modifier - 8).ok_or(
485                        DecodeError::InvalidModifier {
486                            opcode,
487                            modifier,
488                            offset,
489                        },
490                    )?;
491                    Operation::WaveReduce { op, rd, rs1 }
492                } else {
493                    let op = WaveOpType::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
494                        opcode,
495                        modifier,
496                        offset,
497                    })?;
498                    match op {
499                        WaveOpType::Ballot => Operation::WaveBallot { rd, ps: rs1 },
500                        WaveOpType::Any | WaveOpType::All => Operation::WaveVote {
501                            op,
502                            pd: rd,
503                            ps: rs1,
504                        },
505                        _ => Operation::WaveOp {
506                            op,
507                            rd,
508                            rs1,
509                            rs2: Some(rs2),
510                        },
511                    }
512                }
513            }
514
515            Opcode::Control => self.decode_control(rd, rs1, rs2, modifier, scope, flags, offset)?,
516        };
517
518        Ok(op)
519    }
520
521    #[allow(clippy::too_many_arguments)]
522    fn decode_control(
523        &mut self,
524        rd: u8,
525        rs1: u8,
526        _rs2: u8,
527        modifier: u8,
528        scope: u8,
529        flags: u8,
530        offset: u32,
531    ) -> Result<Operation, DecodeError> {
532        if (flags & SYNC_OP_FLAG) != 0 {
533            let op = SyncOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
534                opcode: Opcode::Control,
535                modifier,
536                offset,
537            })?;
538            match op {
539                SyncOp::Return => Ok(Operation::Return),
540                SyncOp::Halt => Ok(Operation::Halt),
541                SyncOp::Barrier => Ok(Operation::Barrier),
542                SyncOp::FenceAcquire => {
543                    let scope_val = Scope::from_u8(scope).unwrap_or(Scope::Workgroup);
544                    Ok(Operation::FenceAcquire { scope: scope_val })
545                }
546                SyncOp::FenceRelease => {
547                    let scope_val = Scope::from_u8(scope).unwrap_or(Scope::Workgroup);
548                    Ok(Operation::FenceRelease { scope: scope_val })
549                }
550                SyncOp::FenceAcqRel => {
551                    let scope_val = Scope::from_u8(scope).unwrap_or(Scope::Workgroup);
552                    Ok(Operation::FenceAcqRel { scope: scope_val })
553                }
554                SyncOp::Wait => Ok(Operation::Wait),
555                SyncOp::Nop => Ok(Operation::Nop),
556            }
557        } else if (flags & MISC_OP_FLAG) != 0 {
558            let op = MiscOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
559                opcode: Opcode::Control,
560                modifier,
561                offset,
562            })?;
563            match op {
564                MiscOp::Mov => Ok(Operation::Mov { rd, rs1 }),
565                MiscOp::MovImm => {
566                    let imm = self.read_u32();
567                    Ok(Operation::MovImm { rd, imm })
568                }
569                MiscOp::MovSr => Ok(Operation::MovSr { rd, sr_index: rs1 }),
570            }
571        } else {
572            let op = ControlOp::from_u8(modifier).ok_or(DecodeError::InvalidModifier {
573                opcode: Opcode::Control,
574                modifier,
575                offset,
576            })?;
577            match op {
578                ControlOp::If => Ok(Operation::If { ps: rs1 }),
579                ControlOp::Else => Ok(Operation::Else),
580                ControlOp::Endif => Ok(Operation::Endif),
581                ControlOp::Loop => Ok(Operation::Loop),
582                ControlOp::Break => Ok(Operation::Break { ps: rs1 }),
583                ControlOp::Continue => Ok(Operation::Continue { ps: rs1 }),
584                ControlOp::Endloop => Ok(Operation::Endloop),
585                ControlOp::Call => {
586                    let target = self.read_u32();
587                    Ok(Operation::Call { target })
588                }
589            }
590        }
591    }
592}
593
594/// Decode a single instruction at the given offset
595///
596/// # Errors
597///
598/// Returns a `DecodeError` if the instruction cannot be decoded.
599pub fn decode_at(code: &[u8], offset: u32) -> Result<DecodedInstruction, DecodeError> {
600    let mut decoder = Decoder::new(code);
601    decoder.offset = offset;
602    decoder.decode_next()
603}
604
605/// Decode all instructions in the code section
606///
607/// # Errors
608///
609/// Returns a `DecodeError` if any instruction cannot be decoded.
610pub fn decode_all(code: &[u8]) -> Result<Vec<DecodedInstruction>, DecodeError> {
611    let mut decoder = Decoder::new(code);
612    let mut instructions = Vec::new();
613
614    while decoder.has_more() {
615        instructions.push(decoder.decode_next()?);
616    }
617
618    Ok(instructions)
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624
625    fn encode_base(opcode: u8, rd: u8, rs1: u8, rs2: u8, modifier: u8, flags: u8) -> [u8; 4] {
626        let word = ((u32::from(opcode) & OPCODE_MASK) << OPCODE_SHIFT)
627            | ((u32::from(rd) & RD_MASK) << RD_SHIFT)
628            | ((u32::from(rs1) & RS1_MASK) << RS1_SHIFT)
629            | ((u32::from(rs2) & RS2_MASK) << RS2_SHIFT)
630            | ((u32::from(modifier) & MODIFIER_MASK) << MODIFIER_SHIFT)
631            | u32::from(flags);
632        word.to_le_bytes()
633    }
634
635    #[test]
636    fn test_decode_iadd() {
637        let code = encode_base(0x00, 5, 3, 4, 0, 0);
638        let instr = decode_at(&code, 0).unwrap();
639        assert_eq!(instr.size, 4);
640        assert_eq!(
641            instr.operation,
642            Operation::Iadd {
643                rd: 5,
644                rs1: 3,
645                rs2: 4
646            }
647        );
648    }
649
650    #[test]
651    fn test_decode_halt() {
652        let code = encode_base(0x3F, 0, 0, 0, SyncOp::Halt as u8, SYNC_OP_FLAG);
653        let instr = decode_at(&code, 0).unwrap();
654        assert_eq!(instr.operation, Operation::Halt);
655    }
656
657    #[test]
658    fn test_decode_mov_imm() {
659        let mut code = encode_base(0x3F, 5, 0, 0, MiscOp::MovImm as u8, MISC_OP_FLAG).to_vec();
660        code.extend_from_slice(&0x12345678u32.to_le_bytes());
661
662        let instr = decode_at(&code, 0).unwrap();
663        assert_eq!(instr.size, 8);
664        assert_eq!(
665            instr.operation,
666            Operation::MovImm {
667                rd: 5,
668                imm: 0x12345678
669            }
670        );
671    }
672
673    #[test]
674    fn test_decode_mov_sr() {
675        let code = encode_base(0x3F, 5, 4, 0, MiscOp::MovSr as u8, MISC_OP_FLAG);
676        let instr = decode_at(&code, 0).unwrap();
677        assert_eq!(instr.operation, Operation::MovSr { rd: 5, sr_index: 4 });
678    }
679
680    #[test]
681    fn test_decode_device_store_u32() {
682        let code = encode_base(0x39, 0, 3, 5, MemWidth::U32 as u8, 0);
683        let instr = decode_at(&code, 0).unwrap();
684        assert_eq!(
685            instr.operation,
686            Operation::DeviceStore {
687                width: MemWidth::U32,
688                addr: 3,
689                value: 5
690            }
691        );
692    }
693
694    #[test]
695    fn test_decode_all() {
696        let mut code = encode_base(0x00, 5, 3, 4, 0, 0).to_vec(); // iadd
697        code.extend_from_slice(&encode_base(
698            0x3F,
699            0,
700            0,
701            0,
702            SyncOp::Halt as u8,
703            SYNC_OP_FLAG,
704        )); // halt
705
706        let instructions = decode_all(&code).unwrap();
707        assert_eq!(instructions.len(), 2);
708        assert_eq!(instructions[0].offset, 0);
709        assert_eq!(instructions[1].offset, 4);
710    }
711
712    #[test]
713    fn test_decode_shl() {
714        let code = encode_base(0x24, 2, 0, 1, 0, 0);
715        let instr = decode_at(&code, 0).unwrap();
716        assert_eq!(
717            instr.operation,
718            Operation::Shl {
719                rd: 2,
720                rs1: 0,
721                rs2: 1
722            }
723        );
724    }
725
726    #[test]
727    fn test_decode_predicated() {
728        let word = ((0x00u32) << OPCODE_SHIFT)
729            | ((5u32) << RD_SHIFT)
730            | ((3u32) << RS1_SHIFT)
731            | ((4u32) << RS2_SHIFT)
732            | ((1u32) << PRED_SHIFT); // p1
733
734        let code = word.to_le_bytes();
735        let instr = decode_at(&code, 0).unwrap();
736        assert_eq!(instr.predicate, 1);
737        assert!(!instr.predicate_negated);
738    }
739}