Dr. Dobb's Journal June, 2004
Software details such as data typing, function partitioning, and external code integration can be specified at very low levels. Bit-accurate simulation of the detailed model provides a baseline of expected responses that facilitate actual testing of the embedded system and software when deployed. Some developers go so far as to consider the detailed model their "source code."
As the model is elaborated, implementation code can be generated at any point along the way, typically in C. Traditionally, the code is used for simulation, rapid prototyping, hardware-in-the-loop testing, and other purposes. Within the past few years, code has been optimized and improved to the extent that it is now being deployed onto embedded systems. Some of these embedded systems are certified to high-integrity levels such as RTCA/DO-178B Level A, which is used by the Federal Aviation Administration (see "Software Considerations in Airborne Systems and Equipment Certification," RTCA/DO-178B, RTCA Inc., 1992).
Code deployed in embedded systems is termed "production code." Production-code generation helps software and electronics engineers by providing a common framework for adding software details to the behavioral algorithm model. As with previous software evolutions, such as going to C from assembly, a number of software engineering best practices need to migrate up to the next level. Best practices include requirement tracing and configuration management. Verification and validation (V&V) techniques also need to migrate up into the modeling domain, especially for high-integrity systems.
One of the primary aspects of high-integrity software is that it is often certified by a certification authority. This is certainly true in aerospace where a multitude of government agenciesthe Federal Aviation Administration, European Joint Aviation Authority, Department of Defense, UK Ministry of Defense, and othersare involved in certifying airborne software. These systems undergo a safety assessment to identify various failure conditions and evaluate how the system responds to those failures. The response might be catastrophic, have no effect, or fall somewhere in between.
With DO-178B, the software integrity level is derived from the software's contribution to the system failure condition. If the system response to a software-induced failure is catastrophic, or severely impedes safe flight, the software must be developed as high-integrity software. In recent years, high-integrity software has become more pervasive and increasingly complex as new aircraft systems increase their reliance on autonomous embedded control strategies, such as fly-by-wire and integrated avionics. Unmanned aerial vehicles (UAVs) also obviously depend heavily on autonomous algorithms and, as such, develop them using high-integrity software practices.
Many standards exist throughout the world to assist developers in creating high-integrity software. Each standard has a governing body, which certifies that the system was developed properly based on the evidence or artifacts that the engineers were required to produce. These standards and processes are followed regardless of whether the flight code was generated automatically or by hand.
The software engineering process can be separated into development, V&V, and Framework.
The bottom line is that production-code generation technology is of little use for high-integrity systems if it cannot address these activities. In this article, I step through each software engineering activity, demonstrating some of the available methods and tools for model-based design and production-code generation technologies from The MathWorks (the company I work for; http://www.mathworks.com/).
At the heart of a model is an algorithm consisting of block diagrams and state machines, each with specific timing and real-time execution characteristics. With model-based design, the algorithm is often combined with environmental components to produce a complete executable system. Components include sensors, actuators, mechanical devices, dynamic vehicles, electronics, and other physical elements that interact with the algorithm.
For example, a system model of an aircraft fault detection, isolation, and recovery (FDIR) application would need to model the FDIR logic along with the aircraft and actuator physical dynamics. The model would also need a user interface to let developers interact with the model and assess its nominal and fault behavior. Figure 1, a model of this system, includes mode logic, control laws, actuator models, a plant (for example, the aircraft dynamics and atmosphere effects), signal conditioning, and a method for inserting faults.
Figures 2 and 3 show FDIR model components, including a block diagram for feedback control and state machine for mode logic. Additional components can be included such as digital-signal processing (DSP) filters. The primary purpose of the system model is to provide algorithm designers with detailed insight into the problem domain, allowing them to design the appropriate system behavior.
Not every model needs to include detailed physical model components. In fact, developers using new model-based design often start by simply surrounding the algorithm with signal generation and scope blocks to emulate an open loop test harness (Figure 4).
Detailed software design begins once the system and software requirements have been specified using the models. In many circumstances, this is where the system engineering groups end their work and the embedded software teams begin. Unfortunately, this is the also the point where miscommunication often occurs.
Production-code generation helps bridge this gap by providing mechanisms for performing detailed software design directly on the same model used by the system or algorithm designers. Once the software has been fully designed, automatic code generation produces the final code.
Examples of detailed software design that can be performed directly on the models include:
With model-based design, the algorithm model is in effect constrained by the software design. For example, the algorithm may be developed in double precision for the ideal behavior, then fixed-point constraints are added to get the real behavior on the microcontroller. Figure 5 shows a model and parameter form for the Sum Block. A "show additional parameters" checkbox is where design information or constraints are entered.
The floating-point version of the Sum Block used during algorithm specification is converted into a detailed software design simply by changing fields in the block's parameter form. It is also possible to use a data dictionary or workspace to set the various signal and parameter attributes.
The fixed-point information can also be ignored, allowing for simulation and code generation in floating point. This makes it easy to go back and forth between requirements and design, which is a problem with some development methods, especially during maintenance.
High-integrity software needs clearly defined data types and data qualifiers. Figure 6 shows how storage classes can be defined and illustrates a few of the detailed data and data-type modifiers available. User-specified data types and attributes could also be added using the advanced custom storage classes feature.
High-integrity software also requires explicit use of data and restricts implicit promotion and conversion of data types during math or other operations. For simulation and code generation, it is possible to set warning and error flags that detect a variety of mistyped operations. This ensures that proper typing is used when the model is the detailed specification source. This is a great benefit over using C as the source because C is a weakly typed language.
Simulation on the host or code execution on the target lets developers verify that the implementation satisfies (or closely approximates) the requirements model. It is then time to generate the actual embedded production code. Once the detail design is complete, code generation from a model is a straightforward process, much like the process for using a C compiler. Various code-generation optimization settings and user-configuration options exist. The key is to make sure that the code is efficient, accurate, deterministic, and integrates well with other code or tools. It is also important for the code to be traceable to the diagram so that it may be reviewed and verified. Sometimes, optimization settings conflict with traceability settings, so developers are advised to understand these tradeoffs and develop code-generation standards for their projects.
Tracing high-level requirements to final implementation is a key part of high-integrity software development. Figure 7 shows that the generated C code can be traced back to the model. The figure shows an HTML report that was automatically produced with hyperlinks back to the model. As shown, when the developer selects the Sum Descriptor in the code, the Sum Block in the Rate Monotonic model is highlighted.
Figure 7 uses Rate Transition Blocks to specify the scheduler. Links also exist to commercial RTOSs, including OSEK variants. Additionally, integration with device drivers and legacy code can be accomplished in many ways. Some development organizations create device-driver blocks and place these within the model to fully specify the entire software system. Other groups prefer to keep the application software separate from the operating system to improve modularity and portability. Both approaches are supported.
Software standards such as DO-178B provide guidelines on an acceptable software verification strategy. Testing is often based on the requirements. After running the tests, structural coverage is determined. If full coverage was not achieved, additional testing and analysis is needed to ensure that there is no dead code, possibly due to typos or specification flaws.
Obtaining 100 percent MC/DC coverage is often one of the biggest challenges for developing high-integrity software.
One way to increase the likelihood of achieving MC/DC coverage of the code is to develop a full test suite with model coverage capability within the modeling environment.
Figure 8 shows a series of test sequences specified using a signal builder block. Assertion blocks are placed within the model and, optionally, the generated code to determine if the tests pass or fail based on expected results.
Model coverage also can be generated as part of the test procedure. The coverage reports provide MC/DC information and details on state reachability. Production-code generation configuration options include the use of makefiles and templates, which allow for the automatic invocation of source analysis tools, such as instruction set simulators, Lint, and code coverage tools. This lets developers reuse the model's test scenarios on the code level.
Configuration management (CM) systems help ensure that changes to documents, models, software, and builds are carefully controlled and managed. Developers are required to check out software before making changes, and then check in the changed software with revision information. These changes are then merged with other changes in a repeatable and reversible manner. CM is a fundamental part of any software engineering process. With high-integrity systems, additional scrutiny and approvals come into play, typically involving configuration control boards. An example CM interface from a model is shown in Figure 9.
Every project has requirements, usually in the language best understood by the user. Management and control of these requirements keep projects on schedule and within budget. Requirements management interfaces exist for linking requirements specified in software packages to the model that satisfies the requirements. When the requirement is selected, the model, block, or diagram that satisfies the requirement is then automatically invoked and highlighted to clearly show the trace.
Finally, documentation is always needed. It allows management, customers, and suppliers to comprehend and approve the model at key milestone events such as formal design reviews (PDRs or CDRs, for instance). Automatic report generation creates reports based on the models, simulation, and test results. These reports can be executed or simulated, as opposed to simply being static text.
Production-code generation is being used in a wide variety of applications. Recent examples include automotive electronic control units (ECUs), aerospace flight software, medical devices, power plants, and signal-processing systems. The technology is being developed at a fast pace, with each release providing more optimizations and capabilities. However, it is not enough to simply generate good code. The entire software engineering process needs to be supported.
DDJ