Here’s how to turn MATLAB code into efficient, embeddable C code. Topics covered include the Embedded MATLAB language subset, coding practices, and The MathWorks’s M-to-C tools.
As the design evolves toward embedded implementation, real-world constraints must be incorporated, which typically requires the user to manually translate MATLAB algorithms into C code. Manual translation involves rewriting the convenient MATLAB syntax for matrices into C implementations. The end result is multiple copies of the same algorithm written in different languages. At this stage, the user faces the burden of verifying that these copies remain equivalent throughout multiple design iterations. In this workflow the cost of verifying revisions quickly becomes prohibitive, resulting in a design that either solidifies too quickly or simply diverges from the original specification.
Recently The MathWorks, creator of MATLAB, introduced new tools that directly address this issue. These tools can automatically convert a well-defined subset of MATLAB language, called Embedded MATLAB, into embeddable C code. This technology can reduce the development and verification cost of manual translation from MATLAB to C. The Embedded MATLAB language subset supports more than 270 MATLAB operators and functions and 90 Fixed-Point Toolbox functions.
Working within the Embedded MATLAB subset, you can maintain one copy of the design—one « truth »—and elaborate it directly within MATLAB to incorporate embedded implementation requirements. Design iterations become easier because the Embedded MATLAB algorithm is still MATLAB code, and you retain the interactive debugging and visualization capabilities in MATLAB. This approach provides the algorithm domain expert and the embedded software engineer a common language and shared understanding of the design intent. You can automatically generate C code from the Embedded MATLAB code, eliminating the cost of producing and verifying hand-written C code.
In this article, we first review features of MATLAB that are useful at the early stages of the design process. We then examine the inefficiencies associated with manually translating MATLAB « concept » code into C code for implementation. We then present an alternative workflow, where all the embedded elaborations to the algorithm are done in MATLAB instead of C. Using an example, we highlight the steps necessary to make an algorithm compliant with the Embedded MATLAB subset and show the use of new tools that automatically translate an Embedded MATLAB code into embeddable C code.
The table below compares MATLAB to C-code with respect to features relevant in the early phases of design.
(Click to enlarge)
Table 1.Converting a typical MATLAB algorithm into embeddable C code involves accommodating several implementation requirements:
- Data Type Management—Data types must be determined before implementation. For example, the pixel values in image processing are often represented as 8-bit unsigned integers, and samples in speech processing are represented as 16-bit signed integers. The use of default 64-bit double precision variables in MATLAB is not memory efficient.
- Static Memory Allocation—MATLAB seamlessly handles dynamically changing variable sizes at run time. In embedded applications, however, we usually avoid dynamic memory allocation and statically define memory for a given size and data type before using it.
- Reduction of Computational Complexity and Memory Footprint—Embedded software designers spend a lot of time mapping high-level algorithms to operate efficiently within the limited memory and computational resources of the target hardware. This effort results in tuning the design to the instruction set and data representation of the target processor.
- Fixed-Point Support—Implementation in embedded software or hardware may require that the algorithm be completely specified with fixed-point data types.
Engineers typically perform these modifications by first translating the MATLAB algorithm into C code, which creates the design gap mentioned above. During this translation, the software engineer may introduce errors or numerical changes into the C code. If those changes are the intentional result of code optimizations, the algorithm designer may need to reproduce them in the MATLAB algorithm to maintain equivalence. This process adds unnecessary work and potential for errors. A workflow for embedded implementation based on Embedded MATLAB addresses these issues.Unlike many MATLAB algorithms, Embedded MATLAB code is not an abstract mathematical representation of the design. It contains all the details needed for an efficient, embeddable C implementation. Any MATLAB code that complies with the Embedded MATLAB subset can generate embeddable C code. The process of ensuring compliance with the Embedded MATLAB subset involves the same four implementation requirements discussed previously. With Embedded MATLAB, the implementation constraints are specified directly in the MATLAB code.
Applying the implementation requirements lets you:
- Maintain one copy of your design in MATLAB
- Elaborate the design from a concept form to an implementation-ready form by incorporating embedded design constraints directly in MATLAB
- Iterate, test, and debug the code using the visualization and analysis capabilities available in the MATLAB environment
- Verify the functional correctness of the elaborated design
- Automatically generate embeddable C code using Real-Time Workshop
Making a MATLAB algorithm compliant with the Embedded MATLAB subset may require you to:
- Set data types for variables—You assign data types either in the body of the MATLAB function or at compile time. Assignment at compile time is more convenient, since it permits a single MATLAB function to produce multiple C function variants with different data types, dimensions, and complexity. Because you specify data type and sizes of variables at the function interface, the data types and sizes of variables used in the body of the function are automatically inferred.
- Accommodate array size changes without dynamic data allocation—In MATLAB, the size of a variable may change between loop iterations. You can accommodate array size changes for embedded implementations by using buffers of a constant maximum size and addressing sub-portions of the constant-size buffers.
- Create an Embedded MATLAB compliant function—Not all MATLAB toolbox functions comply with the Embedded MATLAB subset. These functions are designed for flexibility and numerical accuracy, not for embedded implementation. You can use the toolbox function as a template or reference to develop a functionally equivalent Embedded MATLAB function that meets the computational and memory constraints needed for efficient embedded C-code implementation. The desired C code can then be generated automatically from the Embedded MATLAB code.
- Convert from floating-point to fixed-point—You can use tools such as data logging and data-type override in the Fixed Point Toolbox to observe the dynamic ranges of variables in your MATLAB algorithm. These tools help you convert the algorithm to an optimized fixed-point representation in MATLAB. Because the original and the converted algorithms are both in MATLAB, you can directly compare the floating- and fixed-point results to ensure that the differences are within acceptable tolerance levels.
Embedded MATLAB example
Let’s use an example to illustrate the steps of this development process:
- Elaborate the concept code to add embedded constraints by ensuring that it is Embedded MATLAB compliant and functionally equivalent to the reference code.
- Automatically generate C code directly from the MATLAB desktop.
An Embedded MATLAB workflow can be applied to a wide range of engineering problems in control design, communications, and signal and image processing. For this example, we chose an image processing algorithm known as adaptive median filtering because it deals with changing variable sizes, a typical scenario in many MATLAB programs.The MATLAB function shown in Figure 1, « adaptive_stats.m, » extracts successively larger regions around a given element of a matrix to compute statistics (minimum, maximum, and median) over these regions. The function outputs a filtered center element of the matrix based on these statistics.
Figure 1. Noncompliant statistics function.Step 1: Verifying compliance with an Embedded MATLAB subset
To determine whether this function is compliant with the Embedded MATLAB subset, we recommend using the EMLMEX command. As it compiles the Embedded MATLAB code, EMLMEX checks for all potential Embedded MATLAB syntax violations and automatically generates a compilation report that describes the violations and provides links to problematic lines of code.
The syntax of EMLMEX as applied to this function is:
The « example » option (following the –eg delimiter) with EMLMEX provides an intuitive way to perform compile-time assignment. Recall the compliance task mentioned earlier of setting data types for variables: the -eg option sets the data types of variables in the Embedded MATLAB function by specifying an example for the function interface. EMLMEX extracts the data types and sizes of the variables in the MATLAB workspace listed in the cell array following the –eg option. It assigns them at compile time to the inputs of the function, and then infers the data types and sizes of all variables in the function.
The compilation report in Figure 1 shows that the original function is not compliant, since five variables (window_ind, region, rmin, rmax, rmed) change size as the variable s takes on different values in the loop. (The non-compliant portion is highlighted.) Recall the second compliance task mentioned earlier of accommodating array size changes without dynamic data allocation: translating this function to C manually would have resulted in undesirable dynamic memory allocations for those five variables.
The solution is to introduce a new function that operates on the regions of interest of a constant maximum-size matrix. The modified function « adaptive_stats_roi.m, » as shown in Figure 2, is identical to the original function except that it contains no size-change violations and has a call to the new function « roi_stats.m, » which implements the new region-of-interest function. The new function call is highlighted in Figure 2.
Figure 2. Compliant function.Note that we included some implementation-oriented optimizations into the MATLAB code as we wrote the new function « roi_stats.m, » by:
- removing overhead of multiple function calls
- reducing computational complexity by removing redundant for-loops for each statistic
- obtaining three statistics by indexing different elements of the same sorted array
Many MATLAB constructs, operators, and functions in our example are supported as part of the Embedded MATLAB subset. The « mysort » MATLAB function below sorts a sub-portion of an array.
By running the EMLMEX command and observing no error messages, we ascertain that the updated function « adaptive_stats_roi.m » is now Embedded MATLAB compliant and ready for automatic C code generation. Note that you can also use the MATLAB M-Lint checker to identify the compliance issues of your algorithm.
Step 2: Automatic C-code generation from an Embedded MATLAB compliant function
EMLC is a command-line tool that automatically translates compliant MATLAB code into C source code. You can use the following command to generate code for the compliant function « adaptive_stats_roi.m »:
The –report option lets you create an HTML report with hyperlinks to C source files and header files automatically generated from the MATLAB function.
(Click to enlarge)You can easily integrate existing C code within the Embedded MATLAB workflow. You may have developed a library of C functions and want to reuse them within your program. Embedded MATLAB provides C interface functions like « eml.ceval », which enable you to call external C functions from within Embedded MATLAB functions. For example, in our MATLAB function « roi_stats.m », we can replace the call to MATLAB function « mysort » with a call to an existing C function « c_sort » declared as:
This replacement involves the use of « eml.ceval » below, which passes input and output arguments to C functions by value or reference.
Code generation using the Embedded MATLAB language subset aims to close the idea-to-implementation gap. It introduces a new workflow where algorithm design and elaboration occur in a single language and environment. Through automatic C-code generation from MATLAB, you maintain a single source for your design, tracking of iterations becomes easier, and algorithm designers and software engineers can work more collaboratively to quickly produce the best implementation.
(To learn more about Embedded MATLAB, visit the Embedded MATLAB page on the MathWorks web site at www.mathworks.com/products/featured/embeddedmatlab/.
This page contains demos, customer testimonials, technical details, and links to related MathWorks products. To learn how to use Embedded MATLAB through a detailed demonstration, watch one of the Webinars featuring Embedded MATLAB, such as « Algorithm Design and Code Generation with Embedded MATLAB » at www.mathworks.com/company/events/archived_webinars.html.
Also visit MATLAB CENTRAL www.mathworks.com/matlabcentral and search by keyword « Embedded MATLAB » to see user-contributed examples featuring Embedded MATLAB along with the MATLAB files used for demonstrations of the Webinar.)
Part 2 discusses the process of integrating Embedded MATLAB code into Simulink models as part of a Model-Based Design workflow.
part 2: System-level integration and verification
The MATLAB language is widely used by engineers to develop algorithms that are then implemented in embedded software, typically through manual translation into C code. The algorithm intellectual property (IP) created in MATLAB is frequently a component of a larger hardware or software system.
To facilitate integration into the embedded system, algorithm functionality and interfaces need to be verified in the context of the overall system design. Creating a realistic test bench in lower-level languages such as C can be a time-consuming engineering effort that will most likely be discarded at the end of the project. It can be more efficient to integrate the algorithm using a graphical system design environment like Simulink.
Simulink allows the user to quickly construct a test bench, model and simulate a system with a set of pre-defined blocks, and verify algorithm operation within the context of the complete system. Algorithm debugging and design iterations are easier in this environment because the algorithm remains in MATLAB. When the algorithm meets requirements, you can generate embeddable C code from the Simulink model or by using the workflow from MATLAB to C described in the previous article.
This article presents a workflow for system-level integration and verification of algorithms written in the Embedded MATLAB language subset. As an example, we use an adaptive noise filtering algorithm to enhance a video signal received over a wireless multimedia system. We construct a Simulink test bench to test the Embedded MATLAB algorithm, integrate the algorithm into a system-level wireless model, refine the algorithm to meet system-level metrics, and add control logic to improve processing efficiency.
Our example features a simplified algorithm and system model for the purpose of clarity. However, the workflow can be extended to more complex algorithms and systems.
Imagine that you are tasked with designing a video post-processing algorithm that compensates for errors in a live video stream over a mobile communications network. You need to verify that your design provides adequate video quality even in areas where the signal reception is poor.
The overall quality of the received video greatly depends on the interaction between the communications and video codec subsystems. Taking into account the system-level perspective will help you find the best solution. Therefore, you need a single environment in which to develop your algorithm that can accurately model the behavior of each individual subsystem as well as their interactions.
MATLAB is well-known as an algorithm development environment. Simulink is a graphical environment that enables multi-domain modeling and simulation that is useful for system-level design and integration. In the section below, we explain how to integrate existing MATLAB algorithms into a Simulink model.
Embedded MATLAB Function Block in Simulink
In this example, we assume that an algorithm has already been developed in MATLAB and complies with the Embedded MATLAB requirements discussed in the first article of this series. We also assume that system-level models are constructed in Simulink and may contain other blocks that use Embedded MATLAB code.
To integrate an Embedded MATLAB function into a Simulink model, you place an Embedded MATLAB Function block in your model. The Embedded MATLAB Function block in Simulink is specifically designed for the purpose of integrating MATLAB code into a Simulink model. The process starts by defining a top-level MATLAB function in the Embedded MATLAB Function block. Input variables of the top-level function automatically become either input ports or parameters of the block, and output variables of the function become output ports on the block, as shown in Figure 1.
From within the Embedded MATLAB Function block you can:
- Call Embedded MATLAB compliant M-code anywhere on the MATLAB path
- Take advantage of the MATLAB debugger to identify and correct problems
- Call your own C functions for maximum performance and reuse
At the start of a simulation, Simulink first compiles code included in the Embedded MATLAB Function block and executes it as part of the Simulink model. The Embedded MATLAB code executes at compiled C-code speed.
(Click to enlarge)
Figure 1. Embedded MATLAB Function block referencing a MATLAB function.Functional and Interface Verification of Embedded MATLAB Code in Simulink
To verify the algorithm function and interface, we first perform a unit test before integrating the algorithm into the entire system-level model. Focusing on the video filtering algorithm, we next show how to use Simulink and Video and Image Processing Blockset software to construct a system-level test bench for quickly testing and refining the video algorithm.In Figure 2, we show an example of such a test bench constructed around several filtering options. The video source brings in the video stream and salt-and-pepper noise is added to the video frames to simulate the effects of a noisy wireless channel. Before we integrate the Embedded MATLAB filtering algorithm, we first try different default video processing filter blocks from Video and Image Processing Blockset to quickly verify the functionality of the test bench. The effectiveness of these default blocks is displayed with a video display. Although in this example, the source is a static file, there are additional library blocks that allow data capture from live devices like video cameras.
(Click to enlarge)
Figure 2. Simulink test bench containing filter being analyzed. Salt-and-pepper noise is added to the source video stream. The outputs from the default low-pass and median filters from Video and Image Processing Blockset are displayed. As can be seen from Figure 2, this approach eliminates the noise but produces a blurry image, and it needs to be replaced with the adaptive median filter algorithm (developed in the first part of this series) that minimizes the blurring artifacts. To integrate the MATLAB algorithm into the Simulink test bench, we replace the filtering block in the test bench with an Embedded MATLAB Function block. We reference the Embedded MATLAB code directly in the block editor, as shown in Figure 3.
Figure 3. Referencing the MATLAB algorithm.With the test bench model in place, we can rapidly debug and refine the MATLAB algorithm. During simulation, a debugger helps detect run-time errors. The M-Lint code analyzer helps to identify and fix syntax errors in Embedded MATLAB functions.
Another useful debugging technique is to declare extrinsic MATLAB functions, so that you can take advantage of the extensive debugging and analysis capabilities of MATLAB. Using the eml.extrinsic declaration leaves the declared code uncompiled; it is instead interpreted by MATLAB. With this approach you can include calls to MATLAB visualization and data analysis functions that may be outside of the Embedded MATLAB subset. You can also use advanced MATLAB debugging capabilities, such as setting conditional breakpoints and read/write variable values and sizes, during simulation. Although the extrinsic functions are not embeddable, using them can provide valuable insight during testing and debugging as you elaborate the algorithm toward the final implementation.
System-Level Integration and Design Refinement
You can use the Embedded MATLAB Function block debugger to step into the adaptive median filter function and investigate the effect of varying algorithm parameters, as shown in Figures 4 and 5. One such parameter controls the maximum of the adaptive window size. A larger maximum improves the effect of the filter, but increases execution time. You can sweep though a range of window sizes, using real-time visual feedback and quantitative metrics to evaluate this tradeoff. With a maximum window size of 5 × 5 pixels, the filter removed a majority of the noise, as shown in Figure 6.
Figure 4. Enabling the debugger.
(Click to enlarge)
Figure 5. Utilizing debugging features to iterate algorithm parameters directly in MATLAB.
Figure 6. Filtered output on salt-and-pepper noise using a test bench.By performing this unit test with this test bench, we are now confident that the filter works well in isolation and is ready to be integrated into the system.
System-Level Integration and Design Refinement
Our next step is to determine if this filter design is feasible in the context of the entire system. A more comprehensive system model with access to system-level metrics lets us verify the performance of our algorithm using a wider range of criteria and evaluate tradeoffs of speed and implementation complexity.
Figure 7 shows the system-level model we use to verify the video algorithm and perform the system-level design. From the figure, you see a top-level diagram of a wireless multimedia communication system modeled using Simulink, Video and Image Processing Blockset, and Communications Blockset. A video source reads a standard AVI file and provides a stream of video data that is encoded by the video encoder. The encoded data is transmitted through a wireless channel, and a receiver recovers the signal. The received data is then decoded by a video decoder and displayed. The overall system is characterized by both the quality of the received video and the error rate measurements of the communications channel.
(Click to enlarge)
Figure 7. Using Embedded MATLAB Function block to reference the tested algorithm. Looking more closely at the video processing source system, we see that it reads an AVI file at 30 frames per second, performs a color space conversion, crops the image, and reduces the frame rate. Video encoding is performed through a discrete cosine transform (DCT) method similar to MPEG.
Introducing Control Logic to Optimize System Performance
Within the communications system, the transmitter subsystem contains a convolutional encoder, a matrix interleaver, and a 16 QAM modulator. The communication channel is modeled as an additive white Gaussian noise (AWGN) channel. The receiver subsystem consists of an equalizer, a 16 QAM demodulator, a matrix deinterleaver, and a Viterbi decoder.Inside the video processing receiver system there is a video decoding subsystem that first decodes the bitstream and then performs an inverse discrete cosine transform to reconstruct the frames of video that are eventually viewed with the video display block.
If you are a video processing engineer, the system-level model provides enough fidelity to the communications system to serve as a useful test bench. The pre-built block libraries available with Simulink allow you to reach beyond your core area of expertise to model other subsystem. This keeps you focused on the details of the video algorithm while allowing you to understand the impact of other subsystems on your work.
Similarly, if you are a communications system engineer, you can quickly construct an abstract video algorithm or reference existing IP while focusing your attention on the overall design of the communications system. For example, you can use blocks from Communications Blockset for well-understood components like modulators, encoders, and channel models, while keeping your customized algorithms in Embedded MATLAB or even C. By using such a system-level framework, engineers of different disciplines can focus on their own design details while utilizing the rest of the system as a test bench.
With the model in Figure 7, we can simulate the effect of varying system parameters such as SNR, encoder block size, and interleaver length on the video algorithm to evaluate the overall system behavior under a range of operating conditions. As shown in Figure 8, both the system-level model and our video algorithm can be modified by making adjustments to the parameters and monitoring metrics like BER and video quality. Iteration in this process allows us to discover the best solution to the video post-processing design problem.
(Click to enlarge)
Figure 8. System-Level integration and design refinement.Simulation results over a range of signal levels shown in Figure 9 indicate that when the SNR drops, the resulting bit errors are exhibited in the received image as large checkered blocks rather then the salt-and-pepper noise assumed when the algorithm was tested in isolation.
Figure 9. Checkered block errors from system-level simulation.To fix this problem, we experimented with different video encoder block sizes and found that the encoder block size is directly related to the sizes of the resulting error blocks. By decreasing the encoder block size from 16 to 8 and increasing the maximum adaptive window of the algorithm to 9 × 9 pixels, improvements in the video quality can be observed.
Introducing Control Logic to Optimize System Performance
This improvement of video quality comes at a cost, however. By increasing the window size of the filter from 5 × 5 to 9 × 9 pixels, the computation load increases drastically, slowing down simulation speed and increasing implementation complexity. Because we have access to system-level parameters, we can see that block errors occur only when the SNR falls below a particular threshold. When the SNR exceeds the threshold, however, the video stream does not exhibit the block errors and the filtering becomes unnecessary because it does not improve image quality.
We can avoid some unnecessary computations by creating a control logic that detects the SNR of each video frame and activates the computationally intensive filter only when the video frames exhibit block errors. With this approach, we can decrease the number of overall operations, which speeds up the simulation while maintaining the same video quality.
The logic described above can be easily modeled in Stateflow software, which is an extension of Simulink that lets users develop state machines and flow charts. Coupled with MATLAB and Simulink, Stateflow can manage the logic and control flow of algorithms and the conditional execution of individual components. This includes setting up the logical framework of the system so that the system operates in distinct modes depending on the nature of the input, the environment, and the situation. Like Simulink, Stateflow can call Embedded MATLAB functions directly, and it can be used to enable the execution of blocks in Simulink.
Figure 10 shows a state machine implemented in Stateflow that turns the filter algorithm on and off based on the SNR calculation of video frames. The filter operates when the SNR falls below a user-defined threshold for three or more consecutive frames. The filter immediately turns off when the SNR of the current frame exceeds the same threshold.
(Click to enlarge)
Figure 10. Control logic implemented in Stateflow to enable and disable the filter algorithm.Conclusion
MATLAB is frequently used to design components of system-level simulations and embedded system implementations. Integration of these algorithms with other system components frequently produces unexpected interactions, which requires refinement of the algorithm to satisfy system-level performance metrics. In this article, we described a workflow for bridging algorithm development and system-level design using Embedded MATLAB code and Simulink models that provides the following benefits:
- Algorithm developers can use the Embedded MATLAB subset to create IP for embedded implementation.
- They can also use the Embedded MATLAB function block to test their algorithms with real-time data and verify that the interfaces conform to the system-level specifications while keeping the debugging and analysis capabilities of MATLAB.
- Design optimizations and iterations can be performed accurately and efficiently through a combination of modeling, simulation, and analysis techniques that take advantage of a combined textual and graphical approach.
- Manual translation of MATLAB into Simulink blocks can be eliminated by referencing available MATLAB IP in Embedded MATLAB Function blocks.
- C and HDL code can be automatically generated from Embedded MATLAB functions for embedded implementation.
Stay tuned for additional articles that discuss other aspects of the Embedded MATLAB workflow.
About the author
Dr. Houman Zarrinkoub joined The MathWorks in 2001 as the senior team leader of the Signal Processing Applications team responsible for the Video and Image Processing Blockset. He is currently the Signal Processing Product Marketing Manager, responsible for signal processing toolboxes. Prior to joining The MathWorks, he spent 6 years at Nortel Networks as a wireless speech processing software engineer. He holds a BSEE from the McGill University in 1994, and MSEE and a PhD from the Institut Nationale de la Recherche Scientifique, Universite du Quebec in Canada.