Adapting traditional embedded debug strategies to SoC designs
By Chuck Brokish, Senior Member, Technical Staff, Dennis Kertis, Applications Engineer, Texas Instruments, Austin, Texas, EE Times
June 3, 2002 (10:15 a.m. EST)
Embedded systems are becoming more complex. The trend in silicon design has been to put the whole system on a single chip. While there are many advantages, one primary disadvantage of embedding the entire system is debugging. As peripherals get integrated into the package and are connected directly to the processor, it is no longer possible to connect a logic analyzer to the external pins to monitor activity, because all the data paths are inside the chip.
Along with advancements in hardware also came an increase in software complexity. As the hardware became capable of doing more, and the cost of items such as memory reduced, today's software applications are capable of meeting larger requirements and are more complex. These larger requirements are in the form of multithreaded applications and the constraints of meeting real-time deadlines.
Debugging a real-time system increases the complexity of finding a problem in an SoC environ ment dramatically. Typically, in a real-time system, there are a few main tasks performing core functions and multiple background tasks in the form of interrupts handling things such as system I/O. In the case of applications such as motor-control, these real-time systems cannot be stopped or damage may occur to the device being controlled. Also, in multithreaded applications, many of the problems that arise are due to timing issues. So. halting the processor or stealing cycles for debugging purposes will change the timing of system, causing the problem to change or possibly go away.
Clearly, with the advances in today's hardware and software applications, a more advanced debugger is needed to accommodate these systems. Such a debugger must give you complete visibility into the system as well as the opportunity to monitor timing characteristics of the system, all while not disturbing the system's timing.
In the absence of such an ideal solution, what is left to us is to take advantage o f classic debugging techniques, combining them with new emulation techniques are being developed that allows more visibility into the system so that debugging methods such as triggering and tracing are possible today - even with our new systems being integrated into a single chip.
Combined with hardware-based techniques (JTAG and scan-based emulation), traditional techniques (hardware breakpoints) and variations (chained and complex breakpoints, state sequencing and real -time data exchange), it is possible to monitor the state of the processor within an SoC without affecting its ability to handle application demands.
Hardware breakpoints are very useful when debugging embedded systems because they allow you to monitor bus (data or program) activity for a certain value or a range of values, and break (stop the CPU) when the bus contains that value. A DSP processor such as the TMS320C55x core has a number of buses, including 3 read, 2 write and a program bus. Allowing the debugger to monitor either the address generated or the data value being transferred on a bus is a powerful capability.
This data monitoring is all done with extra hardware contained in the silicon, which means that the actual processor does not have to do anything extra to have this capability. This allows the debugger to set its data or address breakpoints without affecting the real-time requirements of the system.
A good example of when hardware breakpoints can be beneficial is when tracking "system hangs." Typically, a system hang occurs when the program counter branches into an invalid memory address. Usually, by the time we halt the processor, checking the value of the program counter or any registers would be futile, because they would have already been overwritten by invalid opcodes. But by setting a hardware breakpoint to monitor the program bus, we can detect immediately when the processor tries to execute an invalid section of memory and analyze the state of our system to determine the cause of t he invalid branch.
Hardware breakpoints can also be used to monitor data values as well. This can be useful for things such as intermittent data errors. If you have a variable that is being written to often inside a function, but occasionally seems to contain a value that you are not writing to it inside that function, you can setup a hardware breakpoint to monitor the values written to that address. When the incorrect value is written to that variable address, the processor will halt the system, allowing you to check where in your program the invalid value is being written to that variable.
These techniques of monitoring data ranges can also be used to monitor the index of an array. One of the most common mistakes in C programming is writing past the end of an array. By setting a hardware breakpoint to break if your index variable goes above a certain range, you can catch these mistakes before they happen.
Hardware breakpoints can also be used to spot a common problem known as stack overflow. Since hardware breakpoints give you the ability to monitor the address bus, you can setup the system to break if memory just below the stack is accessed. Provided, of course, that there is not valid memory that would need to be accessed by some part of your program already there.
The availability of multiple breakpoints in a system can provide advanced debugging features such as chained breakpoints. By chaining breakpoints, you set up a sequence of events that must occur in a specific order before the processor is halted. For example, for the debugger to halt the processor the system must hit breakpoint A, followed by Breakpoint B.
This can be useful if a function is called many times but corrupts data only if a specific sequence of events has occurred in that function.
While breakpoints are used to detect a specific operating condition of the devic e being debugged, due to the limitations of common hardware and software breakpoints, something else is necessary. In that case, it is common, upon halting the CPU, to interrogate the contents of specific registers, data, or memory and decide whether to continue running until the next breakpoint is hit. This becomes a form of "stop-mode interrogation", in which we would rather not really stop the processor until all of the specific criteria have been met to indicate a condition in which we would like to halt the processor for deeper debug.
The ability to recognize multiple conditions and/or a sequence of events entails a state sequencer. By combining debug logic and enabling enough capability with the tool itself, we can allow the target CPU to run and detect a series of events, and only halt when all conditions have been met to indicate an error in the application. State sequencing is also useful in a number of other ways in an embedded SoC design. For example, it may be that a certain memory locat ion gets an incorrect value written to it. However, that value is written from within the proper subroutine. We cannot set a breakpoint to halt the CPU every time the address is written. Instead, we need to try detecting other events that occur coincident with this error.
As control code operates, it can take different paths through our executable program. This results in some code that rarely gets executed. In an attempt to make sure that we have covered all paths within a module of control code, we may want to check the data at the address in question only after it has gone through a specific code sequence. In this case, we would want to detect if a specific set of instructions are executed, then the address in question is written to. This is referred to a state sequencer, or sequential trigger points
Another debug methodology that can be used to advantage in embedded SoC designs is through the exchange of data in real time between the target system and the host debugging environment during the debugging phase of an embedded system. Using Real Time Data Exchange, you can stream data from the target system to the host debugger without halting the processor. In fact, the data is sent up during the processor's idle loops so that the data is transferred with minimal intrusion on the target processor.