Design and debug your ROM-based codeAssuming ROM is your memory of choice, proper debugging can save you loads of time and money.
It's difficult to think of an embedded device that doesn't contain any type of memory. Lowering costs and increasing densities have made memory use in today's system designs very generous. More memory is provided to let software consume it all up in the name of more features and personalities, and of course, more bugs to debug. Commonly found memory types in systems include ROM, SRAM, DRAM, PROM, and flash.
The moment your PC game execution starts choking, the blame is "not enough RAM." Much of the code is executed out of RAM. SRAM (static random access memory) is faster than DRAM (dynamic random access memory), and hence, costs more. RAM retains data as long as power is applied, and DRAM in particular, needs to be refreshed regularly. Typically, SRAM is available in limited sizes while DRAM can more easily be scaled to fit the system.
ROM, on the other hand, can retain data, even when power is removed. Many ROM variants are available, including OTPROM (one-time programmable read-only memory), EPROM (erasable programmable read-only memory), and EEPROM (electrically erasable programmable read-only memory).
Many system-on-chip (SoC) designs contain both ROM and SRAM. This ROM is usually hardwired or masked ROM. The ROM is mostly used to store the first-order boot-loader program. After reset, ROM is usually mapped to the address from where the main CPU starts fetching instructions. A second-order boot loader or the main program can then be downloaded from an I/O interface. As ROM is fast, it can also be used to store the critical and well-tested library of important routines. Such routines typically don't change during the system's lifetime.
In ROM, software becomes hardware. The content can't be changed once the chip is manufactured. This "read-only" nature mandates a thorough debug of ROM code. You can't debug ROM-based code once the chip is manufactured using traditional debug techniques, namely putting breakpoints and watching the system state. There's very little you can do even after the bug is traced in ROM code. Lots of resources need to be spent to remove that null pointer reference.
But then, why do we use ROM in SoC designs? One can always map a NOR flash at the reset vector, and NOR flash can be re-reprogrammed until that last known bug is trashed. A few reasons to use ROM in SoC designs are:
- ROM is not very expensive and provides a default boot option.
- ROM helps during production stage; ROM code can program the flash in a live system.
- Critical and time-tested routines can be executed faster through ROM.
- Secret data can be stored in ROM, though it's possible to reverse engineer the system.
For some security system requirements, use of external flash memory isn't desirable. It's a must to be paranoid while designing ROM-based code. Be prepared for the worst and keep a few basic tenets in mind:
- Keep the design small and simple. Don't try to do fancy things in ROM code.
- Don't complicate the program flow. Break your code into well-thought-out decision points.
- Provide an exit path from all error conditions.
- Provide hooks to execute code from RAM at regular places in code. Such patching can save the day for you in buggy times.
- Keep extra margins for timing-related behavior.
- Implement simple device drivers for I/O interfaces. Put limitations on data transfer sizes to ensure a simple design.
- Review your code's assembly output for sections where keywords like volatile are used. Ensure that compiler is doing the right thing.
- Make sure that the register header file used is same as the current hardware description.
- Make sure the ROM release build is done with all debug-related flags turned off.
- Prepare a detailed checklist to use during any ROM code release. This list should enforce the checking of all aspects of function and logic behavior.
- Document your design extensively. This will help in reviews.
- Open your design and code for external peer review. Don't provide advance information of program behavior; let the reviewer decode the program flow herself. Validate the understanding at review time.
It's always a difficult time for the implementer of ROM code when the first sample from the fab is tested. This is even worse when there's no other boot option apart from booting from ROM. However, testing and debugging of ROM-code SoC designs is very important. It's always worthwhile to invest in hardware-software coverification, particularly if it's the first silicon. Such testing can take a relatively long time. Consider this while estimating your efforts for ROM-code development. Electronic system level (ESL) tools can provide a faster option.
Second-level boot options usually include booting from external storage devices, such as those containing NAND or EEPROM. Interfaces like SPI and I2C, though very simple, can cause timing problems. Modeling these interfaces with the storage-device characteristics isn't easy in a hardware-software coverification environment. Such limitations make FPGA-based reference-board validation very important. Use tools like ROM emulators, and follow these guidelines:
Run your tests with storage devices of various sizes and from various vendors. Run tests on a release build. Debug builds can affect timing characteristics, which can lead to differences in program behavior. The best form of testing ROM code is to ensure a total review. Review every line of code.
Arvind Chauhan works for Wipro Technologies, based in Bangalore, India. For the past three years, he's been designing ARM-based SoCs with a focus on hardware-software partitions, platform code development, embedded Linux porting, and system modeling. Chauhan can be reached at firstname.lastname@example.org.