Skip to main content

rustysnes_cart/coproc/armv3/
cpu.rs

1//! Instruction decode/execute for the ARMv3 core.
2//!
3//! Ties [`crate::coproc::armv3::regs`]'s register file and pipeline together with the
4//! [`crate::coproc::armv3::bus::ArmBus`] trait and drives them one instruction at a time (Mesen2
5//! `ArmV3Cpu::Exec`/`InitArmOpTable` and friends).
6//!
7//! **Status: the full ARMv3 instruction set is implemented** — data processing, branch, MSR/MRS,
8//! exception entry, `LDR`/`STR`, `LDM`/`STM`, `MUL`/`MLA`/`MULL`/`MLAL`, and `SWP`/`SWPB`. Only the
9//! SNES-side board wrapper (firmware loading, the master-clock catch-up loop, the handshake
10//! registers) and its `board::select` wiring remain (`docs/st018-arm-notes.md` tracks the
11//! remaining build order).
12
13// Register-heavy decode code inherently pairs up short, similarly-named fields (`rm`/`rn`/`rs`,
14// `rm_val`/`rs_val`) that mirror the ARM ARM's own mnemonics — same rationale as the other
15// coprocessor cores in this crate (`sdd1`, `gsu`, `superfx`, `upd77c25`, `hg51b`, `sa1`).
16#![allow(clippy::similar_names)]
17
18use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
19
20use crate::coproc::armv3::bus::ArmBus;
21use crate::coproc::armv3::primitives::{self, Flags};
22use crate::coproc::armv3::regs::{Cpsr, Mode, Pipeline, Regs, mode};
23
24/// Which real ARM exception vector an entry lands at (Mesen2 `ArmV3CpuVector`).
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Vector {
27    /// `$04` — undefined-instruction trap.
28    Undefined,
29    /// `$08` — `SWI`.
30    SoftwareIrq,
31    /// `$18` — normal interrupt.
32    Irq,
33}
34
35impl Vector {
36    const fn address(self) -> u32 {
37        match self {
38            Self::Undefined => 0x04,
39            Self::SoftwareIrq => 0x08,
40            Self::Irq => 0x18,
41        }
42    }
43
44    /// The mode entered for this vector.
45    const fn mode(self) -> Mode {
46        match self {
47            Self::Undefined => mode::UNDEFINED,
48            Self::SoftwareIrq => mode::SUPERVISOR,
49            Self::Irq => mode::IRQ,
50        }
51    }
52}
53
54/// The 16 ARM data-processing ALU opcodes, in their real bit-field order (`(opcode>>21)&0xF`) —
55/// the switch in [`Cpu::exec_data_processing`] relies on this exact ordering matching the
56/// hardware encoding, not on the variant names.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58enum AluOp {
59    And,
60    Eor,
61    Sub,
62    Rsb,
63    Add,
64    Adc,
65    Sbc,
66    Rsc,
67    Tst,
68    Teq,
69    Cmp,
70    Cmn,
71    Orr,
72    Mov,
73    Bic,
74    Mvn,
75}
76
77impl AluOp {
78    const fn from_bits(bits: u32) -> Self {
79        match bits & 0xF {
80            0x0 => Self::And,
81            0x1 => Self::Eor,
82            0x2 => Self::Sub,
83            0x3 => Self::Rsb,
84            0x4 => Self::Add,
85            0x5 => Self::Adc,
86            0x6 => Self::Sbc,
87            0x7 => Self::Rsc,
88            0x8 => Self::Tst,
89            0x9 => Self::Teq,
90            0xA => Self::Cmp,
91            0xB => Self::Cmn,
92            0xC => Self::Orr,
93            0xD => Self::Mov,
94            0xE => Self::Bic,
95            _ => Self::Mvn,
96        }
97    }
98
99    /// `TST`/`TEQ`/`CMP`/`CMN` have no destination register — a comparison only.
100    const fn is_comparison(self) -> bool {
101        matches!(self, Self::Tst | Self::Teq | Self::Cmp | Self::Cmn)
102    }
103}
104
105/// The bit-pattern category an opcode's 12-bit index (`((opcode&0x0FF00000)>>16)|((opcode&0xF0)>>4)`,
106/// Mesen2's `InitArmOpTable` key) decodes to. Priority mirrors the reference table's construction
107/// order exactly: later `addEntry` calls overwrite earlier ones for overlapping indices (Multiply/
108/// MultiplyLong/SingleDataSwap/SoftwareInterrupt all carve sparse holes out of ranges that would
109/// otherwise read as DataProcessing/InvalidOp), so [`Category::decode`] checks the LATEST-writing
110/// pattern first and falls through, reproducing the same final table state without needing a real
111/// 4096-entry array.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113enum Category {
114    DataProcessing,
115    Msr,
116    Mrs,
117    Branch,
118    SingleDataTransfer,
119    BlockDataTransfer,
120    Multiply,
121    MultiplyLong,
122    SingleDataSwap { byte: bool },
123    SoftwareInterrupt,
124    InvalidOp,
125}
126
127impl Category {
128    fn decode(index: u16) -> Self {
129        // Software Interrupt: index 0xF00-0xFFF, populated last, always wins.
130        if (0xF00..=0xFFF).contains(&index) {
131            return Self::SoftwareInterrupt;
132        }
133        // Single Data Swap: base 0x109 (word) / 0x149 (byte), with index bits 7,5,4 free
134        // (opcode bits 23,21,20 — the loop enumerates all 8 combinations via `i` in 0..=7).
135        if index & !0xB0 == 0x109 {
136            return Self::SingleDataSwap { byte: false };
137        }
138        if index & !0xB0 == 0x149 {
139            return Self::SingleDataSwap { byte: true };
140        }
141        // Multiply Long: base 0x089, index bits 6,5,4 free (opcode bits 22,21,20).
142        if index & !0x70 == 0x089 {
143            return Self::MultiplyLong;
144        }
145        // Multiply: base 0x009, same free bits.
146        if index & !0x70 == 0x009 {
147            return Self::Multiply;
148        }
149        if (0x800..=0x9FF).contains(&index) {
150            return Self::BlockDataTransfer;
151        }
152        if (0x400..=0x7FF).contains(&index) {
153            return Self::SingleDataTransfer;
154        }
155        if (0xA00..=0xBFF).contains(&index) {
156            return Self::Branch;
157        }
158        if index <= 0x3FF {
159            let operation = AluOp::from_bits(u32::from(index) >> 5);
160            let set_condition_codes = index & 0x10 != 0;
161            if !set_condition_codes && operation.is_comparison() {
162                return if index & 0x20 != 0 {
163                    Self::Msr
164                } else {
165                    Self::Mrs
166                };
167            }
168            return Self::DataProcessing;
169        }
170        Self::InvalidOp
171    }
172}
173
174/// The full ARMv3 core: register file, pipeline, and instruction execution.
175#[derive(Debug, Clone, Copy, Default)]
176pub struct Cpu {
177    /// The register file (`R0-R15`, mode-banked, plus CPSR/SPSRs).
178    pub regs: Regs,
179    /// The 3-stage Fetch/Decode/Execute pipeline.
180    pub pipeline: Pipeline,
181}
182
183impl Cpu {
184    /// Power on (or reset) the core (Mesen2 `PowerOn`): zero every register, enter Supervisor
185    /// mode with both interrupt lines masked, and prime the pipeline from address 0 (the ARM
186    /// reset vector). Unlike `PowerOn(forReset=true)`, this does not preserve a cycle counter —
187    /// the board wrapper (not yet built) owns cycle-count bookkeeping, not the core itself.
188    pub fn power_on(&mut self, bus: &mut impl ArmBus) {
189        self.regs = Regs::default();
190        self.pipeline = Pipeline::default();
191        self.regs.cpsr.mode = mode::SUPERVISOR;
192        self.regs.cpsr.irq_disable = true;
193        self.regs.cpsr.fiq_disable = true;
194        self.pipeline.request_reload();
195        self.advance_pipeline(bus);
196    }
197
198    fn advance_pipeline(&mut self, bus: &mut impl ArmBus) {
199        self.pipeline
200            .process(&mut self.regs.r[15], |addr| bus.read_code(addr));
201    }
202
203    /// Execute the instruction currently in the Execute pipeline stage (if its condition holds),
204    /// then advance the pipeline (Mesen2 `Exec`). Call once per CPU cycle-step.
205    pub fn step(&mut self, bus: &mut impl ArmBus) {
206        let opcode = self.pipeline.execute.opcode;
207        let cond = (opcode >> 28) as u8;
208        if primitives::check_condition(cond, self.regs.cpsr.flags) {
209            self.execute(opcode, bus);
210        }
211        self.advance_pipeline(bus);
212    }
213
214    /// Write `value` into `reg`, requesting a pipeline reload if `reg` is R15 (Mesen2 `SetR`) —
215    /// every instruction that can write the PC (data processing, `LDR`, `LDM`, branch) routes
216    /// through this so the reload is never forgotten.
217    ///
218    /// Not marked `const fn`: `Cpu` carries nested `Regs`/`Pipeline` fields, and const-ness here
219    /// is fragile against future field changes to either — the same posture `coproc::hg51b`
220    /// documents for its own dense register-port methods.
221    #[allow(clippy::missing_const_for_fn)]
222    fn set_r(&mut self, reg: u8, value: u32) {
223        self.regs.r[reg as usize] = value;
224        if reg == 15 {
225            self.pipeline.request_reload();
226        }
227    }
228
229    fn execute(&mut self, opcode: u32, bus: &mut impl ArmBus) {
230        // Always < 0x1000 by construction (8 bits from opcode>>16 combined with 4 bits from
231        // opcode>>4, both masked first) -- never truncates.
232        #[allow(clippy::cast_possible_truncation)]
233        let index = (((opcode & 0x0FF0_0000) >> 16) | ((opcode & 0xF0) >> 4)) as u16;
234        match Category::decode(index) {
235            Category::DataProcessing => self.exec_data_processing(opcode, bus),
236            Category::Msr => self.exec_msr(opcode),
237            Category::Mrs => self.exec_mrs(opcode),
238            Category::Branch => self.exec_branch(opcode),
239            Category::SoftwareInterrupt => self.enter_exception(Vector::SoftwareIrq),
240            Category::InvalidOp => self.enter_exception(Vector::Undefined),
241            Category::SingleDataTransfer => self.exec_single_data_transfer(opcode, bus),
242            Category::BlockDataTransfer => self.exec_block_data_transfer(opcode, bus),
243            Category::Multiply => self.exec_multiply(opcode, bus),
244            Category::MultiplyLong => self.exec_multiply_long(opcode, bus),
245            Category::SingleDataSwap { byte } => self.exec_single_data_swap(opcode, byte, bus),
246        }
247    }
248
249    /// `MOVS PC, ...` / any S-bit data-processing write to R15 restores CPSR wholesale from the
250    /// current mode's SPSR — the idiomatic ARM exception-handler return (Mesen2's trailing
251    /// `if(dstReg==15 && updateFlags)` block, checked unconditionally on the DECODED destination
252    /// field regardless of whether this particular op actually had a real destination — so even
253    /// a comparison op with a stray `dstReg==15` encoding triggers it, matching real hardware).
254    fn maybe_restore_cpsr_from_spsr(&mut self, dst_reg: u8, update_flags: bool) {
255        if dst_reg == 15 && update_flags {
256            let spsr = self.regs.spsr();
257            self.regs.switch_mode(spsr.mode);
258            self.regs.cpsr = spsr;
259        }
260    }
261
262    /// The full 16-op ALU dispatch is dense (like the rest of this direct hardware port) but
263    /// stays a single function on purpose — splitting the shift-operand assembly from the ALU
264    /// switch would separate two halves of one instruction that share `op1`/`op2`/`carry`
265    /// locals, matching the precedent already set for equally dense ported dispatch elsewhere in
266    /// this crate (`coproc::gsu`, `coproc::hg51b_instructions`, `coproc::upd77c25`).
267    #[allow(clippy::too_many_lines)]
268    fn exec_data_processing(&mut self, opcode: u32, bus: &mut impl ArmBus) {
269        let immediate = opcode & (1 << 25) != 0;
270        let rn = ((opcode >> 16) & 0xF) as u8;
271        let dst_reg = ((opcode >> 12) & 0xF) as u8;
272        // TST/TEQ/CMP/CMN always update flags regardless of the decoded S-bit -- dispatch only
273        // ever routes them here with S=1 anyway (S=0 in that opcode range means MSR/MRS
274        // instead), but this mirrors the source's own explicit `true` rather than relying on
275        // that invariant silently holding.
276        let operation = AluOp::from_bits(opcode >> 21);
277        let update_flags = (opcode & (1 << 20) != 0) || operation.is_comparison();
278
279        let mut op1 = self.regs.r[rn as usize];
280        let mut carry = self.regs.cpsr.flags.c;
281        let op2 = if immediate {
282            let rotate = (opcode >> 8) & 0xF;
283            let imm = opcode & 0xFF;
284            if rotate == 0 {
285                imm
286            } else {
287                let (v, c) = primitives::rotate_right_carry(imm, rotate * 2, carry);
288                carry = c;
289                v
290            }
291        } else {
292            let shift_type = (opcode >> 5) & 0x3;
293            let rm = (opcode & 0xF) as u8;
294            let mut v = self.regs.r[rm as usize];
295            let use_reg_shift = opcode & (1 << 4) != 0;
296            let shift = if use_reg_shift {
297                // Register-specified shift amount costs an extra internal cycle; R15 (as ANY of
298                // rm, rn, or rs) reads as address+12 here instead of the usual address+8, since
299                // the pipeline has already advanced R15 to +8 and this is one MORE cycle on top.
300                // The source applies the +4 to each of the three independently (`shift = R(rs) +
301                // (rs==15?4:0)`, then separately `op2 += 4`/`op1 += 4` for rm/rn) -- ported as
302                // three explicit +4s exactly where the source applies each one, not folded into
303                // a single "R15 always reads +12 in this instruction" rule applied once.
304                bus.idle();
305                let rs = ((opcode >> 8) & 0xF) as u8;
306                #[allow(clippy::cast_possible_truncation)]
307                let s = (self.regs.r[rs as usize] as u8).wrapping_add(if rs == 15 { 4 } else { 0 });
308                if rm == 15 {
309                    v = v.wrapping_add(4);
310                }
311                if rn == 15 {
312                    op1 = op1.wrapping_add(4);
313                }
314                s
315            } else {
316                #[allow(clippy::cast_possible_truncation)]
317                let s = ((opcode >> 7) & 0x1F) as u8;
318                s
319            };
320            let (result, c) = match shift_type {
321                0 => primitives::shift_lsl(v, shift, carry),
322                1 => primitives::shift_lsr(
323                    v,
324                    if use_reg_shift || shift != 0 {
325                        shift
326                    } else {
327                        32
328                    },
329                    carry,
330                ),
331                2 => primitives::shift_asr(
332                    v,
333                    if use_reg_shift || shift != 0 {
334                        shift
335                    } else {
336                        32
337                    },
338                    carry,
339                ),
340                _ => {
341                    if !use_reg_shift && shift == 0 {
342                        primitives::shift_rrx(v, carry)
343                    } else {
344                        primitives::shift_ror(v, shift, carry)
345                    }
346                }
347            };
348            carry = c;
349            result
350        };
351
352        let prior = self.regs.cpsr.flags;
353        let (result, flags): (Option<u32>, Flags) = match operation {
354            AluOp::And => {
355                let r = op1 & op2;
356                (Some(r), primitives::logical_flags(r, carry, prior))
357            }
358            AluOp::Eor => {
359                let r = op1 ^ op2;
360                (Some(r), primitives::logical_flags(r, carry, prior))
361            }
362            AluOp::Sub => {
363                let (r, f) = primitives::sub(op1, op2, true, prior);
364                (Some(r), f)
365            }
366            AluOp::Rsb => {
367                let (r, f) = primitives::sub(op2, op1, true, prior);
368                (Some(r), f)
369            }
370            AluOp::Add => {
371                let (r, f) = primitives::add(op1, op2, false, prior);
372                (Some(r), f)
373            }
374            AluOp::Adc => {
375                let (r, f) = primitives::add(op1, op2, prior.c, prior);
376                (Some(r), f)
377            }
378            AluOp::Sbc => {
379                let (r, f) = primitives::sub(op1, op2, prior.c, prior);
380                (Some(r), f)
381            }
382            AluOp::Rsc => {
383                let (r, f) = primitives::sub(op2, op1, prior.c, prior);
384                (Some(r), f)
385            }
386            AluOp::Tst => {
387                let r = op1 & op2;
388                (None, primitives::logical_flags(r, carry, prior))
389            }
390            AluOp::Teq => {
391                let r = op1 ^ op2;
392                (None, primitives::logical_flags(r, carry, prior))
393            }
394            AluOp::Cmp => {
395                let (_, f) = primitives::sub(op1, op2, true, prior);
396                (None, f)
397            }
398            AluOp::Cmn => {
399                let (_, f) = primitives::add(op1, op2, false, prior);
400                (None, f)
401            }
402            AluOp::Orr => {
403                let r = op1 | op2;
404                (Some(r), primitives::logical_flags(r, carry, prior))
405            }
406            AluOp::Mov => (Some(op2), primitives::logical_flags(op2, carry, prior)),
407            AluOp::Bic => {
408                let r = op1 & !op2;
409                (Some(r), primitives::logical_flags(r, carry, prior))
410            }
411            AluOp::Mvn => {
412                let r = !op2;
413                (Some(r), primitives::logical_flags(r, carry, prior))
414            }
415        };
416
417        if update_flags {
418            self.regs.cpsr.flags = flags;
419        }
420        if let Some(r) = result {
421            self.set_r(dst_reg, r);
422        }
423        self.maybe_restore_cpsr_from_spsr(dst_reg, update_flags);
424    }
425
426    /// `B`/`BL` (Mesen2 `ArmBranch`): a sign-extended 24-bit word offset. `R14` (for `BL`) gets
427    /// `R15 - 4`, NOT `R15` itself — R15 is already +8 ahead of the branch instruction's own
428    /// address by the time this runs, so `R15 - 4` = branch_addr + 4 = the correct "next
429    /// sequential instruction" return address.
430    ///
431    /// Not marked `const fn`: same rationale as [`Self::set_r`].
432    #[allow(clippy::missing_const_for_fn)]
433    fn exec_branch(&mut self, opcode: u32) {
434        let with_link = opcode & (1 << 24) != 0;
435        #[allow(clippy::cast_possible_wrap)]
436        let offset = ((opcode as i32) << 8) >> 6; // sign-extend the 24-bit field, then <<2
437        if with_link {
438            self.regs.r[14] = self.regs.r[15].wrapping_sub(4);
439        }
440        self.regs.r[15] = self.regs.r[15].wrapping_add(offset.cast_unsigned());
441        self.pipeline.request_reload();
442    }
443
444    /// `MSR` (Mesen2 `ArmMsr`): write CPSR or the current mode's SPSR, optionally only the flag
445    /// bits (mask bit 3) or only mode/interrupt-mask bits (mask bit 0) — a partial MSR (e.g. only
446    /// updating flags) must NOT touch the bits its mask excludes.
447    fn exec_msr(&mut self, opcode: u32) {
448        let immediate = opcode & (1 << 25) != 0;
449        let write_to_spsr = opcode & (1 << 22) != 0;
450        let mask = (opcode >> 16) & 0xF;
451
452        if write_to_spsr && matches!(self.regs.cpsr.mode, mode::USER | mode::SYSTEM) {
453            return; // User/System have no real SPSR to write.
454        }
455
456        let value = if immediate {
457            let imm = opcode & 0xFF;
458            let shift = (opcode >> 8) & 0xF;
459            if shift == 0 {
460                imm
461            } else {
462                primitives::rotate_right(imm, shift * 2)
463            }
464        } else {
465            self.regs.r[(opcode & 0xF) as usize]
466        };
467
468        let user_mode = self.regs.cpsr.mode == mode::USER;
469        let target: &mut Cpsr = if write_to_spsr {
470            self.regs.spsr_mut()
471        } else {
472            &mut self.regs.cpsr
473        };
474
475        if mask & 0x8 != 0 {
476            target.flags = Flags {
477                n: value & (1 << 31) != 0,
478                z: value & (1 << 30) != 0,
479                c: value & (1 << 29) != 0,
480                v: value & (1 << 28) != 0,
481            };
482        }
483        if mask & 0x1 != 0 && (write_to_spsr || !user_mode) {
484            let new_mode = (value & 0x1F) as u8;
485            let fiq_disable = value & (1 << 6) != 0;
486            let irq_disable = value & (1 << 7) != 0;
487            if write_to_spsr {
488                target.mode = new_mode | mode::BIT;
489                target.fiq_disable = fiq_disable;
490                target.irq_disable = irq_disable;
491            } else {
492                // `target` (the `&mut CPSR` borrow) must end before `switch_mode` can take its
493                // own `&mut self.regs` -- reborrow via the live field instead of the alias.
494                self.regs.switch_mode(new_mode);
495                self.regs.cpsr.fiq_disable = fiq_disable;
496                self.regs.cpsr.irq_disable = irq_disable;
497            }
498        }
499    }
500
501    /// `MRS` (Mesen2 `ArmMrs`): read CPSR or the current mode's SPSR into a register.
502    ///
503    /// Not marked `const fn`: same rationale as [`Self::set_r`].
504    #[allow(clippy::missing_const_for_fn)]
505    fn exec_mrs(&mut self, opcode: u32) {
506        let use_spsr = opcode & (1 << 22) != 0;
507        let rd = ((opcode >> 12) & 0xF) as u8;
508        let value = if use_spsr {
509            self.regs.spsr().to_u32()
510        } else {
511            self.regs.cpsr.to_u32()
512        };
513        self.set_r(rd, value);
514    }
515
516    /// Exception entry (Mesen2 `ProcessException`): save CPSR into the new mode's SPSR, switch
517    /// mode, mask IRQ, park the return address (the Decode-stage address -- one instruction past
518    /// the one that trapped, since Decode is the instruction that would have executed next) in
519    /// R14, and jump to the vector.
520    fn enter_exception(&mut self, vector: Vector) {
521        let cpsr = self.regs.cpsr;
522        self.regs.switch_mode(vector.mode());
523        *self.regs.spsr_mut() = cpsr;
524        self.regs.cpsr.irq_disable = true;
525        self.regs.r[14] = self.pipeline.decode.address;
526        self.regs.r[15] = vector.address();
527        self.pipeline.request_reload();
528    }
529
530    /// `LDR`/`STR` (Mesen2 `ArmSingleDataTransfer`). The offset is either a 12-bit immediate or a
531    /// shifted register — unlike data processing, the shift amount here is ALWAYS an immediate
532    /// (bits 11-7); there is no register-specified-shift-amount form for this instruction class.
533    fn exec_single_data_transfer(&mut self, opcode: u32, bus: &mut impl ArmBus) {
534        let immediate = opcode & (1 << 25) == 0;
535        let pre = opcode & (1 << 24) != 0;
536        let up = opcode & (1 << 23) != 0;
537        let byte = opcode & (1 << 22) != 0;
538        let write_back = opcode & (1 << 21) != 0;
539        let load = opcode & (1 << 20) != 0;
540        let rn = ((opcode >> 16) & 0xF) as u8;
541        let rd = ((opcode >> 12) & 0xF) as u8;
542
543        let mut addr = self.regs.r[rn as usize];
544        let offset = if immediate {
545            opcode & 0xFFF
546        } else {
547            let shift_type = (opcode >> 5) & 0x3;
548            #[allow(clippy::cast_possible_truncation)]
549            let shift = ((opcode >> 7) & 0x1F) as u8;
550            let rm = (opcode & 0xF) as u8;
551            let v = self.regs.r[rm as usize];
552            let carry = self.regs.cpsr.flags.c;
553            match shift_type {
554                0 => primitives::shift_lsl(v, shift, carry).0,
555                1 => primitives::shift_lsr(v, if shift == 0 { 32 } else { shift }, carry).0,
556                2 => primitives::shift_asr(v, if shift == 0 { 32 } else { shift }, carry).0,
557                _ => {
558                    if shift == 0 {
559                        primitives::shift_rrx(v, carry).0
560                    } else {
561                        primitives::shift_ror(v, shift, carry).0
562                    }
563                }
564            }
565        };
566
567        if pre {
568            addr = if up {
569                addr.wrapping_add(offset)
570            } else {
571                addr.wrapping_sub(offset)
572            };
573        }
574
575        if load {
576            let value = bus.read(addr, byte);
577            self.set_r(rd, value);
578            bus.idle();
579        } else {
580            // Storing R15 stores address+12, not the usual address+8 -- a real, documented ARM6-
581            // class quirk, ported exactly where the source applies it rather than folded into a
582            // general rule.
583            let value = self.regs.r[rd as usize].wrapping_add(if rd == 15 { 4 } else { 0 });
584            bus.write(addr, value, byte);
585        }
586
587        if !pre {
588            addr = if up {
589                addr.wrapping_add(offset)
590            } else {
591                addr.wrapping_sub(offset)
592            };
593        }
594
595        // Post-indexed addressing ALWAYS writes back, even without the explicit W bit; a load
596        // into the same register as the base is never written back (the loaded value wins).
597        if (rd != rn || !load) && (write_back || !pre) {
598            self.set_r(rn, addr);
599        }
600    }
601
602    /// `LDM`/`STM` (Mesen2 `ArmBlockDataTransfer`) — the most complex ARM instruction. Every
603    /// quirk below is a real, documented hardware behavior ported verbatim, not a simplification:
604    /// the empty-register-list glitch, the load/store write-back timing asymmetry, and the S-bit
605    /// (`psrForceUser`) user-bank-transfer/exception-return dual role. See `docs/st018-arm-notes.md`
606    /// §`ArmBlockDataTransfer` for the full breakdown of each one.
607    #[allow(clippy::too_many_lines)]
608    fn exec_block_data_transfer(&mut self, opcode: u32, bus: &mut impl ArmBus) {
609        let pre = opcode & (1 << 24) != 0;
610        let up = opcode & (1 << 23) != 0;
611        let psr_force_user = opcode & (1 << 22) != 0;
612        let write_back = opcode & (1 << 21) != 0;
613        let load = opcode & (1 << 20) != 0;
614        let rn = ((opcode >> 16) & 0xF) as u8;
615        #[allow(clippy::cast_possible_truncation)]
616        let mut reg_mask = opcode as u16;
617
618        let base = self.regs.r[rn as usize].wrapping_add(if rn == 15 { 4 } else { 0 });
619        let mut addr = base;
620
621        let mut reg_count = reg_mask.count_ones();
622        if reg_mask == 0 {
623            // Empty-list glitch: only R15 is actually transferred, but the address advances as
624            // if all 16 registers were.
625            reg_count = 16;
626            reg_mask = 0x8000;
627        }
628
629        if !up {
630            addr = addr.wrapping_sub((reg_count - u32::from(!pre)) * 4);
631        } else if pre {
632            addr = addr.wrapping_add(4);
633        }
634
635        let write_back_addr = base.wrapping_add(if up {
636            reg_count * 4
637        } else {
638            (reg_count * 4).wrapping_neg()
639        });
640        if write_back && load {
641            self.set_r(rn, write_back_addr);
642        }
643
644        let org_mode = self.regs.cpsr.mode;
645        if psr_force_user && (!load || reg_mask & 0x8000 == 0) {
646            self.regs.switch_mode(mode::USER);
647        }
648
649        let mut first_reg = true;
650        for i in 0..16u8 {
651            if reg_mask & (1 << i) == 0 {
652                continue;
653            }
654            if !load {
655                let value = self.regs.r[i as usize].wrapping_add(if i == 15 { 4 } else { 0 });
656                bus.write(addr, value, false);
657            }
658            if first_reg && write_back {
659                // Write-back happens here for a STORE (and, harmlessly, a second time with the
660                // same value for a LOAD, which already wrote back above -- ported as-is, not
661                // "optimized" away, matching the source exactly). If `psr_force_user` switched
662                // to User mode above, this `set_r` deliberately lands in the User bank rather
663                // than `org_mode`'s bank (a real, empirically-validated ARM quirk -- Mesen2's
664                // `ArmBlockDataTransfer` cites `gba-tests/arm` test 522 for this exact ordering;
665                // combining S-bit-forced-user-bank transfer with base-register write-back is
666                // otherwise UNPREDICTABLE per the ARM ARM, so this is the one documented,
667                // test-verified answer, not a bug to "fix" toward `org_mode`).
668                self.set_r(rn, write_back_addr);
669                first_reg = false;
670            }
671            if load {
672                // LDM is NOT affected by the misalignment rotation a plain LDR would apply --
673                // this port doesn't model that rotation at all (`ArmBus::read` reads four fixed
674                // byte lanes, matching the real board's `St018::ReadCpu`, which doesn't rotate
675                // either -- see `docs/st018-arm-notes.md`), so there's nothing to suppress here.
676                let value = bus.read(addr, false);
677                self.set_r(i, value);
678            }
679            addr = addr.wrapping_add(4);
680        }
681
682        if load {
683            bus.idle();
684        }
685
686        self.regs.switch_mode(org_mode);
687
688        if psr_force_user && load && reg_mask & 0x8000 != 0 {
689            let spsr = self.regs.spsr();
690            self.regs.switch_mode(spsr.mode);
691            self.regs.cpsr = spsr;
692        }
693    }
694
695    /// `MUL`/`MLA` (Mesen2 `ArmMultiply`). The reference source delegates the actual multiply and
696    /// its variable cycle count to a cycle-EXACT Booth's-algorithm circuit simulation
697    /// (`GbaCpuMultiply`) built for GBA hardware test-ROM precision — deliberately NOT ported
698    /// here (see `docs/st018-arm-notes.md`'s multiply section for the full rationale). This
699    /// computes the mathematically correct 64-bit-widened result directly and idles for the ARM
700    /// ARM's own DOCUMENTED (not reverse-engineered) early-termination cycle count instead
701    /// (`multiply_cycles`). The result and Z/N flags are bit-exact either way; only the idle-
702    /// cycle count and the C flag (see below) differ from GBA-test-ROM-level precision.
703    fn exec_multiply(&mut self, opcode: u32, bus: &mut impl ArmBus) {
704        let rd = ((opcode >> 16) & 0xF) as u8;
705        let rn = ((opcode >> 12) & 0xF) as u8;
706        let rs = ((opcode >> 8) & 0xF) as u8;
707        let rm = (opcode & 0xF) as u8;
708        let update_flags = opcode & (1 << 20) != 0;
709        let mult_and_acc = opcode & (1 << 21) != 0;
710
711        let rm_val = self.regs.r[rm as usize];
712        let rs_val = self.regs.r[rs as usize];
713        let mut result = rm_val.wrapping_mul(rs_val);
714        if mult_and_acc {
715            result = result.wrapping_add(self.regs.r[rn as usize]);
716        }
717
718        for _ in 0..multiply_cycles(rs_val) {
719            bus.idle();
720        }
721        if mult_and_acc {
722            bus.idle();
723        }
724
725        if rd != 15 {
726            self.set_r(rd, result);
727        }
728        if update_flags {
729            // C is left UNCHANGED, not fabricated: real ARMv3/v4 hardware sets it to an
730            // implementation-defined ("meaningless") value derived from internal multiplier
731            // state that this port deliberately doesn't simulate (see the function doc) --
732            // leaving it alone is a documented, deterministic choice, not an oversight.
733            self.regs.cpsr.flags.z = result == 0;
734            self.regs.cpsr.flags.n = result & 0x8000_0000 != 0;
735        }
736    }
737
738    /// `MULL`/`MLAL` (Mesen2 `ArmMultiplyLong`) — signed or unsigned 64-bit-widened multiply,
739    /// optionally accumulating into the existing `Rl:Rh` pair. See [`Self::exec_multiply`]'s doc
740    /// for the same cycle-count/`C`-flag tradeoff (applies identically here).
741    fn exec_multiply_long(&mut self, opcode: u32, bus: &mut impl ArmBus) {
742        let rh = ((opcode >> 16) & 0xF) as u8;
743        let rl = ((opcode >> 12) & 0xF) as u8;
744        let rs = ((opcode >> 8) & 0xF) as u8;
745        let rm = (opcode & 0xF) as u8;
746        let update_flags = opcode & (1 << 20) != 0;
747        let mult_and_acc = opcode & (1 << 21) != 0;
748        let signed = opcode & (1 << 22) != 0;
749
750        bus.idle();
751
752        let rm_val = self.regs.r[rm as usize];
753        let rs_val = self.regs.r[rs as usize];
754        let mut result: u64 = if signed {
755            (i64::from(rm_val.cast_signed()) * i64::from(rs_val.cast_signed())).cast_unsigned()
756        } else {
757            u64::from(rm_val) * u64::from(rs_val)
758        };
759        if mult_and_acc {
760            let acc =
761                (u64::from(self.regs.r[rh as usize]) << 32) | u64::from(self.regs.r[rl as usize]);
762            result = result.wrapping_add(acc);
763        }
764
765        for _ in 0..multiply_cycles(rs_val) {
766            bus.idle();
767        }
768        if mult_and_acc {
769            bus.idle();
770        }
771
772        #[allow(clippy::cast_possible_truncation)]
773        if rl != 15 {
774            self.set_r(rl, result as u32);
775        }
776        #[allow(clippy::cast_possible_truncation)]
777        if rh != 15 {
778            self.set_r(rh, (result >> 32) as u32);
779        }
780        if update_flags {
781            // See exec_multiply's doc: C is left unchanged, not fabricated.
782            self.regs.cpsr.flags.z = result == 0;
783            self.regs.cpsr.flags.n = result & (1 << 63) != 0;
784        }
785    }
786
787    /// `SWP`/`SWPB` (Mesen2 `ArmSingleDataSwap`): an atomic read-modify-write at ONE address —
788    /// read the old value into `rd`, then write `rm`'s value (or, for `rm==15`, `R15+4`) to the
789    /// SAME address, in that exact order with an idle cycle between them (a real read-then-write
790    /// bus cycle real hardware serializes, not two independent accesses).
791    fn exec_single_data_swap(&mut self, opcode: u32, byte: bool, bus: &mut impl ArmBus) {
792        let rn = ((opcode >> 16) & 0xF) as u8;
793        let rd = ((opcode >> 12) & 0xF) as u8;
794        let rm = (opcode & 0xF) as u8;
795
796        let addr = self.regs.r[rn as usize];
797        let old = bus.read(addr, byte);
798        bus.idle();
799        let new_value = self.regs.r[rm as usize].wrapping_add(if rm == 15 { 4 } else { 0 });
800        bus.write(addr, new_value, byte);
801        self.set_r(rd, old);
802    }
803
804    /// Serialize the full register file + pipeline state (the board wrapper composes this with
805    /// its own handshake/ROM/RAM state — `docs/st018-arm-notes.md` step 9).
806    pub(crate) fn save_state(&self, w: &mut SaveWriter) {
807        self.regs.save_state(w);
808        self.pipeline.save_state(w);
809    }
810
811    /// The inverse of [`Self::save_state`].
812    ///
813    /// # Errors
814    /// Propagates [`SaveReader`]'s own truncation error.
815    pub(crate) fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
816        self.regs.load_state(r)?;
817        self.pipeline.load_state(r)
818    }
819}
820
821/// The ARM ARM's documented (not reverse-engineered) multiply early-termination cycle count: 1
822/// cycle if bits 31-8 of the multiplier are all the same (all-0 or all-1), 2 if bits 31-16, 3 if
823/// bits 31-24, else 4. Real silicon terminates the multiply pipeline early once the remaining
824/// high bits of `Rs` stop contributing partial products — this is the documented rule, not
825/// `GbaCpuMultiply`'s cycle-exact Booth's-algorithm derivation (see `docs/st018-arm-notes.md`).
826const fn multiply_cycles(rs: u32) -> u32 {
827    if rs & 0xFFFF_FF00 == 0 || rs & 0xFFFF_FF00 == 0xFFFF_FF00 {
828        1
829    } else if rs & 0xFFFF_0000 == 0 || rs & 0xFFFF_0000 == 0xFFFF_0000 {
830        2
831    } else if rs & 0xFF00_0000 == 0 || rs & 0xFF00_0000 == 0xFF00_0000 {
832        3
833    } else {
834        4
835    }
836}
837
838#[cfg(test)]
839// Every truncating cast in this test harness narrows a value already masked/shifted into the
840// target width by construction (a `u8` byte lane of a `u32` word, a word index that never
841// exceeds the 64 KiB test address space).
842#[allow(clippy::cast_possible_truncation)]
843mod tests {
844    use super::*;
845    use alloc::vec::Vec;
846
847    /// A flat 64 KiB ARM address space for tests -- more than enough for hand-assembled short
848    /// programs, with an idle-cycle counter so multiply/shift-by-register timing can be asserted
849    /// on later without changing the harness.
850    struct TestBus {
851        mem: Vec<u8>,
852        idle_count: u32,
853    }
854
855    impl TestBus {
856        fn new() -> Self {
857            Self {
858                mem: alloc::vec![0u8; 0x1_0000],
859                idle_count: 0,
860            }
861        }
862
863        fn word_at(&self, addr: u32) -> u32 {
864            let a = addr as usize;
865            u32::from(self.mem[a])
866                | (u32::from(self.mem[a + 1]) << 8)
867                | (u32::from(self.mem[a + 2]) << 16)
868                | (u32::from(self.mem[a + 3]) << 24)
869        }
870
871        fn set_word(&mut self, addr: u32, value: u32) {
872            let a = addr as usize;
873            self.mem[a] = value as u8;
874            self.mem[a + 1] = (value >> 8) as u8;
875            self.mem[a + 2] = (value >> 16) as u8;
876            self.mem[a + 3] = (value >> 24) as u8;
877        }
878    }
879
880    impl ArmBus for TestBus {
881        fn read_code(&mut self, addr: u32) -> u32 {
882            self.word_at(addr & 0xFFFF)
883        }
884        fn read(&mut self, addr: u32, byte: bool) -> u32 {
885            if byte {
886                u32::from(self.mem[(addr & 0xFFFF) as usize])
887            } else {
888                self.word_at(addr & 0xFFFF)
889            }
890        }
891        fn write(&mut self, addr: u32, value: u32, byte: bool) {
892            if byte {
893                self.mem[(addr & 0xFFFF) as usize] = value as u8;
894            } else {
895                self.set_word(addr & 0xFFFF, value);
896            }
897        }
898        fn idle(&mut self) {
899            self.idle_count += 1;
900        }
901    }
902
903    /// `ADD r0, r0, #imm8` (immediate data processing, condition AL, no S).
904    const fn add_r0_imm(imm: u32) -> u32 {
905        0xE280_0000 | (imm & 0xFF)
906    }
907
908    fn boot(program: &[u32]) -> (Cpu, TestBus) {
909        let mut bus = TestBus::new();
910        for (i, &w) in program.iter().enumerate() {
911            bus.set_word((i as u32) * 4, w);
912        }
913        let mut cpu = Cpu::default();
914        cpu.power_on(&mut bus);
915        (cpu, bus)
916    }
917
918    #[test]
919    fn power_on_enters_supervisor_with_both_interrupts_masked() {
920        let (cpu, _bus) = boot(&[]);
921        assert_eq!(cpu.regs.cpsr.mode, mode::SUPERVISOR);
922        assert!(cpu.regs.cpsr.irq_disable);
923        assert!(cpu.regs.cpsr.fiq_disable);
924        assert_eq!(cpu.pipeline.execute.address, 0);
925    }
926
927    #[test]
928    fn data_processing_add_immediate() {
929        // ADD r0, r0, #5 ; ADD r0, r0, #3
930        let (mut cpu, mut bus) = boot(&[add_r0_imm(5), add_r0_imm(3)]);
931        cpu.step(&mut bus);
932        assert_eq!(cpu.regs.r[0], 5);
933        cpu.step(&mut bus);
934        assert_eq!(cpu.regs.r[0], 8);
935    }
936
937    #[test]
938    fn condition_code_gates_execution() {
939        // MOV r0, #1 (AL) ; ADDEQ r0, r0, #1 (only if Z set -- it isn't) ; ADD r0,r0,#1 (AL)
940        let mov_r0_1 = 0xE3A0_0001u32; // MOV r0, #1, cond=AL
941        let addeq_r0_1 = 0x0280_0001u32; // ADD r0, r0, #1, cond=EQ
942        let add_r0_1 = add_r0_imm(1); // cond=AL
943        let (mut cpu, mut bus) = boot(&[mov_r0_1, addeq_r0_1, add_r0_1]);
944        cpu.step(&mut bus);
945        assert_eq!(cpu.regs.r[0], 1);
946        cpu.step(&mut bus); // Z is clear (MOV #1 doesn't even update flags -- no S bit), EQ fails
947        assert_eq!(cpu.regs.r[0], 1, "ADDEQ must not execute when Z is clear");
948        cpu.step(&mut bus);
949        assert_eq!(cpu.regs.r[0], 2);
950    }
951
952    #[test]
953    fn subs_sets_flags_and_cmp_never_writes_a_destination() {
954        // MOVS r0, #0 ; CMP r0, #0 (sets Z, never writes r0)
955        let movs_r0_0 = 0xE3B0_0000u32; // MOV r0, #0, S=1
956        let cmp_r0_0 = 0xE350_0000u32; // CMP r0, #0
957        let (mut cpu, mut bus) = boot(&[movs_r0_0, cmp_r0_0]);
958        cpu.step(&mut bus);
959        assert!(cpu.regs.cpsr.flags.z);
960        // Perturb r0 to a nonzero value: CMP against #0 now must clear Z (proving CMP actually
961        // read the CURRENT r0, not some cached/stale value) while still never writing r0 back.
962        cpu.regs.r[0] = 0x1234;
963        cpu.step(&mut bus);
964        assert_eq!(
965            cpu.regs.r[0], 0x1234,
966            "CMP must never write a destination register"
967        );
968        assert!(
969            !cpu.regs.cpsr.flags.z,
970            "CMP r0,#0 with r0==0x1234 must clear Z"
971        );
972    }
973
974    #[test]
975    fn r15_reads_as_address_plus_8_inside_data_processing() {
976        // At address 0: MOV r0, pc  (opcode = E1A0000F)
977        let mov_r0_pc = 0xE1A0_000Fu32;
978        let (mut cpu, mut bus) = boot(&[mov_r0_pc]);
979        cpu.step(&mut bus);
980        assert_eq!(
981            cpu.regs.r[0], 8,
982            "PC read as an operand is address+8, not address"
983        );
984    }
985
986    #[test]
987    fn register_specified_shift_amount_also_adds_4_when_rs_is_r15() {
988        // ADD r0, r0, r1, LSR r15 -- op=ADD, I=0, S=0, Rn=0, Rd=0, Rs=15, shift_type=LSR(01),
989        // Rm=1. (LSL(00) is deliberately NOT used here: on this exact decode table a
990        // register-shifted-by-register LSL always collides with Multiply/MultiplyLong/SWP's
991        // sparse index carve-outs -- verified by brute-force sweep over every ALU op -- so it's
992        // simply unreachable as a data-processing encoding, matching real ARM hardware's own
993        // Multiply-vs-data-processing disambiguation. LSR sidesteps that collision entirely.)
994        let add_r0_r1_lsr_r15 = 0xE080_0FB1u32;
995        let (mut cpu, mut bus) = boot(&[add_r0_r1_lsr_r15]);
996        cpu.regs.r[0] = 0;
997        cpu.regs.r[1] = 0x1000;
998        // R15 during Execute is 8 (power-on default); if rs==15 gets the documented +4, the
999        // shift amount is 12 (0x1000 >> 12 == 1); without it, the shift would be 8 (>> 8 == 16).
1000        cpu.step(&mut bus);
1001        assert_eq!(
1002            cpu.regs.r[0], 1,
1003            "rs==15 must read as address+12 (the usual +8, plus the extra internal cycle), \
1004             exactly like rm/rn do -- the source applies +4 to all three independently"
1005        );
1006    }
1007
1008    #[test]
1009    fn branch_with_link_sets_lr_to_the_next_sequential_instruction() {
1010        // At address 0: BL +8 (branch to address 0x10: offset encodes (target-(pc_at_fetch+8))>>2)
1011        // Encode BL to absolute address 0x10 from pc=0: offset_words = (0x10 - 8) >> 2 = 2
1012        let bl = 0xEB00_0002u32; // cond=AL, L=1, offset=2
1013        let (mut cpu, mut bus) = boot(&[bl]);
1014        cpu.step(&mut bus);
1015        assert_eq!(
1016            cpu.regs.r[15],
1017            0x10 + 8,
1018            "branch target re-establishes the +8 pipeline offset"
1019        );
1020        assert_eq!(
1021            cpu.regs.r[14], 4,
1022            "LR = branch instruction's own address + 4"
1023        );
1024    }
1025
1026    #[test]
1027    fn msr_writes_only_the_flag_bits_when_masked_to_flags_only() {
1028        // MSR CPSR_f, r0 with r0 = 0xF000_0000 (N=Z=C=V=1); mask=1000 (flags only).
1029        let msr_flags_only = 0xE128_F000u32; // MSR CPSR_f, r0
1030        let (mut cpu, mut bus) = boot(&[msr_flags_only]);
1031        cpu.regs.r[0] = 0xF000_0000;
1032        let mode_before = cpu.regs.cpsr.mode;
1033        cpu.step(&mut bus);
1034        assert!(
1035            cpu.regs.cpsr.flags.n
1036                && cpu.regs.cpsr.flags.z
1037                && cpu.regs.cpsr.flags.c
1038                && cpu.regs.cpsr.flags.v
1039        );
1040        assert_eq!(
1041            cpu.regs.cpsr.mode, mode_before,
1042            "flags-only MSR must not touch mode"
1043        );
1044    }
1045
1046    #[test]
1047    fn mrs_reads_back_the_packed_cpsr() {
1048        // MRS r0, CPSR
1049        let mrs_r0_cpsr = 0xE10F_0000u32;
1050        let (mut cpu, mut bus) = boot(&[mrs_r0_cpsr]);
1051        cpu.step(&mut bus);
1052        assert_eq!(cpu.regs.r[0], cpu.regs.cpsr.to_u32());
1053    }
1054
1055    #[test]
1056    fn software_interrupt_enters_supervisor_and_parks_the_return_address() {
1057        // SWI at address 0.
1058        let swi = 0xEF00_0000u32;
1059        let (mut cpu, mut bus) = boot(&[swi]);
1060        let decode_addr_before = cpu.pipeline.decode.address;
1061        cpu.step(&mut bus);
1062        assert_eq!(
1063            cpu.pipeline.execute.address, 0x08,
1064            "jumped to the SoftwareIrq vector"
1065        );
1066        assert_eq!(
1067            cpu.regs.r[15],
1068            0x08 + 8,
1069            "pipeline re-establishes +8 at the new vector"
1070        );
1071        assert_eq!(cpu.regs.r[14], decode_addr_before);
1072        assert_eq!(cpu.regs.cpsr.mode, mode::SUPERVISOR);
1073        assert!(cpu.regs.cpsr.irq_disable);
1074    }
1075
1076    #[test]
1077    fn movs_pc_restores_cpsr_from_spsr_like_an_exception_return() {
1078        // SWI at address 0, MOVS pc, lr placed exactly at the SoftwareIrq vector (0x08 -> word
1079        // index 2), matching how a real handler would sit there.
1080        let swi = 0xEF00_0000u32;
1081        let movs_pc_lr = 0xE1B0_F00Eu32; // MOVS pc, lr
1082        let (mut cpu, mut bus) = boot(&[swi, 0, movs_pc_lr, 0]);
1083        // Simulate a User-mode program making the SWI call, so the round trip is meaningful
1084        // (returning to a DIFFERENT mode than the handler ran in, not a same-mode no-op).
1085        cpu.regs.switch_mode(mode::USER);
1086        cpu.step(&mut bus); // SWI -> Supervisor; SPSR_svc = the User-mode CPSR just saved
1087        assert_eq!(cpu.regs.cpsr.mode, mode::SUPERVISOR);
1088        let lr = cpu.regs.r[14];
1089        cpu.step(&mut bus); // MOVS pc, lr at the vector -- the idiomatic exception return
1090        assert_eq!(
1091            cpu.regs.cpsr.mode,
1092            mode::USER,
1093            "CPSR restored from SPSR_svc, back to the mode the SWI was made from"
1094        );
1095        assert_eq!(
1096            cpu.regs.r[15],
1097            lr + 8,
1098            "PC = LR, then the pipeline re-establishes +8"
1099        );
1100    }
1101
1102    #[test]
1103    fn undefined_opcode_traps_to_the_undefined_vector() {
1104        // Coprocessor-space opcode bits27-24=1100 (the ARM "Coprocessor Data Transfer" class):
1105        // ST018 has no coprocessor, and the reference InitArmOpTable never populates index range
1106        // 0xC00-0xEFF with anything, so it stays the InvalidOp default -- unlike, say, a
1107        // register-offset Single Data Transfer with bit4 set (real ARM's "undefined instruction
1108        // space"), which Mesen2's table does NOT carve out of its SingleDataTransfer range, so
1109        // this port must not treat that pattern as undefined either (matching the source, not
1110        // the general ARM ARM, since this is a port of Mesen2's exact behavior).
1111        let undefined = 0xEC00_0000u32;
1112        let (mut cpu, mut bus) = boot(&[undefined]);
1113        cpu.step(&mut bus);
1114        assert_eq!(
1115            cpu.pipeline.execute.address, 0x04,
1116            "jumped to the Undefined vector"
1117        );
1118        assert_eq!(
1119            cpu.regs.r[15],
1120            0x04 + 8,
1121            "pipeline re-establishes +8 at the new vector"
1122        );
1123        assert_eq!(cpu.regs.cpsr.mode, mode::UNDEFINED);
1124    }
1125
1126    #[test]
1127    fn ldr_pre_indexed_with_no_writeback() {
1128        // LDR r0, [r1] -- immediate offset 0, pre-indexed, W=0: must NOT write r1 back.
1129        let ldr_r0_r1 = 0xE591_0000u32;
1130        let (mut cpu, mut bus) = boot(&[ldr_r0_r1]);
1131        cpu.regs.r[1] = 0x2000;
1132        bus.set_word(0x2000, 0xDEAD_BEEF);
1133        cpu.step(&mut bus);
1134        assert_eq!(cpu.regs.r[0], 0xDEAD_BEEF);
1135        assert_eq!(cpu.regs.r[1], 0x2000, "P=1,W=0 must not write back");
1136    }
1137
1138    #[test]
1139    fn ldr_post_indexed_always_writes_back_even_without_the_w_bit() {
1140        // LDR r0, [r1], #4 -- post-indexed: writeback happens unconditionally, even though the
1141        // encoded W bit is 0 (post-indexing itself implies writeback on real ARM hardware).
1142        let ldr_r0_r1_post4 = 0xE491_0004u32;
1143        let (mut cpu, mut bus) = boot(&[ldr_r0_r1_post4]);
1144        cpu.regs.r[1] = 0x2000;
1145        bus.set_word(0x2000, 0x1234_5678);
1146        cpu.step(&mut bus);
1147        assert_eq!(
1148            cpu.regs.r[0], 0x1234_5678,
1149            "loaded from the ORIGINAL address"
1150        );
1151        assert_eq!(
1152            cpu.regs.r[1], 0x2004,
1153            "post-indexed writeback always happens"
1154        );
1155    }
1156
1157    #[test]
1158    fn str_r15_stores_address_plus_12_not_plus_8() {
1159        // STR r15, [r1] -- storing R15 itself uses the +12 quirk (one MORE cycle than the usual
1160        // +8 read-as-operand exposure), a real, documented ARM6-class store timing detail.
1161        let str_r15_r1 = 0xE581_F000u32;
1162        let (mut cpu, mut bus) = boot(&[str_r15_r1]);
1163        cpu.regs.r[1] = 0x2000;
1164        cpu.step(&mut bus);
1165        assert_eq!(
1166            bus.word_at(0x2000),
1167            8 + 4,
1168            "stored value is address+12, not address+8"
1169        );
1170    }
1171
1172    #[test]
1173    fn ldm_ia_writeback_loads_registers_in_ascending_order() {
1174        // LDMIA r0!, {r1,r2}
1175        let ldmia_r0_r1_r2 = 0xE8B0_0006u32;
1176        let (mut cpu, mut bus) = boot(&[ldmia_r0_r1_r2]);
1177        cpu.regs.r[0] = 0x3000;
1178        bus.set_word(0x3000, 0x1111_1111);
1179        bus.set_word(0x3004, 0x2222_2222);
1180        cpu.step(&mut bus);
1181        assert_eq!(cpu.regs.r[1], 0x1111_1111);
1182        assert_eq!(cpu.regs.r[2], 0x2222_2222);
1183        assert_eq!(
1184            cpu.regs.r[0], 0x3008,
1185            "writeback: base + register_count * 4"
1186        );
1187    }
1188
1189    #[test]
1190    fn stm_ia_writeback_stores_registers_in_ascending_order() {
1191        // STMIA r0!, {r1,r2}
1192        let stmia_r0_r1_r2 = 0xE8A0_0006u32;
1193        let (mut cpu, mut bus) = boot(&[stmia_r0_r1_r2]);
1194        cpu.regs.r[0] = 0x3000;
1195        cpu.regs.r[1] = 0xAAAA_AAAA;
1196        cpu.regs.r[2] = 0xBBBB_BBBB;
1197        cpu.step(&mut bus);
1198        assert_eq!(bus.word_at(0x3000), 0xAAAA_AAAA);
1199        assert_eq!(bus.word_at(0x3004), 0xBBBB_BBBB);
1200        assert_eq!(cpu.regs.r[0], 0x3008);
1201    }
1202
1203    #[test]
1204    fn ldm_with_an_empty_register_list_transfers_only_r15_but_advances_as_if_all_16_did() {
1205        // LDM r0, {} -- the documented empty-list glitch: regMask becomes 0x8000 (R15 only) but
1206        // regCount is forced to 16 for address-advancement purposes.
1207        let ldm_r0_empty = 0xE890_0000u32;
1208        let (mut cpu, mut bus) = boot(&[ldm_r0_empty]);
1209        cpu.regs.r[0] = 0x4000;
1210        bus.set_word(0x4000, 0x9000); // the word LDM will load into R15
1211        cpu.step(&mut bus);
1212        assert_eq!(
1213            cpu.pipeline.execute.address, 0x9000,
1214            "R15 was loaded from address 0x4000 despite the empty encoded list"
1215        );
1216    }
1217
1218    #[test]
1219    fn ldm_with_s_bit_and_pc_in_the_list_restores_cpsr_from_spsr() {
1220        // LDM r0, {r1, pc}^ -- S=1 with R15 in the list: no temporary User-mode switch during
1221        // the transfer (unlike LDM^ without PC, or any STM^), and CPSR is restored wholesale
1222        // from the CURRENT mode's SPSR after the transfer -- the LDM-based exception return.
1223        let ldm_r0_r1_pc_caret = 0xE8D0_8002u32;
1224        let (mut cpu, mut bus) = boot(&[ldm_r0_r1_pc_caret]);
1225        cpu.regs.switch_mode(mode::SUPERVISOR);
1226        cpu.regs.spsr_mut().mode = mode::USER; // simulate SPSR_svc holding a User-mode CPSR
1227        cpu.regs.r[0] = 0x5000;
1228        bus.set_word(0x5000, 0xCAFE_BABE); // -> r1
1229        bus.set_word(0x5004, 0x2000); // -> r15
1230        cpu.step(&mut bus);
1231        assert_eq!(cpu.regs.r[1], 0xCAFE_BABE);
1232        assert_eq!(
1233            cpu.regs.cpsr.mode,
1234            mode::USER,
1235            "CPSR restored from SPSR_svc"
1236        );
1237        assert_eq!(
1238            cpu.pipeline.execute.address, 0x2000,
1239            "R15 loaded from the list"
1240        );
1241    }
1242
1243    #[test]
1244    fn mul_computes_the_low_32_bits_and_sets_z_n() {
1245        // MULS r0, r1, r2  (r0 = r1 * r2, S=1)
1246        let muls_r0_r1_r2 = 0xE010_0291u32;
1247        let (mut cpu, mut bus) = boot(&[muls_r0_r1_r2]);
1248        cpu.regs.r[1] = 6;
1249        cpu.regs.r[2] = 7;
1250        cpu.step(&mut bus);
1251        assert_eq!(cpu.regs.r[0], 42);
1252        assert!(!cpu.regs.cpsr.flags.z);
1253        assert!(!cpu.regs.cpsr.flags.n);
1254    }
1255
1256    #[test]
1257    fn mla_accumulates_into_the_multiply_result() {
1258        // MLA r0, r1, r2, r3  (r0 = r1*r2 + r3)
1259        let mla_r0_r1_r2_r3 = 0xE020_3291u32;
1260        let (mut cpu, mut bus) = boot(&[mla_r0_r1_r2_r3]);
1261        cpu.regs.r[1] = 6;
1262        cpu.regs.r[2] = 7;
1263        cpu.regs.r[3] = 100;
1264        cpu.step(&mut bus);
1265        assert_eq!(cpu.regs.r[0], 142);
1266    }
1267
1268    #[test]
1269    fn umull_widens_to_64_bits_across_two_registers() {
1270        // UMULLS r3, r2, r0, r1  (r2:r3 = r0 * r1, unsigned, S=1)
1271        let umulls_r3_r2_r0_r1 = 0xE092_3190u32;
1272        let (mut cpu, mut bus) = boot(&[umulls_r3_r2_r0_r1]);
1273        cpu.regs.r[0] = 0xFFFF_FFFF;
1274        cpu.regs.r[1] = 2;
1275        cpu.step(&mut bus);
1276        let result = (u64::from(cpu.regs.r[2]) << 32) | u64::from(cpu.regs.r[3]);
1277        assert_eq!(result, u64::from(0xFFFF_FFFFu32) * 2);
1278        assert!(!cpu.regs.cpsr.flags.z);
1279    }
1280
1281    #[test]
1282    fn smull_sign_extends_negative_operands() {
1283        // SMULLS r3, r2, r0, r1  (r2:r3 = r0 * r1, signed, S=1)
1284        let smulls_r3_r2_r0_r1 = 0xE0D2_3190u32;
1285        let (mut cpu, mut bus) = boot(&[smulls_r3_r2_r0_r1]);
1286        cpu.regs.r[0] = (-5i32).cast_unsigned();
1287        cpu.regs.r[1] = 3;
1288        cpu.step(&mut bus);
1289        let result = ((u64::from(cpu.regs.r[2]) << 32) | u64::from(cpu.regs.r[3])).cast_signed();
1290        assert_eq!(result, -15);
1291        assert!(cpu.regs.cpsr.flags.n, "negative 64-bit result sets N");
1292    }
1293
1294    #[test]
1295    fn swp_reads_the_old_value_then_writes_the_new_one_atomically() {
1296        // SWP r0, r2, [r1]  (r0 = [r1]; [r1] = r2)
1297        let swp_r0_r2_r1 = 0xE101_0092u32;
1298        let (mut cpu, mut bus) = boot(&[swp_r0_r2_r1]);
1299        cpu.regs.r[1] = 0x2000;
1300        cpu.regs.r[2] = 0xBEEF_CAFE;
1301        bus.set_word(0x2000, 0x1111_1111);
1302        cpu.step(&mut bus);
1303        assert_eq!(cpu.regs.r[0], 0x1111_1111, "old value read into rd");
1304        assert_eq!(
1305            bus.word_at(0x2000),
1306            0xBEEF_CAFE,
1307            "new value written to the same address"
1308        );
1309    }
1310
1311    #[test]
1312    fn swpb_swaps_a_single_byte() {
1313        // SWPB r0, r2, [r1]
1314        let swpb_r0_r2_r1 = 0xE141_0092u32;
1315        let (mut cpu, mut bus) = boot(&[swpb_r0_r2_r1]);
1316        cpu.regs.r[1] = 0x2000;
1317        cpu.regs.r[2] = 0xAB;
1318        bus.set_word(0x2000, 0xFFFF_FF42); // low byte = 0x42
1319        cpu.step(&mut bus);
1320        assert_eq!(cpu.regs.r[0], 0x42, "old byte read into rd");
1321        assert_eq!(
1322            bus.word_at(0x2000),
1323            0xFFFF_FFAB,
1324            "only the low byte was swapped"
1325        );
1326    }
1327
1328    #[test]
1329    fn multiply_cycles_matches_the_documented_early_termination_rule() {
1330        assert_eq!(multiply_cycles(0), 1);
1331        assert_eq!(multiply_cycles(0xFF), 1);
1332        assert_eq!(multiply_cycles(0xFFFF_FF00), 1);
1333        assert_eq!(multiply_cycles(0xFFFF), 2);
1334        assert_eq!(multiply_cycles(0x00FF_FFFF), 3);
1335        assert_eq!(multiply_cycles(0x7FFF_FFFF), 4);
1336    }
1337}