m68k 0.10.10

A safe Rust M68000 family CPU emulator
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
//! Exception handling.
//!
//! Defines exception vectors and processing.

use super::cpu::{CpuCore, SFLAG_SET};
use super::execute::RUN_MODE_BERR_AERR_RESET;
use super::memory::AddressBus;
use super::types::CpuType;

/// Exception vector numbers.
pub mod vector {
    /// Reset vector-table entry containing the initial supervisor stack pointer.
    pub const RESET_SSP: u32 = 0;
    /// Reset vector-table entry containing the initial program counter.
    pub const RESET_PC: u32 = 1;
    /// Bus-error exception vector.
    pub const BUS_ERROR: u32 = 2;
    /// Address-error exception vector.
    pub const ADDRESS_ERROR: u32 = 3;
    /// Illegal-instruction exception vector.
    pub const ILLEGAL_INSTRUCTION: u32 = 4;
    /// Integer divide-by-zero exception vector.
    pub const ZERO_DIVIDE: u32 = 5;
    /// CHK/CHK2 bounds exception vector.
    pub const CHK: u32 = 6;
    /// TRAPV/TRAPcc exception vector.
    pub const TRAPV: u32 = 7;
    /// Privilege-violation exception vector.
    pub const PRIVILEGE_VIOLATION: u32 = 8;
    /// Instruction-trace exception vector.
    pub const TRACE: u32 = 9;
    /// Line-A emulator exception vector.
    pub const LINE_1010: u32 = 10;
    /// Line-F emulator and coprocessor exception vector.
    pub const LINE_1111: u32 = 11;
    /// Invalid exception-frame format vector (68010+).
    pub const FORMAT_ERROR: u32 = 14;
    /// Uninitialized interrupt-vector exception.
    pub const UNINITIALIZED_INTERRUPT: u32 = 15;
    /// Spurious-interrupt vector.
    pub const SPURIOUS_INTERRUPT: u32 = 24;
    /// Base vector for `TRAP #0` through `TRAP #15`.
    pub const TRAP_BASE: u32 = 32;

    /// 68060: integer instructions removed from silicon (MOVEP, CHK2/CMP2,
    /// CAS2, misaligned CAS, 64-bit MUL/DIV) trap here for the OS-side
    /// 68060 software package to emulate.
    pub const UNIMPLEMENTED_INTEGER: u32 = 61;
    /// 68060: FP operand data types the FPU no longer handles in hardware
    /// (packed decimal, denormals) - pre-instruction, format $0.
    pub const FP_UNSUPP_DATA_TYPE: u32 = 55;
    /// 68060: FP addressing forms dropped from silicon (dynamic-list
    /// FMOVEM, immediate packed operands, multi-register control-list
    /// FMOVEM.L #imm) - pre-instruction, format $0.
    pub const FP_UNIMPLEMENTED_EA: u32 = 60;

    /// 68020+ MMU configuration-error vector.
    pub const MMU_CONFIGURATION_ERROR: u32 = 56;
    /// 68020+ MMU illegal-operation vector.
    pub const MMU_ILLEGAL_OPERATION_ERROR: u32 = 57;
    /// 68020+ MMU access-level-violation vector.
    pub const MMU_ACCESS_LEVEL_VIOLATION_ERROR: u32 = 58;
}

/// 68060 fault status long word (FSLW) bits, as consumed by OS fault
/// handlers (bit names per MC68060UM Table 8-9 / Linux asm/traps.h).
pub mod fslw {
    /// Read access.
    pub const RW_R: u32 = 0x0100_0000;
    /// Write access.
    pub const RW_W: u32 = 0x0080_0000;
    /// Transfer size shift (bits 22-21): 00 long, 01 byte, 10 word.
    pub const SIZE_SHIFT: u32 = 21;
    /// Transfer modifier shift (bits 18-16): holds the function code.
    pub const TM_SHIFT: u32 = 16;
    /// Instruction (1) or operand (0) access.
    pub const IO: u32 = 0x0000_8000;
    /// Invalid descriptor in the root (level A) table.
    pub const PTA: u32 = 0x0000_1000;
    /// Invalid descriptor in the pointer (level B) table.
    pub const PTB: u32 = 0x0000_0800;
    /// Invalid indirect page descriptor.
    pub const IL: u32 = 0x0000_0400;
    /// Page fault (invalid page descriptor).
    pub const PF: u32 = 0x0000_0200;
    /// Supervisor protection violation.
    pub const SP: u32 = 0x0000_0100;
    /// Write protection violation.
    pub const WP: u32 = 0x0000_0080;
    /// Bus error on table search.
    pub const TWE: u32 = 0x0000_0040;
    /// Bus error on read.
    pub const RE: u32 = 0x0000_0020;
    /// Bus error on write.
    pub const WE: u32 = 0x0000_0010;
}

/// Compose the 68060 FSLW for an access error.
fn fslw_060(
    write: bool,
    instruction: bool,
    size: u32,
    fc: u16,
    cause: Option<crate::mmu::MmuFaultCause>,
) -> u32 {
    use crate::mmu::MmuFaultCause;
    let mut w = if write { fslw::RW_W } else { fslw::RW_R };
    // As on the 68040, there is no three-byte size encoding (0b11 is a line
    // transfer), so a three-byte operand reports as a long.
    w |= match size {
        1 => 0b01 << fslw::SIZE_SHIFT,
        2 => 0b10 << fslw::SIZE_SHIFT,
        _ => 0, // long
    };
    w |= u32::from(fc & 7) << fslw::TM_SHIFT;
    if instruction {
        w |= fslw::IO;
    }
    w |= match cause {
        Some(MmuFaultCause::PointerA) => fslw::PTA,
        Some(MmuFaultCause::PointerB) => fslw::PTB,
        Some(MmuFaultCause::Indirect) => fslw::IL,
        Some(MmuFaultCause::PageFault) => fslw::PF,
        Some(MmuFaultCause::WriteProtect) => fslw::WP,
        Some(MmuFaultCause::SupervisorProtect) => fslw::SP,
        Some(MmuFaultCause::TableWalkBusError) => fslw::TWE,
        // A physical bus error on the access itself.
        Some(MmuFaultCause::AccessBusError) | None => {
            if write {
                fslw::WE
            } else {
                fslw::RE
            }
        }
    };
    w
}

/// Function code bits for exception stack frames.
pub mod fc {
    /// User-data address space.
    pub const USER_DATA: u16 = 1;
    /// User-program address space.
    pub const USER_PROGRAM: u16 = 2;
    /// Supervisor-data address space.
    pub const SUPERVISOR_DATA: u16 = 5;
    /// Supervisor-program address space.
    pub const SUPERVISOR_PROGRAM: u16 = 6;
}

impl CpuCore {
    #[inline]
    fn push_16_raw<B: AddressBus>(&mut self, bus: &mut B, value: u16) {
        self.dar[15] = self.dar[15].wrapping_sub(2);
        bus.write_word(self.address(self.dar[15]), value);
    }

    #[inline]
    fn push_32_raw<B: AddressBus>(&mut self, bus: &mut B, value: u32) {
        self.dar[15] = self.dar[15].wrapping_sub(4);
        bus.write_long(self.address(self.dar[15]), value);
    }

    #[inline]
    fn fake_push_16_raw(&mut self) {
        self.dar[15] = self.dar[15].wrapping_sub(2);
    }

    #[inline]
    fn fake_push_32_raw(&mut self) {
        self.dar[15] = self.dar[15].wrapping_sub(4);
    }

    /// Push the 68000's 3-word exception frame (SR + PC) in the bus order the
    /// hardware uses: PC low word first, then SR, then PC high word.
    pub(crate) fn push_exception_frame_68000<B: AddressBus>(
        &mut self,
        bus: &mut B,
        stacked_pc: u32,
        sr: u16,
    ) {
        let sp = self.dar[15].wrapping_sub(6);
        self.dar[15] = sp;
        self.write_16(bus, sp.wrapping_add(4), (stacked_pc & 0xFFFF) as u16);
        self.write_16(bus, sp, sr);
        self.write_16(bus, sp.wrapping_add(2), (stacked_pc >> 16) as u16);
    }

    /// Process TRAP #n instruction.
    ///
    /// TRAP #n pushes the four-word format $0 frame on every 68010+ model
    /// (M68020UM table 6-5 lists TRAP #N under format $0; the earlier
    /// Musashi-derived format $2 frame on the 020/030 was wrong) with the
    /// next instruction's PC stacked.
    pub fn trap<B: AddressBus>(&mut self, bus: &mut B, trap_num: u8) -> i32 {
        let vector = vector::TRAP_BASE + (trap_num & 0xF) as u32;
        self.take_exception(bus, vector)
    }

    /// Group-2 instruction exceptions: CHK/CHK2, TRAPcc/TRAPV (and FTRAPcc),
    /// zero divide, and trace. The stacked PC is the next instruction on
    /// every model; the 68020+ push the six-word format $2 frame whose
    /// extra long is the address of the instruction that caused the
    /// exception (M68020UM table 6-5), so a handler can decode or skip it.
    pub(crate) fn take_group2_exception<B: AddressBus>(&mut self, bus: &mut B, vector: u32) -> i32 {
        let old_sr = self.get_sr();

        // Enter supervisor, clear trace.
        self.set_s_flag(SFLAG_SET);
        self.t1_flag = 0;
        self.t0_flag = 0;

        let next_pc = self.pc;
        match self.cpu_type {
            super::types::CpuType::M68000 => {
                self.push_exception_frame_68000(bus, next_pc, old_sr);
            }
            super::types::CpuType::M68010 | super::types::CpuType::SCC68070 => {
                self.push_16(bus, (vector as u16) << 2);
                self.push_32(bus, next_pc);
                self.push_16(bus, old_sr);
            }
            _ => {
                let vec_word = (vector as u16) << 2;
                self.push_32(bus, self.ppc);
                self.push_16(bus, 0x2000 | (vec_word & 0x0FFF));
                self.push_32(bus, next_pc);
                self.push_16(bus, old_sr);
            }
        }

        self.jump_vector(bus, vector);
        self.exception_cycles(vector)
    }

    /// Process CHK exception.
    ///
    /// The caller (exec_chk) reports the comparison's internal clocks before
    /// calling this (8 for trap-on-too-big, 10 for trap-on-negative).
    pub fn exception_chk<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.take_group2_exception(bus, vector::CHK)
    }

    /// Process zero divide exception.
    pub fn exception_zero_divide<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.take_group2_exception(bus, vector::ZERO_DIVIDE)
    }

    /// Process privilege violation exception.
    pub fn exception_privilege<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.take_exception(bus, vector::PRIVILEGE_VIOLATION)
    }

    /// Process trace exception.
    pub fn exception_trace<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        // A pending trace recovers the CPU from the STOP state: STOP executed
        // with the trace bit set in the incoming SR takes the trace exception
        // instead of remaining stopped (the trace has priority over both the
        // stopped state and its supervisor check).
        self.stopped &= !super::execute::STOP_LEVEL_STOP;
        // 4 internal clocks precede the trace frame's first stack write
        // (Moira execException TRACE: SYNC(4)).
        self.internal_cycles(4);
        self.take_group2_exception(bus, vector::TRACE)
    }

    /// Process address error exception.
    ///
    /// 68000 pushes additional info: access address, instruction register, status.
    pub fn exception_address_error<B: AddressBus>(
        &mut self,
        bus: &mut B,
        address: u32,
        write: bool,
        instruction: bool,
    ) -> i32 {
        let old_sr = self.get_sr();
        let was_supervisor = (old_sr & 0x2000) != 0;

        // Enter supervisor mode, clear trace
        self.set_s_flag(SFLAG_SET);
        self.t1_flag = 0;
        self.t0_flag = 0;

        // Build function code / status word
        // Bits: R/W (4), I/N (3), Function Code (2:0)
        let fc = if was_supervisor {
            if instruction {
                fc::SUPERVISOR_PROGRAM
            } else {
                fc::SUPERVISOR_DATA
            }
        } else if instruction {
            fc::USER_PROGRAM
        } else {
            fc::USER_DATA
        };
        let status_word = fc | if write { 0 } else { 0x10 } | if instruction { 0 } else { 0x08 };

        match self.cpu_type {
            CpuType::M68000 => {
                // 68000 address error frame (14 bytes):
                // Push: PC (4), SR (2), IR (2), Access Address (4), Status Word (2)
                //
                // Use raw bus writes (no alignment/address-error checks) to avoid recursive
                // address-error exceptions if the stack pointer is itself misaligned.
                self.push_16_raw(bus, status_word);
                self.push_32_raw(bus, address);
                self.push_16_raw(bus, self.ir as u16);
                self.push_16_raw(bus, old_sr);
                self.push_32_raw(bus, self.ppc);
            }
            CpuType::M68010 | CpuType::SCC68070 => {
                // 68010 uses the "format 8" (0x8) bus/address error stack frame (29 words).
                // We intentionally mirror Musashi's placeholder implementation here: most internal
                // words are zero/undefined and we primarily preserve the format/vector word, PC, SR.
                //
                // Layout (from Musashi m68kcpu.h m68ki_stack_frame_1000):
                // - lots of internal words (mostly not written)
                // - fault address (long) = 0
                // - special status word = 0
                // - format/vector word = 0x8000 | (vector<<2)
                // - stacked PC (long)
                // - stacked SR (word)
                for _ in 0..8 {
                    self.fake_push_32_raw();
                }
                self.push_16_raw(bus, 0); // instruction input buffer
                self.fake_push_16_raw();
                self.push_16_raw(bus, 0); // data input buffer
                self.fake_push_16_raw();
                self.push_16_raw(bus, 0); // data output buffer
                self.fake_push_16_raw();
                self.push_32_raw(bus, 0); // fault address
                self.push_16_raw(bus, 0); // special status word
                self.push_16_raw(bus, 0x8000 | ((vector::ADDRESS_ERROR as u16) << 2));
                self.push_32_raw(bus, self.ppc);
                self.push_16_raw(bus, old_sr);
            }
            _ => {
                // TODO: 68020+ address error stack frames (format A/B/7 variants) are not yet
                // implemented. Use a minimal 68010+ format-0-like frame to avoid totally losing
                // control flow, but this is not architecturally accurate.
                self.push_16_raw(bus, (vector::ADDRESS_ERROR as u16) << 2);
                self.push_32_raw(bus, self.ppc);
                self.push_16_raw(bus, old_sr);
                let _ = (status_word, address); // currently unused in this fallback
            }
        }

        // Jump to vector
        self.jump_vector(bus, vector::ADDRESS_ERROR);

        50 // Cycles for address error
    }

    /// Process bus error exception.
    pub fn exception_bus_error<B: AddressBus>(
        &mut self,
        bus: &mut B,
        address: u32,
        write: bool,
        instruction: bool,
        size: u32,
        cause: Option<crate::mmu::MmuFaultCause>,
    ) -> i32 {
        let old_sr = self.get_sr();
        let was_supervisor = (old_sr & 0x2000) != 0;
        // The faulted write's data, captured before any frame push: the
        // pushes below are ordinary translated writes and each one replaces
        // pending_fault_wdata, so reading the field mid-frame would hand the
        // handler's writeback (WB3D / the 030 data output buffer) a zero
        // instead of the value the guest was storing.
        let fault_wdata = self.pending_fault_wdata;

        // Enter supervisor mode, clear trace
        self.set_s_flag(SFLAG_SET);
        self.t1_flag = 0;
        self.t0_flag = 0;

        // Build function code / status word. A MOVES data fault carries the
        // SFC/DFC space it faulted in, not the CPU-state code: the handler
        // reads the fc back out of the frame's SSW to PTEST the faulted
        // space (mmu.library does exactly this). The override is consumed
        // here: SFC/DFC only governs the MOVES operand cycle, never the
        // exception dispatch itself -- the frame pushes and vector fetch
        // below run in supervisor space (a stuck override would walk the
        // user root pointer for the vector fetch, which on a kernel with
        // split user/supervisor trees reads garbage).
        let fc = match self.mmu_fc_override.take() {
            Some(ofc) => ofc as u16,
            None => {
                if was_supervisor {
                    if instruction {
                        fc::SUPERVISOR_PROGRAM
                    } else {
                        fc::SUPERVISOR_DATA
                    }
                } else if instruction {
                    fc::USER_PROGRAM
                } else {
                    fc::USER_DATA
                }
            }
        };
        let status_word = fc | if write { 0 } else { 0x10 } | if instruction { 0 } else { 0x08 };

        match self.cpu_type {
            CpuType::M68000 => {
                // 68000 bus error frame (same as address error)
                self.push_16_raw(bus, status_word);
                self.push_32_raw(bus, address);
                self.push_16_raw(bus, self.ir as u16);
                self.push_16_raw(bus, old_sr);
                self.push_32_raw(bus, self.ppc);
            }
            CpuType::M68010 | CpuType::SCC68070 => {
                // 68010 format 8 (0x8) bus error frame (placeholder, matching Musashi).
                for _ in 0..8 {
                    self.fake_push_32_raw();
                }
                self.push_16_raw(bus, 0); // instruction input buffer
                self.fake_push_16_raw();
                self.push_16_raw(bus, 0); // data input buffer
                self.fake_push_16_raw();
                self.push_16_raw(bus, 0); // data output buffer
                self.fake_push_16_raw();
                self.push_32_raw(bus, 0); // fault address
                self.push_16_raw(bus, 0); // special status word
                self.push_16_raw(bus, 0x8000 | ((vector::BUS_ERROR as u16) << 2));
                self.push_32_raw(bus, self.ppc);
                self.push_16_raw(bus, old_sr);
                let _ = (status_word, address); // currently unused in this placeholder
            }
            _ if self.is_060() => {
                // 68060 access-error stack frame (format $4, 8 words): the
                // fault address long at +$08 and the fault status long word
                // (FSLW) at +$0C. The instruction has been rolled back, so
                // RTE restarts it (same demand-paging model as the 040).
                let fslw = fslw_060(write, instruction, size, fc, cause);
                let fmt_vec = 0x4000 | ((vector::BUS_ERROR as u16) << 2);
                self.push_32(bus, fslw);
                self.push_32(bus, address);
                self.push_16(bus, fmt_vec);
                self.push_32(bus, self.ppc); // restart PC
                self.push_16(bus, old_sr);
                let _ = status_word;
            }
            _ if self.is_040() => {
                // 68040 access-error stack frame (format 7, 30 words). The
                // caller (trigger_bus_error) has already rolled the instruction
                // back, so we stack PPC and leave the writeback/continuation
                // fields clear: RTE then restarts the faulting instruction
                // (demand-paging / Enforcer fix-and-retry model).
                // Layout (Musashi m68ki_stack_frame_0111), pushed high->low.
                //
                // SSW: RW (bit 8), SZ (bits 6:5, 040 encoding: 00 long,
                // 01 byte, 10 word), TM (bits 2:0) = function code, and ATC
                // (bit 10) when the fault came out of the MMU table walk
                // rather than the physical bus. ATC is what an OS-level
                // page-fault handler (mmu.library, VMM, Enforcer) tests to
                // tell a translation fault it must service from a real bus
                // error it must pass on, so a translation fault without it
                // gurus instead of demand-faulting.
                let rw = if write { 0 } else { 0x0100 }; // SSW bit 8: 1 = read
                let atc = if cause.is_some() { 0x0400u16 } else { 0 }; // SSW bit 10
                // The 68040 bus has no three-byte encoding (SZ 11 is a line
                // transfer), so a three-byte operand reports as a long here.
                let sz = match size {
                    1 => 0x0020, // byte
                    2 => 0x0040, // word
                    _ => 0x0000, // long
                };
                let ssw = rw | atc | sz | (fc & 0x7); // TM = function code
                let fmt_vec = 0x7000 | ((vector::BUS_ERROR as u16) << 2);
                // A normal faulted write is reported in writeback slot 3 (V
                // bit, size and TM in WB3S; address and data in WB3A/WB3D),
                // matching real 68040 silicon and the WinUAE/Amiberry MMU
                // reference (cpummu.cpp sets regs.wb3_status/wb3_data on a
                // write fault and clears wb2 -- WB2 is reserved for MOVE16
                // cacheline writes). MuGuardianAngel completes an allowed
                // write by storing WB3D to WB3A, and Enforcer/MuForce discard
                // a protected store by clearing WB3S.V; RTE (below) honours
                // the cleared V. Putting the store in WB2 instead makes MuGA
                // read a zero WB3D and clobber the target with 0.
                let wb3s = if write {
                    0x0080 | sz | (fc & 0x7) // V | SZ | TM
                } else {
                    0
                };
                for _ in 0..3 {
                    self.push_32(bus, 0); // PD3, PD2, PD1
                }
                self.push_32(bus, 0); // WB1D / PD0
                self.push_32(bus, 0); // WB1A
                self.push_32(bus, 0); // WB2D
                self.push_32(bus, 0); // WB2A
                self.push_32(bus, if write { fault_wdata } else { 0 }); // WB3D
                self.push_32(bus, if write { address } else { 0 }); // WB3A
                self.push_32(bus, address); // fault address
                self.push_16(bus, 0); // WB1S
                self.push_16(bus, 0); // WB2S
                self.push_16(bus, wb3s); // WB3S
                self.push_16(bus, ssw); // special status word
                self.push_32(bus, address); // effective address
                self.push_16(bus, fmt_vec); // format 7 / vector offset
                self.push_32(bus, self.ppc); // restart PC
                self.push_16(bus, old_sr);
            }
            _ => {
                // 68020/68030 long bus-cycle fault frame (format $B, 46
                // words, M68030UM 8.2). Real silicon dumps pipeline state
                // here and RTE *continues* the faulted instruction, with the
                // handler able to rerun or complete the data cycle through
                // the DF bit and the data input/output buffers. This core
                // rolls the faulting instruction back instead and stacks its
                // PC, so RTE restarts it from scratch: the pipeline dump and
                // writeback buffers are left zero, and a handler that
                // clears DF to suppress the rerun is not honoured (the
                // restart re-issues the access; harmless for memory, the
                // documented gap for side-effecting hardware registers).
                //
                // The special status word at +$0A is what a page-fault
                // handler (mmu.library, VMM, Enforcer) actually parses:
                // DF (bit 8) marks a faulted data cycle with its address in
                // the long at +$10, RW (bit 6) the direction, SIZ (bits 5:4,
                // 01 byte / 10 word / 00 long) the width, and FC2:0 the
                // address space. An instruction-fetch fault instead reports
                // a stage-B rerun (FB|RB, bits 14/12) with the fetch address
                // in the stage B address long at +$24, the shape real
                // handlers use to demand-page code.
                let data_fault = !instruction;
                let mut ssw: u16 = fc & 0x7;
                if data_fault {
                    ssw |= 0x0100; // DF: rerun data cycle
                    if !write {
                        ssw |= 0x0040; // RW: read
                    }
                    // SIZ 11 is the three-byte transfer the 68020/68030 bus
                    // performs for a dynamically sized access or a bit-field
                    // operand spanning three bytes.
                    ssw |= match size {
                        1 => 0x0010, // SIZ 01: byte
                        2 => 0x0020, // SIZ 10: word
                        3 => 0x0030, // SIZ 11: three byte
                        _ => 0x0000, // SIZ 00: long
                    };
                } else {
                    ssw |= 0x4000 | 0x1000; // FB|RB: rerun instruction stage B
                }
                let fmt_vec = 0xB000 | ((vector::BUS_ERROR as u16) << 2);
                // Pushed high address -> low: internal words +$38..$5A (18
                // words), version word +$36, internal +$30..$34 (3 words),
                // data input buffer +$2C, internal +$28/$2A, stage B address
                // +$24, internal +$1C..$22 (4 words), data output buffer
                // +$18, internal +$14/$16, data fault address +$10, stage B
                // and C pipe words +$0E/$0C, SSW +$0A, internal +$08.
                for _ in 0..9 {
                    self.push_32(bus, 0); // +$38..$5A
                }
                self.push_16(bus, 0); // +$36 version/internal
                for _ in 0..3 {
                    self.push_16(bus, 0); // +$30..$34
                }
                self.push_32(bus, 0); // +$2C data input buffer
                self.push_32(bus, 0); // +$28/$2A internal
                self.push_32(bus, if data_fault { 0 } else { address }); // +$24 stage B address
                self.push_32(bus, 0); // +$20/$22 internal
                self.push_32(bus, 0); // +$1C/$1E internal
                // +$18 data output buffer: the value of a faulted write, so
                // a handler can complete the write itself (clear DF).
                self.push_32(bus, if write { fault_wdata } else { 0 });
                self.push_32(bus, 0); // +$14/$16 internal
                self.push_32(bus, if data_fault { address } else { 0 }); // +$10 data fault address
                self.push_16(bus, 0); // +$0E pipe stage B
                self.push_16(bus, 0); // +$0C pipe stage C
                self.push_16(bus, ssw); // +$0A special status word
                self.push_16(bus, 0); // +$08 internal
                self.push_16(bus, fmt_vec); // +$06 format/vector
                self.push_32(bus, self.ppc); // +$02 restart PC
                self.push_16(bus, old_sr); // +$00 SR
            }
        }

        // Jump to vector
        self.jump_vector(bus, vector::BUS_ERROR);

        50 // Cycles for bus error
    }

    /// Common exception processing (simple frame: SR, PC).
    ///
    /// Implements double-fault detection: if an exception occurs while already
    /// processing an exception, the CPU halts (similar to x86 triple fault).
    pub fn take_exception<B: AddressBus>(&mut self, bus: &mut B, vector: u32) -> i32 {
        // Double-fault detection: if we're already processing an exception and
        // another exception occurs, halt the CPU. This prevents infinite recursion.
        if self.exception_processing {
            // Double fault - halt the CPU
            self.stopped = 1;
            self.run_mode = RUN_MODE_BERR_AERR_RESET;
            return 0;
        }

        // Mark that we're processing an exception. This flag is checked by translate()
        // to bypass MMU translation during exception frame writes.
        self.exception_processing = true;

        // Exception entry spends 4 internal clocks (vector number / state
        // capture) before the first stack write.
        self.internal_cycles(4);

        let old_sr = self.get_sr();

        // Match Musashi `m68ki_init_exception`: enter supervisor mode but do not modify M.
        self.set_s_flag(SFLAG_SET);

        // Clear trace flags
        self.t1_flag = 0;
        self.t0_flag = 0;

        // Select stacked PC (Musashi-style: traps/interrupts stack the next PC; faults stack PPC).
        let stacked_pc = if vector == vector::TRAPV
            || vector == vector::TRACE
            || vector == vector::ZERO_DIVIDE
            || (vector::TRAP_BASE..vector::TRAP_BASE + 16).contains(&vector)
            || (24..=31).contains(&vector)
        {
            self.pc
        } else {
            self.ppc
        };

        // Match Musashi `m68ki_stack_frame_0000`:
        // - 68000: push PC, then SR (3-word frame)
        // - 68010+: push vector offset word (vector<<2), then PC, then SR (format 0)
        if self.cpu_type == super::types::CpuType::M68000 {
            self.push_exception_frame_68000(bus, stacked_pc, old_sr);
        } else {
            self.push_16(bus, (vector as u16) << 2);
            self.push_32(bus, stacked_pc);
            self.push_16(bus, old_sr);
        }

        // Read vector and jump
        self.jump_vector(bus, vector);

        // Done processing exception
        self.exception_processing = false;

        self.exception_cycles(vector)
    }

    /// 68060 "FP unimplemented instruction" exception: Line-F vector with
    /// the six-word format $2 frame the 68060SP dispatches on (fmt/vector
    /// word $202C). The frame's PC is the NEXT instruction (every extension
    /// word must be consumed before calling this), the EA field holds the
    /// calculated operand address (0 when the operand is not in memory),
    /// and FPIAR points at the faulting instruction - the 060SP fetches
    /// the opcode through FPIAR, not the frame.
    pub(crate) fn take_fp_unimp_060<B: AddressBus>(&mut self, bus: &mut B, ea: u32) -> i32 {
        self.fpiar = self.ppc;
        let old_sr = self.get_sr();
        self.set_s_flag(SFLAG_SET);
        self.t1_flag = 0;
        self.t0_flag = 0;
        let vec_word = (vector::LINE_1111 as u16) << 2;
        self.push_32(bus, ea);
        self.push_16(bus, 0x2000 | (vec_word & 0x0FFF));
        self.push_32(bus, self.pc);
        self.push_16(bus, old_sr);
        self.jump_vector(bus, vector::LINE_1111);
        self.exception_cycles(vector::LINE_1111)
    }

    /// 68060 "FPU disabled" exception (PCR.DFP set, or an LC/EC060): the
    /// eight-word format $4 frame ($402C) whose +$0C long holds the PC of
    /// the faulted instruction so the OS can enable the FPU and restart.
    /// The stacked PC also restarts the instruction; FPIAR is untouched
    /// (the FPU never saw the instruction).
    pub(crate) fn take_fp_disabled_060<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        let old_sr = self.get_sr();
        self.set_s_flag(SFLAG_SET);
        self.t1_flag = 0;
        self.t0_flag = 0;
        let vec_word = (vector::LINE_1111 as u16) << 2;
        self.push_32(bus, self.ppc); // PC of the faulted instruction
        self.push_32(bus, 0); // effective address (unused for disabled)
        self.push_16(bus, 0x4000 | (vec_word & 0x0FFF));
        self.push_32(bus, self.ppc); // restart the instruction
        self.push_16(bus, old_sr);
        self.jump_vector(bus, vector::LINE_1111);
        self.exception_cycles(vector::LINE_1111)
    }

    /// Get cycles for exception processing.
    fn exception_cycles(&self, vector: u32) -> i32 {
        match vector {
            vector::RESET_SSP | vector::RESET_PC => 40,
            vector::BUS_ERROR | vector::ADDRESS_ERROR => 50,
            vector::ILLEGAL_INSTRUCTION => 34,
            vector::ZERO_DIVIDE => 38,
            vector::CHK => 40,
            vector::TRAPV => 34,
            vector::PRIVILEGE_VIOLATION => 34,
            vector::TRACE => 34,
            vector::LINE_1010 | vector::LINE_1111 => 34,
            24..=31 => 44, // Autovector interrupts
            _ => 34,       // TRAPs and user vectors
        }
    }

    /// Check for trace exception after instruction execution.
    /// SR-writing instructions (MOVE to SR, ORI/ANDI/EORI to SR) run the
    /// pending-trace check against the OLD T0 on the 68020+: writing SR is
    /// a pipeline-synchronizing event, so a set T0 traces the instruction
    /// even when the write clears it. Marking the boundary as a change of
    /// flow lets check_trace() (which reads the pre-instruction SR) see it.
    pub(crate) fn trace_t0_sr_write(&mut self) {
        if !self.is_pre_68020 {
            self.change_of_flow = true;
        }
    }

    /// The 68040 additionally runs the T0 check after a small set of
    /// pipeline-synchronizing instructions that are not flow changes:
    /// NOP, MOVEC, MOVE to USP, CAS, CAS2, MOVES, TAS, PFLUSH and PTEST
    /// (WinUAE's trace_t0_68040_only sites).
    pub(crate) fn trace_t0_68040_sync(&mut self) {
        if matches!(
            self.cpu_type,
            super::types::CpuType::M68EC040
                | super::types::CpuType::M68LC040
                | super::types::CpuType::M68040
        ) {
            self.change_of_flow = true;
        }
    }

    /// Determine whether the instruction that just retired requests a trace
    /// exception and clear the one-instruction flow-change latch.
    ///
    /// T1 traces every instruction. On the 68020 and later, T0 traces only
    /// flow-changing or pipeline-synchronizing instructions. The decision
    /// uses [`CpuCore::sr_save`], the status register captured before the
    /// instruction, so an RTE that restores a trace bit does not trace itself.
    pub fn check_trace(&mut self) -> bool {
        // T1 trace: trace after every instruction
        // T0 trace: trace only on change-of-flow (68020+)
        // We check the T1/T0 bits from sr_save (SR BEFORE instruction), not current SR.
        // This is important for RTE: if RTE restores T1=1, we don't take trace immediately.
        let t1_before = (self.sr_save & 0x8000) != 0;
        let t0_before = (self.sr_save & 0x4000) != 0;
        let should_trace = t1_before || (t0_before && self.change_of_flow);
        // Reset change_of_flow flag after checking
        self.change_of_flow = false;
        should_trace
    }

    // ========== Fallback Exception Methods for Unhandled Traps ==========

    /// Take A-line exception for an unhandled trap.
    ///
    /// Call this to manually take an A-line exception (vector 10).
    /// Note: With `step()`, this is called automatically. This method is
    /// primarily used internally by `step_with_hle_handler()` when the
    /// handler returns `false`.
    ///
    /// This rewinds the PC to the trap instruction before taking the exception.
    pub fn take_aline_exception<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.pc = self.ppc; // Rewind PC to the trap instruction
        self.take_exception(bus, vector::LINE_1010)
    }

    /// Take F-line exception for an unhandled trap.
    ///
    /// Call this after receiving `StepResult::FlineTrap` if you cannot handle the trap.
    /// This rewinds the PC and takes the real hardware exception (vector 11).
    pub fn take_fline_exception<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.pc = self.ppc; // Rewind PC to the trap instruction
        self.take_exception(bus, vector::LINE_1111)
    }

    /// Take TRAP #n exception for an unhandled trap.
    ///
    /// Call this after receiving `StepResult::TrapInstruction` if you cannot handle the trap.
    /// This takes the real hardware exception (vector 32+n).
    ///
    /// Note: Unlike A-line/F-line exceptions, TRAP exceptions stack the PC of the
    /// instruction AFTER the TRAP, so we do NOT rewind PC here.
    pub fn take_trap_exception<B: AddressBus>(&mut self, bus: &mut B, trap_num: u8) -> i32 {
        // Don't rewind PC - TRAP stacks the NEXT instruction address
        self.trap(bus, trap_num)
    }

    /// Take BKPT exception for an unhandled breakpoint.
    ///
    /// Call this after receiving `StepResult::Breakpoint` if you cannot handle it.
    /// This rewinds the PC and takes the illegal instruction exception (vector 4).
    pub fn take_bkpt_exception<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.pc = self.ppc; // Rewind PC to the breakpoint instruction
        self.take_exception(bus, vector::ILLEGAL_INSTRUCTION)
    }

    /// Take illegal instruction exception for an unhandled illegal opcode.
    ///
    /// Call this after receiving `StepResult::IllegalInstruction` if you cannot handle it.
    /// This rewinds the PC and takes the real hardware exception (vector 4).
    pub fn take_illegal_exception<B: AddressBus>(&mut self, bus: &mut B) -> i32 {
        self.pc = self.ppc; // Rewind PC to the illegal instruction
        self.take_exception(bus, vector::ILLEGAL_INSTRUCTION)
    }
}