Intel® Quartus® Prime Software
Intel® Quartus® Prime Design Software, Design Entry, Synthesis, Simulation, Verification, Timing Analysis, System Design (Platform Designer, formerly Qsys)
17240 Discussions

VHDL Variables: Is it combinational code inside a clocked process?

Altera_Forum
Honored Contributor II
11,792 Views

Hello all 

 

Since the VHDL variables topic strikes as a little bit odd even to VHDL designers who have some experience I would like to ask about VHDL variables in order to clarify a few things. 

 

If we have the usual case of register A feeding register B through some combinational logic (registers in same clock domain) then, until now,when I needed to make some complicated combinational code to represent the combinational code between the two registers, I used to do it in another non-clocked process having as inputs the outputs of register stage A and the outputs of the combinational code feeding the input of register stage B. Then inside the clocked process of stage B assign the values of the input. 

 

I was wondering, in order to make the code more readable and maintanable, is it possible to use variables to implement the complicated combinational code inside the clocked process so that I do not have to use another non-clocked process? 

 

Since I havent found somewhere a clear answer about the use of variables, I wanted to ask, is that the purpose of existence of VHDL variables? Do variables exist in order simplify the writing of combinational code inside a clocked process? 

 

Note: If I used signals inside the clocked process to help me store intermediate results,that would lead to register inference which is not intended if the designer wants to implement pure combionational code inside the clocked process.
0 Kudos
25 Replies
Altera_Forum
Honored Contributor II
6,852 Views

 

--- Quote Start ---  

Hello all 

 

 

I was wondering, in order to make the code more readable and maintanable, is it possible to use variables to implement the complicated combinational code inside the clocked process so that I do not have to use another non-clocked process? 

 

Since I havent found somewhere a clear answer about the use of variables, I wanted to ask, is that the purpose of existence of VHDL variables? Do variables exist in order simplify the writing of combinational code inside a clocked process? 

 

Note: If I used signals inside the clocked process to help me store intermediate results,that would lead to register inference which is not intended if the designer wants to implement pure combionational code inside the clocked process. 

--- Quote End ---  

 

 

That is one of the 'great' uses of variables: splitting up a long series of complicated comb code inside a clocked process over multiple lines. The other use is: instead of declaring globally visible 'signals' which are only used in this process, you can declare variables locally and make them infer registers. 

You have perfect control whether a variable infers registers or not: if you assign to the variable first before 'reading' it, no registers will be inferred. 

I made a 'contrived' example: 

--variables.vhd library ieee ; use ieee.std_logic_1164.all ; use ieee.numeric_std.all ; entity variables is port( Clk : in std_logic; Reset : in std_logic; A : in natural range 0 to 255; B : in natural range 0 to 255; C : in natural range 0 to 255; Y : out natural range 0 to 1023 ); end variables; architecture arch of variables is begin process ( Clk , Reset ) is variable t1 : natural ; -- combinatorial variable t2 : natural ; -- registered begin if (Reset = '1') then Y <= 0 ; elsif rising_edge( Clk ) then Y <= t2 + C ; -- this will infer registers for t2 if (A > B) then t1 := A ; else t1 := B ; end if ; t2 := t1 + B ; end if; end process ; end arch ; 

If we run this through the Analysis & Synthesis we see the following in the RTL Viewer: see .pdf attached. 

You can see the combinatorial logic for 't1' and the inferred registers for 't2'.
0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

No more to say! 

 

Thanks a lot!
0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

Oh. Another question came up though. 

 

What happens when the designer wants to export asynchronously a variable from a clocked process? 

 

Declaring a signal and assigning it to the variable inside the clocked process would infer a register... 

 

I suppose doing the assignment after the "clocked" IF, ELSEIF(rising edge) would still keep the signal asynchronous...right?
0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

You are right but I think it's a matter of readibility and avoiding mistakes. 

 

I find it more easy to read splitting the RTL into a combination process and a sequential process. So, no need for variable. when you need the _next value(comb) right away in the sequential process just use the LVALUE (next) in the decision making. 

 

It's always harder to spot errors in simulation using Variables and simulation/synthesis mismatch ocurrs more. 

 

YMMV
0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

 

--- Quote Start ---  

Oh. Another question came up though. 

 

What happens when the designer wants to export asynchronously a variable from a clocked process? 

 

Declaring a signal and assigning it to the variable inside the clocked process would infer a register... 

 

I suppose doing the assignment after the "clocked" IF, ELSEIF(rising edge) would still keep the signal asynchronous...right? 

--- Quote End ---  

 

No. I just modified my 'contrived' example to try this out.--variables.vhd library ieee ; use ieee.std_logic_1164.all ; use ieee.numeric_std.all ; entity variables is port( Clk : in std_logic; Reset : in std_logic; A : in natural range 0 to 255; B : in natural range 0 to 255; C : in natural range 0 to 255; Y : out natural range 0 to 1023; Y5 : out natural range 0 to 255; Y2 : out natural range 0 to 255; Y3 : out natural range 0 to 511; Y4 : out natural range 0 to 1023 ); end variables; architecture arch of variables is begin process ( Clk , Reset , A, B) is variable t0 : natural range 0 to 255 ; variable t1 : natural range 0 to 255 ; variable t2 : natural range 0 to 511 ; variable t4 : natural range 0 to 255 ; begin if (Reset = '1') then Y <= 0 ; t2 := 0 ; elsif rising_edge( Clk ) then Y <= t2 + C ; if (A > B) then t1 := A ; t0 := B ; else t1 := B ; t0 := A ; end if ; t2 := t0 + B ; end if; Y2 <= t1 ; Y3 <= t2 ; Y4 <= t1 + t2 ; if (A > B) then t4 := A ; else t4 := B ; end if ; Y5 <= t4 ; end process ; end arch ; 

In variables3.pdf you can see what happens:
0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

Why does t1 become a register?

0 Kudos
Altera_Forum
Honored Contributor II
6,852 Views

 

--- Quote Start ---  

Why does t1 become a register? 

--- Quote End ---  

 

 

Puzzles me too :) 

It would have been very nice had it stayed combinatorial. 

This may be one of the oddities Aprado hinted at. Modelsim may behave differently than Quartus II. Until now I've only used variables as I show in my first code example. (Playing safe)
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

Well it has to do with the involvement of t2. Did you get any warnings? Because t1 now has no reset value. 

 

If you have put the assignment of t4 inside the clocked if (it would still be combinational) and assign it to Y5 outside the clocked if then would Y5 still be combinational? 

 

By answering to this question i also solve the asynchronous propagation of my variables outside of the process (as soon as the variables do not interact with clocked variables or signals, as we saw here with t1 and t2)
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

Puzzles me too :) 

It would have been very nice had it stayed combinatorial. 

This may be one of the oddities Aprado hinted at. Modelsim may behave differently than Quartus II. Until now I've only used variables as I show in my first code example. (Playing safe) 

--- Quote End ---  

 

 

t1 is not register. It is y2 and y4 that are registered.
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

You are right but I think it's a matter of readibility and avoiding mistakes. 

 

I find it more easy to read splitting the RTL into a combination process and a sequential process. So, no need for variable. when you need the _next value(comb) right away in the sequential process just use the LVALUE (next) in the decision making. 

 

It's always harder to spot errors in simulation using Variables and simulation/synthesis mismatch ocurrs more. 

 

YMMV 

--- Quote End ---  

 

 

I don't know of any mismatch between tools regarding variables as long as they conform to vhdl standards. 

One case when I need to use variable is modulo n accumulator when n is not power of 2. 

For example if I need to add 7 modulo 100. I need to prevent counter going > 99 so I need to use variable to check value of counter and force it: 

variable count : integer range 0 to 99 := 0; begin ... if count > 99 then count := count -100; else count := count + 7; end if; ...  

if you use signal then count will be > 99 for one sample. Here one cay use if (count + 7) > 99... but I prefer variable
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

t1 is not register. It is y2 and y4 that are registered. 

--- Quote End ---  

 

I think the opposite view is more appropriate. Viewn from the outside of the clock sensitive code, t1 behaves as a register because it's assigned under clock control. 

 

The view of variable assignments as "combinational code" inside a clocked process isn't completely true. The behaviour is only combinational for succeeding references to the variable inside the clock sensitive code. It's registered for preceeding references and those not controlled by the same clock edge. 

 

This can lead to hardly readable behaviour in some cases. In so far I understand why people want to separate combinational and registered processes and consider multiple clock sensitive events in a process as sacrileg. 

 

But if you want to write compact code that keeps functionally related actions close together, these "mixed" processes can serve a purpose.  

 

Assigning a variable in the clocked code and reading it in the combinational code, as done in the previous example, is probably not the best idea, because it's "avoidable confusing".  

 

The latest integer variable example has implications of synthesis to simulation mismatch, but they are not primarly specific to variables, I think. 

 

Exceeding the range of 0 to 99 in simulation causes an exception. To keep the code simulation compatible, the range must be increased (or the code changed).
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

From my observation a variable always means: 

(1) combinatorial logic for the given assignment even though it is inside clocked process. Thus 

 

t1 := A and B; 

y <= t1; 

 

means just that t1 is output of ANDed A,B without any register(no clock period delay) . 

y is a register on output of ANDed inputs 

 

(2) in the case t1 is read before its assignment e.g. 

C <= t1; 

t1 := A AND B; 

 

then 

C is obviously registered. t1 is not but the tool understands t1 to keep its last value which is to be assigned later so a further register is added to save t1 for C assignment leading to two registers (one is named C, the other one on t1 is not known as t1 to the tool since t1 refers to the combinatorial node but this might depend on tool) 

 

regarding my example (count integer range 0 to 99), yes it should be 0 to say anything suitable more than 99.
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

The Quartus compiler seems to create a combinational and a registered variant of variable t1 and uses it to fed the different expressions containing t1 respectively. Interestingly, the functionally identical register t1 and D~reg0 are kept separate. 

 

I think, it makes no sense to say variable t1 is not registered. Another example of registered variable usage is the Quartus binary counter template. 

 

library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity test is Port ( clk: in std_logic; A: in STD_LOGIC; B: in STD_LOGIC; C : out STD_LOGIC; D : out STD_LOGIC; E : out STD_LOGIC); end test; architecture rtl of test is begin process (clk) variable t1: std_logic; begin if rising_edge(clk) then C <= t1; t1 := A and B; D <= t1; end if; E <= t1; end process; end rtl;
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

As you an see even though  

C <= t1; 

D <= t1; 

 

literally means both are driven by t1 but not exactly in sequential sense. first t1 implied registered version of t1 (or say after process update in software mindset) 

 

regarding name of register, the habit is to name it by its output and not by its input and if you search for t1 reg in timequest I doubt you will find it. 

 

On the other hand the counter example I gave above gives more insight into behaviour of variables: 

if counter is variable or signal you end up with same counting logic (adder and register) but the test logic is put on different nodes. For variable count the test logic is on the count node (i.e. input to register). For signal count the test is on count node again (but is now output of register). 

 

by the way the statement: 

count := count + 1; is similar to the case of reading variable before its assignment.
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

The assignment C <= t1 ; reads t1 before t1 is assigned to, hence it infers a register 

The assignment D <= t1 ; reads t1 after t1 has been assigned with 'A and B' hence D is fed with the combinatorial 

The assignment E <= t1 ; also infers a register, but in this case this is not visible as the compiler can re-use the previously inferred register (from C <= t1; ) 

Splitting the example over the three different assignments shows it better:-- test1------------------------ library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity test1 is Port(clk : in std_logic; A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC); end test1; architecture rtl of test1 is begin process(clk) variable t1 : std_logic; begin if rising_edge(clk) then Y <= t1; -- read first t1 := A and B; end if; end process; end rtl; -- test2------------------------ library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity test2 is Port(clk : in std_logic; A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC); end test2; architecture rtl of test2 is begin process(clk) variable t1 : std_logic; begin if rising_edge(clk) then t1 := A and B; -- assign first Y <= t1; end if; end process; end rtl; -- test3------------------------ library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity test3 is Port(clk : in std_logic; A : in STD_LOGIC; B : in STD_LOGIC; Y : out STD_LOGIC; Yb : out std_logic); end test3; architecture rtl of test3 is begin process(clk) variable t1 : std_logic; begin if rising_edge(clk) then t1 := A and B; Yb <= t1; end if; Y <= t1; -- attempt to produce a comb output fails end process; end rtl; ----------------------------- -- all ----------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity test is Port(clk : in std_logic; A : in STD_LOGIC; B : in STD_LOGIC; Y1, Y2, Y3, Y3b : out STD_LOGIC); end test; architecture rtl of test is begin t1 : entity work.test1 port map(clk => clk, A => A, B => B, Y => Y1); t2 : entity work.test2 port map(clk => clk, A => A, B => B, Y => Y2); t3 : entity work.test3 port map(clk => clk, A => A, B => B, Y => Y3, Yb => Y3b); end rtl;  

producing this RTL Viewer image:
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

The assignment .... 

--- Quote End ---  

 

 

Josyb can you please try doing again test3 but now with using another variable t2 to store t1 before setting Yb? 

 

process(clk) variable t1 : std_logic; variable t2 : std_logic; begin if rising_edge(clk) then t1 := A and B; t2 := t1; Yb <= t1; end if; Y <= t2; -- does this attempt fail to export asynchronous t2? end process;  

 

Can you do this test and provide the RTL diagram again?
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

 

Can you do this test and provide the RTL diagram again? 

--- Quote End ---  

 

 

You can try this yourself too, you know? 

I doesn't make a difference the register called 't1' is now called 't2' :)
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

Josyb can you please try doing again test3 but now with using another variable t2 to store t1 before setting Yb? 

 

process(clk) variable t1 : std_logic; variable t2 : std_logic; begin if rising_edge(clk) then t1 := A and B; t2 := t1; Yb <= t1; end if; Y <= t2; -- does this attempt fail to export asynchronous t2? end process;  

 

Can you do this test and provide the RTL diagram again? 

--- Quote End ---  

 

 

t2 will be just wired to t1. 

 

The assignment Y <= t2; being outside clock edge is firstly very uncommon. Secondly it implies that t2 drive Y whether t2 is assigned inside clock construct or occurs outside. We know that if clock edge occurs then t2 is assigned but if clock edge is not true then t2 must retains its value and this implies registered version of t2 to drive Y
0 Kudos
Altera_Forum
Honored Contributor II
6,853 Views

 

--- Quote Start ---  

t2 will be just wired to t1. 

 

The assignment Y <= t2; being outside clock edge is firstly very uncommon. Secondly it implies that t2 drive Y whether t2 is assigned inside clock construct or occurs outside. We know that if clock edge occurs then t2 is assigned but if clock edge is not true then t2 must retains its value and this implies registered version of t2 to drive Y 

--- Quote End ---  

 

 

Yes you are right. I tried it too. 

 

In general my problem is to be able to create one solid process per logic entity with complex combinational logic that could also be exported out of the process as asynchronous signals to be used for other processes. In order to achieve that I suppose I have to use variables that get assigned outside the clocked if (but also read but not assigned inside the clocked if) and when I need to export them I can assign the variables-to-be-exported to signals but outside the clocked if. 

 

Another question is that maybe inside the clocked process I need to make a variable assignment. Is it possible to split the clocked if in two (so have two if(rising edge)) inside the same process and in between make asynchronous variable assignments? 

 

process(clk) variable t1 : std_logic; variable t2 : std_logic; begin t1 := A and B; if rising_edge(clk) then Yb <= t1; end if; Y <= t1 XOR Yb; t2 := A or B; if rising_edge(clk) then Yc <= t2 OR Y; end if; end process;  

 

Is this mixture possible?
0 Kudos
Altera_Forum
Honored Contributor II
6,510 Views

 

--- Quote Start ---  

The assignment Y <= t2; being outside clock edge is firstly very uncommon 

--- Quote End ---  

 

Whether it is uncommon doesn't matter. It is valid VHDL. (neither QuartusII nor ModelSim complained ...) This method allows you to make a Mealy state machine factoring in the inputs after the clocked process. Very concise, everything in one process. (That does appeal to you, doesn't it?) 

 

 

--- Quote Start ---  

Secondly it implies that t2 drive Y whether t2 is assigned inside clock construct or occurs outside. We know that if clock edge occurs then t2 is assigned but if clock edge is not true then t2 must retains its value and this implies registered version of t2 to drive Y 

--- Quote End ---  

 

Saying it simpler: "Any output leaving a clocked process (the part between the 'if rising_edge() then' and the matching 'end if ;' or as you call it 'inside the clock edge' ) will be registered". 

I'm not sure whether all VHDL compilers will generate the same RTL. I couldn't find any specifics on that in the LRM, but I only did a quick text search for variable.
0 Kudos
Reply