feo3boy_opcodes/microcode/
mod.rs

1//! This module provides the [`Microcode`] type, which specifies the sub-steps that
2//! [`Opcodes`][crate::opcode::Opcode] can be broken down into.
3use quote::quote;
4
5use feo3boy_microcode_generator::define_microcode;
6
7use crate::gbz80types::Flags;
8use crate::microcode::args::{Reg16, Reg8};
9
10pub mod args;
11pub mod combocodes;
12
13impl ValType {
14    /// Size in bytes of values of this type.
15    pub fn bytes(self) -> usize {
16        match self {
17            ValType::U8 | ValType::Bool | ValType::Flags => 1,
18            ValType::U16 => 2,
19        }
20    }
21}
22
23impl Microcode {
24    /// Returns true if this microcode operation causes the currently executing
25    /// instruction to terminate. True for
26    /// [`FetchNextInstruction`][Microcode::FetchNextInstruction]
27    /// [`ParseOpcode`][Microcode::ParseOpcode], and
28    /// [`ParseCBOpcode`][Microcode::ParseCBOpcode].
29    pub fn is_terminal(self) -> bool {
30        matches!(
31            self,
32            Microcode::FetchNextInstruction | Microcode::ParseOpcode | Microcode::ParseCBOpcode
33        )
34    }
35}
36
37/// [`Microcode`] is a set of simple instructions designed specifically for implementing
38/// the opcodes on the gbz80 processor used on the gameboy. These instructions operate on
39/// a stack of bytes called the microcode stack, which is separate from the gameboy's
40/// stack. Each microcode operation pops some values off of the stack, computes a result,
41/// and pushes its results onto the stack.
42///
43/// Most microcode operations are pure functions, meaning they only operate on the
44/// microcode stack. The functionality of those operations is defined directly in this
45/// crate. For operations which are not-pure, such as skips which execute part of the
46/// microcode only conditionally or operations which act directly on the gbz80 CPU in some
47/// way, the behavior of those operations is defined externally.
48#[define_microcode(Microcode)]
49pub mod defs {
50    use crate::gbz80types::Flags;
51
52    allowed_types! {
53        name = ValType,
54        types = [
55            /// Specifies a stack argument of type `u8`.
56            u8 => U8,
57            /// Specifies a stack argument of type `bool`. (Assumed to occupy 8 bits on
58            /// the stack like u8).
59            bool => Bool,
60            /// Specifies a stack argument of type `u16`.
61            u16 => U16,
62            /// Specifies a stack argument of type `Flags`. (Assumed to occupy 8 bits on
63            /// the stack like u8).
64            Flags => Flags,
65        ],
66    }
67
68    /// Delays execution for 1m cycle.
69    #[microcode_extern(Yield)]
70    pub fn r#yield() {}
71
72    /// Read an 8-bit value from a register.
73    #[microcode_extern(ReadReg)]
74    pub fn read_reg(
75        /// The register to read from.
76        #[field]
77        reg: Reg8,
78    ) -> u8 {
79    }
80
81    /// Write an 8-bit value to a register.
82    #[microcode_extern(WriteReg)]
83    pub fn write_reg(
84        /// The register to write to.
85        #[field]
86        reg: Reg8,
87        val: u8,
88    ) {
89    }
90
91    /// Read an 8-bit value from a register.
92    #[microcode_extern(ReadReg16)]
93    pub fn read_reg(
94        /// The register to read from.
95        #[field]
96        reg: Reg16,
97    ) -> u16 {
98    }
99
100    /// Write an 8-bit value to a register.
101    #[microcode_extern(WriteReg16)]
102    pub fn write_reg(
103        /// The register to write to.
104        #[field]
105        reg: Reg16,
106        val: u16,
107    ) {
108    }
109
110    /// Pop a 16 bit value from the stack and use it to read an 8 bit value onto the
111    /// stack.
112    #[microcode_extern(ReadMem)]
113    pub fn read_mem(addr: u16) -> u8 {}
114
115    /// Pop a 16 bit value from the stack and use it as the address, then pop an 8 bit
116    /// value from the stack and wite it to that address.
117    #[microcode_extern(WriteMem)]
118    pub fn write_mem(addr: u16, val: u8) {}
119
120    /// Fetches the flags register onto the microcode stack,
121    #[microcode_extern(GetFlagsMasked)]
122    pub fn get_flags_masked(#[field] mask: Flags) -> Flags {}
123
124    /// Pops flags off the microcode stack, masks them, and applies them to the flags
125    /// register.
126    #[microcode_extern(SetFlagsMasked)]
127    pub fn set_flags_masked(#[field] mask: Flags, flags: Flags) {}
128
129    /// Append an 8-bit value to the microcode stack. (Essentially provides a constant
130    /// value).
131    #[microcode(Append)]
132    #[inline]
133    pub fn append(
134        /// The value to place on the stack.
135        #[field]
136        val: u8,
137    ) -> u8 {
138        val
139    }
140
141    /// Takes one u16 from the stack and pushes 2 copies of it onto the stack.
142    #[microcode(Dup16)]
143    #[inline]
144    pub fn dup(v: u16) -> (u16, u16) {
145        (v, v)
146    }
147
148    /// Discard an 8 bit value from the microcode stack.
149    #[microcode(Discard8)]
150    #[inline]
151    pub fn discard8(_: u8) {}
152
153    /// Discard a 16 bit value from the microcode stack.
154    #[microcode(Discard16)]
155    #[inline]
156    pub fn discard16(_: u16) {}
157
158    /// Pop a u8 and a u16 off the microcode stack and push them in reverse order (u16 on
159    /// top, u8 below it).
160    #[microcode(Swap816)]
161    #[inline]
162    pub fn swap816(top: u8, second: u16) -> (u8, u16) {
163        (top, second)
164    }
165
166    /// Takes a u16 address off the stack followed by a u16 value, then splits the value
167    /// into separate bytes and pushes them on the stack in the order: u8 high, u16 addr,
168    /// u8 low, u16 addr. This is useful for performing a 16 bit write where the low byte
169    /// is written first.
170    #[microcode(Intersperse)]
171    #[inline]
172    pub fn intersperse(addr: u16, val: u16) -> (u8, u16, u8, u16) {
173        let [low, high] = val.to_le_bytes();
174        (high, addr, low, addr)
175    }
176
177    /// Boolean not. Note this is an interanl microcode operation, not a gameboy ALU
178    /// operation.
179    #[microcode(Not)]
180    #[inline]
181    pub fn not(val: bool) -> bool {
182        !val
183    }
184
185    /// Perform an 8 bit add.
186    #[microcode(Add)]
187    pub fn add(lhs: u8, rhs: u8) -> (u8, Flags) {
188        let mut flags = Flags::empty();
189        if (lhs & 0xf) + (rhs & 0xf) > 0xf {
190            flags |= Flags::HALFCARRY;
191        }
192        let (res, carry) = lhs.overflowing_add(rhs);
193        flags |= Flags::check_zero(res) | Flags::check_carry(carry);
194        (res, flags)
195    }
196
197    /// Perform an 8 bit add-carry.
198    #[microcode(Adc)]
199    pub fn adc(prevflags: Flags, lhs: u8, rhs: u8) -> (u8, Flags) {
200        let (mut res, mut flags) = add(lhs, rhs);
201        if prevflags.contains(Flags::CARRY) {
202            let (res2, flags2) = add(res, 1);
203            res = res2;
204            // Zero flag should only be set if the second add had a result of zero.
205            flags = flags2 | (flags - Flags::ZERO);
206        }
207        (res, flags)
208    }
209
210    /// Perform an 8 bit sub.
211    #[microcode(Sub)]
212    pub fn sub(lhs: u8, rhs: u8) -> (u8, Flags) {
213        let mut flags = Flags::SUB;
214        if (lhs & 0xf) < (rhs & 0xf) {
215            flags |= Flags::HALFCARRY;
216        }
217        let (res, carry) = lhs.overflowing_sub(rhs);
218        flags |= Flags::check_zero(res) | Flags::check_carry(carry);
219        (res, flags)
220    }
221
222    /// Perform an 8 bit sub-carry.
223    #[microcode(Sbc)]
224    pub fn sbc(prevflags: Flags, lhs: u8, rhs: u8) -> (u8, Flags) {
225        let (mut res, mut flags) = sub(lhs, rhs);
226        if prevflags.contains(Flags::CARRY) {
227            let (res2, flags2) = sub(res, 1);
228            res = res2;
229            // Zero flag should only be set if the second subtract had a result of
230            // zero.
231            flags = flags2 | (flags - Flags::ZERO);
232        }
233        (res, flags)
234    }
235
236    /// Perform a microcode `and`.
237    #[microcode(And)]
238    #[inline]
239    pub fn and(lhs: u8, rhs: u8) -> (u8, Flags) {
240        let res = lhs & rhs;
241        let flags = Flags::HALFCARRY | Flags::check_zero(res);
242        (res, flags)
243    }
244
245    /// Perform a microcde `or`
246    #[microcode(Or)]
247    #[inline]
248    pub fn or(lhs: u8, rhs: u8) -> (u8, Flags) {
249        let res = lhs | rhs;
250        let flags = Flags::check_zero(res);
251        (res, flags)
252    }
253
254    /// Perform a microcde `xor`
255    #[microcode(Xor)]
256    #[inline]
257    pub fn xor(lhs: u8, rhs: u8) -> (u8, Flags) {
258        let res = lhs ^ rhs;
259        let flags = Flags::check_zero(res);
260        (res, flags)
261    }
262
263    /// Rotate the value left by 8 bits.
264    #[microcode(RotateLeft8)]
265    #[inline]
266    pub fn rotate_left8(val: u8) -> (u8, Flags) {
267        let res = val.rotate_left(1);
268        let flags = Flags::check_zero(res) | Flags::check_carry(res & 1 != 0);
269        (res, flags)
270    }
271
272    /// Rotate the value left by 9 bits, rotating through the carry flag.
273    #[microcode(RotateLeft9)]
274    pub fn rotate_left9(prevflags: Flags, val: u8) -> (u8, Flags) {
275        let rotated = val.rotate_left(1);
276        // Pull the carry flag into the first bit.
277        let res = rotated & 0xfe | prevflags.contains(Flags::CARRY) as u8;
278        // Set the carry flag based on the bit that was rotated out of max position.
279        let flags = Flags::check_carry(rotated & 1 != 0) | Flags::check_zero(res);
280        (res, flags)
281    }
282
283    /// Rotate the value right by 8 bits.
284    #[microcode(RotateRight8)]
285    #[inline]
286    pub fn rotate_right8(val: u8) -> (u8, Flags) {
287        let res = val.rotate_right(1);
288        let flags = Flags::check_zero(res) | Flags::check_carry(res & 0x80 != 0);
289        (res, flags)
290    }
291
292    /// Rotate the value right by 9 bits, rotating through the carry flag.
293    #[microcode(RotateRight9)]
294    pub fn rotate_right9(prevflags: Flags, val: u8) -> (u8, Flags) {
295        let rotated = val.rotate_right(1);
296        // Pull the carry flag into the highest bit.
297        let res = rotated & 0x7f | ((prevflags.contains(Flags::CARRY) as u8) << 7);
298        // Set the carry flag based on the bit that was rotated out of min position.
299        let flags = Flags::check_carry(val & 1 != 0) | Flags::check_zero(res);
300        (res, flags)
301    }
302
303    /// Complement the value on top of the stack.
304    #[microcode(Compliment)]
305    pub fn compliment(val: u8) -> (u8, Flags) {
306        const FLAGS: Flags = Flags::SUB.union(Flags::HALFCARRY);
307        let res = !val;
308        (res, FLAGS)
309    }
310
311    /// Shift the value left by one bit.
312    #[microcode(ShiftLeft)]
313    pub fn shift_left(val: u8) -> (u8, Flags) {
314        let res = val << 1;
315        let flags = Flags::check_zero(res) | Flags::check_carry(val & 0x80 != 0);
316        (res, flags)
317    }
318
319    /// Shift the value right by one bit.
320    #[microcode(ShiftRight)]
321    pub fn shift_right(val: u8) -> (u8, Flags) {
322        let res = val >> 1;
323        let flags = Flags::check_zero(res) | Flags::check_carry(val & 1 != 0);
324        (res, flags)
325    }
326
327    /// Shift the value right by one bit, performing sign-extension.
328    #[microcode(ShiftRightSignExt)]
329    pub fn shift_right_sign_ext(val: u8) -> (u8, Flags) {
330        let res = ((val as i8) >> 1) as u8;
331        let flags = Flags::check_zero(res) | Flags::check_carry(val & 1 != 0);
332        (res, flags)
333    }
334
335    /// Swaps the lower and upper nybble of the byte.
336    #[microcode(Swap)]
337    pub fn swap(val: u8) -> (u8, Flags) {
338        let res = ((val & 0x0f) << 4) | ((val & 0xf0) >> 4);
339        let flags = Flags::check_zero(res);
340        (res, flags)
341    }
342
343    /// Helper for doing binary-coded-decimal. Adjusts the hex didgits to keep both
344    /// nybbles in range 0..=9 by adding 0x06 and/or 0x60 to push the digit to the next
345    /// nybble. Depends on the carry/halfcarry flags.
346    #[microcode(DecimalAdjust)]
347    pub fn decmial_adjust(prevflags: Flags, val: u8) -> (u8, Flags) {
348        // Always clears HALFCARRY.
349        let mut flags = Flags::empty();
350        let mut res = val;
351        if prevflags.contains(Flags::SUB) {
352            if prevflags.contains(Flags::CARRY) {
353                flags |= Flags::CARRY;
354                res = res.wrapping_sub(0x60);
355            }
356            if prevflags.contains(Flags::HALFCARRY) {
357                res = res.wrapping_sub(0x06);
358            }
359        } else {
360            if prevflags.contains(Flags::CARRY) || val > 0x99 {
361                flags |= Flags::CARRY;
362                res = res.wrapping_add(0x60);
363            }
364            if prevflags.contains(Flags::HALFCARRY) || res & 0xf > 9 {
365                res = res.wrapping_add(0x06);
366            }
367        }
368        flags |= Flags::check_zero(res);
369        (res, flags)
370    }
371
372    /// Tests if a particular bit is set in the output.
373    #[microcode(TestBit)]
374    #[inline]
375    pub fn test_bit(
376        /// The index of the bit to test.
377        #[field]
378        bit: u8,
379        val: u8,
380    ) -> Flags {
381        Flags::check_zero(val & (1 << bit)) | Flags::HALFCARRY
382    }
383
384    /// Sets a paricular bit in the output.
385    #[microcode(SetBit)]
386    #[inline]
387    pub fn set_bit(
388        /// The index of the bit to set.
389        #[field]
390        bit: u8,
391        val: u8,
392    ) -> u8 {
393        val | (1 << bit)
394    }
395
396    /// Clears a paricular bit in the output.
397    #[microcode(ResetBit)]
398    #[inline]
399    pub fn reset_bit(
400        /// The index of the bit to clear..
401        #[field]
402        bit: u8,
403        val: u8,
404    ) -> u8 {
405        val & !(1 << bit)
406    }
407
408    /// Increments a 16 bit value.
409    #[microcode(Inc16)]
410    #[inline]
411    pub fn inc16(val: u16) -> u16 {
412        val.wrapping_add(1)
413    }
414
415    /// Decrements a 16 bit value.
416    #[microcode(Dec16)]
417    #[inline]
418    pub fn dec16(val: u16) -> u16 {
419        val.wrapping_sub(1)
420    }
421
422    /// Performs a 16 bit add with flags. Pops two 16 bit args off the stack (lhs on top,
423    /// rhs below it), adds them, and pushes the result followed by the flags on top. The
424    /// returned flags will have 00HC set based on the upper byte of the operation (as if
425    /// it was performed by running the pseudo-instructions `add l,<arg-low>; adc
426    /// h,<arg-high>`.
427    #[microcode(Add16)]
428    #[inline]
429    pub fn add16(lhs: u16, rhs: u16) -> (u16, Flags) {
430        let mut flags = Flags::empty();
431        if (lhs & 0x7ff) + (rhs & 0x7ff) > 0x7ff {
432            flags |= Flags::HALFCARRY;
433        }
434        let (res, carry) = lhs.overflowing_add(rhs);
435        flags |= Flags::check_carry(carry);
436        (res, flags)
437    }
438
439    /// Pops a 16 bit address off the stack followed by an 8 bit offset below it. Applies
440    /// address offsetting and pushes the new address followed by the flags on top.
441    #[microcode(OffsetAddr)]
442    pub fn offset_addr(addr: u16, offset: u8) -> (u16, Flags) {
443        // Perform sign-extension, then treat as u16.
444        let offset = offset as i8 as i16 as u16;
445
446        // JR ignores flags, SP+i8 is used in two instructions and sets flags for carry
447        // and half carry based on the lower byte.
448        let mut flags = Flags::empty();
449        if (addr & 0xf) + (offset & 0xf) > 0xf {
450            flags |= Flags::HALFCARRY;
451        }
452        // Since we're working on 16 bits, we calculate CARRY for the lower byte the same
453        // way we typically do a HALFCARRY.
454        if (addr & 0xff) + (offset & 0xff) > 0xff {
455            flags |= Flags::CARRY;
456        }
457
458        // In two's compliment, adding a negative is the same as adding with wraparound.
459        (addr.wrapping_add(offset), flags)
460    }
461
462    /// Panics if the stop instruction is reached. This should probably become an extern
463    /// if stop is ever implemented.
464    #[microcode(Stop)]
465    pub fn stop() {
466        panic!("STOP is bizarre and complicated and not implemented.")
467    }
468
469    /// Puts the CPU into the halted state.
470    #[microcode_extern(Halt)]
471    pub fn halt() {}
472
473    /// Enables interrupts, either immediately or after the next instruction.
474    #[microcode_extern(EnableInterrupts)]
475    pub fn enable_interrupts(
476        /// If true, enable interrupts immediately, otherwise after the next instruction.
477        #[field]
478        immediate: bool,
479    ) {
480    }
481
482    /// Disables interrupts immediately.
483    #[microcode_extern(DisableInterrupts)]
484    pub fn disable_interrupts() {}
485
486    /// Retrieves the value of the halted flag from the CPU.
487    #[microcode_extern(CheckHalt)]
488    pub fn check_halt() -> bool {}
489
490    /// Sets the CPU to not be halted.
491    #[microcode_extern(ClearHalt)]
492    pub fn clear_halt() {}
493
494    /// Gets a bool indicating if IME is set.
495    #[microcode_extern(CheckIme)]
496    pub fn check_ime() -> bool {}
497
498    /// Gets the set of currently active and enabled interrupts.
499    #[microcode_extern(GetActiveInterrupts)]
500    pub fn get_active_interrupts() -> u8 {}
501
502    /// Gets the address of the interrupt handler for the next active and enabled
503    /// interrupt from the interupt vector and clears that interrupt from the interrupt
504    /// vector. Does not disable interrupts.
505    #[microcode_extern(PopInterrupt)]
506    pub fn pop_interrupt() -> u16 {}
507
508    /// Pushes the value of the Halt Bug flag onto the microcode stack, clearing the value
509    /// to false.
510    #[microcode_extern(PopHaltBug)]
511    pub fn pop_halt_bug() -> bool {}
512
513    /// Tells the CPU that interrupt_master_enable.tick should be run when the current
514    /// instruction finishes. This will happen on FetchNextInstruction, whether that is
515    /// triggered explicitly or by reaching the end of the current instruction.
516    #[microcode_extern(TickImeOnEnd)]
517    pub fn tick_ime_on_end() {}
518
519    /// Unconditionally skip the given number of microcode steps.
520    ///
521    /// Skips the microcode pc forward by this number of steps. Note that the microcode pc
522    /// is already incremented for the skip instruction, so that is not counted when
523    /// figuring out how many steps to skip.
524    #[microcode_extern(Skip)]
525    pub fn skip(
526        /// Number of steps in the microcode to skip over.
527        #[field]
528        steps: usize,
529    ) {
530    }
531
532    /// Conditionally skip the given number of microcode steps.
533    ///
534    /// Pops an 8 bit value off the microcode stack, and if it is non-zero, skips the
535    /// microcode pc forward by this number of steps. Note that the microcode pc is
536    /// already incremented for the skip instruction, so that is not counted when figuring
537    /// out how many steps to skip.
538    #[microcode_extern(SkipIf)]
539    pub fn skip_if(
540        /// Number of steps in the microcode to skip over.
541        #[field]
542        steps: usize,
543        cond: bool,
544    ) {
545    }
546
547    /// Replace the currently executing instruction with the microcode for the CPU's
548    /// internal halt check, interrupt handler, and instruction fetch.
549    ///
550    /// Executing this instruction also checks if `previous_ime` is set, and if so, ticks
551    /// the IME state forward.
552    ///
553    /// It is not necessary to include this in every instruction, as the CPU will perform
554    /// this automatically if it runs out of steps in the currently executing instruction.
555    /// This can be useful as a 'break' or 'return' from within an instruction.
556    #[microcode_extern(FetchNextInstruction)]
557    pub fn fetch_next_instruction() {}
558
559    // Pop an 8 bit value off the microcode stack and look it up the the opcode table.
560    // Replace the currently executing instruction with that instruction.
561    #[microcode_extern(ParseOpcode)]
562    pub fn parse_opcode(opcode: u8) {}
563
564    // Pop an 8 bit value off the microcode stack and look it up the the CB opcode table.
565    // Replace the currently executing instruction with that instruction.
566    #[microcode_extern(ParseCBOpcode)]
567    pub fn parse_cb_opcode(opcode: u8) {}
568}