rustysnes_cart/coproc/armv3/primitives.rs
1//! Pure, state-free ARM primitives: the barrel shifter, condition codes, and the ALU core.
2//!
3//! The barrel shifter, the condition-code checker, and the Add/Sub/logical-op ALU core — each
4//! verified against the ARM Architecture Reference Manual's own documented truth tables. See the
5//! parent module (`coproc::armv3`) doc for the full picture.
6
7// Chip-name jargon (ARMv3, CPSR, SPSR, ...) is not Rust code. `Flags` is a direct port of the
8// architectural N/Z/C/V condition-code register — four independent hardware bits, not a
9// state-machine candidate for an enum, so `struct_excessive_bools` is noise here.
10#![allow(clippy::doc_markdown, clippy::struct_excessive_bools)]
11
12/// The four ARM condition-code flags (CPSR bits 31-28 / N,Z,C,V).
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub struct Flags {
15 /// Negative (bit 31 of the last flag-setting result).
16 pub n: bool,
17 /// Zero (the last flag-setting result was all-zero).
18 pub z: bool,
19 /// Carry (carry-out of the last flag-setting add/subtract/shift).
20 pub c: bool,
21 /// Overflow (signed overflow of the last flag-setting add/subtract).
22 pub v: bool,
23}
24
25/// Evaluate a 4-bit ARM condition code against the current flags (Mesen2 `CheckConditions`).
26///
27/// `cond` is the top 4 bits of every ARM instruction word (`opcode >> 28`); only `0..=15` are
28/// meaningful (`15` = NV, reserved/never on this architecture generation — ported as `false`
29/// verbatim, matching the source rather than guessing at later-ARM NV semantics).
30#[must_use]
31pub const fn check_condition(cond: u8, f: Flags) -> bool {
32 match cond & 0xF {
33 0 => f.z, // EQ
34 1 => !f.z, // NE
35 2 => f.c, // CS/HS
36 3 => !f.c, // CC/LO
37 4 => f.n, // MI
38 5 => !f.n, // PL
39 6 => f.v, // VS
40 7 => !f.v, // VC
41 8 => f.c && !f.z, // HI
42 9 => !f.c || f.z, // LS
43 10 => f.n == f.v, // GE
44 11 => f.n != f.v, // LT
45 12 => !f.z && (f.n == f.v), // GT
46 13 => f.z || (f.n != f.v), // LE
47 14 => true, // AL
48 _ => false, // NV (15) — reserved, never taken
49 }
50}
51
52/// ARM `ADD`-family: `op1 + op2 + carry_in`.
53///
54/// Uses the exact overflow/carry formulas the ARM ARM specifies (Mesen2 `Add`) — NOT
55/// reimplemented from first principles, since the signed-overflow and carry-out derivations
56/// below are the well-known highest-bug-density spot in a from-scratch ARM core. Returns
57/// `(result, flags_if_update_requested)`; the caller decides whether to commit the returned
58/// flags (mirrors the `updateFlags`/`S`-bit gate every ARM ALU instruction carries).
59#[must_use]
60pub const fn add(op1: u32, op2: u32, carry_in: bool, prior: Flags) -> (u32, Flags) {
61 let result = op1.wrapping_add(op2).wrapping_add(carry_in as u32);
62 let overflow = (!(op1 ^ op2) & (op1 ^ result)) & 0x8000_0000 != 0;
63 let carry = (op1 ^ op2 ^ (overflow as u32).wrapping_shl(31) ^ result) & 0x8000_0000 != 0;
64 let flags = Flags {
65 n: result & 0x8000_0000 != 0,
66 z: result == 0,
67 c: carry,
68 v: overflow,
69 };
70 let _ = prior;
71 (result, flags)
72}
73
74/// ARM `SUB`-family: `Add(op1, !op2, carry_in, ...)`.
75///
76/// ARM's subtract IS add-with-inverted-operand-and-carry, not an independently-implemented
77/// subtraction (Mesen2 `Sub`); porting it as a direct call to [`add`], not a separate formula,
78/// is deliberate — the two must never drift.
79#[must_use]
80pub const fn sub(op1: u32, op2: u32, carry_in: bool, prior: Flags) -> (u32, Flags) {
81 add(op1, !op2, carry_in, prior)
82}
83
84/// ARM logical-op flag update (`AND`/`EOR`/`ORR`/`MOV`/`BIC`/`MVN`/`TST`/`TEQ`, Mesen2 `LogicalOp`).
85///
86/// `V` is left UNAFFECTED (only `ADD`/`SUB`-family ops touch it); `C` becomes the barrel
87/// shifter's carry-out (or is preserved when the shift was `LSL #0` — the caller passes the
88/// shifter's carry-out unconditionally, which is already correct in that case since `LSL #0`
89/// returns the flags' existing carry unchanged, see [`shift_lsl`]).
90#[must_use]
91pub const fn logical_flags(result: u32, shifter_carry: bool, prior: Flags) -> Flags {
92 Flags {
93 n: result & 0x8000_0000 != 0,
94 z: result == 0,
95 c: shifter_carry,
96 v: prior.v,
97 }
98}
99
100/// `ROR` by a fixed 1-31 amount with no carry-out tracking (Mesen2's 2-argument `RotateRight`).
101///
102/// Used by `MSR`'s immediate-operand rotate. `shift` must be `1..=31`; the immediate encodings
103/// that call this always derive it as `(nibble) * 2` from a nonzero nibble, so it's never 0.
104#[must_use]
105pub const fn rotate_right(value: u32, shift: u32) -> u32 {
106 value.rotate_right(shift)
107}
108
109/// `ROR` by a `0..=31` amount, also returning the carry-out (bit `shift-1` of `value`) — Mesen2's
110/// 3-argument `RotateRight`, used by `ArmDataProcessing`'s immediate-operand rotate.
111///
112/// Every current call site only ever passes a nonzero `shift` (the immediate encodings derive it
113/// as `nibble * 2` from a nonzero nibble, matching [`rotate_right`]'s own contract), but this is a
114/// public helper, so `shift == 0` is handled safely and total rather than left to underflow
115/// `shift - 1`: it returns `carry_in` unchanged, matching every other shift function in this
116/// module's shared "shift 0 preserves the existing carry" contract (see [`shift_lsl`]).
117#[must_use]
118pub const fn rotate_right_carry(value: u32, shift: u32, carry_in: bool) -> (u32, bool) {
119 if shift == 0 {
120 return (value, carry_in);
121 }
122 let carry = (value >> (shift - 1)) & 1 != 0;
123 (rotate_right(value, shift), carry)
124}
125
126/// `LSL` (logical shift left) by a register-derived amount `0..=255` (Mesen2 `ShiftLsl`).
127///
128/// `shift == 0` is a documented ARM no-op: both `value` and `carry` pass through UNCHANGED (the
129/// existing carry flag is preserved, not recomputed) — every ARM shift function shares this
130/// "shift 0 changes nothing" contract, ported here via the same `if shift != 0` guard the source
131/// uses rather than folding it into the arithmetic (shifting a `u32` by literally 32 or more is
132/// disallowed in Rust — every branch below is guarded to never execute one).
133#[must_use]
134pub const fn shift_lsl(value: u32, shift: u8, carry: bool) -> (u32, bool) {
135 if shift == 0 {
136 return (value, carry);
137 }
138 let carry = if shift < 33 {
139 value & (1u32 << (32 - shift as u32)) != 0
140 } else {
141 false
142 };
143 let value = if shift < 32 { value << shift } else { 0 };
144 (value, carry)
145}
146
147/// `LSR` (logical shift right) by a register-derived amount `0..=255` (Mesen2 `ShiftLsr`). See
148/// [`shift_lsl`] for the shared `shift == 0` no-op contract.
149#[must_use]
150pub const fn shift_lsr(value: u32, shift: u8, carry: bool) -> (u32, bool) {
151 if shift == 0 {
152 return (value, carry);
153 }
154 let carry = if shift < 33 {
155 value & (1u32 << (shift as u32 - 1)) != 0
156 } else {
157 false
158 };
159 let value = if shift < 32 { value >> shift } else { 0 };
160 (value, carry)
161}
162
163/// `ASR` (arithmetic shift right, sign-extending) by a register-derived amount `0..=255`.
164///
165/// (Mesen2 `ShiftAsr`.) For `shift >= 32` the result is the sign bit smeared across all 32 bits
166/// (an ASR by 31 of the original value achieves this — never a literal `>> 32`); carry-out is
167/// the sign bit itself in that case. See [`shift_lsl`] for the shared `shift == 0` no-op contract.
168#[must_use]
169pub const fn shift_asr(value: u32, shift: u8, carry: bool) -> (u32, bool) {
170 if shift == 0 {
171 return (value, carry);
172 }
173 let sign = value & 0x8000_0000 != 0;
174 let carry = if shift < 33 {
175 value & (1u32 << (shift as u32 - 1)) != 0
176 } else {
177 sign
178 };
179 let value = if shift < 32 {
180 (value.cast_signed() >> shift).cast_unsigned()
181 } else {
182 (value.cast_signed() >> 31).cast_unsigned()
183 };
184 (value, carry)
185}
186
187/// `ROR` (rotate right) by a register-derived amount `0..=255` (Mesen2 `ShiftRor`).
188///
189/// The rotate amount is first reduced mod 32 (`shift & 0x1F`); if that reduction is itself 0
190/// (i.e. the original amount was a nonzero multiple of 32), `value` is left UNCHANGED but carry
191/// still becomes bit 31 of `value` — a real, easy-to-miss ARM ARM special case, ported exactly
192/// as Mesen2 encodes it (the inner `if shift != 0` only guards the rotate, not the carry
193/// update). See [`shift_lsl`] for the shared outer `shift == 0` no-op contract (the ORIGINAL,
194/// pre-mask amount — distinct from the inner post-mask check).
195#[must_use]
196pub const fn shift_ror(value: u32, shift: u8, carry: bool) -> (u32, bool) {
197 if shift == 0 {
198 return (value, carry);
199 }
200 let masked = shift & 0x1F;
201 let value = if masked == 0 {
202 value
203 } else {
204 rotate_right(value, masked as u32)
205 };
206 let carry = value & 0x8000_0000 != 0;
207 (value, carry)
208}
209
210/// `RRX` (rotate right extended by 1, through the carry flag; Mesen2 `ShiftRrx`).
211///
212/// The immediate-operand encoding `ROR #0` is repurposed to mean this: the incoming carry
213/// becomes bit 31 of the result, and the outgoing carry becomes the value's own bit 0.
214#[must_use]
215pub const fn shift_rrx(value: u32, carry_in: bool) -> (u32, bool) {
216 let carry_out = value & 1 != 0;
217 let result = (value >> 1) | ((carry_in as u32) << 31);
218 (result, carry_out)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 // Positional booleans read naturally at every call site below (`f(N, Z, C, V)`, matching the
226 // ARM ARM's own NZCV ordering) — a struct-literal helper would be noisier for a test-only fn.
227 #[allow(clippy::fn_params_excessive_bools)]
228 fn f(n: bool, z: bool, c: bool, v: bool) -> Flags {
229 Flags { n, z, c, v }
230 }
231
232 #[test]
233 fn condition_codes_match_the_arm_arm_truth_table() {
234 let nzcv = f(true, true, true, true);
235 let clear = f(false, false, false, false);
236 assert!(check_condition(0, f(false, true, false, false))); // EQ, Z set
237 assert!(!check_condition(0, clear)); // EQ, Z clear
238 assert!(check_condition(1, clear)); // NE, Z clear
239 assert!(check_condition(2, f(false, false, true, false))); // CS, C set
240 assert!(check_condition(3, clear)); // CC, C clear
241 assert!(check_condition(4, f(true, false, false, false))); // MI, N set
242 assert!(check_condition(5, clear)); // PL, N clear
243 assert!(check_condition(6, f(false, false, false, true))); // VS, V set
244 assert!(check_condition(7, clear)); // VC, V clear
245 assert!(check_condition(8, f(false, false, true, false))); // HI, C set && Z clear
246 assert!(!check_condition(8, nzcv)); // HI fails when Z set even if C set
247 assert!(check_condition(9, clear)); // LS, C clear
248 assert!(check_condition(10, clear)); // GE, N==V (both false)
249 assert!(check_condition(10, nzcv)); // GE, N==V (both true)
250 assert!(check_condition(11, f(true, false, false, false))); // LT, N!=V
251 assert!(check_condition(12, clear)); // GT, Z clear && N==V
252 assert!(!check_condition(12, f(false, true, false, false))); // GT fails when Z set
253 assert!(check_condition(13, f(false, true, false, false))); // LE, Z set
254 assert!(check_condition(14, clear)); // AL always true
255 assert!(!check_condition(15, nzcv)); // NV always false
256 }
257
258 #[test]
259 fn add_carry_and_overflow_match_known_arm_cases() {
260 // 0x7FFFFFFF + 1 = signed overflow (positive + positive -> negative), no unsigned carry.
261 let (r, fl) = add(0x7FFF_FFFF, 1, false, Flags::default());
262 assert_eq!(r, 0x8000_0000);
263 assert!(fl.v, "signed overflow expected");
264 assert!(!fl.c, "no unsigned carry expected");
265 assert!(fl.n);
266 assert!(!fl.z);
267
268 // 0xFFFFFFFF + 1 = unsigned carry out, result 0, no signed overflow.
269 let (r, fl) = add(0xFFFF_FFFF, 1, false, Flags::default());
270 assert_eq!(r, 0);
271 assert!(fl.c, "unsigned carry expected");
272 assert!(!fl.v, "no signed overflow expected");
273 assert!(fl.z);
274
275 // Carry-in propagates like a real add-with-carry.
276 let (r, _) = add(1, 1, true, Flags::default());
277 assert_eq!(r, 3);
278 }
279
280 #[test]
281 fn sub_is_add_with_inverted_operand_and_carry() {
282 // ARM SUB passes carry=true for a plain subtract with no borrow-in (SBC uses the real C).
283 let (r, fl) = sub(5, 3, true, Flags::default());
284 assert_eq!(r, 2);
285 assert!(fl.c, "no borrow: 5 >= 3");
286
287 // 0 - 1 borrows: unsigned carry clear, result wraps to 0xFFFFFFFF.
288 let (r, fl) = sub(0, 1, true, Flags::default());
289 assert_eq!(r, 0xFFFF_FFFF);
290 assert!(!fl.c, "borrow occurred");
291 }
292
293 #[test]
294 fn logical_flags_leaves_overflow_untouched_and_uses_shifter_carry() {
295 let prior = f(false, false, false, true); // V already set
296 let fl = logical_flags(0x8000_0000, true, prior);
297 assert!(fl.n);
298 assert!(!fl.z);
299 assert!(fl.c);
300 assert!(fl.v, "V must be preserved, not recomputed, by a logical op");
301 }
302
303 #[test]
304 fn shift_by_zero_is_a_true_no_op_on_every_shifter() {
305 for carry in [true, false] {
306 assert_eq!(shift_lsl(0x1234, 0, carry), (0x1234, carry));
307 assert_eq!(shift_lsr(0x1234, 0, carry), (0x1234, carry));
308 assert_eq!(shift_asr(0x1234, 0, carry), (0x1234, carry));
309 assert_eq!(shift_ror(0x1234, 0, carry), (0x1234, carry));
310 }
311 }
312
313 #[test]
314 fn lsl_boundary_cases_32_and_beyond() {
315 // LSL #1: 0x8000_0000 -> 0, carry = old bit 31.
316 assert_eq!(shift_lsl(0x8000_0000, 1, false), (0, true));
317 // LSL #32: result 0, carry = bit 0 of the original value.
318 assert_eq!(shift_lsl(1, 32, false), (0, true));
319 assert_eq!(shift_lsl(2, 32, false), (0, false));
320 // LSL #33 (and beyond): result 0, carry 0.
321 assert_eq!(shift_lsl(0xFFFF_FFFF, 33, false), (0, false));
322 }
323
324 #[test]
325 fn lsr_boundary_cases_32_and_beyond() {
326 // LSR #1: bit 0 -> carry.
327 assert_eq!(shift_lsr(1, 1, false), (0, true));
328 // LSR #32: result 0, carry = bit 31 of the original value.
329 assert_eq!(shift_lsr(0x8000_0000, 32, false), (0, true));
330 assert_eq!(shift_lsr(0x7FFF_FFFF, 32, false), (0, false));
331 // LSR #33+: result 0, carry 0.
332 assert_eq!(shift_lsr(0xFFFF_FFFF, 33, false), (0, false));
333 }
334
335 #[test]
336 fn asr_sign_extends_and_boundary_cases_saturate_to_the_sign_bit() {
337 // ASR #1 of a negative value sign-extends (top bit stays set) and carries out bit 0.
338 assert_eq!(shift_asr(0x8000_0001, 1, false), (0xC000_0000, true));
339 // ASR #32+ of a negative value: all 1s, carry = sign bit (1).
340 assert_eq!(shift_asr(0x8000_0000, 32, false), (0xFFFF_FFFF, true));
341 assert_eq!(shift_asr(0x8000_0000, 40, false), (0xFFFF_FFFF, true));
342 // ASR #32+ of a positive value: all 0s, carry = sign bit (0).
343 assert_eq!(shift_asr(0x7FFF_FFFF, 32, false), (0, false));
344 }
345
346 #[test]
347 fn ror_by_a_multiple_of_32_leaves_value_unchanged_but_still_updates_carry() {
348 // shift=32 masks to 0: value unchanged, carry = bit 31 of the (unchanged) value.
349 assert_eq!(shift_ror(0x8000_0001, 32, false), (0x8000_0001, true));
350 assert_eq!(shift_ror(0x0000_0001, 32, false), (0x0000_0001, false));
351 }
352
353 #[test]
354 fn ror_ordinary_rotation() {
355 // ROR #1 of a value with bit 0 set: that bit moves to bit 31, becomes the new carry too.
356 assert_eq!(shift_ror(0x0000_0001, 1, false), (0x8000_0000, true));
357 // ROR #4 of 0x1 -> 0x1000_0000, no carry (bit 31 clear).
358 assert_eq!(shift_ror(0x0000_0001, 4, false), (0x1000_0000, false));
359 }
360
361 #[test]
362 fn rrx_rotates_through_the_carry_flag() {
363 // RRX with carry-in=1: bit 31 of the result becomes 1; bit 0 of value becomes carry-out.
364 assert_eq!(shift_rrx(0x0000_0000, true), (0x8000_0000, false));
365 assert_eq!(shift_rrx(0x0000_0001, false), (0x0000_0000, true));
366 assert_eq!(shift_rrx(0x8000_0001, true), (0xC000_0000, true));
367 }
368
369 #[test]
370 fn rotate_right_matches_the_manual_bit_algebra() {
371 assert_eq!(rotate_right(0x0000_0001, 1), 0x8000_0000);
372 let (v, c) = rotate_right_carry(0x0000_0001, 1, false);
373 assert_eq!(v, 0x8000_0000);
374 assert!(c, "carry = bit(shift-1) = bit 0 of the original value");
375 }
376
377 #[test]
378 fn rotate_right_carry_by_zero_is_total_and_preserves_carry_in() {
379 // shift=0 must not underflow `shift - 1`; it preserves carry_in unchanged, matching every
380 // other shift function's shared "shift 0 changes nothing" contract.
381 assert_eq!(
382 rotate_right_carry(0x1234_5678, 0, true),
383 (0x1234_5678, true)
384 );
385 assert_eq!(
386 rotate_right_carry(0x1234_5678, 0, false),
387 (0x1234_5678, false)
388 );
389 }
390}