Due to long development cycles (and short schedules!), firmware designers often struggle to generate modular intellecutal property (IP) that is reusable between applications. Modularity relies heavily upon parameterization which can be a difficult challenge in firmware design. Instead of a single IDE with an integrated compiler, parameters are scattered across HDL design files, modeling tools (MATLAB/Octave/Python), vendor tools (Vivado/Quartus/Libero), and simulation tools (QuestaSim/Cadence). The modification of these parameters must be synchronized when reusing IP.
For example: Let’s say that you have developed a FIR filter firmware IP that uses a Xilinx Complex Multiplier IP core. You are building/packaging your FIR filter with Xilinx Vivado, modeling the mathematical behavior with Octave, and simulating with Mentor Graphics QuestaSim. Now let’s say that you want to change the bit widths of your input! Using a manual design flow to update the bit widths, you will need to modify your HDL, regenerate the Xilinx Complex Multiplier IP core, update the Octave model, and update the QuestaSim simulation inputs. These types of small updates are very common and result in multiple inefficient design iterations. (Keep reading if you are feeling this pain!)
The heterogeneous toolset of firmware design can also make automation of IP building, simulation, and packaging a challenge. The result is often a manual flow for regenerating IP – the Achilles heel of reuse. The final challenge I will mention here is that no unified standard exists for cataloguing and source control of IP and HDL libraries. Too often reuse of a firmware module or IP involves a literal copy of the files!
OpenCPI is a great example of an open-source modular firmware design pattern, and it implements a common structure for embedded software designs as well. However, the complexity of this solution makes it prohibitive for tight schedules and budgets due to the large learning curves and infrastructure overhead. Large organizations can support the staffing required for a solution with this sophistication, but the rest of us just don’t want to spend the time and money!
In order to mitigate the challenges I mentioned above, apply the following best practices to your firmware IP design flow in order to have a design pattern for modular firmware IP generation. Implement each concept from the beginning of the design cycle for maximum effectiveness. The next section walks you through my approach to applying these best practices to my own design pattern using open source tools with Xilinx Vivado.
Minimize Source Control
Only keep the bare minimum files needed to reproduce the firmware IP in your source control managment system. Temporary or generated files should not be stored, and make an effort to script the generation of files (i.e. parameter files, vendor IP) instead of storing the generated products. Utilize libraries as much as possible to share common scripts and HDL modules between IP. Source control is critical to reuse and scaleability of modular IP. One additional note: retrieve libraries used by your module by a specific tag or commit to ensure the module can always be recreated regardless of future development to the libraries.
Create a Singleton IP Definition
Create a single file for the definitition of your IP module’s parameters. Keeping a singleton definition of all parameters means that customization of your IP happens in a single place – enabling reuse! Generate parameter files (HDL packages/headers, parameter scripts) from the singleton IP definition to use these parameters in your HDL, scripts, and simulation. The generation of parameter files reinforces the practice of minimizing source control. This is not a common practice in the community, but it is a powerful paradigm that accelerates development.
Design for Reuse
Design your HDL with standardized interfaces and generic parameters, and limit the use of custom packages. Write every small HDL module with the intention of putting it into a library to minimize source control. Design HDL for your IP to use the parameters generated by your singleton IP definition. Standard interfaces are critical for portability and reuse, but also they allow vendor tools (like Xilinx) to infer port types (like AXI-Stream). Modern toolsuites have been moving to an IP-driven hierarchy, so package your IP with the vendor tools when possible.
Implement a scripting environment that automates every phase of the design cycle. This small investment up front with automation will pay off through multiple design, simulation, and test iterations. Automate the generation of parameter files from the singleton IP definition to propagate customization quickly. Retreival of dependent repositories from source control should be automated, allowing versioning to be controlled with scripts. Finally, automate the building, packaging, and simulation of your IP to make reuse and scaleability easy.
I used GNU Make for automation, Python+Jinja2+JSON to maintain a singleton IP definition, Git for source control, GNU Octave for modeling and verification, and Vivado for IP packaging and simulation. The diagram below shows the overall flow of the design pattern:
The first step in the process is cloning all repositories required by the IP using Git and the makefile
repos target. These repositories include libraries for HDL, verification, scripting, and other IP. Notice the red boxes in the diagram – all these files are reused elements!
After the library repositories are available to the IP, the parameter files are generated from a JSON singleton IP definition file using Python and Jinja2 templates. This setup allows me to make a modification to a single element in the JSON file, run
make params, and every aspect of my IP has been updated! The blue boxes in the diagram represent the generated and temporary files – files that don’t need to be stored in Git because they are automatically regenerated. Notice too in the diagram that the
ip_make makefile is a generated parameter file. Generating the IP makefile itself allow even target dependencies to be setup based upon the singleton IP definition.
The next step of this design pattern is the building and packaging of the IP using the
all makefile target. Within the “Vivado” group, notice how both the
ip_simulate.tcl scripts are generic scripts from a library repository. This reuse of library scripts is only possible because those scripts are sourcing the
ip_params.tcl script which sets up the necessary properties to generate and simulate the Vivado project.
The final step of the design pattern is the simulation of the IP using the
sim makefile target. The parameter file generated here is the
sim_params.m Octave script which sets up a
params structure to pass the IP definition to Octave. Modeling tools like Octave are commonly used to verify firmware designs, and the “MATLAB/Octave” group in the diagram is my implementation of this simulation flow (a future blog post is planned to describe the simulation flow in more detail, so stay tuned!).
Back to our example: Now let’s say you need to update your the bit widths of your FIR filter IP and you have implemented the modular firmware IP best practices using my design pattern. Instead of updating your HDL, regenerating Xilinx IP, updating simulation files, and changing your model, you only have to update one parameter in the JSON singleton IP definition file. Executing
make paramsthen regenerates the HDL package file, the TCL script to regenerate the IP, the octave parameters script, and the simulation files!
Enjoy applying these best practices for modular firmware IP development, and let us know how we can help you bring advanced firmware capabilities to your customers!