Skip to main content

ab_riscv_interpreter/v/zvexx/widen_narrow/
zvexx_widen_narrow_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::{VRegGroup, VectorRegistersExt};
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::OpSrc;
5use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
6use crate::v::zvexx::zvexx_helpers::{ExtensionSew, WideningSew};
7use ab_riscv_primitives::instructions::v::Vsew;
8use ab_riscv_primitives::prelude::*;
9use core::hint::cold_path;
10
11/// Sign-extend the low `sew.bits_width()` of `val` to `i64`.
12#[inline(always)]
13#[doc(hidden)]
14#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
15pub fn sign_extend_bits(val: u64, sew: Vsew) -> i64 {
16    let shift = u64::BITS - u32::from(sew.bits_width());
17    (val.cast_signed() << shift) >> shift
18}
19
20/// Interpret a scalar operand as an unsigned SEW-wide value.
21///
22/// RVV widening scalar instructions (.vx/.wx) conceptually use a scalar
23/// operand whose width matches the current SEW, not the full XLEN width.
24///
25/// For example on RV64:
26///
27/// SEW=8:
28///     val = 0x0000_0000_0000_01ff
29///     result = 0x0000_0000_0000_00ff
30///
31/// SEW=16:
32///     val = 0x0000_0000_0000_01ff
33///     result = 0x0000_0000_0000_01ff
34///
35/// SEW=32:
36///     val = 0xffff_ffff_1234_5678
37///     result = 0x0000_0000_1234_5678
38///
39/// This helper performs that SEW-width truncation without sign extension.
40#[inline(always)]
41#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
42fn scalar_unsigned_for_sew(val: u64, sew: Vsew) -> u64 {
43    val & (u64::MAX >> (u64::BITS - u32::from(sew.bits_width())))
44}
45
46/// Interpret a scalar operand as a signed SEW-wide value.
47///
48/// The scalar is first truncated to SEW bits, then sign-extended back to
49/// 64 bits.
50///
51/// For example on RV64:
52///
53/// SEW=8:
54///     val = 0x0000_0000_0000_00ff
55///     result = 0xffff_ffff_ffff_ffff (-1)
56///
57/// SEW=8:
58///     val = 0x0000_0000_0000_007f
59///     result = 0x0000_0000_0000_007f (+127)
60///
61/// SEW=16:
62///     val = 0x0000_0000_0000_ffff
63///     result = 0xffff_ffff_ffff_ffff (-1)
64///
65/// This matches the signed widening behavior required by instructions such
66/// as vwadd.vx and vwsub.vx.
67#[inline(always)]
68#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
69fn scalar_signed_for_sew(val: u64, sew: Vsew) -> u64 {
70    sign_extend_bits(val, sew).cast_unsigned()
71}
72
73/// Execute a widening integer add/subtract.
74///
75/// Each source element is SEW-wide; the destination element is 2×SEW-wide.
76/// `ZERO_EXTEND_AB` selects unsigned or signed widening for sources (unsigned = zero-extend,
77/// signed = sign-extend).
78///
79/// `op` receives `(wide_a: u64, wide_b: u64) -> u64`.
80///
81/// All register groups must have the same `vl`, which holds for groups created from the same
82/// configuration, nothing is written otherwise.
83#[inline(always)]
84#[doc(hidden)]
85#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
86pub fn execute_widen_op<const ZERO_EXTEND_AB: bool, Reg, Env, F>(
87    env: &mut Env,
88    vd: VRegGroup<Env::Hart>,
89    vs2: VRegGroup<Env::Hart>,
90    src: OpSrc<VRegGroup<Env::Hart>>,
91    vm: bool,
92    sew: WideningSew<Env::Hart>,
93    op: F,
94) where
95    Reg: Register,
96    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
97    F: Fn(u64, u64) -> u64,
98{
99    let vl = vd.vl();
100    let (Some(vs2), Some(src)) = (vs2.with_same_vl(vl), src.with_same_vl(vl)) else {
101        cold_path();
102        return;
103    };
104    let sew = sew.narrow();
105
106    let mask_buf = snapshot_mask(env.read_vregs(), vm);
107
108    for i in vl.indices() {
109        if !mask_bit(&mask_buf, i) {
110            continue;
111        }
112        let raw_a = env
113            .read_vregs()
114            .read(vs2, i)
115            .expect("`vs2` has the same `vl` as `vd`, checked above; qed");
116        let wide_a = if ZERO_EXTEND_AB {
117            raw_a
118        } else {
119            sign_extend_bits(raw_a, sew).cast_unsigned()
120        };
121        let wide_b = match src.vreg {
122            Some(vs1) => {
123                let raw_b = env
124                    .read_vregs()
125                    .read(vs1, i)
126                    .expect("`vs1` has the same `vl` as `vd`, checked above; qed");
127                if ZERO_EXTEND_AB {
128                    raw_b
129                } else {
130                    sign_extend_bits(raw_b, sew).cast_unsigned()
131                }
132            }
133            None => {
134                if ZERO_EXTEND_AB {
135                    scalar_unsigned_for_sew(src.scalar, sew)
136                } else {
137                    scalar_signed_for_sew(src.scalar, sew)
138                }
139            }
140        };
141        let result = op(wide_a, wide_b);
142        env.write_vregs()
143            .write(vd, i, result)
144            .expect("`i < vl` of `vd`; qed");
145    }
146    env.mark_vs_dirty();
147}
148
149/// Execute a widening add/subtract where `vs2` is already 2×SEW wide.
150///
151/// `vs2` is read at `wide_sew.bytes_width()`; `src` (narrow) is read at `sew.bytes_width()` and
152/// widened. `ZERO_EXTEND_B` selects unsigned vs signed widening for the narrow source operand.
153///
154/// All register groups must have the same `vl`, which holds for groups created from the same
155/// configuration, nothing is written otherwise.
156#[inline(always)]
157#[doc(hidden)]
158#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
159pub fn execute_widen_w_op<const ZERO_EXTEND_B: bool, Reg, Env, F>(
160    env: &mut Env,
161    vd: VRegGroup<Env::Hart>,
162    vs2: VRegGroup<Env::Hart>,
163    src: OpSrc<VRegGroup<Env::Hart>>,
164    vm: bool,
165    sew: WideningSew<Env::Hart>,
166    op: F,
167) where
168    Reg: Register,
169    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
170    F: Fn(u64, u64) -> u64,
171{
172    let vl = vd.vl();
173    let (Some(vs2), Some(src)) = (vs2.with_same_vl(vl), src.with_same_vl(vl)) else {
174        cold_path();
175        return;
176    };
177    let sew = sew.narrow();
178
179    let mask_buf = snapshot_mask(env.read_vregs(), vm);
180
181    for i in vl.indices() {
182        if !mask_bit(&mask_buf, i) {
183            continue;
184        }
185        // vs2 is already 2×SEW; read at wide width
186        let wide_a = env
187            .read_vregs()
188            .read(vs2, i)
189            .expect("`vs2` has the same `vl` as `vd`, checked above; qed");
190        let wide_b = match src.vreg {
191            Some(vs1) => {
192                let raw_b = env
193                    .read_vregs()
194                    .read(vs1, i)
195                    .expect("`vs1` has the same `vl` as `vd`, checked above; qed");
196                if ZERO_EXTEND_B {
197                    raw_b
198                } else {
199                    sign_extend_bits(raw_b, sew).cast_unsigned()
200                }
201            }
202            None => {
203                if ZERO_EXTEND_B {
204                    scalar_unsigned_for_sew(src.scalar, sew)
205                } else {
206                    scalar_signed_for_sew(src.scalar, sew)
207                }
208            }
209        };
210        let result = op(wide_a, wide_b);
211        env.write_vregs()
212            .write(vd, i, result)
213            .expect("`i < vl` of `vd`; qed");
214    }
215    env.mark_vs_dirty();
216}
217
218/// Execute a narrowing right-shift.
219///
220/// `vs2` is 2×SEW wide; the shift amount comes from `src` (SEW-wide or scalar).
221/// The shift amount is masked to `log2(2*SEW)` bits per spec §12.6.
222/// `ARITHMETIC` selects sign-extending (true) vs zero-extending (false) before shifting.
223///
224/// All register groups must have the same `vl`, which holds for groups created from the same
225/// configuration, nothing is written otherwise.
226#[inline(always)]
227#[doc(hidden)]
228#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
229pub fn execute_narrow_shift<const ARITHMETIC: bool, Reg, Env>(
230    env: &mut Env,
231    vd: VRegGroup<Env::Hart>,
232    vs2: VRegGroup<Env::Hart>,
233    src: OpSrc<VRegGroup<Env::Hart>>,
234    vm: bool,
235    sew: WideningSew<Env::Hart>,
236) where
237    Reg: Register,
238    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
239{
240    let vl = vd.vl();
241    let (Some(vs2), Some(src)) = (vs2.with_same_vl(vl), src.with_same_vl(vl)) else {
242        cold_path();
243        return;
244    };
245    let wide_sew = sew.wide();
246    let sew = sew.narrow();
247    // Shift amount mask: log2(2*SEW) bits = log2(SEW) + 1 bits
248    let shamt_mask = u64::from(wide_sew.bits_width() - 1);
249
250    let mask_buf = snapshot_mask(env.read_vregs(), vm);
251
252    for i in vl.indices() {
253        if !mask_bit(&mask_buf, i) {
254            continue;
255        }
256        let wide_val = env
257            .read_vregs()
258            .read(vs2, i)
259            .expect("`vs2` has the same `vl` as `vd`, checked above; qed");
260        let shamt = match src.vreg {
261            Some(vs1) => {
262                let raw = env
263                    .read_vregs()
264                    .read(vs1, i)
265                    .expect("`vs1` has the same `vl` as `vd`, checked above; qed");
266                raw & shamt_mask
267            }
268            // Scalar shift amount: only the low log2(2*SEW) bits are used per spec
269            None => src.scalar & shamt_mask,
270        };
271        let result_wide = if ARITHMETIC {
272            // Sign-extend to i64 first, then shift arithmetically as i64 to
273            // preserve sign bits, then cast back. Shifting u64 after cast_unsigned()
274            // would be a logical shift and lose sign bits.
275            (sign_extend_bits(wide_val, wide_sew) >> shamt).cast_unsigned()
276        } else {
277            wide_val >> shamt
278        };
279        // Truncate to SEW bits
280        let result = result_wide & ((1u64 << sew.bits_width()) - 1);
281        env.write_vregs()
282            .write(vd, i, result)
283            .expect("`i < vl` of `vd`; qed");
284    }
285    env.mark_vs_dirty();
286}
287
288/// Execute an integer extension (vzext/vsext).
289///
290/// Source element width is `sew.source()`, destination is `sew.dest()`. `SIGN` selects sign- or
291/// zero-extension.
292///
293/// The source EMUL = LMUL / factor; the source register group is `max(1, group_regs / factor)`
294/// registers.
295///
296/// All register groups must have the same `vl`, which holds for groups created from the same
297/// configuration, nothing is written otherwise.
298#[inline(always)]
299#[doc(hidden)]
300#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
301pub fn execute_extension<const SIGN: bool, Reg, Env>(
302    env: &mut Env,
303    vd: VRegGroup<Env::Hart>,
304    vs2: VRegGroup<Env::Hart>,
305    vm: bool,
306    sew: ExtensionSew,
307) where
308    Reg: Register,
309    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
310{
311    let vl = vd.vl();
312    let Some(vs2) = vs2.with_same_vl(vl) else {
313        cold_path();
314        return;
315    };
316    let src_sew = sew.source();
317
318    let mask_buf = snapshot_mask(env.read_vregs(), vm);
319
320    for i in vl.indices() {
321        if !mask_bit(&mask_buf, i) {
322            continue;
323        }
324        let raw = env
325            .read_vregs()
326            .read(vs2, i)
327            .expect("`vs2` has the same `vl` as `vd`, checked above; qed");
328        let result = if SIGN {
329            sign_extend_bits(raw, src_sew).cast_unsigned()
330        } else {
331            raw
332        };
333        env.write_vregs()
334            .write(vd, i, result)
335            .expect("`i < vl` of `vd`; qed");
336    }
337    env.mark_vs_dirty();
338}