What's Your Type?

 

People new to VHDL usually come from a software background, maybe C/C++ or Java and might have a difficult time grasping the fundamental concepts of HDLs. VHDL in particular can be used as a sequential software language - everything you can do in C you can do in VHDL and while this might make sense when creating testbenches for functional simulation it is not the right approach for actual hardware design.

 

The first fundamental concept you have to grasp as a VHDL beginner is that HDLs are parallel programming languages, where various parts of your program do not execute sequentially like in C or C++ but they run in parallel the way the hardware we are describing does. Every flip-flop and every combinatorial look-up table, which is what FPGA fabric is made out of, executes its own little function in parallel with all the others. An average FPGA these days has hundreds of thousands of FFs and LUTs and speeds in the 500MHz range are normal. Describing a design of such complexity and especially choreographing and making sure all these small pieces work in synchronism is very different from your usual software design approach. This is like writing multi-threaded software with hundreds of thousands of threads! The key fact to understand here is that everything inside a VHDL program is parallel except for the code inside processes, which is sequential, just like in C and C++.

 

The second challenge people coming from a software background have with VHDL is with signals versus variables and their types. In a software environment you have variables of 8, 16, 32 and 64 bits, maybe integer and floating point numbers and you evaluate expressions and assign them to variables sequentially. If you master these basic concepts you can more or less start writing software. In VHDL we have variables too but they are local to processes. Inside a process you can use variables and sequential code to write very "software like" code but in general this is a bad coding habit and unless there is a very good reason, and there are a few, variable use in synthesizable code is frowned upon.

 

What VHDL designers use instead are signals which are actually wires connecting concurrent processes that run in parallel. You can assign the same expressions to variables and signals but there are distinct assignment operators to avoid confusion, := for variables and <= for signals. Variable assignments are immediate while signal assignments are delayed and happen at the end of the process and all processes run concurrently in parallel. This is a complex subject and can be very confusing to a VHDL beginner, especially one coming from a software background, I will devote a later post to discussing this further.

 

Once again, I am assuming here that you already have an understanding of these fundamental basic concepts, you are either a somewhat experienced VHDL programmer or you have at least browsed through the free books mentioned in the previous post, otherwise things will quickly stop making much sense.

 

In VHDL we have much finer control about the types of our variables and signals. There are predefined types and the user can define his own types too. The fundamental types are defined in standard.vhd :


type
BOOLEAN is (FALSE, TRUE);
type BIT is ('0', '1');
type INTEGER is range -2147483648 to 2147483647;
type
REAL is range -1.7014111e+308 to 1.7014111e+308;
subtype
NATURAL is INTEGER range 0 to INTEGER'high;
subtype POSITIVE is INTEGER range 1 to INTEGER'high;
type STRING is array (POSITIVE range <>) of CHARACTER;
type BIT_VECTOR is array (NATURAL range <>) of BIT;

 

BOOLEAN and BIT are actually enumerated types. INTEGER is 32-bit signed integer and REAL is 64-bit double precision floating point. NATURAL and POSITIVE are INTEGER subtypes with a more restricted range while STRING and BIT_VECTOR are arrays of CHARACTER and BIT respectively. The "range <>" construct means that the array type definition is unconstrained and the actual range can be defined later on, when the type is used - this is a VHDL specific feature that does not exist in Verilog or even SystemVerilog and will play an important role later on. BIT is almost never used in hardware design, nobody really knows why.

 

But the types that are most widely used in VHDL designs are defined in the std_logic_1164.vhd package:


type
STD_ULOGIC is ('U',  – Uninitialized
                    'X',  – Forcing  Unknown
                    '0',  – Forcing  0
                    '1',  – Forcing  1
                    'Z',  – High Impedance
                    'W',  – Weak     Unknown
                    'L',  – Weak     0
                    'H',  – Weak     1
                    '-');
– Don't care  
type
STD_ULOGIC_VECTOR is array (NATURAL range <>) of STD_ULOGIC;
subtype STD_LOGIC is resolved STD_ULOGIC;
type
STD_LOGIC_VECTOR is array (NATURAL range <>) of STD_LOGIC;

 

The fundamental hardware type is STD_ULOGIC, a 9-value type. Everything except '0' and '1' has meaning only for simulation and is not synthesizable, except 'Z' in some very particular cases for tristatable buffers and bidirectional input/output ports.

 

To make things even more confusing, we have both STD_ULOGIC and its subtype STD_LOGIC. The difference between them is that a signal can only be driven by one STD_ULOGIC source, like a FF or logic gate output or module input port. Having two drivers on an STD_ULOGIC signal is an error and will be rejected by the tools, which is good for preventing and detecting typos. It is OK to have two or more drivers on a single STD_LOGIC signal and if they do not agree the resulting value will be 'X'. This of course is not synthesizable so for describing normal hardware like the one we would like to place inside an FPGA it would make sense to use STD_ULOGIC. If you drive an STD_LOGIC signal by mistake with multiple drivers the synthesis tool will issue a warning but finish and you will only catch the error when trying to implement the FPGA design. If we would use STD_ULOGIC instead the error will be caught earlier by the synthesis tool. Unfortunately, there is a very long and entrenched tradition in the VHDL community of completely ignoring STD_ULOGIC and using STD_LOGIC all the time, most likely to save typing the extra U every time, so respecting that tradition we will use from now on STD_LOGIC and STD_LOGIC_VECTOR, which is an unconstrained array of STD_LOGIC. Note that STD_LOGIC_VECTOR is array(NATURAL range <>) not array(INTEGER range <>), an unfortunate choice made in the very early days of VHDL and impossible to fix now due to backward compatibility reasons. We will see later on why this is actually a major limitation and find ways to fix it.

 

STD_LOGIC_VECTOR is where we first start running into coding style issues. While you can define STD_LOGIC_VECTORs of any size and range to match all the software types mentioned earlier and much more, you cannot really do arithmetic with STD_LOGIC_VECTOR, which is a string of STD_LOGIC bits but doesn't have a precise numerical meaning. In particular it is not obvious if the integer value encoded by an STD_LOGIC_VECTOR signal is signed or unsigned and there is not way to represent fixed point numbers with an integer and a fractional part because the definition of the type uses NATURAL instead of INTEGER ranges as mentioned earlier and this prevents defining ranges with negative 'low values. Unless you use some specific package you cannot do any kind of arithmetic operations with STD_LOGIC_VECTORs and you definitely cannot mix them with INTEGER.

 

In the very early days of HDL during the last millennium when the current VHDL standard was VHDL-87, Synopsis tried to address this issue introducing the std_logic_arith, std_logic_signed and std_logic_unsigned packages, which overloaded STD_LOGIC_VECTOR with arithmetic operators like +, -, * and the like. These packages were never adopted as IEEE standards, the conversion functions between STD_LOGIC_VECTOR and INTEGER were cumbersome and inconsistent and using both signed and unsigned STD_LOGIC_VECTOR signals in the same module was not possible. With the release of VHDL-93 this problem was addressed with the numeric_std package, which introduces two new types:


type
UNSIGNED is array (NATURAL range <>) of STD_LOGIC;
type SIGNED is array (NATURAL range <>) of STD_LOGIC;

 

and a complete set of overloaded operators and conversion functions that makes doing arithmetic operations with these types much easier. Note that although STD_LOGIC_VECTOR, SIGNED and UNSIGNED are essentially the same thing, arrays of STD_LOGIC, the three types are incompatible with each other, which is a good thing because it prevents design mistakes that are otherwise very difficult to catch, just ask any Verilog designer! You can still do assignments between these three types but you have to use explicit typecast conversions.

 

You can still see after all this time VHDL designers using the three old Synopsis packages even today, bad habits die hard. Just say no, use STD_LOGIC_VECTOR for signals without an explicit numerical meaning and use SIGNED and UNSIGNED for doing integer arithmetic. In the same spirit it would also make sense to use BOOLEAN instead of STD_LOGIC for single bit ports that have a boolean meaning like a clock or write enable, a data valid indicator or an error indicator, it makes the code cleaner and easier to read and understand.

 

This is more or less all you need to know about VHDL predefined signal and variable types to be able to start writing code and many designers, once they reach this level of expertise just stop here, with their designs limited to STD_LOGIC, STD_LOGIC_VECTOR and maybe a bit of SIGNED and UNSIGNED. But this is just scratching the surface and there is much more to VHDL types than that. In the next post we will look at explicit type conversions, user defined types, overloading and user defined operators and the use of unconstrained types to write generic and reusable code.

 

Back to the top: The Art of FPGA Design