Return to previous page Advance to next page
VHDL Reference Guide
Chapter 2: Design Descriptions

Architecture

Architecture, which determines the implementation of an entity, can range in abstraction from an algorithm (a set of sequential statements within a process) to a structural netlist (a set of component instantiations).

The syntax follows.

architecture architecture_name of entity_name is
{ block_declarative_item }
begin
{ concurrent_statement }
end [ architecture_name ] ;

The following example shows a description for a 3-bit counter that contains an entity specification and an architecture statement.

The following figure shows a schematic of the previous example.

Figure 2.1 3-Bit Counter Synthesized Circuit

Note: In an architecture, you must not give constants or signals the same name as any of the entity's ports in the entity specification.

If you declare a constant or signal with a port's name, the new declaration hides that port name. If the new declaration lies directly in the architecture declaration (as shown in the following example) and not in an inner block, Foundation Express reports an error.

entity X is 
port(SIG, CONST: in BIT;
OUT1, OUT2: out BIT);
end X;

architecture EXAMPLE of X is
signal SIG : BIT;
constant CONST: BIT := '1';
begin
...
end EXAMPLE;

The error messages generated for the previous example follow.

  signal   SIG  : BIT;
^
Error: (VHDL-1872) line 13
Illegal redeclaration of SIG.

constant CONST: BIT := '1';
^
Error: (VHDL-1872) line 14
Illegal redeclaration of CONST.

Declarations

An architecture consists of a declaration section where you declare the following.

Components

If your design consists only of VHDL entity statements, every component declaration in the architecture or package statement has to correspond to an entity.

Components declared in an architecture are local to that architecture.

The syntax follows.

component identifier
[ generic( generic_declarations ); ]
[ port( port_declarations ); ]
end component ;

The following example shows a simple component declaration statement.

component AND2
port(I1, I2: in BIT;
O1: out BIT);
end component;

The following example shows a component declaration statement that uses a generic parameter.

component ADD
generic(N: POSITIVE);

port(X, Y:   in  BIT_VECTOR(N-1 downto 0);
Z: out BIT_VECTOR(N-1 downto 0);
CARRY: out BIT);
end component;

The component declaration makes a design entity (AND2 in the example of the 2-input AND gate and ADD in the example of the N-bit adder) usable within an architecture. You must declare a component in an architecture or package before you can instantiate it.

Sources of Components

A declared component can come from the following.

Consistency of Component Ports

Foundation Express checks for consistency among its VHDL entities. For other entities, the port names are taken from the original design description as follows.

The bit widths of each port must also match.

Component Instantiation Statement

You use a component instantiation statement to define a design hierarchy or build a netlist in VHDL. A netlist is a structural description of a design.

To form a netlist, use component instantiation statements to instantiate and connect components. A component instantiation statement creates a new level of design hierarchy.

The syntax of the component instantiation statement follows.

instance_name : component_name 
[ generic map (
    generic_name => expression
    { , generic_name => expression }
) ]
port map (
    [ port_name => ] expression
    { , [ port_name => ] expression }
);

Foundation Express uses the following two rules to select which entity and architecture to associate with a component instantiation.

Mapping Generic Values

When you instantiate a component with generics, you can map generics to values. A generic without a default value must be instantiated with a generic map value.

For example, a four-bit instantiation of the component ADD in the following example might use the following generic map.

U1:  ADD generic map (N => 4) 
port map (X, Y, Z, CARRY...);

Mapping Port Connections

The port map maps component ports to actual signals.

Use named or positional association to specify port connections in component instantiation statements, as follows.

The first example that follows shows named and positional association for the U5 component instantiation statement in the second example.

EU5: or2 port map (O => n6, I1 => n3, I2 => n1);
-- Named association

U5: or2 port map (n3, n1, n6);
-- Positional association

Note: When you use positional association, the instantiated port expressions (signals) must be in the same order as the ports in the component declaration statement.

The following example shows a structural netlist description for the COUNTER3 design entity.

architecture STRUCTURE of COUNTER3 is
component DFF
port(CLK, DATA: in BIT;
Q: out BIT);
end component;
component AND2
port(I1, I2: in BIT;
O: out BIT);
end component;
component OR2
port(I1, I2: in BIT;
O: out BIT);
end component;
component NAND2
port(I1, I2: in BIT;
O: out BIT);
end component;
component XNOR2
port(I1, I2: in BIT;
O: out BIT);
end component;
component INV
port(I: in BIT;
O: out BIT);
end component;

  signal N1, N2, N3, N4, N5, N6, N7, N8, N9: BIT;

begin
u1: DFF port map(CLK, N1, N2);
u2: DFF port map(CLK, N5, N3);
u3: DFF port map(CLK, N9, N4);
u4: INV port map(N2, N1);
u5: OR2 port map(N3, N1, N6);
u6: NAND2 port map(N1, N3, N7);
u7: NAND2 port map(N6, N7, N5);
u8: XNOR2 port map(N8, N4, N9);
u9: NAND2 port map(N2, N3, N8);
COUNT(0) <= N2;
COUNT(1) <= N3;
COUNT(2) <= N4;
end STRUCTURE;

Concurrent Statements

Each concurrent statement in an architecture defines a unit of computation that does the following.

Concurrent statements all compute their values at the same time. Although the order of concurrent statements has no effect on the order in which Foundation Express executes them, concurrent statements coordinate their processing by communicating with each other through signals.

The five kinds of concurrent statements follow.

Concurrent statements are described further in the “Concurrent Statements” chapter.

Constant Declarations

Constant declarations create named values of a given type. The value of a constant can be read but not changed.

Constant declarations are allowed in architectures, packages, entities, blocks, processes, and subprograms. Constants declared in an architecture are local to that architecture. An example of constant declarations follows.

constant WIDTH: INTEGER := 8;
constant X : NEW_BIT := 'X';

You can use constants in expressions, as described in the “Identifiers” section and“Literals” section of the “Expressions” chapter and as source values in assignment statements, as described in the “Assignment Statements and Targets” section of the “Sequential Statements” chapter.

Processes

A process, which is declared within an architecture, is a concurrent statement. But it is made up of sequentially executed statements that define algorithms. The sequential statements can be any of the following, all of which are discussed in the “Sequential Statements” chapter.

Processes, like all other concurrent statements, read and write signals and the values of interface ports to communicate with the rest of the architecture and with the enclosing system.

Processes are unique in that they behave like concurrent statements to the rest of the design, but they are internally sequential. In addition, only processes can define variables to hold intermediate values in a sequence of computations.

Because the statements in a process are sequentially executed, several constructs are provided to control the order of execution, such as if and loop statements.

Variable Declarations

Variable declarations define a named value of a given type. An example of variable declarations follows.

variable A, B: BIT;
variable INIT: NEW_BIT;

You can use variables in expressions, as described in the “Expressions” chapter. You assign values to variables by using variable assignment statements, as described in the “Variable Assignment Statements” section of the “Sequential Statements” chapter.

Foundation Express does not support variable initialization. If you try to initialize a variable, Foundation Express generates the following message.

Warning: Initial values for signals are not supported for synthesis. They are ignored on line %n (VHDL-2022)

Note: Variables are declared and used only in processes and subprograms, because processes and subprograms cannot declare signals for internal use.

Signal Declarations

Signals connect the separate concurrent statements of an architecture to each other, and to other parts of a design, through interface ports.

Signal declarations create new named signals (wires) of a given type. Signals can be given default (initial) values, but these initial values are ignored for synthesis.

Signals with multiple drivers (signals driven by wired logic) can have associated resolution functions, as described in the “Package Body” section. An example of signal declarations follows.

signal A, B: BIT;
signal INIT: INTEGER := -1;

Note: Ports are also signals, with the restriction that out ports cannot be read, and in ports cannot be assigned a value. You create signals either with port declarations or with signal declarations. You create ports only with port declarations.

You can declare signals in architectures, entities, and blocks, and use them in processes and subprograms. Processes and subprograms cannot declare signals for internal use.

You can use signals in expressions, as described in the “Sequential Statements” chapter. Signals are assigned values by signal assignment statements, as described in the “Signal Assignment Statements” section of the “Sequential Statements” chapter.

Subprograms

Subprograms use sequential statements to define algorithms and are useful for performing repeated calculations, often in different parts of an architecture. (See the “Subprograms” section of the “Sequential Statements” chapter.) Subprograms declared in an architecture are local to that architecture.

Subprograms differ from processes in that subprograms cannot directly read or write signals from the rest of the architecture. All communication is through the subprogram's interface. Each subprogram call has its own set of interface signals.

Signal declarations create new named signals (wires) of a given type. Signals can be given default (initial) values, but these initial values are ignored for synthesis.

Signals with multiple drivers (signals driven by wired logic) can have associated resolution functions, as described in the “Resolution Functions” section of this chapter..

Subprograms also differ from component instantiation statements, in that the use of a subprogram by an entity or another subprogram does not create a new level of design hierarchy.

There are two types of subprograms, which can have zero or more parameters.

A subprogram has two parts.

When you declare a subprogram in an architecture, the program body must be in the architecture body but there is no corresponding subprogram declaration.

Subprogram Declarations

A subprogram declaration lists the names and types of its parameters and, for functions, the type of the subprogram's return value.

Procedure Declaration Syntax

The syntax of a procedure declaration follows.

procedure proc_name [(parameter_declarations)];

Function Declaration Syntax

The syntax of a function declaration follows.

function func_name [ ( parameter_declarations ) ]
    return type_name ;

Declaration Examples

The following example shows sample subprogram declarations for a function and a procedure.

type BYTE   is array (7 downto 0) of BIT;
type NIBBLE is array (3 downto 0) of BIT;

function IS_EVEN(NUM: in INTEGER) return BOOLEAN;
-- Returns TRUE if NUM is even.

procedure BYTE_TO_NIBBLES(B: in BYTE;
UPPER, LOWER: out NIBBLE);
-- Splits a BYTE into UPPER and LOWER halves.

When Foundation Express calls a subprogram, it substitutes actual parameters for the declared formal parameters. Actual parameters are the following.

An actual parameter must support the formal parameter's type and mode. For example, Foundation Express does not accept an input port as an out parameter and uses a constant only as an in actual parameter.

The following example shows some calls to the subprogram declarations from the example above.

signal INT : INTEGER;
variable EVEN : BOOLEAN;
. . .
INT <= 7;
EVEN := IS_EVEN(INT);
. . .

variable TOP, BOT: NIBBLE;
. . .
BYTE_TO_NIBBLES("00101101", TOP, BOT);

Subprogram Body

A subprogram body defines an implementation of a subprogram's algorithm.

Procedure Body Syntax

The syntax of a procedure body follows.

procedure procedure_name [ (parameter_declarations) ] is
{ subprogram_declarative_item }
begin
{ sequential_statement }
end [ procedure_name ] ;

Function Body Syntax

The syntax of a function body follows.

function function_name [  (parameter_declarations) ]
return type_name is
{ subprogram_declarative_item }
begin
{ sequential_statement }
end [ function_name ] ;

The following example shows subprogram bodies for the sample subprogram declarations for a function and a procedure.

function IS_EVEN(NUM: in INTEGER) 
return BOOLEAN is
begin
return ((NUM rem 2) = 0);
end IS_EVEN;

procedure BYTE_TO_NIBBLES(B: in BYTE;
UPPER, LOWER: out NIBBLE) is
begin
UPPER := NIBBLE(B(7 downto 4));
LOWER := NIBBLE(B(3 downto 0));
end BYTE_TO_NIBBLES;

Subprogram Overloading

You can overload subprograms which means that one or more subprograms can have the same name. Each subprogram that uses a given name must have a different parameter profile.

A parameter profile specifies a subprogram's number and type of parameters. This information determines which subprogram is called when more than one subprogram has the same name. Overloaded functions are also distinguished by the type of their return values.

The following example shows two subprograms with the same name, but different parameter profiles.

type SMALL is range 0 to 100;
type LARGE is range 0 to 10000;

function IS_ODD(NUM: SMALL) return BOOLEAN;
function IS_ODD(NUM: LARGE) return BOOLEAN;

signal A_NUMBER: SMALL;
signal B: BOOLEAN;
. . .
B <= IS_ODD(A_NUMBER); -- Will call the first
-- function above

Operator Overloading

You can overload predefined operators such as +, and, and mod. By using overloading, you can adapt predefined operators to work with your own data types.

For example, you can declare new logic types, rather than use the predefined types BIT and INTEGER. However, you cannot use predefined operators with these new types unless you overload the operators for the types.

The following example shows how some predefined operators are overloaded for a new logic type.

type NEW BIT is (`0', `1', `X');
-- New logic type

function "and"(I1, I2: in NEW_BIT) return NEW_BIT;
function "or" (I1, I2: in NEW_BIT) return NEW_BIT;
-- Declare overloaded operators for new logic type
. . .
signal A, B, C: NEW_BIT;
. . .

C <= (A and B) or C;

VHDL requires overloaded operator declarations to enclose the operator name or symbol in double quotation marks, because they are infix operators (they are used between operands). If you declared the overloaded operators without quotation marks, a VHDL tool considers them functions rather than operators.

Variable Declarations

Variable declarations define a named value of a given type.

You can use variables in expressions, as described in the “Identifiers” section and “Literals” section of the “Expressions” chapter. You assign values to variables by using variable assignment statements, as described in the “Variable Assignment” section of the “Sequential Statements” chapter.

Foundation Express does not support variable initialization. If you try to initialize a variable, Foundation Express generates the following message.

Warning: Initial values for signals are not supported for synthesis. They are ignored on line %n (VHDL-2022)

The following example shows some variable declarations.

variable A, B: BIT;
variable INIT: NEW_BIT;

Note: Variables are declared and used only in processes and subprograms, because processes and subprograms cannot declare signals for internal use.

To use these declarations in more than one entity or architecture, place them in a package, as described in the “Examples of Architectures for NAND2 Entity” section.

Type Declarations

You declare each signal with a type that determines the kind of data it carries. Types declared in an architecture are local to that architecture.

You can use type declarations in architectures, packages, entities, blocks, processes, and subprograms.

Type declarations define the name and characteristics of a type. Types and type declarations are fully described in the “Data Types” chapter. A type is a named set of values, such as the set of integers or the set (red, green, blue). An object of a given type, such as a signal, can have any value of that type.

The following example shows a type declaration for type NEW_BIT and some functions and variables of that type.

type NEW_BIT is ('0', '1', 'X');
-- New logic type

function ”and”(I1, I2: in NEW_BIT) return NEW_BIT;
function ”or” (I1, I2: in NEW_BIT) return NEW_BIT;
-- Declare overloaded operators for new logic type
. . .
signal A, B, C: NEW_BIT;
. . .

C <= (A and B) or C;

Subtype Declarations

Use subtype declarations to define the name and characteristics of a constrained subset of another type or subtype. A subtype is fully compatible with its parent type, but only over the subtype's range.

The following subtype declaration (NEW_LOGIC) is a subrange of the type declaration in the previous example.

subtype NEW_LOGIC is NEW_BIT range '0' to '1';

You can use subtype declarations wherever you use type declarations: in architectures, packages, entities, blocks, processes, and subprograms.

Examples of Architectures for NAND2 Entity

The following three examples show three different architectures for the entity NAND2. The three examples define equivalent implementations of NAND2. After optimization and synthesis, they all produce the same circuit, a 2-input NAND gate in the target technology. The architecture description style you use for this entity depends on your own preferences.

The first example shows how the entity NAND2 can be implemented by using two components from a technology library. The entity inputs A and B are connected to AND gate U0, producing an intermediate I signal. Signal I is then connected to inverter U1, producing the entity output Z.

architecture STRUCTURAL of NAND2 is
signal I: BIT;

  component AND_2    -- From a technology library
port(I1, I2: in BIT;
O1: out BIT);
end component;

  component INVERT     -- From a technology library
port(I1: in BIT;
O1: out BIT);
end component;

begin
U0: AND_2 port map (I1 => A, I2 => B, O1 => I);
U1: INVERT port map (I1 => I, O1 => Z);
end STRUCTURAL;

The following example shows how you can define the entity NAND2 by its logical function.

architecture DATAFLOW of NAND2 is
begin
Z <= A nand B;
end DATAFLOW;

The following example shows another implementation of NAND2.

architecture RTL of NAND2 is
begin
process(A, B)
begin
if (A = '1') and (B = '1') then
Z <= '0';
else
Z <= '1';
end if;
end process;
end RTL;