Over on the right is an excerpt from the UART components package. The code sample shows the VHDL description of a general purpose register used widely in the serial port example. Without having to do a step by step analysis of all the code, this VHDL entity demonstrates most of the important VHDL concepts.
Overall the entity is designed to be a representation of a typical TTL logic function, the sort of thing you might find in a small plastic dual in line package. In terms of it being a direct representation of any specific device, there is no direct equivalent. One of the benefits of VHDL, is that you can get exactly what you want.
From the perspective of this register module, VHDL allows the specification of generics. In the entity declaration at the top you'll see each of the input/output signal descriptions listed. Clearly, if a full data bus enters a module, it is most convenient to describe the group of signals that comprise the bus as a single construct. This can be done in a fixed way, but in this case generics allow the width of the bus to be specified when the entity is instantiated. By default, here, the bus width is eight bits, but this can be overridden to any convenient width.
Entities are separate from their architectural descriptions. In this case a single architecture is described, but it is possible to describe multiple architectures, a choice from which can be specified on instantiation. In normal circumstances this capability is rarely used but is of benefit when attempting to simulate accurate, real world, timings from either a semiconductor technology, or a specific programmable logic device.
The first part of the architecture body shows the declaration of a local signal. In C++, this might be considered a private member variable. Whilst VHDL does not really support inheritance and therefore omits any equivalent to the protected keyword, it does have a protection model.
Notably the generic declaration in the entity, is available throughout the architecture body. Inside the architecture body, and by default any statements that are made, are described as "concurrent" statements. Such statements are purely asynchronous combinatorial logic. If any logic is described as a concurrent statement, then inputs will immediately propagate to the output.
Although it is not shown here, VHDL offers the generate keyword, to allow multiple architectural instantiations to be made as concurrent statements. This might typically be used when connecting a scalable bus to an externally referenced entity, where each entity processes an individual bit on the bus. In general terms, because concurrent statements represent combinatorial logic, they do not allow for the maintenance of state which would imply sequential logic. They are commonly used for implementing Tri-State buffers, multiplexers and demultiplexers and of course just connecting up referenced architectural blocks or entities.
17.architectureGenReg_A1 ofGenReg is
36.Buf <=Buf +1;
40.Buf <=Buf srl1;
51.-- Tristate outputs controlled by OEn
At the heart of the VHDL language, is the process statement. A process usually, but not always, infers some kind of memory in the form of flip flops or transparent latches. Unlike languages like ABEL, flip flops and latches are never explicitly described in VHDL. Their presence is always inferred. A process does not have to infer state. Critical then, to the procedure of describing a process is the "sensitivity list", which is seen immediately after the actual process statement.
The process can be considered normally to be idle, unless there is activity of any kind on a member of the sensitivity list, whereupon the process will begin to execute. As can be seen in the first process of the example the sensitivity list contains the clock signal and the reset signal (AClr). This essentially says if the clock or reset signal changes, then the contained code will be executed. In the second process, which implements a Tri-State buffer, concurrent statements have been written, but they are technically not concurrent statements because they exist inside a process statement. In this case all of the potential inputs to the process are described in the sensitivity list, so there is no inferred state to maintain.
Some say that this is the most complicated aspect of VHDL, but to me it seems as though this is VHDL's main strength. In some cases actually typing VHDL can be long winded. An example would be the repetitious nature of entity and component declarations. On the other side of the coin, if you're familiar with creating a thread in C++, the process statement just seems such a natural way to manage parallel computation. I wonder why it's not caught on more widely. The core difference with, say, C++ is that any variable declaration is inherently registered. In VHDL signals (the equivalent to a C++ variable) are never registered unless one is specifically inferred. Even then it's only an inference.
The only important thing to remember is that within the process the compiler can only see signals when items on the sensitivity list change. If, as a programmer, one makes an assignment of a signal not on the sensitivity list, the compiler knows that it must remember that signal assignment, and therefore must infer a flip flop or a transparent latch.
In most other respects VHDL is very similar to any programming language which is strongly typed, like C and Pascal. You can see, above on the left, examples of type casting between std_logic_vector and unsigned, in both directions. One must be slightly more careful with types and operators than perhaps one would be with C, since some basic types do not support all of the mathematical functions. This is not a challenge to the weary, more just that base types like std_logic support a very wide variety of signal levels. In C, a single bit in, say, an unsigned long can only be '1' or '0'. The std_logic type supports these values but also 'U' (unknown), 'Z' (high impedance), '-' (don't care), 'W' (weak), there are many. Implementing base two numeric functions, when individual bits can be in so may different states is not entirely pleasurable. It is however a good reflection of the real world. VHDL is capable of these things, but from a simulation and synthesis perspective this overhead in the math operators is to all intents and purposes unnecessary.
Worthy of mention is the others keyword. It is seen in the example and is merely a means of treating a whole range of bits, in the same way. If you want to set a whole bus to zero others is a quick way to do it. Individual bits, and bit ranges can be accessed in a similar way to arrays in C, except that round brackets are used rather than square ones.
Functions or procedures can be described in VHDL. These behave in a similar manner to those of C, or Pascal. In the context of VHDL, they are very much like an inherent, inline macro language. This statement does not imply that they are syntactically impure, just that they are used to extend the basic capability of the language. Functions generally tend to produce concurrent behaviour. They eject asynchronous behaviour which can then be made synchronous by their use in a process statement or an architecture. A typical example of a function is an operator say "Plus" which implements the standard plus sign operator in a different way.
A final important distinction is that of variables and signals. I already described that a process can infer memory onto a signal. A signal, is like a piece of wire. It has no memory unless a flip flop (memory cell) output is inferred onto it. Variables are different. By direct comparison with C++ variables, at a function level (VHDL function and C++ function), they are identical. If however, one is comparing VHDL to C++ (VHDL and C++) then the equivalent of a C++ variable is a signal. In turn this means that a VHDL variable is dissimilar to a C++ variable. It is difficult to explain, but a VHDL variable is both identical to, and completely different from a C++ variable at the same time.
To try and better describe this complex situation, it is like VHDL is two languages, but it isn't preprocessed. Both languages have variables, but one calls them signals and the other calls them variables. It is not simply a different type of variable, since both signals and variables in VHDL can have the same type. Further, variables and signals can assign to each other, using separate assignment operators.
The two language paradigm, is a good one because VHDL offers the capability to describe functions and procedures. In general functions and procedures will be used as an assistant, or macro language for the main architectural part of VHDL. Whilst the architecture describes what happens on the semiconductor, the functions and procedures only describe how to build the architecture. Although there is no explicit association, variables are part of the functional or macro part of VHDL, whereas signals are part of the architectural or declarative part of VHDL. To conclude, variables only exist, as a transitory part of an architectural description. Variables can be exchanged with signals, but in a working design variables are either infinitely fast or impenetrably constant.