Reuse has been an industry buzzword for years now. It is hardly a new idea, and probably goes back as far as the time when man first realized he could use the same fire both for keeping warm and for roasting his sabre‐tooth tiger ribs. When it comes to IP, reuse can be an extremely powerful way of saving resources and shortening project timescales.
At RF Engines, we find that reusing existing IP is very desirable. Not only does it save us development time and help us fulfill challenging delivery requirements, but making use of pre‐proven components also helps give customers confidence in our designs.
Another dimension to this is that multi‐FPGA designs are becoming increasingly widespread, and there are obvious advantages to having a chip‐level infrastructure that is reusable within the project.
Reuse, then, is clearly A Good Thing. However, in practice it has often proven surprisingly difficult to achieve. So it is worth bearing in mind a few principles and techniques that can be applied to make it more straightforward. Though it would be foolish to say that creating reusable VHDL doesn’t require any extra effort, it frequently pays significant dividends in the longer term.
At the beginning of implementation, thinking about what else your component might be useful for in the future may not be the first thing on your mind. Sometimes it’s not obvious that your component has multiple applications, and that reuse should be one of your design goals. But even when you don’t know what the component might be useful for in the future, if you take a step back, it’s often easy to see a few aspects of the design which can be written in a flexible way without too much of an overhead.
One issue that often arises in design for reuse is that many of the language features which make design for reuse easier are software‐style constructs. Hardware engineers may be wary of these constructs as they can obscure the relationship between the VHDL and the resulting implementation, but if used carefully they can be a great help. Here are a few examples.
Using a generic to set parameters such as the width of entity ports is common practice. It not only makes the component easier to reuse in other circumstances, but helps to document the code, since a meaningful generic name can be used instead of an apparently random number.
Any code which depends on the width of such ports or signals then also needs to be parameterized on the generic, e.g. to produce a reduction‐and of data_in above:
Generics are also useful to make different variants of a component. You can add a Boolean generic and use an if generate statement to implement optional code, rather than writing a different variant of an entity.
Another useful trick is to gather collections of signals together using arrays. Signal processing applications often operate on blocks of data at a time, and the operations often have a strong regularly structure. Using arrays in this context can help extensibility and readability. For example, say you want to instance several copies of an entity to perform a signal processing operation, each working with one of a number of parallel data streams. You could write this as:
The width of the data vectors, or the number of vectors in a collection, can be changed simply by altering the relevant constant. Notice also the use of the ‘range attribute, which yields the range of an array type, variable or signal. You could alternatively just use the explicit range 1 to collection_size instead of collection_type’range, but it’s a little more restrictive as it assumes that collection_type’s range starts at 1. Commonly in a signal processing application, once you have operated on each element of your data block, you will want to perform another operation on the results. For example, you could sum the results of from all the operations above using a process such as:
This is again very flexible as it doesn’t rely on knowing the data width or number of results. The ‘range attribute has been used again, this time to help us create an unsigned vector of the same dimensions as data_vec (which is a std_logic_vector). Of course, the code above could produce a very large adder chain and so in practice a more sophisticated pipelined architecture would almost certainly be necessary.
There are a couple of pitfalls to watch out for in this example. One is that VHDL requires the element type (data_vec) of the array (collection_type) to be globally static. That unfortunately means you can’t use a generic to parameterize both the width of data_vec and the size of collection_type (hence the use of constants instead). In fact, collection_size could be a generic in the example, but data_width could not. There are various ways to get around this but none are perfect. One is to use a two-dimensional array, which many synthesis tools now support. In this case, if we wrote:
then both collection_size and data_width could be generics. This can make it a little messier to access the data “rows”, so it is a trade off of flexibility against simplicity.
One other issue in this area is that, if data_collection_in/out had been entity ports, many synthesis tools would have broken them up into separate vectors, which could cause incompatibilities when doing gate level simulation. If you are unwilling to take this hit, you could create a wrapper which assigns the array elements to individual ports at the top‐level.
A final tip for making design reuse easier is to put a component declaration for your block in a package. Usually, you would have to place a component declaration in every architecture where your block is to be used, but if you create a package which contains a component declaration, then you save yourself and subsequent users the hassle:
Users of the block then simply need to reference the package, instead of re-declaring the component:
Is it worth it?
So is design – or redesign – for reuse really worth the effort? It may seem like a lot of work at the outset, but it can bring significant benefits in the long term. Or, looking at it the other way round, if you don’t have reusable components, you could end up wishing you had. Remember, a stitch in time saves nine. A bird in the hand is worth two in the bush. Many hands make light work but too many cooks spoil the broth... well, you get the idea.