View Course Path

Dataflow modeling architecture in VHDL

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.

An important detail to remember in dataflow modeling is that due to the concurrent nature of execution, the order of the statements does not matter.

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.

Syntax

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.

Syntax

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.

Timing diagram of delta delay.
The timing diagram of the delta delay.

 

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.

You may think about how delays make our models more practical. Elementary electronics state that propagation delays exist in every transistor; thus, by extension, in every logic gate. Every component has its propagation delay, and it adds up as we use more and more gates. Delay should be considered because otherwise, every signal will flow through gates almost instantaneously, which is a violation practically. There will always be delays (however small) between a signal starting off at one end and reaching the other via a bunch of logic gates.

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 end keyword.

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;

Generate statements

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.

4-bit parallel adder

Program and explanation

We start by including the necessary library and using its required package by use keyword.

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 end the architecture.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *