feo3boy_opcodes/opcode/defs.rs
1//! Provides the definitions of the opcodes in terms of microcode.
2
3use crate::compiler::instr::builder::{InstrBuilder, MicrocodeReadable, MicrocodeWritable};
4use crate::compiler::instr::{InstrDef, InstrId};
5use crate::gbz80types::Flags;
6use crate::microcode::args::{Reg16, Reg8};
7use crate::microcode::combocodes::{
8 GbStack16, HandleHalt, HlDec, HlInc, Immediate16, Immediate8, LoadAndExecute, Mem,
9 ServiceInterrupt,
10};
11use crate::microcode::Microcode;
12use crate::opcode::args::{AluOp, AluUnaryOp, ConditionCode, Operand16, Operand8};
13use crate::opcode::{CBOpcode, CBOperation, InternalFetch, Opcode};
14
15impl From<InternalFetch> for InstrDef {
16 fn from(_: InternalFetch) -> Self {
17 // Check if halted (which may just yield and return).
18 InstrBuilder::first(HandleHalt)
19 // Service interrupts (which may jump to an interrupt and return).
20 .then(ServiceInterrupt)
21 // Load and execute the next instruction.
22 .then(LoadAndExecute)
23 .build(InstrId::InternalFetch)
24 }
25}
26
27impl From<Opcode> for InstrDef {
28 fn from(value: Opcode) -> Self {
29 let builder = match value {
30 Opcode::Nop => InstrBuilder::new(),
31 Opcode::Stop => Microcode::Stop.into(),
32 Opcode::JumpRelative(cond) => jump_relative(cond),
33 Opcode::Inc8(operand) => inc8(operand),
34 Opcode::Dec8(operand) => dec8(operand),
35 Opcode::Load8 { dest, source } => InstrBuilder::read(source).then_write(dest),
36 Opcode::Inc16(operand) => inc16(operand),
37 Opcode::Dec16(operand) => dec16(operand),
38 Opcode::Load16 { dest, source } => load16(dest, source),
39 Opcode::Add16(operand) => add16(operand),
40 Opcode::Halt => Microcode::Halt.into(),
41 Opcode::AluOp { operand, op } => InstrBuilder::read(operand).then(op),
42 Opcode::AluUnary(op) => op.into(),
43 Opcode::Call(cond) => call(cond),
44 Opcode::Jump(cond) => jump(cond),
45 Opcode::Ret(cond) => ret(cond),
46 Opcode::Push(operand) => InstrBuilder::read(operand)
47 // Push has an extra delay before writing.
48 .then_yield()
49 .then_write(GbStack16),
50 Opcode::Pop(operand) => InstrBuilder::read(GbStack16).then_write(operand),
51 Opcode::PrefixCB => InstrBuilder::read(Immediate8).then(Microcode::ParseCBOpcode),
52 Opcode::DisableInterrupts => Microcode::DisableInterrupts.into(),
53 Opcode::EnableInterrupts => Microcode::EnableInterrupts { immediate: false }.into(),
54 Opcode::RetInterrupt => interrupt_return(),
55 Opcode::OffsetSp => offset_sp(),
56 Opcode::AddressOfOffsetSp => address_of_offset_sp(),
57 Opcode::JumpHL => InstrBuilder::read(Reg16::HL).then_write(Reg16::Pc),
58 Opcode::Reset(dest) => reset(dest),
59 // A brief note on this doc page:
60 // https://gbdev.io/pandocs/CPU_Comparison_with_Z80.html
61 // says that the unused opcodes will lock up the CPU, rather than behave as a
62 // no-op.
63 // However, we just build them as an empty instruction, which is a no-op.
64 Opcode::MissingInstruction(_) => InstrBuilder::new(),
65 };
66 builder.build(InstrId::Opcode(value))
67 }
68}
69
70/// Build microcode for the relative jump instruction.
71fn jump_relative(cond: ConditionCode) -> InstrBuilder {
72 // stack: ...|
73 // Read the jump offset from the next immediate.
74 // Loading the offset also moves the program counter over the next instruction, which
75 // is good because the jump is relative to the following instruction.
76 // stack: ...|i8 |
77 InstrBuilder::read(Immediate8)
78 // Read the PC address of the instruction after this one onto the stack.
79 // stack: ...|i8 |pcl |pch |
80 .then_read(Reg16::Pc)
81 // Apply the PC offset.
82 // Read the PC address of the instruction after this one onto the stack.
83 // stack: ...|dstl|dsth|flag|
84 .then(Microcode::OffsetAddr)
85 // Discard the flags since JR doesn't use them.
86 // stack: ...|dstl|dsth|
87 .then(Microcode::Discard8)
88 // Apply the jump only if the condition matches.
89 // First yield, because there's an extra dely in JR instructions when branching,
90 // then pop the destination off the stack and into the PC register.
91 .then(cond.cond(
92 // If the condition matches, apply it to the PC.
93 InstrBuilder::r#yield().then_write(Reg16::Pc),
94 // If it doesn't match, discard the computed new PC value.
95 Microcode::Discard16,
96 ))
97}
98
99/// Provides microcode for an 8 bit increment instruction.
100fn inc8(operand: Operand8) -> InstrBuilder {
101 // Inc doesn't set the carry flag.
102 const MASK: Flags = Flags::all().difference(Flags::CARRY);
103
104 // Start by putting a 1 (the right-hand-side for our ALU sub) onto the stack, since
105 // binary ops operate with the top of the stack being the LHS.
106 // stack: ...|1|
107 InstrBuilder::first(Microcode::Append { val: 1 })
108 // Then fetch the operand.
109 // stack: ...|1|v|
110 .then_read(operand)
111 // Apply the operation
112 // stack: ...|r|f|
113 .then(Microcode::Add)
114 // Write out the flags which are modified
115 // stack: ...|r|
116 .then_write(MASK)
117 // Write the result back to the same operand.
118 // stack: ...|
119 .then_write(operand)
120}
121
122/// Provides microcode for an 8 bit decrement instruction.
123fn dec8(operand: Operand8) -> InstrBuilder {
124 // Dec doesn't set the carry flag.
125 const MASK: Flags = Flags::all().difference(Flags::CARRY);
126
127 // Start by putting a 1 (the right-hand-side for our ALU sub) onto the stack, since
128 // binary ops operate with the top of the stack being the LHS.
129 // stack: ...|1|
130 InstrBuilder::first(Microcode::Append { val: 1 })
131 // Then fetch the operand.
132 // stack: ...|1|v|
133 .then_read(operand)
134 // Apply the operation
135 // stack: ...|r|f|
136 .then(Microcode::Sub)
137 // Write out the flags which are modified
138 // stack: ...|r|
139 .then_write(MASK)
140 // Write the result back to the same operand.
141 // stack: ...|
142 .then_write(operand)
143}
144
145/// Provides microcode for a 16 bit increment instruction.
146fn inc16(operand: Operand16) -> InstrBuilder {
147 InstrBuilder::read(operand)
148 .then(Microcode::Inc16)
149 // 16 bit inc doesn't set any flags, and all actual operands are always registers,
150 // but it does delay by 1 additional M cycle, probably because it has to operate
151 // on two bytes.
152 .then(Microcode::Yield)
153 .then_write(operand)
154}
155
156/// Provides microcode for a 16 bit decrement instruction.
157fn dec16(operand: Operand16) -> InstrBuilder {
158 InstrBuilder::read(operand)
159 .then(Microcode::Dec16)
160 // 16 bit dec doesn't set any flags, and all actual operands are always registers,
161 // but it does delay by 1 additional M cycle, probably because it has to operate
162 // on two bytes.
163 .then(Microcode::Yield)
164 .then_write(operand)
165}
166
167/// Provides microcode for 16 bit load operations.
168fn load16(dest: Operand16, source: Operand16) -> InstrBuilder {
169 InstrBuilder::read(source)
170 .then(if (dest, source) == (Operand16::Sp, Operand16::HL) {
171 // Most of the 16 bit loads are <Pair>,<Immediate> and take time based on
172 // number of memory accesses. There are two exceptions. LD (u16),SP, which is
173 // also just timed based on the number of memory accesses, and LD SP,HL, which
174 // is all registers but still takes an extra 1m cycle, which isn't
175 // automatically provided by Operand16 register interactions, so we insert it
176 // here.
177 Some(Microcode::Yield)
178 } else {
179 None
180 })
181 .then_write(dest)
182}
183
184/// Generates microcode for a 16 bit register add into HL. Never sets the zero flag and
185/// clears the subtract flag, but does set carry and half-carry based on the upper byte of
186/// the operation (as if it was performed by running the pseudo-instructions `add
187/// l,<arg-low>; adc h,<arg-high>`.
188fn add16(arg: Operand16) -> InstrBuilder {
189 // 16 bit add never modifies the zero flag.
190 const MASK: Flags = Flags::all().difference(Flags::ZERO);
191
192 // RHS goes on the stack first.
193 InstrBuilder::read(arg)
194 // Then LHS goes on the stack.
195 .then_read(Reg16::HL)
196 // This produces the unmasked flags on top of the stack with the result
197 // underneath.
198 .then(Microcode::Add16)
199 // Add an extra delay cycle, since this op is an 8t not 4t, probably because it is
200 // operating on two bytes, and that extra delay doesn't come from memory accesses.
201 .then_yield()
202 .then_write(MASK)
203 .then_write(Reg16::HL)
204}
205
206/// Build the microcode for a conditional or unconditional call.
207fn call(cond: ConditionCode) -> InstrBuilder {
208 // Conveniently, unconditional call behaves exactly the same as a conditional call
209 // with a true value, down to the timing.
210 // First, load the destination address onto the microcode stack.
211 // stack: ...|dstl|dsth|
212 InstrBuilder::read(Immediate16)
213 // Evaluate conditionally:
214 // stack: ...|dstl|dsth|
215 .then(
216 cond.cond(
217 // If true:
218 // Read the PC onto the microcode stack so we can push it to the gameboy
219 // stack.
220 // stack: ...|dstl|dsth|pcl |pch |
221 InstrBuilder::read(Reg16::Pc)
222 // Conditional jump has an extra internal delay if the condition is
223 // true before pushing the return address to the GB stack.
224 .then_yield()
225 // Write the return address to the Gameboy Stack.
226 // stack: ...|dstl|dsth|
227 .then_write(GbStack16)
228 // Write the destination of the jump to the PC.
229 // stack: ...|
230 .then_write(Reg16::Pc),
231 // If false:
232 // Discard the destination address from the microcode stack.
233 Microcode::Discard16,
234 ),
235 )
236}
237
238/// Builds microcode for a conditional absolute jump.
239fn jump(cond: ConditionCode) -> InstrBuilder {
240 // Conveniently, unconditional jump behaves exactly the same as a conditional jump
241 // with a true value, down to the timing.
242
243 // Fetch the destination of the jump from the immediate.
244 // stack: ...|dstl|dsth|
245 InstrBuilder::read(Immediate16)
246 // Evaluate conditionally:
247 .then(
248 cond.cond(
249 // If true:
250 // Delay by one extra cycle because branching adds an extra cycle despite not
251 // accessing memory.
252 InstrBuilder::r#yield()
253 // Write the desintation address to the PC.
254 // stack: ...|
255 .then_write(Reg16::Pc),
256 // If false:
257 // Discard the destination address.
258 Microcode::Discard16,
259 ),
260 )
261}
262
263/// Performs a conditional return.
264fn ret(cond: ConditionCode) -> InstrBuilder {
265 // First do a yield if this is a conditional return. The conditional returns have an
266 // extra delay at the beginning which isn't part of the unconditional return.
267 InstrBuilder::first(match cond {
268 ConditionCode::Unconditional => None,
269 _ => Some(Microcode::Yield),
270 })
271 // Apply the actual return only if the condition is true.
272 .then(
273 cond.if_true(
274 // Pop the return address off the Gameboy stack and onto the microcode stack.
275 // This takes two m cycles.
276 // stack: ...|retl|reth|
277 InstrBuilder::read(GbStack16)
278 // Apply the additional yield that happens after the GB stack pop and
279 // before the return is applied to the program counter.
280 .then_yield()
281 // Pop the return address off the microcode stack and into the pc.
282 .then_write(Reg16::Pc),
283 ),
284 )
285}
286
287/// Build microcode to enable interrupts and return.
288fn interrupt_return() -> InstrBuilder {
289 // Pop the return address off the GB stack and onto the microcode stack.
290 InstrBuilder::read(GbStack16)
291 // Delay by one additional cycle since there's one extra delay in RETI.
292 .then_yield()
293 // Write the return address to the PC.
294 .then_write(Reg16::Pc)
295 // Enable interrupts immediately.
296 .then(Microcode::EnableInterrupts { immediate: true })
297}
298
299/// Get microcode to offset the stack pointer by an immediate value.
300fn offset_sp() -> InstrBuilder {
301 // stack: ...|off|
302 InstrBuilder::read(Immediate8)
303 // stack: ...|off|spl|sph|
304 .then_read(Reg16::Sp)
305 // stack: ...|SPL|SPH|flg|
306 .then(Microcode::OffsetAddr)
307 // This instruction takes two more cycles after loading the offset.
308 .then_yield()
309 .then_yield()
310 // stack: ...|SPL|SPH|
311 .then_write(Flags::all())
312 .then_write(Reg16::Sp)
313}
314
315/// Create microcode to load the result of offsetting the stack pointer by an immediate
316/// value into HL.
317fn address_of_offset_sp() -> InstrBuilder {
318 // stack: ...|off|
319 InstrBuilder::read(Immediate8)
320 // stack: ...|off|spl|sph|
321 .then_read(Reg16::Sp)
322 // stack: ...|SPL|SPH|flg|
323 .then(Microcode::OffsetAddr)
324 // This instruction takes one more cycle after loading the offset.
325 // Interestingly, this instruction is actually faster than `ADD SP,i8`.
326 .then_yield()
327 // stack: ...|SPL|SPH|
328 .then_write(Flags::all())
329 .then_write(Reg16::HL)
330}
331
332/// Builds microcode for the reset instruction. Similar to call with a fixed destination.
333fn reset(dest: u8) -> InstrBuilder {
334 // There's an extra delay at the start of an RST instruction.
335 InstrBuilder::r#yield()
336 // Push the PC onto the gameboy stack.
337 .then_read(Reg16::Pc)
338 .then_write(GbStack16)
339 // Push the reset destination low byte onto the stack.
340 // stack: ...|addrl|
341 .then(Microcode::Append { val: dest })
342 // Push the high order bytes of the dest address (always 0).
343 // stack: ...|addrl|addrh|
344 .then(Microcode::Append { val: 0 })
345 // Set the PC to the specified address.
346 .then_write(Reg16::Pc)
347}
348
349impl From<CBOpcode> for InstrDef {
350 fn from(value: CBOpcode) -> Self {
351 // Builds microcode for the CB Opcodes that takes a value and affects flags.
352 fn cb_unary_op(operand: Operand8, operator: Microcode) -> InstrBuilder {
353 // First, read the operand onto the microcode stack.
354 // stack: ...|val|
355 InstrBuilder::read(operand)
356 // Rotate left/right 9 require the current flags value in addition to the
357 // operand.
358 .then(
359 if matches!(operator, Microcode::RotateLeft9 | Microcode::RotateRight9) {
360 Some(Microcode::GetFlagsMasked { mask: Flags::all() })
361 } else {
362 None
363 },
364 )
365 // Apply the operator, generating the result and flags.
366 // stack: ...|res|flags|
367 .then(operator)
368 // Apply the flags, overwriting all flags in the register.
369 .then_write(Flags::all())
370 .then_write(operand)
371 }
372 let builder = match value.op {
373 CBOperation::RotateLeft8 => cb_unary_op(value.operand, Microcode::RotateLeft8),
374 CBOperation::RotateLeft9 => cb_unary_op(value.operand, Microcode::RotateLeft9),
375 CBOperation::RotateRight8 => cb_unary_op(value.operand, Microcode::RotateRight8),
376 CBOperation::RotateRight9 => cb_unary_op(value.operand, Microcode::RotateRight9),
377 CBOperation::ShiftLeft => cb_unary_op(value.operand, Microcode::ShiftLeft),
378 CBOperation::ShiftRight => cb_unary_op(value.operand, Microcode::ShiftRight),
379 CBOperation::ShiftRightSignExt => {
380 cb_unary_op(value.operand, Microcode::ShiftRightSignExt)
381 }
382 CBOperation::Swap => cb_unary_op(value.operand, Microcode::Swap),
383 CBOperation::TestBit(bit) => {
384 // Doesn't affect the carry flag.
385 const MASK: Flags = Flags::all().difference(Flags::CARRY);
386 InstrBuilder::read(value.operand)
387 .then(Microcode::TestBit { bit })
388 .then_write(MASK)
389 }
390 CBOperation::SetBit(bit) => InstrBuilder::read(value.operand)
391 .then(Microcode::SetBit { bit })
392 .then_write(value.operand),
393 CBOperation::ResetBit(bit) => InstrBuilder::read(value.operand)
394 .then(Microcode::ResetBit { bit })
395 .then_write(value.operand),
396 };
397 builder.build(InstrId::CBOpcode(value))
398 }
399}
400
401/// Builds the microcode for a single ALU operation. This assumes that the ALU operation's
402/// single u8 arg is already on the stack and handles fetching the accumulator and flags
403/// (if needed) and applying the results.
404impl From<AluOp> for InstrBuilder {
405 fn from(op: AluOp) -> Self {
406 // stack: ...|val|
407 // Fetch the accumulator.
408 // stack: ...|val|acc|
409 InstrBuilder::read(Reg8::Acc)
410 // If the operation requires flags, fetch the flags register.
411 .then(if matches!(op, AluOp::AddCarry | AluOp::SubCarry) {
412 Some(Microcode::GetFlagsMasked { mask: Flags::all() })
413 } else {
414 None
415 })
416 // Apply the appropriate BinaryOp.
417 // stack: ...|res|flg|
418 .then(match op {
419 AluOp::Add => Microcode::Add,
420 AluOp::AddCarry => Microcode::Adc,
421 AluOp::Sub => Microcode::Sub,
422 AluOp::SubCarry => Microcode::Sbc,
423 AluOp::And => Microcode::And,
424 AluOp::Or => Microcode::Or,
425 AluOp::Xor => Microcode::Xor,
426 AluOp::Compare => Microcode::Sub,
427 })
428 // Write the resulting flags (overwriting all flags)
429 // stack: ...|res|
430 .then_write(Flags::all())
431 // Either put the result into the Acc or discard it (in case of cmp).
432 // stack: ...|
433 .then(match op {
434 AluOp::Compare => Microcode::Discard8,
435 _ => Microcode::WriteReg { reg: Reg8::Acc },
436 })
437 }
438}
439
440/// Builds the microcode for a single ALU unary operation. This handles reading the
441/// accumulator and flags as needed, and handles applying the results.
442impl From<AluUnaryOp> for InstrBuilder {
443 fn from(value: AluUnaryOp) -> Self {
444 match value {
445 AluUnaryOp::RotateLeft8 => InstrBuilder::read(Reg8::Acc)
446 .then(Microcode::RotateLeft8)
447 .then_write(Flags::all())
448 // Unlike CB Opcodes, this Rotate always clears the zero flag, so we need to
449 // clear that here. To do that, we load an all-zero byte and apply it masked
450 // to just the zero flag.
451 .then(Microcode::Append { val: 0 })
452 .then_write(Flags::ZERO)
453 .then_write(Reg8::Acc),
454 AluUnaryOp::RotateLeft9 => InstrBuilder::read(Reg8::Acc)
455 .then_read(Flags::all())
456 .then(Microcode::RotateLeft9)
457 .then_write(Flags::all())
458 // Unlike CB Opcodes, this Rotate always clears the zero flag, so we need to
459 // clear that here. To do that, we load an all-zero byte and apply it masked
460 // to just the zero flag.
461 .then(Microcode::Append { val: 0 })
462 .then_write(Flags::ZERO)
463 .then_write(Reg8::Acc),
464 AluUnaryOp::RotateRight8 => InstrBuilder::read(Reg8::Acc)
465 .then(Microcode::RotateRight8)
466 .then_write(Flags::all())
467 // Unlike CB Opcodes, this Rotate always clears the zero flag, so we need to
468 // clear that here. To do that, we load an all-zero byte and apply it masked
469 // to just the zero flag.
470 .then(Microcode::Append { val: 0 })
471 .then_write(Flags::ZERO)
472 .then_write(Reg8::Acc),
473 AluUnaryOp::RotateRight9 => InstrBuilder::read(Reg8::Acc)
474 .then_read(Flags::all())
475 .then(Microcode::RotateRight9)
476 .then_write(Flags::all())
477 // Unlike CB Opcodes, this Rotate always clears the zero flag, so we need to
478 // clear that here. To do that, we load an all-zero byte and apply it masked
479 // to just the zero flag.
480 .then(Microcode::Append { val: 0 })
481 .then_write(Flags::ZERO)
482 .then_write(Reg8::Acc),
483 AluUnaryOp::DecimalAdjust => {
484 // Does not modify SUB.
485 const MASK: Flags = Flags::all().difference(Flags::SUB);
486 InstrBuilder::read(Reg8::Acc)
487 .then_read(Flags::all())
488 .then(Microcode::DecimalAdjust)
489 .then_write(MASK)
490 .then_write(Reg8::Acc)
491 }
492 AluUnaryOp::Compliment => {
493 // Always sets only SUB and HALFCARRY to 1.
494 const MASK: Flags = Flags::SUB.union(Flags::HALFCARRY);
495 InstrBuilder::read(Reg8::Acc)
496 .then(Microcode::Compliment)
497 .then_write(MASK)
498 .then_write(Reg8::Acc)
499 }
500 AluUnaryOp::SetCarryFlag => {
501 const MASK: Flags = Flags::all().difference(Flags::ZERO);
502 InstrBuilder::first(Microcode::Append {
503 val: Flags::CARRY.bits(),
504 })
505 .then_write(MASK)
506 }
507 AluUnaryOp::ComplimentCarryFlag => {
508 // stack: ...|
509 // Fetch the carry flag from the flags register.
510 // stack: ...|000C|
511 InstrBuilder::read(Flags::CARRY)
512 // Compliment the carry flags.
513 // stack: ...|111c|flgs|
514 .then(Microcode::Compliment)
515 // Discard the flags from the complement operation.
516 // stack: ...|111c|
517 .then(Microcode::Discard8)
518 // Overwrite the carry flag:
519 // stack: ...|
520 .then_write(Flags::CARRY)
521 // Add a 0 to use to clear the SUB and HALFCARRY flags, as CCF always
522 // sets those to 0.
523 // stack: ...|0000|
524 .then(Microcode::Append { val: 0 })
525 // Overwrite the SUB and HALFCARRY flags.
526 // stack: ...|
527 .then_write(Flags::SUB.union(Flags::HALFCARRY))
528 }
529 }
530 }
531}
532
533/// As a MicrocodeReadable, `Operand8` will result in a u8 being pushed onto the microcode
534/// stack from the appropriate source.
535impl MicrocodeReadable for Operand8 {
536 fn to_read(self) -> InstrBuilder {
537 match self {
538 Self::A => InstrBuilder::read(Reg8::Acc),
539 Self::B => InstrBuilder::read(Reg8::B),
540 Self::C => InstrBuilder::read(Reg8::C),
541 Self::D => InstrBuilder::read(Reg8::D),
542 Self::E => InstrBuilder::read(Reg8::E),
543 Self::H => InstrBuilder::read(Reg8::H),
544 Self::L => InstrBuilder::read(Reg8::L),
545 Self::AddrHL => InstrBuilder::read(Reg16::HL).then_read(Mem),
546 Self::AddrBC => InstrBuilder::read(Reg16::BC).then_read(Mem),
547 Self::AddrDE => InstrBuilder::read(Reg16::DE).then_read(Mem),
548 Self::AddrHLInc => {
549 // stack: ...|
550 // Retrieve HL and increment it.
551 // stack: ...|h|l|
552 InstrBuilder::read(HlInc)
553 // Use hl to read the value from memory.
554 // stack: ...|v|
555 .then_read(Mem)
556 }
557 Self::AddrHLDec => {
558 // stack: ...|
559 // Retrieve HL and decrement it.
560 // stack: ...|h|l|
561 InstrBuilder::read(HlDec)
562 // Use the original hl value to read the value from memory.
563 // stack: ...|v|
564 .then_read(Mem)
565 }
566 Self::Immediate => InstrBuilder::read(Immediate8),
567 Self::AddrImmediate => InstrBuilder::read(Immediate16).then_read(Mem),
568 Self::AddrRelC => InstrBuilder::read(Reg8::C)
569 // We want to build a pair |l|h| on the top of the stack as the address to
570 // read from, so after reading C as the low byte of the address, we just
571 // push the 0xff as the high byte.
572 .then(Microcode::Append { val: 0xff })
573 .then_read(Mem),
574 Self::AddrRelImmediate => InstrBuilder::read(Immediate8)
575 // We want to build a pair |l|h| on the top of the stack as the address to
576 // read from, so after reading an immediate as the low byte of the
577 // address, we just push the 0xff as the high byte.
578 .then(Microcode::Append { val: 0xff })
579 .then_read(Mem),
580 }
581 }
582}
583
584/// As a MicrocodeWritable, `Operand8` will result in a u8 popped off of the stack and
585/// written to the appropriate destination.
586impl MicrocodeWritable for Operand8 {
587 fn to_write(self) -> InstrBuilder {
588 match self {
589 Self::A => InstrBuilder::write(Reg8::Acc),
590 Self::B => InstrBuilder::write(Reg8::B),
591 Self::C => InstrBuilder::write(Reg8::C),
592 Self::D => InstrBuilder::write(Reg8::D),
593 Self::E => InstrBuilder::write(Reg8::E),
594 Self::H => InstrBuilder::write(Reg8::H),
595 Self::L => InstrBuilder::write(Reg8::L),
596 Self::AddrHL => InstrBuilder::read(Reg16::HL).then_write(Mem),
597 Self::AddrBC => InstrBuilder::read(Reg16::BC).then_write(Mem),
598 Self::AddrDE => InstrBuilder::read(Reg16::DE).then_write(Mem),
599 Self::AddrHLInc => {
600 // stack: ...|v|
601 // Retrieve HL and increment it.
602 // stack: ...|v|h|l|
603 InstrBuilder::read(HlInc)
604 // Use hl to write the value to memory.
605 // stack: ...|
606 .then_write(Mem)
607 }
608 Self::AddrHLDec => {
609 // stack: ...|v|
610 // Retrieve HL and decrement it.
611 // stack: ...|v|h|l|
612 InstrBuilder::read(HlDec)
613 // Use hl to write the value to memory.
614 // stack: ...|
615 .then_write(Mem)
616 }
617 Self::Immediate => panic!("Immediates cannot be used as store destinations"),
618 Self::AddrImmediate => InstrBuilder::read(Immediate16).then_write(Mem),
619 Self::AddrRelC => InstrBuilder::read(Reg8::C)
620 // We want to build a pair |l|h| on the top of the stack as the address to
621 // write to, so after reading C as the low byte of the address, we just
622 // push the 0xff as the high byte.
623 .then(Microcode::Append { val: 0xff })
624 .then_write(Mem),
625 Self::AddrRelImmediate => InstrBuilder::read(Immediate8)
626 // We want to build a pair |l|h| on the top of the stack as the address to
627 // write to, so after reading an immediate as the low byte of the address,
628 // we just push the 0xff as the high byte.
629 .then(Microcode::Append { val: 0xff })
630 .then_write(Mem),
631 }
632 }
633}
634
635/// As a MicrocodeReadable, `Operand16` will result in a u16 being pushed onto the
636/// microcode stack from the appropriate source.
637impl MicrocodeReadable for Operand16 {
638 fn to_read(self) -> InstrBuilder {
639 match self {
640 Self::BC => InstrBuilder::read(Reg16::BC),
641 Self::DE => InstrBuilder::read(Reg16::DE),
642 Self::HL => InstrBuilder::read(Reg16::HL),
643 Self::AF => InstrBuilder::read(Reg16::AF),
644 Self::Sp => InstrBuilder::read(Reg16::Sp),
645 Self::Immediate => InstrBuilder::read(Immediate16),
646 Self::AddrImmediate => {
647 panic!("No actual operation uses (u16) as the source for a 16 bit load")
648 }
649 }
650 }
651}
652
653/// As a MicrocodeWritable, `Operand16` will result in a u16 popped off of the stack and
654/// written to the appropriate destination.
655impl MicrocodeWritable for Operand16 {
656 fn to_write(self) -> InstrBuilder {
657 match self {
658 Self::BC => InstrBuilder::write(Reg16::BC),
659 Self::DE => InstrBuilder::write(Reg16::DE),
660 Self::HL => InstrBuilder::write(Reg16::HL),
661 Self::AF => InstrBuilder::write(Reg16::AF),
662 Self::Sp => InstrBuilder::write(Reg16::Sp),
663 Self::Immediate => panic!("Immediates cannot be used as store destinations"),
664 // First get the address to write to.
665 // stack: ...|vall|valh|destl|desth|
666 Self::AddrImmediate => InstrBuilder::read(Immediate16)
667 // Intersperse inverts the order of the value bytes and puts copies of the
668 // address between them, which sets us up perfectly for the needed writes.
669 // stack: ...|valh|destl|desth|vall|destl|desth|
670 .then(Microcode::Intersperse)
671 // Write the low byte to memory at the specified address.
672 // stack: ...|valh|destl|desth|
673 .then_write(Mem)
674 // Increment the destination address to get the address for the second
675 // byte.
676 // stack: ...|valh|DESTL|DESTH|
677 .then(Microcode::Inc16)
678 // Write the high byte to memory at the specified address.
679 // stack: ...|
680 .then_write(Mem),
681 }
682 }
683}
684
685impl ConditionCode {
686 /// Wrap the provided microcode in a conditional handling. If unconditional, just
687 /// returns the `code_if_condition_true`. If conditional, fetches the appropriate flag
688 /// and applies a micorcode conditional to run the provided code only when the
689 /// condition matches.
690 fn cond(
691 self,
692 code_if_condition_true: impl Into<InstrBuilder>,
693 code_if_condition_false: impl Into<InstrBuilder>,
694 ) -> InstrBuilder {
695 match self {
696 ConditionCode::Unconditional => code_if_condition_true.into(),
697 // To implement the inverted flags (NonZero and NoCarry), we execute the
698 // "code_if_condition_true" in the "false" case of the InstrBuilder::cond.
699 ConditionCode::NonZero => InstrBuilder::read(Flags::ZERO).then(InstrBuilder::cond(
700 code_if_condition_false,
701 code_if_condition_true,
702 )),
703 ConditionCode::Zero => InstrBuilder::read(Flags::ZERO).then(InstrBuilder::cond(
704 code_if_condition_true,
705 code_if_condition_false,
706 )),
707 ConditionCode::NoCarry => InstrBuilder::read(Flags::CARRY).then(InstrBuilder::cond(
708 code_if_condition_false,
709 code_if_condition_true,
710 )),
711 ConditionCode::Carry => InstrBuilder::read(Flags::CARRY).then(InstrBuilder::cond(
712 code_if_condition_true,
713 code_if_condition_false,
714 )),
715 }
716 }
717
718 /// Wrap the given code to run only if this conditional evaluates to true. If
719 /// unconditional, returns the microcode unchanged, otherwise runs the specified code
720 /// only if the appropriate flag matches.
721 fn if_true(self, code_if_condition_true: impl Into<InstrBuilder>) -> InstrBuilder {
722 self.cond(code_if_condition_true, InstrBuilder::new())
723 }
724}