Running and Stepping Code

The Z80 code can be stepped through using the buttons in the register view or by using Function keys. Run To is only available with a right click...

Operation Key Button Menu Effect
Reset Yes Reset Z80
Run F5 Yes Yes Run from current PC
Stop F6 Yes Yes Stop Z80
Step Over F10 Yes Yes Step Over
Step Into F11 Yes Yes Step Into
Run To Mouse Right Click Run to clicked line

'Yes' in the Button column indicates that there is a button under the register list. 'Yes' in the Menu column indicates that the operation is on the Debug menu. The Debug menu has the function keys listed for quick reference.

If a function key isn't working, it is because the focus is not in the code view - simply click on the code and the function keys will work again.

Reset resets the Z80. On real hardware, this only resets the CPU so it is not equivalent to a system reset. If you do a system reset with the debugger connected, a dialog box will appear and the debugger will attempt to recover.

Run starts the Z80 running. Before it is started, the register values shown on the debugger are loaded on to the Z80 target. The register values can be freely edited when debugging to manipulate the code execution. The PC can be set to any address and execution resumed from there. The Z80 flags can also be set to break out of loops or test your code. When the CPU is running, the code view is greyed out. The Z80 will continue to run until it hits a breakpoint or is stopped by the user.

Stop stops the Z80 and reads the current register values. Register values go red if they have changed after each run or step.

Step Into executes one Z80 instruction (unless there is an interrupt, see below). If a CALL is stepped, the next instruction will be the start of the called function.

Step Over is similar to Step Into but stepping a CALL will execute the whole function and stop on the line after the CALL. For most other instructions Step Over behaves the same as Step Into. Step Over is the natural way to debug higher level code and Step Into is only used to debug a called function.

Run To runs to the line that was right clicked. This is done by putting a temporary breakpoint on the line and running from the current PC. When debugging C code, each C line can be stepped by right-clicking the start of the line. If the code view goes grey, it means the Z80 hasn't reached the line you clicked on and you'll have to press Stop.

There is no support for a Step Out operation which is available on some debuggers. Simply right-clicking the RET instruction and doing a step is all that is required to leave a function.


How Stepping Works

At first glance, stepping looks simple - just execute one instruction. This is trivial on an emulation and can be done by controlling the Z80 clock on hardware. Some simple ICEs do exactly this and it is very difficult to debug code with them because they don't support Step Over which is the main stepping operation and code can't be executed with the interrupts enabled because every step takes you into the ISR. The simple approach also has problems with breakpoints and stopping the clock has implications for hardware e.g. DRAM refresh. So we use a completely different approach.

The debugger first works out where the Z80 is going to stop after the step. In the case of a Run To, this is a known address (the line which you clicked). In the case of a Step Into or Step Over it is more complicated.

The debugger reads a few bytes of memory at the PC address, decodes the instruction and works out the next PC address after the instruction has been executed. Most instructions don't jump anywhere so the next instruction is a few bytes away but some instructions are more complex e.g. JR Z, -10 where the debugger needs to work out whether the jump will happen and what address it will jump to by reading the registers and flags.

Once the next address is known, the debugger puts a breakpoint on that address and runs from the current PC address at full CPU speed. In reality, the process is complicated by the need to manage the user breakpoints for example to run from a line with a breakpoint and to deal with differences between the different types of step and the fact that the step may not reach the breakpoint or take so long that the user presses Stop.

As much as possible, the debugger is designed to give you an intuitive experience similar to modern devices with built-in debug hardware such as x86 or ARM systems and debuggers such as Visual Studio. However, it is important to have an idea what is going on under the hood so you understand what could be happening when you get surprising results. Here are a few things to look out for...

Impossible Steps

Some instructions are impossible to step, for example...

      
            LOOP1:  JP    LOOP1

            LOOP2:  DJNZ  LOOP2
          

In these cases, the breakpointed address is the same as the instruction. So when the debugger tells the Z80 to run, it will stop immediately because there is a breakpoint on the instruction. Any instruction that jumps to itself has the same problem. This includes CALLs, indirect JUMPs or even a RET to it's own address. Of course these situations are extremely rare in normal code, the only common cases being the examples above.

You can RUN from these instructions but if you try to STEP them the debugger will show an error box explaining what you did.

Workaround: For the JP example, there is no need to step the code. For the DJNZ, change the B register so the instruction exits or RUN TO (right click) the next instruction.


HALT Instruction

On an emulation target, if you run some code and the Z80 reaches a HALT instruction then you press STOP in the debugger, it shows the PC at the HALT instruction. Trying to run or step a HALT instruction simply stays on the same address unless an interrupt happens. This means that sometimes a HALT cannot be stepped and sometimes it can (when it goes through the ISR).

On a hardware ICE, there are some further restrictions with the HALT instruction - more details.


Stepping with a Breakpoint in the ISR

If you step an instruction and an interrupt is taken, the Z80 will go through the ISR and stop after the stepped instruction. Most of the time this has no effect except that the registers or memory contents may change as a result of executing the ISR and the performance metrics will be different. However, sometimes the debugger may behave differently to what you might expect.

One common case happens if a breakpoint is placed inside the ISR. Stepping the foreground code will stop in the ISR when the interrupt is taken. If the user tries to step over the RETI at the end of the ISR, the expected result would be to return to the instruction after the one that was originally stepped but often that doesn't occur. Instead the RETI returns to the original instruction. For example, in the following code...

      
            100 > NOP
            101   ADD  A, B
          

...if the PC is at 100 and a STEP OVER is done, then an interrupt occurs and the Z80 stops at a breakpoint in the ISR, stepping out of the ISR may return to the same place (the NOP) rather than the next instruction (the ADD).

What happens is that the interrupt is recognised by the Z80 before the NOP is executed and the return address is to the NOP. This happens on emulation targets because the interrupt line has been set by the previous instruction and happens on real hardware because the clock on the Z80 in the ICE is always running and so it can recognise the interrupt before it runs user code (the instruction before the NOP in this example).


LDIR Instruction

Repeating instructions like LDIR can be interrupted part way through a transfer and continue after the ISR exits. This works because the Z80 uses the registers to keep track of the current state. So, similar to the previous problem, stepping from a breakpoint in the ISR lands at the LDIR, not the next instruction. This applies to all the repeating instructions including DJNZ.

In the case of LDIR, a similar problem occurs if there is a data breakpoint on a memory address accessed by the LDIR during the transfer.


Interrupt Controls

The Z80 ICE doesn't stop the Z80 clock. Therefore, if there are regular interrupts such as a 1ms timer running, every step will enter the ISR when the interrupts are enabled. The debugger will execute the whole ISR and stop at the breakpoint on the next instruction. For this reason, we have added control of the interrupts to give you a choice whether to service interrupts while stepping code.

The IE check box tells you whether the Z80 interrupts are enabled and the Block checkbox can be set to block interrupts when connected to hardware with the ICE. The difference between them is that the Z80 code can control IE whereas Block is a hardware feature of the ICE. The Z80 Emulator has no interrupt connection so Block does nothing but the System Emulator behaves the same way as the hardware ICE.