VHDL for Hardware Design

VHDL is a hardware-description language that frees you from low-level details. Phil uses VHDL to design an interface between a PC's parallel-printer interface and a high-speed data-acquisition system that can generate several million 8-bit data samples per second.


June 01, 1996
URL:http://www.drdobbs.com/architecture-and-design/vhdl-for-hardware-design/184409899

Figure 1

Figure 2

JUN96: VHDL for Hardware Design

VHDL for Hardware Design

The softening of hardware design

Phil Tomson

Phil is a senior software engineer with Cypress Semiconductor. He can be reached at [email protected].


While software engineers have been experimenting with visual-programming methods, hardware engineers have been moving away from graphical schematics, toward language-based methodologies that resemble the traditional software-design approach. This shift was sparked by the development of logic-synthesis tools (see the accompanying text box entitled "Logic Optimization and Synthesis") and hardware-description languages (HDLs) such as VHDL and Verilog.

HDLs allow hardware designs to be described at high levels of abstraction, freeing the designer from details that were inherent in the schematic-based method. The move from the schematic-based approach to HDLs in hardware design is comparable to the move from assembly languages to high-level languages in the software domain. Just as adding two floating-point numbers in a high-level language is trivial compared to performing the same operation in assembly language, using a "+" sign in an HDL is much easier than, say, drawing all the logic gates involved in an adder. This freedom from low-level details has generally made hardware engineers more productive.

A History of Acronyms

The Department of Defense (DoD) developed VHDL, short for "VHSIC Hardware Description Language;" (VHSIC is short for "Very High Speed Integrated Circuit") in the early 1980s in cooperation with IBM, Texas Instruments, and Intermetrics. The DoD wanted a standard HDL that all of its vendors could use when submitting designs for defense contracts. This would allow designs to be portable and easy to reuse, as well as provide a standard form of documentation. The language became an IEEE standard in 1987 known as "IEEE Std 1076-1987," and was updated in 1993 to "IEEE Std 1076-1993."

VHDL was derived from Ada and is similar in syntax and structure. Support for design hierarchy is provided by the LIBRARY and USE statements used in conjunction with Ada's PACKAGE concept.

VHDL Design Example

The best way to understand VHDL is through a practical design example that illustrates the language's basic concepts. My example includes an interface between the PC's parallel printer interface and some type of high-speed data-acquisition system that can generate several million 8-bit data samples per second. It is actually part of a larger design I intend to use in a PC-hosted, 8-channel logic analyzer.

On the PC side, the printer interface is actually composed of three separate ports, each with its own I/O address. The output data port is 8 bits wide and accessed by writing data to I/O address 378H. The status port is 5 bits wide and read only. It is accessed by reading I/O address 379H. The 4-bit control port is an output port accessed by writing to address 37AH.

The problem to overcome in this example design is the sample-rate limitation of the PC's parallel port. Several million 8-bit samples per second will certainly be overwhelming, especially since each sample requires two reads of the status port, because only 5 bits of input are available in this interface. The solution is to use an external, high-speed, dual-port static random-access memory (SRAM) to buffer the sampled data; see the block diagram in Figure 1. An SRAM with 20-ns cycle times can acquire up to 50 million samples per second. After the acquired data is stored in the SRAM, it can be read at a leisurely pace through the PC's printer interface, which is capable of reading, at best, a few hundred-thousand bytes per second.

As Figure 1 illustrates, the design will need the following components to interface to the SRAM:

The SRAM interface has been defined, but the design still needs some mechanism for indicating to the data-acquisition module that it is free to begin taking samples and writing data into the SRAM. To do this, the arbitration logic block (see Figure 1) will generate a signal called free.

There are four outputs available from the control port of the printer interface used to perform various functions in the design. The address counter can either be loaded with a value from the output port or incremented. Two signals, strobe and load, indicate which function to perform. When load is active (1), the address counter is loaded with data on the rising edge (the 0 to 1 transition) of strobe; otherwise, the counter increments on the rising edge of strobe. The address counter has 16 bits, but only 8 bits can be written from the printer interface; a third signal from the control port, upper/lower, determines which part of the address counter gets loaded. One control port signal remains, which will be used as a read/write signal to the SRAM: When writing to the SRAM, this signal will allow data out of the tristate buffers; otherwise these buffers will be in the high-impedance state (Z), thus releasing the bus.

All four outputs of the control port have been used, but some method of switching between the upper and lower four bits of the eight-bit sample data from the SRAM is still needed. The upper/lower signal can be used for this without conflicting with its use in loading the address counter.

VHDL Design Implementation

Listing One is the VHDL implementation of this design. The LIBRARY ieee declaration tells the VHDL compiler about a library called ieee and makes it visible within the description that follows. Most VHDL tools (whether logic synthesizers or simulators) have a built-in, IEEE-defined library. The USE declaration works like its counterpart in Ada or like #include in C; it tells the VHDL tool where to look for packages that contain functions or types that may be needed in the following code. The std_logic_1164 package called for in the first USE statement contains the std_logic type our design uses. Type std_logic represents a nine-valued logic system. Signals (which represent wires in a circuit) of type std_logic can take on any of the nine values defined in the std_logic type. In my example, only three of the nine std_logic values are used: 0 (logical 0), 1 (logical 1), and Z (the tristate value).

Next comes the ENTITY declaration. This is where the design's external interface is defined. Input and output signals that go to, or are generated by, the design are declared in the PORT list. Each pin declared in the port list has a direction and type. When the design is mapped into a device, these signals will represent the actual I/O pins. The first pin declared in the PORT list is r_wn. The direction of r_wn is IN, meaning that this is an input pin, and is of type std_logic.

Further down in the PORT list, we find a declaration for data that has a direction of INOUT and type std_logic_vector. Vectors represent collections of signals that are somehow related, such as a data bus. The std_logic_vector type is defined in the std_logic_1164 package as an array of type std_logic. In this case, you are declaring an 8-bit data bus. The INOUT indicates that this is a bidirectional bus used to pass data to, or read data from, the external SRAM. The other buses in the design are wr_data, 8-bit data from the printer interface's output data port; rd_data, 4-bit data that goes back to the printer interface's status port; and addr_out, 16-bit address bus to the SRAM.

The signal free has a direction of BUFFER. When a port output signal needs to be referenced internally, the BUFFER direction is used. This allows the value of free to be visible inside of the design, and also allows its value to appear on the external free pin.

After the ENTITY declaration comes the ARCHITECTURE declaration. This is where the entity's internal behavior is described. Notice that there are some signal declarations before the BEGIN statement. These are signals internal to the architecture and will not appear on any external pins of the device. In this case, you are declaring the 16-bit address counter as an internal vector of signals. Notice, also, that there are two ALIAS declarations here. These allow you to refer to the upper eight bits of the address counter as addr_counter_hi, and the lower eight bits of the address counter as addr_counter_lo; this makes the code more readable.

Statements between the architecture's BEGIN and END statements are handled very differently from the way they would be in typical programming languages. Since you are modeling a piece of hardware in which there can be a large degree of parallelism, all statements in this section are executed concurrently ("executed" in this context refers to the way a VHDL simulator or synthesis tool would interpret them, since VHDL descriptions are not compiled into stand-alone executables). This important point separates HDLs from normal sequential programming languages. A hardware design consists of a collection of logic gates (AND, OR, XOR, and the like), each of which processes data from its inputs and passes on results to its outputs. It is best to describe such a system concurrently as opposed to sequentially; hence, the reason for concurrent statements in HDLs.

The first concurrent signal assignment, rdn_out <= NOT r_wn, means that the inverted value of r_wn gets assigned to rdn_out, which is the active low read signal to the SRAM. The next statement is a conditional signal assignment to rd_data that implements a multiplexer where either the upper or lower four bits of data from the SRAM are selected, depending on the value of the signal u_ln. A Conditional signal assignment is also used for the data bus, but in this case, it is being used to implement a tristate-able bus instead of a multiplexer. In the data bus, when the signal r_wn is 1, indicating that we are reading data from the SRAM, the tristate value, Z, is assigned to data that releases the bus and allows the SRAM to output data without conflict. (The OTHERS clause is used to assign a specified value to each signal in a vector of signals.) Since these signal assignments are concurrent statements, they could have been placed in any order in the body of the design description.

There is a case where statements are executed sequentially in VHDL just as they would be in most programming languages. This occurs within a PROCESS. The example design has two processes: address_ counter and arbitrate. The statements within a process are executed when signals within the sensitivity list change value. The sensitivity list comes after the PROCESS statement and is a list of signals between parentheses. In the address_counter process, the sensitivity list contains the signals strobe and reset. Whenever the value on strobe or reset changes, the address_ counter process will execute. Processes are usually used to model memory elements--such as flip-flops or latches--or state machines.

In our example, the address process implements the address counter for our design. If the reset signal is 1, all outputs of the internal address counter, represented by the vector signal addr_counter, are assigned the value 0. The statement ELSIF (strobe''vent AND strobe='1') means that the rest of the actions that are to take place within the address process can only happen if there is a change in the value of the strobe signal and that change makes the value of strobe '1'. (When the 'event attribute is applied to a signal, the Boolean value True is returned if the signal has changed value.) This defines strobe as the rising-edge clock for a synchronous, loadable counter with an asynchronous reset.

The arbitrate process has the same sensitivity list at the address_counter process. When reset is 1, the free signal is assigned the value 0. When free is 1, it tells the data-acquisition equipment that it can now take samples and write to the SRAM. We reset the value of free to 0 so that our device initially has control of the SRAM. Since there are no remaining, unused control-port signals, we need to come up with another method of controlling the free signal. We do this in the arbitrate process by checking the values of the addr_cntr, wr_data, load, and r_wn signals on the rising edge of the strobe signal. If load is 1, (indicating that we are loading data into the address counter), r_wn is 1 (indicating that the bidirectional data bus is tristated), and all the individual signals of the wr_data and addr_counter buses are 1, then the value of free is toggled. Since free starts out in the inactive state after a reset, we have to first load FFH into addr_counter_lo and addr_counter_hi and then (while keeping load 1 and wr_data FFH) toggle strobe from 0 to 1 while rd_wn is high in order to get free to become active (1). free will then remain active until strobe is again toggled from 0 to 1. During the time that free is active, the PC program that controls the interface will need to monitor the busy signal from the data-acquisition module. When the module is no longer busy, free can easily be toggled back to its initial inactive state (0) by making the strobe signal go from 0 to 1 again. Though this method for controlling the free signal might seem somewhat arbitrary, it does not cause any loss of access to any SRAM locations; if you want to read data from address FFFFH, it is still possible if the load signal is inactive (0).

Simulation

When the VHDL-design specification is complete, simulation gives you a way of ensuring that it is functionally correct. Use a VHDL simulator that will allow you to specify input values to the model and check the outputs for correct behavior.

The waveforms in Figure 2 show the results of simulating the example design. (In this case, I used the V-System VHDL simulator from Model Technology of Beaverton, OR.) After the power-on reset is finished, the address counter is loaded with FFH. In the next strobe cycle, the free signal becomes active until the fifth strobe cycle toggles free back to 0. strobe cycles 6 through 9 simulate the reading of data from the SRAM. The values 70H, 71H, 71H, and 73H are read from locations 0000 through 0003. The signal u_ln is toggled with strobe during these cycles so that the value on rd_data is multiplexed between the upper and lower nibble of data from the SRAM for each location being read. In the last two cycles, the values 45H and 46H are written to locations 0004 and 0005, respectively.

After the design is validated by simulation, it can then be taken through the logic synthesis process. After synthesis, the design is simulated again to check timing and to ensure that functionality is still correct.

Conclusion

While the design example presented here certainly does not contain all possible VHDL constructs, it illustrates the major concepts involved in designing hardware with the language.

The use of HDLs in conjunction with simulation and logic synthesis provides a powerful design methodology that is becoming popular in the hardware-engineering community. In many ways, the HDL design methodology blurs the distinction between hardware and software design; software engineers are often employed to create HDL models, because it is a methodology they can easily relate to.

References

Bhasker, Jayaram. A VHDL Primer, Englewood Cliffs, NJ: Prentice Hall, 1992.

Gadre, Dhananjay. "Parallel-printer Adapters Find New Use as Instrument Interfaces." EDN (June 1995).

Logic Optimization and Synthesis

Since logic gates take up real estate in devices, and since there is a direct correlation between chip size and cost, there has long been the need to ensure that logic equations are minimal. Originally, the process of Boolean minimization was done "manually" with "hand tools" such as Karnaugh maps and state tables. As digital designs grew, this manual logic-minimization process became a headache. This led to the development of automated logic-optimization software tools such as Espresso, from the University of California, Berkeley. These tools use many different types of algorithms and heuristics to minimize Boolean logic functions.

The term "logic synthesis" is applied to the whole process of converting a design specification into a form that can ultimately be realized as an actual piece of hardware--the target device. While the steps of this process and the terminology involved vary from one vendor to another, they usually follow this flow:

    1. Design compilation. Taking the user's source code and converting it into an internal format that describes the Boolean equations that represent the design intent. Syntax is also checked in this step.

    2. Logic optimization. Minimizing the equations from step 1.

    3. Device fitting or technology mapping. Minimized equations from step 2 are mapped into the target device's architecture.

The third step (technology mapping) allows the synthesis tool to efficiently target designs to many different types of devices. A gate array may have what is known as a "sea-of-gates" architecture (a two-dimensional array or grid of logic gates) in which the basic cell is a two-input NAND gate, while a PLD's (programmable logic device) basic cell might be quite complex and would include a flip-flop and perhaps several multiplexers or logic gates. What might be a minimal realization of the design in a fine-grained gate-array architecture may not be minimal in a PLD and vice versa. The technology mapper (or "fitter") is supplied with information about the target device's internal structure and is able to determine an efficient implementation based on this information.

After technology mapping, more processing may be needed before the design can be realized in an actual hardware device. For instance, if the target device is a gate array, logic elements will need to be placed on a two-dimensional grid, and signal wires will need to be routed between them. This is generally not considered part of the logic synthesis process, however, and it is not needed for PLDs.

--P.T.

Figure 1: Block diagram for the example design.

Figure 2: Form results of simulating the example design.

Listing One

--parallel port bidirectional interface
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE work.std_arith.all ; -- overloaded '+' is from this package
ENTITY port_con IS
   PORT ( --pins defined here
    r_wn    : IN std_logic ; --read when 1/write when 0
    u_ln    : IN std_logic ; --upper when 1/lower when 0
    strobe  : IN std_logic ; --strobe
    load    : IN std_logic ; --load 
    busy    : IN std_logic ; --data acq device controls bus when active
    reset   : IN std_logic ;
    wr_data     : IN std_logic_vector(7 DOWNTO 0);--data from PC 
    data    : INOUT std_logic_vector(7 DOWNTO 0);--data in/out of RAM
    rdn_out,wrn_out: OUT std_logic ;--read/write signals out to RAM
    free    : BUFFER std_logic ;--tells acq device that it can write data
    addr_out    : OUT std_logic_vector(15 DOWNTO 0);--address to RAM
    rd_data     : OUT std_logic_vector(3 DOWNTO 0)--4bits data to status port
   );
END port_con;
ARCHITECTURE behave OF port_con IS
SIGNAL addr_counter : std_logic_vector(15 DOWNTO 0);
ALIAS  addr_counter_lo : std_logic_vector(7 DOWNTO 0) 
       IS addr_counter(7 DOWNTO 0);
ALIAS  addr_counter_hi : std_logic_vector(7 DOWNTO 0) 
       IS addr_counter(15 DOWNTO 8);
BEGIN
    ------------concurrent statements-------------
    rdn_out <= NOT r_wn ;
    rd_data <= data(3 DOWNTO 0) WHEN (u_ln='0') ELSE
           data(7 DOWNTO 4);--implements 8:4 bit mux on data bus
    wrn_out <= r_wn ;
    data <= wr_data WHEN (r_wn = '0') ELSE
             (others => 'Z') ;  --implements tristate buffer
    addr_out <= addr_counter; 
    -------------------------------------------
    address_counter : PROCESS (strobe, reset)
    BEGIN
    ---seqentially executed statements here----
    IF (reset = '1') THEN
      addr_counter <= (OTHERS=>'0');
    ELSIF (strobe'event AND strobe='1') THEN
      IF (load='1' AND u_ln ='0') THEN --load lower 8bits of addr count
        addr_counter_lo <= wr_data; 
      ELSIF (load='1' AND u_ln='1') THEN--load upper 8bits of addr count
        addr_counter_hi <= wr_data;
      ELSE
        addr_counter <= addr_counter+1;--increment address counter
      END IF;
    END IF;
    END PROCESS address_counter;
    arbitrate : PROCESS (strobe, reset)
    BEGIN
      IF (reset = '1' ) THEN
        free <= '0' ;
      ELSIF(strobe'event AND strobe='1') THEN
        IF(load = '1' AND wr_data = "11111111" AND 
        addr_counter = "1111111111111111" AND 
        r_wn = '1' ) THEN --toggle free
        free <= NOT free ;--this is why free direction is BUFFER
        END IF; 
      END IF;
    END PROCESS arbitrate ;
END behave ;

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.