One question that I’ve debated many times over the years is whether it’s OK to use variables for registers in VHDL. It’s safe to say that newbies are more likely to do it than experienced VHDL designers. But is there any merit to that, or is it just a matter of preference?
In this blog post, I will try to shed some light on the issue so that you can make an informed decision about using this design practice.
First of all, let me explain what I mean by using a variable as a register.
If you read a variable in a VHDL process before you write to it, the synthesis tool will have to implement it using physical storage. That’s because its value has to be stored somewhere until the next time the process wakes up. In FPGAs, that means either registers (flip-flops) or memory (block RAM).
The code below shows an example process where my_var is not a register. The logic doesn’t rely on any previous value of the variable. We give it a default value of ‘0’ as soon as we enter the process.
process(clk) variable my_var : std_logic; begin if rising_edge(clk) then my_var := '0'; if some_condition then my_var := some_value; end if; my_signal <= my_var; end if; end process;
If we comment out the default assignment to my_var, as shown in the example below, it becomes a register. That’s because if some_condition is false, my_signal gets whatever value my_var had the last time the process completed. You are telling the synthesis tool to remember the value of my_var over time, and the only way to do that is by using a register.
process(clk) variable my_var : std_logic; begin if rising_edge(clk) then -- my_var := '0'; if some_condition then my_var := some_value; end if; my_signal <= my_var; end if; end process;
It’s perfectly legal to use variables like that in VHDL. Also, the FPGA tools won’t have any trouble implementing it most of the time. Still, it’s a design practice that’s frowned upon by many FPGA engineers. Some companies even prohibit such use of variables through their coding standards. Let’s have a look at an example and examine the pros and cons of using variables over signals.
Example using a variable to infer block RAM
In this example, I’ve created a VHDL process for inferring a dual-port RAM. The width and depth match a configuration of the Xilinx RAMB36E1 primitive, as shown on page 30 of the 7 Series FPGAs Memory Resources user guide. The code below shows the VHDL process. We store the values in the ram_v object, which is a regular variable.
DUAL_PORT_RAM : process(clk) type ram_type is array (0 to 2**10 - 1) of std_logic_vector(35 downto 0); variable ram_v : ram_type; begin if rising_edge(clk) then data_out <= ram_v(addr_out); ram_v(addr_in) := data_in; end if; end process;
When we synthesize the code in Xilinx Vivado, we see that it has indeed implemented ram_v in block RAM. Below is an excerpt from the synthesis log, showing the variable’s name mapped to a RAMB36 primitive.
Block RAM: Preliminary Mapping Report (see note below) +------------+------------+------------------------+---+---+------------------------+---+---+------------------+--------+--------+ |Module Name | RTL Object | PORT A (Depth x Width) | W | R | PORT B (Depth x Width) | W | R | Ports driving FF | RAMB18 | RAMB36 | +------------+------------+------------------------+---+---+------------------------+---+---+------------------+--------+--------+ |bram | ram_v_reg | 1 K x 36(READ_FIRST) | W | | 1 K x 36(WRITE_FIRST) | | R | Port A and B | 0 | 1 | +------------+------------+------------------------+---+---+------------------------+---+---+------------------+--------+--------+
Variables used for limiting the scope
A possible advantage of using a variable is that its scope is limited to within the process. Along with the variable, we can also place the type declaration of the array inside of the process. Limiting the scope of data objects is generally considered to be a good coding practice. Keeping all the constructs that “belong” to the process within it helps to refine the process as a separate design unit.
There are ways of creating limited scopes for signals as well, for example, by using the VHDL block statement. But as you can see from the example below, it adds more code lines and another indentation level to your VHDL file. I have to give a small victory to variables when it comes to encapsulation.
RAM_BLOCK : block type ram_type is array (0 to 2**10 - 1) of std_logic_vector(35 downto 0); signal ram : ram_type; begin DUAL_PORT_RAM : process(clk) begin if rising_edge(clk) then data_out <= ram(addr_out); ram(addr_in) <= data_in; end if; end process; end block RAM_BLOCK;
Line ordering matters with variables
A thing to be aware of when using variables is that the ordering of the lines matter. If we swap lines 7 and 8 in the original DUAL_PORT_RAM process, it’s broken. That’s not the case if we swap lines 12 and 13 in the code above. With signals, only which enclosure they are within matters, while with variables, the correct ordering of code lines is crucial.
Refer to my earlier blog post to understand the general difference between signals and variables:
How a signal is different from a variable in VHDL
That’s one of the main objections many VHDL designers have against variables used for registers. Some engineers are accustomed to reading the code within an enclosure, like an if-statement, as parallel events. By using both variables and signals interchangeably, it becomes harder to follow the program flow. The code becomes less readable because your mind has to comprehend two constructs with different sets of rules, describing the same thing.
Of course, the arguments about readability is a subjective one. Furthermore, if you expect only signals to describe registers, you may be more inclined to overlook a variable that does the same. But if you use variables for that regularly, it may not be that big an issue for you.
Viewing variables in the ModelSim waveform
When using variables for data storage, you may want to track the value over time in the simulator. Most simulators treat variables different from signals in many ways. In ModelSim, they are not immediately accessible from the Objects window. It’s something to be aware of, but it’s a minor problem.
The video above shows how you can add a variable to the waveform in ModelSim. Bring up the Locals window by choosing View→Locals from the main menu. Select the process that contains the variable, and it will appear under Locals. Right-click the variable and add select Add Wave, just as you would with a signal.
Before writing this article, I posted a question in my private Facebook group to see what other FPGA engineers had to say about using variables for storage. As I expected, most dislike using variables in this way in RTL modules. The consensus is that they are confusing to work with when they assume the same role as signals: to act as registers.
One user wrote that there’s a higher risk of creating latches with variables than signals. I’ve heard that statement before, and I tried to construct an example to demonstrate the problem, but I couldn’t. I wasn’t able to find any situations where a variable causes a latch while a signal doesn’t. Of course, that doesn’t mean that it can’t happen. But we will have to leave that statement unconfirmed at the moment.
Leave a comment if you have such an example!
Finally, a user whose first name is Ray sums up nicely what I think is the most significate argument against variables. He says:
My problem with this is that you are now relying on the synthesis tool to do “the right thing” rather than simply writing code that makes it do the right thing.
I would only use variables in processes that are implementing some combinatorial logic. Then I’d register that output in a separate clocked process with its own reset.
And I have to agree on that. It’s better to be explicit in VHDL. If you want a register, it’s safest to use a signal.