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.