It is common, when building a newly designed board, to install only a few components at a time and test the partially built board to protect expensive components, to validate portions of a design, or just to contain the hardware debugging problems. The first time power is applied to a board, often only the CPU, memory and ROM socket are installed. Naturally, software is usually required and a development environment that works in this case is necessary, especially as the board design and construction progresses.
Figure: Imaging Subsystem Engine (ISE) Block Diagram [9]
Even complex designs can have real estate constraints, leaving no room for the extra hardware to support a full operating system. A case example of this is shown in Figure . In order to fit this design on a PCI card, extra parts like UARTS had to be left out, and program memory had to be kept to one flash and 2 DRAM chips.
Conventional operating systems usually serve two interesting roles: they abstract the target hardware, and they provide a means of loading and executing programs, often in separate protection domains. An operating system provides an operating environment, including but not limited to a device driver interface and a common interaction with the user. It is separated from applications by a kernel structure, bounded by trap handlers or some form of call gate that allows the operating system to function to some degree independent of and protected from the applications that it carries.
Several commercial embedded operating systems are available that run on the relatively conventional CPU in Figure , but most commercial operating systems, available in binary form, require board support packages written to provide the necessary support for the O/S, including a console, time ticks, and memory setup.
The ISE board (Figure ) in particular has no serial port, so program loading must be done either by programming the socketed FLASH memory with a prom programmer, or writing into the board support package a console driver that uses the PCI bus to communicate as a console. The MON960 monitor [8] supports the latter, and the Cyclone-911 board [4] in particular can be used this way, given the appropriate host software.
Although it is sometimes nice to have an operating system that is portable, and essential that certain libraries be portable, it is rare that an embedded program is, or should be, portable. The whole point of a program is to manipulate the specific toaster. There is no value being able to run the toaster program on the VCR. It therefore is rarely useful to have a device-driver interface in an embedded kernel--such can actually make things harder.
We questioned the prudence of forcing a kernelized operating system onto a board with only a few LEDS and an oscilliscope for debug output, and a ROM socket for input. We anticipated this happening often, as designing and building boards is our business. We also noted that the device driver interface of a kernel is pointless, and our targets typically run a single trusted program from reset to power off. We eventually concluded that we didn't really need an operating system at all.
This, then, became the chosen path. We wrote a minimal runtime to support C and C++ that works on the sorts of target boards expected, and we provided that support for a specific compiler, the GNU GCC compiler. Writing the support for the compiler alone, we reasoned, would be easier then writing a board support package for compiler and operating system and would get everything needed without the added constraints of an operating system.
This runtime support for the compiler, called uCR, proved lightweight and powerful enough that we not only used it as the regular development environment, we used it to build bootstrap loaders and other programs in support of embedded development itself.