Most embedded system IC designs are done using a top-down design approach. The total design is divided into manageable functional blocks, which are each designed separately.
This paper presents some design organization techniques at the more abstract functional block level that greatly improve the ease with which firmware engineers can understand and control the overall design. This means the firmware engineer can work to a tighter schedule, resulting in earlier time to market than might be the case with less well-planned designs.
Separate access to each logic block
When hardware engineers are designing individual blocks in a design, each block will have a set of control and status bits that allow the firmware to read the current state of the design and control the future operation of the design. Sometimes there are many control or status bits, requiring several registers for the logic block. Other times there may be very few of these register bits.
If the control registers in several logic blocks only contains a few functional bit fields it is often tempting, for the sake of "efficiency," to combine these bits into a single register. From the standpoint of the hardware, address decode gates have been saved; from the standpoint of firmware, the project has just become a collaboration nightmare.
In this time of massive embedded system designs, firmware is never a one-man task. A firmware development project is executed in much the same manner as a hardware development project. The firmware team will generally consist of an architect and several firmware designers.
The architect is responsible for the overall design of the firmware. He determines how interrupts are handled, and how the firmware for the various low level hardware blocks communicate with each other. The total firmware design is then divided into manageable hardware blocks, and a firmware designer is assigned to each part. The code generated to control a hardware block in this context is called a hardware driver.
If two hardware blocks share a control register, then the firmware will run into situations where two different functions each try to modify the same register. Normally this is not necessarily a problem in firmware because, unlike hardware, only one function can be active at a time. There are exceptions to this rule, however:
1. Real-time operating systems split up the different threads of the firmware and allocate a certain amount of processor time for each thread. When a time slice is used up that thread is halted, status is stored, and another firmware thread is restored and begins running.
2. Interrupts can cause the currently running firmware to halt, store status, and relinquish control of the processor so that the interrupt service routine can react to the interrupt signal.
When firmware is modifying a bit field in a control or configuration register, it does this by reading the register, modifying the read value, and writing the new value back. This allows the processor to modify only the desired bit field while keeping the remaining bit field values the same.
In each of the cases mentioned above, a firmware thread can be halted between reading a control register and writing back the modified value. If, while that thread is halted, another firmware thread or an ISR reads, modifies, and writes that register, returning to the original thread will cause the value written by the ISR or other thread to be overwritten.
The result of implementing combined control and status registers, then, is more complex, slower, and less reliable firmware. Separate registers for each hardware block result in a much more efficient team effort on the firmware development.
Designing in this manner can also improve the portability of the firmware to the next generation IC. As future modifications are made to the hardware, only the drivers for the affected hardware blocks need to be modified.
Use separate registers for each bit field type
There are many types of register bit fields that are common to logical blocks in an embedded system IC. These include:
- Control Bit Fields These bit fields modify the current state, next state, or operation parameters of the hardware block. These bit fields can be changed while the hardware block is in operation. Control bit fields can be changed during a "pause" state, meaning a state where the logic is not currently operational but is expected to resume at the point where it stopped operation. "Go" bits, or bits that start an operation when modified, should be in a control register.
- Configuration Bit Fields These bit fields modify the operational parameters for the hardware block. These bit fields should only be changed while the hardware block is in a "reset" or "stopped" state. A bit that sets the parity to be used in a data path is a good example of the type of bit field that belongs in a configuration register. A "pause" state, when the hardware is currently stopped but is expected to resume where it was stopped, is not a point where a configuration bit field should be changed.
- Status Bit Fields These bit fields are read-only bit fields that supply state and configuration information to the microprocessor. These may be read at any time, but some of the bit fields may not be guaranteed to be stable if the register is read while the logic block is in operation.
- Interrupt Bit Fields These bit fields cause an interrupt signal to asynchronously affect the flow of the firmware. See my previous article for further information on intelligent interrupt design.
- Data Bit Fields Data bit fields reflect data on which the hardware is operating. A common data bit field is a bit field containing the top of a FIFO stack. These bit fields may be read-only fields or may be writeable, allowing the microprocessor to change the value of the data. Normally, data bit fields are changed only by the hardware during operation.
- Test Bit Fields These bit fields contain data, status, or control information that is not normally needed by the firmware. See items nine and ten in my previous article for more information on test registers.
The issue that this rule addresses is the basic Keep-It-Simple rule. Bit fields of the same type should be placed in a single register together (unless the total bit count is greater than a single register). Bit fields of different types should be placed in different registers.
With bit fields separated into registers based on their use, comprehending the control of even extremely difficult hardware blocks becomes easier. Firmware engineers in this day and age are dealing with 400+ page specifications in order to try to understand the three basic properties of an embedded system IC:
The added worry regarding whether a bit is a control or configuration bit, which register contains which status bit, and which bits in the interrupt register are really interrupts, adds unneeded headaches to the process.
This rule, combined with the next guideline, can also result in an easier upgrade path from the current IC to the next generation or revision of the hardware. This saves time for both the hardware development team and the firmware developers.
Use consistent relative locations for common register types
All the registers specified in the previous rule also apply here. While an embedded system design is divided into blocks for the various firmware engineers working on the project, a single engineer may be responsible for the design of drivers for multiple logic blocks. The logical and consistent placement of the registers in every block can greatly ease the pain with which he or she switches from one logic block to the next.
There are other benefits to this rule. It eases the learning curve when a firmware engineer is assigned a new block three months into the project. Debug efforts are simpler, as the firmware engineer need not refer back to the spec every time he accesses a register. Collaboration efforts between multiple firmware engineers are more easily handled without the need for those little register map charts taped to everyone's monitor, white board, and desktop.
Figure 1 shows an example of how the memory map for such a design might appear. The addresses shown are byte-wide addresses for 32-bit registers. It is important that hardware specifications always represent addresses and indexes in byte-wide formats. Mixed format addresses simply confuse the users of the IC (I automatically class any format other than byte-wide addresses as mixed formats because some other supplier to the total system is guaranteed to use byte-wide addresses in their specification).
Note the reserved location following the configuration register in the example in Figure 1. This sort of "hole" can appear in a block's memory map due to the fact that another logic block may require more than one of a particular type of register. Note that the example hardware block possesses two status registers; this will result in a hole in the memory maps of other registers possessing a single status register. This guideline really goes hand-in-hand with idea number seven from my previous article. Both of these increase the ease of use of the entire IC by the firmware and systems engineers.
Figure 1 -- Organized register map for a single logic block
Provide each hardware module with a separate block of addresses
This rule becomes almost a given when the previous guidelines are followed. If the relative locations of certain types of registers are consistent from logic block to logic block, it becomes very hard to intermingle the registers from two different logic blocks in the memory map.
Many common methods for setting up register definitions in firmware expect the registers for a single hardware block to be grouped within an address space with no outside registers mixed in. The register definition may be done using a C-code "struct," an array, or using a pointer-plus-offset technique. If registers from different logic blocks are intermixed, these structures or arrays become very complex and may contain large holes in the middle. These "holes," however, are not real holes but are writeable registers that are just not used by the current hardware block.
Grouping the registers of a logic block together means that all the registers for a logic block can be accessed using a single address pointer in the firmware. In addition to saving code space, this can avoid hard-to-find bugs that can crop up when the memory map changes.
This also increases the ease with which the registers of a logic block can be observed while debugging. It is a common practice to dump a set of registers to view when attempting to debug a design. "Holes" which are not really holes but are real registers not controlled by the current hardware block make comprehending a dump such as this extremely difficult.
It may make sense to include memory blocks in the address space for the logic block or it may not. In general, because memory is used differently from registers, memory is usually better off located elsewhere.
Provide unused address locations for each hardware block
In my experience, no new embedded system IC is ever entirely correct on the first pass. Additionally, even if the first version of the IC is functional, I've never met an IC architect who, at the end of a project, didn't have an extensive list of improvements that were not included in the design due to schedule constraints. Often the fixes or next generation upgrades require the addition of new registers into the register map. These can be done in two ways:
1. Add the registers at the end of the current register map.
2. Insert the registers in the middle of the current register map to keep them local to the registers of the modified logic block.
The first suggestion and I think I can safely say that I speak for all firmware engineers here is not an intelligent modification method. The second suggestion is the preferred method.
Depending upon how the memory map is coded in the firmware, inserting an added register into a massive, contiguous map of registers can wreak havoc with the C-code definitions for the registers. The firmware engineers will have to modify some (maybe all) defined literals that set up the register locations that follow the insertion point.
If extra space is initially included in the memory map for each logic block, additional registers become only a minor C-code change issue. The firmware for the current project can be reused on the next generation simply by adding the new registers in their proper places in the address map.
This can be taken a step further by planning future improvements into the memory map. If a logic block has sufficient status bits to use almost the entire width of a single status register, the hardware designer would be proactive by splitting the bit fields into two status registers with empty bit fields in each register. Alternately, the status register may be followed by an empty register space provided for future expansion. The same obviously holds true for control, configuration, or any other register type, which may require the full register width in the current implementation of the system.
Anything that can minimize changes to the memory map in the next revision or next generation of the product will decrease the time-to-market for the system as a whole. Note that Figure 1 shows a reserved space after Data Register 2. This is the impetuous behind the placement of this reserved space. If the next generation of the embedded IC requires an additional data register, the memory map for the hardware block will remain compatible with the previous version of the part.
Assign each logic block the same sized address space
A good corollary to the previous rule is to assign each logic block the same sized address space in the memory map. This is a simple technique that can assist a firmware engineer to keep the memory map in order in his or her mind. This may not be practical due to the varying number of registers in a logic block; this can be worked around, however, as is shown in the following example.
Figure 2 -- Example address map showing hardware blocks
An embedded systems IC contains 14 logic blocks, all with 16 or fewer registers except for two. One logic block contains 33 registers and another contains 57 registers. Assuming the registers are 32-bits wide, a 64-byte address space will work for every logic block except the two larger ones.
The 33-register block could be assigned a 192-byte address space (with 15 empty register locations) and the 57-register block could be assigned a 256-byte address space (with 7 empty register locations). In this manner, the address space for every logic block starts on a 64-byte boundary with the larger logic blocks simply occupying more than one of the 64-byte spaces.
Note that, in Figure 2, the last hardware block uses all sixteen available register spaces. It would have been more prudent to assign two 64-byte address spaces for this logic block in order to provide for future expansion.
The guidelines mentioned in this article are really design organization guidelines. The hardware architect who follows these guidelines will develop an embedded system IC that is not only easier for customers to comprehend, it will also be easier to upgrade or revise in future projects. The revision ease occurs on both the hardware side and the firmware side of the design, saving time in hardware development and in the implementation of the IC in a full system.
David Fechser is a senior staff engineer working for the Systems Verification group of the Storage Products Division at LSI Logic Corp. David lives in Fort Collins, Colorado and can be contacted at 970-206-5678 (firstname.lastname@example.org).