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}