Today's system-on-a-chip (SOC) designs typically contain micro-controllers, digital signal processors, memories, peripherals, and physical interface connections to external devices in a single package. Portions of the firmware used to test and verify these devices can also be used during the normal operation of the device. To allow for this, the engineer implementing the firmware into an external device needs to consider the functional operation of the device and how to interface with other executable code. Examples of the types of external device functions are memory access routines, on-chip peripheral drivers, and bootstrap and system start-up. Since reduced power consumption is a goal of SOC designs, any reduction in external memory access will reduce the power consumption of the design. By placing parts of the firmware in on-chip ROM, memory access can be faster and use less power.
This article describes the requirements and challenges of providing system start-up code for the SOC designs and discusses how different processor designs can be configured and boot code can be started at power up time. Of significant importance is the execution of the code at start-up. This code must be able to detect if it needs to perform a Flash load, run a diagnostic, or perform normal system initialization. The article will also describe how the start-up code can be implemented in on-chip ROM, where it must last throughout the lifetime of the device, and how it will be able to handle unknown system configurations. I'll place particular importance on the Flash loading process. The Flash loading process must handle Flash-programming algorithms of many device types, yet also be small in size. Additionally, the Flash loading process must be able to communicate with an external device to receive the data for programming. Ultimately, my goal is to identify some of the candidate firmware for on-chip code and provide insight to the requirements for accomplishing such a task.
Power and external connections
To begin with, let's assume the device under consideration contains a microcontroller, internal bus controller, external bus controller, internal RAM/ROM, serial IO port, IrDA port, and external Flash memory. Certainly, one of the first design considerations is what the device needs to do at power-on and reset time. At power-on reset time the device must check for external device connection on the serial port, perform system initialization, and execute operational firmware. (This can be verification, diagnostic, or functional code.) Also, an external device is connected to the serial port of the unit under test to load firmware, execute tests, run diagnostics, and load configuration setting. This requires two conditions: to detect device connection at power on reset and to pass execution to operational code.
First, start-up code must be able to detect a device connection. Hardware design must provide this status to the code. As an example, the external device can drive the input to a non-default state. When the unit under test is powered on, the start-up code will read the state of the input pin. An external device connection is indicated when the input pin isn't at the default state. This indication tells the start-up code to expect information to be sent via the serial port. This information will be executable code. Any protocol you chose to use must be set during the design phase.
When the transfer is complete, the start-up code (for example, the bootstrap code) passes execution control to the downloaded code. If there is no indication that an external device is connected, the firmware passes execution control to the operational code.
System initialization code will setup the operating environment of the system. This includes hardware configuration, RAM initialization, setup variables, and any other task required before executing operational code. Operational firmware may be for test and verification, diagnostic, functional operation or production code. I will discuss a Flash-programming module as an example of functional operation code later.
Illustrations of on-chip ROM code
The on-chip ROM code is where the start-up code will reside in the device. Therefore, at power-on reset, hardware must access the first instruction from internal ROM. Since this code can't change for the life of the device, it must be designed with future changes in mind. The key is to keep this code as simple as possible. Some examples of the on-chip ROM code include bootstrap, diagnostic, algorithms, and functions.
As I alluded to earlier, the bootstrap code is responsible for checking for an external connection and passing control to the download code or the operational code. For example, the serial port is designed with a transmit register, receive register, and a control/status register. This control port has a bit defined indicating connection status of an external device, TX register empty bit, and a RX register full bit. The hardware must be designed to place the address of the first instruction of the bootstrap code on the address bus at power-on reset time. The first thing to do is to setup the processor and hardware to allow for code execution. The bootstrap code will read the serial port control register. If the external device connection status bit isn't at its default state, execution control is passed to the download process.
Remember that the communication protocol must be defined early in the SOC design phase. The download process will read the data from the serial port by polling the RX register full bit. Each time the serial port RX register is read, the RX register full bit is reset. The reset is a function of the hardware. The download process will use the first few bytes of data to setup the transfer from the external device. These first few bytes will contain destination address, data length, and any required hardware configuration information. It's very important to only include configuration data that will allow reading the serial port and storing data at the desired memory location. This could be internal or external memory.
Another example of on-chip ROM code can be found in diagnostics, such as memory tests. Memory tests are used over and over again during the life of a device. These tests typically are small in size and can be designed to use parameterized functions and check the results. The functions are candidates to be placed in on-chip ROM. Example: Walking Ones function to test an address bus. In addition, an algorithm, once verified, can also become a candidate for on-chip ROM code. Finally, as indicated above, functions can be a good candidate for on-chip ROM-for instance, memory write, memory read, and value comparison.
Let's take a look at a simple communication protocol that could be used by the bootstrap code. Since the bootstrap code is relatively simple, the protocol must also be simple. Here it is:
External Device (ED)
Target Device (TD)
ED: Driver input pin low
TD: Return ACK char
ED: Send first configuration byte
TD: Return ACK char
ED: Send n-2 configuration byte
TD: Return first configuration byte
ED: Send n-1 configuration byte
TD: Return n-2 configuration byte
ED: Send last configuration byte
TD: Return n-1 configuration byte
ED: Send load address byte n-3
TD: Return last configuration byte
ED: Send load address byte n-2
TD: Return load address byte n-3
ED: Send load address byte n-1
TD: Return load address byte n-2
ED: Send load address byte last
TD: Return load address byte n-1
ED: Send length byte n-1
TD: Return load address byte last
ED: Send length byte n
TD: Return length byte n-1
ED: Send ASCII char 0x7e
TD: Return length byte n
ED: Send data n-length
TD: Return ACK char
TD: ED: Send data n-length+1
TD: Return ACK char
ED: Send data n
TD: Return END char
Echoing the data back in this manner will allow the external device to detect communication failures. After all data has been sent, the bootstrap will pass control to the load address of the loader code. If there is an error, the target must power off, then on, to restart the process. Additional error handling, data validity checking, or other features of communications may be employed as well. Most important, however, is to keep it simple, small, and fast.
The loader code will contain a more robust protocol to communicate with the external device. Once the loader is running, it can interrogate the Flash memory to determine what type it is.
This information can then be sent to the external device. The external device will use the Flash-type ID to select the proper Flash-programming algorithm and download it to the loader. The loader will save the algorithm to internal RAM and use it for programming the Flash. Normally, the Flash type doesn't change much in the product's life cycle. As new Flash is developed, however, a means to update the loader is needed. Also, once the loader is deployed, maintenance becomes an issue. The best way to handle maintenance is to design it in at the start.
The process of selecting Flash type and algorithms for programming is better managed outside the device. Providing a means to load on demand can save time and money down the road. The loader can be modularized to allow for future products as well. Once this has been done, future development of your products won't be consumed by building yet another loader program.
When complete, the external device is disconnected and the target is powered off. With the external device disconnected, at power-up reset the bootstrap code will pass control to the firmware loaded to the Flash memory. This is another area where hardware and software designers need to be in agreement. The first address in the Flash memory space doesn't necessarily have to be Flash memory. Creative mapping memory addresses to accommodate your design can be very useful. This is one reason why co-design is important to SOC development. Other operational code is loaded in the same manner as the loader code. This could be a diagnostic or verification test used to verify or debug device operations. Product operational firmware can be loaded to internal memory for debug.
The development environment required for implementing and verifying the ROM resident code must provide access to the simulated hardware. Memory models, Instruction set simulators, and hardware bus functional models need to be used to accelerate the run time of the hardware simulator. Also, a source code debugger needs to be integrated into the hardware simulation environment. Currently, some tool venders are providing tools that support this co-design/verification environment.
An understanding of the hardware is critical for the development of on-chip code. The software and hardware developers must work in concert to make smart trade-off decisions. Also, the software must know the hardware configuration options well enough to insure the SOC functions properly. Details such as physical memory mapping, bus timing, and bus size all impose perils to firmware.
HW & SW must come together Developing on-chip code for the SOC market is challenging and requires significant communications and understanding between the software and hardware developer. Trade-offs and changes made during this time can significantly impact the success or failure of a product.
Firmware developed during the hardware design and verification not only allows for on-chip code, it also reduces the overall development time. Since a significant portion of the low-level code can be developed, verified, and embedded into the external device, system integration and higher-level software development may start shortly after the device is received from the fab.
David Shanahan is a principal consulting engineer for Intrinsix Corp. During his 25 years in the industry he has worked for Texas Instruments, Burroughs/Unisys, QLogic, and Commquest/IBM. He has been involved in embedded software for peripherals, device controllers, and SOC designs. He is currently focused on embedded firmware for SOC designs.