Skip to main content

ab_riscv_interpreter/v/zvexx/
perm.rs

1//! ZveXx permutation instructions
2
3#[cfg(test)]
4mod tests;
5pub mod zvexx_perm_helpers;
6
7use crate::v::vector_registers::{VectorRegisters, VectorRegistersExt};
8use crate::v::zvexx::zvexx_helpers;
9use crate::{
10    ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands, ExecutionError,
11    ExecutionResult, FetchInstructionResult, InstructionFetcher, OpaqueThreadedExecutionResult,
12    PackedAddress, ProgramCounter, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands,
13    ThreadedExecutableInstruction, ThreadedExecutionResult, VirtualMemory,
14};
15use ab_riscv_macros::instruction_execution;
16use ab_riscv_primitives::prelude::*;
17
18#[instruction_execution]
19const impl<Reg, Hart> ExecutableInstructionOperands for ZveXxPermInstruction<Hart>
20where
21    Reg: Register,
22    Hart: VectorHartConfig<Reg = Reg>,
23{
24}
25
26#[instruction_execution]
27const impl<Reg, Hart, Env> ExecutableInstructionCsr<Env> for ZveXxPermInstruction<Hart>
28where
29    Reg: Register,
30    Hart: VectorHartConfig<Reg = Reg>,
31{
32}
33
34#[instruction_execution]
35impl<Reg, Hart, Regs, Env, Memory, PC> ExecutableInstruction<Regs, Env, Memory, PC>
36    for ZveXxPermInstruction<Hart>
37where
38    Reg: Register,
39    Hart: VectorHartConfig<Reg = Reg>,
40    Regs: RegisterFile<Reg>,
41    Env: VectorRegistersExt<Hart = Hart>,
42    Memory: VirtualMemory,
43    PC: ProgramCounter<Reg::Type, Memory>,
44{
45    #[inline(always)]
46    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
47    fn execute(
48        self,
49        Rs1Rs2OperandValues {
50            rs1_value,
51            rs2_value: _,
52        }: Rs1Rs2OperandValues<Reg::Type>,
53        _regs: &mut Regs,
54        env: &mut Env,
55        _memory: &mut Memory,
56        program_counter: &PC,
57    ) -> ExecutionResult<Reg> {
58        match self {
59            // vmv.x.s rd, vs2
60            // Copies sign-extended element 0 of vs2 (at current SEW) to GPR rd.
61            // Requires valid vtype (needs SEW to know element width).
62            // Does not use vl or masking; always reads element 0.
63            Self::VmvXS { rd, vs2 } => {
64                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
65                    ::core::hint::cold_path();
66                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
67                        address: PackedAddress::new(
68                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
69                        ),
70                    });
71                }
72                let Some(config) = env.vector_config() else {
73                    ::core::hint::cold_path();
74                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
75                        address: PackedAddress::new(
76                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
77                        ),
78                    });
79                };
80                let vtype = config.vtype();
81                let sew = vtype.vsew();
82                let raw = env
83                    .read_vregs()
84                    .read_first(vs2, sew.as_eew())
85                    .expect("SEW never exceeds `ELEN`, which never exceeds `VLEN`; qed");
86                let sign_extended = zvexx_perm_helpers::sign_extend_to_reg::<Reg>(raw, sew);
87                env.mark_vs_dirty();
88
89                return ExecutionResult::Continue {
90                    rd,
91                    value: sign_extended,
92                };
93            }
94            // vmv.s.x vd, rs1
95            // Copies scalar GPR rs1 (zero-extended / truncated to SEW) into element 0 of vd.
96            // When vl == 0, the write is suppressed.
97            Self::VmvSX { vd, rs1: _ } => {
98                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
99                    ::core::hint::cold_path();
100                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
101                        address: PackedAddress::new(
102                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
103                        ),
104                    });
105                }
106                let Some(config) = env.vector_config() else {
107                    ::core::hint::cold_path();
108                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
109                        address: PackedAddress::new(
110                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
111                        ),
112                    });
113                };
114                let vtype = config.vtype();
115                let sew = vtype.vsew();
116                let vl = config.vl().get();
117                // Per spec §16.1: update only when `vstart < vl`, and `vstart` is zero here
118                if vl != Vl::ZERO {
119                    let scalar = rs1_value.as_i64().cast_unsigned();
120                    env.write_vregs()
121                        .write_first(vd, sew.as_eew(), scalar)
122                        .expect("SEW never exceeds `ELEN`, which never exceeds `VLEN`; qed");
123                }
124                env.mark_vs_dirty();
125            }
126            // vslideup.vx vd, vs2, rs1: _, vm
127            // Slides elements of vs2 up by the scalar offset in rs1.
128            // Elements vd[0..offset] are unchanged (tail-undisturbed for those positions).
129            // Elements vd[i] for offset <= i < vl get vs2[i - offset].
130            // Per spec §16.3.1: vd must not overlap vs2.
131            Self::VslideupVx {
132                vd,
133                vs2,
134                rs1: _,
135                vm,
136            } => {
137                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
138                    ::core::hint::cold_path();
139                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
140                        address: PackedAddress::new(
141                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
142                        ),
143                    });
144                }
145                let Some(config) = env.vector_config() else {
146                    ::core::hint::cold_path();
147                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
148                        address: PackedAddress::new(
149                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
150                        ),
151                    });
152                };
153                let sew = config.vtype().vsew();
154                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
155                    program_counter,
156                    config,
157                    vd,
158                    sew.as_eew(),
159                )?;
160                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
161                    program_counter,
162                    config,
163                    vs2,
164                    sew.as_eew(),
165                )?;
166                // vd must not overlap vs2
167                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
168                let offset = rs1_value.as_u64();
169                zvexx_perm_helpers::execute_slideup(env, vd, vs2, vm, offset);
170            }
171            // vslideup.vi vd, vs2, uimm, vm
172            // Same as vslideup.vx but offset is a 5-bit unsigned immediate.
173            Self::VslideupVi { vd, vs2, uimm, vm } => {
174                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
175                    ::core::hint::cold_path();
176                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
177                        address: PackedAddress::new(
178                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
179                        ),
180                    });
181                }
182                let Some(config) = env.vector_config() else {
183                    ::core::hint::cold_path();
184                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
185                        address: PackedAddress::new(
186                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
187                        ),
188                    });
189                };
190                let sew = config.vtype().vsew();
191                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
192                    program_counter,
193                    config,
194                    vd,
195                    sew.as_eew(),
196                )?;
197                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
198                    program_counter,
199                    config,
200                    vs2,
201                    sew.as_eew(),
202                )?;
203                // vd must not overlap vs2
204                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
205                let offset = u64::from(uimm);
206                zvexx_perm_helpers::execute_slideup(env, vd, vs2, vm, offset);
207            }
208            // vslidedown.vx vd, vs2, rs1: _, vm
209            // Element vd[i] = vs2[i + offset] if i + offset < VLMAX, else 0.
210            // vd may overlap vs2 for slidedown.
211            Self::VslidedownVx {
212                vd,
213                vs2,
214                rs1: _,
215                vm,
216            } => {
217                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
218                    ::core::hint::cold_path();
219                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
220                        address: PackedAddress::new(
221                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
222                        ),
223                    });
224                }
225                let Some(config) = env.vector_config() else {
226                    ::core::hint::cold_path();
227                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
228                        address: PackedAddress::new(
229                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
230                        ),
231                    });
232                };
233                let sew = config.vtype().vsew();
234                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
235                    program_counter,
236                    config,
237                    vd,
238                    sew.as_eew(),
239                )?;
240                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
241                    program_counter,
242                    config,
243                    vs2,
244                    sew.as_eew(),
245                )?;
246                let offset = rs1_value.as_u64();
247                zvexx_perm_helpers::execute_slidedown(env, vd, vs2, vm, offset);
248            }
249            // vslidedown.vi vd, vs2, uimm, vm
250            // Same as vslidedown.vx but offset is a 5-bit unsigned immediate.
251            Self::VslidedownVi { vd, vs2, uimm, vm } => {
252                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
253                    ::core::hint::cold_path();
254                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
255                        address: PackedAddress::new(
256                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
257                        ),
258                    });
259                }
260                let Some(config) = env.vector_config() else {
261                    ::core::hint::cold_path();
262                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
263                        address: PackedAddress::new(
264                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
265                        ),
266                    });
267                };
268                let sew = config.vtype().vsew();
269                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
270                    program_counter,
271                    config,
272                    vd,
273                    sew.as_eew(),
274                )?;
275                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
276                    program_counter,
277                    config,
278                    vs2,
279                    sew.as_eew(),
280                )?;
281                let offset = u64::from(uimm);
282                zvexx_perm_helpers::execute_slidedown(env, vd, vs2, vm, offset);
283            }
284            // vslide1up.vx vd, vs2, rs1: _, vm
285            // Element 0 of vd gets the scalar value rs1 (written at SEW width).
286            // Elements vd[i] for 1 <= i < vl get vs2[i - 1].
287            // vd must not overlap vs2.
288            Self::Vslide1upVx {
289                vd,
290                vs2,
291                rs1: _,
292                vm,
293            } => {
294                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
295                    ::core::hint::cold_path();
296                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
297                        address: PackedAddress::new(
298                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
299                        ),
300                    });
301                }
302                let Some(config) = env.vector_config() else {
303                    ::core::hint::cold_path();
304                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
305                        address: PackedAddress::new(
306                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
307                        ),
308                    });
309                };
310                let sew = config.vtype().vsew();
311                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
312                    program_counter,
313                    config,
314                    vd,
315                    sew.as_eew(),
316                )?;
317                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
318                    program_counter,
319                    config,
320                    vs2,
321                    sew.as_eew(),
322                )?;
323                // vd must not overlap vs2
324                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
325                let scalar = rs1_value.as_i64().cast_unsigned();
326                zvexx_perm_helpers::execute_slide1up(env, vd, vs2, vm, scalar);
327            }
328            // vslide1down.vx vd, vs2, rs1: _, vm
329            // Element vd[i] = vs2[i + 1] for 0 <= i < vl - 1.
330            // Element vd[vl - 1] gets the scalar value rs1.
331            // vd may overlap vs2 for slide1down.
332            Self::Vslide1downVx {
333                vd,
334                vs2,
335                rs1: _,
336                vm,
337            } => {
338                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
339                    ::core::hint::cold_path();
340                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
341                        address: PackedAddress::new(
342                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
343                        ),
344                    });
345                }
346                let Some(config) = env.vector_config() else {
347                    ::core::hint::cold_path();
348                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
349                        address: PackedAddress::new(
350                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
351                        ),
352                    });
353                };
354                let sew = config.vtype().vsew();
355                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
356                    program_counter,
357                    config,
358                    vd,
359                    sew.as_eew(),
360                )?;
361                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
362                    program_counter,
363                    config,
364                    vs2,
365                    sew.as_eew(),
366                )?;
367                let scalar = rs1_value.as_i64().cast_unsigned();
368                zvexx_perm_helpers::execute_slide1down(env, vd, vs2, vm, scalar);
369            }
370            // vrgather.vv vd, vs2, vs1, vm
371            // vd[i] = (vs1[i] < VLMAX) ? vs2[vs1[i]] : 0
372            // vd must not overlap vs1 or vs2.
373            Self::VrgatherVv { vd, vs2, vs1, vm } => {
374                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
375                    ::core::hint::cold_path();
376                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
377                        address: PackedAddress::new(
378                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
379                        ),
380                    });
381                }
382                let Some(config) = env.vector_config() else {
383                    ::core::hint::cold_path();
384                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
385                        address: PackedAddress::new(
386                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
387                        ),
388                    });
389                };
390                let sew = config.vtype().vsew();
391                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
392                    program_counter,
393                    config,
394                    vd,
395                    sew.as_eew(),
396                )?;
397                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
398                    program_counter,
399                    config,
400                    vs2,
401                    sew.as_eew(),
402                )?;
403                let vs1 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
404                    program_counter,
405                    config,
406                    vs1,
407                    sew.as_eew(),
408                )?;
409                // vd must not overlap either source
410                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
411                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs1)?;
412                zvexx_perm_helpers::execute_rgather_vv(env, vd, vs2, vs1, vm);
413            }
414            // vrgather.vx vd, vs2, rs1: _, vm
415            // All active elements of vd get vs2[rs1] if rs1 < VLMAX, else 0.
416            // vd must not overlap vs2.
417            Self::VrgatherVx {
418                vd,
419                vs2,
420                rs1: _,
421                vm,
422            } => {
423                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
424                    ::core::hint::cold_path();
425                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
426                        address: PackedAddress::new(
427                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
428                        ),
429                    });
430                }
431                let Some(config) = env.vector_config() else {
432                    ::core::hint::cold_path();
433                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
434                        address: PackedAddress::new(
435                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
436                        ),
437                    });
438                };
439                let sew = config.vtype().vsew();
440                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
441                    program_counter,
442                    config,
443                    vd,
444                    sew.as_eew(),
445                )?;
446                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
447                    program_counter,
448                    config,
449                    vs2,
450                    sew.as_eew(),
451                )?;
452                // vd must not overlap vs2
453                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
454                let index = rs1_value.as_u64();
455                zvexx_perm_helpers::execute_rgather_scalar(env, vd, vs2, vm, index);
456            }
457            // vrgather.vi vd, vs2, uimm, vm
458            // Same as vrgather.vx but index is a 5-bit unsigned immediate.
459            Self::VrgatherVi { vd, vs2, uimm, vm } => {
460                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
461                    ::core::hint::cold_path();
462                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
463                        address: PackedAddress::new(
464                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
465                        ),
466                    });
467                }
468                let Some(config) = env.vector_config() else {
469                    ::core::hint::cold_path();
470                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
471                        address: PackedAddress::new(
472                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
473                        ),
474                    });
475                };
476                let sew = config.vtype().vsew();
477                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
478                    program_counter,
479                    config,
480                    vd,
481                    sew.as_eew(),
482                )?;
483                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
484                    program_counter,
485                    config,
486                    vs2,
487                    sew.as_eew(),
488                )?;
489                // vd must not overlap vs2
490                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
491                let index = u64::from(uimm);
492                zvexx_perm_helpers::execute_rgather_scalar(env, vd, vs2, vm, index);
493            }
494            // vrgatherei16.vv vd, vs2, vs1, vm
495            // Like vrgather.vv but vs1 always uses EEW=16 (regardless of SEW).
496            // EMUL_vs1 = (16 / SEW) * LMUL; must be in [1/8, 8] else illegal.
497            // vd must not overlap vs1 or vs2.
498            Self::Vrgatherei16Vv { vd, vs2, vs1, vm } => {
499                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
500                    ::core::hint::cold_path();
501                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
502                        address: PackedAddress::new(
503                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
504                        ),
505                    });
506                }
507                let Some(config) = env.vector_config() else {
508                    ::core::hint::cold_path();
509                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
510                        address: PackedAddress::new(
511                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
512                        ),
513                    });
514                };
515                let sew = config.vtype().vsew();
516                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
517                    program_counter,
518                    config,
519                    vd,
520                    sew.as_eew(),
521                )?;
522                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
523                    program_counter,
524                    config,
525                    vs2,
526                    sew.as_eew(),
527                )?;
528                let vs1 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
529                    program_counter,
530                    config,
531                    vs1,
532                    Eew::E16,
533                )?;
534                // vd must not overlap either source
535                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
536                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs1)?;
537                // `vs1` is read with EEW=16 and `vs2` with EEW=SEW
538                if sew != Vsew::E16 {
539                    zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(
540                        program_counter,
541                        vs2,
542                        vs1,
543                    )?;
544                }
545                zvexx_perm_helpers::execute_rgatherei16(env, vd, vs2, vs1, vm);
546            }
547            // vmerge.vvm / vmv.v.v
548            // When vm=true: vmv.v.v vd, vs1 - broadcast all active elements from vs1.
549            //   vs2 is ignored; no overlap restriction on vd/vs2.
550            // When vm=false: vmerge.vvm vd, vs2, vs1, v0
551            //   vd[i] = v0[i] ? vs1[i] : vs2[i]
552            //   vd must not overlap v0 (mask source), which the decoder rejects.
553            Self::VmergeVvm { vd, vs2, vs1, vm } => {
554                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
555                    ::core::hint::cold_path();
556                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
557                        address: PackedAddress::new(
558                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
559                        ),
560                    });
561                }
562                let Some(config) = env.vector_config() else {
563                    ::core::hint::cold_path();
564                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
565                        address: PackedAddress::new(
566                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
567                        ),
568                    });
569                };
570                let sew = config.vtype().vsew();
571                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
572                    program_counter,
573                    config,
574                    vd,
575                    sew.as_eew(),
576                )?;
577                let vs1 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
578                    program_counter,
579                    config,
580                    vs1,
581                    sew.as_eew(),
582                )?;
583                // Unmasked, this is `vmv.v.v`, for which the encoding fixes `vs2` to `v0`, which is
584                // a valid register group that is not read
585                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
586                    program_counter,
587                    config,
588                    vs2,
589                    sew.as_eew(),
590                )?;
591                zvexx_perm_helpers::execute_merge_vv(env, vd, vs2, vs1, vm);
592            }
593            // vmerge.vxm / vmv.v.x
594            // When vm=true: vmv.v.x vd, rs1 - broadcast scalar to all active elements.
595            // When vm=false: vmerge.vxm - vd[i] = v0[i] ? rs1 : vs2[i]
596            Self::VmergeVxm {
597                vd,
598                vs2,
599                rs1: _,
600                vm,
601            } => {
602                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
603                    ::core::hint::cold_path();
604                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
605                        address: PackedAddress::new(
606                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
607                        ),
608                    });
609                }
610                let Some(config) = env.vector_config() else {
611                    ::core::hint::cold_path();
612                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
613                        address: PackedAddress::new(
614                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
615                        ),
616                    });
617                };
618                let sew = config.vtype().vsew();
619                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
620                    program_counter,
621                    config,
622                    vd,
623                    sew.as_eew(),
624                )?;
625                // Unmasked, this is `vmv.v.x`/`vmv.v.i`, for which the encoding fixes `vs2` to
626                // `v0`, which is a valid register group that is not read
627                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
628                    program_counter,
629                    config,
630                    vs2,
631                    sew.as_eew(),
632                )?;
633                let scalar = rs1_value.as_i64().cast_unsigned();
634                zvexx_perm_helpers::execute_merge_scalar(env, vd, vs2, vm, scalar);
635            }
636            // vmerge.vim / vmv.v.i
637            // When vm=true: vmv.v.i vd, simm5 - broadcast sign-extended immediate.
638            // When vm=false: vmerge.vim - vd[i] = v0[i] ? simm5 : vs2[i]
639            Self::VmergeVim { vd, vs2, simm5, vm } => {
640                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
641                    ::core::hint::cold_path();
642                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
643                        address: PackedAddress::new(
644                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
645                        ),
646                    });
647                }
648                let Some(config) = env.vector_config() else {
649                    ::core::hint::cold_path();
650                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
651                        address: PackedAddress::new(
652                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
653                        ),
654                    });
655                };
656                let sew = config.vtype().vsew();
657                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
658                    program_counter,
659                    config,
660                    vd,
661                    sew.as_eew(),
662                )?;
663                // Unmasked, this is `vmv.v.x`/`vmv.v.i`, for which the encoding fixes `vs2` to
664                // `v0`, which is a valid register group that is not read
665                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
666                    program_counter,
667                    config,
668                    vs2,
669                    sew.as_eew(),
670                )?;
671                let scalar = i64::from(simm5).cast_unsigned();
672                zvexx_perm_helpers::execute_merge_scalar(env, vd, vs2, vm, scalar);
673            }
674            // vcompress.vm vd, vs2, vs1
675            // Packs active elements of vs2 (where vs1 mask bit is set) sequentially into vd.
676            // Always unmasked (vm=1 in encoding); vs1 is the explicit mask operand.
677            // vd must not overlap vs1 or vs2.
678            Self::VcompressVm { vd, vs2, vs1 } => {
679                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
680                    ::core::hint::cold_path();
681                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
682                        address: PackedAddress::new(
683                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
684                        ),
685                    });
686                }
687                let Some(config) = env.vector_config() else {
688                    ::core::hint::cold_path();
689                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
690                        address: PackedAddress::new(
691                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
692                        ),
693                    });
694                };
695                let sew = config.vtype().vsew();
696                let vd = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
697                    program_counter,
698                    config,
699                    vd,
700                    sew.as_eew(),
701                )?;
702                let vs2 = zvexx_helpers::vreg_group::<Reg, Env, _, _>(
703                    program_counter,
704                    config,
705                    vs2,
706                    sew.as_eew(),
707                )?;
708                // vd must not overlap vs2
709                zvexx_helpers::check_groups_disjoint::<Reg, Env, _, _>(program_counter, vd, vs2)?;
710                // vs1 is a single mask register, which must not be one of the registers of vd
711                zvexx_helpers::check_register_outside_group::<Reg, Env, _, _>(
712                    program_counter,
713                    vd,
714                    vs1,
715                )?;
716                // `vs1` is a mask with EEW=1 and `vs2` has EEW=SEW
717                zvexx_helpers::check_register_outside_group::<Reg, Env, _, _>(
718                    program_counter,
719                    vs2,
720                    vs1,
721                )?;
722                zvexx_perm_helpers::execute_compress(env, vd, vs2, vs1);
723            }
724            // vmv1r.v vd, vs2
725            // Whole register move: copies 1 register.
726            // No masking and no vl dependency, but `EEW = min(VLEN * EMUL, SEW)` depends on vtype,
727            // so it is illegal with vill set.
728            Self::Vmv1rV { vd, vs2 } => {
729                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
730                    ::core::hint::cold_path();
731                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
732                        address: PackedAddress::new(
733                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
734                        ),
735                    });
736                }
737                if env.vector_config().is_none() {
738                    ::core::hint::cold_path();
739                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
740                        address: PackedAddress::new(
741                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
742                        ),
743                    });
744                }
745                if zvexx_perm_helpers::execute_whole_reg_move::<1, _>(env.write_vregs(), vd, vs2)
746                    .is_none()
747                {
748                    ::core::hint::cold_path();
749                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
750                        address: PackedAddress::new(
751                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
752                        ),
753                    });
754                }
755                env.mark_vs_dirty();
756            }
757            // vmv2r.v vd, vs2
758            // Whole register move: copies 2 registers.
759            // vd and vs2 must be aligned to 2 (checked here per spec §17.6).
760            Self::Vmv2rV { vd, vs2 } => {
761                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
762                    ::core::hint::cold_path();
763                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
764                        address: PackedAddress::new(
765                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
766                        ),
767                    });
768                }
769                if env.vector_config().is_none() {
770                    ::core::hint::cold_path();
771                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
772                        address: PackedAddress::new(
773                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
774                        ),
775                    });
776                }
777                if !vd.to_bits().is_multiple_of(2) || !vs2.to_bits().is_multiple_of(2) {
778                    ::core::hint::cold_path();
779                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
780                        address: PackedAddress::new(
781                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
782                        ),
783                    });
784                }
785                if zvexx_perm_helpers::execute_whole_reg_move::<2, _>(env.write_vregs(), vd, vs2)
786                    .is_none()
787                {
788                    ::core::hint::cold_path();
789                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
790                        address: PackedAddress::new(
791                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
792                        ),
793                    });
794                }
795                env.mark_vs_dirty();
796            }
797            // vmv4r.v vd, vs2
798            // Whole register move: copies 4 registers.
799            Self::Vmv4rV { vd, vs2 } => {
800                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
801                    ::core::hint::cold_path();
802                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
803                        address: PackedAddress::new(
804                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
805                        ),
806                    });
807                }
808                if env.vector_config().is_none() {
809                    ::core::hint::cold_path();
810                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
811                        address: PackedAddress::new(
812                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
813                        ),
814                    });
815                }
816                if !vd.to_bits().is_multiple_of(4) || !vs2.to_bits().is_multiple_of(4) {
817                    ::core::hint::cold_path();
818                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
819                        address: PackedAddress::new(
820                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
821                        ),
822                    });
823                }
824                if zvexx_perm_helpers::execute_whole_reg_move::<4, _>(env.write_vregs(), vd, vs2)
825                    .is_none()
826                {
827                    ::core::hint::cold_path();
828                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
829                        address: PackedAddress::new(
830                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
831                        ),
832                    });
833                }
834                env.mark_vs_dirty();
835            }
836            // vmv8r.v vd, vs2
837            // Whole register move: copies 8 registers.
838            Self::Vmv8rV { vd, vs2 } => {
839                if !zvexx_helpers::non_memory_instruction_allowed::<Reg, _>(env) {
840                    ::core::hint::cold_path();
841                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
842                        address: PackedAddress::new(
843                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
844                        ),
845                    });
846                }
847                if env.vector_config().is_none() {
848                    ::core::hint::cold_path();
849                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
850                        address: PackedAddress::new(
851                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
852                        ),
853                    });
854                }
855                if !vd.to_bits().is_multiple_of(8) || !vs2.to_bits().is_multiple_of(8) {
856                    ::core::hint::cold_path();
857                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
858                        address: PackedAddress::new(
859                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
860                        ),
861                    });
862                }
863                if zvexx_perm_helpers::execute_whole_reg_move::<8, _>(env.write_vregs(), vd, vs2)
864                    .is_none()
865                {
866                    ::core::hint::cold_path();
867                    return ExecutionResult::Err(ExecutionError::IllegalInstruction {
868                        address: PackedAddress::new(
869                            program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
870                        ),
871                    });
872                }
873                env.mark_vs_dirty();
874            }
875        }
876
877        ExecutionResult::ContinueNoWrite
878    }
879}