feo3boy_opcodes/microcode/
combocodes.rs

1//! Combo-codes are helper types which allow you to specify common sequences of microcode
2//! operations using a single value.
3//!
4
5use crate::compiler::instr::builder::{InstrBuilder, MicrocodeReadable, MicrocodeWritable};
6use crate::microcode::args::Reg16;
7use crate::microcode::Microcode;
8
9/// 8-bit read helper struct. Expands to a yield followed by an 8-bit read or write
10/// operation.
11pub struct Mem;
12
13impl MicrocodeReadable for Mem {
14    fn to_read(self) -> InstrBuilder {
15        InstrBuilder::r#yield().then(Microcode::ReadMem)
16    }
17}
18
19impl MicrocodeWritable for Mem {
20    fn to_write(self) -> InstrBuilder {
21        InstrBuilder::r#yield().then(Microcode::WriteMem)
22    }
23}
24
25/// 8-bit immediate value. Expands to code to fetch an 8-bit value from memory and
26/// increment the program counter.
27pub struct Immediate8;
28
29impl MicrocodeReadable for Immediate8 {
30    fn to_read(self) -> InstrBuilder {
31        // Fetch the PC.
32        // stack: ...|pcl|pch|
33        InstrBuilder::read(Reg16::Pc)
34            // Copy the PC so we can increment it.
35            // stack: ...|pcl|pch|pcl|pch|
36            .then(Microcode::Dup16)
37            // Increment the program counter.
38            // stack: ...|pcl|pch|PCL|PCH|
39            .then(Microcode::Inc16)
40            // Write back the new PC value.
41            // stack: ...|pcl|pch|
42            .then_write(Reg16::Pc)
43            .then_yield()
44            // Pop the address and use it to read.
45            .then(Microcode::ReadMem)
46    }
47}
48
49/// 16-bit immediate value. Expands to code to fetch two 8-bit immediate values
50/// sequentially.
51///
52/// Endianness is correct because we always push the lower byte of a LE word to the
53/// microcode stack first followed by the higher byte, which always keeps the word in
54/// gameboy LE byte order regardless of the running system endianness.
55pub struct Immediate16;
56
57impl MicrocodeReadable for Immediate16 {
58    fn to_read(self) -> InstrBuilder {
59        InstrBuilder::read(Immediate8).then_read(Immediate8)
60    }
61}
62
63/// Helper for reading the stack pointer and incrementing it.
64///
65/// When used as a [`MicrocodeReadable`], `IncSp` will load the value of the stack pointer
66/// to the microcode stack and then increment the stack pointer in-place.
67pub struct SpInc;
68
69impl MicrocodeReadable for SpInc {
70    fn to_read(self) -> InstrBuilder {
71        // Get the current stack pointer.
72        // stack: ...|spl|sph|
73        InstrBuilder::read(Reg16::Sp)
74            // Copy the SP so we keep the old value as the result.
75            // stack: ...|spl|sph|spl|sph|
76            .then(Microcode::Dup16)
77            // Increment the stack pointer.
78            // stack: ...|spl|sph|SPL|SPH|
79            .then(Microcode::Inc16)
80            // Write the incremented value back to the register.
81            // stack: ...|spl|sph|
82            .then_write(Reg16::Sp)
83    }
84}
85
86/// Helper for decrementing the stack pointer and reading it.
87///
88/// When used as a [`MicrocodeReadable`], `DecSp` will decrement the value of the stack
89/// pointer in place and read the value into the microcode stack.
90pub struct DecSp;
91
92impl MicrocodeReadable for DecSp {
93    fn to_read(self) -> InstrBuilder {
94        // Get the current stack pointer.
95        // stack: ...|SPL|SPH|
96        InstrBuilder::read(Reg16::Sp)
97            // Decrement the stack pointer.
98            // stack: ...|spl|sph|
99            .then(Microcode::Dec16)
100            // Copy the SP so we still have a copy on the microcode stack after putting it
101            // back in the register.
102            // stack: ...|spl|sph|spl|sph|
103            .then(Microcode::Dup16)
104            // Write the decremented value back to the register.
105            // stack: ...|spl|sph|
106            .then_write(Reg16::Sp)
107    }
108}
109
110/// Helper struct for manipulating the GameBoy stack from microcode.
111pub struct GbStack16;
112
113/// When used as a [`MicrocodeReadable`], `GbStack16` acts as a `pop` operation. A 16 bit
114/// value will be read out from the location of the stack pointer, and the stack pointer
115/// will be incremented by 2 (the stack starts at the end of memory and moves down).
116impl MicrocodeReadable for GbStack16 {
117    fn to_read(self) -> InstrBuilder {
118        // Get the SP incremented address.
119        // stack: ...|spl|sph|
120        InstrBuilder::read(SpInc)
121            // Yield a cycle and then read from the sp address on the stack to get the
122            // low-order byte of the value being popped.
123            // stack: ...|vl |
124            .then_read(Mem)
125            // Fetch and increment the stack pointer again.
126            // stack: ...|vl |spl|sph|
127            .then_read(SpInc)
128            // Yield a cycle and then read from the sp address on the stack to get the
129            // high-order byte of the value being popped.
130            // stack: ...|vl |vh |
131            .then_read(Mem)
132    }
133}
134
135/// When used as a [`MicrocodeWritable`], `GbStack16` acts as a `push` operation. A 16 bit
136/// value will be popped from the microcode stack, the stack pointer will be decremented
137/// by 2, and the value will be written to the new location of the stack pointer.
138impl MicrocodeWritable for GbStack16 {
139    fn to_write(self) -> InstrBuilder {
140        // stack: ...|vl |vh |
141        // Get the SP decremented address.
142        // stack: ...|vl |vh |spl|sph|
143        InstrBuilder::read(DecSp)
144            // Yield a cycle and then write to the sp address on the stack to write the
145            // high-order byte of the value being pushed.
146            // stack: ...|vl |
147            .then_write(Mem)
148            // Fetch and decrement the stack pointer again.
149            // stack: ...|vl |spl|sph|
150            .then_read(DecSp)
151            // Yield a cycle and then write to the sp address on the stack to write the
152            // low-order byte of the value being pushed.
153            // stack: ...|
154            .then_write(Mem)
155    }
156}
157
158/// Helper for reading the HL and incrementing it.
159///
160/// When used as a [`MicrocodeReadable`], `HlInc` load the value of HL, and then increment
161/// the HL register in-place.
162pub struct HlInc;
163
164impl MicrocodeReadable for HlInc {
165    fn to_read(self) -> InstrBuilder {
166        // Grab the current value of HL.
167        // stack: ...|l|h|
168        InstrBuilder::read(Reg16::HL)
169            // Copy the value so we can increment it.
170            // stack: ...|l|h|l|h|
171            .then(Microcode::Dup16)
172            // Increment the value
173            // stack: ...|l|h|L|H|
174            .then(Microcode::Inc16)
175            // Write the new value to HL, leaving the original value.
176            // stack: ...|l|h|
177            .then_write(Reg16::HL)
178    }
179}
180
181/// Helper for reading HL and decrementing it.
182///
183/// When used as a [`MicrocodeReadable`], `HlDec` load the value of HL, and then decrement
184/// the HL register in-place.
185pub struct HlDec;
186
187impl MicrocodeReadable for HlDec {
188    fn to_read(self) -> InstrBuilder {
189        // Grab the current value of HL.
190        // stack: ...|L|H|
191        InstrBuilder::read(Reg16::HL)
192            // Copy the value so we can decrement it.
193            // stack: ...|L|H|L|H|
194            .then(Microcode::Dup16)
195            // Increment the value
196            // stack: ...|L|H|l|h|
197            .then(Microcode::Dec16)
198            // Write the new value to HL, leaving the original value.
199            // stack: ...|L|H|
200            .then_write(Reg16::HL)
201    }
202}
203
204/// Provides the CPU's halt-handling behavior.
205pub struct HandleHalt;
206
207impl From<HandleHalt> for InstrBuilder {
208    fn from(_: HandleHalt) -> Self {
209        // stack: ...|
210        // Load the halt flag onto the microcode stack.
211        // stack: ...|halt|
212        InstrBuilder::first(Microcode::CheckHalt)
213            // Branch based on if the value is true:
214            // stack: ...|
215            .then_if_true(
216                // Read the set of active+enabled interrupts.
217                // stack: ...|int |
218                InstrBuilder::first(Microcode::GetActiveInterrupts)
219                    // Branch based on if there are any active interrupts.
220                    // stack: ...|
221                    .then_cond(
222                        // If there are active interrupts, clear the halt.
223                        Microcode::ClearHalt,
224                        // Otherwise, yield and restart the instruction fetch routine.
225                        // Yield is needed here because we haven't fetched an instruction
226                        // yet, so we need to wait for another 1m tick without fetching.
227                        InstrBuilder::r#yield()
228                            // This acts like a return, ending the current Instruction.
229                            .then(Microcode::FetchNextInstruction),
230                    ),
231            )
232    }
233}
234
235/// Helper to build microcode to service an interrupt.
236///
237/// Checks if an interrupt should be serviced, and if so performs the hidden isr
238/// instruction to jump to the interrupt handler. Runs FetchNextInstruction when done to
239/// reset and load the instruction at the microcode handler address
240pub struct ServiceInterrupt;
241
242impl From<ServiceInterrupt> for InstrBuilder {
243    fn from(_: ServiceInterrupt) -> Self {
244        // Retrieve the value of the IME
245        // stack: ...|ime|
246        InstrBuilder::first(Microcode::CheckIme)
247            // If IME is true,
248            // stack: ...|
249            .then_if_true(
250                // Retrieve any active and enabled interrupts.
251                // stack: ...|ai|
252                InstrBuilder::first(Microcode::GetActiveInterrupts)
253                    // If there are any active and enabled interrupts,
254                    // stack: ...|
255                    .then_if_true(
256                        // Get the address of the interrupt we are jumping to.
257                        // stack: ...|intl|inth|
258                        InstrBuilder::first(Microcode::PopInterrupt)
259                            .then(Microcode::DisableInterrupts)
260                            // Get the current PC.
261                            // stack: ...|intl|inth|pcl|pch|
262                            .then_read(Reg16::Pc)
263                            // Get whether the halt-bug is active.
264                            // stack: ...|intl|inth|pcl|pch|hb|
265                            .then(Microcode::PopHaltBug)
266                            // If the halt bug is active, decrement the PC value.
267                            // stack: ...|intl|inth|pcl|pch|
268                            .then_if_true(Microcode::Dec16)
269                            .then_yield()
270                            .then_yield()
271                            // Write the value of the PC to the GameBoy stack.
272                            // stack: ...|intl|inth|
273                            .then_write(GbStack16)
274                            .then_yield()
275                            // Write the interrupt destination to the PC.
276                            // stack: ...|
277                            .then_write(Reg16::Pc)
278                            .then(Microcode::FetchNextInstruction),
279                    ),
280            )
281    }
282}
283
284/// Helper to provide microcode for LoadAndExecute of an instruction, including halt-bug
285/// handling and enabling IME state ticking.
286pub struct LoadAndExecute;
287
288impl From<LoadAndExecute> for InstrBuilder {
289    fn from(_: LoadAndExecute) -> Self {
290        // Load the instruction onto the stack.
291        // stack: ...|opcode|
292        InstrBuilder::read(Immediate8)
293            // Tell the CPU we are about to process a real instruction now, so we should
294            // tick the IME after this.
295            .then(Microcode::TickImeOnEnd)
296            // Before executing, check if the haltbug is triggered, and if so, decrement
297            // the PC.
298            // stack: ...|opcode|hb|
299            .then(Microcode::PopHaltBug)
300            // Pop the haltbug flag and process.
301            // stack: ...|opcode|
302            .then_if_true(
303                // If true, push the PC, decrement it, and pop it back into the PC.
304                InstrBuilder::read(Reg16::Pc)
305                    .then(Microcode::Dec16)
306                    .then_write(Reg16::Pc),
307            )
308            // Pop the opcode off the stack and parse it, replacing this instruction with
309            // that opcode.
310            // stack: ...|
311            .then(Microcode::ParseOpcode)
312    }
313}