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}