We're Not in Kansas Anymore

 

In this post we will try to do things you probably never tried to do using VHDL, assuming they were not even possible in such a low level language.

 

VHDL and Verilog/SystemVerilog are considered low level hardware design languages, compared with C/C++ based HLS flows for example. Only schematic capture is lower on the totem pole and hopefully nobody is still doing FPGA designs using schematics anymore. But the low level label attached to HDLs is only partially correct. They are low level only if you use them that way. It is possible to do very high level things in VHDL if you really want.

 

There is a natural tendency for people to try to solve the problem at hand using the most direct path to the solution. If you only had to solve one engineering problem in your entire life that approach would be the correct one, however, one can expect to have to solve hundreds of somewhat similar problems during a career that can span decades. The shortest possible path to a solution is then rarely the best one in the long term. Rather than trying to solve the current particular problem in the simplest possible way, it might make sense to try to write more general code, which solves a class of problems rather than just a single one. Designs are modular and a good design practice is to try to build generic and reusable modules that can be instantiated multiple times in the same design or used in different designs, even by different designers. This post is the first one in a series that will focus on this idea of writing higher level HDL code. Here we start with the foundation, generic reusable data types.

 

We have seen in the previous post that it is possible to create our own user defined arbitrary size fixed point precision data type, even within the constraints of the VHDL-93 language standard. The next step up would be a complex arbitrary precision fixed point type - let's call it CFIXED - and then arbitrary size vectors and matrices of SFIXED and CFIXED numbers. In VHDL you can create more complicated types out of simpler ones using arrays and records. There is no limit to the complexity of composite types, you can have arrays of records of arrays and so on. However, to create generic and reusable code we want unconstrained arrays and this is where VHDL-93 (and any Verilog and SystemVerilog flavor for that matter) has a major limitation - only the top level array in a composite type can be unconstrained, lower level array types cannot be unconstrained and records of unconstrained arrays are also illegal. So if we were to try to define a complex fixed point type CFIXED based on the SFIXED type we introduced earlier we run into a problem:

  type SFIXED is array(INTEGER range <>) of STD_LOGIC;     – arbitrary precision fixed point signed number, like SIGNED but lower bound can be negative
 
type CFIXED is record RE,IM:SFIXED; end record;          – arbitrary precision fixed point complex signed number - not valid in VHDL-93!
 
signal R:SFIXED(1 downto -4);
 
signal I:SFIXED(R'range); – another SFIXED signal with the same range as R
 
signal A,B,C:CFIXED(RE(R'range),IM(I'range));
begin
  R<=TO_SFIXED(0.125,R);    – 00.0010
  I<=TO_SFIXED(-0.375,I);   – 11.1010
  A<=TO_CFIXED(R,I);        – (00.0010,11.1010)
  B<=TO_CFIXED(1.0,-0.5,B); – (01.0000,11.1000)
  C<=RESIZE(A+B,C);         – (01.0010,11.0010)

This CFIXED type definition is not valid in VHDL-93 and we will get an error. The good news is that this limitation has been removed in VHDL-2008, so the code snippet above is perfectly legal now in that context. The bad news is that support for the new standard is still quite spotty - some tools do not support VHDL-2008 at all, others do but only in some limited way. Just to give an example, the VHDL-2008 feature I used above is fully supported by Vivado Synthesis but not by the Vivado Simulator, although it does implement some other VHDL-2008 aspects. This means that while you can synthesize and implement VHDL designs that use the CFIXED type defined above, you cannot simulate them with Vivado, you have to use a different simulator like QuestaSim, which unfortunately is not free. With the hope that this will be remedied sooner rather than later in Vivado let's move on to introducing some even more complicated types:

  type SFIXED_VECTOR is array(INTEGER range <>) of SFIXED; – unconstrained array of SFIXED
 
type CFIXED_VECTOR is array(INTEGER range <>) of CFIXED; – unconstrained array of CFIXED
 
type CFIXED_MATRIX is array(INTEGER range <>) of CFIXED_VECTOR; – unconstrained array of CFIXED_VECTOR

Similarly, SFIXED_VECTOR, CFIXED_VECTOR and CFIXED_MATRIX are not valid VHDL-93 type definitions but they work without any issue in VHDL-2008. CFIXED_MATRIX for example is a very generic type, it can be used to represent matrices of any size MxN, with complex elements with the real and imaginary parts being arbitrary precision fixed point numbers of any range. We can use these types now to do things like these:

  signal SV:SFIXED_VECTOR(0 to 3)(1 downto -4);
 
signal CV:CFIXED_VECTOR(0 to 3)(RE(1 downto -4),IM(1 downto -4));
 
signal CM:CFIXED_MATRIX(0 to 3)(0 to 3)(RE(1 downto -4),IM(1 downto -4));
begin
  SV(1)<=TO_SFIXED(1.25,1,-4);
  CV(
0).RE<=SV(1);
  CM(
2)<=CV;  – CM(2)(0).RE is now 1.25
  CM(0)(0)<=RESIZE(CM(0)(0)+SV(1),CM(0)(0));

Like we did with SFIXED, we can now create our own overloaded operators and functions to convert and resize signals of these new types. We are free to define whatever operators and functions we need. It is possible for example to overload '+' and '*' so that we can add and multiply CFIXED_MATRIX signals directly in a single step. We can define Galois field arithmetic on vectors and matrices where '+' is actually XOR and '*' is AND and express any CRC like algorithm very elegantly and in a compact way in terms of vector and matrix add and multiply operations.

These are just a few examples but the sky is the limit. This is where thinking about what we actually want to achieve a bit will pay off in the long run. We do not want to have to reinvent the wheel every time we need these types and functions in a new design. The right way to do this is to create a user defined package, collect all these type and function definitions in that package and include it in every new module and design that requires them. Such a package is a design in itself, it is worthwhile coding and especially testing it properly. You do not want to have to debug it years from now or discover later on you wish you had done things differently.

 

Leaving Kansas is not as easy as it seems, you cannot do it in one big jump, it is a long journey of many steps and we might encounter some delays and roadblocks on the way. The shortest route is not necessarily the easiest or fastest one in the long run. Complex generic data types are just the first small step in the right direction. In future posts we will revisit this idea of writing generic reusable VHDL code again and again.

 

Back to the top: The Art of FPGA Design