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}