In this post we look at subprograms, which allow us to create reusable code, and packages. We use the term subprogram to refer to functions and procedures in VHDL.
Whilst functions should be familiar to anyone with experience in other programming languages, procedures are less common in other languages.
The difference between these is that a VHDL function calculates and returns a value.
In contrast, a VHDL procedure executes a number of sequential statement but don't return a value.
Packages provide us with a convenient way of grouping subprograms so that they can be used in other VHDL designs.
We will discuss all three of these concepts in more detail in the rest of this post.
We use functions to implement small portions of code which we can reuse throughout our designs.
In VHDL, a function is a subprogram which takes zero or more input values and returns a calculated output value.
We can not use any construct which consumes time in a function.
The code snippet below shows the general syntax for a function in VHDL.
function <name> (<arguments>) return <return_type> is -- variable declaration begin -- Function end function <name>
We must give every function a name, as denoted by the <name> field above. This name needs to respect the naming conventions which we discussed in a previous post.
We must include all of the input arguments in the <arguments> field, with each argument being separated by a comma.
A function can only ever return one value, with the <return_type> field declaring which data type the returned value has.
We can also declare variables to use in the function. These are often used to store intermediate values, or to make the code simpler to read.
As functions can not consume time, we can not use wait statements or after statements inside of them.
To better demonstrate how to use a VHDL function, let's consider a basic example.
For this example, we will write a function which takes 2 integers as arguments and returns the sum of them.
The code snippet below shows the implementation of this example function in VHDL.
function addition (a : integer, b : integer) return integer is variable sum_result : integer; begin sum_result := a + b; return sum_result; end function addition;
The first thing to note here is that the integer type is unbounded in this example.
As a result, the synthesiser will be unable to determine how many bits are needed to store the value. Therefore, this function would not necessarily be suitable for synthesizable code.
However, we could easily modify our code so that it no longer has this issue.
The second thing to note is that the code uses a variable to determine the return value.
We don't need to do this but it is useful as an example of the methodology.
If we wanted to make the code more concise, we could directly return the result of the addition operation.
The code snippet below shows how we would do this.
return a + b;
When we want to use a function in another part of our VHDL code, we have to call it.
The method we use to do this is similar to other programming languages.
However, there are two different ways we can pass arguments to our function when we call it.
The first method uses positional association. This means we pass parameters to the function in the same order as we declared them in the function declaration.
The code snippet below shows how we would use positional association to call the addition example function. In this example, in_a would map to the a parameter and in_b would map to b.
-- Using positional association to call a function result <= addition(in_a, in_b);
The second method we can use to call a function is known as named association.
When using this method, we must explicitly state which arguments maps to which parameter.
The code snippet below shows how we would use named association to call the addition example function. Again, in_a would map to the a parameter and in_b would map to b in this example.
It is also possible to use a mixture of both approaches when calling a function, although this is not advisable.
-- With named association result <=addition(a => in_a, b => in_b);
The addition function which we used in our previous example is known as a pure function. This means the value it returns dependents only on its arguments.
Prior to the VHDL-93 standard, we also had to include the pure keyword in this type of function declaration. However, this isn't required in all subsequent versions of VHDL.
In contrast, impure functions return a value which isn't solely dependent on its arguments. As a result, we could call the function with the same arguments twice and get different return values.
The syntax for the impure function is almost identical to a normal function in VHDL.
The only difference is the impure keyword at the beginning of the function declaration, as shown in the VHDL code snippet below.
impure function <name> (<arguments>) return <return_type> is -- variable declaration begin -- Function end function <name>;
At first, it may be difficult to see the use for impure functions.
However, one common use is when we require a function which generates random values to use in test benches.
In order to do this, we typically use an algorithm which requires a seed value. This initialises the underlying pseudo random algorithm, allowing it to create a sequence of apparently random numbers.
The sequence of numbers changes if we give the algorithm a different seed. Therefore, we can set the value of the seed value using either a constant or a different function to generate different test patterns. We can then use an impure function to generate the next number in the sequence for the given seed.
The code snippet below shows the VHDL code for an impure function which generates a random number. The seed1 and seed2 values would be assigned externally to the function in this example.
The uniform function is an inbuilt VHDL command which returns a random value between 0 and 1. This function is part of the math_real package in the ieee library.
We call impure functions in the same way as we would call pure functions.
impure function random(max : real) return real is variable result : real; begin uniform(seed1, seed2, result); return (result * max); end function;
Just like functions, we use procedures to implement small sections of code which we can reuse throughout our code.
In VHDL, a procedure can have any number of inputs and can generate multiple outputs.
Unlike functions, we can also use constructs which consume time in a procedure.
Although functions and procedures perform similar function, there are some important differences between the two which effect when we use them.
The first difference is that procedures don't return a value in the same way as a function. However, we can use them to assign values to one or more output.
The second major difference is that we can use wait statements and after statements within them. However, the process which calls the procedure can't have a sensitivity list if we wish to do this.
These two features mean procedures are best used to implement simple tasks which are repeated several times in our code. A good example of this would be driving the pins on a known interface, such as I2C or SPI.
When declaring a procedure in VHDL, we need to include a list of inputs, outputs and bidirectional parameters associated with the procedure. We can include as many of each of these types as we need within the procedure.
In addition, we can declare these as signals, variables or constants. If this isn't explicitly defined, all inputs are treated as constants whilst all output and inout types are treated as variables.
The code snippet below shows the general syntax we use to declare a procedure in VHDL.
procedure <name> (<io_list>) is -- variable declarations begin -- Procedure code end procedure <name>;
Let's consider a basic example to better demonstrate how to write a VHDL procedure. We will use a basic pulse generation function, which inverts a signal for a given amount of time.
To do this we require one input, which determines how long the pulse is, and an inout type to generate the pulse on.
The VHDL code below shows the implementation of this example procedure.
procedure pulse_generate( signal pulse : inout std_logic, signal pulse_length : in time ) is begin pulse <= not pulse; wait for pulse_length; pulse <= not pulse; end procedure pulse_generate;
This example shows how the parameter list differs between a function and procedure in VHDL.
Whilst we only declare the data type in a function, we also declare the direction and the fact that we expect signals in this procedure.
We can also see that a wait statement in used in this procedure. We use this to hold the pulse at the desired level.
One final thing to say about this example is that we could use an output instead of the inout type. However, using the inout type means that we can create negative pulses. To do this we would drive the pulse signal to 1b before calling the procedure.
As with functions, we must call a procedure when we want to use it in another part of our VHDL code. The method we use to do this is similar to the method used to call a function.
However, we also need to ensure the arguments match the object type and direction of the procedure. This means that the types must match, as in a function, as well as matching signals, variables and constants. If we are passing arguments which are part of the entity declaration, we must also match the direction (in/out/inout).
Other than this subtlety, we use either named association or positional association. We must also take care to ensure procedures which contain a wait statement are only called within a suitable process block.
The code snippet below shows how we would map to the previously defined pulse_generate procedure.
-- Positional association pulse_generate(pulse_sig, pulse_time); -- With named association pulse_generate( pulse => pulse_sig, pulse_length => in_pulse_time );
A VHDL package provides a convenient way of keeping a number of related functions, procedures, type definitions, components and constants grouped together.
This allows us to reuse any of the elements in the package in different VHDL designs. In this sense, packages can be thought of as being similar to headers in programming languages like C.
In our previous post on libraries in VHDL, we have already seen examples of packages being used in our code.
Packages generally consist of two distinct parts - the package declaration and the package body. However, we don't always need to write a package body.
The code snippet below shows the method for declaring a package in VHDL.
package <package_name> is -- This part is used to declare the contents of the package end package <package_name>; package body <package_name> is -- The code defining the behaviour of functions and procedures -- is contained in the package body end package body <package_body>;
In the initial declaration, we declare the prototypes for the functions and procedures which we wish to include in our VHDL package.
In these prototypes we simply indicate the types and number of inputs and outputs our subprograms require.
We write the code which implements the subprograms in the package body using the syntax we have already discussed.
It is only subprograms which require this separation between declaration and implementation. As a result, package bodies are not required if no subprograms are included in the package.
The code snippet below shows the syntax we use to declare procedure and function prototypes in VHDL.
-- Function prototype declaration in a package function <name> (<arguments>) return <return_type>; -- Procedure prototype declaration in a package procedure <name> (<io_list>);
As we saw in the post on VHDL libraries, we compile packages into libraries. To include the package in a VHDL design unit, we must include the relevant library and package.
The code snippet below shows the method we use to include a library and packe in our VHDL design.
It is also possible to replace the all keyword with the name of the package elements which we need in the design. However, we rarely see this method used in practise as it often requires more lines of code.
library <library_name>; use <library_name>.<package_name>.all;
Let' consider a basic example to better demonstrate how we create a package in VHDL. We will use this example package as a container for the addition function and the pulse generator procedure we looked at previously.
As this package contains subprograms, we must create a separate package declaration and package body.
In the package declaration, we simply declare the prototype for both of the subprograms in the example.
The code snippet below shows how we do this.
package example_package is -- In the package declaration we only declare prototypes function addition (a : integer, b : integer) return integer; procedure pulse_generate( signal pulse : inout std_logic, signal pulse_length : in time); end package example_package;
We then have to create a separate package body which implements our subprograms. The code snippet below shows how we do this.
package body example_package is -- We write the code which implements the functions -- and procedures in the package body function addition (a : integer, b : integer) return integer is variable sum_result : integer; begin sum_result := a + b; return sum_result; end function addition; procedure pulse_generate( signal pulse : inout std_logic, signal pulse_length : in time ) is begin pulse <= not pulse; wait for pulse_length; pulse <= not pulse; end procedure pulse_generate; end package body example_package;
Write a VHDL function which multiplies 2 values together and returns the result.show answer
function multiply (a : integer, b : integer) return integer is begin return a * b; end function multiply;
What is the difference between a pure function and an impure functionshow answer
A pure function returns a value which depends only on the value of it’s argument but an impure function doesn’t.hide answer
What are the two main differences between functions and procedures in VHDL?show answer
Functions always return a value whereas procedures don’t. Functions can not consume time whereas procedures can.hide answer
Write a procedure which adds 2 integers together after 10 ns.show answer
procedure delayed_addition( signal a : in integer, signal b : in integer, signal c : out integer ) is begin wait for 10 ns; c <= a + b; end procedure delayed_addition;
When do we need to include a separate package body in the code for package?show answer
If the code contains a sub program it must be implemented in a package body.hide answer
very well structured and presented content!