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