For dataflow modeling in VHDL, we specify the functionality of an entity by defining the flow of information through each gate. We primarily use concurrent signal assignment statements and block statements in dataflow modeling. Let’s take a look at these statements in detail, and what transpires in dataflow modeling on the whole.
Concurrent signal assignment statements
Concurrent statements are those statements that are executed in parallel. These are the primary choice for modeling the behavior of an entity in the dataflow style. Let’s understand more about the concurrent signal assignment by an example.
Consider the program below:
entity ckt is port (A: in BIT:=1; B: in BIT; Y,Z: out BIT); end ckt; architecture ckt of ckt is begin B <= A and A; Y<= A and B; Z<= B after 10 ns; end ckt;
The architecture body of the above program contains three concurrent signal assignment statements. These statements represent the dataflow, or you may say information flow through the entity. Just try to guess the values of Y & Z after the execution of this program.
If you’re familiar with some kind of programing language, then you may guess the following results:
Y = 1, Z = 1
And yes, you’d be right. Let’s understand this.
First, let’s write the initial values of the ports.
A = 1, B = 0, Y = 0,Z = 0.
You might think that only A was initialized with a value, how did we write/assume the values of other ports? We did that because those are of datatype BIT. Are you still confused? Then you should go through the post on Datatypes in VHDL. In that article, we have explained what default values different datatypes assume if their values are not explicitly declared initially.
Moving ahead, the VHDL compiler executes those three statements concurrently/parallelly.
So, A and A means 1 and 1, which is equals to 1, and this ‘1’ will be assigned to B after a very small delay known as delta delay. Now the value of B is updated, so all the expressions which contain B will now execute concurrently. This results in A and B (which means 1 and 1) because the value of B is now updated. This finally transpires to Y = 1.
Z will be equal to ‘1’ because we have modeled a delay of 10 ns with it. We will study about delay modeling in detail in this article later on.
So, the VHDL compiler calculates the value of Z but assigns it after 10 ns. That’s how concurrent statements work.
Conditional signal assignment statements
This conditional statement will assign the value to its target signal only when a condition is true. You can give multiple values with multiple conditions. They are similar to if-else statements.
Targeted_signal <= Value when condition else Value_2 when condition_2 else . . . . . Value_last;
In the above code, the VHDL compiler assigns a value (or values) to ‘Targeted_signal’ when its corresponding condition is true. Conditions can be a Boolean expression, and value (or values) can be any waveform element. When more than one conditions are true, then compiler gives precedence to the first occurring true condition. For example,
Output <= input(0) after 20 ns when S(0) = ‘0’ and S(1) = ‘0’ else input(1) after 20 ns when S(0) = ‘0’ and S(1) = ‘1’ else input(2) after 20 ns when S(0) = ‘1’ and S(1) = ‘0’ else input(3) after 20 ns;
Whenever there is an event on signals input(0), input(1), input(2), input(3), S(0) or S(1). The VHDL compiler will execute the above statements. So, these signals act as a sensitivity list for this conditional statement.
Now, what is a sensitivity list you may ask? It’s just a list of signals. And when any change occurs in the value of any signal mentioned inside the sensitivity list, it triggers the event (i.e instruction in the program that involves the signal in question). In this case, it triggers our conditional signal assignment statement.
After that, conditions will be checked. Here the condition is in the form of Boolean expressions. [S(0) = ‘0’ and S(1) = ‘0’] will be evaluated first.
And if that is true, then the VHDL compiler will assign input(0) to output and will not evaluate further.
But if it is false then second condition [S(0) = ‘0’ and S(1) = ‘1’ ] will be evaluated.
And this procedure continues, and if no condition is true, then a
else value is assigned to the ‘Targeted_signal’. In this example that
else value is [input(3) after 20 ns].
Selected signal assignment statements
This signal assignment statement will select and assign a value to its target signal based on the value of the select expression. They are similar to case statements in the C-programming language. You can also give multiple values with multiple choices.
with expression select Target_signal <= Value1 when choice1, Value2 when choice2, Value3 when choice3, . . . . Value(Nth)when choice(Nth);
This is very similar to conditional signal assignment statements. The compiler will execute these statements whenever there is any change in any signal value or select expression. VHDL compiler will assign the value whose corresponding choice matches the expression.
One thing you must remember that you must cover all possible values of the expression in choices. Values not covered explicitly may be covered by an
others clause, which will cover all remaining cases. Also, the evaluation of choices is not in sequence. Let’s understand this all with an example,
Type Func is ( ADD, SUB, MUL, DIV); Signal Arithmetic_Func: Func; . . . . with Arithmetic_Func select Z <= A + B when ADD, A – B when SUB, A * B when MUL, A / B when DIV;
In this above example, Firstly, we create a user-defined datatype (Func) using type declaration. After that, we initialize a signal of type Func with the label (Arithmetic_Func). One thing that you may have noticed that the above code is not complete. That’s because it is just for a demonstration.
After that, we use a conditional signal assignment statement. In our case, the expression is ‘Arithmetic_Func’, and its possible values are ADD, SUB, MUL, DIV.
When [Arithmetic_Func = ADD], the VHDL compiler will add the values of A and B and will assign the result to Z. Similarly, for the rest of the choices, the compiler will evaluate and assign appropriate values.
Delay in the signal assignment
Delta delay( Δ )
Delta delay is a very small delay, or you can say infinitesimally small. It does not mean an actual delay. That is why it can be modeled as a delay of 0 ns. We use it to model the hardware with almost no delay. VHDL compiler uses it to make simulations possible in software.
We say that some statements run concurrently, which can be easy in hardware due to the presence of separate circuits. But in simulation, physically, there is only one hardware present, which is simulating the whole system.
Hence, we use delta delay to give spacing between concurrent statements in software. It will not impact our results in any way.
The above figure shows the presence of a delta delay. The value of D is updated at time ‘T’, and Z is updated at the time (T+ Δ). Both are concurrent statements which are executed concurrently, but due to hardware limitation, they have a delta delay.
Delay using ‘after’ statement
We know that VHDL is a hardware description language. It means that we use it to describe hardware entities or circuits. We mainly use it to model our hardware for verification purposes. So, for that, our hardware description should be as practical as possible. VHDL has so many amazing features that we can implement to make out hardware description more practical. One of these features is creating a delay.
So, to create definite delays, we use
after clause. The expression is evaluated on execution, but the value is assigned after the delay. For example,
Sum <= A xor B after 10 ns;
The above statement models a xor gate having a propagation delay of 10 nanoseconds. As the VHDL compiler is a software and simulating the hardware, so it really can’t simulate the exact same behavior. In actual hardware, the time in which signal travels/propagates through the entire gate is called its propagation delay.
In software, when we model a delay, it doesn’t really mean that now the calculation is taking that time. It actually means that now the compiler will assign the result of calculation after that particular delay.
when-else statements in VHDL
We’ll understand this with an example of a 2-bit magnitude comparator.
Library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all;
Firstly, we include the necessary libraries and use the needed packages.
entity Comparator is port ( inA,inB : in std_logic_vector(2 downto 0); greater, equal, smaller : out std_logic ); end Comparator ;
Then we create the entity with two input ports of two-bit each. And three output ports of 1 bit each.
architecture bhv of Comparator is begin greater <= '1' when (inA > inB) else '0'; equal <= '1' when (inA = inB) else '0'; smaller <= '1' when (inA < inB) else '0'; end bhv;
Then we use three when-else conditional signal assignment statements to compare the magnitudes.
with-select statements in VHDL
We’ll understand this with an example of a BCD to seven segment code converter.
Firstly, we include the necessary libraries and use the needed packages.
library ieee; use ieee.std_logic_1164.all;
Then we create the entity, one input ports of the 4-bit and second input port of 1 bit for enable. And one output ports of 7-bit.
entity SS_CC is port( enb : in std_logic; BCDin : in std_logic_vector(3 downto 0); Op_Active_high : out std_logic_vector(6 downto 0) ); end SS_CC;
Then we start the
architecture and initialize required signals.
architecture dataflow of SS_CC is signal seven_segment : std_logic_vector(6 downto 0); begin
Then we use
with-select statement having BCDin as expression. And we assign the precalculated values of output for every possible input.
with BCDin select seven_segment <= "1000000" when "0000", "1111001" when "0001", "0100100" when "0010", "0110000" when "0011", "0011001" when "0100", "0010010" when "0101", "0000010" when "0110", "1111000" when "0111", "0000000" when "1000", "0010000" when "1001", "0001000" when "1010", "0000011" when "1011", "1000110" when "1100", "0100001" when "1101", "0000110" when "1110", "0001110" when others;
Then we check for the state of enabling pin and use
and operation for every bit individually. We do this to ensure that output appears only when the enable pin is high. After that, we finish the program using
Op_Active_high(0) <= not seven_segment(0) and enb; Op_Active_high(1) <= not seven_segment(1) and enb; Op_Active_high(2) <= not seven_segment(2) and enb; Op_Active_high(3) <= not seven_segment(3) and enb; Op_Active_high(4) <= not seven_segment(4) and enb; Op_Active_high(5) <= not seven_segment(5) and enb; Op_Active_high(6) <= not seven_segment(6) and enb; end dataflow;
In concurrent assignments, we use
generate statements to create loops. But these loops are not the same as we use in other programming languages.
Generally, we use loops for a variable say ‘k’ from 1 to N. It will assign ‘1’ to k in the first iteration, ‘2’ in the second, and so on.
But in VHDL, it is different than that.
Here, loops create a replica of the circuit N times, which is not desirable in most of the cases. Because then it also needs a much larger physical space during fabrication.
But still, we can use it for some purposes in VHDL. For example, creating an N-bit adder using just one full adder. You can also do this simply by using structural modeling. But still, if you need a 32-bit or 64-bit parallel adder, then you have to write 32 or 64 lines for it. Instead, you can use
generate statement for the purpose.
Let’s explore this example furthermore. We will be designing a 4-bit parallel adder using
generate statement. If you want to learn more about parallel adders, then refer to this article here.
Program and explanation
We start by including the necessary
library and using its required package by
library ieee; use ieee.std_logic_1164.all;
Then we define the
entity and initialize its ports. Here we initialize three inputs ports out of which two are of 4-bit each and one is of a single bit. Also, here are two output ports one is of 4-bit, and another one is of a single bit.
entity Parallel_adder is port (A, B: in BIT_VECTOR(3 downto 0); Y: in BIT; SUM: out BIT_VECTOR(3 downto 0); CARRY: out BIT); end Parallel_adder;
Then we start writing the
architecture body for the above entity. Before
begin keyword, we initialize a signal CAR of 4-bit. This will help us in storing values of intermediate carry.
architecture Looping of Parallel_adder is signal CAR: BIT_VECTOR(4 downto 0); begin
Now firstly, we store the value of Y in CAR(0).
CAR(0) <= Y;
Then we create a
loop labeled GenerateK and initialize a loop variable ‘K’ and loop length to 4. This will run the
loop four times and hence replicate the circuit that is modeled inside it four times.
GenerateK: for K in 3 downto 0 generate
The below statements are self-explanatory if you have studied full-adder in DSD. If not, you can refer to this article on full-adder.
SUM(K) <= A(K) xor B(K) xor CAR(K); CAR(K+1) <= (A(K) and B(K)) or (B(K) and CAR(K)) or (CAR(K) and A(K));
Then we use
end keyword to terminate the generate statement.
end generate GK;
Now we assign the value of the signal to the output port. And we
CARRY <= CAR(4); end Looping;
The complete program is given below
library ieee; use ieee.std_logic_1164.all; entity Parallel_adder is port (A, B: in BIT_VECTOR(3 downto 0); Y: in BIT; SUM: out BIT_VECTOR(3 downto 0); CARRY: out BIT); end Parallel_adder; architecture Looping of Parallel_adder is signal CAR: BIT_VECTOR(4 downto 0); begin CAR(0) <= Y; GenerateK: for K in 3 downto 0 generate SUM(K) <= A(K) xor B(K) xor CAR(K); CAR(K+1) <= (A(K) and B(K)) or (B(K) and CAR(K)) or (CAR(K) and A(K)); end generate GenerateK; CARRY <= CAR(4); end Looping;
That sums up dataflow modeling in VHDL. The comment box below awaits your queries if you have any. Next up in this VHDL course, we will study the behavioral modeling architecture.