Using Protected Types and Shared Variables in VHDL

In this post we look at shared variables and protected types in VHDL. These techniques allow us to incorporate aspects of object orientation into our code which helps us to write code which is more maintainable. At the end of the post there is a full example to show how we use the protected type in VHDL.

Many modern programming languages make use of object orientation. This is a programming approach which involves writing code to define a data structure and all operations which can be performed on it.

We use a single unit in our program to encapsulate the data structure and their associated operations. We normally refer to these closed units as being objects.

In languages such as C++ or Java, we use classes to define objects. These are a collection of different variables which determine the state of our object and a number of methods (or functions) which allow us to modify the state.

We then write more complex programs by instantiating a number of these objects and defining how they interact with each other.

Object oriented code has a number of advantages over other approaches, such as improved code reusability and maintainability, which can improve our productivity.

VHDL was not designed to be a primarily an object oriented language. However, the VHDL-2002 standard introduced a protected type which allows us to create object oriented style code.

In this post, we will discuss protected types in more detail. We will also look at the closely related concept of shared variables.

This post doesn't provide an extensive discussion of the concepts associated with object oriented programming. This is because it is not necessary to have a full understanding of these concepts in order to use the techniques we discuss here.

However, it is beneficial to have a full understanding of object oriented concepts and techniques in the long term. You can find a simple explanation of the terms and methods associated with object oriented coding techniques here or here.

VHDL Shared Variables

In a previous post we saw how we can declare a variable within a process block. The scope of any variables which we declare this way is limited to the process where they are declared.

Another type of variable was introduced as part of the VHDL-93 standard - shared variables. The VHDL-2008 standard further refined shared variables to make their usage safer.

Shared variables are exactly the same as normal variables in VHDL except that they can be used in more than one process. This means their value is always updated immediately after assignment.

The shared variable is particularly useful in modern testbenches, where we often create high level data structures which define test stimulus for the FPGA.

We then need to pass this test data to other processes in our test bench which can generate the inputs or check the outputs. We often use a shared variable for this purpose when we write testbenches in VHDL.

Unlike normal variables, we can declare shared variables in the architecture, as we would with a signal. This allows more than one process in our code to have access to them.

The code snippet below shows the syntax we use to declare shared variables. After we have declared our variables, we assign and read them in the exact same way as a normal variable.

shared variable <variable_name> : <type>;

Mutual Access

Although shared variables provide a useful function in VHDL, they also come with a downside.

As our processes can access the shared variable simultaneously, we may have instances where multiple processes attempt to modify data at the same time.

This leads to unpredictable behaviour in our code as we can't be certain our data will be modified as expected. As a result, the behaviour of our code is no longer deterministic.

To demonstrate how this can cause a problem, let's consider a basic example where a shared variable acts as a counter in our VHDL design.

Suppose that we have two processes which both try to increment the value of this shared variable at the same time.

This operation typically involves our CPU reading the variable from memory, updating the value and then writing the new data back to the original memory location.

If one process completes this process before the other has a chance to read the variable, then the code will work as expected.

However, a problem occurs when both processes read the variable before either writes the updated value into memory.

In this case, both processes will increment the original value by one and then write it back to the memory location.

Clearly we do not want this behavior as the variable is incremented by one rather than two.

VHDL Protected Type

In VHDL, the protected type is the language feature which most closely resembles the concept of objects. We use protected types in VHDL to implement the encapsulation of variables as well as the procedures and functions associated with them. In addition to this, they also ensure exclusive access to their data members.

The protected type was introduced in VHDL-2002 to overcome the problems which can occur with shared variables. The VHDL-2008 standard made it mandatory for all shared variables to be a protected type.

As protected types exhibit several properties of object orientation, they are useful for designing FPGA test benches. Making frequent use of them can help to improve the maintainability and reusability of our code.

In fact, the source code for the OSVVM library makes extensive use of protected types to implement its underlying functionality. This library is one of the most popular simulation tools in VHDL.

We use protected type to define a collection of variables and VHDL subprograms which we want to group together in order to model a given object in our VHDL code.

As with packages, we split the code for a protected type into two different parts.

We use the first part to declare the protected type and a separate protected body to implement our type.

Anything which we write in the declaration is the public interface to the protected type.

In contrast to this, any variables, functions or procedures we declare in the protected body are private. This means we can't access these variables and methods outside of the protected body unless.

We also implement any methods which we declared in the protected type declaration in the protected body. These methods are visible outside of the protected body.

We typically define protected types in packages, meaning we write the protected body inside the package body.

However, it is also possible to write protected types inside of architectures. In this case, we write the the protected body immediately after the declaration.

Writing a Protected Type in VHDL

When declaring a protected type in VHDL, we only need to declare the procedures and functions which we want to use externally. This is similar to the way we declare public object members in other programming languages.

The code snippet below shows the way in which we declare protected types.

type <type_name> is protected
  -- Definition of functions and procedures
end protected <type_name>;

We use the protected body to implement our procedures and functions using the same approach we have seen before in package bodies.We also use this to declare any variables we use in our protected type. We do this using the exact same method we have seen before.

The code snippet below shows the general syntax for a protected body.

type <type_name> is protected body
  -- Variable declarations

  -- Function and procedure implementation
end protected <type_name>;

Once we have written code to create our protected type, we can then create instances of it elsewhere in our VHDL code. To do this, we must create either a variable or shared variable which acts as an instance of the object.

The code snippet below shows the way we declare object instances. This code snippet also shows the syntax we use to call a method which is associated with our object.

-- Create an object instance as a variable
variable <variable_name> : <type_name>;

-- Create a object instance as a shared variable
shared variable <variable_name> : <type_name>;

-- Call one of the objects methods
<variable_name>.<method>(<arguments>);

Accessing Variables

Protected types use exclusive access to ensure that only one process can modify the underlying data at a time.

As a result of this, we can't directly access these variables from outside of the protected type. Instead, we must access or modify the variables in inside a protected type using a subprogram.

This means that we often need to include getters and setters in our code.

We use getters and setters to either get the value of some variable (getters) or set the value of a variable (setters).

These types of method are common in languages such as Java or C++ which make extensive use of object orientation.

In VHDL, we always use procedures to write setters whilst functions are normally used for getters.

The code snippet below shows an example of a setter.

procedure set_example_variable(set_value : integer) is
begin
  example_variable := set_value;
end procedure set_example_variable;

This is actually as complicated as a setter function will ever get. Here we would have already declared the "example_variable" as a shared variable within the VHDL protected body.

The code snippet below shows the code which would implement the getter method for the same variable. Again, this is as complicated as getter function will ever get.

impure function get_example_variable return integer is   begin
  return example_variable;
end function get_example_variable;

All subprograms defined in the protected type body have full access to internal variables. This means that there is no need to specify them as formal input parameters.

However, we do have to declare all functions in the protected type as impure if they access a shared variable. This is shown in the code snippet above which implements our example getter function

VHDL Protected Type Example

Let's consider a basic example in order to demonstrate how we can create object oriented code.

For this example, we will create an object which implements a basic synchronous counter.

As this is a trivial example, we could just as easily implement it in a process. However, it provides a way for us to look at the basic syntax we require to create a protected type in VHDL.

We will also see how we can use the object in our code after we have created it.

Writing Our VHDL Protected Type

When creating an object, the first thing we have to do is declare all the subprograms which are part of it. We will do this in a package which we will later use to declare our variables and implement our methods.

Our protected type will use two procedures, one which resets the counter and one which increments it by a given value. In addition, we will also declare one function which simply acts as a getter.

The VHDL code below shows how we declare the protected type with the required methods.

type counter_t is protected
  procedure reset;
  procedure increment (value : integer);
  impure function get_value return integer;
end protected counter_t;

Now that we have declared our protected type, we need to write the code which implements it.

We code the implementation of our protected type using a protected body. We must write the code for the protected body in the body of our package.

Our example also has one variable which we use to store the current value of our counter. We also need to declare this in our protected body.

The code snippet below shows the implementation of our protected body.

type counter_t is protected body

  -- Variable to store the counter value
  variable counter_val  : integer := 0;
 
  -- Implementation of our reset procedure
  -- This simply resets the counter to 0
  procedure reset is
  begin
    counter_val := 0;
  end procedure reset;

  -- Implementation of the increment function
  procedure increment (value : integer) is
  begin
    counter_val := counter_val + value;
  end procedure increment;

  --Implementation of the getter function
  impure function get_value return integer is
  begin
    return counter_val;
  end function get_value;
    
end protected body counter_t;

Using our Protected Type

After writing our protected type, we can create an instance of it to use in our code.

If we are going to use the object in more than one process, we need to create this instance outside of a process and declare it as a shared variable.

When we only use the object in one process, then we can simply create it inside of our process.

In either case, the process for declaring it is exactly the same as for other signals or variables.

For our example, we only use our object in one process. This means that we can simply declare it as if it were a normal variable.

Once we have declared our object, we can then access all of the functions and procedures associated with it.

For our example, the counter generates a short pulse whenever it is triggered by a clock.

We control the length of this pulse through a constant which we declare outside of the process. We then create a loop inside our process which increments the counter object every 1 ns. The loop stops when our counter value is equal the value of our constant.

The code snippet below shows the process which performs these tasks.

pulse_process:
process is
  variable count_object : counter_t;
begin
  -- Wait for a trigger event
  wait until rising_edge(trigger);

  -- Reset the counter before using it
  count_object.reset;
    
  -- Start generating a pulse
  -- The length of the pulse is determined by
  -- a constant outside the process
  pulse <= '1';
    
  while count_object.get_value < pulse_width loop
    count_object.increment(1);
    wait for 1 ns;
  end loop;
    
  pulse <= '0';

end process pulse_process;

This is a trivial example which will simply generate a single pulse of a given width every time it is triggered. However, this does show us the general methodology required for creating and using a protected type.

Complete Code

We have to split the code for this example over two files - one for the package and one for our functional code. The contents of both of these files are listed in full below.

However, you can also find these source files on eda playground where it is possible to simulate them.

The source code below shows the declaration of our protected type within a package.

package counter_example_pkg is
  type counter_t is protected
    procedure reset;
    procedure increment (value : integer);
    impure function get_value return integer;
  end protected counter_t;
end package counter_example_pkg;

The source code below shows the full package body for our protected type example.

Note that we would include this code in the same file as our package declaration above. We have split them up in this case as it makes them easier to read and understand.

package body counter_example_pkg is

  type counter_t is protected body
    -- Variable to store the counter value
    variable counter_val  : integer := 0;
 
    -- Implementation of our reset procedure
    -- This simply resets the counter to 0
    procedure reset is
    begin
      counter_val := 0;
    end procedure reset;

    -- Implementation of the increment function
    procedure increment (value : integer) is
    begin
      counter_val := counter_val + value;
    end procedure increment;

    --Implementation of the getter function
    impure function get_value return integer is
    begin
      return counter_val;
    end function get_value;
  end protected body counter_t;

end package body counter_example_pkg;

Finally, we would have a file which implements the actual functional code. This is the code which we would use to create instances of our object and is also the file we'd use for simulations. The source code below shows this functional code.

library ieee;
  use ieee.std_logic_1164.all;

library work;
  use work.counter_example_pkg.all;

entity counter_example is
end entity counter_example;
 
architecture behav of counter_example is

  -- Constant to control the pulse length
  constant pulse_width : integer := 100;

  -- Trigger and pulse signals
  signal trigger : std_logic := '0';
  signal pulse   : std_logic := '0';

begin

  -- Create a pulse train on the trigger signal
  trigger <= not trigger after 500 ns;

  pulse_process:
  process is
	variable count_object : counter_t;
  begin
    -- Wait for a trigger event
    wait until rising_edge(trigger);

    -- Reset the counter before using it
    count_object.reset;
    
    -- Start generating a pulse
    -- The length of the pulse is determined by
    -- a constant outside the process
    pulse <= '1';
    while count_object.get_value < pulse_width loop
      count_object.increment(1);
      wait for 1 ns;
    end loop;
    pulse <= '0';
  end process pulse_process;

end architecture behav;

Exercises

What is the main difference between a variable and a shared variable?

Shared variables can be used in multiple process blocks whereas a variable can only be used in one process.

Write the code which declares an 8 bit std_logic_vector as a shared variable.

shared variable example : std_logic_vector(7 downto 0);

What are the two separate parts we use to write a protected type? What do we use them for when writing a protected type?

We use the protected type declaration to declare all the subprograms which form the external interface to our protected type. The protected body is used to implement the subprograms and to declare variables which can't be externally accessed.

Why do we use getters and setters in a protected type?

We use getters and setters to access variables in our protected type. We have to do this as our variables are not directly accessible in order to ensure mutual access to them.

Write the code for a basic protected type which contains a single integer variable, a getter and a setter method.

-- Declare the interface ot the protected type
type example_t is protected
  procedure set_value (value : integer);
  impure function get_value return integer;
end protected example_t;
 
-- Implement the getters and setter code in the protected body
protected body example_t is
  -- Declare the integer type variable in the body
  variable example_int : integer;
 
  -- Implementation of the setter type
  procedure set_value(value : integer) is
  begin
    example_int := value;
  end procedure set_value;
 
  -- Implementation of the getter type
  impure function get_value return integer is
  begin
    return counter_val;
  end function get_value;
 
end protected body example_t;

Writing Reusable VHDL Code using Generics and Generate Statements

In this post we look at the use of VHDL generics and generate statements to create reusable VHDL code. This includes a discussion of both the iterative generate and conditional generate statements.

As with most programming languages, we should try to make as much of our code as possible reusable. This allows us to reduce development time for future projects as we can more easily port code from one design to another.

In VHDL, we can make use of generics and generate statements to create code which is more generic. When we use these constructs, we can easily modify the behavior of a component when we instantiate it.

Looks look at both of these constructs in more detail.

VHDL Generic

In VHDL, generics are a local form of constant which can be assigned a value when we instantiate a component. As generics have a limited scope, we can call the same VHDL component multiple times and assign different values to the generic.

We can use generics to configure the behaviour of a component on the fly.

As we saw in the post on VHDL entities and architectures, we use an entity to define the inputs and outputs of any component we write. This gives us an interface which we can use to interconnect a number of components within our FPGA.

In addition to inputs and outputs, we also declare generics in our entity.

The VHDL code snippet below shows the method we use to declare a generic in an entity.

entity <module_name> is
    generic (
        <generic_name> : <type> := <default_value>
    );
    port (
        -- Port declarations
    );
end entity <module_name>;

The <generic_name> field in the VHDL code above is used to give an identifier to our generic. We use this identifier to call the generic value within our code, much like with a normal signal, port or variable.

We can also assign a default value to our generic using the <default_value> field in the example above. This is useful as it allows us to instantiate the component without having to specifically assign a value to the generic.

When we instantiate a component in a VHDL design unit, we use a generic map to assign values to our generics. The code snippet below shows how we use a generic map to assign values to our generics in VHDL.

<instance_name> : entity <library>.<module_name> is
    generic map (
        <generic_name> => <value>
    )
    port map (
        -- Port connections
    );

VHDL Generic Example

In order to better understand how we can declare and use a generic in VHDL, let's consider a basic example. For this example we will look at a design which features two synchronous counters, one which is 8 bits wide and another which is 12 bits wide.

To implement this circuit, we could write two different counter components which have a different number of bits in the output. However, this is an inefficient way of coding our circuit.

Instead, we will write a single counter circuit and use a generic to change the number of bits.

As it is not important to understanding how we use generics, we will exclude the RTL code in this example. Instead, we will look only at how we declare and instantiate an entity which includes a generic in VHDL.

The code snippet below shows how we would write the entity for the counter circuit.

entity counter_example is
  generic (
    count_width : integer := 8
  );
  port (
    clock : in std_logic;
    reset : in std_logic;
    count : out std_logic_vector(count_width-1 downto 0)
  );
end entity counter_example;

In this example we see how we can use a generic to adjust the size of a port in VHDL. Rather than using a fixed number to declare the port width, we substitute the generic value into the declaration.

This is one of the most common use cases for generics in VHDL. We can use this approach to dynamically alter the width of a port, signal or variable.

Now we need a component which we can use to instantiate two instances of this counter. This component will have two inputs - clock and reset - as well as the two outputs from the instantiated counters.

In the counter code above, we defined the default counter output as 8 bits. This means that we can instantiate the 8 bit counter without assigning a value to the generic.

However, we must assign the generic a value when we instantiate the 12 bit counter.

We can see from the VHDL code below how we use a generic map to override the count_width value when instantiating the 12 bit counter.

entity top_level is
  port map (
    clock     : in std_logic;
    reset     : in std_logic;
    count_8   :
    count_12  : out std_logic_vector(11 downto 0)
  );
end entity top_level;

architecture struct of top_level is

begin

  -- Instantiation of the 8 bit counter
  -- In this instance we can use the default
  -- value of the generic
  count_8bit : entity work.counter_example
    port map(
      clock => clock
      reset => reset
      count => count_8
    );

  -- Instantiation of the 12 bit counter
  -- In this instance we must override the 
  -- value of the count_width generic 
  count_12bit : entity work.counter_example
    generic map (
      count_width => 12
    )
    port map(
      clock => clock
      reset => reset
      count => count_12
    );

end architecture struct;

Generate Statements

We use the generate statement in VHDL to either conditionally or iteratively generate blocks of code in our design. This allows us to selectively include or exclude blocks of code or to create multiple instances of a given code block.

The generate statement was introduced in VHDL-1993 and was further improved upon in the VHDL-2008 standard.

We can only use the generate statement outside of processes, in the same way we would write concurrent code.

In addition to this, we have to use either the if or the for keyword in conjunction with the generate command. We use the if generate statement to conditionally generate code whilst the for generate statement iteratively generates code.

We can write any concurrent statements which we require inside generate blocks, including process blocks, component instantiations and even other generate statements.

For Generate Statement in VHDL

The for generate statement allows us to iteratively create multiple instances of a code block. We use the for generate statement in a similar way to the VHDL for loop which we previously discussed.

In fact, we can broadly consider the for generate statement to be a concurrent equivalent to the for loop. However, there are some important differences.

The code snippet below shows the general syntax for the iterative generate statement in VHDL.

<generate_name>: for <variable> in <range> generate

  -- Code to generate goes here

end generate <generate_name>;

As we can see from this snippet, the iterative generate statement syntax is very similar to the for loop syntax. In fact, the code is virtually identical apart from the fact that the loop keyword is replaced with generate.

Unlike with a lot of VHDL statements, we must give a label to all generate statements which we write.

We typcially use the for generate statement to describe hardware which has a regular and repetitive structure.

For example, we may wish to describe a number of RAM modules which are controlled by a single bus. If we use a for generate statement rather than manually instantiating all of the components in the array then we can reduce our code overhead.

For Generate Example

To better demonstrate how the for generate statement works, let's consider a basic example. For this example, we will use an array of 3 RAM modules which are connected to the same bus.

Each of the RAM modules has a write enable port, a 4-bit address bus and 4-bit data input bus. These ports are all connected to the same bus.

In addition, each of the RAMs has a 4-bit data out bus and an enable signal, which are independent for each memory.

The circuit diagram shows the circuit we are going to describe.

Circuit diagram showing three RAM modules connected to a single bus.

We need to declare a 3-bit std_logic type to use in the iterative generate statement so that we can connect to the RAM enable ports. We can then connect a different bit to each of the ports based on the value of the loop variable.

For the data output bus, we must also create an array which we can connect to the output.

We could do this by creating a 12-bit std_logic_vector type and assigning the read data to different 4-bit slices of the array. However, a more elegant solution is to create our own VHDL array type which consists of 3 4-bit std_logic_vectors.

Again, we can then use the loop variable to assign different elements of this array as required.

The VHDL code snippet below shows how we would write this code using the for generate statement.

-- Code to declare an array type
type slv_array_t is array (0 to 2) of std_logic_vector(3 downto 0);
rd_data_array : slv_array_t;

-- Generate the RAM modules
gen_ram_array: for i in 0 to 2 generate
  
  ram_module : entity work.ram_model  
    port map (
      clock => clock,
      enable  => enable(i)
      wr_en   => wr_en,
      addr.   => addr,
      wr_data => wr_data
      rd_data => rd_data_array(i)
    );

end generate gen_ram_array;

If Generate Statement in VHDL

The if generate statement allows us to conditionally include blocks of VHDL code in our design.

We use the if generate statement in a similar way to the VHDL if statement which we previously discussed. In many ways, we can consider the if generate statement to be a concurrent equivalent to the if statement.

The code snippet below shows the general syntax for the if generate statement.

<generate_name>: if <condition> generate
  
  -- Code to generate goes here

end generate <generate_name>;

The if generate statement was extended in the VHDL-2008 standard so that it can use multiple branches. As a result of this, we can now use the elsif and else keywords within an if generate statement.

Prior to the VHDL-2008 standard, we would have needed to write a separate generate statement for each of the different branches.

The code snippet below shows the general syntax for an if generate statement using VHDL-2008 syntax.

<generate_name>: if <condition> generate

  -- Code to generate goes here

elsif <condition> generate

  -- Code to generate goes here

else generate

  -- Code to generate goes here

end generate <generate_name>;

As we can see from this snippet, the conditional generate statement syntax is very similar to the if statement syntax. In fact, the code is virtually identical apart form the fact that the then keyword is replaced with generate.

As we discussed before, it is mandatory to give generate statements a label.

We use the if generate statement when we have code that we only want to use under certain conditions.

One example of this is when we want to include a function in our design specifically for testing. We can use an if generate statement to make sure that we only include this function with debug builds and not with production builds.

If Generate Example

To better demonstrate how the conditional generate statement works, let's consider a basic example.

For this example, we will write a test function which outputs the value 4-bit counter. As this is a test function, we only need this to be active when we are using a debug version of our code. When we build a production version of our code, we want the counter outputs to be tied to zero instead.

We will use a boolean constant to determine when we should build a debug version.

The code snippet below shows the implementation of this example.

-- Use a constant to control our build
constant debug_build : boolean := true;

-- Conditional generate statement
gen_test_count: if (debug_build) generate

  -- Basic counter output for debug
  counter_stimulus:
  process (clock, reset) is
  begin
    if (reset = '1') then
      -- Reset the counter to zero
      test_count <= (others => '0');
    elsif rising_edge(clock) then
       -- Increment the counter allowing the counter
       -- to roll round to zero naturally
       test_count <= test_count+1;
    end if;
  end process counter_stimulus;

else generate

  -- Tie the test signal to zero for production runs
  test_count <= (others => '0');

end generate gen_test_count;

This example code is fairly simple to understand. If we set the debug_build constant to true, then we generate the code which implements the counter.

If we are building a production version of our code, we set the debug_build constant to false. In this case, the else branch of our code is executed and the counter is tied to zero.

As we previously discussed, we can only use the else branch in VHDL-2008.

When we use earlier versions of VHDL then we have to use a pair of if generate statements instead. One of these statements covers the case when debug_build is true whilst the other covers the case when it is false.

The code snippet below shows how we would do this.

-- Use a constant to control our build
constant debug_build : boolean := true;

-- Conditional generate statement for true case
gen_test_count: if (debug_build) generate

  -- Basic counter output for debug
  counter_stimulus:
  process (clock, reset) is
  begin
    if (reset = '1') then
      -- Reset the counter to zero
      test_count <= (others => '0');
    elsif rising_edge(clock) then
       -- Increment the counter allowing the counter
       -- to roll round to zero naturally
       test_count <= test_count+1;
    end if;
  end process counter_stimulus;

end generate gen_test_count;

-- Conditional generate statement for true case
gen_test_count_gnd: if (debug_build = false) generate
  -- Tie the test signal to zero for production runs
  test_count <= (others => '0');
end generate gen_test_count_gnd;

Exercises

How can we use generics to make our code reusable?

We can define certain parameters which are set when we instantiate a component. This allows us to configure some behaviour on the fly.

How do we assign a value do a generic when we instantiate a module?

We use a generic map to assign values to generics.

Write the entity for a counter with a parallel load function using a generic to set the size of the counter output.

entity counter_example is
  generic (
    count_width : integer
  );
  port (
    clock    : in std_logic;
    reset    : in std_logic;
    load     : in std_logic;
    load_val : in std_logic_vector(count_width-1 downto 0);
    count    : out std_logic_vector(count_width-1 downto 0)
  );
end entity counter_example;

What is the difference between an if generate and a for generate statement

An if statement conditionally generates code whereas a for generate statement generates code iteratively

When can we use the elsif and else keywords in an if generate statement?

We can only use these keywords when we are using VHDL-2008.

Sign up free for exclusive content.

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close
The fpgatutorial.com site logo

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close