Skip to main content

ab_riscv_interpreter/v/zvexx/carry/
zvexx_carry_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_mask_dest_overlap, check_vreg_group_alignment,
6};
7use crate::v::zvexx::arith::zvexx_arith_helpers::{sew_mask, write_mask_bit};
8use crate::v::zvexx::load::zvexx_load_helpers::mask_bit;
9use ab_riscv_primitives::prelude::*;
10
11// TODO: Safety comment here doesn't make sense
12/// Read a single mask bit from vector register `v0` at element index `i`.
13///
14/// Used to retrieve the per-element carry-in or borrow-in for vadc/vsbc.
15///
16/// # Safety
17/// `i / 8 < VLEN.bytes()` must hold, guaranteed when `i < vl <= VLEN`.
18#[inline(always)]
19#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
20pub(in super::super) unsafe fn carry_bit<const VLEN: Vlen>(
21    vregs: &VectorRegisterFile<VLEN>,
22    i: u16,
23) -> u64 {
24    let v0 = vregs.get(VReg::V0);
25    u64::from(mask_bit(v0, i))
26}
27
28/// Execute an element-wise add-with-carry over `vstart..vl`, writing SEW-wide data results into
29/// `vd`.
30///
31/// Carry-in for each element is read from `v0[i]` when `WITH_CARRY` is true. All elements in
32/// `vstart..vl` are processed unconditionally (no execution mask).
33///
34/// # Safety
35/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32`
36/// - `vs2.to_bits() % group_regs == 0` and `vs2.to_bits() + group_regs <= 32`
37/// - `src` register satisfies the same alignment (verified by caller)
38/// - `vd.to_bits() != 0` (vd must not overlap v0, which holds the carry-in)
39/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
40#[inline(always)]
41#[doc(hidden)]
42#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
43pub unsafe fn execute_carry_add<const WITH_CARRY: bool, Reg, Env>(
44    env: &mut Env,
45    vd: VReg,
46    vs2: VReg,
47    src: OpSrc,
48    sew: Vsew,
49) where
50    Reg: Register,
51    Env: VectorRegistersExt<Reg>,
52    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
53{
54    let vl = env.vl();
55    let vstart = env.vstart();
56    for i in vstart.range_to(vl) {
57        // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32` (caller precondition);
58        // `i < vl <= group_regs * elems_per_reg`, so
59        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
60        let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
61        let b = match src {
62            OpSrc::Vreg(vs1_base) => {
63                // SAFETY: caller verified that the vs1 register group satisfies the same alignment
64                // constraint as vs2; the index argument is identical, so the same bound holds:
65                // `vs1_base + i / elems_per_reg < 32`
66                unsafe { env.read_vregs().read_element(vs1_base, i, sew) }
67            }
68            OpSrc::Scalar(val) => val,
69        };
70        let c = if WITH_CARRY {
71            // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
72            unsafe { carry_bit(env.read_vregs(), i) }
73        } else {
74            0
75        };
76
77        // Wrap naturally: write_element_u64 writes only the low sew_bytes
78        let result = a.wrapping_add(b).wrapping_add(c);
79        // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32` (caller precondition);
80        // `i < vl <= group_regs * elems_per_reg`, so
81        // `vd + i / elems_per_reg < vd + group_regs <= 32`
82        unsafe {
83            env.write_vregs().write_element(vd, i, sew, result);
84        }
85    }
86
87    env.mark_vs_dirty();
88    env.reset_vstart();
89}
90
91/// Execute an element-wise subtract-with-borrow over `vstart..vl`, writing SEW-wide data results
92/// into `vd`.
93///
94/// Borrow-in for each element is read from `v0[i]` (always true for vsbc). All elements in
95/// `vstart..vl` are processed unconditionally.
96///
97/// # Safety
98/// Same as [`execute_carry_add()`].
99#[inline(always)]
100#[doc(hidden)]
101#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
102pub unsafe fn execute_carry_sub<Reg, Env>(env: &mut Env, vd: VReg, vs2: VReg, src: OpSrc, sew: Vsew)
103where
104    Reg: Register,
105    Env: VectorRegistersExt<Reg>,
106    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
107{
108    let vl = env.vl();
109    let vstart = env.vstart();
110    for i in vstart.range_to(vl) {
111        // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32` (caller precondition);
112        // `i < vl <= group_regs * elems_per_reg`, so
113        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
114        let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
115        let b = match src {
116            OpSrc::Vreg(vs1_base) => {
117                // SAFETY: caller verified that the vs1 register group satisfies the same alignment
118                // constraint as vs2; the index argument is identical, so the same bound holds:
119                // `vs1_base + i / elems_per_reg < 32`
120                unsafe { env.read_vregs().read_element(vs1_base, i, sew) }
121            }
122            OpSrc::Scalar(val) => val,
123        };
124        // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
125        let borrow = unsafe { carry_bit(env.read_vregs(), i) };
126
127        let result = a.wrapping_sub(b).wrapping_sub(borrow);
128        // SAFETY: `vd % group_regs == 0` and `vd + group_regs <= 32` (caller precondition);
129        // `i < vl <= group_regs * elems_per_reg`, so
130        // `vd + i / elems_per_reg < vd + group_regs <= 32`
131        unsafe {
132            env.write_vregs().write_element(vd, i, sew, result);
133        }
134    }
135
136    env.mark_vs_dirty();
137    env.reset_vstart();
138}
139
140/// Execute an element-wise add-with-carry over `vstart..vl`, writing the carry-out as a single mask
141/// bit per element into `vd`.
142///
143/// When `WITH_CARRY` is true, carry-in for element `i` is read from `v0[i]`. When false, carry-in
144/// is treated as zero.
145///
146/// All elements are processed unconditionally (no execution mask).
147///
148/// Tail mask bits (indices `>= vl`) are left undisturbed per spec ยง5.3.
149///
150/// # Safety
151/// - `vs2.to_bits() % group_regs == 0` and `vs2.to_bits() + group_regs <= 32`
152/// - `src` register satisfies the same alignment
153/// - `vl <= group_regs * VLEN.bytes() / sew_bytes` and `vl <= VLEN`
154/// - vd overlap constraints checked by caller
155#[inline(always)]
156#[doc(hidden)]
157#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
158pub unsafe fn execute_carry_add_mask<const WITH_CARRY: bool, Reg, Env>(
159    env: &mut Env,
160    vd: VReg,
161    vs2: VReg,
162    src: OpSrc,
163    sew: Vsew,
164) where
165    Reg: Register,
166    Env: VectorRegistersExt<Reg>,
167    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
168{
169    let vl = env.vl();
170    let vstart = env.vstart();
171    let mask = sew_mask(sew);
172
173    for i in vstart.range_to(vl) {
174        // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32` (caller precondition);
175        // `i < vl <= group_regs * elems_per_reg`, so
176        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
177        let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
178        let b = match src {
179            OpSrc::Vreg(vs1_base) => {
180                // SAFETY: caller verified that the vs1 register group satisfies the same alignment
181                // constraint as vs2; the index argument is identical, so the same bound holds:
182                // `vs1_base + i / elems_per_reg < 32`
183                unsafe { env.read_vregs().read_element(vs1_base, i, sew) }
184            }
185            OpSrc::Scalar(val) => val,
186        };
187        let c = if WITH_CARRY {
188            // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
189            unsafe { carry_bit(env.read_vregs(), i) }
190        } else {
191            0
192        };
193
194        // Use u128 to capture the carry-out bit beyond SEW
195        let sum = u128::from(a & mask) + u128::from(b & mask) + u128::from(c);
196        let carry_out = (sum >> sew.bits_width()) & 1 != 0;
197
198        // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
199        unsafe {
200            write_mask_bit(env.write_vregs(), vd, i, carry_out);
201        }
202    }
203
204    env.mark_vs_dirty();
205    env.reset_vstart();
206}
207
208/// Execute an element-wise subtract-with-borrow over `vstart..vl`, writing the borrow-out as a
209/// single mask bit per element into `vd`.
210///
211/// When `WITH_BORROW` is true, borrow-in for element `i` is read from `v0[i]`. When false,
212/// borrow-in is treated as zero.
213///
214/// Borrow-out is 1 when the subtraction underflows unsigned:
215/// `borrow_out = (b + borrow_in) > a` (compared as SEW-wide unsigned values).
216///
217/// # Safety
218/// Same as [`execute_carry_add_mask()`].
219#[inline(always)]
220#[doc(hidden)]
221#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
222pub unsafe fn execute_carry_sub_mask<const WITH_BORROW: bool, Reg, Env>(
223    env: &mut Env,
224    vd: VReg,
225    vs2: VReg,
226    src: OpSrc,
227    sew: Vsew,
228) where
229    Reg: Register,
230    Env: VectorRegistersExt<Reg>,
231    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
232{
233    let vl = env.vl();
234    let vstart = env.vstart();
235    let mask = sew_mask(sew);
236
237    for i in vstart.range_to(vl) {
238        // SAFETY: `vs2 % group_regs == 0` and `vs2 + group_regs <= 32` (caller precondition);
239        // `i < vl <= group_regs * elems_per_reg`, so
240        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
241        let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
242        let b = match src {
243            OpSrc::Vreg(vs1_base) => {
244                // SAFETY: caller verified that the vs1 register group satisfies the same alignment
245                // constraint as vs2; the index argument is identical, so the same bound holds:
246                // `vs1_base + i / elems_per_reg < 32`
247                unsafe { env.read_vregs().read_element(vs1_base, i, sew) }
248            }
249            OpSrc::Scalar(val) => val,
250        };
251        let borrow_in = if WITH_BORROW {
252            // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
253            unsafe { carry_bit(env.read_vregs(), i) }
254        } else {
255            0
256        };
257
258        let a_m = u128::from(a & mask);
259        let rhs = u128::from(b & mask) + u128::from(borrow_in);
260        let borrow_out = a_m < rhs;
261
262        // SAFETY: `i < vl <= VLEN`, so `i / 8 < VLEN.bytes()`
263        unsafe {
264            write_mask_bit(env.write_vregs(), vd, i, borrow_out);
265        }
266    }
267
268    env.mark_vs_dirty();
269    env.reset_vstart();
270}