• No results found

Three-state buffers

In document Digital System Design With VHDL (Page 68-81)

Combinational building blocks

4.1 Three-state buffers

4.2 Decoders 58

4.3 Multiplexers 64

4.4 Priority encoder 66

4.5 Adders 69

4.6 Parity checker 72

4.7 Testbenches for combinational blocks 75

While it is possible to design all combinational (and indeed sequential) circuits in terms of logic gates, in practice this would be extremely tedious. It is far more efficient, in terms of both the designer’s time and the use of programmable logic resources, to use higher level building blocks. If we were to build systems using TTL or CMOS integrated circuits on a printed circuit board, we would look in a catalogue and choose devices to implement standard circuit functions. If we use VHDL and programmable logic we are not constrained to using just those devices in the catalogue, but we still think in terms of the same kinds of circuit functions. In this chapter we will look at a number of com-binational circuit functions. As we do so, various features of VHDL will be introduced. In addition, the IEEE dependency notation will also be introduced, allowing us to describe circuits using both graphical and textual representations.

4.1 Three-state buffers

4.1.1 Multi-valued logic

In addition to the normal Boolean logic functions, it is possible to design digital hard-ware using switches to disconnect a signal from a wire. For instance, we can connect

54 Combinational building blocks

the outputs of several gates together, through switches, such that only one output is connected to the common wire at a time. This same functionality could be achieved using conventional logic, but would probably require a greater number of transistors.

The IEEE symbol for a three-state buffer is shown in Figure 4.1. The symbol ‘1’ shows the device is a buffer. ‘EN’ is the symbol for an output enable and the inverted equilat-eral triangle indicates a three-state output.

When a switched gate is disconnected, it is usual to speak of the output of the entire block, gate and switch, as being in a ‘high-impedance’ state. This state must be included in the algebra used to define the functionality of gates. We have, so far, used the VHDL type BIT to describe signals. Such signals can take the values of ‘0’

and ‘1’. If we are going to use the high-impedance state, BIT is no longer adequate to represent logic signal values. We can define a new type to represent logic signals in VHDL:

type tri is ('0', '1', 'Z');

Hence we can define signals and ports to be of this type, instead of being of type BIT:

signal a, b, c : tri;

It would obviously now be desirable to be able to use signals of type tri in exactly the same way as signals of type BIT. In other words, we want to be able to write VHDL statements of the form:

a <= '0' and '1';

b <= a or c after 5 NS;

Thus we need an and operator described by the following truth table. (This assumes that a high-impedance input – a floating input – tends to be pulled to the same value as the other input of an AND gate. This is a modelling decision that may or may not be realistic.) The first row and first column represent the two inputs to the function. The nine elements in the body of the table are the outputs.

AND 0 1 Z

0 0 0 0

1 0 1 1

Z 0 1 1

1 EN

Figure 4.1 Three-state buffer.

Three-state buffers 55

In VHDL, functions and operators can be overloaded. For example, the and oper-ator normally takes two operands of type bit and returns the Boolean AND of the two operands. We can write a new AND operator to take two operands of type tri and return the values shown in the truth table. The syntax of this function will become clear later.

function "and" (Left, Right: tri) return tri is type tri_array is array (tri, tri) of tri;

constant and_table : tri_array := (('0', '0', '0'), ('0', '1', '1'), ('0', '1', '1'));

begin

return and_table(Left, Right);

end function "and";

The VHDL compiler can work out which is the correct version of the operator to use by the types of the operands. If we tried to AND together a signal of type BIT and a sig-nal of type tri, the compilation would fail because such an operator has not been defined. We could equally write an and operator that implements what is normally considered to be an or operator. This can easily render a piece of VHDL code incom-prehensible. Extreme care should be taken with overloading of operators.

4.1.2 Standard logic package

Having defined a new type with values ‘0’, ‘1’ and ‘Z’, we would have to write VHDL functions for the various logical operations. Moreover, we might wonder whether three states are sufficient for everything we might wish to model. IEEE standard 1164 defines an enumerated type with nine values:

‘U’ Uninitialized

‘X’ Forcing (i.e. strong) unknown

‘0’ Forcing 0

‘1’ Forcing 1

‘Z’ High impedance

‘W’ Weak unknown

‘L’ Weak 0

‘H’ Weak 1

‘–’ Don’t care

The standard logic type is defined by:

type std_ulogic is ('U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '–');

The and function for std_ulogic is given by the following truth table. As before, the two inputs are given by the first row and column.

56 Combinational building blocks

U X 0 1 Z W L H

U U U 0 U U U 0 U U

X U X 0 X X X 0 X X

0 0 0 0 0 0 0 0 0 0

1 U X 0 1 X X 0 1 X

Z U X 0 X X X 0 X X

W U X 0 X X X 0 X X

L 0 0 0 0 0 0 0 0 0

H U X 0 1 X X 0 1 X

U X 0 X X X 0 X X

If we write a model using signals of type BIT or std_ulogic, we must ensure that two models do not attempt to put a value onto the same signal. In VHDL terms, a signal may have one or more sources. A source may be an out, inout or buffer port of an instantiated component or a driver. In simple terms, a driver is the right-hand side of a signal assignment. The one occasion when we do try to connect two or more outputs together is when we use three-state buffers. We still have to be careful that no more than one output generates a logic 1 or 0 and the rest of the outputs are in the high-impedance state, but we want the simulator to tell us if there is a design mis-take. This cannot be done with std_ulogic – a VHDL simulator does not treat ‘Z’

as a special case.

The IEEE 1164 standard defines std_logic, which allows more than one output to be connected to the same signal. Std_logic is defined as a subtype of std_ulogic, for which a resolution function is declared. The resolved function defines the state of a signal if, for example, a ‘Z’ and a ‘1’ are driven onto the same sig-nal. Because VHDL is strongly typed, operations involving two or more types must be explicitly defined. A subtype may, however, be used in place of the type from which it is derived, without causing an error.

subtype std_logic is resolved std_ulogic;

The resolution function is defined by the following truth table.

U X 0 1 Z W L H

U U U U U U U U U U

X U X X X X X X X X

0 U X 0 X 0 0 0 0 X

1 U X X 1 1 1 1 1 X

Z U X 0 1 Z W L H X

W U X 0 1 W W W W X

L U X 0 1 L W L W X

H U X 0 1 H W W H X

U X X X X X X X X

Thus a ‘1’ and a ‘0’ driving the same signal would cause that signal to take the value ‘X’.

Three-state buffers 57

1In fact, this is exactly what was done in the first edition of this book.

Ideally, we should use std_ulogic for all signals unless we intend that any con-tention should be resolved. If we were to do this, the simulator would immediately tell us (by halting) if we were erroneously trying to force two conflicting values onto the same piece of wire.1In practice, however, some synthesis tools have difficulties with std_ulogic. The use of std_logic now seems to be the accepted industry stand-ard, so in the rest of this book we will use std_logic as the types of all Boolean signals. Contention can be recognized by the unexpected appearance of ‘X’ values in a simulation.

The various standard logic types and the functions needed to use them are gath-ered together in a package. Packages are described in more detail later. It is suffi-cient to know that a package is a separately compiled set of functions and types.

This particular package is kept separately from the working library in a library called IEEE. This may translate to a directory somewhere on the system. Therefore, every VHDL model that uses the standard logic package must be prefixed with the lines:

library IEEE;

use IEEE.std_logic_1164.all;

In general these lines should appear before each entity declaration and will apply to any architectures declared for that entity. If more than one entity declaration appears in a file (for instance, of a model and of its testbench), the library and use statements must appear before each entity. In other words, VHDL scope rules apply to design units and not to the files in which those design units are declared.

4.1.3 When . . . else statement

The behaviour of a three-state buffer can be described verbally as ‘when the enable sig-nal is asserted connect the output to the input, else let the output float’. This statement cannot be implemented using standard logic gates. VHDL has a number of program-ming constructs to perform this task. One is as follows.

library IEEE;

use IEEE.std_logic_1164.all;

entity three_state is

port (a, enable : in std_logic;

z : out std_logic);

end entity three_state;

architecture when_else of three_state is begin

z <= a when enable = '1' else 'Z';

end architecture when_else;

58 Combinational building blocks

BIN/1-OF-4 1

2

1 2 3 4

Figure 4.2 2 to 4 decoder.

If we wish to model the delay through the buffer, the when statement is changed as follows:

architecture after_when_else of three_state is begin

z <= a after 4 NS when enable = '1' else 'Z';

end architecture after_when_else;

4.2 Decoders

4.2.1 2 to 4 decoder

A decoder converts data that has previously been encoded into some other form. For example, n bits can represent 2ndistinct values. The truth table for a 2 to 4 decoder is given below.

Inputs Outputs

A1 A0 Z3 Z2 Z1 Z0

0 0 0 0 0 1

0 1 0 0 1 0

1 0 0 1 0 0

1 1 1 0 0 0

The IEEE symbol for a 2 to 4 decoder is shown in Figure 4.2. BIN/1-OF-4 indicates a binary decoder in which one of four outputs will be asserted. The numbers give the

‘weight’ of each input or output.

We could choose to treat each of the inputs and outputs separately, but as they are obviously related, it makes sense to treat the input and output as two vectors of size 2 and 4 respectively. Vectors can be described using an array of Boolean signals:

type std_logic_vector is array (NATURAL range <>) of std_logic;

NATURAL is a predefined subtype with integer values from 0 to the maximum integer value. range <> means an undefined range. Thus std_logic_vector can be used to represent a logic vector of any size, subject to the constraints of the compiler.

Decoders 59

The 2 to 4 decoder can be modelled using a when . . . else statement:

library IEEE;

use IEEE.std_logic_1164.all;

entity decoder is

port (a : in std_logic_vector(1 downto 0);

z : out std_logic_vector(3 downto 0));

end entity decoder;

architecture when_else of decoder is begin

z <= "0001" when a = "00" else

"0010" when a = "01" else

"0100" when a = "10" else

"1000" when a = "11" else

"XXXX";

end architecture when_else;

The std_logic_vectors are declared to have a range of an integer value downto zero. It is also possible to declare a range of zero to an integer value. In this example, either form would be equally valid. Integers, however, are normally represented such that the most significant bit is on the left. As can be seen from the values of a in the when statement, using downto rather than to allows us to represent integer values in the usual way. The value of a vector is placed within double quotation marks ("), unlike that of a single bit.

In this example, there are four when . . . else clauses. Each condition (i.e. each value of a) is tested in turn until a condition is found to be true. If none of the conditions is true, i.e. if one bit of a is neither 1 nor 0, the value following the final else, corres-ponding to all bits being unknown, is assigned to z. The final else can be omitted:

z <= "0001" when a = "00" else

"0010" when a = "01" else

"0100" when a = "10" else

"1000" when a = "11";

If the final else is omitted, z continues to take the last value assigned to it. In VHDL, a signal takes a value until a new value is assigned. This may be interpreted as zholding its value in a latch. This is equivalent to writing:

z <= "0001" when a = "00" else

"0010" when a = "01" else

"0100" when a = "10" else

"1000" when a = "11" else unaffected;

An almost equivalent form is:

z <= "0001" when a = "00" else

"0010" when a = "01" else

60 Combinational building blocks

"0100" when a = "10" else

"1000" when a = "11" else z;

The last version differs from the first two in that any pending assignments to z are thrown away. As we will see in Chapter 6, it cannot be used with the entity declar-ation shown. The interpretdeclar-ation of such statements in hardware terms will be described in detail in Chapter 9 on synthesis, but it is sufficient to note that assigning a signal to itself or not assigning a new value can be interpreted as meaning that a memory elem-ent exists. Moreover, as will be described in Chapter 12 on asynchronous design, this memory element is likely to be poorly implemented. Therefore a when . . . else statement should normally include the final else clause.

The expression in each when clause must resolve to a Boolean true or false. In the examples we have simply tested one value of a. We can write more complex logical expressions:

z <= "0001" when (a = "00" and (en = '1' or inhibit = '0')) else . . .

4.2.2 With . . . select statement

An alternative to the when . . . else statement is the with . . . select state-ment. Another model of the 2 to 4 decoder is shown below.

architecture with_select of decoder is begin

with a select

z <= "0001" when "00",

"0010" when "01",

"0100" when "10",

"1000" when "11",

"XXXX" when others;

end architecture with_select;

At first glance this appears very similar to the when . . . else statement, but there are important differences. Each clause of a when . . . else statement is interpreted in turn until one expression evaluates to ‘true’ or, failing that, the final else is chosen.

In a with . . . select statement all the alternatives are checked simultaneously to find a matching pattern. Therefore, the with . . . select must cover all possible values of the selector. Being of type std_logic_vector, the bits of a can take more than the values ‘0’ and ‘1’, so the when others clause must be included. If the when othersline is omitted, compilation will fail. Equally, the same pattern must not be included in more than one branch. Further, the patterns in the branches must be constants – the patterns must be determined when the VHDL is compiled, not dynam-ically in the course of a simulation.

If more than one pattern should give the same output, the patterns can be listed. For example, the following model describes a seven-segment decoder that displays the

Decoders 61

0001

0000 0010 0011 0100 0101

0111

0110 1000 1001 1010 Others

Bit ordering 6

4 0 1 5 3 2

Figure 4.3 Seven-segment display.

digits ‘0’ to ‘9’. If the bit patterns corresponding to decimal values ‘10’ to ‘15’ are fed into the decoder, an ‘E’ (for ‘Error’) is displayed. If the inputs contain X’s or other invalid values, the display is blanked. These patterns are shown in Figure 4.3.

library IEEE;

use IEEE.std_logic_1164.all;

entity seven_seg is

port (a : in std_logic_vector(3 downto 0);

z : out std_logic_vector(6 downto 0));

end entity seven_seg;

architecture with_select of seven_seg is begin

with a select

z <= "1110111" when "0000",

"0010010" when "0001",

"1011101" when "0010",

"1011011" when "0011",

"0111010" when "0100",

"1101011" when "0101",

"1101111" when "0110",

"1010010" when "0111",

"1111111" when "1000",

"1111011" when "1001",

"1101101" when "1010"|"1011"|"1100"|

"1101"|"1110"|"1111",

"0000000" when others;

end architecture with_select;

62 Combinational building blocks

4.2.3 n to 2ndecoder – shift operators

We have seen two ways to describe a 2 to 4 decoder. The same structures could eas-ily be adapted to model a 3 to 8 decoder or a 4 to 16 decoder. Although these devices are clearly more complex than the 2 to 4 decoder, conceptually there is little differ-ence. It would be convenient to have a general n to 2ndecoder that could be described once but used for any application. We saw in the previous chapter that generics can be used to pass parameters, such as delays, to VHDL models. We can similarly use a generic to define the size of a structure. In the entity declaration below, the generic nis declared to be of type POSITIVE. POSITIVE is a predefined sub-type of INTEGER that can take values in the range 1 to the maximum integer value.

If we tried to create a decoder with n equal to 0 or to a negative number, we would get a compilation error. Thus the strong typing of VHDL can be used to ensure we do not get impossible hardware models.

library IEEE;

use IEEE.std_logic_1164.all;

entity decoder is

generic (n : POSITIVE);

port (a : in std_logic_vector(n-1 downto 0);

z : out std_logic_vector(2**n-1 downto 0));

end entity decoder;

ais now defined to be an n-bit vector and z is defined to be a 2n-bit vector. ‘**’ is the power operator and has a higher precedence than other arithmetic operators (which is why 2**n-1 is interpreted as 2n 1 and not 2n–1). We could have given a default value to n in the generic clause. Whether or not a default value is supplied, n must be defined before the decoder can be simulated or synthesized.

The n-bit decoder will have to be written in a different way from the 2-bit decoder. We have noted that the with . . . select construct must use constants.

We cannot write a list of 2n constants because we do not know the size of n.

Similarly we do not know how many when . . . else clauses to write. Looking at the values assigned to z, however, reveals another pattern. The value is always

‘00 . . . 01’ rotated left by the number of places given by the decimal value of a. We can declare a vector of length 2nwith all bits set to ‘0’ other than bit ‘0’ with a constant declaration:

constant z_out : std_logic_vector(2**n-1 downto 0) :=

(0 => '1', others => '0');

A constant is declared in the same way as a signal, but (of course) its value can never be changed. The value of the constant is given after the ‘:=’ assignment. In this example, an aggregate is used to define the initial value. An aggregate consists of a set of value expressions. In this example, bit 0 is set to ‘1’ and all the others are set to ‘0’.

VHDL has six shift operators: sll, sla, rol, srl, sra, and ror. The difference between these operators is shown in Figure 4.4.

Decoders 63

sII

‘0’

sIa

roI

‘0’

srI

sra

ror

Figure 4.4 VHDL shift operators.

We need to be very careful how we write the code because of VHDL’s strong typing.

These operators are defined, by default, to shift a BIT_VECTOR by an integer number of places. We want to shift a std_logic_vector by a number of places given by the integer interpretation of another std_logic_vector. Therefore, it would be easier to declare z_out as a BIT_VECTOR, to convert a to an INTEGER and to con-vert the final result to a std_logic_vector. This last conversion can be done by a function in the std_logic_1164 package. The other conversion function is not, however, provided. To do this we need to use another package, numeric_std, that provides a set of numeric operators for vectors of std_logic – but not std_logic_vectors! Because vectors of bits can be interpreted to be either signed (two’s complement) or unsigned integers, we need to distinguish the operations per-formed on such vectors. Therefore the numeric_std package defines two new types:

signedand unsigned. VHDL’s strong typing means that we cannot mix signed, unsignedand std_logic_vector by accident, but because all three types con-sist of arrays of std_logic, we can explicitly convert from one to the other using statements of the kind

x <= unsigned(y);

y <= std_logic_vector(x);

y <= std_logic_vector(x);

In document Digital System Design With VHDL (Page 68-81)