feo3boy_opcodes/opcode/
mod.rs

1//! Provides the [`Opcode`] and [`CBOpcode`] types, which define the opcodes available on
2//! the gbz80 CPU.
3
4use std::fmt;
5
6use crate::opcode::args::{AluOp, AluUnaryOp, ConditionCode, Operand16, Operand8};
7
8pub mod args;
9pub mod defs;
10
11/// Represents the internal-fetch opcode which is used to begin every instruction cycle.
12pub struct InternalFetch;
13
14impl fmt::Display for InternalFetch {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        f.write_str("[Internal Fetch]")
17    }
18}
19
20// Opcode References:
21// - Decoding: www.z80.info/decoding.htm
22// - GB Z80 Opcode Table: https://izik1.github.io/gbops/
23// - Regular Z80 Opcode Table: http://z80-heaven.wikidot.com/opcode-reference-chart
24// - Regular Z80 Flag Reference: http://www.z80.info/z80sflag.htm
25// - GB Z80 Instruction Reference: https://rgbds.gbdev.io/docs/v0.4.1/gbz80.7
26
27/// Parsed Opcode, not including any arguments that may be loaded from immediates, nor any follow-up
28/// ops if the operation is a prefixed op.
29#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
30pub enum Opcode {
31    /// No operation.
32    Nop,
33    /// Stop -- not sure what this does or how this differs from Halt.
34    Stop,
35    /// Relative jump. Load a signed immediate for the jump destination, then check the condition,
36    /// then jump if the condition is met.
37    JumpRelative(ConditionCode),
38    /// Increment the given 8 bit operand.
39    Inc8(Operand8),
40    /// Decrement the given 8 bit operand.
41    Dec8(Operand8),
42    /// Load a value from on 8 bit operand to another.
43    Load8 { dest: Operand8, source: Operand8 },
44    /// Increment the given 16 bit operand.
45    Inc16(Operand16),
46    /// Decrement the given 16 bit operand.
47    Dec16(Operand16),
48    /// Load a 16 bit value from one operand to another.
49    Load16 { dest: Operand16, source: Operand16 },
50    /// Add the given 16 bit operand to HL.
51    Add16(Operand16),
52    /// Halt instruction. Pauses but still accepts interrupts.
53    Halt,
54    /// Run the given operation on the ALU. The source is given by the operand, the destination is
55    /// always `A`, the accumulator register.
56    AluOp { op: AluOp, operand: Operand8 },
57    /// Run the given unary operation on the ALU. These ops all affect only the accumulator and
58    /// flags, or just the flags.
59    AluUnary(AluUnaryOp),
60    /// Conditional call. Load unsigned 16 bit immediate, check condtion, then if condition matches
61    /// push current PC and jump to the loaded address.
62    Call(ConditionCode),
63    /// Conditional absolute jump. Load unsigned 16 bit immediate, check condition, then if
64    /// condition matches, jump to the loaded address.
65    Jump(ConditionCode),
66    /// Conditional return. Check the condition, then pop the stack and jump to the address.
67    Ret(ConditionCode),
68    /// Push a 16 bit register pair to the stack, moving the stack pointer.
69    Push(Operand16),
70    /// Pop a 16 bit register pair from the stack, moving the stack pointer.
71    Pop(Operand16),
72    /// Loads and executes a prefixed instruction code.
73    PrefixCB,
74    /// Disable interrupt handling.
75    DisableInterrupts,
76    /// Enable Interrupt handling.
77    EnableInterrupts,
78    /// Interrupt return, similar to unconditional return but also enables interrupts.
79    RetInterrupt,
80    /// Load an 8 bit *signed* immediate and add it to the stack pointer.
81    OffsetSp,
82    /// Load an 8 bit *signed* immediate and add it to the stack pointer, storing the result in HL.
83    AddressOfOffsetSp,
84    /// Jump to the address stored in HL.
85    JumpHL,
86    /// Reset with numeric arg.
87    Reset(u8),
88    /// The instruction decoded to an opcode the processor doesn't actually have. This functions
89    /// equivalently to Nop (I think????). Contained value is the raw opcode.
90    MissingInstruction(u8),
91}
92
93impl Opcode {
94    /// Decodes a u8 to an `Opcode`. All possible u8 values are valid `Opcode`s, so this cannot
95    /// fail.
96    pub fn decode(opcode: u8) -> Self {
97        // Based on www.z80.info/decoding.htm, but adjusted based on codes which don't exist on the
98        // GB Z80, using https://izik1.github.io/gbops/. Also referencing
99        let x = (opcode & 0b11000000) >> 6;
100        let p = (opcode & 0b00110000) >> 4;
101        let y = (opcode & 0b00111000) >> 3;
102        let q = (opcode & 0b00001000) != 0;
103        let z = opcode & 0b00000111;
104        match x {
105            0 => match z {
106                0 => match y {
107                    0 => Self::Nop,
108                    1 => Self::Load16 {
109                        dest: Operand16::AddrImmediate,
110                        source: Operand16::Sp,
111                    },
112                    2 => Self::Stop,
113                    3..=7 => Self::JumpRelative(ConditionCode::from_relative_cond_code(y)),
114                    _ => unreachable!(),
115                },
116                1 => match q {
117                    false => Self::Load16 {
118                        dest: Operand16::from_pair_code_sp(p),
119                        source: Operand16::Immediate,
120                    },
121                    true => Self::Add16(Operand16::from_pair_code_sp(p)),
122                },
123                2 => match q {
124                    false => Self::Load8 {
125                        dest: Operand8::from_indirect(p),
126                        source: Operand8::A,
127                    },
128                    true => Self::Load8 {
129                        dest: Operand8::A,
130                        source: Operand8::from_indirect(p),
131                    },
132                },
133                3 => match q {
134                    false => Self::Inc16(Operand16::from_pair_code_sp(p)),
135                    true => Self::Dec16(Operand16::from_pair_code_sp(p)),
136                },
137                4 => Self::Inc8(Operand8::from_regcode(y)),
138                5 => Self::Dec8(Operand8::from_regcode(y)),
139                6 => Self::Load8 {
140                    dest: Operand8::from_regcode(y),
141                    source: Operand8::Immediate,
142                },
143                7 => Self::AluUnary(AluUnaryOp::from_ycode(y)),
144                _ => unreachable!(),
145            },
146            1 => match (z, y) {
147                (6, 6) => Self::Halt,
148                _ => Self::Load8 {
149                    dest: Operand8::from_regcode(y),
150                    source: Operand8::from_regcode(z),
151                },
152            },
153            2 => Self::AluOp {
154                op: AluOp::from_ycode(y),
155                operand: Operand8::from_regcode(z),
156            },
157            3 => match z {
158                0 => match y {
159                    0..=3 => Self::Ret(ConditionCode::from_absolute_cond_code(y)),
160                    // On normal Z80 these are more conditionals, which are replaced with extra load
161                    // ops on GB Z80.
162                    4 => Self::Load8 {
163                        dest: Operand8::AddrRelImmediate,
164                        source: Operand8::A,
165                    },
166                    5 => Self::OffsetSp,
167                    6 => Self::Load8 {
168                        dest: Operand8::A,
169                        source: Operand8::AddrRelImmediate,
170                    },
171                    7 => Self::AddressOfOffsetSp,
172                    _ => unreachable!(),
173                },
174                1 => match q {
175                    false => Opcode::Pop(Operand16::from_pair_code_af(p)),
176                    true => match p {
177                        0 => Opcode::Ret(ConditionCode::Unconditional),
178                        1 => Opcode::RetInterrupt,
179                        2 => Opcode::JumpHL,
180                        3 => Opcode::Load16 {
181                            dest: Operand16::Sp,
182                            source: Operand16::HL,
183                        },
184                        _ => unreachable!(),
185                    },
186                },
187                2 => match y {
188                    0..=3 => Self::Jump(ConditionCode::from_absolute_cond_code(y)),
189                    // On normal Z80 these are more conditionals, which are replaced with extra load
190                    // ops on GB Z80.
191                    4 => Self::Load8 {
192                        dest: Operand8::AddrRelC,
193                        source: Operand8::A,
194                    },
195                    5 => Self::Load8 {
196                        dest: Operand8::AddrImmediate,
197                        source: Operand8::A,
198                    },
199                    6 => Self::Load8 {
200                        dest: Operand8::A,
201                        source: Operand8::AddrRelC,
202                    },
203                    7 => Self::Load8 {
204                        dest: Operand8::A,
205                        source: Operand8::AddrImmediate,
206                    },
207                    _ => unreachable!(),
208                },
209                3 => match y {
210                    0 => Self::Jump(ConditionCode::Unconditional),
211                    1 => Self::PrefixCB,
212                    // In, Out and EX insturctions are missing on GB Z80.
213                    2..=5 => Self::MissingInstruction(opcode),
214                    6 => Self::DisableInterrupts,
215                    7 => Self::EnableInterrupts,
216                    _ => unreachable!(),
217                },
218                4 => match y {
219                    0..=3 => Self::Call(ConditionCode::from_absolute_cond_code(y)),
220                    // These are conditional calls for conditions the GB Z80 doesn't have.
221                    4..=7 => Self::MissingInstruction(opcode),
222                    _ => unreachable!(),
223                },
224                5 => match q {
225                    false => Opcode::Push(Operand16::from_pair_code_af(p)),
226                    true => match p {
227                        0 => Opcode::Call(ConditionCode::Unconditional),
228                        // These are prefix instructions for prefixes the GB Z80 doesn't support.
229                        1..=3 => Opcode::MissingInstruction(opcode),
230                        _ => unreachable!(),
231                    },
232                },
233                6 => Self::AluOp {
234                    op: AluOp::from_ycode(y),
235                    operand: Operand8::Immediate,
236                },
237                7 => Opcode::Reset(y * 8),
238                _ => unreachable!(),
239            },
240            _ => unreachable!(),
241        }
242    }
243}
244
245impl fmt::Display for Opcode {
246    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
247        match *self {
248            Self::Nop => f.write_str("NOP"),
249            Self::Stop => f.write_str("STOP"),
250            Self::JumpRelative(ConditionCode::Unconditional) => f.write_str("JR i8"),
251            Self::JumpRelative(code) => write!(f, "JR {},i8", code),
252            Self::Inc8(operand) => write!(f, "INC {}", operand),
253            Self::Dec8(operand) => write!(f, "DEC {}", operand),
254            Self::Load8 { dest, source } => write!(f, "LD {},{}", dest, source),
255            Self::Inc16(operand) => write!(f, "INC {}", operand),
256            Self::Dec16(operand) => write!(f, "DEC {}", operand),
257            Self::Load16 { dest, source } => write!(f, "LD {},{}", dest, source),
258            Self::Add16(operand) => write!(f, "ADD HL,{}", operand),
259            Self::Halt => f.write_str("HALT"),
260            Self::AluOp { op, operand } => write!(f, "{} A,{}", op, operand),
261            Self::AluUnary(op) => fmt::Display::fmt(&op, f),
262            Self::Call(ConditionCode::Unconditional) => f.write_str("CALL u16"),
263            Self::Call(code) => write!(f, "CALL {},u16", code),
264            Self::Jump(ConditionCode::Unconditional) => f.write_str("JP u16"),
265            Self::Jump(code) => write!(f, "JP {},u16", code),
266            Self::Ret(ConditionCode::Unconditional) => f.write_str("RET"),
267            Self::Ret(code) => write!(f, "RET {}", code),
268            Self::Pop(operand) => write!(f, "POP {}", operand),
269            Self::Push(operand) => write!(f, "PUSH {}", operand),
270            Self::PrefixCB => f.write_str("PREFIX CB"),
271            Self::DisableInterrupts => f.write_str("DI"),
272            Self::EnableInterrupts => f.write_str("EI"),
273            Self::RetInterrupt => f.write_str("RETI"),
274            Self::OffsetSp => f.write_str("ADD SP,i8"),
275            Self::AddressOfOffsetSp => f.write_str("LD HL,SP+i8"),
276            Self::JumpHL => f.write_str("JP HL"),
277            Self::Reset(target) => write!(f, "RST {:02X}h", target),
278            Self::MissingInstruction(opcode) => write!(f, "<Missing Instruction {:2X}>", opcode),
279        }
280    }
281}
282
283//////////////////////
284// CB Prefixed Opcodes
285//////////////////////
286
287/// Opcodes that come after a CB prefix opcode.
288#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
289pub struct CBOpcode {
290    /// The operand to operate on.
291    pub operand: Operand8,
292    /// The operation being performed.
293    pub op: CBOperation,
294}
295
296impl CBOpcode {
297    /// Decodes an 8-bit opcode found after a CB prefix into a CBOpcode.
298    pub fn decode(opcode: u8) -> Self {
299        let x = (opcode & 0b11000000) >> 6;
300        let y = (opcode & 0b00111000) >> 3;
301        let z = opcode & 0b00000111;
302        let operand = Operand8::from_regcode(z);
303        let op = match x {
304            0 => match y {
305                0 => CBOperation::RotateLeft8,
306                1 => CBOperation::RotateRight8,
307                2 => CBOperation::RotateLeft9,
308                3 => CBOperation::RotateRight9,
309                4 => CBOperation::ShiftLeft,
310                5 => CBOperation::ShiftRightSignExt,
311                6 => CBOperation::Swap,
312                7 => CBOperation::ShiftRight,
313                _ => unreachable!(),
314            },
315            1 => CBOperation::TestBit(y),
316            2 => CBOperation::ResetBit(y),
317            3 => CBOperation::SetBit(y),
318            _ => unreachable!(),
319        };
320        Self { operand, op }
321    }
322}
323
324impl fmt::Display for CBOpcode {
325    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326        if self.op.is_bit_op() {
327            write!(f, "{},{}", self.op, self.operand)
328        } else {
329            write!(f, "{} {}", self.op, self.operand)
330        }
331    }
332}
333
334/// Type of operation performed in a CB prefix opcode.
335#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
336pub enum CBOperation {
337    /// 8-bit left rotate. Bit 7 goes to both the carry and bit 0.
338    RotateLeft8,
339    /// 9-bit left rotate. Bit 7 goes to carry and carry goes to bit 0.
340    RotateLeft9,
341    /// 8-bit right rotate. Bit 0 goes to both the carry and bit 7.
342    RotateRight8,
343    /// 9-bit left rotate. Bit 0 goes to carry and carry goes to bit 7.
344    RotateRight9,
345    /// Shift left. Bit 7 gotes to carry, and 0 fills in Bit 0.
346    ShiftLeft,
347    /// Shift right. Bit 0 goes to carry, and 0 fills in Bit 7.
348    ShiftRight,
349    /// Shift right with sign-extension. Bit 0 goes to carry, and Bit 7 is copied with its current
350    /// value.
351    ShiftRightSignExt,
352    /// Swap the nybbles of the byte.
353    Swap,
354    /// Check if the given bit (given as an index in range 0..=7) is set in the operand.
355    TestBit(u8),
356    /// Sets the given bit (given as an index in range 0..=7) in the operand.
357    SetBit(u8),
358    /// Clears the given bit (given as an index in range 0..=7) in the operand.
359    ResetBit(u8),
360}
361
362impl CBOperation {
363    /// Returns true if this CBOperation is one of the ones that affects a single bit. Otherwise, it
364    /// is one of the rotate/shift/swap operations.
365    fn is_bit_op(self) -> bool {
366        matches!(self, Self::TestBit(_) | Self::SetBit(_) | Self::ResetBit(_))
367    }
368}
369
370impl fmt::Display for CBOperation {
371    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
372        match *self {
373            Self::RotateLeft8 => f.write_str("RLC"),
374            Self::RotateRight8 => f.write_str("RRC"),
375            Self::RotateLeft9 => f.write_str("RL"),
376            Self::RotateRight9 => f.write_str("RR"),
377            Self::ShiftLeft => f.write_str("SLA"),
378            Self::ShiftRightSignExt => f.write_str("SRA"),
379            Self::Swap => f.write_str("SWAP"),
380            Self::ShiftRight => f.write_str("SRL"),
381            Self::TestBit(bit) => write!(f, "BIT {}", bit),
382            Self::ResetBit(bit) => write!(f, "RES {}", bit),
383            Self::SetBit(bit) => write!(f, "SET {}", bit),
384        }
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn can_decode_any_opcode() {
394        for op in 0u8..=0xff {
395            Opcode::decode(op);
396        }
397    }
398
399    #[test]
400    fn can_decode_any_cb_opcode() {
401        for op in 0u8..=0xff {
402            CBOpcode::decode(op);
403        }
404    }
405}