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}