Debugging Software
Debugging Software
1 Introduction
This tutorial presents some basic concepts that can be helpful in debugging of application programs written in the
Nios® II assembly language, which run on the Intel® DE-series boards. A simple illustrative example involves the
Intel FPGA Monitor Program software used to compile, load and run the example assembly language code on the
Nios II processor with a DE-series board.
The reader is expected to be familiar with the Nios II processor, as well as the Intel FPGA Monitor Program. An
introduction to this software is provided in tutorials: Introduction to the Intel Nios II Soft Processor and Intel FPGA
Monitor Program, which can be found in the University Program section of the web site.
Contents:
• Example Circuit
• Debugging Concepts
• Corrective Action
• Concluding Remarks
2 Background
Designers of digital systems are inevitably faced with the task of debugging their imperfect initial designs. This
task becomes more difficult as the system grows in complexity. The debugging process requires determination of
possible flaws in the designed circuits as well as the flaws in application programs that are supposed to run on this
hardware. This tutorial addresses the second of these aspects of debugging. A discussion of hardware debugging is
available in the tutorial Debugging of Hardware Designs on DE-Series Boards.
Host computer
(USB connection) DE2-115 Board
USB
Blaster USB Audio VGA TV RS-232
KEY0 chip CODEC DAC Decoder chip
Reset
JTAG Nios II processor USB Audio A/V Video-out Video-in Serial PS/2
port port port config port port port ports
System Cyclone IV
Interval Floating On-chip
ID FPGA chip
timer point memory
Parallel Parallel Parallel LCD Parallel SRAM SDRAM Flash IrDA SD Card Parallel
port ports ports port port controller controller controller controller controller port
3 Example System
Our example system is the DE2-115 Computer System that can be used to test the reaction time of a person to a
visual stimulus. The user initiates the test by pressing a pushbutton key, KEY1 . After some delay (of at least one
second) the circuit turns on a light. In response, the user presses any one of KEY3 to KEY1 , as quickly as possible,
which results in turning the light off and displaying the elapsed time in hundredths of a second on the 7-segment
displays on the DE-series board. A block diagram and Platform Designer implementation of the system for the
DE2-115 board is given in Figure 1 and Figure 2, respectively.
The computer system for the other DE-series boards varies only where a given peripheral does not exist or is of a
different size. See the computer system documentation for your specific board for more information.
The processor is Nios II/s and the application is loaded from SDRAM. The following key components and PIO
interfaces are implemented:
• Pushbuttons is a multi-bit input port that reacts to the falling edge of the input signal by setting to 1 a corre-
sponding bit in its edge-capture register; KEY1 (bit1) is used as the button to start the test and is configured in
software to raise an interrupt when pressed.
• Green_LEDs is a multi-bit output port that drives the green LED that tells the user to react.
• Interval_Timer is a 32-bit interval timer with a writable period and will be used to measure the reaction time.
The addresses assigned to the PIO interfaces and interval timer are as indicated in Figure 2. A possible application
program, written in the Nios II assembly language, is presented in Figure 3. Note that we have numbered the
statements to make it easy to refer to them in the discussion below. The main program sets up the stack and enables
interrupts to be raised by the interval timer and the Pushbuttons PIO. It also sets up the processor to allow interrupts.
Finally, it enters a loop continuously checking the status of the start flag before it begins the test.
1 .include "nios_macros.s"
2 .equ GREEN_LED_BASE, 0xFF200010 /* Green_LEDs PIO */
3 .equ HEX3_HEX0_BASE, 0xFF200020 /* Seven Segment PIO */
4 .equ PUSHBUTTON_BASE, 0xFF200050 /* Pushbutton PIO */
5 .equ INTERVAL_TIMER_BASE, 0xFF202000 /* Interval Timer */
6 .equ stack_end, 0x007FFFFC /* Initial stack address */
7 .section .reset, "ax"
8 br _start /* Upon reset go to start */
9 .section .exceptions, "ax"
10 /* Interrupt service routine */
11 subi sp, sp, 4 /* Save registers on */
12 stw r2, 0(sp) /* the stack */
13 subi sp, sp, 4
14 stw r3, 0(sp)
15 subi sp, sp, 4
16 stw r4, 0(sp)
17 subi sp, sp, 4
18 stw ra, 0(sp)
19 rdctl et, ctl4 /* Check if external */
20 beq et, r0, END_ISR /* interrupt has occurred */
21 subi ea, ea, 4 /* If yes, decrement ea */
22 andi r4, et, 1 /* Check if timer irq0 has */
23 /* been asserted */
24 beq r4, r0, BUTTONIRQ /* If not, do not call timer ISR */
25 call TIMERISR /* Call timer ISR */
26 BUTTONIRQ: andi r4, et, 2 /* Check if pushbuttons irq1 */
27 /* has been asserted */
28 beq r4, r0, END_ISR /* If not, end exception */
29 movia r4, PUSHBUTTON_BASE /* Clear button edge capture */
30 addi r3, r0, 0
31 stwio r3, 12(r4)
32 movia r11, 0x1 /* Set start flag to 1 */
33 END_ISR: ldw ra, 0(sp) /* Restore registers */
34 addi sp, sp, 4 /* from the stack */
35 ldw r4, 0(sp)
36 addi sp, sp, 4
37 ldw r3, 0(sp)
38 addi sp, sp, 4
39 ldw r2, 0(sp)
40 addi sp, sp, 4
41 eret /* Return from Exception */
The interrupt service routine is used to set the start test flag and to service the timer interrupts. The main program
uses three subroutines NEW_TEST, DECIMAL, and DISPLAY. NEW_TEST controls the counter that measures the
reaction time. DECIMAL converts the binary count into a decimal number by using the standard conversion method
of dividing by 10 and keeping track of the remainder. The DISPLAY subroutine simply displays the reaction time on
the seven-segment display. Note that the interrupt service routine follows the normal practice of saving the registers
that it uses (r2, r3 and r4) on the stack. Registers (r10 and r11) are used to store the counter value and the start flag
value.
4 Debugging Concepts
Debugging of very small programs is fairly simple, but the task can be difficult when larger programs are involved.
The chore is made easier if one uses an organized approach with the aid of debugging tools.
• Identifying the source of the problem, i.e. the nature and the location of a bug
Often it is easy to see that there is a problem because the observed behavior does not match the designer’s expec-
tations in an obvious way. In our reaction tester, an obvious problem appears when a bug has an effect such as not
turning the green light on or off, producing a meaningless output on the 7-segment display, or not producing any
output at all.
Bugs can also cause errors that are not readily apparent. For example, our tester may evaluate the reaction of a user
incorrectly and display a time that is wrong by a factor of 2 or more. A difference between 20 and 40 one-hundredths
of a second may not be easy to spot.
Finding the source of a bug is often a challenging task. It requires a good understanding of the computation that
the application program is supposed to perform. Breakpoints can be used stop the execution of a program at a
particular point of interest. Then, examining the contents of processor registers may indicate a problem. Examining
the contents of memory locations can also be helpful. Single stepping through a portion of the code can be used to
verify that the instructions generate the expected intermediate results.
For a large program, it is prudent to use the divide-and-conquer approach. Relatively small portions of the program
may be tested separately. This is especially effective if the program is written in a modular fashion. For example, in
the program in Figure 3 it is possible check the code that converts a binary count into a decimal number by loading
a predetermined value into register r10 and then executing the subroutine DECIMAL.
The Intel FPGA Monitor Program provides a useful vehicle for both running and debugging of programs. It gives
the user an ability to:
Using the Monitor Program, the user can investigate the state of the system at various points of execution of a
program that has to be debugged. The execution is stopped by placing a breakpoint, at which point one can examine
the contents of processor registers and memory locations as well as observe the results of evaluated watches. New
data can be written directly into both registers and memory locations. Single-stepping through critical sections of
the program is useful to determine whether the flow of execution and the generated intermediate results are correct.
Success in debugging tends to reflect the user’s experience. An experienced user, who has debugged many different
programs, will need much less time than a novice.
5 Corrective Action
Having identified a bug, it is necessary to determine its cause. There are many possible causes, which include:
• Typing mistakes
• Erroneous communication with input/output devices, particularly when interrupts are involved
Of course, a poorly written program may also contain code that simply does not implement a desired task.
Once a bug is identified, the necessary corrective action is obvious in most cases. But, sometimes the user may need
to learn more about the details of the system that does not appear to be functioning correctly. For example, a bug
due to an error in interrupt handling cannot be dealt with without a sufficient knowledge of the interrupt mechanism.
A corrected program must be tested to ensure that it does not contain other bugs.
Syntax errors are usually easy to find and correct. The Nios II compiler and assembler will generate messages that
identify the erroneous statements. For example, suppose that instead of statement 61, which loads a part of the
desired period into the periodl register of the interval timer, we include the statement
In this case, the Monitor Program will indicate an error and display the message
Errors due to typing mistakes may be difficult to discover. Consider an error where the statement 87 is mistyped as
Running the erroneous program will execute all instructions in the correct sequence, but the displayed output will
indicate that no time was was taken, which is obviously the wrong result. In this case, one can speculate that
something is wrong with the counting mechanism. This can be verified by checking the value read from the counter,
which is used in the conversion to a decimal number. The value of interest is in register r10. To discover this value,
use the Monitor Program to place a breakpoint at instruction 97 and then run the application program. The execution
will halt just before the call instruction is executed which causes a branch to the subroutine DECIMAL. Observe
that the value in register r10 will be 0, which suggests that the obtained count was wrong.
Next, we should check the counting scheme. An error may occur if there is something wrong with instructions that
control the counter. Remove the previous breakpoint, place a new breakpoint at instruction 79, and run the program.
The execution will stop at this instruction. Now, start single stepping through the instructions that follow. Observe
that the green light will turn on when instruction 80 is executed. The next three instructions reset the reaction time
count register. They are followed by instructions that enable the counter. Single step through these instructions and
observe the contents of registers r3 and r10 in the Registers pane of the Monitor Program window. r10 should be
0 when the counter is cleared. Observing that instruction 87 causes the contents of r3 to be 8, which means the the
timer was never correctly started.
Interrupts must be handled exactly as specified by the Nios II interrupt mechanism. Any omissions in the initial-
ization of interrupts will result in failure. For example, suppose we forget to set a required interrupt mask bit in
an I/O interface. To illustrate this, remove statements 55, 56 and 57 in our example program. Recompile and run
the program. When this program is running there will be no observable response to pressing the pushbutton, KEY1 .
Stop the execution of the program by clicking on the Monitor Program icon . Note that the program execution
stops at instruction 68, which suggests that the interrupt service routine may not have been reached. This guess
can be verified by placing a breakpoint in the interrupt service routine, perhaps at instruction 11, and running the
program again. The same behavior will be observed; the breakpoint will not be reached during the execution of the
program. Clearly, the interrupt service routine is not being executed. Now, check the values in the processor control
registers, which will be 0x00000001 and 0x00000003 in registers status and ienable, respectively. This indicates
that an interrupt from the Pushbuttons PIO will be recognized and serviced by the processor. However, the contents
of register ipending are equal to zero, even though KEY1 has been pressed. This means that no interrupt request
appears at the processor, because the PIO has not raised such a request. The likely reason is that this interrupt has
not been enabled in the PIO circuit, which indeed is the case in this example.
The reader may be tempted to simply single step through it until the interrupt service routine is reached. This would
not work because the single-stepping feature of the Intel FPGA Monitor Program disables all interrupts. Hence,
single stepping will never get past the branch instruction in statement 68.
Remove statements 30 and 31 in Figure 3 and run the program. Place a breakpoint at line 29. When KEY1 is pressed,
the breakpoint at the pushbutton interrupt service routine will be reached. Continue the program and observe that the
breakpoint will be continuously reached without pressing KEY1 . This behavior suggests that continuous interrupts
are being received by the processor. Remove the breakpoint at line 25. Observe the state of the control registers
at the end of the interrupt service routine by placing a breakpoint at line 41. The status register is properly cleared
to zero, which happens when the processor enters the interrupt service routine in response to an interrupt request.
The immediately preceding state of this register is saved in the estatus register, which has a 1 in bit estatus0 as
expected. The ienable register shows that interrupts from the Pushbuttons PIO are enabled, which is also correct.
The ipending register indicates that there is an outstanding interrupt request, which the processor is trying to service.
This is wrong, because a single pressing of KEY1 should not result in multiple interrupts.
At this point it may be useful to look at what is happening in the PIO. Place a breakpoint in the beginning of the
interrupt service routine, at statement 9. Run the program and press KEY1 . The execution will stop at the breakpoint.
To examine the state of the PIO registers, click on the Memory tab in the Monitor window. The Pushbuttons PIO
registers are mapped to memory locations starting at 0xFF200050. So, type FF200050 in the address box of the
displayed window and click Go to display the contents starting at this address. Note that 32 bits are shown for
each register address (because these registers can be specified to have from 1 to 32 bits), even though each of these
registers are much smaller in our circuit. The unimplemented bit positions are displayed as being 0. The data register
(at address FF200050) contains data0 = 0, which is the state of KEY1 when it is not pressed. The interruptmask
register (at address FF200058) shows interruptmask1 = 1, because this bit was set by executing instruction 57. The
edgecapture register (at address FF20005C) also shows the value 2, which means that the PIO has an active interrupt
request. This bit must be cleared to 0 by the interrupt service routine. Now single step through the instructions
until the end of the interrupt service routine. Observe that the state of edgecapture register does not change, which
means that the processor will see another interrupt request as soon as it exits the interrupt service routine (at which
time it restores its status register using the contents of register estatus. The conclusion is that the program lacks an
instruction that clears the edgecapture register.
When calling a subroutine by executing the call instruction, a Nios II processor saves the return address (which is
the address of the next instruction) in register ra (r31). A return from the subroutine, performed by executing the ret
instruction, loads the address in ra into the program counter. In the case of nested subroutines, it is essential to save
the address in ra on the stack, so that it is not lost when the inner subroutine is called.
If an instruction addresses a wrong location it will lead to a bad outcome. Consider an error where the address in
statement 3 in Figure 4 is specified as FF200030 rather than FF200020. Running this program turns the green light
on and off as expected, but the 7-segment display will not display a value which indicates that there is a problem.
Placing breakpoints into the various segments of the program and stepping through these segments will indicate
that the flow of execution appears to be correct. The error can be discovered by checking the contents of processor
registers while single stepping through the instructions that read the contents of the counter and convert them for
display.
7 Concluding Remarks
In this tutorial we discussed the key issues in debugging. Bugs range from simple to very difficult to find. Successful
debugging is contingent on:
• Debugging aids that are provided in tools such as the Intel FPGA Monitor Program
Another key factor is the experience of the user. A novice can become an expert only through experience.
Copyright © Intel Corporation. All rights reserved. Intel, the Intel logo, Altera, Arria, Avalon, Cyclone, Enpirion,
MAX, Nios, Quartus and Stratix words and logos are trademarks of Intel Corporation or its subsidiaries in the U.S.
and/or other countries. Intel warrants performance of its FPGA and semiconductor products to current specifications
in accordance with Intel’s standard warranty, but reserves the right to make changes to any products and services
at any time without notice. Intel assumes no responsibility or liability arising out of the application or use of any
information, product, or service described herein except as expressly agreed to in writing by Intel. Intel customers
are advised to obtain the latest version of device specifications before relying on any published information and
before placing orders for products or services.