Skip to main content

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}