Skip to main content

ab_riscv_interpreter/v/zvexx/muldiv/
zvexx_muldiv_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::{VectorRegisterFile, VectorRegistersExt};
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::{
5    OpSrc, check_vreg_group_alignment, sew_mask, sign_extend,
6};
7use crate::v::zvexx::arith::zvexx_arith_helpers::{read_element_u64, write_element_u64};
8use crate::v::zvexx::fixed_point::zvexx_fixed_point_helpers::read_wide_element_u64;
9use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
10use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
11use crate::{ExecutionError, PackedAddress, ProgramCounter};
12use ab_riscv_primitives::prelude::*;
13use core::hint::cold_path;
14use core::num::NonZeroU8;
15
16/// Whether a widening operation is representable for the given `SEW` and `ELEN`.
17///
18/// Widening instructions produce a `2*SEW` result, and an EEW greater than `ELEN` is reserved by
19/// the RISC-V "V" spec §3.4.2 for *every* implementation. This is therefore not an extension-level
20/// restriction like the one on the high-half multiplies in Zve64x: no vector extension makes
21/// `SEW == ELEN` legal for a widening instruction.
22///
23/// This also underpins the safety preconditions of [`execute_widening_op()`],
24/// [`execute_widening_muladd_op()`] and [`execute_widening_muladd_scalar_op()`], because
25/// `ELEN <= 64` means `2*SEW <= 64` and the wide element fits in a `u64`.
26#[inline(always)]
27#[doc(hidden)]
28#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
29pub fn widening_eew_supported(sew: Vsew, elen: Elen) -> bool {
30    u32::from(sew.bits_width()) * 2 <= u32::from(elen)
31}
32
33/// Compute the destination register count for a widening operation (`EMUL = 2 × LMUL`).
34///
35/// Returns `None` when the resulting EMUL falls outside the legal range `[1/8, 8]`, i.e. when
36/// `LMUL` is already `M8` (EMUL would be 16) or the caller asks for a multiplication factor that
37/// pushes the fraction past the legal lower bound.
38///
39/// The register count returned is `max(1, EMUL)`: fractional EMUL values (1/2, 1/4) still occupy
40/// exactly one physical register.
41#[inline(always)]
42#[doc(hidden)]
43#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
44pub fn widening_dest_register_count(vlmul: Vlmul) -> Option<NonZeroU8> {
45    let (lmul_num, lmul_den) = vlmul.as_fraction();
46    // EMUL = 2 × LMUL = (2 * lmul_num) / lmul_den
47    let Some(emul_num) = 2u8.checked_mul(lmul_num.get()) else {
48        cold_path();
49        return None;
50    };
51    let emul_den = lmul_den.get();
52    // Reduce the fraction by GCD (both are powers of two so min works as GCD)
53    let g = emul_num.min(emul_den);
54    let (n, d) = (emul_num / g, emul_den / g);
55    // Legal EMUL fractions: 1/8, 1/4, 1/2, 1, 2, 4, 8
56    #[expect(clippy::unnested_or_patterns, reason = "Readability")]
57    let legal = matches!(
58        (n, d),
59        (1, 8) | (1, 4) | (1, 2) | (1, 1) | (2, 1) | (4, 1) | (8, 1)
60    );
61    if !legal {
62        cold_path();
63        return None;
64    }
65    // Register count: max(1, n/d) = n when d==1, else 1
66    Some(NonZeroU8::new(if d > 1 { 1 } else { n }).expect("Not zero; qed"))
67}
68
69/// Check that a narrower source register group does not *illegally* overlap the wider destination
70/// group of a widening instruction.
71///
72/// For widening instructions `vd` occupies `dest_group_regs` registers (which is
73/// [`widening_dest_register_count()`] of the source LMUL); `vs` occupies `src_group_regs`.
74///
75/// Per the RISC-V "V" spec §5.2, because the destination EEW (`2*SEW`) is greater than the source
76/// EEW (`SEW`), the source group *may* overlap the destination group, but only when both of the
77/// following hold:
78/// - the source EMUL is at least 1, and
79/// - the overlap is in the highest-numbered part of the destination register group, i.e. the source
80///   occupies exactly the top `src_group_regs` registers of the destination group.
81///
82/// When the source EMUL is at least 1, `dest_group_regs == 2 * src_group_regs`, so
83/// `dest_group_regs > src_group_regs` is an equivalent test for "source EMUL >= 1": a fractional
84/// source EMUL (`< 1`) yields `dest_group_regs == src_group_regs == 1`, in which case no overlap is
85/// ever legal. Any overlap that is not the legal "source in the highest-numbered part" form is
86/// rejected as an illegal instruction.
87#[inline(always)]
88#[doc(hidden)]
89#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
90pub fn check_no_widening_overlap<Reg, Memory, PC>(
91    program_counter: &PC,
92    vd: VReg,
93    vs: VReg,
94    dest_group_regs: NonZeroU8,
95    src_group_regs: NonZeroU8,
96) -> Result<(), ExecutionError<Reg::Type>>
97where
98    Reg: Register,
99    PC: ProgramCounter<Reg::Type, Memory>,
100{
101    let dest_group_regs = dest_group_regs.get();
102    let src_group_regs = src_group_regs.get();
103    let vd_start = vd.to_bits();
104    let vd_end = vd_start + dest_group_regs;
105    let vs_start = vs.to_bits();
106    let vs_end = vs_start + src_group_regs;
107    // Disjoint register groups are always fine
108    if vs_start >= vd_end || vd_start >= vs_end {
109        return Ok(());
110    }
111    // The groups overlap. This is legal only when the source EMUL is at least 1
112    // (`dest_group_regs > src_group_regs`) and the source occupies exactly the highest-numbered
113    // part of the destination group (`vs_start == vd_end - src_group_regs`).
114    if dest_group_regs > src_group_regs && vs_start == vd_end - src_group_regs {
115        return Ok(());
116    }
117
118    cold_path();
119    Err(ExecutionError::IllegalInstruction {
120        address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
121    })
122}
123
124/// Write a 2*SEW-wide element into the widened destination register group at element index
125/// `elem_i`.
126///
127/// # Safety
128/// - `base_reg + elem_i / (VLEN.bytes() / (2*sew_bytes)) < 32` must hold.
129/// - `2 * sew.bits_width() <= 64` must hold, so that the wide element fits in a `u64` and
130///   `wide_bytes <= 8`. Callers establish this via
131///   [`widening_eew_supported()`][crate::v::zvexx::muldiv::zvexx_muldiv_helpers::widening_eew_supported],
132///   since `2*SEW <= ELEN` and `ELEN <= 64` for every supported configuration.
133#[inline(always)]
134#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
135unsafe fn write_wide_element_u64<const VLEN: Vlen>(
136    vregs: &mut VectorRegisterFile<VLEN>,
137    base_reg: VReg,
138    elem_i: u16,
139    sew: Vsew,
140    value: u64,
141) {
142    let wide_bytes = u32::from(sew.bytes_width()) * 2;
143    let elems_per_reg = VLEN.bytes() / wide_bytes;
144    let reg_off = u32::from(elem_i) / elems_per_reg;
145    let byte_off = (u32::from(elem_i) % elems_per_reg) * wide_bytes;
146    let buf = value.to_le_bytes();
147    // SAFETY: `base_reg + reg_off < 32` by caller's precondition
148    let reg = unsafe {
149        vregs.get_mut(VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked())
150    };
151    // SAFETY: `byte_off + wide_bytes <= VLEN.bytes()`; `wide_bytes <= 8` by caller's precondition
152    let dst = unsafe { reg.get_unchecked_mut(byte_off as usize..(byte_off + wide_bytes) as usize) };
153    // SAFETY: `wide_bytes <= 8` because `2*SEW <= ELEN <= 64` is enforced before widening ops are
154    // called
155    dst.copy_from_slice(unsafe { buf.get_unchecked(..wide_bytes as usize) });
156}
157
158/// Execute a single-width element-wise arithmetic operation over `vstart..vl`.
159///
160/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew)` and returns the `u64` result.
161/// Only the low `sew.bytes()` of the result are written back.
162///
163/// # Safety
164/// - `vd` and source register alignment verified by caller
165/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
166/// - When `vm=false`: `vd.to_bits() != 0`
167#[inline(always)]
168#[doc(hidden)]
169#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
170pub unsafe fn execute_arith_op<Reg, Env, F>(
171    env: &mut Env,
172    vd: VReg,
173    vs2: VReg,
174    src: OpSrc,
175    vm: bool,
176    sew: Vsew,
177    op: F,
178) where
179    Reg: Register,
180    Env: VectorRegistersExt<Reg>,
181    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
182    F: Fn(u64, u64, Vsew) -> u64,
183{
184    let vl = env.vl();
185    let vstart = env.vstart();
186    // SAFETY: `vl <= VLMAX <= VLEN`
187    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
188    for i in vstart.range_to(vl) {
189        if !mask_bit(&mask_buf, i) {
190            continue;
191        }
192        // SAFETY: register bounds verified by caller
193        let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
194        let b = match src {
195            // SAFETY: register bounds verified by caller
196            OpSrc::Vreg(vs1_base) => unsafe {
197                read_element_u64(env.read_vregs(), vs1_base, i, sew)
198            },
199            OpSrc::Scalar(val) => val,
200        };
201        let result = op(a, b, sew);
202        // SAFETY: register bounds verified by caller
203        unsafe {
204            write_element_u64(env.write_vregs(), vd, i, sew, result);
205        }
206    }
207    env.mark_vs_dirty();
208    env.reset_vstart();
209}
210
211/// Execute a single-width widening operation over `vstart..vl`.
212///
213/// Reads SEW-wide elements from `vs2` and `src`, computes `op`, and writes a 2*SEW-wide result
214/// into `vd`.
215///
216/// # Safety
217/// - `vd` uses `dest_group_regs` registers (result of `widening_dest_register_count()`); alignment
218///   and non-overlap verified by caller
219/// - `vl <= src_group_regs * VLEN.bytes() / sew_bytes`
220/// - `2*SEW <= ELEN` verified by caller via [`widening_eew_supported()`], so the 2*SEW result fits
221///   in a `u64`; this holds for every implementation because an EEW may not exceed ELEN
222/// - When `vm=false`: `vd.to_bits() != 0`
223#[inline(always)]
224#[doc(hidden)]
225#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
226pub unsafe fn execute_widening_op<Reg, Env, F>(
227    env: &mut Env,
228    vd: VReg,
229    vs2: VReg,
230    src: OpSrc,
231    vm: bool,
232    sew: Vsew,
233    op: F,
234) where
235    Reg: Register,
236    Env: VectorRegistersExt<Reg>,
237    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
238    F: Fn(u64, u64, Vsew) -> u64,
239{
240    let vl = env.vl();
241    let vstart = env.vstart();
242    // SAFETY: `vl <= VLMAX <= VLEN`
243    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
244    for i in vstart.range_to(vl) {
245        if !mask_bit(&mask_buf, i) {
246            continue;
247        }
248        // SAFETY: register bounds verified by caller
249        let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
250        let b = match src {
251            // SAFETY: register bounds verified by caller
252            OpSrc::Vreg(vs1_base) => unsafe {
253                read_element_u64(env.read_vregs(), vs1_base, i, sew)
254            },
255            OpSrc::Scalar(val) => val,
256        };
257        let result = op(a, b, sew);
258        // SAFETY: vd has dest_group_regs registers; element `i` fits within them because
259        // `vl <= src_group_regs * VLEN.bytes() / sew_bytes` and dest stores at 2*SEW width so
260        // `i < dest_group_regs * VLEN.bytes() / (2*sew_bytes)`; `2*SEW <= ELEN <= 64` by caller
261        unsafe {
262            write_wide_element_u64(env.write_vregs(), vd, i, sew, result);
263        }
264    }
265    env.mark_vs_dirty();
266    env.reset_vstart();
267}
268
269/// Execute a single-width multiply-add where the first multiplier is a vector register group.
270///
271/// `op` receives `(acc: u64, a: u64, b: u64, sew: Vsew)` where `acc` is the current `vd[i]`,
272/// `a` is the element from `a_reg`, and `b` is the element from `src`. Returns the new `vd[i]`.
273///
274/// # Safety
275/// - `vd`, `a_reg`, and `src` register alignment verified by caller
276/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
277/// - When `vm=false`: `vd.to_bits() != 0`
278#[inline(always)]
279#[doc(hidden)]
280#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
281pub unsafe fn execute_muladd_op<Reg, Env, F>(
282    env: &mut Env,
283    vd: VReg,
284    a_reg: VReg,
285    src: OpSrc,
286    vm: bool,
287    sew: Vsew,
288    op: F,
289) where
290    Reg: Register,
291    Env: VectorRegistersExt<Reg>,
292    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
293    F: Fn(u64, u64, u64, Vsew) -> u64,
294{
295    let vl = env.vl();
296    let vstart = env.vstart();
297    // SAFETY: `vl <= VLMAX <= VLEN`
298    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
299    for i in vstart.range_to(vl) {
300        if !mask_bit(&mask_buf, i) {
301            continue;
302        }
303        // SAFETY: register bounds verified by caller
304        let acc = unsafe { read_element_u64(env.read_vregs(), vd, i, sew) };
305        // SAFETY: register bounds verified by caller
306        let a = unsafe { read_element_u64(env.read_vregs(), a_reg, i, sew) };
307        let b = match src {
308            // SAFETY: register bounds verified by caller
309            OpSrc::Vreg(b_reg) => unsafe { read_element_u64(env.read_vregs(), b_reg, i, sew) },
310            OpSrc::Scalar(val) => val,
311        };
312        let result = op(acc, a, b, sew);
313        // SAFETY: register bounds verified by caller
314        unsafe {
315            write_element_u64(env.write_vregs(), vd, i, sew, result);
316        }
317    }
318    env.mark_vs_dirty();
319    env.reset_vstart();
320}
321
322/// Execute a single-width multiply-add where the first multiplier is a scalar.
323///
324/// Analogous to [`execute_muladd_op`] but `a` is a fixed scalar instead of a register element.
325///
326/// # Safety
327/// Same as [`execute_muladd_op`], minus constraints on `a_reg`.
328#[inline(always)]
329#[doc(hidden)]
330#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
331pub unsafe fn execute_muladd_scalar_op<Reg, Env, F>(
332    env: &mut Env,
333    vd: VReg,
334    scalar: u64,
335    src: OpSrc,
336    vm: bool,
337    sew: Vsew,
338    op: F,
339) where
340    Reg: Register,
341    Env: VectorRegistersExt<Reg>,
342    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
343    F: Fn(u64, u64, u64, Vsew) -> u64,
344{
345    let vl = env.vl();
346    let vstart = env.vstart();
347    // SAFETY: `vl <= VLMAX <= VLEN`
348    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
349    for i in vstart.range_to(vl) {
350        if !mask_bit(&mask_buf, i) {
351            continue;
352        }
353        // SAFETY: register bounds verified by caller
354        let acc = unsafe { read_element_u64(env.read_vregs(), vd, i, sew) };
355        let b = match src {
356            // SAFETY: register bounds verified by caller
357            OpSrc::Vreg(b_reg) => unsafe { read_element_u64(env.read_vregs(), b_reg, i, sew) },
358            OpSrc::Scalar(val) => val,
359        };
360        let result = op(acc, scalar, b, sew);
361        // SAFETY: register bounds verified by caller
362        unsafe {
363            write_element_u64(env.write_vregs(), vd, i, sew, result);
364        }
365    }
366    env.mark_vs_dirty();
367    env.reset_vstart();
368}
369
370/// Execute a widening multiply-add where the first multiplier is a vector register group.
371///
372/// Reads SEW-wide `acc` from the widened `vd` group, SEW-wide `a` from `a_reg`, and SEW-wide
373/// `b` from `src`. Writes a 2*SEW-wide result back into `vd`.
374///
375/// `op` receives `(acc: u64, a: u64, b: u64, sew: Vsew)`.
376///
377/// # Safety
378/// - `vd` uses `dest_group_regs` registers (result of `widening_dest_register_count()`); alignment
379///   and non-overlap verified by caller
380/// - `2*SEW <= ELEN` verified by caller via [`widening_eew_supported()`], so both the accumulator
381///   and the result fit in a `u64`
382/// - When `vm=false`: `vd.to_bits() != 0`
383#[inline(always)]
384#[doc(hidden)]
385#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
386pub unsafe fn execute_widening_muladd_op<Reg, Env, F>(
387    env: &mut Env,
388    vd: VReg,
389    a_reg: VReg,
390    src: OpSrc,
391    vm: bool,
392    sew: Vsew,
393    op: F,
394) where
395    Reg: Register,
396    Env: VectorRegistersExt<Reg>,
397    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
398    F: Fn(u64, u64, u64, Vsew) -> u64,
399{
400    let vl = env.vl();
401    let vstart = env.vstart();
402    // SAFETY: `vl <= VLMAX <= VLEN`
403    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
404    for i in vstart.range_to(vl) {
405        if !mask_bit(&mask_buf, i) {
406            continue;
407        }
408        // Read the existing 2*SEW accumulator from vd
409        // SAFETY: vd has dest_group_regs registers; element `i` fits within them (see
410        // `execute_widening_op` for the bound argument); `2*SEW <= ELEN <= 64` by caller
411        let acc = unsafe { read_wide_element_u64(env.read_vregs(), vd, i, sew) };
412        // SAFETY: register bounds verified by caller
413        let a = unsafe { read_element_u64(env.read_vregs(), a_reg, i, sew) };
414        let b = match src {
415            // SAFETY: register bounds verified by caller
416            OpSrc::Vreg(b_reg) => unsafe { read_element_u64(env.read_vregs(), b_reg, i, sew) },
417            OpSrc::Scalar(val) => val,
418        };
419        let result = op(acc, a, b, sew);
420        // SAFETY: same as acc read above
421        unsafe {
422            write_wide_element_u64(env.write_vregs(), vd, i, sew, result);
423        }
424    }
425    env.mark_vs_dirty();
426    env.reset_vstart();
427}
428
429/// Execute a widening multiply-add where the first multiplier is a scalar.
430///
431/// Analogous to [`execute_widening_muladd_op`] but `a` is a fixed scalar.
432///
433/// # Safety
434/// Same as [`execute_widening_muladd_op`], minus constraints on `a_reg`.
435#[inline(always)]
436#[doc(hidden)]
437#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
438pub unsafe fn execute_widening_muladd_scalar_op<Reg, Env, F>(
439    env: &mut Env,
440    vd: VReg,
441    scalar: u64,
442    src: OpSrc,
443    vm: bool,
444    sew: Vsew,
445    op: F,
446) where
447    Reg: Register,
448    Env: VectorRegistersExt<Reg>,
449    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
450    F: Fn(u64, u64, u64, Vsew) -> u64,
451{
452    let vl = env.vl();
453    let vstart = env.vstart();
454    // SAFETY: `vl <= VLMAX <= VLEN`
455    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
456    for i in vstart.range_to(vl) {
457        if !mask_bit(&mask_buf, i) {
458            continue;
459        }
460        // SAFETY: vd has dest_group_regs registers; element `i` fits within them (see
461        // `execute_widening_op` for the bound argument); `2*SEW <= ELEN <= 64` by caller
462        let acc = unsafe { read_wide_element_u64(env.read_vregs(), vd, i, sew) };
463        let b = match src {
464            // SAFETY: register bounds verified by caller
465            OpSrc::Vreg(b_reg) => unsafe { read_element_u64(env.read_vregs(), b_reg, i, sew) },
466            OpSrc::Scalar(val) => val,
467        };
468        let result = op(acc, scalar, b, sew);
469        // SAFETY: same as acc read above
470        unsafe {
471            write_wide_element_u64(env.write_vregs(), vd, i, sew, result);
472        }
473    }
474    env.mark_vs_dirty();
475    env.reset_vstart();
476}
477
478/// Signed × signed high half.
479///
480/// Both operands are sign-extended to i64, multiplied as i128, and the upper SEW bits of the
481/// 2*SEW product are returned (zero-extended to u64 for writeback into a SEW-wide element slot).
482///
483/// Valid for every SEW including 64, because the intermediate product is formed in i128. Whether
484/// SEW=64 is reachable at all is an extension-level decision made by the caller (Zve64x excludes
485/// it, the full "V" extension does not).
486#[inline(always)]
487#[doc(hidden)]
488#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
489pub fn mulh_ss(a: u64, b: u64, sew: Vsew) -> u64 {
490    let sa = sign_extend(a, sew);
491    let sb = sign_extend(b, sew);
492    let product = sa.widening_mul(sb);
493    // Extract bits [2*SEW-1 : SEW] of the product
494    let high = (product >> sew.bits_width()).cast_unsigned() as u64;
495    high & sew_mask(sew)
496}
497
498/// Unsigned × unsigned high half.
499///
500/// Valid for every SEW including 64; see [`mulh_ss()`] for the extension-level caveat.
501#[inline(always)]
502#[doc(hidden)]
503#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
504pub fn mulhu_uu(a: u64, b: u64, sew: Vsew) -> u64 {
505    let ua = a & sew_mask(sew);
506    let ub = b & sew_mask(sew);
507    let product = ua.widening_mul(ub);
508    let high = (product >> sew.bits_width()) as u64;
509    high & sew_mask(sew)
510}
511
512/// Signed × unsigned high half.
513///
514/// `a` (vs2) is the signed operand; `b` (vs1/rs1) is the unsigned operand.
515///
516/// Valid for every SEW including 64; see [`mulh_ss()`] for the extension-level caveat. Note that
517/// the unsigned operand is widened to i128 rather than i64, so a full-width unsigned value at
518/// SEW=64 keeps its magnitude instead of being reinterpreted as negative.
519#[inline(always)]
520#[doc(hidden)]
521#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
522pub fn mulhsu_su(a: u64, b: u64, sew: Vsew) -> u64 {
523    let sa = i128::from(sign_extend(a, sew));
524    let ub = i128::from(b & sew_mask(sew));
525    let product = sa.wrapping_mul(ub);
526    let high = (product >> sew.bits_width()).cast_unsigned() as u64;
527    high & sew_mask(sew)
528}
529
530/// Signed divide with division-by-zero and signed-overflow semantics from the RISC-V V spec §12.11.
531///
532/// - Division by zero: result = all-ones (i.e., −1 as signed SEW-wide integer)
533/// - Signed overflow (MIN / −1): result = MIN (i.e., `1 << (SEW-1)`)
534#[inline(always)]
535#[doc(hidden)]
536#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
537pub fn sdiv(a: u64, b: u64, sew: Vsew) -> u64 {
538    let sa = sign_extend(a, sew);
539    let sb = sign_extend(b, sew);
540    // Division by zero: return all-ones in the SEW-wide slot (= −1 signed)
541    if sb == 0 {
542        return sew_mask(sew);
543    }
544    sa.wrapping_div(sb).cast_unsigned() & sew_mask(sew)
545}
546
547/// Signed remainder with division-by-zero and signed-overflow semantics from the RISC-V V spec
548/// §12.11.
549///
550/// - Division by zero: remainder = dividend
551/// - Signed overflow (MIN % −1): remainder = 0
552#[inline(always)]
553#[doc(hidden)]
554#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
555pub fn srem(a: u64, b: u64, sew: Vsew) -> u64 {
556    let sa = sign_extend(a, sew);
557    let sb = sign_extend(b, sew);
558    // Division by zero: remainder = dividend
559    if sb == 0 {
560        return a & sew_mask(sew);
561    }
562    sa.wrapping_rem(sb).cast_unsigned() & sew_mask(sew)
563}