Skip to main content

dsp56300_core/
lib.rs

1//! DSP56300 ISA definitions: architectural constants, register indices, status
2//! register bits, instruction decoder, encoder, and parallel ALU types.
3
4pub mod decode;
5pub mod encode;
6
7// DSP56300 architectural constants
8pub const PERIPH_BASE: u32 = 0xFFFF80;
9pub const PERIPH_SIZE: usize = 128;
10pub const PC_MASK: u32 = 0xFFFFFF;
11
12/// Mask a value to the 24-bit program address space.
13#[inline(always)]
14pub const fn mask_pc(v: u32) -> u32 {
15    v & PC_MASK
16}
17
18// Register file indices (6-bit DDDDDD encoding per DSP56300 instruction set).
19pub mod reg {
20    pub const X0: usize = 0x04;
21    pub const X1: usize = 0x05;
22    pub const Y0: usize = 0x06;
23    pub const Y1: usize = 0x07;
24    pub const A0: usize = 0x08;
25    pub const B0: usize = 0x09;
26    pub const A2: usize = 0x0a;
27    pub const B2: usize = 0x0b;
28    pub const A1: usize = 0x0c;
29    pub const B1: usize = 0x0d;
30    pub const A: usize = 0x0e;
31    pub const B: usize = 0x0f;
32
33    pub const R0: usize = 0x10;
34    pub const R1: usize = 0x11;
35    pub const R2: usize = 0x12;
36    pub const R3: usize = 0x13;
37    pub const R4: usize = 0x14;
38    pub const R5: usize = 0x15;
39    pub const R6: usize = 0x16;
40    pub const R7: usize = 0x17;
41
42    pub const N0: usize = 0x18;
43    pub const N1: usize = 0x19;
44    pub const N2: usize = 0x1a;
45    pub const N3: usize = 0x1b;
46    pub const N4: usize = 0x1c;
47    pub const N5: usize = 0x1d;
48    pub const N6: usize = 0x1e;
49    pub const N7: usize = 0x1f;
50
51    pub const M0: usize = 0x20;
52    pub const M1: usize = 0x21;
53    pub const M2: usize = 0x22;
54    pub const M3: usize = 0x23;
55    pub const M4: usize = 0x24;
56    pub const M5: usize = 0x25;
57    pub const M6: usize = 0x26;
58    pub const M7: usize = 0x27;
59
60    pub const EP: usize = 0x2a;
61
62    pub const VBA: usize = 0x30;
63    pub const SC: usize = 0x31;
64
65    pub const SZ: usize = 0x38;
66    pub const SR: usize = 0x39;
67    pub const OMR: usize = 0x3a;
68    pub const SP: usize = 0x3b;
69    pub const SSH: usize = 0x3c;
70    pub const SSL: usize = 0x3d;
71    pub const LA: usize = 0x3e;
72    pub const LC: usize = 0x3f;
73
74    pub const NULL: usize = 0x00;
75    // Internal temporary register used by REP to save/restore LC (manual page 13-160:
76    // "LC -> TEMP ... TEMP -> LC"). Uses reserved EEE slot 0x2B.
77    pub const TEMP: usize = 0x2b;
78
79    pub const COUNT: usize = 0x40;
80}
81
82/// QQ register mapping for standard multiply instructions (mpyi/maci/etc).
83pub fn qq_reg(qq: u8) -> usize {
84    match qq {
85        0 => reg::X0,
86        1 => reg::Y0,
87        2 => reg::X1,
88        3 => reg::Y1,
89        _ => unreachable!(),
90    }
91}
92
93/// QQ register mapping for MulShift instructions (mpy/mpyr/mac/macr S,#n,D).
94/// Different from the standard qq mapping used by mpyi/maci/etc.
95pub fn qq_reg_mulshift(qq: u8) -> usize {
96    match qq {
97        0 => reg::Y1,
98        1 => reg::X0,
99        2 => reg::Y0,
100        3 => reg::X1,
101        _ => unreachable!(),
102    }
103}
104
105/// QQQQ register pair mapping for DMAC/MpySU/MacSU instructions.
106/// 4-bit field per Table 12-16 "Data ALU Multiply Operands Encoding 4".
107/// All 16 values (0x0-0xF) are valid.
108pub fn qqqq_regs(qqqq: u8) -> Option<(usize, usize)> {
109    match qqqq {
110        0x0 => Some((reg::X0, reg::X0)),
111        0x1 => Some((reg::Y0, reg::Y0)),
112        0x2 => Some((reg::X1, reg::X0)),
113        0x3 => Some((reg::Y1, reg::Y0)),
114        0x4 => Some((reg::X0, reg::Y1)),
115        0x5 => Some((reg::Y0, reg::X0)),
116        0x6 => Some((reg::X1, reg::Y0)),
117        0x7 => Some((reg::Y1, reg::X1)),
118        0x8 => Some((reg::X1, reg::X1)),
119        0x9 => Some((reg::Y1, reg::Y1)),
120        0xA => Some((reg::X0, reg::X1)),
121        0xB => Some((reg::Y0, reg::Y1)),
122        0xC => Some((reg::Y1, reg::X0)),
123        0xD => Some((reg::X0, reg::Y0)),
124        0xE => Some((reg::Y0, reg::X1)),
125        0xF => Some((reg::X1, reg::Y1)),
126        _ => None,
127    }
128}
129
130/// SSS register mapping (Table 12-13, S1 column): 3-bit field, values 0-1 reserved.
131pub fn sss_reg(sss: u8) -> Option<usize> {
132    match sss {
133        2 => Some(reg::A1),
134        3 => Some(reg::B1),
135        4 => Some(reg::X0),
136        5 => Some(reg::Y0),
137        6 => Some(reg::X1),
138        7 => Some(reg::Y1),
139        _ => None,
140    }
141}
142
143/// QQQ register mapping (Table 12-13, S2 column): 3-bit field, values 0-1 reserved.
144/// Differs from sss: 2=A0, 3=B0 (vs A1, B1 for sss).
145pub fn qqq_reg(qqq: u8) -> Option<usize> {
146    match qqq {
147        2 => Some(reg::A0),
148        3 => Some(reg::B0),
149        4 => Some(reg::X0),
150        5 => Some(reg::Y0),
151        6 => Some(reg::X1),
152        7 => Some(reg::Y1),
153        _ => None,
154    }
155}
156
157/// GGG register mapping for CMPU: 3-bit field, values 1-3 reserved.
158/// Value 0 selects the opposite accumulator from `d`.
159pub fn ggg_reg(ggg: u8, d: Accumulator) -> Option<usize> {
160    match ggg {
161        0 => Some(match d {
162            Accumulator::A => reg::B,
163            Accumulator::B => reg::A,
164        }),
165        4 => Some(reg::X0),
166        5 => Some(reg::Y0),
167        6 => Some(reg::X1),
168        7 => Some(reg::Y1),
169        _ => None,
170    }
171}
172
173// Status register bit positions
174pub mod sr {
175    pub const C: u32 = 0;
176    pub const V: u32 = 1;
177    pub const Z: u32 = 2;
178    pub const N: u32 = 3;
179    pub const U: u32 = 4;
180    pub const E: u32 = 5;
181    pub const L: u32 = 6;
182    pub const S: u32 = 7;
183    pub const I0: u32 = 8;
184    pub const I1: u32 = 9;
185    pub const S0: u32 = 10;
186    pub const S1: u32 = 11;
187    pub const SC: u32 = 13;
188    pub const DM: u32 = 14;
189    pub const LF: u32 = 15;
190    pub const FV: u32 = 16;
191    pub const SA: u32 = 17;
192    pub const CE: u32 = 19;
193    pub const SM: u32 = 20;
194    pub const RM: u32 = 21;
195}
196
197/// Architectural register masks applied when writing registers.
198///
199/// Each entry is the bitmask of valid bits for the corresponding `reg::*` index.
200/// Writes through `mask_reg()` or `store_reg()` are ANDed with this mask.
201/// This is the **single source of truth** for register widths.
202pub const REG_MASKS: [u32; reg::COUNT] = {
203    let mut m = [0xFFFF_FFFFu32; reg::COUNT];
204    m[reg::X0] = 0x00FF_FFFF;
205    m[reg::X1] = 0x00FF_FFFF;
206    m[reg::Y0] = 0x00FF_FFFF;
207    m[reg::Y1] = 0x00FF_FFFF;
208    m[reg::A0] = 0x00FF_FFFF;
209    m[reg::B0] = 0x00FF_FFFF;
210    m[reg::A2] = 0x0000_00FF;
211    m[reg::B2] = 0x0000_00FF;
212    m[reg::A1] = 0x00FF_FFFF;
213    m[reg::B1] = 0x00FF_FFFF;
214    m[reg::A] = 0x00FF_FFFF;
215    m[reg::B] = 0x00FF_FFFF;
216    let mut i = 0;
217    while i < 8 {
218        m[reg::R0 + i] = 0x00FF_FFFF;
219        m[reg::N0 + i] = 0x00FF_FFFF;
220        m[reg::M0 + i] = 0x00FF_FFFF;
221        i += 1;
222    }
223    m[reg::EP] = 0x00FF_FFFF; // 24-bit extension pointer
224    m[reg::VBA] = 0x00FF_FF00; // VBA[7:0] are read-only and always cleared
225    m[reg::SC] = 0x0000_001F; // 5-bit stack counter
226    m[reg::SZ] = 0x00FF_FFFF; // 24-bit stack size
227    m[reg::SR] = 0x00FB_EFFF; // bits 12 and 18 reserved
228    m[reg::OMR] = 0x00FF_FFDF; // bit 5 reserved
229    m[reg::SP] = 0x0000_003F; // 6-bit in non-extended mode
230    m[reg::SSH] = 0x00FF_FFFF;
231    m[reg::SSL] = 0x00FF_FFFF;
232    m[reg::LA] = 0x00FF_FFFF;
233    m[reg::LC] = 0x00FF_FFFF;
234    m[reg::TEMP] = 0x00FF_FFFF;
235    m[reg::NULL] = 0;
236    m
237};
238
239/// Mask a value to the architectural width of register `r`.
240#[inline(always)]
241pub const fn mask_reg(r: usize, v: u32) -> u32 {
242    v & REG_MASKS[r]
243}
244
245/// Register names (indexed by register number, matching `reg::*` constants).
246pub const REGISTER_NAMES: [&str; 64] = [
247    "", "", "", "", "x0", "x1", "y0", "y1", "a0", "b0", "a2", "b2", "a1", "b1", "a", "b", "r0",
248    "r1", "r2", "r3", "r4", "r5", "r6", "r7", "n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "m0",
249    "m1", "m2", "m3", "m4", "m5", "m6", "m7", "", "", "ep", "", "", "", "", "", "vba", "sc", "",
250    "", "", "", "", "", "sz", "sr", "omr", "sp", "ssh", "ssl", "la", "lc",
251];
252
253/// Condition code names (indexed by 4-bit CCCC field).
254pub const CC_NAMES: [&str; 16] = [
255    "cc", "ge", "ne", "pl", "nn", "ec", "lc", "gt", "cs", "lt", "eq", "mi", "nr", "es", "ls", "le",
256];
257
258/// Long-move register names for L: parallel moves.
259pub const REGISTERS_LMOVE: [&str; 8] = ["a10", "b10", "x", "y", "a", "b", "ab", "ba"];
260
261/// TCC register pairs: [src, dst] indexed by (opcode>>3) & 0xF.
262pub const REGISTERS_TCC: [[usize; 2]; 16] = [
263    [reg::B, reg::A],
264    [reg::A, reg::B],
265    [reg::NULL, reg::NULL],
266    [reg::NULL, reg::NULL],
267    [reg::NULL, reg::NULL],
268    [reg::NULL, reg::NULL],
269    [reg::NULL, reg::NULL],
270    [reg::NULL, reg::NULL],
271    [reg::X0, reg::A],
272    [reg::X0, reg::B],
273    [reg::Y0, reg::A],
274    [reg::Y0, reg::B],
275    [reg::X1, reg::A],
276    [reg::X1, reg::B],
277    [reg::Y1, reg::A],
278    [reg::Y1, reg::B],
279];
280
281// Memory spaces
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283#[repr(u32)]
284pub enum MemSpace {
285    X = 0,
286    Y = 1,
287    P = 2,
288}
289
290impl MemSpace {
291    /// Convert from opcode bit (0 = X, 1 = Y). Panics on invalid values.
292    pub fn xy(bit: u32) -> Self {
293        match bit {
294            0 => Self::X,
295            1 => Self::Y,
296            _ => unreachable!(),
297        }
298    }
299}
300
301/// Accumulator selector (bit d in many encodings).
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum Accumulator {
304    A = 0,
305    B = 1,
306}
307
308/// Operation type for MulShift (S,#n,D) instructions.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum MulShiftOp {
311    Mpy = 0,
312    Mpyr = 1,
313    Mac = 2,
314    Macr = 3,
315}
316
317/// Condition code (4-bit CCCC field).
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319#[repr(u8)]
320pub enum CondCode {
321    CC = 0, // carry clear (HS)
322    GE = 1,
323    NE = 2,
324    PL = 3,
325    NN = 4,
326    EC = 5,
327    LC = 6,
328    GT = 7,
329    CS = 8, // carry set (LO)
330    LT = 9,
331    EQ = 10,
332    MI = 11,
333    NR = 12,
334    ES = 13,
335    LS = 14,
336    LE = 15,
337}
338
339impl CondCode {
340    pub fn from_bits(bits: u32) -> Self {
341        assert!(bits < 16);
342        // Safety: all values 0-15 are valid
343        unsafe { std::mem::transmute(bits as u8) }
344    }
345}
346
347/// Decoded parallel ALU operation (bits 7:0 of a parallel instruction).
348///
349/// The encoding is defined in DSP56300FM Tables 12-19 and 12-20:
350/// - Non-multiply (0x00-0x7F): `0 JJJ D kkk`
351/// - Multiply (0x80-0xFF): `1 QQQ d kkk`
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353pub enum ParallelAlu {
354    /// 0x00: No ALU operation (move-only parallel)
355    Move,
356
357    // --- 0x01-0x0F: JJJ=000, acc-to-acc (source is opposite of d) ---
358    TfrAcc {
359        src: Accumulator,
360        d: Accumulator,
361    },
362    Addr {
363        src: Accumulator,
364        d: Accumulator,
365    },
366    Tst {
367        d: Accumulator,
368    },
369    CmpAcc {
370        src: Accumulator,
371        d: Accumulator,
372    },
373    Subr {
374        src: Accumulator,
375        d: Accumulator,
376    },
377    CmpmAcc {
378        src: Accumulator,
379        d: Accumulator,
380    },
381
382    // --- 0x10-0x1F: JJJ=001, acc-to-acc ops ---
383    AddAcc {
384        src: Accumulator,
385        d: Accumulator,
386    },
387    Rnd {
388        d: Accumulator,
389    },
390    Addl {
391        src: Accumulator,
392        d: Accumulator,
393    },
394    Clr {
395        d: Accumulator,
396    },
397    SubAcc {
398        src: Accumulator,
399        d: Accumulator,
400    },
401    /// 0x1D: always max a,b
402    Max,
403    /// 0x15: always maxm a,b
404    Maxm,
405    Subl {
406        src: Accumulator,
407        d: Accumulator,
408    },
409    Not {
410        d: Accumulator,
411    },
412
413    // --- 0x20-0x3F: JJJ=01x, X/Y register pair source ---
414    AddXY {
415        hi: usize,
416        lo: usize,
417        d: Accumulator,
418    },
419    Adc {
420        hi: usize,
421        lo: usize,
422        d: Accumulator,
423    },
424    SubXY {
425        hi: usize,
426        lo: usize,
427        d: Accumulator,
428    },
429    Sbc {
430        hi: usize,
431        lo: usize,
432        d: Accumulator,
433    },
434    Asr {
435        d: Accumulator,
436    },
437    Lsr {
438        d: Accumulator,
439    },
440    Abs {
441        d: Accumulator,
442    },
443    Ror {
444        d: Accumulator,
445    },
446    Asl {
447        d: Accumulator,
448    },
449    Lsl {
450        d: Accumulator,
451    },
452    Neg {
453        d: Accumulator,
454    },
455    Rol {
456        d: Accumulator,
457    },
458
459    // --- 0x40-0x7F: JJJ=1xx, single register source (X0/Y0/X1/Y1) ---
460    AddReg {
461        src: usize,
462        d: Accumulator,
463    },
464    TfrReg {
465        src: usize,
466        d: Accumulator,
467    },
468    Or {
469        src: usize,
470        d: Accumulator,
471    },
472    Eor {
473        src: usize,
474        d: Accumulator,
475    },
476    SubReg {
477        src: usize,
478        d: Accumulator,
479    },
480    CmpReg {
481        src: usize,
482        d: Accumulator,
483    },
484    And {
485        src: usize,
486        d: Accumulator,
487    },
488    CmpmReg {
489        src: usize,
490        d: Accumulator,
491    },
492
493    // --- 0x80-0xFF: multiply/MAC (1 QQQ d kkk) ---
494    Mpy {
495        negate: bool,
496        s1: usize,
497        s2: usize,
498        d: Accumulator,
499    },
500    Mpyr {
501        negate: bool,
502        s1: usize,
503        s2: usize,
504        d: Accumulator,
505    },
506    Mac {
507        negate: bool,
508        s1: usize,
509        s2: usize,
510        d: Accumulator,
511    },
512    Macr {
513        negate: bool,
514        s1: usize,
515        s2: usize,
516        d: Accumulator,
517    },
518
519    /// Undefined encoding (0x04, 0x08, 0x0C, 0x15)
520    Undefined,
521}
522
523/// QQQ register pair mapping for parallel multiply instructions (Table 12-16, Encoding 1).
524/// QQQ is bits 6:4 of the ALU byte (0x80-0xFF).
525fn qqq_mpy_regs(qqq: u8) -> (usize, usize) {
526    match qqq {
527        0 => (reg::X0, reg::X0),
528        1 => (reg::Y0, reg::Y0),
529        2 => (reg::X1, reg::X0),
530        3 => (reg::Y1, reg::Y0),
531        4 => (reg::X0, reg::Y1),
532        5 => (reg::Y0, reg::X0),
533        6 => (reg::X1, reg::Y0),
534        7 => (reg::Y1, reg::X1),
535        _ => unreachable!(),
536    }
537}
538
539/// JJ register mapping for parallel ALU instructions (0x40-0x7F).
540const JJ_REGS: [usize; 4] = [reg::X0, reg::Y0, reg::X1, reg::Y1];
541
542/// Decode the parallel ALU byte (bits 7:0) into a structured `ParallelAlu`.
543pub fn decode_parallel_alu(b: u8) -> ParallelAlu {
544    use Accumulator::*;
545    use ParallelAlu::*;
546
547    if b == 0x00 {
548        return Move;
549    }
550
551    if b & 0x80 != 0 {
552        // Multiply: 1 QQQ d kkk
553        let qqq = (b >> 4) & 7;
554        let d = if (b >> 3) & 1 == 0 { A } else { B };
555        let negate = (b >> 2) & 1 == 1;
556        let op = b & 3;
557        let (s1, s2) = qqq_mpy_regs(qqq);
558        return match op {
559            0 => Mpy { negate, s1, s2, d },
560            1 => Mpyr { negate, s1, s2, d },
561            2 => Mac { negate, s1, s2, d },
562            3 => Macr { negate, s1, s2, d },
563            _ => unreachable!(),
564        };
565    }
566
567    // Non-multiply: 0 JJJ D kkk
568    let jjj = (b >> 4) & 7;
569    let d = if (b >> 3) & 1 == 0 { A } else { B };
570    let src = match d {
571        A => B,
572        B => A,
573    };
574    let kkk = b & 7;
575
576    match jjj {
577        0 => match kkk {
578            1 => TfrAcc { src, d },
579            2 => Addr { src, d },
580            3 => Tst { d },
581            5 => CmpAcc { src, d },
582            6 => Subr { src, d },
583            7 => CmpmAcc { src, d },
584            _ => Undefined, // 0, 4 (but 0x00=Move handled above, 0x04/0x08/0x0C are undefined)
585        },
586        1 => match kkk {
587            0 => AddAcc { src, d },
588            1 => Rnd { d },
589            2 => Addl { src, d },
590            3 => Clr { d },
591            4 => SubAcc { src, d },
592            5 => {
593                if b == 0x1D {
594                    Max
595                } else if b == 0x15 {
596                    Maxm
597                } else {
598                    Undefined
599                }
600            }
601            6 => Subl { src, d },
602            7 => Not { d },
603            _ => unreachable!(),
604        },
605        2 => {
606            // X register pair source (x1:x0)
607            match kkk {
608                0 => AddXY {
609                    hi: reg::X1,
610                    lo: reg::X0,
611                    d,
612                },
613                1 => Adc {
614                    hi: reg::X1,
615                    lo: reg::X0,
616                    d,
617                },
618                2 => Asr { d },
619                3 => Lsr { d },
620                4 => SubXY {
621                    hi: reg::X1,
622                    lo: reg::X0,
623                    d,
624                },
625                5 => Sbc {
626                    hi: reg::X1,
627                    lo: reg::X0,
628                    d,
629                },
630                6 => Abs { d },
631                7 => Ror { d },
632                _ => unreachable!(),
633            }
634        }
635        3 => {
636            // Y register pair source (y1:y0)
637            match kkk {
638                0 => AddXY {
639                    hi: reg::Y1,
640                    lo: reg::Y0,
641                    d,
642                },
643                1 => Adc {
644                    hi: reg::Y1,
645                    lo: reg::Y0,
646                    d,
647                },
648                2 => Asl { d },
649                3 => Lsl { d },
650                4 => SubXY {
651                    hi: reg::Y1,
652                    lo: reg::Y0,
653                    d,
654                },
655                5 => Sbc {
656                    hi: reg::Y1,
657                    lo: reg::Y0,
658                    d,
659                },
660                6 => Neg { d },
661                7 => Rol { d },
662                _ => unreachable!(),
663            }
664        }
665        4..=7 => {
666            // Single register source: JJ = jjj[1:0]
667            let jj = (jjj & 3) as usize;
668            let src = JJ_REGS[jj];
669            match kkk {
670                0 => AddReg { src, d },
671                1 => TfrReg { src, d },
672                2 => Or { src, d },
673                3 => Eor { src, d },
674                4 => SubReg { src, d },
675                5 => CmpReg { src, d },
676                6 => And { src, d },
677                7 => CmpmReg { src, d },
678                _ => unreachable!(),
679            }
680        }
681        _ => unreachable!(),
682    }
683}
684
685impl ParallelAlu {
686    /// Encode back to the ALU byte. Returns `None` for `Undefined`.
687    pub fn encode(&self) -> Option<u8> {
688        use ParallelAlu::*;
689
690        Some(match *self {
691            Move => 0x00,
692
693            // JJJ=000
694            TfrAcc { d, .. } => 0x01 | ((d as u8) << 3),
695            Addr { d, .. } => 0x02 | ((d as u8) << 3),
696            Tst { d } => 0x03 | ((d as u8) << 3),
697            CmpAcc { d, .. } => 0x05 | ((d as u8) << 3),
698            Subr { d, .. } => 0x06 | ((d as u8) << 3),
699            CmpmAcc { d, .. } => 0x07 | ((d as u8) << 3),
700
701            // JJJ=001
702            AddAcc { d, .. } => 0x10 | ((d as u8) << 3),
703            Rnd { d } => 0x11 | ((d as u8) << 3),
704            Addl { d, .. } => 0x12 | ((d as u8) << 3),
705            Clr { d } => 0x13 | ((d as u8) << 3),
706            SubAcc { d, .. } => 0x14 | ((d as u8) << 3),
707            Max => 0x1D,
708            Maxm => 0x15,
709            Subl { d, .. } => 0x16 | ((d as u8) << 3),
710            Not { d } => 0x17 | ((d as u8) << 3),
711
712            // JJJ=010 (X pair)
713            AddXY { hi, d, .. } if hi == reg::X1 => 0x20 | ((d as u8) << 3),
714            Adc { hi, d, .. } if hi == reg::X1 => 0x21 | ((d as u8) << 3),
715            Asr { d } => 0x22 | ((d as u8) << 3),
716            Lsr { d } => 0x23 | ((d as u8) << 3),
717            SubXY { hi, d, .. } if hi == reg::X1 => 0x24 | ((d as u8) << 3),
718            Sbc { hi, d, .. } if hi == reg::X1 => 0x25 | ((d as u8) << 3),
719            Abs { d } => 0x26 | ((d as u8) << 3),
720            Ror { d } => 0x27 | ((d as u8) << 3),
721
722            // JJJ=011 (Y pair)
723            AddXY { d, .. } => 0x30 | ((d as u8) << 3),
724            Adc { d, .. } => 0x31 | ((d as u8) << 3),
725            Asl { d } => 0x32 | ((d as u8) << 3),
726            Lsl { d } => 0x33 | ((d as u8) << 3),
727            SubXY { d, .. } => 0x34 | ((d as u8) << 3),
728            Sbc { d, .. } => 0x35 | ((d as u8) << 3),
729            Neg { d } => 0x36 | ((d as u8) << 3),
730            Rol { d } => 0x37 | ((d as u8) << 3),
731
732            // JJJ=1xx (single register)
733            AddReg { src, d } => 0x40 | (jj_encode(src)? << 4) | ((d as u8) << 3),
734            TfrReg { src, d } => 0x41 | (jj_encode(src)? << 4) | ((d as u8) << 3),
735            Or { src, d } => 0x42 | (jj_encode(src)? << 4) | ((d as u8) << 3),
736            Eor { src, d } => 0x43 | (jj_encode(src)? << 4) | ((d as u8) << 3),
737            SubReg { src, d } => 0x44 | (jj_encode(src)? << 4) | ((d as u8) << 3),
738            CmpReg { src, d } => 0x45 | (jj_encode(src)? << 4) | ((d as u8) << 3),
739            And { src, d } => 0x46 | (jj_encode(src)? << 4) | ((d as u8) << 3),
740            CmpmReg { src, d } => 0x47 | (jj_encode(src)? << 4) | ((d as u8) << 3),
741
742            // Multiply
743            Mpy { negate, s1, s2, d } => {
744                0x80 | (qqq_mpy_encode(s1, s2)? << 4) | ((d as u8) << 3) | ((negate as u8) << 2)
745            }
746            Mpyr { negate, s1, s2, d } => {
747                0x81 | (qqq_mpy_encode(s1, s2)? << 4) | ((d as u8) << 3) | ((negate as u8) << 2)
748            }
749            Mac { negate, s1, s2, d } => {
750                0x82 | (qqq_mpy_encode(s1, s2)? << 4) | ((d as u8) << 3) | ((negate as u8) << 2)
751            }
752            Macr { negate, s1, s2, d } => {
753                0x83 | (qqq_mpy_encode(s1, s2)? << 4) | ((d as u8) << 3) | ((negate as u8) << 2)
754            }
755
756            Undefined => return None,
757        })
758    }
759
760    /// The destination accumulator of this ALU operation, if any.
761    /// Returns `None` for `Move`, `Undefined`, and comparison/test ops (`Cmp*`, `Cmpm*`, `Tst`).
762    pub fn dest_accumulator(&self) -> Option<Accumulator> {
763        use ParallelAlu::*;
764        match *self {
765            Move | Undefined => None,
766            // Comparisons and test only set condition codes
767            CmpAcc { .. } | CmpmAcc { .. } | Tst { .. } | CmpReg { .. } | CmpmReg { .. } => None,
768            // Max/Maxm always write to B (with A as source)
769            Max | Maxm => Some(Accumulator::B),
770            // All other variants have a `d` field
771            TfrAcc { d, .. }
772            | Addr { d, .. }
773            | Subr { d, .. }
774            | AddAcc { d, .. }
775            | Rnd { d }
776            | Addl { d, .. }
777            | Clr { d }
778            | SubAcc { d, .. }
779            | Subl { d, .. }
780            | Not { d }
781            | AddXY { d, .. }
782            | Adc { d, .. }
783            | SubXY { d, .. }
784            | Sbc { d, .. }
785            | Asr { d }
786            | Lsr { d }
787            | Abs { d }
788            | Ror { d }
789            | Asl { d }
790            | Lsl { d }
791            | Neg { d }
792            | Rol { d }
793            | AddReg { d, .. }
794            | TfrReg { d, .. }
795            | Or { d, .. }
796            | Eor { d, .. }
797            | SubReg { d, .. }
798            | And { d, .. }
799            | Mpy { d, .. }
800            | Mpyr { d, .. }
801            | Mac { d, .. }
802            | Macr { d, .. } => Some(d),
803        }
804    }
805
806    /// Parse from the display text format (inverse of `Display`).
807    ///
808    /// Accepts strings like `"add x,a"`, `"mpy +x0,y1,b"`, `"move"`, etc.
809    pub fn from_text(text: &str) -> Option<ParallelAlu> {
810        use ParallelAlu::*;
811
812        fn parse_acc(s: &str) -> Option<Accumulator> {
813            match s {
814                "a" => Some(Accumulator::A),
815                "b" => Some(Accumulator::B),
816                _ => None,
817            }
818        }
819
820        fn parse_jj_reg(s: &str) -> Option<usize> {
821            match s {
822                "x0" => Some(reg::X0),
823                "y0" => Some(reg::Y0),
824                "x1" => Some(reg::X1),
825                "y1" => Some(reg::Y1),
826                _ => None,
827            }
828        }
829
830        fn parse_xy_pair(s: &str) -> Option<(usize, usize)> {
831            match s {
832                "x" => Some((reg::X1, reg::X0)),
833                "y" => Some((reg::Y1, reg::Y0)),
834                _ => None,
835            }
836        }
837
838        fn parse_qqq_pair(s1: &str, s2: &str) -> Option<(usize, usize)> {
839            let r1 = parse_jj_reg(s1)?;
840            let r2 = parse_jj_reg(s2)?;
841            // Validate it's a legal QQQ combination
842            match (r1, r2) {
843                (reg::X0, reg::X0)
844                | (reg::Y0, reg::Y0)
845                | (reg::X1, reg::X0)
846                | (reg::Y1, reg::Y0)
847                | (reg::X0, reg::Y1)
848                | (reg::Y0, reg::X0)
849                | (reg::X1, reg::Y0)
850                | (reg::Y1, reg::X1) => Some((r1, r2)),
851                _ => None,
852            }
853        }
854
855        if text == "move" {
856            return Some(Move);
857        }
858
859        let (mnemonic, operands) = text.split_once(' ')?;
860        let ops: Vec<&str> = operands.split(',').collect();
861
862        match mnemonic {
863            // Unary ops (1 operand = accumulator)
864            "tst" | "rnd" | "clr" | "not" | "asr" | "lsr" | "abs" | "ror" | "asl" | "lsl"
865            | "neg" | "rol" => {
866                let d = parse_acc(ops.first()?)?;
867                match mnemonic {
868                    "tst" => Some(Tst { d }),
869                    "rnd" => Some(Rnd { d }),
870                    "clr" => Some(Clr { d }),
871                    "not" => Some(Not { d }),
872                    "asr" => Some(Asr { d }),
873                    "lsr" => Some(Lsr { d }),
874                    "abs" => Some(Abs { d }),
875                    "ror" => Some(Ror { d }),
876                    "asl" => Some(Asl { d }),
877                    "lsl" => Some(Lsl { d }),
878                    "neg" => Some(Neg { d }),
879                    "rol" => Some(Rol { d }),
880                    _ => unreachable!(),
881                }
882            }
883
884            // Binary ops: src,d -- source determines variant
885            "tfr" | "add" | "sub" | "cmp" | "cmpm" | "addr" | "subr" | "addl" | "subl" | "adc"
886            | "sbc" | "or" | "eor" | "and" => {
887                if ops.len() != 2 {
888                    return None;
889                }
890                let d = parse_acc(ops[1])?;
891                let src_str = ops[0];
892
893                // Try accumulator source (acc-to-acc)
894                if let Some(src) = parse_acc(src_str) {
895                    return match mnemonic {
896                        "tfr" => Some(TfrAcc { src, d }),
897                        "addr" => Some(Addr { src, d }),
898                        "cmp" => Some(CmpAcc { src, d }),
899                        "subr" => Some(Subr { src, d }),
900                        "cmpm" => Some(CmpmAcc { src, d }),
901                        "add" => Some(AddAcc { src, d }),
902                        "addl" => Some(Addl { src, d }),
903                        "sub" => Some(SubAcc { src, d }),
904                        "subl" => Some(Subl { src, d }),
905                        _ => None,
906                    };
907                }
908
909                // Try XY pair source (x/y)
910                if let Some((hi, lo)) = parse_xy_pair(src_str) {
911                    return match mnemonic {
912                        "add" => Some(AddXY { hi, lo, d }),
913                        "adc" => Some(Adc { hi, lo, d }),
914                        "sub" => Some(SubXY { hi, lo, d }),
915                        "sbc" => Some(Sbc { hi, lo, d }),
916                        _ => None,
917                    };
918                }
919
920                // Try single register source (x0/y0/x1/y1)
921                if let Some(src) = parse_jj_reg(src_str) {
922                    return match mnemonic {
923                        "add" => Some(AddReg { src, d }),
924                        "tfr" => Some(TfrReg { src, d }),
925                        "or" => Some(Or { src, d }),
926                        "eor" => Some(Eor { src, d }),
927                        "sub" => Some(SubReg { src, d }),
928                        "cmp" => Some(CmpReg { src, d }),
929                        "and" => Some(And { src, d }),
930                        "cmpm" => Some(CmpmReg { src, d }),
931                        _ => None,
932                    };
933                }
934
935                None
936            }
937
938            // Special: max a,b / maxm a,b
939            "max" | "maxm" => {
940                if ops.len() == 2 && ops[0] == "a" && ops[1] == "b" {
941                    Some(if mnemonic == "max" { Max } else { Maxm })
942                } else {
943                    None
944                }
945            }
946
947            // Multiply: mpy/mpyr/mac/macr [+/-]s1,s2,d
948            "mpy" | "mpyr" | "mac" | "macr" => {
949                if ops.len() != 3 {
950                    return None;
951                }
952                let d = parse_acc(ops[2])?;
953                let (negate, s1_str) = if let Some(rest) = ops[0].strip_prefix('-') {
954                    (true, rest)
955                } else if let Some(rest) = ops[0].strip_prefix('+') {
956                    (false, rest)
957                } else {
958                    return None;
959                };
960                let (s1, s2) = parse_qqq_pair(s1_str, ops[1])?;
961                match mnemonic {
962                    "mpy" => Some(Mpy { negate, s1, s2, d }),
963                    "mpyr" => Some(Mpyr { negate, s1, s2, d }),
964                    "mac" => Some(Mac { negate, s1, s2, d }),
965                    "macr" => Some(Macr { negate, s1, s2, d }),
966                    _ => unreachable!(),
967                }
968            }
969
970            _ => None,
971        }
972    }
973
974    /// Whether this is a logical or shift operation (writes only A1/B1, not full accumulator).
975    pub fn is_logical_or_shift(&self) -> bool {
976        use ParallelAlu::*;
977        matches!(
978            self,
979            And { .. }
980                | Or { .. }
981                | Eor { .. }
982                | Not { .. }
983                | Asl { .. }
984                | Asr { .. }
985                | Lsl { .. }
986                | Lsr { .. }
987                | Rol { .. }
988                | Ror { .. }
989        )
990    }
991}
992
993/// Encode a register index to the JJ field (2-bit, 0x40-0x7F range).
994fn jj_encode(src: usize) -> Option<u8> {
995    match src {
996        reg::X0 => Some(0),
997        reg::Y0 => Some(1),
998        reg::X1 => Some(2),
999        reg::Y1 => Some(3),
1000        _ => None,
1001    }
1002}
1003
1004/// Encode (s1, s2) register pair to the QQQ field (3-bit) for parallel multiply.
1005fn qqq_mpy_encode(s1: usize, s2: usize) -> Option<u8> {
1006    match (s1, s2) {
1007        (reg::X0, reg::X0) => Some(0),
1008        (reg::Y0, reg::Y0) => Some(1),
1009        (reg::X1, reg::X0) => Some(2),
1010        (reg::Y1, reg::Y0) => Some(3),
1011        (reg::X0, reg::Y1) => Some(4),
1012        (reg::Y0, reg::X0) => Some(5),
1013        (reg::X1, reg::Y0) => Some(6),
1014        (reg::Y1, reg::X1) => Some(7),
1015        _ => None,
1016    }
1017}
1018
1019/// Display name for a register pair used as X or Y source in parallel ALU.
1020fn xy_pair_name(hi: usize) -> &'static str {
1021    match hi {
1022        reg::X1 => "x",
1023        reg::Y1 => "y",
1024        _ => unreachable!(),
1025    }
1026}
1027
1028impl std::fmt::Display for ParallelAlu {
1029    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1030        use ParallelAlu::*;
1031
1032        match *self {
1033            Move => write!(f, "move"),
1034
1035            TfrAcc { src, d } => write!(f, "tfr {},{}", acc_name(src), acc_name(d)),
1036            Addr { src, d } => write!(f, "addr {},{}", acc_name(src), acc_name(d)),
1037            Tst { d } => write!(f, "tst {}", acc_name(d)),
1038            CmpAcc { src, d } => write!(f, "cmp {},{}", acc_name(src), acc_name(d)),
1039            Subr { src, d } => write!(f, "subr {},{}", acc_name(src), acc_name(d)),
1040            CmpmAcc { src, d } => write!(f, "cmpm {},{}", acc_name(src), acc_name(d)),
1041
1042            AddAcc { src, d } => write!(f, "add {},{}", acc_name(src), acc_name(d)),
1043            Rnd { d } => write!(f, "rnd {}", acc_name(d)),
1044            Addl { src, d } => write!(f, "addl {},{}", acc_name(src), acc_name(d)),
1045            Clr { d } => write!(f, "clr {}", acc_name(d)),
1046            SubAcc { src, d } => write!(f, "sub {},{}", acc_name(src), acc_name(d)),
1047            Max => write!(f, "max a,b"),
1048            Maxm => write!(f, "maxm a,b"),
1049            Subl { src, d } => write!(f, "subl {},{}", acc_name(src), acc_name(d)),
1050            Not { d } => write!(f, "not {}", acc_name(d)),
1051
1052            AddXY { hi, d, .. } => write!(f, "add {},{}", xy_pair_name(hi), acc_name(d)),
1053            Adc { hi, d, .. } => write!(f, "adc {},{}", xy_pair_name(hi), acc_name(d)),
1054            SubXY { hi, d, .. } => write!(f, "sub {},{}", xy_pair_name(hi), acc_name(d)),
1055            Sbc { hi, d, .. } => write!(f, "sbc {},{}", xy_pair_name(hi), acc_name(d)),
1056            Asr { d } => write!(f, "asr {}", acc_name(d)),
1057            Lsr { d } => write!(f, "lsr {}", acc_name(d)),
1058            Abs { d } => write!(f, "abs {}", acc_name(d)),
1059            Ror { d } => write!(f, "ror {}", acc_name(d)),
1060            Asl { d } => write!(f, "asl {}", acc_name(d)),
1061            Lsl { d } => write!(f, "lsl {}", acc_name(d)),
1062            Neg { d } => write!(f, "neg {}", acc_name(d)),
1063            Rol { d } => write!(f, "rol {}", acc_name(d)),
1064
1065            AddReg { src, d } => write!(f, "add {},{}", REGISTER_NAMES[src], acc_name(d)),
1066            TfrReg { src, d } => write!(f, "tfr {},{}", REGISTER_NAMES[src], acc_name(d)),
1067            Or { src, d } => write!(f, "or {},{}", REGISTER_NAMES[src], acc_name(d)),
1068            Eor { src, d } => write!(f, "eor {},{}", REGISTER_NAMES[src], acc_name(d)),
1069            SubReg { src, d } => write!(f, "sub {},{}", REGISTER_NAMES[src], acc_name(d)),
1070            CmpReg { src, d } => write!(f, "cmp {},{}", REGISTER_NAMES[src], acc_name(d)),
1071            And { src, d } => write!(f, "and {},{}", REGISTER_NAMES[src], acc_name(d)),
1072            CmpmReg { src, d } => write!(f, "cmpm {},{}", REGISTER_NAMES[src], acc_name(d)),
1073
1074            Mpy { negate, s1, s2, d } => {
1075                write!(
1076                    f,
1077                    "mpy {}{},{},{}",
1078                    if negate { "-" } else { "+" },
1079                    REGISTER_NAMES[s1],
1080                    REGISTER_NAMES[s2],
1081                    acc_name(d),
1082                )
1083            }
1084            Mpyr { negate, s1, s2, d } => {
1085                write!(
1086                    f,
1087                    "mpyr {}{},{},{}",
1088                    if negate { "-" } else { "+" },
1089                    REGISTER_NAMES[s1],
1090                    REGISTER_NAMES[s2],
1091                    acc_name(d),
1092                )
1093            }
1094            Mac { negate, s1, s2, d } => {
1095                write!(
1096                    f,
1097                    "mac {}{},{},{}",
1098                    if negate { "-" } else { "+" },
1099                    REGISTER_NAMES[s1],
1100                    REGISTER_NAMES[s2],
1101                    acc_name(d),
1102                )
1103            }
1104            Macr { negate, s1, s2, d } => {
1105                write!(
1106                    f,
1107                    "macr {}{},{},{}",
1108                    if negate { "-" } else { "+" },
1109                    REGISTER_NAMES[s1],
1110                    REGISTER_NAMES[s2],
1111                    acc_name(d),
1112                )
1113            }
1114
1115            Undefined => write!(f, "undefined"),
1116        }
1117    }
1118}
1119
1120fn acc_name(a: Accumulator) -> &'static str {
1121    match a {
1122        Accumulator::A => "a",
1123        Accumulator::B => "b",
1124    }
1125}
1126
1127/// Parallel move type (bits 23:20 of a parallel instruction).
1128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1129#[repr(u8)]
1130pub enum ParallelMoveType {
1131    /// No parallel data move
1132    Pm0 = 0,
1133    /// Register-to-register
1134    Pm1 = 1,
1135    /// Address register update (ea)
1136    Pm2 = 2,
1137    /// X memory with short displacement
1138    Pm3 = 3,
1139    /// X memory and register
1140    Pm4 = 4,
1141    /// XY memory double move
1142    Pm5 = 5,
1143    /// Long displacement
1144    Pm8 = 8,
1145}
1146
1147impl ParallelMoveType {
1148    pub fn from_bits(bits: u32) -> Self {
1149        match bits {
1150            0 => Self::Pm0,
1151            1 => Self::Pm1,
1152            2 => Self::Pm2,
1153            3 => Self::Pm3,
1154            4 => Self::Pm4,
1155            5..=7 => Self::Pm5,
1156            8..=15 => Self::Pm8,
1157            _ => unreachable!(),
1158        }
1159    }
1160}
1161
1162/// Decoded DSP56300 instruction.
1163#[derive(Debug, Clone)]
1164pub enum Instruction {
1165    // === Parallel move + ALU ===
1166    Parallel {
1167        alu: ParallelAlu,
1168        move_type: ParallelMoveType,
1169        /// Full 24-bit opcode for field extraction by the emitter
1170        opcode: u32,
1171    },
1172
1173    // === Arithmetic with immediate ===
1174    AddImm {
1175        imm: u8,
1176        d: Accumulator,
1177    },
1178    AddLong {
1179        d: Accumulator,
1180    },
1181    SubImm {
1182        imm: u8,
1183        d: Accumulator,
1184    },
1185    SubLong {
1186        d: Accumulator,
1187    },
1188    CmpImm {
1189        imm: u8,
1190        d: Accumulator,
1191    },
1192    CmpLong {
1193        d: Accumulator,
1194    },
1195    CmpU {
1196        src: usize,
1197        d: Accumulator,
1198    },
1199    AndImm {
1200        imm: u8,
1201        d: Accumulator,
1202    },
1203    AndLong {
1204        d: Accumulator,
1205    },
1206    OrLong {
1207        d: Accumulator,
1208    },
1209    OrImm {
1210        imm: u8,
1211        d: Accumulator,
1212    },
1213    EorImm {
1214        imm: u8,
1215        d: Accumulator,
1216    },
1217    EorLong {
1218        d: Accumulator,
1219    },
1220    AndI {
1221        imm: u8,
1222        dest: u8,
1223    },
1224    OrI {
1225        imm: u8,
1226        dest: u8,
1227    },
1228
1229    // === Shifts ===
1230    AslImm {
1231        shift: u8,
1232        s: Accumulator,
1233        d: Accumulator,
1234    },
1235    AsrImm {
1236        shift: u8,
1237        s: Accumulator,
1238        d: Accumulator,
1239    },
1240    LslImm {
1241        shift: u8,
1242        d: Accumulator,
1243    },
1244    LsrImm {
1245        shift: u8,
1246        d: Accumulator,
1247    },
1248    AslReg {
1249        src: usize,
1250        s: Accumulator,
1251        d: Accumulator,
1252    },
1253    AsrReg {
1254        src: usize,
1255        s: Accumulator,
1256        d: Accumulator,
1257    },
1258    LslReg {
1259        src: usize,
1260        d: Accumulator,
1261    },
1262    LsrReg {
1263        src: usize,
1264        d: Accumulator,
1265    },
1266
1267    // === Branches ===
1268    Bcc {
1269        cc: CondCode,
1270        addr: i32,
1271    },
1272    BccLong {
1273        cc: CondCode,
1274    },
1275    Bra {
1276        addr: i32,
1277    },
1278    BraLong,
1279    Bsr {
1280        addr: i32,
1281    },
1282    BsrLong,
1283    BccRn {
1284        cc: CondCode,
1285        rn: u8,
1286    },
1287    BraRn {
1288        rn: u8,
1289    },
1290    BsrRn {
1291        rn: u8,
1292    },
1293    Bscc {
1294        cc: CondCode,
1295        addr: i32,
1296    },
1297    BsccLong {
1298        cc: CondCode,
1299    },
1300    BsccRn {
1301        cc: CondCode,
1302        rn: u8,
1303    },
1304    Brkcc {
1305        cc: CondCode,
1306    },
1307    Jcc {
1308        cc: CondCode,
1309        addr: u32,
1310    },
1311    JccEa {
1312        cc: CondCode,
1313        ea_mode: u8,
1314    },
1315    Jmp {
1316        addr: u32,
1317    },
1318    JmpEa {
1319        ea_mode: u8,
1320    },
1321    Jscc {
1322        cc: CondCode,
1323        addr: u32,
1324    },
1325    JsccEa {
1326        cc: CondCode,
1327        ea_mode: u8,
1328    },
1329    Jsr {
1330        addr: u32,
1331    },
1332    JsrEa {
1333        ea_mode: u8,
1334    },
1335
1336    // === Bit manipulation ===
1337    BchgEa {
1338        space: MemSpace,
1339        ea_mode: u8,
1340        bit_num: u8,
1341    },
1342    BchgAa {
1343        space: MemSpace,
1344        addr: u8,
1345        bit_num: u8,
1346    },
1347    BchgPp {
1348        space: MemSpace,
1349        pp_offset: u8,
1350        bit_num: u8,
1351    },
1352    BchgQq {
1353        space: MemSpace,
1354        qq_offset: u8,
1355        bit_num: u8,
1356    },
1357    BchgReg {
1358        reg_idx: u8,
1359        bit_num: u8,
1360    },
1361    BclrEa {
1362        space: MemSpace,
1363        ea_mode: u8,
1364        bit_num: u8,
1365    },
1366    BclrAa {
1367        space: MemSpace,
1368        addr: u8,
1369        bit_num: u8,
1370    },
1371    BclrPp {
1372        space: MemSpace,
1373        pp_offset: u8,
1374        bit_num: u8,
1375    },
1376    BclrQq {
1377        space: MemSpace,
1378        qq_offset: u8,
1379        bit_num: u8,
1380    },
1381    BclrReg {
1382        reg_idx: u8,
1383        bit_num: u8,
1384    },
1385    BsetEa {
1386        space: MemSpace,
1387        ea_mode: u8,
1388        bit_num: u8,
1389    },
1390    BsetAa {
1391        space: MemSpace,
1392        addr: u8,
1393        bit_num: u8,
1394    },
1395    BsetPp {
1396        space: MemSpace,
1397        pp_offset: u8,
1398        bit_num: u8,
1399    },
1400    BsetQq {
1401        space: MemSpace,
1402        qq_offset: u8,
1403        bit_num: u8,
1404    },
1405    BsetReg {
1406        reg_idx: u8,
1407        bit_num: u8,
1408    },
1409    BtstEa {
1410        space: MemSpace,
1411        ea_mode: u8,
1412        bit_num: u8,
1413    },
1414    BtstAa {
1415        space: MemSpace,
1416        addr: u8,
1417        bit_num: u8,
1418    },
1419    BtstPp {
1420        space: MemSpace,
1421        pp_offset: u8,
1422        bit_num: u8,
1423    },
1424    BtstQq {
1425        space: MemSpace,
1426        qq_offset: u8,
1427        bit_num: u8,
1428    },
1429    BtstReg {
1430        reg_idx: u8,
1431        bit_num: u8,
1432    },
1433
1434    // === Bit branch ===
1435    BrclrEa {
1436        space: MemSpace,
1437        ea_mode: u8,
1438        bit_num: u8,
1439    },
1440    BrclrAa {
1441        space: MemSpace,
1442        addr: u8,
1443        bit_num: u8,
1444    },
1445    BrclrPp {
1446        space: MemSpace,
1447        pp_offset: u8,
1448        bit_num: u8,
1449    },
1450    BrclrQq {
1451        space: MemSpace,
1452        qq_offset: u8,
1453        bit_num: u8,
1454    },
1455    BrclrReg {
1456        reg_idx: u8,
1457        bit_num: u8,
1458    },
1459    BrsetEa {
1460        space: MemSpace,
1461        ea_mode: u8,
1462        bit_num: u8,
1463    },
1464    BrsetAa {
1465        space: MemSpace,
1466        addr: u8,
1467        bit_num: u8,
1468    },
1469    BrsetPp {
1470        space: MemSpace,
1471        pp_offset: u8,
1472        bit_num: u8,
1473    },
1474    BrsetQq {
1475        space: MemSpace,
1476        qq_offset: u8,
1477        bit_num: u8,
1478    },
1479    BrsetReg {
1480        reg_idx: u8,
1481        bit_num: u8,
1482    },
1483
1484    // === Bit branch to subroutine ===
1485    BsclrEa {
1486        space: MemSpace,
1487        ea_mode: u8,
1488        bit_num: u8,
1489    },
1490    BsclrAa {
1491        space: MemSpace,
1492        addr: u8,
1493        bit_num: u8,
1494    },
1495    BsclrPp {
1496        space: MemSpace,
1497        pp_offset: u8,
1498        bit_num: u8,
1499    },
1500    BsclrQq {
1501        space: MemSpace,
1502        qq_offset: u8,
1503        bit_num: u8,
1504    },
1505    BsclrReg {
1506        reg_idx: u8,
1507        bit_num: u8,
1508    },
1509    BssetEa {
1510        space: MemSpace,
1511        ea_mode: u8,
1512        bit_num: u8,
1513    },
1514    BssetAa {
1515        space: MemSpace,
1516        addr: u8,
1517        bit_num: u8,
1518    },
1519    BssetPp {
1520        space: MemSpace,
1521        pp_offset: u8,
1522        bit_num: u8,
1523    },
1524    BssetQq {
1525        space: MemSpace,
1526        qq_offset: u8,
1527        bit_num: u8,
1528    },
1529    BssetReg {
1530        reg_idx: u8,
1531        bit_num: u8,
1532    },
1533
1534    JclrEa {
1535        space: MemSpace,
1536        ea_mode: u8,
1537        bit_num: u8,
1538    },
1539    JclrAa {
1540        space: MemSpace,
1541        addr: u8,
1542        bit_num: u8,
1543    },
1544    JclrPp {
1545        space: MemSpace,
1546        pp_offset: u8,
1547        bit_num: u8,
1548    },
1549    JclrQq {
1550        space: MemSpace,
1551        qq_offset: u8,
1552        bit_num: u8,
1553    },
1554    JclrReg {
1555        reg_idx: u8,
1556        bit_num: u8,
1557    },
1558    JsetEa {
1559        space: MemSpace,
1560        ea_mode: u8,
1561        bit_num: u8,
1562    },
1563    JsetAa {
1564        space: MemSpace,
1565        addr: u8,
1566        bit_num: u8,
1567    },
1568    JsetPp {
1569        space: MemSpace,
1570        pp_offset: u8,
1571        bit_num: u8,
1572    },
1573    JsetQq {
1574        space: MemSpace,
1575        qq_offset: u8,
1576        bit_num: u8,
1577    },
1578    JsetReg {
1579        reg_idx: u8,
1580        bit_num: u8,
1581    },
1582    JsclrEa {
1583        space: MemSpace,
1584        ea_mode: u8,
1585        bit_num: u8,
1586    },
1587    JsclrAa {
1588        space: MemSpace,
1589        addr: u8,
1590        bit_num: u8,
1591    },
1592    JsclrPp {
1593        space: MemSpace,
1594        pp_offset: u8,
1595        bit_num: u8,
1596    },
1597    JsclrQq {
1598        space: MemSpace,
1599        qq_offset: u8,
1600        bit_num: u8,
1601    },
1602    JsclrReg {
1603        reg_idx: u8,
1604        bit_num: u8,
1605    },
1606    JssetEa {
1607        space: MemSpace,
1608        ea_mode: u8,
1609        bit_num: u8,
1610    },
1611    JssetAa {
1612        space: MemSpace,
1613        addr: u8,
1614        bit_num: u8,
1615    },
1616    JssetPp {
1617        space: MemSpace,
1618        pp_offset: u8,
1619        bit_num: u8,
1620    },
1621    JssetQq {
1622        space: MemSpace,
1623        qq_offset: u8,
1624        bit_num: u8,
1625    },
1626    JssetReg {
1627        reg_idx: u8,
1628        bit_num: u8,
1629    },
1630
1631    // === Loop ===
1632    DoEa {
1633        space: MemSpace,
1634        ea_mode: u8,
1635    },
1636    DoAa {
1637        space: MemSpace,
1638        addr: u8,
1639    },
1640    DoImm {
1641        count: u16,
1642    },
1643    DoReg {
1644        reg_idx: u8,
1645    },
1646    DoForever,
1647    DorEa {
1648        space: MemSpace,
1649        ea_mode: u8,
1650    },
1651    DorAa {
1652        space: MemSpace,
1653        addr: u8,
1654    },
1655    DorImm {
1656        count: u16,
1657    },
1658    DorReg {
1659        reg_idx: u8,
1660    },
1661    DorForever,
1662    EndDo,
1663    RepEa {
1664        space: MemSpace,
1665        ea_mode: u8,
1666    },
1667    RepAa {
1668        space: MemSpace,
1669        addr: u8,
1670    },
1671    RepImm {
1672        count: u16,
1673    },
1674    RepReg {
1675        reg_idx: u8,
1676    },
1677
1678    // === Move ===
1679    MoveLongDisp {
1680        space: MemSpace,
1681        w: bool,
1682        offreg_idx: u8,
1683        numreg: u8,
1684    },
1685    MoveShortDisp {
1686        space: MemSpace,
1687        offset: u8,
1688        w: bool,
1689        offreg_idx: u8,
1690        numreg: u8,
1691    },
1692    MovecEa {
1693        ea_mode: u8,
1694        numreg: u8,
1695        w: bool,
1696        space: MemSpace,
1697    },
1698    MovecAa {
1699        addr: u8,
1700        numreg: u8,
1701        w: bool,
1702        space: MemSpace,
1703    },
1704    MovecReg {
1705        src_reg: u8,
1706        dst_reg: u8,
1707        w: bool,
1708    },
1709    MovecImm {
1710        imm: u8,
1711        dest: u8,
1712    },
1713    MovemEa {
1714        ea_mode: u8,
1715        numreg: u8,
1716        w: bool,
1717    },
1718    MovemAa {
1719        addr: u8,
1720        numreg: u8,
1721        w: bool,
1722    },
1723    Movep23 {
1724        pp_offset: u8,
1725        ea_mode: u8,
1726        w: bool,
1727        perspace: MemSpace,
1728        easpace: MemSpace,
1729    },
1730    MovepQq {
1731        qq_offset: u8,
1732        ea_mode: u8,
1733        w: bool,
1734        qqspace: MemSpace,
1735        easpace: MemSpace,
1736    },
1737    Movep1 {
1738        pp_offset: u8,
1739        ea_mode: u8,
1740        w: bool,
1741        space: MemSpace,
1742    },
1743    Movep0 {
1744        pp_offset: u8,
1745        reg_idx: u8,
1746        w: bool,
1747        space: MemSpace,
1748    },
1749    MovepQqPea {
1750        qq_offset: u8,
1751        ea_mode: u8,
1752        w: bool,
1753        space: MemSpace,
1754    },
1755    MovepQqR {
1756        qq_offset: u8,
1757        reg_idx: u8,
1758        w: bool,
1759        space: MemSpace,
1760    },
1761
1762    // === Multiply ===
1763    /// MPY/MPYR/MAC/MACR (+/-)S,#n,D -- multiply source by 2^-n
1764    MulShift {
1765        op: MulShiftOp,
1766        shift: u8,
1767        src: usize,
1768        d: Accumulator,
1769        k: bool,
1770    },
1771    MpyI {
1772        k: bool,
1773        d: Accumulator,
1774        src: usize,
1775    },
1776    MpyrI {
1777        k: bool,
1778        d: Accumulator,
1779        src: usize,
1780    },
1781    MacI {
1782        k: bool,
1783        d: Accumulator,
1784        src: usize,
1785    },
1786    MacrI {
1787        k: bool,
1788        d: Accumulator,
1789        src: usize,
1790    },
1791    Dmac {
1792        ss: u8,
1793        k: bool,
1794        d: Accumulator,
1795        s1: usize,
1796        s2: usize,
1797    },
1798    MacSU {
1799        s: u8,
1800        k: bool,
1801        d: Accumulator,
1802        s1: usize,
1803        s2: usize,
1804    },
1805    MpySU {
1806        s: u8,
1807        k: bool,
1808        d: Accumulator,
1809        s1: usize,
1810        s2: usize,
1811    },
1812    Div {
1813        src: usize,
1814        d: Accumulator,
1815    },
1816
1817    // === Address ===
1818    Lua {
1819        ea_mode: u8,
1820        dst_reg: u8,
1821    },
1822    LuaRel {
1823        aa: u8,
1824        addr_reg: u8,
1825        dst_reg: u8,
1826        dest_is_n: bool,
1827    },
1828    LraRn {
1829        addr_reg: u8,
1830        dst_reg: u8,
1831    },
1832    LraDisp {
1833        dst_reg: u8,
1834    },
1835    Norm {
1836        rreg_idx: u8,
1837        d: Accumulator,
1838    },
1839
1840    // === Transfer conditional ===
1841    Tcc {
1842        cc: CondCode,
1843        acc: Option<(usize, usize)>,
1844        r: Option<(u8, u8)>,
1845    },
1846
1847    // === Misc ===
1848    Nop,
1849    Dec {
1850        d: Accumulator,
1851    },
1852    Inc {
1853        d: Accumulator,
1854    },
1855    Illegal,
1856    Reset,
1857    Rti,
1858    Rts,
1859    Stop,
1860    Wait,
1861
1862    // === Tier 3: Specialized ===
1863    Clb {
1864        s: Accumulator,
1865        d: Accumulator,
1866    },
1867    Normf {
1868        src: usize,
1869        d: Accumulator,
1870    },
1871    Debug,
1872    Debugcc {
1873        cc: CondCode,
1874    },
1875    Trap,
1876    Trapcc {
1877        cc: CondCode,
1878    },
1879
1880    // Bit-field manipulation
1881    Merge {
1882        src: usize,
1883        d: Accumulator,
1884    },
1885    ExtractReg {
1886        s1: usize,
1887        s2: Accumulator,
1888        d: Accumulator,
1889    },
1890    ExtractImm {
1891        s2: Accumulator,
1892        d: Accumulator,
1893    },
1894    ExtractuReg {
1895        s1: usize,
1896        s2: Accumulator,
1897        d: Accumulator,
1898    },
1899    ExtractuImm {
1900        s2: Accumulator,
1901        d: Accumulator,
1902    },
1903    InsertReg {
1904        s1: usize,
1905        s2: usize,
1906        d: Accumulator,
1907    },
1908    InsertImm {
1909        s2: usize,
1910        d: Accumulator,
1911    },
1912
1913    // Viterbi
1914    Vsl {
1915        s: Accumulator,
1916        ea_mode: u8,
1917        i_bit: u8,
1918    },
1919
1920    // Cache control (treated as NOPs)
1921    Pflush,
1922    Pflushun,
1923    Pfree,
1924    PlockEa {
1925        ea_mode: u8,
1926    },
1927    Plockr,
1928    PunlockEa {
1929        ea_mode: u8,
1930    },
1931    Punlockr,
1932
1933    /// Instruction recognized but not yet implemented.
1934    Unimplemented {
1935        name: &'static str,
1936        opcode: u32,
1937    },
1938
1939    /// Unknown/invalid encoding.
1940    Unknown {
1941        opcode: u32,
1942    },
1943}
1944
1945#[cfg(test)]
1946mod tests {
1947    use super::*;
1948
1949    #[test]
1950    fn test_parallel_alu_exhaustive_roundtrip() {
1951        let undefined_bytes = [0x04, 0x08, 0x0C];
1952        for b in 0..=255u8 {
1953            let decoded = decode_parallel_alu(b);
1954            if undefined_bytes.contains(&b) {
1955                assert!(
1956                    matches!(decoded, ParallelAlu::Undefined),
1957                    "byte 0x{b:02X}: expected Undefined, got {decoded:?}"
1958                );
1959                assert_eq!(decoded.encode(), None);
1960            } else {
1961                assert!(
1962                    !matches!(decoded, ParallelAlu::Undefined),
1963                    "byte 0x{b:02X}: unexpected Undefined"
1964                );
1965                assert_eq!(
1966                    decoded.encode(),
1967                    Some(b),
1968                    "byte 0x{b:02X}: encode roundtrip failed (decoded as {decoded:?})"
1969                );
1970            }
1971        }
1972    }
1973
1974    #[test]
1975    fn test_parallel_alu_display_all() {
1976        // Full 256-entry display table, 8 columns matching DSP56300FM Tables 12-19 through 12-21.
1977        // Non-multiply rows: one row per JJJ+D combination (kkk=0..7).
1978        // Multiply rows: one row per QQQ+d combination (kkk=0..7).
1979        #[rustfmt::skip]
1980        const EXPECTED: [&str; 256] = [
1981            "move",       "tfr b,a",   "addr b,a",  "tst a",     "undefined",  "cmp b,a",    "subr b,a",  "cmpm b,a",  // 0x00-0x07: JJJ=000, D=a
1982            "undefined",  "tfr a,b",   "addr a,b",  "tst b",     "undefined",  "cmp a,b",    "subr a,b",  "cmpm a,b",  // 0x08-0x0F: JJJ=000, D=b
1983            "add b,a",    "rnd a",     "addl b,a",  "clr a",     "sub b,a",    "maxm a,b",   "subl b,a",  "not a",     // 0x10-0x17: JJJ=001, D=a
1984            "add a,b",    "rnd b",     "addl a,b",  "clr b",     "sub a,b",    "max a,b",    "subl a,b",  "not b",     // 0x18-0x1F: JJJ=001, D=b
1985            "add x,a",    "adc x,a",   "asr a",     "lsr a",     "sub x,a",    "sbc x,a",    "abs a",     "ror a",     // 0x20-0x27: JJJ=010, D=a
1986            "add x,b",    "adc x,b",   "asr b",     "lsr b",     "sub x,b",    "sbc x,b",    "abs b",     "ror b",     // 0x28-0x2F: JJJ=010, D=b
1987            "add y,a",    "adc y,a",   "asl a",     "lsl a",     "sub y,a",    "sbc y,a",    "neg a",     "rol a",     // 0x30-0x37: JJJ=011, D=a
1988            "add y,b",    "adc y,b",   "asl b",     "lsl b",     "sub y,b",    "sbc y,b",    "neg b",     "rol b",     // 0x38-0x3F: JJJ=011, D=b
1989            "add x0,a",   "tfr x0,a",  "or x0,a",   "eor x0,a",  "sub x0,a",   "cmp x0,a",   "and x0,a",  "cmpm x0,a", // 0x40-0x47: JJ=00 X0, D=a
1990            "add x0,b",   "tfr x0,b",  "or x0,b",   "eor x0,b",  "sub x0,b",   "cmp x0,b",   "and x0,b",  "cmpm x0,b", // 0x48-0x4F: JJ=00 X0, D=b
1991            "add y0,a",   "tfr y0,a",  "or y0,a",   "eor y0,a",  "sub y0,a",   "cmp y0,a",   "and y0,a",  "cmpm y0,a", // 0x50-0x57: JJ=01 Y0, D=a
1992            "add y0,b",   "tfr y0,b",  "or y0,b",   "eor y0,b",  "sub y0,b",   "cmp y0,b",   "and y0,b",  "cmpm y0,b", // 0x58-0x5F: JJ=01 Y0, D=b
1993            "add x1,a",   "tfr x1,a",  "or x1,a",   "eor x1,a",  "sub x1,a",   "cmp x1,a",   "and x1,a",  "cmpm x1,a", // 0x60-0x67: JJ=10 X1, D=a
1994            "add x1,b",   "tfr x1,b",  "or x1,b",   "eor x1,b",  "sub x1,b",   "cmp x1,b",   "and x1,b",  "cmpm x1,b", // 0x68-0x6F: JJ=10 X1, D=b
1995            "add y1,a",   "tfr y1,a",  "or y1,a",   "eor y1,a",  "sub y1,a",   "cmp y1,a",   "and y1,a",  "cmpm y1,a", // 0x70-0x77: JJ=11 Y1, D=a
1996            "add y1,b",   "tfr y1,b",  "or y1,b",   "eor y1,b",  "sub y1,b",   "cmp y1,b",   "and y1,b",  "cmpm y1,b", // 0x78-0x7F: JJ=11 Y1, D=b
1997            "mpy +x0,x0,a",  "mpyr +x0,x0,a",  "mac +x0,x0,a",  "macr +x0,x0,a",  "mpy -x0,x0,a",  "mpyr -x0,x0,a",  "mac -x0,x0,a",  "macr -x0,x0,a", // 0x80-0x87: QQQ=000 x0*x0, d=a
1998            "mpy +x0,x0,b",  "mpyr +x0,x0,b",  "mac +x0,x0,b",  "macr +x0,x0,b",  "mpy -x0,x0,b",  "mpyr -x0,x0,b",  "mac -x0,x0,b",  "macr -x0,x0,b", // 0x88-0x8F: QQQ=000 x0*x0, d=b
1999            "mpy +y0,y0,a",  "mpyr +y0,y0,a",  "mac +y0,y0,a",  "macr +y0,y0,a",  "mpy -y0,y0,a",  "mpyr -y0,y0,a",  "mac -y0,y0,a",  "macr -y0,y0,a", // 0x90-0x97: QQQ=001 y0*y0, d=a
2000            "mpy +y0,y0,b",  "mpyr +y0,y0,b",  "mac +y0,y0,b",  "macr +y0,y0,b",  "mpy -y0,y0,b",  "mpyr -y0,y0,b",  "mac -y0,y0,b",  "macr -y0,y0,b", // 0x98-0x9F: QQQ=001 y0*y0, d=b
2001            "mpy +x1,x0,a",  "mpyr +x1,x0,a",  "mac +x1,x0,a",  "macr +x1,x0,a",  "mpy -x1,x0,a",  "mpyr -x1,x0,a",  "mac -x1,x0,a",  "macr -x1,x0,a", // 0xA0-0xA7: QQQ=010 x1*x0, d=a
2002            "mpy +x1,x0,b",  "mpyr +x1,x0,b",  "mac +x1,x0,b",  "macr +x1,x0,b",  "mpy -x1,x0,b",  "mpyr -x1,x0,b",  "mac -x1,x0,b",  "macr -x1,x0,b", // 0xA8-0xAF: QQQ=010 x1*x0, d=b
2003            "mpy +y1,y0,a",  "mpyr +y1,y0,a",  "mac +y1,y0,a",  "macr +y1,y0,a",  "mpy -y1,y0,a",  "mpyr -y1,y0,a",  "mac -y1,y0,a",  "macr -y1,y0,a", // 0xB0-0xB7: QQQ=011 y1*y0, d=a
2004            "mpy +y1,y0,b",  "mpyr +y1,y0,b",  "mac +y1,y0,b",  "macr +y1,y0,b",  "mpy -y1,y0,b",  "mpyr -y1,y0,b",  "mac -y1,y0,b",  "macr -y1,y0,b", // 0xB8-0xBF: QQQ=011 y1*y0, d=b
2005            "mpy +x0,y1,a",  "mpyr +x0,y1,a",  "mac +x0,y1,a",  "macr +x0,y1,a",  "mpy -x0,y1,a",  "mpyr -x0,y1,a",  "mac -x0,y1,a",  "macr -x0,y1,a", // 0xC0-0xC7: QQQ=100 x0*y1, d=a
2006            "mpy +x0,y1,b",  "mpyr +x0,y1,b",  "mac +x0,y1,b",  "macr +x0,y1,b",  "mpy -x0,y1,b",  "mpyr -x0,y1,b",  "mac -x0,y1,b",  "macr -x0,y1,b", // 0xC8-0xCF: QQQ=100 x0*y1, d=b
2007            "mpy +y0,x0,a",  "mpyr +y0,x0,a",  "mac +y0,x0,a",  "macr +y0,x0,a",  "mpy -y0,x0,a",  "mpyr -y0,x0,a",  "mac -y0,x0,a",  "macr -y0,x0,a", // 0xD0-0xD7: QQQ=101 y0*x0, d=a
2008            "mpy +y0,x0,b",  "mpyr +y0,x0,b",  "mac +y0,x0,b",  "macr +y0,x0,b",  "mpy -y0,x0,b",  "mpyr -y0,x0,b",  "mac -y0,x0,b",  "macr -y0,x0,b", // 0xD8-0xDF: QQQ=101 y0*x0, d=b
2009            "mpy +x1,y0,a",  "mpyr +x1,y0,a",  "mac +x1,y0,a",  "macr +x1,y0,a",  "mpy -x1,y0,a",  "mpyr -x1,y0,a",  "mac -x1,y0,a",  "macr -x1,y0,a", // 0xE0-0xE7: QQQ=110 x1*y0, d=a
2010            "mpy +x1,y0,b",  "mpyr +x1,y0,b",  "mac +x1,y0,b",  "macr +x1,y0,b",  "mpy -x1,y0,b",  "mpyr -x1,y0,b",  "mac -x1,y0,b",  "macr -x1,y0,b", // 0xE8-0xEF: QQQ=110 x1*y0, d=b
2011            "mpy +y1,x1,a",  "mpyr +y1,x1,a",  "mac +y1,x1,a",  "macr +y1,x1,a",  "mpy -y1,x1,a",  "mpyr -y1,x1,a",  "mac -y1,x1,a",  "macr -y1,x1,a", // 0xF0-0xF7: QQQ=111 y1*x1, d=a
2012            "mpy +y1,x1,b",  "mpyr +y1,x1,b",  "mac +y1,x1,b",  "macr +y1,x1,b",  "mpy -y1,x1,b",  "mpyr -y1,x1,b",  "mac -y1,x1,b",  "macr -y1,x1,b", // 0xF8-0xFF: QQQ=111 y1*x1, d=b
2013        ];
2014
2015        for (i, expected) in EXPECTED.iter().enumerate() {
2016            assert_eq!(
2017                decode_parallel_alu(i as u8).to_string(),
2018                *expected,
2019                "byte 0x{i:02X}"
2020            );
2021        }
2022    }
2023
2024    #[test]
2025    fn test_parallel_alu_from_text_roundtrip() {
2026        for b in 0..=255u8 {
2027            let alu = decode_parallel_alu(b);
2028            if matches!(alu, ParallelAlu::Undefined) {
2029                continue;
2030            }
2031            let text = alu.to_string();
2032            let parsed = ParallelAlu::from_text(&text).unwrap_or_else(|| {
2033                panic!("from_text failed for {:?} (0x{b:02X}): \"{text}\"", alu)
2034            });
2035            assert_eq!(
2036                parsed, alu,
2037                "from_text roundtrip mismatch for 0x{b:02X}: \"{text}\" -> {parsed:?} != {alu:?}"
2038            );
2039        }
2040    }
2041}