Updated version of an article originally published in Personal Engineering and Instrumentation News, January 1997, pages 67-70. (download original in Adobat Acrobat format)
What you see isn�t always what you get. This clich� applies particularly to digital design with hardware description languages (HDLs). VHDL and Verilog offer tremendous abstraction and productivity benefits. However, just as with any good power tool, HDLs present some potential dangers. Used inappropriately, a power saw could just as easily rip through your arm as through a sheet of plywood. Likewise, HDLs provide previously undreamed of productivity� but used incorrectly, they promise a hellish design experience.Think back to the days when you began programming or drawing schematics. That first program or chip was probably much slower and far bigger than intended. With experience, though, your design techniques improved. Using HDLs follows a similar path: first, learn the tool�s capabilities and limitations, then recognize that HDLs aren�t a panacea. They can�t transform a slow, inefficient design into a model of perfection just as a power saw won�t make you a master carpenter. Drawing from my experience, this column addresses some issues to be aware of when using VHDL to design programmable logic.
One interesting trap beginners typically fall into is allowing unintended logic to creep into designs. For instance, it�s possible to inadvertently specify a latch in VHDL source code. While not a catastrophe, these “inferred” latches consume valuable real estate. A simple example demonstrates the concept.Assume a multiplexer selects between three input signals (A, B and C) using two select lines (SEL1 and SEL0) as in Fig 1a. The VHDL code to specify the multiplexer might look something like the text in Fig 1b. The code functionally simulates correctly, and the logic performs as you might expect. However, when synthesizing the design, software might issue a warning indicating that it generated a latch, probably caused by a missing assignment in an IF or a CASE statement. What exactly does this mean?
library IEEE; use IEEE.std_logic_1164.all; entity inferred_latch is port (A, B, C: in STD_LOGIC; SEL : in STD_LOGIC_VECTOR(1 downto 0); MUX : out STD_LOGIC); end inferred_latch; architecture BEHAV of inferred_latch is begin process (SEL, A, B, C) begin if (SEL = "00") then MUX <= A; elseif (SEL = "01") then MUX <= B; elseif (SEL = "10") then MUX <= C; end if; end process; end BEHAV;
Figure 1: A 3-input multiplexer function in classic form (a) follows the intuitive structure and datasheet view a designer experiences with schematic-based design. The VHDL source code (b) that implements the 3-input multiplexer implicitly creates a latch unless the designer specifies a default assignment.Examine the design in Fig 1a more closely. What happens when SEL1 and SEL0 are both asserted High? The explicit VHDL code indicates that nothing happens. VHDL implicitly uses a latch to retain a prior state if there isn�t an explicit transition, which means that whatever appears on the MUX output remains there as long as both SEL1 and SEL0 are High. How would a logic synthesizer resolve this logic physically? It would use a gated latch.
Without closely examining the resulting netlist, this extra logic might go unnoticed. However, during place and route you might note that the design consumes a bit more logic than expected. Some of the better logic-synthesis packages provide a schematic viewer to display the resulting synthesized design. For example, Synplicity�s (Sunnyvale, CA) Synplify offers an optional design viewer, HDL Analyst, that provides both register-transfer-level (RTL) and technology mapping views of a VHDL or Verilog source file.
Fig 2a shows HDL Analyst�s RTL view for the design of Fig 1b. It contains mostly gate-level primitives and provides a look at how the synthesis software interprets the HDL source code. Fig 2b shows the corresponding technology view for the same design but indicates how the logic maps into functions available on the target device�in this case, a Xilinx XC4000E FPGA. The blocks labeled
FMAP...in Fig 2b represent lookup tables (LUTs) in a Xilinx XC4000E.
Note the latch primitive in Fig 2a. The resulting implementation uses the XC4000E�s level-sensitive RAM element to build it. Consequently, the code in Fig 1b results in the use of four LUTs�a less than optimal solution. While not ultimately efficient, this implementation functions correctly, albeit a bit slow. Although the machine-generated schematic for the design isn�t as clear as one created by a human, it�s far more understandable than digging through a text-based netlist to figure out the functionality�not a pleasant experience.
So, is there a better way to build this design? You bet! The inferred latch appears because the VHDL source didn�t specify all possible conditions. Adding a default assignment to the bottom of the nested-IF statement (see Fig 1b) results in a more efficient result (Fig 3). The latch disappears and the technology view is greatly simplified. Adding the default assignment reduces the resource requirements from four LUTs down to only two�a near optimal solution.
architecture BEHAV of case_mux is begin process (SEL, A, B, C) begin case SEL is when "00" -> if (SEL = "00") then MUX <= A; elseif (SEL = "01") then MUX <= B; elseif (SEL = "10") then MUX <= C; end if; end process; end BEHAV;
Coding style also affects the efficiency of the resulting logic. Generally, CASE statements tend to be more efficient than nested-IF statements for multiplexer functions. Fig 4 shows the VHDL code fragment implementing the 3:1 multiplexer using a CASE construct. Note the default assignment at the end of the statement using the OTHERS keyword.
Using 3-state buffers
These seemingly minor points of style illustrate a common misconception among design novices. They assume that a logic synthesizer and optimizer always find the best implementation. In fact, much of the result depends on the initial source code, which is especially true with arithmetic functions, where various carry-look-ahead techniques result in different speed-vs-area implementations. This column�s multiplexer example demonstrates this idea. Note that all the implementations built so far (Figs 2 and 3) use logic gates to construct the multiplexer. The Xilinx XC4000E family also has on-chip 3-state buffers intended primarily to implement bidirectional data buses. In reality, a bidirectional bus is nothing more than a multiplexer structure. However, most synthesis tools won�t even consider a 3-state buffer implementation without some source-code changes.The VHDL fragment in Fig 5 causes the logic-synthesis software to build a 3-state buffer multiplexer (Fig 6). Depending on the state of the SEL inputs, the MUX output either drives the values on A, B, or C, or the output is in the high-impedance state. This particular implementation is available only for devices containing internal 3-state buffers such as the Xilinx XC4000 family and the Lucent ORCA family.
General HDL tips
Based on the earlier examples, keep in mind a few general tips for HDL designs:
- Know your synthesis tool, its capabilities and limitations.
- A bad design won�t get better after running it through logic synthesis. Synthesis provides an efficient means to enter a design, whether it�s good or bad. Spaghetti code in any programing language is still spaghetti code after a compiler is finished with it.
- Designing with HDLs is an ongoing learning process. As you gain experience, your designs will be faster and consume less logic. Don�t be afraid to try trial implementations to understand how a tool responds. Also look at other people�s code for ideas.
- Investigate how your coding affects the final implementation. Schematic viewers are helpful. Without them, you must reconstruct the design�s structure from a text-based netlist.
- Always include a default assignment in all IF and CASE statements, even if you�re sure they cover all conditions. Without a default assignment, a synthesis tool can generate inferred latches. Watch for warnings from the software.
- It�s generally better to code multiplexers using a CASE construct rather than a nested-IF construct.
- Each programmable-logic family has unique features. Some special methods might be required to use these features, such as those needed to build a multiplexer using 3-state buffers.