ZEN & THE ART OF VHDL

Return to top VHDL Document.

Go to Table of Contents.



Introduction .

This chapter explains the relationships between constructs in VHDL and the logic which is synthesized. It focuses on coding styles which give the best performance for Logic Synthesis.

VHDL synthesis produces registered and combinational logic at the Register Transfer Level ( RTL ). All combinational behavior around the registers is, unless prohibited by the user, optimized automatically. The style of coding combinational behavior, such as if-then-else and case statements, has some effect on the final circuit result, but the style of coding sequential behavior has significant impact on your design.

The purpose of this section is to show how sequential behavior is produced in VHDL, so that you understand why registers are generated at certain places and not at others. Most examples explain the generation of these modules with short VHDL descriptions in a process.

Return to top of document




Hypertext Table of Contents

The following hypertext list is a table of contents for this document. The user can navigate to any section of this document by "Pointing and Clicking" on any of the blue highlighted text. This will automatically direct the user to that particular subsection.

Introduction.

Arithmetic Logic.

Busses.

Clock Enables.

Decoders, ROMs, & PLAs.

Flip-Flops, Edge Sensitive.

Edge Sensitivity and Event Attributes.

Generating Registers with Wait Statements.

Sets and Resets, Asynchronous.

Sets and Resets, Synchronous.

I/O Buffer Assignments.

Buffer Instantiation.

Bi-Directional Buffers.

Manual Buffer Assignments.

Tri-State Buffers.

Latches - Level Sensitive.

Sets and Resets Asynchronous

Multiplexers & Selectors.

Ranged Integers.

Resets, Registers and Latches.

Sets and Resets, Asynchronous.

Sets and Resets, Synchronous.

Generating Registers with Wait Statements.

Resource Sharing.

State Machines.

State Machine Coding Styles

State Machine Example #1.

State Machine Example #2.

Manual State Assignments.

Mealy vs. Moore Machines

Mealy State Machine Example.

State Machine One-Hot Example.

Variables.

Wait Statements.

Return to top of document






Registers, Latches & Resets.

This section will give implementation examples of various registers and latches.

Level Sensitive Latches

This first example describes a level sensitive latch in VHDL.

   signal input_stuff, output_stuff, ena : bit ; 
                .....      
        process ( ena, input_stuff ) 
        begin                        
                if ( ena = '1' ) then  
                        output_stuff <= input_stuff ;
                end if ;
        end process ;   

In this example, the sensitivity list is required, and it indicates that the process is executed whenever the signals ena or input_stuff change. Also, since the assignment to the global output signal output_stuff is hidden in a conditional clause, output_stuff cannot change ( it will preserve it's old value ) if ena is '0'. If ena is '1', output_stuff is immediately updated with the value of input_stuff whenever it changes. This is the behavior of a level sensitive latch.

Latches can also be generated in dataflow statements, using a guarded block:

        b1 : block ( ena = '1' )
        begin                  
                output_stuff <= GUARDED input_stuff ;
        end_block ; 

Note: Some FPGA technologies ( Xilinx 3000, 4000, and 5000 families ) do not have specific latch macrocells, consequently, the first program above would not be synthesized. By using the code of the second example, a latch at least could be generated (not very efficiently ) with combinational logic. Return to top of document.


Edge Sensitive Flip-Flops.

An edge triggered flip-flop is generated from a VHDL description if a signal assignment is executed on the leading or falling edge of another signal. For that reason, the condition under which the assignment is done should include an edge-detecting mechanism. The EVENT attribute on a signal is the most commonly used edge detecting mechanism. See Event Attribute

Return to top of document.


The Event Attribute.

The EVENT attribute operates on a signal and returns a Boolean result. This result is always FALSE, unless the signal showed a change ( an edge ) in value. If the signal started the process by a change in value, the EVENT attribute is TRUE all the way through that process.

Here is one example of the EVENT attribute, used in the condition clause in a process. VHDL compilers recognize an edge triggered flip-flop from this behavior, with output_stuff updated only if clk shows a leading edge.

        signal input_stuff, output_stuff, clk : bit; 
                ...... 
        process ( clk ) ; 
        begin               
                if (clk'event and clk = '1' ) then 
                        output_stuff <= input_stuff ; 
                end if ; 
        end process ; 

The attribute STABLE is the Boolean inversion of the EVENT attribute. Hence, CLK'EVENT is the same as NOT CLK'STABLE. (Note: this is not always supported by all VHDL compilers. )

Flip-Flops and registers can also be generated with dataflow statements ( as opposed to from a process ) using a GUARDED block.

        b2 : block ( clk'event and clk = '1' ) 
        begin 
                output_stuff <= GUARDED input_stuff ; 
        end block ; 

By adding the GUARDED statement option, a flip-flop will be inserted in between input_stuff and output_stuff since the GUARD expression of the block specifies a clock edge.

Return to top of document.


Synchronous Sets and Resets.

All conditional assignments to the signal output_stuff inside the if clause translate into combinational logic in front of the D-input of the flip-flop. For instance, you could make a synchronous reset on the flip-flop by doing a conditional assignment to output_stuff.

        signal input_stuff, output_stuff, reset, clk : bit;
                ......                                              
        process ( clk ) ;                           
        begin                                       
                if (clk'event and clk = '1' ) then 
                        if reset = '1' then   
                                output_stuff <= '0' ;    
                        else                         
                                output_stuff <= input_stuff ;
                        end if ;          
                end if ;                  
        end process ; 

Note that the signals reset and input_stuff do not have to be on the sensitivity list ( although it is allowed ) since their change does not resulting any action inside of the process.

Alternatively, dataflow statements could be used to specify a synchronous reset, using a guarded block and a conditional signal assignment.

      b3 : block (clk'event and clk = '1' )

      begin 

           output_stuff <= GUARDED '0' when reset = '1' else input_stuff ;

      end block ; 

Return to top of document.


Asynchronous Sets and Resets.

If you want the reset signal to have immediate effect on the output, but still let the assignment from input_stuff to output_stuff only happen on the leading clock edge, it requires the behavior of an asynchronous reset. Here is the process.

   signal input_stuff, output_stuff, reset, clk : bit;
           ......

   process ( clk, reset ) ;

   begin 

           if ( reset = '1' ) then 

                   output_stuff <= '0' ;

           elsif (clk'event and clk = '1' ) then

                   output_stuff <= input_stuff ;

           end if ; 

   end process ; 

Now reset has to be on the sensitivity list. If it were not the, VHDL semantics require that the process should not start if reset changes. It would only start if clk changes. That means that if reset becomes a '1', output_stuff would be set to '0' if clk either goes up , or foes down, but not before any change of clk. This behavior cannot be synthesized into logic.

Asynchronous set and reset can both be used. It is also possible to have expressions instead of the fixed '1' or '0' in the assignments to output_stuff in the reset t and set conditions. This results in the combinational logic driving the set and reset input of the flip-flop of the target signal. The following piece of code shows the structure of such a process.

   process ( <clock>, <asynchronously _used_signals> )

   begin 

           if ( < Boolean _expression > ) then
                   < asynchronous_signal_assignments > 

           elsif ( Boolean_expressions > ) then 

                   < asynchronous_signal_assignments > 

           elsif ( <clock >'event and < clock> = < constant > ) 
                           then < synchronous_signal_assignments> 

   end process ; 

There can be several asynchronous elsif clauses, but the synchronous elsif clause ( if present ) has to be the last one in the if clause. A flip-flop is generated for each signal that is assigned in the synchronous signal assignment. The asynchronous clauses result in combinatorial logic that drives the set and reset inputs of the Flip-Flops. If there is not synchronous clause, all logic becomes combinational.

Return to top of document.


Clock Enables.

It is also possible to specify an enable signal in a process. Some technologies have a special enable pin on their basic building blocks. The logic synthesizer recognizes the function of the enable from the VHDL description and generates a flip-flop with an enable signal from the following code:

   signal input_stuff, output_stuff, enable, clk : bit;

                   ......

   process ( clk ) ;

   begin 

           if (clk'event and clk = '1' ) then

                   if ( enable = '1' ) then 

                           output_stuff <= input_stuff ;

                   end if ; 

           end if ; 

   end process ; 

In dataflow statements, a clock enable can be constructed with a GUARDED block and a conditional signal assignment.

   b4 : block (clk'event and clk = '1' )

   begin 

           output_stuff <= GUARDED input_stuff when enable= '1' 

           else output_stuff ;

   end block ; 

Return to top of document.


Wait Statements.

Another way to generate registers is by using the 'wait until' statement. The 'wait until' clause can be used in a process, and is synthesizable if it is the first statement in the process. The following example generates an edge triggered flip-flop between the signal input_stuff and output_stuff:

   signal input_stuff, output_stuff, clk : bit;

           ......

   process 

   begin 

           wait until clk'event and clk = '1';

                   output_stuff <= input_stuff ;

   end process ; 

Note that there is no sensitivity list on this process. In VHDL, a process can have a sensitivity list or a wait statement, but not both. In this example, the process is executed if clk is present in the wait condition. Also the wait condition can be simplified to "wait until clk ='1':" since the process only starts if clk changes, and thus clk'event is always true.

Note: Many logic synthesizers do not support asynchronous preset or reset behavior with wait statement. A synchronous reset is still possible by describing the reset behavior after the wait statement.

Return to top of document.


Variables.

Variables ( like signals ) can also generate flip-flops. Since the variable is defined in the process itself, and its value never leaves the process, the only time a variable generates a flip-flop is when the variable is used before it is assigned in a clocked process. For instance, the following code segment generates a three-bit shift register:

   signal input_stuff, output_stuff, clk : bit;

           ......

   process ( clk ) ;

           variable a, b, : bit ;

   begin 

           if (clk'event and clk = '1' ) then

                   output_stuff <= b ;

                   b := a ;

                   a := input_stuff ;

           end if ; 

   end process ; 

In this case the variables a and b are used before they are assigned. Therefore, they pass their values from the last run through the process, which is the assigned value delayed by one clock cycle. If the variables are assigned before they are used, you will get a different circuit.

   signal input_stuff, output_stuff, clk : bit;

                   ......

   process ( clk ) ;

           variable a, b, : bit ;

   begin

           if (clk'event and clk = '1' ) then

                   a := input_stuff ;

                   b := a ;

                   output_stuff <= b ;

           end if ; 

   end process ; 

Here, a and b are assigned before being used, and therefore do not generate flip-flops. Instead, they generate a single "wire". Only one flip-flop remains in between input_stuff and output_stuff because of the signal assignment in the clocked process.

Return to top of document.


I/O Buffer Assignments from VHDL.

I/O buffers can be assigned to a design by using the buffer_sig attribute on a port in the VHDL source or in the control file or by using direct component instantiation in VHDL of the buffer that you require.

In logic synthesis the type of buffer that you specify in VHDL is a function of the FPGA or ASIC technology that your code is ultimately targeting. The target technology defines the appropriate input, output, tri-state, or bi-directional buffers to the ports in your entity definition. As an example:

   entity buffer_example is

           port ( inp, clk in std_logic ;

                   outp : out std_logic ;

                   inoutp : inout std_logic ;

                 );

   end buffer_example ;

If this code is targeted to the Xilinx XC-3000 family it translates to an IBUF for inp, GCLK (global clock ) for clk, and OBUF for outp. If it were targeted to the ACTEL ACT2 family, then INBUF translates into INBUF for inp and clk, an OUTBUF for outp, and a BIBUF for inoutp ( if it is both used and assigned), outp would become a TRIBUF if it was assigned to a three-state value under a condition:

           outp <= inp when ena = '1' else 'Z' ;

The above example also holds for buses. Jump to Tri-State Buffers

Return to top of document.



Manual Buffer Assignment.

Special buffers can be assigned using the buffer_sig property, i.e. clock buffers. This can be set on a port using the buffer_sig attribute in the VHDL source code.

   entity example is 

   port ( inp, clk : in std_logic ;

                   outp : out std_logic ;

                   inoutp : inout std_logic ;

                ) ;

           attribute buffer_sig : string ;

           attribute buffer_sig of clk : signal is "CLOCK_BUFFER";

   end example ;

The port clk will be connected to the input of the external clock buffer CLOCK_BUFFER. Gates specified in the VHDL source code are searched for in the source technology's library.

Return to top of document.


Buffer Instantiation.

Buffers can be created in the VHDL source file by using component instantiation. Specifically, if you want to particular complex input to present on a specific input or output, component instantiation can be very useful.

   entity special_buffers is 

           port ( inp : in std_logic ;

                   clk : in std_logic ;

                   outp : out std_logic ;

                   inoutp : inout std_logic

                 ) ;

   end special_buffers ;

   architecture vhdl_example of special_buffers is 

           component OUTPUT_REGISTER

                   port ( c, d, t : in std_logic ;

                           o : out std_logic

                         );

           end component ; 

           component INPUT_BUFFER

                   port ( I : in std_logic ;

                           o : out std_logic

                        ) ;

           end component ; 

           signal internal_in internal_out, io_direction : std_logic;

           begin 

                   b1: OUTPUT_REGISTER : port map ( c => clk, d => internal_out,

                                   t => io_direction, o => inoutp ) ;

                   b2: OUTPUT_BUFFER : port map ( I => inoutp, o=> internal_in ) ;

   end vhdl_example ;

In this example using component instantiation forces and OUTPUT_REGISTER buffer on the bi-directional pin inoutp. Also an input buffer INPUT_BUFFER is specified to pick up the value from this pin to be used internally.

The type of buffers that result from this example are dependent upon the target technology. Care must be taken to insure that the VHDL construct you have written is capable of being expressed in the target technology.

Return to top of document.


Tri-State Buffers

Tri-state and bi-directional buffers are very easy to generate from a VHDL description. A disabled tri-state buffer will be in a high impedance state. The IEEE Standard 1164 standard logic package actually defines the characteristics of a tri-state device. It states that the 'Z" literal character has a behavior that behaves exactly like the high impedance state of a tri-state buffer. A signal ( a port or an internal signal ) of the standard logic type can be assigned a 'Z' value.

   entity tri_state is 

   port ( input_signal : in std_logic ;

           ena : in std_logic ;

           output_signal : out std_logic

         ) ;

   end tri_state ;

   architecture vhdl_example of tri_state is

   begin 

           output_signal <= input_signal when ena = '1' else'Z' ;

   end vhdl_example ;

Normally, simultaneous assignments to one signal in VHDL is not allowed for synthesis, since it would cause data conflicts. However, if a conditional 'Z' is assigned in each statement, the simultaneous assignment resembles multiple tri-state buffers driving the same bus.

   entity tri_state is 

           port ( input_signal1, input_signal2 : in std_logic;

                   ena1, ena2 : in std_logic ;

                   output_signal : out std_logic

               ) ;

   end tri_state ;

   architecture vhdl_example of tri_state is

   begin 

           output_signal <= input_signal1 when ena1 = '1'
                   else 'Z' ;

           output_signal <= input_signal2 when ena2 = '1'
                   else 'Z' ;

   end vhdl_example ;

Warning: It is easy to create bus conflicts when creating tri-state bus assignments. Certain EPLD and FPGA families do not check for contention when logic is synthesized. Make sure that the enables of the tri-state signals are active concurrently. If that happens there can be a bus contention problem, and possible physical damage to the chip, as two or more buffers attempt to drive the same bus.

These examples show assignments to output ports (device ports ). It is also possible to do the assignments to an internal signal. This will create internal busses in such a case.

As another example, you can also generate tri-state buffers from process statements.

   buffer1: process ( ena1, input_signal1 ) begin

           if ( ena1='1' ) then 

                   output_signal <= input_signal1 ; 

           else 

                   output_signal <= 'Z' ; 

           end if ; 

   end process ; 

Return to top of document.


Bi-directional Buffers

Bi-directional I/O buffer can be synthesized if an external port is both used and assigned inside the architecture. Here is an example.

   entity bidirectional is 

           port ( bidirectional_port : inout std_logic;

                   ena : in std_logic ;

                 ) ;

   end bidirectional ;

   architecture vhdl_example of bidirectional is 

           signal internal_signal, internal_input : std_logic ;

   begin 

           bidirectional_port <= internal_signal when ena= '1' else 'Z' ;

                   internal_input <= bidirectional_port ;

                           -- use internal_input

                           -- generate internal_signal

   end vhdl_example ;

The difference from the previous example is that in this case, the output itself is used again internally. Note for that reason , the port bidirectional_port is declared to be inout. The enable signal ena could also be generated from inside the architecture, instead of being a primary input as in this example.

Return to top of document.


Busses.

The example in the section on tri-state signals all use single bits as signals. In reality, busses are often used: arrays of bits with ( multiple ) tri-state drivers. In that case, the type of the bus signal should be std_logic_vector All of the examples given still apply for busses, although the 'Z' character literal now has to be a string literal, as shown in the following example:

   entity tri_state is 

   port ( input_signal1, input_signal2 : in std_logic_vector( 0 to 7 ) ;

           ena1, ena2 : in std_logic ;

           output_signal : out std_logic_vector ( 0 to7 ) ;

        );

   end tri_state ;

   architecture vhdl_example of tri_state is

   begin 

           output_signal <= input_signal1 when ena1 = '1'

                   else "ZZZZZZZZ";

           output_signal <= input_signal2 when ena2 = '1'

                   else "ZZZZZZZZ" ;

   end vhdl_example ;

This example generates two sets of eight tri-state buffers, two on each line of the bus output_signal. As with single tri-state drivers, busses can be internal signals, or ports. Similarly, busses can be created using processes.

Return to top of document.


Arithmetic & Relational Logic.

This section gives an overview of how arithmetic logic is generated from VHDL, and how to avoid getting into combinational logic growth resulting from large amounts of arithmetic behavior. In general logic synthesis is very powerful in optimizing "random" combinational behavior but has problems with logic which is arithmetic in nature. Often special precautions must be taken to avoid ending up with inefficient logic or excessive compilation times.. Alternatively, macros may be used to implement these functions (insert hyperlink here ).

VHDL supports the arithmetic operator "+', "-", "*", and "ABS". These operators work on integers. The operator "+" used on integers generates an adder. The number of bits of the adder depends on the size of the operands. If you use integers, a 32-bit adder is generated. If you use ranged integers, the size of the adder is defined so that the entire range can be represented in bits. For example, if variable a and b do not evaluate to constants, the following code segment:

   variable a, b, c : integer ; 

           c := a + b ; 

generates a 32-bit (signed) adder, however,

   variable a, b, c : integer range 0 to255 ; 

           c := a + b 

generates an 8-bit ( unsigned ) adder.

If one of the operands is a constant, initially a full-sized adder is still generated but logic minimization eliminates much of the logic inside of the adder, since half of the inputs of the adder are constant.

The predefined operator "-" on integers generates a subtracter. The same concepts apply as with the "+" operator.

The pre-defined operator "*" on integers generates a multiplier. The user must be cognizant of the technology being ported to in order to create a multiplier. Many EPLD/ FPGA's lack the resources to generate the logic resources necessary to generate a multiply function.

The pre-defined operator " / " is division on integers. In general only division by a power of two is supported, which in reality is only a shift register that moves the bits in the integer to the right. In order to generate an actual divider circuit, the user must again be aware of the resources available in the target technology.

The operators = , /=, <, >, <=, >= all generate comparators with the appropriate functionality.

Return to top of document


Resource Sharing.

When the same arithmetic expression is used multiple times in an VHDL construct, the compiler sees each instance of the expression as unique set of logic and will use chip resources to create it. Therefore, it is best to avoid duplication of arithmetic expressions. As an example, here is a code segment where the expression a + b is used in two different places:

   signal a, b, c, d : integer range 0 to 255 ;

                      ......

   process ( a, b, c , d ) begin 

           if ( a + b = c ) then 

                   < statements >

           elsif ( a + b = d ) then 

                   < more statements >

           end if ; 

   end process ; 

This code generates Two 8-bit Adders that both add a and b , this is a waste of resources. A work around for this is a simple re-coding of the equations. The expression a + b is assigned to an intermediate variable, and then the variable is used twice:

   process ( a, b, c , d )

           variable temp : integer range 0 to 255 ;

   begin 

           temp := a + b ;

           if ( temp = c ) then 

                   < statements >

           elsif ( temp = d ) then 

                   < more statements >

           end if ; 

   end process ; 

The result of this code segment is that only one adder is generated.

Resource sharing is also an issue of complex arithmetic expressions are mutually exclusively used. In this example:

   process ( a, b, c , test) begin 

           if ( test = TRUE ) then 

                   o <= a + b ;

           else 

                   o <= a + c

           end if ; 

   end process; 

If resource sharing is not used, the code portion outlined above will be implemented with two adders and one multiplexer to generate the output o. In this example, the expressions a + b and a + c are never executed simultaneously. In such a case, it is possible to let both expressions " share" one adder. Here is the proper way to code this section:

   process ( a, b, c , test)

           variable temp: integer range 0 to 255;

   begin 

           if ( test = TRUE ) then 

                   temp := b ;

           else 

                   temp := c ;

           end if ; 

           0 <= a + temp ;

   end process; 

This way, first the selection is done and then the addition.

Return to top of document.


Ranged Integers.

It is best to use ranged integers instead of unbounded integers. IN VHDL, an unbounded integer ( an integer with no range specification ) is guaranteed to include the range -2147483647 to +2147483647. This means that at least 32 bits are required to implement an object of this type. It is technology dependent, but many VHDL compilers will generate large amounts of logic in order to perform operations on these objects. Some of this logic may become redundant and get eliminated in the logic optimization process, but the compile times end up taking much longer.

If you use integers as ports, all logic has to remain in place and synthesis algorithms are faced with a complex problem. Therefore is you do not need the full range of an integer, specify the range that you will need in the object declaration:

   signal small_int : integer range 255 downto 0 ; 

small_int only uses 8 bits in this example, instead of the 32 bits if the range was not specified.

Return to top of document.


Advanced Design Optimization.

Module generation, resource sharing, and the use of ranged integers are all examples of how a particular design can be improved for logic synthesis without changing its functionality. Sometimes it is possible to change the functionality of the design slightly, without violating the constraints of the design specification and still improve the implementation for synthesis. This require in depth understanding of VHDL and what kind of circuitry is generated, as well as understanding the specification of the design. An example is the following pre-loadable loop counter. Often, applications involve a counter that counts up to an input signal value, and if it reaches that value, some actions are needed and the counter is reset to 0.

   process begin 

           wait until clk'event and clk = '1';

                   if ( count = input_signal ) then 

                           count <= 0;

                   else 

                           count <= count + 1 ;

                   end if; 

   end process ; 

For this example, an incrementer and a full-sized comparator that compares the incoming signal with the counter value are built. In this example, the full comparator has to be created since the VHDL description indicates that the comparison has to be done each clock cycle. If the specification allows the comparison to only be done during the reset, the VHDL could be recoded and reduce the overall circuit size by loading the counter with the input_signal, and then counting down to zero.

   process begin 

           wait until clk'event and clk = '1';

                   if ( count = 0 ) then 

                           count <= input_signal ;

                   else 

                           count <= count - 1 ;

                   end if; 

   end process ; 

Here, one incrementer is needed plus a comparison to a constant ( 0 ). Since comparisons to constants are a lot cheaper to implement, this new behavior is much easier to synthesize, and results in a smaller circuit.

This is a single example of how to improve synthesis results by changing the functionality of the design, while still staying within the design specification. However, the possibilities are endless, and a designer should try to use the freedom in the design specification to get optimal synthesis performance.

Return to top of document.


Multiplexers and Selectors.

Any case statement ( or selected signal assignment in the data flow area ) will create a selector. The following is an example of a simple selector that selects a bus line based on the number of '1's in an array.

   case test_vector is 

           when "000" => o <= bus(0) ;

           when "001" | "010" | "100"=> o <= bus(1) ;

           when "011" | "101" | "110"=> o <= bus(2) ;

           when "111" => o <= bus(3) ;

   end case ; 

If the selector value is the index to be selected from an array, the selector will resemble a multiplexer. It is still possible to express this in a case statement, but it is also possible to use a variable indexed array. For example, if an integers value defines the index of an array, a variable indexed array will create the multiplexer function.

   signal vec : std_logic_vector ( 0 to 15 );

   signal o : std_logic ;

   signal I: integer range 0 to 15 ;

                   ......................

           o <= vec ( I ) ;

This selects bit I out of the vector "vec". This
is equivalent to the more complex writing style with a case statement.

   case I is 

           when 0 => o <= vec( 0 ) ;

           when 1 => o <= vec( 1 ) ;

           when 2 => o <= vec( 2 ) ;

           when 3 => o <= vec( 3 ) ;

                   .................

   end case ; 

Although both statements have the same functionality, the initial circuit generated by the logic synthesizer may be larger for a case statement. In general, the variable array index statement will perform better ( area wise and timing wise ) in synthesis than the case statement. It also gives a considerably more compact writing style.

See additional Multiplexer examples in VHDL Document #3(Tips & Tricks), Creating Multiplexers.

Return to top of document.


ROMs, Decoders, and PLAs.

There are many ways to express decoder behavior from a ROM or a PLA ( programmable logic array ) table. The clearest description of a ROM would be a case statement with the ROM addresses in the case conditions and the ROM data in the case statements. In this section, two other examples are discussed - a decoder as a constant array of arrays, and a decoder as a constant two-dimensional array.

Here is an example of a ROM implemented with an array of array type. The ROM defines a hexadecimal to 7 segment decoder:

   type seven_segment is array ( 6 downto 0 ) ;

   type ROM_type is array ( natural range <>) of seven_segment ;

           constant hex_to_7 : rom_type ( 0 to 15 ) :=

                   ("0111111" , -- 0

                    "0011000" , -- 1

                    "1101101" , -- 2

                    "1111100" , -- 3

                    "1011010" , -- 4

                    "1110110" , -- 5

                    "1110111" , -- 6

                    "0011100" , -- 7

                    "1111111" , -- 8

                    "1111110" , -- 9

                    "1011111" , -- A

                    "1110011" , -- B

                    "0100111" , -- C

                    "1111001" , -- D

                    "1100111" , -- E

                    "1000111" , -- F );

           -- Now the ROM field can be accessed via an integer index.

           display_bus <= hex_to_7 ( I );

The ROM with the array of array implementation has the advantage that it can be accessed via a simple integer value as its address. A disadvantage is that each time another ROM is defined, a new element ( seven_segment) and a new rom type ( rom_type ) have to be defined.

PLA ( programmable logic arrays ) descriptions should allow a 'X' or "don't care" value in the input field. to indicate a product line's insensitivity to a particular input combination. You cannot use a case statement for a PLA with "don't cares" in the input field since a comparison with a value that is not '0' or '1' will return FALSE in a case condition ( as opposed to just ignoring the inout ). Instead, a small procedure or function is needed that explicitly defines comparisons to 'X'. The following example describes such a procedure. First a two-dimensional PLA type is declared.

   type std_logic_pla is array ( natural range <>, natural range <> ) of std_logic;

                   .....

   procedure pla_table ( constant invec : std_logic_vector;

           signal outvec : out std_logic_vector ;

           constant table : std_logic_pla ) is 

           variable x : std_logic_vector (table'range( 1 ) ;
 

           variable y : std_logic_vector (outvec'range ) ; -- outputs

           variable b : std_logic ;
           ); 

           begin 

                   assert ( invec'length + outvec'length = table'length(2 ))

                   report "Size of Inputs and Outputs do not match tablesize "

                   severity ERROR ;

                           -- Calculate the AND plane.

                           x := ( others => '1' ) ;

                   for I in table'range( 1 ) loop 

                           for j in invec'range loop 

                                   b := table ( I, table'left(2)-invec'left + j ) ;

                                   if ( b= '1') then 

                                           x( I ) := x( I ) AND invec( j ) ;

                                   elsif ( b = '0' ) then 

                                           x( I ) := x( I ) AND NOT invec( j ) ;

                                   end if ; 

                             -- If b is not '0' or '1' (i.e. Don't Care ) product line
                             -- is insensitive to invec( j ).

                           end loop ;

                   end loop ; 

                            -- Calculate the OR plane.

           y := ( others => '0' ) ;

           for I in table'range( 1 ) loop 

                   for j in outvec'range loop 

                            b := table (I, table'right(2)-outvec'right + j ) ;

                            if ( b = '1') then 

                                    y( j ) := y( j ) OR x ( I ) ;

                            end if; 

                   end loop ; 

           end loop ; 

                   outvec <= y ;

     end pla_table ;

Once the two-dimensional array type and the pla procedure are defined, it is easy to generate and use PLAs and ROMs. What follows is a simple example of a PLA decoder that returns the position of the first '1' in an array. The PLA has 5 product line ( the first dimension ) and 7 I/Os ( 4 inputs, 3 outputs, the second dimension ).

   constant pos_of_first_one : std_logic_pla ( 4 downto 0, 6 downto 0 ) :=

           ("1---000", -- first '1' is at position 0

            "01--001", -- first '1' is at position 1

            "001-010", -- first '1' is at position 2

            "0001011", -- first '1' is at position 3

            "0000111" ) ; -- there is not '1' in the input

   signal test_vector : std_logic_vector ( 3 downto 0 ) ;

   signal result_vector : std_logic_vector ( 2 downto 0 );

                   .....

           -- Now use the PLA table procedure with PLA pos_of_first_one

           -- test_vector is the input of the PLA, result_vector is the output.

                   .....

   pla_table ( test_vector, result_vector, pos_of_first_one) ;

The PLA could have been defined in an array-of-array type as well. just as the ROM described above. A procedure or function for the PLA description will always be necessary to resolve the don't care information in the PLA input field. The above is a generic way of producing a ROM or a PLA it is universal, but it has the drawback of becoming quite large. If it becomes too inefficient from a logic and real estate perspective, consider using a technology specific solution by directly instantiating a ROM or PLA component in the VHDL description. Many FPGA and ASIC vendors supply ROM and/or PLA modules in their libraries for this purpose.

Return to top of document.


State Machines.

A state machine is a sequential circuit that advances through a number of states. To describe a state machine in VHDL, you can declare an Enumeration Type for the states, and use a Process Statement for the state register and the next-state logic. There are many ways to describe a state machine in VHDL. What follows is one commonly used method. The possible states of a state machine are listed as an enumerated type. A signal of this type ( present_state ) defines in which state the state machine appears. In a case statement of one process, a second signal ( next_state ) is updated depending upon present_state and the inputs. In the same case statement, the outputs are also updated. Another process updates present_state with next_state on a clock edge, and takes care of the state machine reset.

The VHDL example shown below implements a 2-state state machine.

State Machine Example #1.

   entity state_machine is 

           port(

                   clk : in bit ;

                   input : in bit ;

                   output : out bit );

   end state_machine;

   architecture a of state_machine is

           type state_type is (s0, s1);

           signal state : state_type;

   begin 

           process 

           begin 

           wait until clk = '1';

                           case state is 

                                 when s0=>

                                        state <= s1;

                                 when s1=>
                                           if input = '1' then 

                                                   state <= s0;

                                           else 

                                                   state <= s1;
                                           end if;
                           end case;
            end process ; 
           output <= '1' when state = s1 else '0';
   end a;

This state machine includes a Process Statement that is activated on every positive edge of the clk control signal. The signal state stores the current state of the state machine. The declaration of the type STATE_TYPE defines the states s0 and s1 for state_machine.

At start-up, the state machine is initialized to the first state in the Type Declaration. Otherwise, the Case Statement defines the transitions between the states, i.e., determines which state to enter on the next rising edge of clk. The VHDL will recognize state machines and synthesize them as such only if all of the following conditions are met:

  • The Process Statement that describes the state machine must be clocked, and contain a Wait Statement that is either explicit or implied by a sensitivity list. The state machine behavior, i.e., the next-state logic, is defined with Case Statements at the top level.
  • All assignments to the signal or variable that represents the state machine are within the process. No If Statement that is a function of the state variable can assign to the state variable. Case Statements should be used instead. Example VHDL state machines that do not meet these conditions are converted into logic gates and registers that are not listed as state machines in the Report File.
  • The following is a state machine that controls the refresh circuitry for a Dynamic RAM (DRAM), this design generates the Row Address Strobe (RAS) and the Column Address Strobe (CAS) that preserves the data stored in the volatile DRAM memory cells. This is a Moore State Machine

Return to top of document.

Dram Refresh Controller State Machine, Example # 2.

   entity refresh_controller is 

           port ( clk, cs, refresh, reset : in bit ;

                           ras, cas, ready : out bit ) ; 

   end refresh_controller ; 

   architecture vhdl_example of refresh_controller is 

                   -- Define all of the states of the state machine. 

           type state_type is ( s0, s1, s2, s3, s4 ) ;

                   signal present_state, next_state : state_type ; 

           begin 

                           -- process to update the present state 

                   if ( reset = '1' ) then 

                           present_state <= s0 ; 

                   elsif clk'event and clk = '1' ; 

                           present_state <= next_state ; 

                   end if ; 

           end process ; 

   transitions : process ( present_state, refresh, cs) 

           begin 

                   -- process to calculate the next state and it's
                   -- associated outputs. 

                   case present_state is 

                           when s0 => 

                                   ras <= '1' ; cas <= '1' ; ready <= '1' ; 

                                   if ( refresh = '1' ) then 

                                           next_state <= s3 ; 

                                   elsif ( cs = '1' ) then 

                                           next_state <= s1 ; 

                                   else 

                                           next_state <= s0 ; 

                                   end if ; 

                           when s1 => 

                                   ras <= '0' ; cas <= '1' ; ready <= '0' ; 

                                   next_state <= s2 ; 

                           when s2 => 

                                   ras <= '0' ; cas <= '0' ; ready <= '0' ; 

                                   if ( cs = '0' ) then 

                                           next_state <= s0 ; 

                                   else 

                                           next_state <= s2 ; 

                                   end if 

                           when s3 => 

                                   ras <= '1' ; cas <= '0' ; ready <= '0' ; 

                                   next_state <= s4 ; 

                           when s4 => 

                                   ras <= '0' ; cas <= '0' ; ready <= '0' ; 

                                   next_state <= s0 ; 
                   end case ; 
           end process ; 
   end vhdl_example ;

Return to top of document.

The present_state signal is the example above is a user defined enumerated type. There are five state values in this type: s0, s1, s2, s3, and s4. This means that for logic synthesis, the state assignment has to be done on a number of state bits to represent each of these states ( if this state machine were One-Hot encoded, then the operation would only occur on one bit). Many VHDL compilers will default to binary state encoding. This means that three state bits are required to encode the 5 states. Encoding is done on based on the definition of the state type. In the DRAM refresh example, the following state values were assigned to each state:

           State :State-bit. 

              s0 : 0 0 0 

              s1 : 0 0 1 

              s2 : 0 1 0 

              s3 : 0 1 1 

              s4 : 1 0 0 

If you require different binary encoding values for each of the states, you have to change the order of the enumerated values.


State Machine Coding Styles

In the above example, a case statement has been used to test the conditions of the present_state. A case statement is more efficient than a if-then-else type of statement. An if-then-else statement would result in a priority encoder being synthesized to test each state, and a priority encoder would be more costly in terms of logic to build.

It is also important to note the in the above case statement there is no OTHERS section included at the end. An OTHERS entry could create extra logic if not all the states are mentioned in the case statement. This extra logic will have to determine if the state machine is in any of the already defined states or not.

In many cases the others generates more logic than is needed, but in others it can be beneficial. In real life a state machine can jump to an undefined state due to glitches on the clock or the inputs, etc. and get stuck there. An OTHERS statement can be useful to allow the machine to gracefully recover from an undefined state and return to a known one. Preferably to reset itself back to state 0.

Another issue to be aware of in VHDL, is the assignments to the outputs and next_state in the state transition process. VHDL defines that any signal that is not assigned anything should retain its value. This means that if you forget to assign something to an output ( or next_state )

Note: Unless you are confident of the target technology of your code, it is impossible to know in which state your state machine will power up in. The state machine could power up in a state that is not even defined in your code given the metastability of the flip-flops in the targeted device. Therefore, it is very important to provide a reset input to your state machine.

Return to top of document.


Manual State Assignments.

You can override the VHDL Compiler's automatic state assignments and specify your own state assignments with the ENUM_ENCODING attribute. The ENUM_ENCODING attribute must follow the associated Type Declaration. This attribute is defined in the IEEE 1164 version of VHDL but is not always available with all VHDL tools.

In the following example, the ENUM_ENCODING attribute is defined and declared:

   library ieee;

                   use ieee.std_logic_1164.all;

           entity enum_st_machine is 

           port 

                   updown : in std_logic;

                   clock : in std_logic;

                   lsb : out std_logic;

                   msb : out std_logic

                 );

           end enum_st_machine;

   architecture first_example of enum_st_machine is 

           type count_state is (zero, one, two, three);

           attribute enum_encoding : string;

           attribute enum_encoding of count_state : TYPE IS "1 1 0 1 1 0 0 0";

                   signal present_state, next_state : count_state;

           begin 
           process (present_state, updown)
                   begin 

                           case present_state is 

                                   when zero =>

                                           if (updown = '0') then 

                                                   next_state <= one;

                                                   lsb <= '0';

                                                   msb <= '0';

                                           else 

                                                   next_state <= three;

                                                   lsb <= '1';

                                                   msb <= '1';

                                           end if ; 

                                   when one =>

                                           if (updown = '0') then 

                                                   next_state <= two;

                                                   lsb <= '1';

                                                   msb <= '0';

                                           else 

                                                   next_state <= zero;

                                                   lsb <= '0';

                                                   msb <= '0';

                                           end if ; 

                                   when two =>

                                           if (updown = '0') then 

                                                   next_state <= three;

                                                   lsb <= '0';

                                                   msb <= '1';

                                           else 

                                                   next_state <= one;

                                                   lsb <= '1';

                                                   msb <= '0';

                                           end if;

                                   when three =>

                                           if (updown = '0') then 

                                                   next_state <= zero;

                                                   lsb <= '1';

                                                   msb <= '1';

                                           else 

                                                   next_state <= two;

                                                   lsb <= '0';

                                                   msb <= '1';
                                           end if ; 
                                   end case ; 
                           end process ; 
          process 
          begin 

                  wait until clock'event and clock = '1';

                           present_state <= next_state;

           end process ; 
   end first_example ;

The ENUM_ENCODING attribute must be a STRING literal that contains a series of state assignments. These state assignments are the constant values that correspond to the state names in the Enumerated Type Declaration. In this example, the states are encoded with the following values:

           zero  = "11"

           one   = "01"

           two   = "10"

           three = "00"

These states could be assigned to code in any particular sequence that the designer wishes, i.e. binary coding, gray coding, etc. These states could also be assigned any name that has significance to the design.

Return to top of document.


State Machines - Mealy vs. Moore

There are various issues of coding style for state machines that affect the performance of the final result. The big question is what form of state machine will be created.

There are basically two forms of state machines, Mealy machines and Moore Machines. In a Moore machine, the outputs do not directly depend on the inputs, on the present state. In a Mealy machine, the outputs depend directly on the present state and the inputs as well.

In the DRAM refresh state machine described in the previous section ( Jump to DRAM Example.), the outputs ras, cas and read only depend on the value of present_state. This means that the VHDL description implements a Moore machine.

If the outputs were set to different values under the input conditions in the "if" statements inside the case statements, a Mealy machine would have been created. In a Moore machine, there is always a register between the inputs and the outputs. This does not have to be the case in Mealy machines.

Example, Mealy State Machine.

   entity mealy_machine is 
           port(

                   clk : in bit ;

                   input1, input2, input3 : in bit ;

                   output1, output2 : out bit);

           end mealy_machine;

   architecture a of mealy_machine is
           type state_type is (s0, s1, s3, s4);

                   signal state : state_type;

           begin 
           process 
                   begin 
                           wait until clk = '1';
                           case state is 

                                  when s0=>

                                          if input1 = '0' then 
                                               next_state <= s0 ;
                                               output1 <= '0', output2 <= '0' ;

                                          elsif input1 = '1' then 
                                                next_state <= s1 ;
                                                output1 <= '1', output2 <= '0' ;

                                          end if; 
                                  when s1=>

                                          if input2= '0' then 
                                                 next_state <= s2 ;
                                                 output1 <= '0', output2 <= '1 ;

                                          elsif input1 = '1' then 
                                                  next_state <= s3 ;
                                                  output1 <= '1', output2 <= '1 ;

                                  when s2=>

                                          if input3 = '0' then 
                                                  next_state <= s0 ;
                                                  output1 <= '0', output2 <= '1 ;

                                          elsif input3 = '1' then 
                                                  next_state <= s3 ;
                                                  output1 <= '1', output2 <= '1 ;

                                  when s3=>

                                          if ( input2= '0' and input 1 = '0' ) then
                                                   next_state <= s0 ;
                                                   output1 <= '0', output2 <= '0 ;

                                          else 

                                                    next_state <= s3 ;
                                                    output1 <= '1', output2 <= '1 ;
                                          end if;
                           end case;
           end process ; 
   end a;

Return to top of document.


State Machine - One Hot Encoding

A One Hot State Machine is one which assigns one flip-flop per state. In VHDL State Machines , where the state signal is of an enumerated type, will get one flip-flop per enumeration value ( state ). Only one bit is '1' at any time in such a state machine. This strategy often improves the speed of the synthesized circuit.

One hot encoding has an additional benefit when implementing state machines in FPGA's. Most state machines use a fair amount of combinational logic to decode next state information. By using the one hot method, it reduces the amount of combinational logic and instead uses flip-flops which require very little real estate and routing resources in an FPGA.

The following example is a one-hot implementation of the DRAM Refresh State Machine previously shown.

   entity refresh_one_hot is 

           port ( clk, cs, refresh, reset : in bit ;
                   ras, cas, ready : out bit ) ; 

   end refresh_one_hot ; 

   archibtecture vhdl_example of refresh_one_hot is
                   -- Define all of the states of the state machine. 
           subtype state_type is bit_vector ( 0 to4 ) ; 
                   constant s0 : integer := 0 ; 
                   constant s1 : integer := 0 ; 
                   constant s2 : integer := 0 ; 
                   constant s3 : integer := 0 ; 
                   constant s4 : integer := 0 ; 
                   signal present_state, next_state : state_type ; 
           begin 
                   register : process ( clk, reset ) 

                   begin 
                           if ( reset = '1' ) then -- initialize to state0 

                                   present_state <= ( s0 => '1' , others => '0' )
                           elsif ( clk'event and clk = '1' ) then 

                                   present_state <= next_state ; 

                           end if ; 
                   end process ; 

           transitions : process ( refresh, cs, present_state) 
           begin 
                           -- process to calculate the next state and it's associated outputs. 
                   next_state <= ( others => '0' ) ; 

                   if ( present_state ( s0 ) = '1' then -- State0 
                            ras <= '1' ; cas <= '1' ; ready <= '1' ; 

                            if ( refresh = '1' ) then 
                                   next_state <= (s3 ) <= '1'; 

                            elsif ( cs = '1' ) then 
                                    next_state <= ( s1) <= 1 ; 

                            else 
                                      -- refresh = '0' 
                                   next_state <= s0 <= '1' ; 

                            end if ; 
                   end if ; 

                   if ( present_state ( s1 ) = '1' then -- State1 

                            ras <= '0' ; cas <= '1' ; ready <= '0' ; 
                            next_state <= ( s2 ) <= '1' ; 
                   end if ; 

                   if ( present_state ( s2 ) = '1' then -- State2 
                            ras <= '0' ; cas <= '0' ; ready <= '0' ; 

                            if ( cs = '0' ) then 
                                    next_state <= ( s0 ) <= '1' ; 
                            else -- cs = '1' 
                                    next_state <= ( s2 ) <= '1' ; 
                            end if 
                   end if ; 

                   if ( present_state ( s3 ) = '1' then -- State3 

                            ras <= '1' ; cas <= '0' ; ready <= '0' ; 
                            next_state <= s4 <= '1' ; 
                   end if ; 

                   if ( present_state ( s4 ) = '1' then -- State4 

                            ras <= '0' ; cas <= '0' ; ready <= '0' ; 
                            next_state <= s0 <= '1' ; 
                   end if ; 
           end process ; 
   end vhdl_example ; 

Care must be taken with the code in order to manually force the VHDL compiler into creating a one-hot state machine. If the proper approach is not used, a plain Mealy or Moore Machine will result and logic real estate may be squandered as a result. Remember, a One-hot has only one state bit changing at a time, so some ideas to keep in mind from the above example are:

  • The case statement should not be used of state comparisons, since the state comparison has to depend on only one bit of the state vector. present_state used with a case statement can only compare the whole vector.
  • the elsif construct should not be used to do the state comparisons, since it introduces additional constraints on the values of each state. By using elsif it means that this section of code is only entered if all the previous conditions are false. In the case of one-hot encoding, you can be certain that all of the previous conditions are already false.
  • It is essential to assign the next_state to zero before the state transitions are generated. Since only a single bit of next_state will be set in the transition process, combinational loops would have to be generated to preserve the other bits if this initial assignment is not made, which would consume more logic.

This state machine description works fine, as long as the machine can never appear in a state with more than one '1' in the state vector. In order to assure that condition never happens, a one-hot state machine definitely requires a power up reset.

As this example shows, changing an existing enumerated type encoded state machine into a one-hot type in VHDL is not a trivial process when done manually. Some "high end" VHDL synthesis tools contain special library elements and function calls that will allow the user to automatically force the compiler to implement the state machine as a one-hot.

Return to top of document.




Go to VHDL Document #3, Tips & Tricks - Coding Examples.