2

I'm writing a simple GB emulator (wow, now that's something new, isn't it), since I'm really taking my first steps in emu.

What i don't seem to understand is how to correctly implement the CPU cycle and unconditional jumps.

Consider the command JP nn (unconditional jump to memory adress pointed out), like JP 1000h, if I have a basic loop of:

increment PC
read opcode
execute command

Then after the JP opcode has been read and the command executed (read 1000h from memory and set PC = 1000h), the PC gets incremented and becomes 1001h, thus resulting in bad emulation.

tl;dr How do you emulate jumps in emulators, so that PC value stays correct, when having cpu loops that increment PC?

joncys
  • 1,300
  • 11
  • 25

3 Answers3

0

Move increment PC to the end of the loop, and have it performed conditionally depending on the opcode?

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Wow, this is pretty embarrassing. I cannot do it the way you suggested, because I'm writing a more sophisticated interpreter than just switch - case of all opcodes, and even so — two switch - case statements would be an redundant (having to check if that was a JP command 2nd time). What I can do though, is have the increment PC in the middle of the loop, that way the opcode gets read before INC PC — this solves the problem with jumps without breaking anything in the current loop implementation. Please update your answer to reflect this, so that I can accept it. Thanks for your help! – joncys May 15 '12 at 22:55
0

I know next to nothing about emulation, but two obvious approaches spring to mind.

  1. Instead of hardcoding PC += 1 into the main loop, let the evaluation if each opcode return the next PC value (or the offset, or a flag saying whether to increment it, or etc). Then the difference between jumps and other opcodes (their effect on the program counter) is definable along with everything else about them.

  2. Knowing that the main loop will always increment the PC by 1, just have the implementation of jumps set the PC to target - 1 rather than target.

Ben
  • 68,572
  • 20
  • 126
  • 174
  • I thought about these two approaches, I just knew in my gut there has to be a classy way of solving this for all cases. It came to my mind, that you can put inc PC to the middle of the loop, after reading Oli's answer. Thanks for your insight, appreciated. – joncys May 15 '12 at 22:59
  • To me, 1. is the "classy" way of solving it, and 2 is the slightly less classy but pragmatic and saving on typing approach. I would want everything that is determined by which opcode is executed to be encapsulated in a single definition for each opcode; the "PC += 1" really **is** something that's specific to each opcode, which is demonstrated by the fact that you run into this problem. Doing it conditionally based on the opcode means your implementation of jump codes are spread out across multiple places, and if you ever add another jump and forget to update the main loop things go wrong. – Ben May 16 '12 at 00:22
  • approach 2 normally (most processors) does not require any adjustment, a relative branch assumes the pc has been incremented. An infinite loop, an instruction that branches to it self is a pc = pc - 1 instruction if the pc is known to be one ahead on execute or pc = pc -2 if known to be two ahead. 1) requires EVERY instruction to have a pc modifier, much more typing. 2) only the strange cases if any have a pc modifier in the implementation. Instead of *pc. instead of a single *pc++ replace that with *pc and a few hundred pc++; lines? 1) is not less typing. – old_timer May 16 '12 at 04:51
  • the 1) approach for most processors is actually your answer number 2) at the same time by removing the increment when used you have to add pc modifiers all over the place. target = pc+1+offset instead of target = pc+offset. for the modified z80 in the gb, most of the 200+ instructions do not use the pc so you dont want all those pc++; lines of code put it up top. some/many are variable word length, only a few use the pc as an operand for computing an address and for those you have to know what the pc modifier is. – old_timer May 16 '12 at 04:55
  • the 1) approach while more typing might be more sane from a programmers perspective, the code may be much easier for people to read and understand as things only change in the implementation of the instruction rather than above and below and all over the place. more readable might imply easier to debug and maintain. the argument could go both ways, more lines means more human error means more bugs. – old_timer May 16 '12 at 04:57
0

The PC should be incremented as an 'atomic' operation every time it is used to return a byte. This means immediate operands as well as opcodes.

In your example, the PC would be used three times, once for the opcode and twice for the two operand bytes. By the time the CPU has fetched the three bytes and is in a position to load the PC, the PC is already pointing to the next instruction opcode after the second operand but, since actually implementing the instruction reloads the PC, it doesn't matter.

Martin James
  • 24,453
  • 3
  • 36
  • 60
  • Yes, this explains the logic behind the whole "put increment PC in the middle of the loop" thing. Thank You, Sir. Much appreciated. – joncys May 15 '12 at 23:03
  • @joncys In fact, this is how microprocessors used to work internally. (Modern microprocessors do all sorts of stuff these days, but still have to expose the original behaviour as an abstraction.) – Neil May 15 '12 at 23:16
  • @Neil was this specifically designed in the first specs for these types of situations, or did it came as a "side effect" (very loosely used term here) of the wiring (hardware), that proved to be very useful? – joncys May 15 '12 at 23:27
  • @joncys I don't know, but `*p++` is a common pattern in C, so I'd be surprised if it wasn't designed. – Neil May 15 '12 at 23:45
  • That sounds like a horrible way to code if it isn't forced on you by hardware, in my opinion. What happens when you implement something like a relative jump, which need to read the PC during opcode execution? What happened to the rule of thumb that an operation should *either* have side effects *or* return a value, not both? – Ben May 16 '12 at 00:28
  • @Ben Z80 processor relative jumps 'The offset needs to be calculated from the address of the *next* instruction, which for these instructions is always $ + 2'. So, my 'hardware design' is OK for relative jumps. 'rule of thumb that an operation should either have side effects or return a value, not both' - where did that come from? C#, Delphi etc. architects obviously did not know that rule when they implemented properties with getter/setter methods. – Martin James May 16 '12 at 09:54